优化小票打印新增餐位费和打包费

This commit is contained in:
gyq
2026-01-14 09:39:37 +08:00
parent a60acb4f1a
commit 217cabcb96
7 changed files with 225 additions and 116 deletions

File diff suppressed because one or more lines are too long

View File

@@ -6,8 +6,33 @@ import os from "os";
import fs from "fs";
import { exec } from "child_process";
// ===== 核心配置:单文件缓存 =====
// 固定的缓存文件路径永远只存这1个文件
const CACHE_LOGO_PATH = path.join(app.getPath("temp"), "yinshouke_print", "cache_logo.png");
// 记录上次的Logo URL用于对比
let lastLogoUrl = "";
// 1. 初始化缓存目录(应用启动时执行)
const initLogoCacheDir = () => {
const cacheDir = path.dirname(CACHE_LOGO_PATH);
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
// 清理目录下除了cache_logo.png之外的所有文件应用启动时
if (fs.existsSync(cacheDir)) {
fs.readdirSync(cacheDir).forEach(file => {
const filePath = path.join(cacheDir, file);
if (filePath !== CACHE_LOGO_PATH) {
fs.unlinkSync(filePath);
console.log("启动清理删除历史残留Logo文件", filePath);
}
});
}
};
let win;
app.whenReady().then(() => {
initLogoCacheDir();
win = new BrowserWindow({
title: "银收客",
width: 1024,
@@ -220,6 +245,51 @@ app.whenReady().then(() => {
// });
// });
// 2. 下载/复用Logo核心函数
const getCachedLogoPath = async (logoUrl) => {
// 情况1Logo URL为空 → 用默认Logo
if (!logoUrl || !logoUrl.startsWith('http')) {
return "./logo.png";
}
// 情况2Logo URL和上次一致 → 直接用缓存文件
if (logoUrl === lastLogoUrl && fs.existsSync(CACHE_LOGO_PATH)) {
console.log("Logo未变化复用本地缓存");
return CACHE_LOGO_PATH;
}
// 情况3Logo URL变化 → 删除旧缓存,下载新的
try {
// 删除旧缓存(如果存在)
if (fs.existsSync(CACHE_LOGO_PATH)) {
fs.unlinkSync(CACHE_LOGO_PATH);
console.log("Logo已更新删除旧缓存文件");
}
// 下载新Logo到固定缓存路径
const response = await axios({
url: logoUrl,
method: "GET",
responseType: "stream",
});
await new Promise((resolve, reject) => {
const writer = fs.createWriteStream(CACHE_LOGO_PATH);
response.data.pipe(writer);
writer.on("finish", resolve);
writer.on("error", reject);
});
// 更新上次的Logo URL记录
lastLogoUrl = logoUrl;
console.log("新Logo下载完成缓存路径", CACHE_LOGO_PATH);
return CACHE_LOGO_PATH;
} catch (error) {
console.error("下载新Logo失败使用默认Logo", error);
return "./logo.png";
}
};
// 标签小票的窗口
const tagPrintWin = new BrowserWindow({
show: false,
@@ -228,7 +298,11 @@ app.whenReady().then(() => {
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
// 允许访问本地文件(关键)
webSecurity: false,
},
// 新增2禁止后台节流隐藏窗口也能加载资源
backgroundThrottling: false,
});
if (process.env.VITE_DEV_SERVER_URL) {
@@ -239,37 +313,32 @@ app.whenReady().then(() => {
}
// 接收渲染进程发送的数据
ipcMain.on("printerTagSync", (event, arg) => {
console.log(arg);
tagPrintWin.webContents.send("getParams", arg);
ipcMain.on("printerTagSync", async (event, arg) => {
console.log("接收打印参数:", arg);
const data = JSON.parse(arg);
// ===== 核心获取缓存的Logo路径 =====
const logoPath = await getCachedLogoPath(data.ticketLogo);
data.ticketLogo = logoPath; // 替换为本地缓存路径
// 传给打印窗口
tagPrintWin.webContents.send("getParams", JSON.stringify(data));
});
// 执行标签小票的打印操作
ipcMain.on("printTagStart", (event, arg) => {
// console.log(arg);
const _parmas = JSON.parse(arg);
// console.log(_parmas)
let name = _parmas.deviceName;
tagPrintWin.webContents.print({
silent: true,
deviceName: name,
pageSize: {
width: 45000,
height: 30000,
},
pageSize: { width: 45000, height: 30000 },
scaleFactor: 80,
landscape: false,
margins: {
marginType: "none",
top: 0,
bottom: 0,
left: 0,
right: 0,
},
dpi: {
horizontal: 203,
vertical: 203,
},
margins: { marginType: "none", top: 0, bottom: 0, left: 0, right: 0 },
dpi: { horizontal: 203, vertical: 203 },
// 新增:必须开启,否则图片打印不出来
printBackground: true
});
});

View File

@@ -1,80 +1,62 @@
<!--
~ Copyright (c) 2023. Author Hubert Formin <2399270194@qq.com>
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Print preview</title>
<link rel="stylesheet" href="./tag_print.css" />
</head>
<body>
<div id="app">
<div class="print_view">
<div class="ewm" id="ewm"></div>
<div class="header">
<img class="logo" :src="data.ticketLogo || './logo.png'" />
<!-- <span class="title">{{data.ticket_logo}}</span> -->
</div>
<div class="number_wrap">
<!-- <div class="num" v-if="data.outNumber">{{data.outNumber}}</div> -->
<div class="info" v-if="data.masterId">座位号:{{data.masterId}}</div>
</div>
<div class="shop_info">
<div class="name">{{data.name}}</div>
<div class="text" v-if="data.skuName">【{{data.skuName}}】</div>
</div>
<div class="time">{{data.createdAt}}</div>
<div class="tips">建议尽快享用,风味更佳 {{ data.count }}</div>
<head>
<meta charset="UTF-8" />
<title>Print preview</title>
<!-- 新增1添加CSP策略允许加载网络图片 -->
<meta http-equiv="Content-Security-Policy"
content="default-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval';" />
<link rel="stylesheet" href="./tag_print.css" />
</head>
<body>
<div id="app">
<div class="print_view">
<div class="ewm" id="ewm"></div>
<div class="header">
<!-- 新增2添加@error回退图片加载失败用默认 -->
<img class="logo" :src="data.ticketLogo + '?t=' + new Date().getTime()"
@error="e => e.target.src = './logo.png'" />
</div>
<div class="number_wrap">
<div class="info" v-if="data.masterId">座位号:{{data.masterId}}</div>
</div>
<div class="shop_info">
<div class="name">{{data.name}}</div>
<div class="text" v-if="data.skuName">【{{data.skuName}}】</div>
</div>
<div class="time">{{data.createdAt}}</div>
<div class="tips">建议尽快享用,风味更佳 {{ data.count }}</div>
</div>
<script src="./qrcode.js"></script>
<script type="module">
const { ipcRenderer } = require("electron");
import {
createApp,
ref,
onMounted,
} from "../node_modules/vue/dist/vue.esm-browser.js";
createApp({
setup() {
const data = ref({});
onMounted(() => {
ipcRenderer.on("getParams", (event, arg) => {
data.value = JSON.parse(arg);
// console.log(data.value);
let size = 46;
let qrcode = new QRCode(document.getElementById("ewm"), {
text: data.value.outNumber,
width: size,
height: size,
correctLevel: QRCode.CorrectLevel.H,
});
ipcRenderer.send(
"printTagStart",
JSON.stringify({ deviceName: data.value.deviceName })
);
// setTimeout(() => {
// ipcRenderer.send(
// "printTagStart",
// JSON.stringify({ deviceName: data.value.deviceName })
// );
// }, 100);
</div>
<script src="./qrcode.js"></script>
<script type="module">
const { ipcRenderer } = require("electron");
import { createApp, ref, onMounted } from "../node_modules/vue/dist/vue.esm-browser.js";
createApp({
setup() {
const data = ref({});
onMounted(() => {
ipcRenderer.on("getParams", (event, arg) => {
data.value = JSON.parse(arg);
let size = 46;
let qrcode = new QRCode(document.getElementById("ewm"), {
text: data.value.outNumber,
width: size,
height: size,
correctLevel: QRCode.CorrectLevel.H,
});
// 新增3延迟500ms打印给网络图片加载时间
setTimeout(() => {
ipcRenderer.send("printTagStart", JSON.stringify({ deviceName: data.value.deviceName }));
}, 500);
});
});
return { data };
},
}).mount("#app");
</script>
</body>
return {
data,
};
},
}).mount("#app");
</script>
</body>
</html>
</html>

View File

@@ -105,14 +105,14 @@ export default (data) => {
</div>
<div style="margin-top: 6px; font-size: 12px;display:flex;justify-content: space-between;">
<span>折扣</span>
<span>-${data.discountAmount}</span>
<span>-${data.discountAllAmount}</span>
</div>
<div style="margin-top: 6px;margin-bottom: 6px;width: 100%">
<hr/>
</div>
<div style="margin-top: 6px; font-size: 22px;display:flex;justify-content: space-between;">
<span>实付</span>
<span>¥${data.amount}</span>
<span>¥${data.orderAmount}</span>
</div>
<div style="margin-top: 6px;margin-bottom: 6px;width: 100%">
<hr/>

View File

@@ -163,6 +163,8 @@ export function commOrderPrintData(orderInfo) {
shop_name: userStore.shopInfo.shopName,
loginAccount: userStore.userInfo.name,
carts: [],
discountAllAmount: formatDecimal(+orderInfo.discountAllAmount),
orderAmount: formatDecimal(+orderInfo.orderAmount),
amount: formatDecimal(+orderInfo.payAmount),
originAmount: formatDecimal(+orderInfo.originAmount),
discountAmount:
@@ -191,6 +193,32 @@ export function commOrderPrintData(orderInfo) {
});
});
if (orderInfo.seatAmount > 0) {
console.log('有餐位费', orderInfo.seatAmount);
data.carts.push({
categoryId: '',
name: '餐位费',
number: orderInfo.seatNum,
skuName: '',
salePrice: formatDecimal(orderInfo.seatAmount / orderInfo.seatNum),
totalAmount: orderInfo.seatAmount,
proGroupInfo: "",
})
}
if (orderInfo.packFee > 0) {
data.carts.push({
categoryId: '',
name: '打包费',
number: '',
skuName: '',
salePrice: '',
totalAmount: orderInfo.packFee,
proGroupInfo: "",
})
}
return data;
}

View File

@@ -184,12 +184,16 @@ const printData = reactive({
amount: "10.00",
originAmount: '10.00',
discountAmount: "0.00",
discountAllAmount: 0,
orderAmount: 10,
discount: 0,
remark: "给我多放点辣椒,谢谢老板",
orderInfo: {
masterId: "",
orderNo: "202404021023542223445",
orderNum: '12'
orderNum: '12',
discountAllAmount: 0,
orderAmount: 10,
},
deviceName: "",
createdAt: "2024-04-02 10:15",

View File

@@ -45,9 +45,9 @@
<div>{{ item.productName }}</div>
<div v-if="item.skuName">规格{{ item.skuName }}</div>
</div>
<div class="item">{{ formatDecimal(item.price) }}</div>
<div class="item">{{ formatDecimal(+item.price) }}</div>
<div class="item">{{ item.num }}</div>
<div class="item">{{ formatDecimal(item.payAmount) }}</div>
<div class="item">{{ formatDecimal(+item.payAmount) }}</div>
</div>
</div>
</div>
@@ -60,9 +60,7 @@
</div>
<div class="row between">
<span>折扣</span>
<span>-{{ orderInfo.status == "unpaid"
? "0.00"
: formatDecimal(orderInfo.originAmount - orderInfo.orderAmount) }}
<span>-{{ formatDecimal(+orderInfo.discountAmount) }}
</span>
</div>
<div class="line"></div>
@@ -92,6 +90,7 @@
</template>
<script setup>
import _ from 'lodash'
import { ref } from "vue";
import { usePrint } from "@/store/print.js";
import { useUser } from "@/store/user.js";
@@ -119,7 +118,7 @@ async function printHandle(type) {
case "normal":
// 打印订单小票
if (printStore.deviceNoteList.length) {
printStore.pushReceiptData(commOrderPrintData(orderInfo.value));
printStore.pushReceiptData(commOrderPrintData(originOrderInfo.value));
} else {
await orderPrint({
id: orderInfo.value.id,
@@ -130,7 +129,7 @@ async function printHandle(type) {
break;
case "label":
// 打印标签小票
printStore.labelPrint(commOrderPrintData(orderInfo.value));
printStore.labelPrint(commOrderPrintData(originOrderInfo.value));
break;
default:
break;
@@ -143,11 +142,38 @@ async function printHandle(type) {
}, 2500);
}
const originOrderInfo = ref('')
async function show(row) {
try {
showDrawer.value = true;
loading.value = true;
orderInfo.value = await getOrderByIdAjax(row.id);
originOrderInfo.value = await getOrderByIdAjax(row.id);
orderInfo.value = _.cloneDeep(originOrderInfo.value)
if (orderInfo.value.seatAmount > 0) {
orderInfo.value.cartList.push({
categoryId: '',
productName: '餐位费',
num: orderInfo.value.seatNum,
skuName: '',
price: formatDecimal(orderInfo.value.seatAmount / orderInfo.value.seatNum),
payAmount: orderInfo.value.seatAmount,
proGroupInfo: "",
})
}
if (orderInfo.value.packFee > 0) {
orderInfo.value.cartList.push({
categoryId: '',
productName: '打包费',
num: '',
skuName: '',
price: '',
payAmount: orderInfo.value.packFee,
proGroupInfo: "",
})
}
} catch (error) {
console.log(error);
}