优化订单小票打印

This commit is contained in:
gyq
2026-04-08 15:06:22 +08:00
parent 8655757dd6
commit 1985713f28
19 changed files with 782 additions and 395 deletions

View File

@@ -82,8 +82,27 @@ const dineModeFilter = t => {
return t
}
// 支付类型 主扫 main_scan
// 被扫 back_scan
// 微信小程序 wechat_mini
// 支付宝小程序 alipay_mini
// 会员支付 vip_pay
// 现金支付 cash_pay
// 挂账支付 credit_pay
const payTypeFilter = t => {
if (t === 'main_scan') return '主扫'
if (t === 'back_scan') return '被扫'
if (t === 'wechat_mini') return '微信小程序'
if (t === 'alipay_mini') return '支付宝小程序'
if (t === 'vip_pay') return '余额支付'
if (t === 'cash_pay') return '现金支付'
if (t === 'credit_pay') return '挂账支付'
return t
}
// ======================== 打印结算小票 ========================
export function printReceipt(printerIp, order) {
// console.log('Printing receipt...', JSON.stringify(order));
return new Promise((resolve) => {
const socket = new net.Socket()
const lineWidth = 48
@@ -98,10 +117,10 @@ export function printReceipt(printerIp, order) {
// ======================== 标题区域(彻底修复居中问题!) ========================
const title1 = order.shop_name || '';
let title2 = `结算单 #${order.orderInfo.orderNum}`
if (order.isBefore && !order.isGuest) {
if (order.isBefore) {
title2 = `${order.isBefore ? '预' : ''}结算单 #${order.orderInfo.orderNum}`;
}
if (order.isGuest && order.isBefore) {
if (order.isGuest) {
title2 = `客看单 #${order.orderInfo.orderNum}`;
}
@@ -145,8 +164,8 @@ export function printReceipt(printerIp, order) {
const rightPart = padRightAlign(`${seatNumText}`, 28) // 加标签更清晰,也可只显示数字
orderInfo += leftPart + rightPart + '\n'
if (!order.isBefore) {
orderInfo += padLeftAlign('结账时间:', 10) + padLeftAlign(order.createdAt || '-', 38) + '\n'
if (!order.isBefore && !order.isGuest) {
orderInfo += padLeftAlign('结账时间:', 10) + padLeftAlign(order.orderInfo.paidTime || '-', 38) + '\n'
}
orderInfo += createDivider(lineWidth, 'thin') + '\n'
@@ -157,15 +176,54 @@ export function printReceipt(printerIp, order) {
orderInfo += padRightAlign('小计', 16) + '\n'
orderInfo += createDivider(lineWidth, 'thin') + '\n'
// 处理商品列表(套餐主项显示金额,子项不显示金额 + 规格换行)
order.carts.forEach(item => {
const name = truncateByPrintWidth(item.name || '未知商品', 18)
const price = formatAmount(item.salePrice)
const num = item.number || 1
const total = formatAmount(item.totalAmount)
orderInfo += padLeftAlign(name, 18)
orderInfo += padLeftAlign(price, 8)
orderInfo += padLeftAlign(num, 6)
orderInfo += padRightAlign(total, 16) + '\n'
if (item.number > 0) {
// 套餐商品逻辑
if (item.proGroupInfo) {
// 套餐主项:正常显示 名称、单价、数量、小计
const packageName = truncateByPrintWidth(item.name || '未知套餐', 18)
orderInfo += padLeftAlign(packageName, 18)
orderInfo += padLeftAlign(formatAmount(item.salePrice), 8)
orderInfo += padLeftAlign(item.number || 1, 6)
orderInfo += padRightAlign(formatAmount(item.totalAmount), 16) + '\n'
// 套餐子项:只显示名称+规格,单价、小计全部为空
const proGroupInfo = item.proGroupInfo || []
proGroupInfo.forEach(subItem => {
const subName = truncateByPrintWidth(`>${subItem.proName || '未知商品'}`, 18)
orderInfo += padLeftAlign(subName, 18)
orderInfo += padLeftAlign('', 8) // 子项单价空
orderInfo += padLeftAlign(subItem.number || 1, 6)
orderInfo += padRightAlign('', 16) + '\n' // 子项小计空
// 子项规格换行显示
if (subItem.skuName) {
const skuText = truncateByPrintWidth(` 规格:${subItem.skuName}`, 18)
orderInfo += padLeftAlign(skuText, 18)
orderInfo += padLeftAlign('', 8)
orderInfo += padLeftAlign('', 6)
orderInfo += padRightAlign('', 16) + '\n'
}
})
} else {
// 普通商品:正常显示 + 规格换行
let productName = truncateByPrintWidth(item.name || '未知商品', 18)
orderInfo += padLeftAlign(productName, 18)
orderInfo += padLeftAlign(item.salePrice ? formatAmount(item.salePrice) : '', 8)
orderInfo += padLeftAlign(item.number || 1, 6)
orderInfo += padRightAlign(formatAmount(item.totalAmount), 16) + '\n'
// 规格单独换行
if (item.skuName) {
const skuLine = truncateByPrintWidth(` 规格:${item.skuName}`, 18)
orderInfo += padLeftAlign(skuLine, 18)
orderInfo += padLeftAlign('', 8)
orderInfo += padLeftAlign('', 6)
orderInfo += padRightAlign('', 16) + '\n'
}
}
}
})
orderInfo += createDivider(lineWidth, 'thin') + '\n'
@@ -176,14 +234,24 @@ export function printReceipt(printerIp, order) {
socket.write(iconv.encode(orderInfo, 'gbk'))
// ======================== 付款信息 ========================
const payableLine = padLeftAlign('应付', 10) + padRightAlign(formatAmount(order.originAmount), 51) + '\n'
socket.write(Buffer.from([0x1B, 0x21, 0x11])); // 放大字号
socket.write(Buffer.from([0x1B, 0x45, 0x01])); // 加粗
socket.write(iconv.encode(payableLine, 'gbk'));
socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 恢复默认字号
socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
socket.write(iconv.encode(createDivider(lineWidth, 'thin') + '\n', 'gbk'));
if (!order.isBefore) {
if (order.isBefore) {
const payableLine = padLeftAlign('应付', 10) + padRightAlign(formatAmount(order.originAmount), 51) + '\n'
socket.write(Buffer.from([0x1B, 0x21, 0x11])); // 放大字号
socket.write(Buffer.from([0x1B, 0x45, 0x01])); // 加粗
socket.write(iconv.encode(payableLine, 'gbk'));
socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 恢复默认字号
socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
socket.write(iconv.encode(createDivider(lineWidth, 'thin') + '\n', 'gbk'));
}
if (!order.isBefore && !order.isGuest) {
const payableLine = padLeftAlign('应付', 10) + padRightAlign(formatAmount(order.orderInfo.payAmount), 51) + '\n'
socket.write(Buffer.from([0x1B, 0x21, 0x11])); // 放大字号
socket.write(Buffer.from([0x1B, 0x45, 0x01])); // 加粗
socket.write(iconv.encode(payableLine, 'gbk'));
socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 恢复默认字号
socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
socket.write(iconv.encode(createDivider(lineWidth, 'thin') + '\n', 'gbk'));
const paidLine = padLeftAlign('已付', 10) + padRightAlign(formatAmount(order.orderInfo.payAmount), 51) + '\n'
socket.write(Buffer.from([0x1B, 0x21, 0x11]));
socket.write(Buffer.from([0x1B, 0x45, 0x01]));
@@ -195,6 +263,9 @@ export function printReceipt(printerIp, order) {
// ======================== 底部 ========================
let bottom = ''
if (!order.isBefore && !order.isGuest) {
bottom += padLeftAlign('支付方式:', 8) + padLeftAlign(payTypeFilter(order.orderInfo.payType) || '无', 38) + '\n'
}
bottom += padLeftAlign('操作员:', 8) + padLeftAlign(order.loginAccount || '无', 38) + '\n'
bottom += padLeftAlign('打印时间:', 10) + padLeftAlign(new Date().toLocaleString(), 38) + '\n'
bottom += padLeftAlign('订单号:', 8) + padLeftAlign(order.orderInfo.orderNo || '-', 38) + '\n'
@@ -222,8 +293,7 @@ export function printReceipt(printerIp, order) {
})
}
// ======================== 打印退菜单/退款小票 ========================
// ======================== 退款小票 ========================
export function printRefund(printerIp, order) {
console.log(JSON.stringify(order));
@@ -240,7 +310,7 @@ export function printRefund(printerIp, order) {
// ======================== 标题区域(彻底修复居中问题!) ========================
const title1 = order.shop_name || '';
let title2 = order.title || '退款单'
let title2 = '退款单';
// 核心修复:先写入空行清空打印机缓冲区,避免残留字符干扰居中起始位置
socket.write(iconv.encode('\n', 'gbk'));
@@ -308,6 +378,7 @@ export function printRefund(printerIp, order) {
}
// ======================== 付款信息 ========================
let payableLine = ''
payableLine += padLeftAlign('退款总计', 10) + padRightAlign(formatAmount(order.amount), 51) + '\n'
socket.write(Buffer.from([0x1B, 0x21, 0x11])); // 放大字号
@@ -315,6 +386,7 @@ export function printRefund(printerIp, order) {
socket.write(iconv.encode(payableLine, 'gbk'));
socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 恢复默认字号
socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
let refundInfo = ''
refundInfo += padLeftAlign('退款方式:', 10) + padLeftAlign(order.refundMethod || '无', 38) + '\n'
refundInfo += padLeftAlign('退款原因:', 10) + padLeftAlign(order.remark || '无', 38) + '\n'
@@ -350,6 +422,135 @@ export function printRefund(printerIp, order) {
})
}
// ======================== 打印退菜单 ========================
export function printRefundDish(printerIp, order) {
console.log(JSON.stringify(order));
return new Promise((resolve) => {
const socket = new net.Socket()
const lineWidth = 48
socket.setTimeout(8000)
const formatAmount = (amount) => Number(amount || 0).toFixed(2)
socket.connect(9100, printerIp, () => {
try {
setupPrinter(socket)
// ======================== 标题区域(彻底修复居中问题!) ========================
const title1 = order.shop_name || '';
let title2 = '退菜单'
// 核心修复:先写入空行清空打印机缓冲区,避免残留字符干扰居中起始位置
socket.write(iconv.encode('\n', 'gbk'));
// 步骤1重置打印机格式避免残留格式影响
socket.write(Buffer.from([0x1B, 0x40]));
// 步骤2居中对齐标题
socket.write(Buffer.from([0x1B, 0x61, 0x01])); // 1 = center
// 步骤3设置标题1的格式加粗+大号字体)
socket.write(Buffer.from([0x1B, 0x21, 0x11])); // 字号放大0x11 是常用放大值)
socket.write(Buffer.from([0x1B, 0x45, 0x01])); // 加粗
socket.write(iconv.encode(title1 + '\n', 'gbk'));
// 步骤4恢复格式打印标题2常规字体
socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 恢复默认字号
socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
socket.write(iconv.encode(title2 + '\n\n', 'gbk'));
socket.write(Buffer.from([0x1B, 0x61, 0x00])); // 0 = left
// ======================== 订单信息(修复显示+换行问题) ========================
const tableLine = padLeftAlign('桌台号:', 8) + padLeftAlign(order.orderInfo.tableName || '无', 14) + '\n'
socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 正常字号
socket.write(Buffer.from([0x1B, 0x45, 0x01])); // 加粗
socket.write(iconv.encode(tableLine, 'gbk'));
socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
let orderInfo = ''
// 修复:打印机-用餐模式 + 就餐人数 展示逻辑
const dineModeText = dineModeFilter(order.orderInfo.dineMode)
// 1. 拼接打印机+用餐模式文本,空值兜底
const printerDineText = `${order.printerName || '未知打印机'}-${dineModeText || '未知模式'}`
// 2. 截断超长文本左侧分配20宽度保证不超限
const truncatedPrinterDine = truncateByPrintWidth(printerDineText, 20)
// 3. 左侧左对齐填充20宽度保证占满分配空间不浪费空白
const leftPart = padLeftAlign(truncatedPrinterDine, 20)
// 4. 右侧就餐人数空值兜底为“无”右对齐填充28宽度20+28=48刚好行宽
const seatNumText = order.orderInfo.seatNum || '0'
const rightPart = padRightAlign(`${seatNumText}`, 28) // 加标签更清晰,也可只显示数字
if (order.carts.length > 0) {
orderInfo += leftPart + rightPart + '\n'
orderInfo += createDivider(lineWidth, 'thin') + '\n'
// ======================== 商品表格 ========================
orderInfo += padLeftAlign('品名', 18)
orderInfo += padLeftAlign('单价', 8)
orderInfo += padLeftAlign('数量', 6)
orderInfo += padRightAlign('小计', 16) + '\n'
orderInfo += createDivider(lineWidth, 'thin') + '\n'
order.carts.forEach(item => {
const name = truncateByPrintWidth(item.name || '未知商品', 18)
const price = formatAmount(item.salePrice)
const num = item.number || 1
const total = formatAmount(item.totalAmount)
orderInfo += padLeftAlign(name, 18)
orderInfo += padLeftAlign(price, 8)
orderInfo += padLeftAlign(num, 6)
orderInfo += padRightAlign(total, 16) + '\n'
})
orderInfo += createDivider(lineWidth, 'thin') + '\n'
socket.write(iconv.encode(orderInfo, 'gbk'))
}
// ======================== 付款信息 ========================
// if (order.isGuest) {
// let payableLine = ''
// payableLine += padLeftAlign('退款总计', 10) + padRightAlign(formatAmount(order.amount), 51) + '\n'
// socket.write(Buffer.from([0x1B, 0x21, 0x11])); // 放大字号
// socket.write(Buffer.from([0x1B, 0x45, 0x01])); // 加粗
// socket.write(iconv.encode(payableLine, 'gbk'));
// socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 恢复默认字号
// socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
// }
// let refundInfo = ''
// refundInfo += padLeftAlign('退款方式:', 10) + padLeftAlign(order.refundMethod || '无', 38) + '\n'
// refundInfo += padLeftAlign('退款原因:', 10) + padLeftAlign(order.remark || '无', 38) + '\n'
// socket.write(iconv.encode(refundInfo, 'gbk'));
// socket.write(iconv.encode(createDivider(lineWidth, 'thin') + '\n', 'gbk'));
// ======================== 底部 ========================
let bottom = ''
bottom += padLeftAlign('操作员:', 8) + padLeftAlign(order.loginAccount || '无', 38) + '\n'
bottom += padLeftAlign('打印时间:', 10) + padLeftAlign(new Date().toLocaleString(), 38) + '\n'
bottom += padLeftAlign('原订单号:', 10) + padLeftAlign(order.orderInfo.orderNo || '-', 38) + '\n'
bottom += createDivider(lineWidth, 'full') + '\n'
bottom += '\n\n'
bottom += '\n\n'
socket.write(iconv.encode(bottom, 'gbk'))
socket.write(Buffer.from([0x1D, 0x56, 0x00]))
setTimeout(() => {
socket.end()
resolve('success')
}, 400)
} catch (err) {
console.error(err)
socket.destroy()
resolve('error')
}
})
socket.on('error', () => resolve('error'))
socket.on('timeout', () => resolve('timeout'))
socket.on('close', () => { })
})
}
// ======================== 打印交班小票(完整版 · 补齐分类/商品数据) ========================
export function printHandoverReceipt(printerIp, data) {
return new Promise((resolve) => {
@@ -390,22 +591,23 @@ export function printHandoverReceipt(printerIp, data) {
let basicInfo = ''
// 优化:拆分当班时间和交班时间,空值兜底
basicInfo += padLeftAlign('交班时间:', 10) + padLeftAlign(data.handoverTime || '-', 36) + '\n'
basicInfo += padLeftAlign('收银员', 8) + padLeftAlign(data.staffName || '-', 36) + '\n'
basicInfo += padLeftAlign('交班周期', 10) + padLeftAlign(`${data.loginTime}-${data.handoverTime}` || '-', 36) + '\n\n'
basicInfo += padLeftAlign('交班人', 8) + padLeftAlign(data.staffName || '-', 36) + '\n'
basicInfo += padLeftAlign('交班:', 6) + padLeftAlign(`${data.loginTime} - ${data.handoverTime}` || '-', 36) + '\n\n'
// basicInfo += createDivider(lineWidth, 'thin') + '\n'
// 收入明细 - 补充会员支付/充值字段
basicInfo += padLeftAlign('当班营业总额:', 10) + padLeftAlign(formatAmount(data.handAmount), 36) + '\n'
basicInfo += padLeftAlign('当班营业总额:', 10) + padLeftAlign(formatAmount(data.turnover), 36) + '\n'
basicInfo += padLeftAlign('实际收款的支付方式', 10) + '\n'
basicInfo += padLeftAlign('现金', 10) + padRightAlign(formatAmount(data.cashAmount), 36) + '\n'
basicInfo += padLeftAlign('微信', 10) + padRightAlign(formatAmount(data.wechatAmount), 36) + '\n'
basicInfo += padLeftAlign('支付宝', 10) + padRightAlign(formatAmount(data.alipayAmount), 36) + '\n'
basicInfo += padLeftAlign('二维码收款', 10) + padRightAlign(formatAmount(data.vipPay), 36) + '\n'
basicInfo += padLeftAlign('扫码收款', 10) + padRightAlign(formatAmount(data.vipRecharge), 36) + '\n\n'
basicInfo += padLeftAlign('现金', 10) + padRightAlign(formatAmount(data.cash), 36) + '\n'
basicInfo += padLeftAlign('微信', 10) + padRightAlign(formatAmount(data.wechat), 36) + '\n'
basicInfo += padLeftAlign('支付宝', 10) + padRightAlign(formatAmount(data.alipay), 36) + '\n'
basicInfo += padLeftAlign('二维码收款', 10) + padRightAlign(formatAmount(data.selfScan), 36) + '\n'
basicInfo += padLeftAlign('扫码收款', 10) + padRightAlign(formatAmount(data.barScan), 36) + '\n'
basicInfo += padLeftAlign('充值', 10) + padRightAlign(formatAmount(data.recharge), 36) + '\n\n'
basicInfo += padLeftAlign('非实际收款的支付方式', 10) + '\n'
basicInfo += padLeftAlign('挂账', 10) + padRightAlign(formatAmount(data.creditAmount), 36) + '\n'
basicInfo += padLeftAlign('余额', 10) + padRightAlign(formatAmount(data.vipPay), 36) + '\n'
basicInfo += padLeftAlign('挂账', 10) + padRightAlign(formatAmount(data.owed), 36) + '\n'
basicInfo += padLeftAlign('余额', 10) + padRightAlign(formatAmount(data.balance), 36) + '\n'
basicInfo += createDivider(lineWidth, 'thin') + '\n'
socket.write(Buffer.from([0x1B, 0x21, 0x00])); // 正常字号
@@ -414,27 +616,27 @@ export function printHandoverReceipt(printerIp, data) {
let refundInfo = ''
refundInfo += padLeftAlign('退款/退菜', 10) + '\n'
refundInfo += padLeftAlign('退款金额', 10) + padRightAlign(formatAmount(data.refundAmount), 36) + '\n'
refundInfo += padLeftAlign('退菜数量', 10) + padRightAlign(formatAmount(data.refundAmount), 36) + '\n'
refundInfo += padLeftAlign('退菜数量', 10) + padRightAlign(formatAmount(data.returnDishCount), 36) + '\n'
refundInfo += createDivider(lineWidth, 'thin') + '\n'
socket.write(iconv.encode(refundInfo, 'gbk'))
let orderInfo = ''
const orderLabel = '订单(数量/订单总额)'
const orderValue = `${data.orderCount}/${formatAmount(data.orderAmount)}`
const orderValue = `${data.orderCount}/${formatAmount(data.orderTurnover)}`
const orderLabelWidth = lineWidth - getPrintWidth(orderValue)
orderInfo += padLeftAlign(orderLabel, orderLabelWidth) + orderValue + '\n\n'
socket.write(iconv.encode(orderInfo, 'gbk'))
// ======================== 分类数据区域 ========================
let categoryInfo = ''
if (data.categoryDataList && data.categoryDataList.length) {
if (data.categoryList && data.categoryList.length) {
categoryInfo += centerAlign('销售数据', lineWidth) + '\n'
categoryInfo += createDivider(lineWidth, 'thin') + '\n'
// 分类表头
categoryInfo += padLeftAlign('名称', 24) + padLeftAlign('数量', 12) + padRightAlign('总计', 12) + '\n'
categoryInfo += padLeftAlign('商品分类', 24) + padLeftAlign('数量', 12) + padRightAlign('总计', 12) + '\n'
// categoryInfo += createDivider(lineWidth, 'thin') + '\n'
// 分类数据行
data.categoryDataList.forEach(item => {
data.categoryList.forEach(item => {
const catName = truncateByPrintWidth(item.categoryName || '未知分类', 24)
const num = item.num || 0
const amount = formatAmount(item.amount)
@@ -445,54 +647,22 @@ export function printHandoverReceipt(printerIp, data) {
socket.write(iconv.encode(categoryInfo, 'gbk'))
// ======================== 商品数据区域 ========================
let productInfo = ''
if (data.printShop && data.productDataList && data.productDataList.length) {
productInfo += centerAlign('商品数据', lineWidth) + '\n'
productInfo += createDivider(lineWidth, 'thin') + '\n'
// 商品表头
productInfo += padLeftAlign('商品', 36) + padRightAlign('数量', 12) + '\n'
// productInfo += createDivider(lineWidth, 'thin') + '\n'
// 商品数据行
data.productDataList.forEach(item => {
const prodName = truncateByPrintWidth(item.productName || '未知商品', 36)
const num = item.num || 0
productInfo += padLeftAlign(prodName, 36) + padRightAlign(num, 12) + '\n'
})
productInfo += createDivider(lineWidth, 'thin') + '\n'
}
socket.write(iconv.encode(productInfo, 'gbk'))
// ======================== 收入明细区域(优化对齐和格式) ========================
// let incomeInfo = ''
// incomeInfo += centerAlign('【收支汇总】', lineWidth) + '\n'
// incomeInfo += createDivider(lineWidth, 'thin') + '\n'
// // 收支汇总项(补充挂账金额)
// const incomeItems = [
// { label: '快捷收款金额:', val: data.quickInAmount },
// { label: '退款金额:', val: data.refundAmount },
// { label: '总收入:', val: data.handAmount },
// { label: '挂账金额:', val: data.creditAmount },
// { label: '总订单数:', val: data.orderCount || 0 }
// ]
// incomeItems.forEach((item, index) => {
// const labelPart = padLeftAlign(item.label, 16)
// let valPart = ''
// // 总收入/总订单数加粗显示
// if (index === 2 || index === 4) {
// socket.write(Buffer.from([0x1B, 0x45, 0x01])); // 加粗
// valPart = padRightAlign(index === 4 ? item.val : formatAmount(item.val), 32)
// incomeInfo += labelPart + valPart + '\n'
// socket.write(Buffer.from([0x1B, 0x45, 0x00])); // 取消加粗
// } else {
// valPart = padRightAlign(formatAmount(item.val), 32)
// incomeInfo += labelPart + valPart + '\n'
// }
// })
// incomeInfo += createDivider(lineWidth, 'full') + '\n'
// socket.write(iconv.encode(incomeInfo, 'gbk'))
// let productInfo = ''
// if (data.detailList && data.detailList.length) {
// productInfo += centerAlign('商品数据', lineWidth) + '\n'
// productInfo += createDivider(lineWidth, 'thin') + '\n'
// // 商品表头
// productInfo += padLeftAlign('商品', 36) + padRightAlign('数量', 12) + '\n'
// // productInfo += createDivider(lineWidth, 'thin') + '\n'
// // 商品数据行
// data.detailList.forEach(item => {
// const prodName = truncateByPrintWidth(item.productName || '未知商品', 36)
// const num = item.num || 0
// productInfo += padLeftAlign(prodName, 36) + padRightAlign(num, 12) + '\n'
// })
// productInfo += createDivider(lineWidth, 'thin') + '\n'
// }
// socket.write(iconv.encode(productInfo, 'gbk'))
// ======================== 底部区域(统一结算小票风格) ========================
let bottomInfo = ''