52 Commits
1.4.3 ... gyq

Author SHA1 Message Date
gyq
14ce5cb468 版本增加1.4.26 2024-12-17 13:30:17 +08:00
gyq
456bcf83a5 隐藏排队叫号 2024-12-17 10:00:52 +08:00
gyq
4b54fdaff1 新增排队叫号功能 2024-12-12 09:33:01 +08:00
gyq
0711d4b07d 优化套餐显示、隐藏规格 2024-12-06 16:44:39 +08:00
gyq
7c27372c4d 1.新增套餐商品 2024-12-06 14:41:58 +08:00
gyq
96ab68f463 优化 2024-11-05 10:50:04 +08:00
gyq
8f42ba8189 新增动态获取标签小票 2024-11-01 10:47:57 +08:00
gyq
8935b9d2f2 优化台桌下单问题 2024-10-30 16:00:08 +08:00
gyq
773a2cf2c9 修改折扣小计计算方式 2024-10-26 11:09:27 +08:00
gyq
06a0d1d0fc 新增预发布版本 2024-10-24 11:11:48 +08:00
gyq
db8c41fd16 增加员工权限校验 2024-10-21 13:48:57 +08:00
gyq
9d73a49a06 优化打印 2024-10-17 09:16:59 +08:00
gyq
5805b1fd7c 优化长连接订单打印没有折扣的问题 2024-10-16 17:00:23 +08:00
gyq
b1272852d8 去除共享库存验证 2024-10-15 18:26:27 +08:00
gyq
45999ef022 优化 2024-10-14 14:50:20 +08:00
gyq
72cf926747 优化台桌点餐 2024-10-11 14:26:39 +08:00
gyq
cfd04625dd 版本更新 2024-09-09 10:28:17 +08:00
gyq
00c6a9a491 增加购物车添加loading 2024-09-09 09:10:39 +08:00
gyq
458b531757 版本更新 2024-09-06 14:01:55 +08:00
gyq
9fb67bd8a2 版本更新 2024-09-05 17:33:44 +08:00
gyq
e17b12687f 优化版本更新 2024-09-05 16:19:50 +08:00
gyq
34f0b306c9 版本更新 2024-09-05 09:49:34 +08:00
gyq
d57cecd91d 新增在线更新功能 2024-09-04 09:08:32 +08:00
gyq
86c8ca6472 优化退款密码 2024-08-31 10:20:25 +08:00
gyq
2d2a014bc4 优化 2024-08-28 15:13:14 +08:00
gyq
3047fe0404 版本更新,折扣打印优化 2024-08-28 09:53:27 +08:00
gyq
fbfee69b25 更新折扣优惠和代客下单 2024-08-26 18:28:24 +08:00
gyq
48a3443c5f 打包更新 2024-08-19 10:02:47 +08:00
gyq
b45793ffc9 1.新增商品编辑 2024-08-19 09:58:03 +08:00
gyq
e4a82411ba 1.增加网络判断刷新页面,防止没网无法连接ws
2.商品增加上下架、售罄、修改库存登操作
2024-08-13 16:00:13 +08:00
gyq
0f522fa9d2 版本更新 2024-08-06 15:26:22 +08:00
gyq
efb7dd3e57 优化 2024-08-06 09:16:06 +08:00
gyq
ac469cbc32 更新没有usb打印机时调用云打印机 2024-08-02 17:54:52 +08:00
gyq
f8c5c9bf59 版本更新 2024-08-02 14:56:30 +08:00
gyq
6e2e7f9719 优化标签测试打印 2024-08-02 11:22:17 +08:00
gyq
b4872fec16 打包更新 2024-08-02 10:49:46 +08:00
gyq
804b677174 更改php https 2024-08-01 15:53:19 +08:00
gyq
367b49c68a 优化 2024-08-01 14:09:19 +08:00
gyq
f393299f0f 优化小票打印 2024-07-31 18:14:23 +08:00
gyq
49cabfed21 小票全部采用本地usb打印 2024-07-30 18:04:53 +08:00
gyq
b2e450fd52 新增本地USB打印 2024-07-29 18:10:01 +08:00
魏啾
4fb34a4235 wwz 2024-07-26 10:47:50 +08:00
魏啾
fdacaab44a Merge branch 'gyq' of https://e.coding.net/g-cphe0354/pczhuomianduan/cashierdesktop into wwz 2024-07-26 10:47:24 +08:00
gyq
b33086ba04 优化更多 2024-07-26 10:17:26 +08:00
魏啾
fd6410f742 打印 2024-07-26 10:16:57 +08:00
gyq
ea0c01bb2b 更新 2024-07-25 14:13:32 +08:00
gyq
4eb5df7668 优化 2024-07-25 10:50:24 +08:00
gyq
2c58c99d1e 1.优化订单开票按钮显示 2.退出登录新增退出团购 2024-07-24 14:22:14 +08:00
gyq
9e57753323 新增订单开票 2024-07-23 18:30:16 +08:00
gyq
d2183eec37 1.交班新增选择是否打印商品销售数据 2024-07-23 10:25:20 +08:00
gyq
a282636266 1.新增订单手动退款 2.团购新增抖音团购核销 2024-07-19 16:31:40 +08:00
gyq
c155e8a805 新增抖音团购券核销 2024-07-17 18:02:01 +08:00
75 changed files with 7098 additions and 1368 deletions

View File

@@ -1,7 +1,6 @@
# 本地环境 # 本地环境
ENV = development ENV = development
# 正式ws # 正式ws
VITE_API_WSS = 'wss://cashier.sxczgkj.cn/client' VITE_API_WSS = 'wss://cashier.sxczgkj.cn/client'
@@ -11,21 +10,32 @@ VITE_API_WSS = 'wss://cashier.sxczgkj.cn/client'
# 阿伟本地ws # 阿伟本地ws
# VITE_API_WSS = 'ws://192.168.2.17:9998/client' # VITE_API_WSS = 'ws://192.168.2.17:9998/client'
# 测试 php
# VITE_API_PHP_URL = 'http://192.168.2.33:1666/index.php/api'
# 正式 php # 正式 php
VITE_API_PHP_URL = 'http://czgdoumei.sxczgkj.com/index.php/api' VITE_API_PHP_URL = 'https://czgdoumei.sxczgkj.com/index.php/api'
# 测试 php 开票
# VITE_API_KP_URL = 'http://192.168.1.13:8888/api'
# 正式 php 开票
VITE_API_KP_URL = 'https://invoice.sxczgkj.cn/api'
# 阿伟 # 阿伟
# VITE_API_URL = 'http://192.168.2.96:10587/cashier-client' # VITE_API_URL = 'http://192.168.2.96:10587/cashier-client'
# 鹏辉 # 鹏辉
# VITE_API_URL = 'http://192.168.2.41:10589/cashier-client' # VITE_API_URL = 'http://192.168.1.106:10589/cashier-client'
# 杰哥
# VITE_API_URL = 'http://192.168.1.34:10589/cashier-client'
# 测试 # 测试
# VITE_API_URL = 'https://cashier-client.sxczgkj.cn/cashier-client' VITE_API_URL = 'https://cashier-client.sxczgkj.cn/cashier-client'
# 预发布
# VITE_API_URL = 'https://pre-cashierclient.sxczgkj.cn/cashier-client'
# 张松本地
# VITE_API_URL = 'https://36z1017t45.goho.co/cashier-client'
# 正式 # 正式
VITE_API_URL = 'https://cashierclient.sxczgkj.cn/cashier-client' # VITE_API_URL = 'https://cashierclient.sxczgkj.cn/cashier-client'

View File

@@ -4,17 +4,14 @@ ENV = production
# 正式ws # 正式ws
VITE_API_WSS = 'wss://cashier.sxczgkj.cn/client' VITE_API_WSS = 'wss://cashier.sxczgkj.cn/client'
#测试ws
# VITE_API_WSS = 'wss://wxcashiertest.sxczgkj.cn/client'
# 测试 php
# VITE_API_PHP_URL = 'http://192.168.2.33:1666/index.php/api'
# 正式 php # 正式 php
VITE_API_PHP_URL = 'http://czgdoumei.sxczgkj.com/index.php/api' VITE_API_PHP_URL = 'https://czgdoumei.sxczgkj.com/index.php/api'
# 测试 # 正式 php 开票
# VITE_API_URL = 'https://cashier-client.sxczgkj.cn/cashier-client' VITE_API_KP_URL = 'https://invoice.sxczgkj.cn/api'
# 线上环境接口地址 # 线上环境接口地址
VITE_API_URL = 'https://cashierclient.sxczgkj.cn/cashier-client/' VITE_API_URL = 'https://cashierclient.sxczgkj.cn/cashier-client/'
# 预发布接口
# VITE_API_URL = 'https://pre-cashierclient.sxczgkj.cn/cashier-client/'

29
.env.test Normal file
View File

@@ -0,0 +1,29 @@
# 线上环境
ENV = test
#测试ws
VITE_API_WSS = 'wss://wxcashiertest.sxczgkj.cn/client'
# 正式ws
# VITE_API_WSS = 'wss://cashier.sxczgkj.cn/client'
# 正式 php
VITE_API_PHP_URL = 'https://czgdoumei.sxczgkj.com/index.php/api'
# 测试 php 开票
# VITE_API_KP_URL = 'http://192.168.1.13:8888/api'
# 正式 php 开票
VITE_API_KP_URL = 'https://invoice.sxczgkj.cn/api'
# 测试
VITE_API_URL = 'https://cashier-client.sxczgkj.cn/cashier-client'
# 预发布
# VITE_API_URL = 'https://pre-cashierclient.sxczgkj.cn/cashier-client'
# 张松本地
# VITE_API_URL = 'https://36z1017t45.goho.co/cashier-client'
# 正式
# VITE_API_URL = 'https://cashierclient.sxczgkj.cn/cashier-client'

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,10 @@
import path from "path"; import path from "path";
import { app, BrowserWindow, ipcMain } from "electron"; import { app, BrowserWindow, ipcMain } from "electron";
import axios from "axios";
import os from "os"; import os from "os";
// const SerialPort = require("serialport"); import fs from "fs";
import { exec } from "child_process";
let win; let win;
app.whenReady().then(() => { app.whenReady().then(() => {
@@ -29,6 +32,54 @@ app.whenReady().then(() => {
win.loadFile(path.resolve(__dirname, "../dist/index.html")); // 打包后使用文件路径访问应用 win.loadFile(path.resolve(__dirname, "../dist/index.html")); // 打包后使用文件路径访问应用
} }
const installExe = async (exePath) => {
return new Promise((resolve, reject) => {
exec(`${exePath}`, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
};
ipcMain.on("downloadFile", async (event, arg) => {
let _parmas = JSON.parse(arg);
axios({
url: _parmas.url,
method: "get",
responseType: "arraybuffer",
onDownloadProgress: (propessEvent) => {
// 更新进度条
const propress = Math.round(
(propessEvent.loaded / propessEvent.total) * 100
);
win.webContents.send("updateProgress", propress);
},
})
.then(async (response) => {
try {
const tempFilePath = path.join(
app.getPath("temp"),
"temp-exe-file.exe"
);
fs.writeFileSync(tempFilePath, response.data);
setTimeout(() => {
win = null;
app.exit();
}, 1500);
const installResult = await installExe(tempFilePath);
console.log(`安装结果:${installResult}`);
} catch (error) {
console.log("error", error);
}
})
.catch((err) => {
console.log("下载失败", JSON.stringify(err));
});
});
app.on("activate", () => { app.on("activate", () => {
// 在 macOS 系统内, 如果没有已开启的应用窗口 // 在 macOS 系统内, 如果没有已开启的应用窗口
// 点击托盘图标时通常会重新创建一个新窗口 // 点击托盘图标时通常会重新创建一个新窗口
@@ -45,6 +96,7 @@ app.whenReady().then(() => {
// 给渲染进程返回打印机列表 // 给渲染进程返回打印机列表
ipcMain.on("getPrintList", () => { ipcMain.on("getPrintList", () => {
win.webContents.getPrintersAsync().then((res) => { win.webContents.getPrintersAsync().then((res) => {
// console.log("打印机列表", res);
win.webContents.send("printList", res); win.webContents.send("printList", res);
}); });
}); });
@@ -62,120 +114,110 @@ app.whenReady().then(() => {
win.webContents.send("getOSmacRes", mac); win.webContents.send("getOSmacRes", mac);
}); });
// ipcMain.on("getSerialPort", () => { // 创建打印小票子窗口
// SerialPort.SerialPort.list().then( // const printWin = new BrowserWindow({
// (ports) => { // show: false,
// console.log(ports); // width: 464,
// win.webContents.send("seriaportList", ports); // height: 1726,
// }, // webPreferences: {
// (err) => console.error(err) // // 集成网页和 Node.js也就是在渲染进程中可以调用 Node.js 方法
// ); // nodeIntegration: true,
// contextIsolation: false,
// },
// }); // });
// 创建打印小票子窗口 // if (process.env.VITE_DEV_SERVER_URL) {
const printWin = new BrowserWindow({ // // 加载打印的html文件
show: false, // printWin.loadFile(path.join(__dirname, "../public/print.html"));
width: 464, // } else {
height: 1726, // printWin.loadFile(path.resolve(__dirname, "../dist/print.html")); // 打包后使用文件路径访问应用
webPreferences: { // }
// 集成网页和 Node.js也就是在渲染进程中可以调用 Node.js 方法
nodeIntegration: true,
contextIsolation: false,
},
});
if (process.env.VITE_DEV_SERVER_URL) { // // 接收订单页面发过来的参数发送给打印页
// 加载打印的html文件 // ipcMain.on("printerInfoSync", (event, arg) => {
printWin.loadFile(path.join(__dirname, "../public/print.html")); // printWin.webContents.send("getParams", arg);
} else { // });
printWin.loadFile(path.resolve(__dirname, "../dist/print.html")); // 打包后使用文件路径访问应用
}
// 接收订单页面发过来的参数发送给打印页 // // 执行打印操作
ipcMain.on("printerInfoSync", (event, arg) => { // ipcMain.on("printStart", (event, arg) => {
printWin.webContents.send("getParams", arg); // console.log(arg);
}); // const _parmas = JSON.parse(arg);
// // console.log(_parmas)
// let name = _parmas.deviceName;
// printWin.webContents.print({
// silent: true,
// deviceName: name,
// pageSize: {
// width: 58000,
// height: 216000,
// },
// scaleFactor: 80,
// landscape: false,
// margins: {
// marginType: "none",
// top: 0,
// bottom: 0,
// left: 0,
// right: 0,
// },
// dpi: {
// horizontal: 203,
// vertical: 203,
// },
// });
// });
// 执行打印操作 // // 交班小票的窗口
ipcMain.on("printStart", (event, arg) => { // const workPrintWin = new BrowserWindow({
console.log(arg); // show: false,
const _parmas = JSON.parse(arg); // width: 464,
// console.log(_parmas) // height: 1726,
let name = _parmas.deviceName; // webPreferences: {
printWin.webContents.print({ // nodeIntegration: true,
silent: true, // contextIsolation: false,
deviceName: name, // },
pageSize: { // });
width: 58000,
height: 216000,
},
scaleFactor: 80,
landscape: false,
margins: {
marginType: "none",
top: 0,
bottom: 0,
left: 0,
right: 0,
},
dpi: {
horizontal: 203,
vertical: 203,
},
});
});
// 交班小票的窗口 // if (process.env.VITE_DEV_SERVER_URL) {
const workPrintWin = new BrowserWindow({ // // 加载打印的html文件
show: false, // workPrintWin.loadFile(path.join(__dirname, "../public/work_print.html"));
width: 464, // } else {
height: 1726, // workPrintWin.loadFile(path.resolve(__dirname, "../dist/work_print.html")); // 打包后使用文件路径访问应用
webPreferences: { // }
nodeIntegration: true,
contextIsolation: false,
},
});
if (process.env.VITE_DEV_SERVER_URL) { // // 接收渲染进程发送的数据
// 加载打印的html文件 // ipcMain.on("printerWorkSync", (event, arg) => {
workPrintWin.loadFile(path.join(__dirname, "../public/work_print.html")); // workPrintWin.webContents.send("getParams", arg);
} else { // });
workPrintWin.loadFile(path.resolve(__dirname, "../dist/work_print.html")); // 打包后使用文件路径访问应用
}
// 接收渲染进程发送的数据 // // 执行交班小票的打印操作
ipcMain.on("printerWorkSync", (event, arg) => { // ipcMain.on("printWorkStart", (event, arg) => {
workPrintWin.webContents.send("getParams", arg); // // console.log(arg);
}); // const _parmas = JSON.parse(arg);
// // console.log(_parmas)
// 执行交班小票的打印操作 // let name = _parmas.deviceName;
ipcMain.on("printWorkStart", (event, arg) => { // workPrintWin.webContents.print({
// console.log(arg); // silent: true,
const _parmas = JSON.parse(arg); // deviceName: name,
// console.log(_parmas) // pageSize: {
let name = _parmas.deviceName; // width: 58000,
workPrintWin.webContents.print({ // height: 216000,
silent: true, // },
deviceName: name, // scaleFactor: 80,
pageSize: { // landscape: false,
width: 58000, // margins: {
height: 216000, // marginType: "none",
}, // top: 0,
scaleFactor: 80, // bottom: 0,
landscape: false, // left: 0,
margins: { // right: 0,
marginType: "none", // },
top: 0, // dpi: {
bottom: 0, // horizontal: 203,
left: 0, // vertical: 203,
right: 0, // },
}, // });
dpi: { // });
horizontal: 203,
vertical: 203,
},
});
});
// 标签小票的窗口 // 标签小票的窗口
const tagPrintWin = new BrowserWindow({ const tagPrintWin = new BrowserWindow({

View File

@@ -1,11 +1,12 @@
{ {
"name": "vite-electron", "name": "vite-electron",
"private": true, "private": true,
"version": "1.4.3", "version": "1.4.26",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"scripts": { "scripts": {
"dev": "chcp 65001 && vite", "dev": "chcp 65001 && vite",
"build": "node ./addVersion.js && vite build && electron-builder", "build": "node ./addVersion.js && vite build && electron-builder",
"build:test": "vite build --mode test && electron-builder",
"preview": "vite preview", "preview": "vite preview",
"build:win": "node ./addVersion.js && vite build && electron-builder --w" "build:win": "node ./addVersion.js && vite build && electron-builder --w"
}, },
@@ -23,6 +24,7 @@
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"reconnecting-websocket": "^4.4.0", "reconnecting-websocket": "^4.4.0",
"serialport": "^12.0.0", "serialport": "^12.0.0",
"speak-tts": "^2.0.8",
"swiper": "^11.1.1", "swiper": "^11.1.1",
"uuid": "^10.0.0", "uuid": "^10.0.0",
"vue": "^3.3.8", "vue": "^3.3.8",

View File

@@ -47,6 +47,9 @@ body {
} }
.print_view .header .logo { .print_view .header .logo {
width: 90px; width: 90px;
height: 30px;
-o-object-fit: cover;
object-fit: cover;
} }
.print_view .header .title { .print_view .header .title {
margin-left: 6px; margin-left: 6px;

View File

@@ -14,8 +14,8 @@
<div class="print_view"> <div class="print_view">
<div class="ewm" id="ewm"></div> <div class="ewm" id="ewm"></div>
<div class="header"> <div class="header">
<img class="logo" src="./logo.png" /> <img class="logo" :src="data.ticketLogo || './logo.png'" />
<!-- <span class="title">双屿Pisces</span> --> <!-- <span class="title">{{data.ticket_logo}}</span> -->
</div> </div>
<div class="number_wrap"> <div class="number_wrap">
<div class="num" v-if="data.outNumber">{{data.outNumber}}</div> <div class="num" v-if="data.outNumber">{{data.outNumber}}</div>

View File

@@ -38,6 +38,8 @@ body {
.logo { .logo {
$size: 90px; $size: 90px;
width: $size; width: $size;
height: 30px;
object-fit: cover;
} }
.title { .title {
margin-left: 6px; margin-left: 6px;

View File

@@ -51,7 +51,7 @@ watch(route, (to) => {
includeList.push(to.name); includeList.push(to.name);
} }
// 需要全屏的路由 // 需要全屏的路由
let arr = ["/login", "/device_list", "/add_device", "/add_label", "/webview"]; let arr = ["/login", "/device_list", "/add_device", "/add_label", "/webview", '/workrecord'];
if (arr.includes(to.path)) { if (arr.includes(to.path)) {
hideLeftMenu.value = true; hideLeftMenu.value = true;
} else { } else {
@@ -164,24 +164,6 @@ async function getBarCode(e) {
} }
} }
// 获取网络状态
const updateInfo = _.throttle(function () {
let isOnLine = navigator.onLine
// // 获取网络信息
// let info = navigator.connection
console.log(isOnLine);
// console.log(info);
if (store.userInfo && store.userInfo.shopId) {
if (isOnLine) {
console.log('有网了重新连接ws~');
socket.init();
} else {
socket.close();
console.log('网络连接失败~');
}
}
}, 100, { leading: true, trailing: false })
onMounted(() => { onMounted(() => {
document.addEventListener("keydown", (e) => { document.addEventListener("keydown", (e) => {
getBarCode(e); getBarCode(e);
@@ -193,7 +175,6 @@ onMounted(() => {
} }
ipcRenderer.on('showCloseDialog', (event, arg) => { ipcRenderer.on('showCloseDialog', (event, arg) => {
console.log('阻止系统关闭软件');
ElMessageBox.confirm("确定要关闭软件吗?") ElMessageBox.confirm("确定要关闭软件吗?")
.then(() => { .then(() => {
ipcRenderer.send("quitHandler", "退出吧"); ipcRenderer.send("quitHandler", "退出吧");
@@ -201,14 +182,16 @@ onMounted(() => {
.catch(() => { }); .catch(() => { });
}) })
// listnerCloseDialog()
// // 监听网络在线状态 window.addEventListener('online', function () {
// window.addEventListener("onLine", updateInfo) console.log('有网络了');
// // 监听网络离线 this.location.reload()
// window.addEventListener("offLine", updateInfo) })
// 监听网络信息变化
// navigator.connection.addEventListener('change', updateInfo) window.addEventListener('offline', function () {
ElMessage.warning('网络异常')
socket.close()
})
}); });
</script> </script>
@@ -272,6 +255,10 @@ html {
color: #333; color: #333;
} }
.el-divider__text {
white-space: nowrap;
}
.el-dialog__headerbtn { .el-dialog__headerbtn {
top: 10px !important; top: 10px !important;
} }

View File

@@ -119,3 +119,126 @@ export function douyinfulfilmentcertificatecancel(data) {
data, data,
}); });
} }
/**
* 门店列表
* @param {*} data
* @returns
*/
export function douyinstorelist(data) {
return request_php({
method: "post",
url: "douyin/storelist",
data,
});
}
/**
* 绑定门店
* @param {*} data
* @returns
*/
export function douyinbindstore(data) {
return request_php({
method: "post",
url: "douyin/bindstore",
data,
});
}
/**
* 登出团购
* @param {*} data
* @returns
*/
export function tglogout(data) {
return request_php({
method: "post",
url: "user/logout",
data,
});
}
/**
* 美团团购核销
* 绑定-获取绑定状态
* @param {*} data
* @returns
*/
export function thirdPartyCoupon_state(data) {
return request_php({
method: "post",
url: "/meituan/searchstorestatus",
data,
});
}
/**
* 美团团购核销
* 绑定-获取绑定url
* @param {*} data
* @returns
*/
export function thirdPartyCoupon_bindUrl(data) {
return request_php({
method: "post",
url: "/meituan/getuisdkurl",
data,
});
}
/**
* 美团团购核销
* 团购券-获取可用券
* @param {*} data
* @returns
*/
export function thirdPartyCoupon_list(data) {
return request_php({
method: "post",
url: "/meituan/fulfilmentcertificateprepare",
data,
});
}
/**
* 美团团购核销
* 执行核销
* @param {*} data
* @returns
*/
export function certificateprepare(data) {
return request_php({
method: "post",
url: "/meituan/certificateprepare",
data,
});
}
/**
* 美团团购核销
* 团购核销记录
* @param {*} data
* @returns
*/
export function meituan_orderlist(data) {
return request_php({
method: "post",
url: "/meituan/orderlist",
data,
});
}
/**
* 美团团购核销
* 团购撤销
* @param {*} data
* @returns
*/
export function meituan_fulfilmentcertificatecancel(data) {
return request_php({
method: "post",
url: "/meituan/fulfilmentcertificatecancel",
data,
});
}

40
src/api/invoice.js Normal file
View File

@@ -0,0 +1,40 @@
import request_kp from "@/utils/request_kp.js";
/**
* 开票人列表
* @param {*} data
* @returns
*/
export function issuedby(data) {
return request_kp({
method: "post",
url: "szzpy/syjissuedby",
data,
});
}
/**
* 商家通过收银机提交开票信息
* @param {*} data
* @returns
*/
export function carsubinvoicing(data) {
return request_kp({
method: "post",
url: "store/carsubinvoicing",
data,
});
}
/**
* 打印二维码
* @param {*} data
* @returns
*/
export function syjprintqrcode(data) {
return request_kp({
method: "post",
url: "store/syjprintqrcode",
data,
});
}

View File

@@ -2,56 +2,69 @@ import request from "@/utils/request.js";
/** /**
* 查询店铺会员信息 * 查询店铺会员信息
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function queryMembermember(params) { export function queryMembermember(params) {
return request({ return request({
method: "get", method: "get",
url: "member/queryMember", url: "member/queryMember",
params params,
}); });
} }
export function createMembermember(data) { export function createMembermember(data) {
return request({ return request({
method: "post", method: "post",
url: "member/createMember", url: "member/createMember",
data data,
}); });
} }
/** /**
* 查询会员流水 * 查询会员流水
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function memberqueryMemberAccount(params) { export function memberqueryMemberAccount(params) {
return request({ return request({
method: "get", method: "get",
url: "member/queryMemberAccount", url: "member/queryMemberAccount",
params params,
}); });
} }
/** /**
* 会员现金充值 * 会员现金充值
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function accountPaymember(data) { export function accountPaymember(data) {
return request({ return request({
method: "post", method: "post",
url: "member/accountPay", url: "member/accountPay",
data data,
}); });
} }
/** /**
* 会员扫码充值 * 会员扫码充值
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function membermemberScanPay(data) { export function membermemberScanPay(data) {
return request({ return request({
method: "post", method: "post",
url: "member/memberScanPay", url: "member/memberScanPay",
data data,
}); });
}
/**
* 会员充值退款
* @param {*} params
* @returns
*/
export function returnFlow(params) {
return request({
method: "get",
url: "member/returnFlow",
params,
});
} }

View File

@@ -29,10 +29,10 @@ export function orderorderDetail(params) {
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function payreturnOrder(data, pwd) { export function payreturnOrder(data, pwd, isOnline) {
return request({ return request({
method: "post", method: "post",
url: `pay/returnOrder?pwd=${pwd}`, url: `pay/returnOrder?pwd=${pwd}&isOnline=${isOnline}`,
data, data,
}); });
} }
@@ -101,3 +101,29 @@ export function getsendMessage(params) {
params, params,
}); });
} }
/**
* 订单详情
* @param {*} params
* @returns
*/
export function orderDetail(params) {
return request({
method: "get",
url: "/order/orderDetail",
params,
});
}
/**
* 获取员工最大优惠点
* @param {*} params
* @returns
*/
export function getStaffDiscount(params) {
return request({
method: "get",
url: "/pay/getOrderDiscount",
params,
});
}

View File

@@ -15,53 +15,53 @@ export function queryPayType(params) {
/** /**
* 付款 * 付款
* @param {*} params * @param {*} data
* @returns * @returns
*/ */
export function payOrder(api, params) { export function payOrder(api, data) {
return request({ return request({
method: "get", method: "post",
url: api, url: api,
params, data,
}); });
} }
/** /**
* 扫码支付 * 扫码支付
* @param {*} params * @param {*} data
* @returns * @returns
*/ */
export function scanpay(params) { export function scanpay(data) {
return request({ return request({
method: "get", method: "post",
url: "pay/scanpay", url: "pay/scanpay",
params, data,
}); });
} }
/** /**
* 储值卡付款 * 储值卡付款
* @param {*} params * @param {*} data
* @returns * @returns
*/ */
export function accountPay(params) { export function accountPay(data) {
return request({ return request({
method: "get", method: "post",
url: "pay/accountPay", url: "pay/accountPay",
params, data,
}); });
} }
/** /**
* 现金付款 * 现金付款
* @param {*} params * @param {*} data
* @returns * @returns
*/ */
export function cashPay(params) { export function cashPay(data) {
return request({ return request({
method: "get", method: "post",
url: "pay/cashPay", url: "pay/cashPay",
params, data,
}); });
} }
@@ -129,3 +129,16 @@ export function queryScanPay(params) {
params, params,
}); });
} }
/**
* 会员余额支付
* @param {*} data
* @returns
*/
export function vipPay(data) {
return request({
method: "post",
url: "/pay/vipPay",
data,
});
}

View File

@@ -2,171 +2,247 @@ import request from "@/utils/request.js";
/** /**
* 查询分类信息 * 查询分类信息
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function queryCategory(params) { export function queryCategory(params) {
return request({ return request({
method: "get", method: "get",
url: "product/queryCategory", url: "product/queryCategory",
params params,
}); });
} }
/** /**
* 查询商品信息 * 查询商品信息
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function productqueryCommodityInfo(params) { export function productqueryCommodityInfo(params) {
return request({ return request({
method: "get", method: "get",
url: "product/queryCommodityInfo", url: "product/queryCommodityInfo",
params params,
}); });
} }
/** /**
* 查询商品信息 * 查询商品信息
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function queryNewCommodityInfo(params) { export function queryNewCommodityInfo(params) {
return request({ return request({
method: "get", method: "get",
url: "product/queryNewCommodityInfo", url: "product/queryNewCommodityInfo",
params params,
}); });
} }
/** /**
* 通过选中的商品规格查询价格 * 通过选中的商品规格查询价格
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function queryProductSku(params) { export function queryProductSku(params) {
return request({ return request({
method: "get", method: "get",
url: "product/queryProductSku", url: "product/queryProductSku",
params params,
}); });
} }
/** /**
* 添加购物车 * 添加购物车
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function createCart(data) { export function createCart(data) {
return request({ return request({
method: "post", method: "post",
url: "/order/createCart", url: "/order/createCart",
data data,
}); });
} }
/** /**
* 获取购物车商品 * 获取购物车商品
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function queryCart(params) { export function queryCart(params) {
return request({ return request({
method: "get", method: "get",
url: "order/queryCart", url: "order/queryCart",
params params,
}); });
} }
/** /**
* 获取取件码 * 获取取件码
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function createCode(params) { export function createCode(params) {
return request({ return request({
method: "get", method: "get",
url: "/order/createCode", url: "/order/createCode",
params params,
}); });
} }
/** /**
* 全部打包 * 全部打包
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function packall(data) { export function packall(data) {
return request({ return request({
method: "post", method: "post",
url: "/order/packall", url: "/order/packall",
data data,
}); });
} }
/** /**
* 删除购物车 * 删除购物车
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function delCart(params) { export function delCart(params) {
return request({ return request({
method: "get", method: "get",
url: "/order/delCart", url: "/order/delCart",
params params,
}); });
} }
/** /**
* 挂单/j激活购物车 * 挂单/j激活购物车
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function cartStatus(data) { export function cartStatus(data) {
return request({ return request({
method: "post", method: "post",
url: "/order/cartStatus", url: "/order/cartStatus",
data data,
}); });
} }
/** /**
* 获取挂起购物车列表 * 获取挂起购物车列表
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function getCartList(params) { export function getCartList(params) {
return request({ return request({
method: "get", method: "get",
url: "/order/getCartList", url: "/order/getCartList",
params params,
}); });
} }
/** /**
* 清空购物车 * 清空购物车
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function clearCart(data) { export function clearCart(data) {
return request({ return request({
method: "post", method: "post",
url: "/order/clearCart", url: "/order/clearCart",
data data,
}); });
} }
/** /**
* 创建订单 * 创建订单
* @param {*} data * @param {*} data
* @returns * @returns
*/ */
export function createOrder(data) { export function createOrder(data) {
return request({ return request({
method: "post", method: "post",
url: "/order/createOrder", url: "/order/createOrder",
data data,
}); });
} }
/**
* 上下架售罄
* @param {*} data
* @returns
*/
export function productStatus(data) {
return request({
method: "PUT",
url: "/product/productStatus",
data,
});
}
/**
* 修改库存
* @param {*} data
* @returns
*/
export function productStock(data) {
return request({
method: "PUT",
url: "/product/productStock",
data,
});
}
/**
* 添加临时菜
* @param {*} data
* @returns
*/
export function temporaryDishes(data) {
return request({
method: "post",
url: "/order/temporaryDishes",
data,
});
}
/**
* 商品单位列表获取
* @param {*} params
* @returns
*/
export function getUnitList(params) {
return request({
method: "get",
url: "/unit",
params,
});
}
/**
* 单品改价
* @param {*} data
* @returns
*/
export function updatePrice(data) {
return request({
method: "PUT",
url: "/order/updatePrice",
data,
});
}
/**
* 免厨打印
* @param {*} data
* @returns
*/
export function orderPrint(data) {
return request({
method: "PUT",
url: "/order/print",
data,
});
}

134
src/api/queue.js Normal file
View File

@@ -0,0 +1,134 @@
/**
* 排队叫号
*/
import request from "@/utils/request.js";
/**
* 记录获取
* @param {*} params
* @returns
*/
export function callRecord(params) {
return request({
method: "get",
url: "/callTable/callRecord",
params,
});
}
/**
* 桌型列表
* @param {*} params
* @returns
*/
export function callTable(params) {
return request({
method: "get",
url: "/callTable",
params,
});
}
/**
* 添加桌型
* @param {*} data
* @returns
*/
export function addCallTable(data) {
return request({
method: data.id ? "put" : "post",
url: "/callTable",
data,
});
}
/**
* 删除桌型
* @param {*} data
* @returns
*/
export function delCallTable(data) {
return request({
method: "delete",
url: "/callTable",
data,
});
}
/**
* 配置信息 获取
* @param {*} params
* @returns
*/
export function callTableConfig(params) {
return request({
method: "get",
url: "/callTable/config",
params,
});
}
/**
* 配置信息 修改
* @param {*} params
* @returns
*/
export function callTableConfigPut(data) {
return request({
method: "put",
url: "/callTable/config",
data,
});
}
/**
* 取号 排队列表获取
* @param {*} params
* @returns
*/
export function callTableQueue(params) {
return request({
method: "GET",
url: "/callTable/queue",
params,
});
}
/**
* 取号 手动取号
* @param {*} params
* @returns
*/
export function takeNumber(data) {
return request({
method: "post",
url: "/callTable/takeNumber",
data,
});
}
/**
* 取号 修改叫号状态
* @param {*} params
* @returns
*/
export function updateState(data) {
return request({
method: "put",
url: "/callTable/updateState",
data,
});
}
/**
* 通知叫号
* @param {*} params
* @returns
*/
export function callTableCall(data) {
return request({
method: "post",
url: "/callTable/call",
data,
});
}

View File

@@ -1,27 +1,53 @@
import request from "@/utils/request.js" import request from "@/utils/request.js";
/** /**
* 查询台桌分类 * 查询台桌分类
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function queryShopArea(params) { export function queryShopArea(params) {
return request({ return request({
method: "get", method: "get",
url: "shopInfo/queryShopArea", url: "shopInfo/queryShopArea",
params params,
}); });
} }
/** /**
* 查询台桌信息 * 查询台桌信息
* @param {*} params * @param {*} params
* @returns * @returns
*/ */
export function queryShopTable(params) { export function queryShopTable(params) {
return request({ return request({
method: "get", method: "get",
url: "shopInfo/queryShopTable", url: "shopInfo/queryShopTable",
params params,
}); });
} }
/**
* 清台
* @param {*} params
* @returns
*/
export function clearTable(data) {
return request({
method: "put",
url: "/shopInfo/clearTable",
data,
});
}
/**
* 选择用餐人数
* @param {*} params
* @returns
*/
export function orderChoseCount(data) {
return request({
method: "put",
url: "/order/choseCount",
data,
});
}

View File

@@ -1,9 +1,81 @@
import request from "@/utils/request.js"; import request from "@/utils/request.js";
import { ElMessage } from "element-plus";
export function login(data) { export function login(data) {
return request({ return request({
method: "post", method: "post",
url: "login/login", url: "login/login",
data, data,
});
}
/**
* 获取版本
* @returns
*/
export function findVersion() {
return request({
method: "post",
url: "login/findVersion",
});
}
/**
* 获取是否显示密码
* @param {*} params
* @returns
*/
export function queryPwdInfo() {
let userInfo = JSON.parse(localStorage.getItem("userInfo"));
return request({
method: "get",
url: "/shopInfo/queryPwdInfo",
params: {
shopId: userInfo.shopId,
},
});
}
/**
* 查询店铺信息
* @param {*} params
* @returns
*/
export function queryShopInfo() {
let userInfo = JSON.parse(localStorage.getItem("userInfo"));
return request({
method: "get",
url: "/shopInfo/queryShopInfo",
params: {
shopId: userInfo.shopId,
},
});
}
/**
* 查询员工是否拥有权限
* @param {*} params
* @returns
*/
export async function staffPermission(code) {
let userInfo = JSON.parse(localStorage.getItem("userInfo"));
if (userInfo.isStaff) {
const res = await request({
method: "get",
url: "/staffPermission",
params: {
staffId: userInfo.staffId,
code: code,
},
}); });
}
if (res) {
return Promise.resolve();
} else {
ElMessage.error("无权操作");
return Promise.reject('无权操作');
}
} else {
return Promise.resolve();
}
}

View File

@@ -51,3 +51,16 @@ export function handoverData(params) {
params, params,
}); });
} }
/**
* 打印交班数据
* @param {*} params
* @returns
*/
export function handoverprint(params) {
return request({
method: "get",
url: "data/handoverprint",
params,
});
}

BIN
src/assets/icon_xq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -59,7 +59,7 @@
</div> </div>
</div> </div>
</div> </div>
<scanModal ref="scanModalRef" fast :amount="money" :selecttype="props.type" :orderId="props.userInfo.id" <scanModal ref="scanModalRef" fast :amount="money" :money="money" :selecttype="props.type" :orderId="props.userInfo.id"
@success="scanCodeSuccess" /> @success="scanCodeSuccess" />
<takeFoodCode ref="takeFoodCodeRef" title="支付密码" :type="2" input-type="password" placeholder="请输入支付密码" <takeFoodCode ref="takeFoodCodeRef" title="支付密码" :type="2" input-type="password" placeholder="请输入支付密码"
@success="passwordSuccess" /> @success="passwordSuccess" />
@@ -78,6 +78,7 @@ import { useUser } from "@/store/user.js";
import { clearNoNum } from "@/utils"; import { clearNoNum } from "@/utils";
import md5 from "js-md5"; import md5 from "js-md5";
import { queryPwdInfo } from '@/api/user.js'
import scanModal from "@/components/payCard/scanModal.vue"; import scanModal from "@/components/payCard/scanModal.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import takeFoodCode from "@/components/takeFoodCode.vue"; import takeFoodCode from "@/components/takeFoodCode.vue";
@@ -128,14 +129,14 @@ function payTypeChange(index, item) {
} }
// 获取支付密码 // 获取支付密码
async function passwordSuccess(e) { async function passwordSuccess(e = '') {
try { try {
payLoading.value = true; payLoading.value = true;
await accountPaymember({ await accountPaymember({
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
memberId: props.userInfo.id, memberId: props.userInfo.id,
amount: money.value, amount: money.value,
pwd: md5(e), pwd: e ? md5(e) : '',
}); });
payLoading.value = false; payLoading.value = false;
ElMessage.success("支付成功"); ElMessage.success("支付成功");
@@ -175,8 +176,15 @@ async function confirmOrder() {
emit("paySuccess"); emit("paySuccess");
} else { } else {
// 会员充值 // 会员充值
takeFoodCodeRef.value.show();
// passwordSuccess() let res = await queryPwdInfo()
if (res.isMemberIn == 1) {
takeFoodCodeRef.value.show();
} else {
passwordSuccess()
}
// takeFoodCodeRef.value.show();
// // passwordSuccess()
} }
break; break;
default: default:

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="left_menu_wrap"> <div class="left_menu_wrap">
<div class="item" :class="{ online: socketStore.online }" @click="connectWsHandle"> <div class="item first" :class="{ online: socketStore.online }" @click="connectWsHandle">
<el-icon class="icon"> <el-icon class="icon">
<Monitor /> <Monitor />
</el-icon> </el-icon>
<el-text :type="socketStore.online ? 'success' : ''"> <el-text :type="socketStore.online ? 'success' : 'danger'">
<template v-if="socketStore.online"> <template v-if="socketStore.online">
在线 在线
</template> </template>
@@ -20,6 +20,12 @@
</el-icon> </el-icon>
<el-text class="text">{{ item.label }}</el-text> <el-text class="text">{{ item.label }}</el-text>
</router-link> </router-link>
<div class="item" @click="workRef.show()">
<el-icon class="icon">
<component is="SwitchButton" />
</el-icon>
<el-text class="text">交班</el-text>
</div>
<div class="item more" @click="moreref.show()"> <div class="item more" @click="moreref.show()">
<el-icon class="icon"> <el-icon class="icon">
<Operation /> <Operation />
@@ -27,6 +33,8 @@
<el-text class="text">更多</el-text> <el-text class="text">更多</el-text>
</div> </div>
</div> </div>
<!-- 交班 -->
<work ref="workRef" />
<!-- 更多 --> <!-- 更多 -->
<more ref="moreref" @openCall="openCall"></more> <more ref="moreref" @openCall="openCall"></more>
<!-- 叫号 --> <!-- 叫号 -->
@@ -39,14 +47,15 @@ import { useRoute } from 'vue-router'
import { useSocket } from '@/store/socket.js' import { useSocket } from '@/store/socket.js'
import more from '@/components/more.vue' import more from '@/components/more.vue'
import callNumber from './callNumber.vue' import callNumber from './callNumber.vue'
import work from '@/views/work/index.vue'
const emits = defineEmits(['connectWsHandle'])
const socketStore = useSocket() const socketStore = useSocket()
const route = useRoute() const route = useRoute()
const moreref = ref(null) const moreref = ref(null)
const callNumberRef = ref(null) const callNumberRef = ref(null)
const workRef = ref(null)
const menus = ref([ const menus = ref([
{ {
label: '收银', label: '收银',
@@ -78,11 +87,16 @@ const menus = ref([
path: '/member', path: '/member',
icon: 'User' icon: 'User'
}, },
{ // {
label: '交班', // label: '排队',
path: '/work', // path: '/queue',
icon: 'SwitchButton' // icon: 'Timer'
} // },
// {
// label: '交班',
// path: '/work',
// icon: 'SwitchButton'
// }
]) ])
// 更新叫号记录 // 更新叫号记录
@@ -96,8 +110,8 @@ function openCall() {
// 手动重新连接ws // 手动重新连接ws
function connectWsHandle() { function connectWsHandle() {
if (socketStore.online) return // if (socketStore.online) return
emits('connectWsHandle') location.reload()
} }
defineExpose({ defineExpose({
@@ -224,7 +238,7 @@ defineExpose({
} }
&.more { &.more {
margin-top: 120px; margin-top: 90px;
} }
.icon { .icon {

View File

@@ -0,0 +1,207 @@
//==本JS是加载Lodop插件或Web打印服务CLodop/Lodop7的综合示例可直接使用建议理解后融入自己程序==
//用双端口加载主JS文件Lodop.js(或CLodopfuncs.js兼容老版本)以防其中某端口被占:
var MainJS = "CLodopfuncs.js",
URL_WS1 = "ws://localhost:8000/" + MainJS, //ws用8000/18000
URL_WS2 = "ws://localhost:18000/" + MainJS,
URL_HTTP1 = "http://localhost:8000/" + MainJS, //http用8000/18000
URL_HTTP2 = "http://localhost:18000/" + MainJS,
URL_HTTP3 = "https://localhost.lodop.net:8443/" + MainJS; //https用8000/8443
var CreatedOKLodopObject, CLodopIsLocal, LoadJsState;
//==判断是否需要CLodop(那些不支持插件的浏览器):==
function needCLodop() {
try {
var ua = navigator.userAgent;
if (ua.match(/Windows\sPhone/i) ||
ua.match(/iPhone|iPod|iPad/i) ||
ua.match(/Android/i) ||
ua.match(/Edge\D?\d+/i))
return true;
var verTrident = ua.match(/Trident\D?\d+/i);
var verIE = ua.match(/MSIE\D?\d+/i);
var verOPR = ua.match(/OPR\D?\d+/i);
var verFF = ua.match(/Firefox\D?\d+/i);
var x64 = ua.match(/x64/i);
if ((!verTrident) && (!verIE) && (x64)) return true;
else if (verFF) {
verFF = verFF[0].match(/\d+/);
if ((verFF[0] >= 41) || (x64)) return true;
} else if (verOPR) {
verOPR = verOPR[0].match(/\d+/);
if (verOPR[0] >= 32) return true;
} else if ((!verTrident) && (!verIE)) {
var verChrome = ua.match(/Chrome\D?\d+/i);
if (verChrome) {
verChrome = verChrome[0].match(/\d+/);
if (verChrome[0] >= 41) return true;
}
}
return false;
} catch (err) {
return true;
}
}
//==检查加载成功与否如没成功则用http(s)再试==
//==低版本CLODOP6.561/Lodop7.043及前)用本方法==
function checkOrTryHttp() {
if (window.getCLodop) {
LoadJsState = "complete";
return true;
}
if (LoadJsState == "loadingB" || LoadJsState == "complete") return;
LoadJsState = "loadingB";
var head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
var JS1 = document.createElement("script"),
JS2 = document.createElement("script"),
JS3 = document.createElement("script");
JS1.src = URL_HTTP1;
JS2.src = URL_HTTP2;
JS3.src = URL_HTTP3;
JS1.onload = JS2.onload = JS3.onload = JS2.onerror = JS3.onerror = function () {
LoadJsState = "complete";
}
JS1.onerror = function (e) {
if (window.location.protocol !== 'https:')
head.insertBefore(JS2, head.firstChild);
else
head.insertBefore(JS3, head.firstChild);
}
head.insertBefore(JS1, head.firstChild);
}
//==加载Lodop对象的主过程:==
(function loadCLodop() {
if (!needCLodop()) return;
CLodopIsLocal = !!((URL_WS1 + URL_WS2).match(/\/\/localho|\/\/127.0.0./i));
LoadJsState = "loadingA";
if (!window.WebSocket && window.MozWebSocket) window.WebSocket = window.MozWebSocket;
//ws方式速度快(小于200ms)且可避免CORS错误,但要求Lodop版本足够新:
try {
var WSK1 = new WebSocket(URL_WS1);
WSK1.onopen = function (e) {
setTimeout(checkOrTryHttp(), 200);
}
WSK1.onmessage = function (e) {
if (!window.getCLodop) eval(e.data);
}
WSK1.onerror = function (e) {
var WSK2 = new WebSocket(URL_WS2);
WSK2.onopen = function (e) {
setTimeout(checkOrTryHttp(), 200);
}
WSK2.onmessage = function (e) {
if (!window.getCLodop) eval(e.data);
}
WSK2.onerror = function (e) {
checkOrTryHttp();
}
}
} catch (e) {
checkOrTryHttp();
}
})();
//==获取LODOP对象主过程,判断是否安装、需否升级:==
function getLodop(oOBJECT, oEMBED) {
var strFontTag = "<br><font color='#FF00FF'>打印控件";
var strLodopInstall = strFontTag + "未安装!点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip'>执行安装</a>";
var strLodopUpdate = strFontTag + "需要升级!点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip'>执行升级</a>";
var strLodop64Install = strFontTag + "未安装!点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip'>执行安装</a>";
var strLodop64Update = strFontTag + "需要升级!点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip'>执行升级</a>";
var strCLodopInstallA =
"<br><font color='#FF00FF'>Web打印服务CLodop未安装启动点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip'>下载执行安装</a>";
// var strCLodopInstallB = "<br>(若此前已安装过,可<a href='CLodop.protocol:setup' target='_self'>点这里直接再次启动</a>";
var strCLodopUpdate =
"<br><font color='#FF00FF'>Web打印服务CLodop需升级!点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip'>执行升级</a>";
var strLodop7FontTag = "<br><font color='#FF00FF'>Web打印服务Lodop7";
var strLodop7HrefX86 = "点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip' target='_self'>下载安装</a>(下载后解压点击lodop文件开始执行)";
var strLodop7HrefARM = "点击这里<a href='https://h5-invoice.sxczgkj.cn/invo/czg.zip'>下载安装</a>(下载后解压点击lodop文件开始执行)";
var strLodop7Install_X86 = strLodop7FontTag + "未安装启动," + strLodop7HrefX86;
var strLodop7Install_ARM = strLodop7FontTag + "未安装启动," + strLodop7HrefARM;
var strLodop7Update_X86 = strLodop7FontTag + "需升级," + strLodop7HrefX86;
var strLodop7Update_ARM = strLodop7FontTag + "需升级," + strLodop7HrefARM;
var strInstallOK = ",成功后请刷新本页面或重启浏览器。</font>";
var LODOP;
try {
var isWinIE = (/MSIE/i.test(navigator.userAgent)) || (/Trident/i.test(navigator.userAgent));
var isWinIE64 = isWinIE && (/x64/i.test(navigator.userAgent));
var isLinuxX86 = (/Linux/i.test(navigator.platform)) && (/x86/i.test(navigator.platform));
var isLinuxARM = (/Linux/i.test(navigator.platform)) && (/aarch/i.test(navigator.platform));
if (needCLodop() || isLinuxX86 || isLinuxARM) {
try {
LODOP = window.getCLodop();
} catch (err) { }
if (!LODOP && LoadJsState !== "complete") {
if (!LoadJsState)
alert("未曾加载Lodop主JS文件请先调用loadCLodop过程.");
else
alert("网页还没下载完毕,请稍等一下再操作.");
return;
}
var strAlertMessage;
if (!LODOP) {
if (isLinuxX86)
strAlertMessage = strLodop7Install_X86;
else if (isLinuxARM)
strAlertMessage = strLodop7Install_ARM;
else
strAlertMessage = strCLodopInstallA + (CLodopIsLocal ? strCLodopInstallB : "");
document.body.innerHTML = strAlertMessage + strInstallOK + document.body.innerHTML;
return;
} else {
if (isLinuxX86 && LODOP.CVERSION < "7.0.7.5")
strAlertMessage = strLodop7Update_X86;
else if (isLinuxARM && LODOP.CVERSION < "7.0.7.5")
strAlertMessage = strLodop7Update_ARM;
else if (CLODOP.CVERSION < "6.5.9.4")
strAlertMessage = strCLodopUpdate;
if (strAlertMessage)
document.body.innerHTML = strAlertMessage + strInstallOK + document.body.innerHTML;
}
} else {
//==如果页面有Lodop插件就直接使用,否则新建:==
if (oOBJECT || oEMBED) {
if (isWinIE)
LODOP = oOBJECT;
else
LODOP = oEMBED;
} else if (!CreatedOKLodopObject) {
LODOP = document.createElement("object");
LODOP.setAttribute("width", 0);
LODOP.setAttribute("height", 0);
LODOP.setAttribute("style", "position:absolute;left:0px;top:-100px;width:0px;height:0px;");
if (isWinIE)
LODOP.setAttribute("classid", "clsid:2105C259-1E0C-4534-8141-A753534CB4CA");
else
LODOP.setAttribute("type", "application/x-print-lodop");
document.documentElement.appendChild(LODOP);
CreatedOKLodopObject = LODOP;
} else
LODOP = CreatedOKLodopObject;
//==Lodop插件未安装时提示下载地址:==
if ((!LODOP) || (!LODOP.VERSION)) {
document.body.innerHTML = (isWinIE64 ? strLodop64Install : strLodopInstall) + strInstallOK + document
.body.innerHTML;
return LODOP;
}
if (LODOP.VERSION < "6.2.2.6") {
document.body.innerHTML = (isWinIE64 ? strLodop64Update : strLodopUpdate) + strInstallOK + document.body
.innerHTML;
}
}
//===如下空白位置适合调用统一功能(如注册语句、语言选择等):=======================
// LODOP.SET_LICENSES("超掌柜独有!","DCFF409304DFCEB3E2C644BF96CD0720","","");
//===============================================================================
return LODOP;
} catch (err) {
alert("getLodop出错:" + err);
}
}
export default getLodop

View File

@@ -0,0 +1,113 @@
<template>
<div>
<el-dialog v-model="centerDialogVisible" title="二维码" width="666" center>
<div class="dialog-footer" style="text-align: center">
<!-- <qrcode-vue :value="form.url" :size="200" /> -->
<div class="qrcodefooter">{{ props.form.article }}</div>
<div class="qrcodefooter">{{ props.form.type }}</div>
<div class="qrcodefooter">
<el-select v-model="rintermodel" placeholder="请选择打印机" @change="changerintermodel">
<el-option v-for="item in rintermodeldata" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<el-button @click="centerDialogVisible = false">关闭</el-button>
<el-button type="primary" :disabled="rintermodel ? false : true" @click="Printing"> 打印 </el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { dayjs } from 'element-plus'
import { ref } from 'vue'
import _lodash from 'lodash'
import getLodop from './LodopFuncs'
const props = defineProps({
form: Object,
})
const centerDialogVisible = ref(false) //显示隐藏
const rintermodeldata = ref([]) // 获取打印机列表
const rintermodel = ref() //打印机类型
// 确定打印机类型
const changerintermodel = (i: string) => {
rintermodel.value = i
}
const emit = defineEmits(['somethingDone'])
// 以下是打印
const Printing = () => {
let LODOP = getLodop()
centerDialogVisible.value = false
rintermodeldata.value = [] //清空
emit('somethingDone')
LODOP.PRINT_INIT('')
// 设置打印纸大小D
LODOP.SET_PRINT_PAGESIZE(3, 800, '', '')
// 二维码控制大小
LODOP.ADD_PRINT_BARCODE('', '30px', '150px', '150px', 'QRCode', props.form.url) //打印产品代码条码
LODOP.SET_PRINT_MODE('PRINT_PAGE_PERCENT', 'Full-Width ') //设置打印风格,这里是等宽打印
LODOP.SET_PRINTER_INDEX(rintermodel.value) //设置默认打印机(这里用的是打印机名称)
LODOP.SET_PRINT_STYLE("TextAlign", "Center");
// 文字内容
LODOP.ADD_PRINT_HTM(
'150px',
'5px',
'100%',
'100%',
`<div style="width: 100%;font-size: 12px; ">项目分类:${props.form.article}</div>
<div style="width: 100%;font-size: 12px; margin-top:6px;">发票类型:${props.form.type}</div>
<div style="width: 100%;font-size: 12px; margin-top:6px;">生成时间:${dayjs().format('YYYY-MM-DD HH:mm:ss')}</div>
<div style="width: 100%;font-size: 12px; margin-top:6px;">*二维码有效期30天,超过自动失效!</div>
<div style="width: 100%;font-size: 14px; margin-top: 15px;">您可以使用微信,扫码开票</div>`,
'150px',
'5px',
'100%',
'100%',
)
LODOP.SET_LICENSES('', 'DCFF409304DFCEB3E2C644BF96CD0720', '', '')
LODOP.PRINT()
}
const initialization = async () => {
rintermodeldata.value = [] //清空
let LODOP = getLodop()
setTimeout(() => {
if (LODOP == null) {
alert('请先安装打印控件')
return
}
for (var i = 0; i < LODOP.GET_PRINTER_COUNT(); i++) {
let obj: {
id: string
name: string
} = {
id: '',
name: '',
}
obj.id = LODOP.GET_PRINTER_NAME(i)
obj.name = LODOP.GET_PRINTER_NAME(i)
// console.log(obj)
rintermodeldata.value.push(obj)
}
}, 1000)
}
const centerDialogVisibleshow = () => {
centerDialogVisible.value = !centerDialogVisible.value
}
defineExpose({
Printing,
centerDialogVisibleshow,
initialization,
})
</script>
<style scoped lang="scss">
.dialog-footer {
text-align: center;
.qrcodefooter {
text-align: center;
margin: 10px;
}
}
</style>

View File

@@ -0,0 +1,32 @@
import getLodop from "./LodopFuncs.js";
/**
* 打印订单发票
*/
export default (data) => {
console.log("data.deviceName===", data.deviceName);
let LODOP = getLodop();
LODOP.PRINT_INIT("打印小票");
// 设置打印纸大小D
LODOP.SET_PRINT_PAGESIZE(3, "58mm", 20, "");
// 二维码控制大小;
LODOP.ADD_PRINT_BARCODE("", "40px", "150px", "150px", "QRCode", data.url);
//设置默认打印机(这里用的是打印机名称)
LODOP.SET_PRINTER_INDEX(data.deviceName);
// 文字内容
let html = `
<div style="height: 100px;"></div>
<div style="width: 100%;font-size: 16px;display:flex;justify-content:center;">
请使用微信扫码下载发票二维码有效期30天超过自动失效
</div>
<div>.</div>
<div>.</div>
<div>.</div>
<div>.</div>
`;
setTimeout(() => {
LODOP.ADD_PRINT_HTM("9mm", "0mm", "RightMargin:0mm", 20, html);
LODOP.SET_LICENSES("", "DCFF409304DFCEB3E2C644BF96CD0720", "", "");
LODOP.PRINT();
}, 800);
};

View File

@@ -0,0 +1,161 @@
import getLodop from "./LodopFuncs.js";
/**
* 打印交班小票
*/
export default (data) => {
console.log("data.deviceName===", data.deviceName);
let LODOP = getLodop();
LODOP.PRINT_INIT("打印小票");
// 设置打印纸大小D
LODOP.SET_PRINT_PAGESIZE(3, "58mm", 20, "");
//设置默认打印机(这里用的是打印机名称)
LODOP.SET_PRINTER_INDEX(data.deviceName);
// 文字内容
let html = `
<div style="font-size: 30px;display:flex;justify-content:center;">
${data.merchantName}
</div>
<div style="font-size: 16px;display: flex; justify-content:center;margin-top:6px;">
交班小票
</div>
<div style="font-size: 12px;margin-top:50px;">
当班时间:${data.startTime}
</div>
<div style="font-size: 12px;">
交班时间:${data.endTime}
</div>
<div style="font-size: 12px;">
收银员:${data.staff}
</div>
<div style="font-size: 12px;margin-top: 4px;">
当班收入:${data.totalAmount}
</div>
`;
let payInfos = "";
if (data.payInfos && data.payInfos.length) {
for (let item of data.payInfos) {
payInfos += `
<div style="font-size: 12px;padding-left:20px;">
${item.payType}${item.amount}
</div>
`;
}
}
let memberTitle = `
<div style="font-size: 12px;margin-top: 4px;">
会员数据
</div>
`;
let memberData = "";
if (data.memberData && data.memberData.length) {
for (let item of data.memberData) {
memberData += `
<div style="font-size: 12px;padding-left:20px;">
${item.deposit}${item.amount}
</div>
`;
}
}
let productCategoriesTabHead = `
<div style="font-size: 12px;margin-top: 4px;">分类数据</div>
<table class="table" style="width: 100%;">
<tr>
<td style="font-size: 12px;width:50%;">名称</td>
<td style="font-size: 12px;width:25%;">数量</td>
<td style="font-size: 12px;width:25%;">总计</td>
</tr>
`;
let productCategoriesTableBody = "";
if (data.productCategories && data.productCategories.length) {
for (let item of data.productCategories) {
productCategoriesTableBody += `
<tr>
<td style="font-size: 12px;width:50%;">
<div>${item.categoryName}</div>
</td>
<td style="font-size: 12px;width:25%;">${item.num}</td>
<td style="font-size: 12px;width:25%;">
${item.amount}
</td>
</tr>
`;
}
}
let tabHead = `
</table>
<div style="font-size: 12px;margin-top: 4px;">商品数据</div>
<table class="table" style="width: 100%;">
<tr>
<td style="font-size: 12px;width:75%;">商品</td>
<td style="font-size: 12px;width:25%;">数量</td>
</tr>
`;
let tableBody = "";
if (data.productInfos && data.productInfos.length) {
for (let item of data.productInfos) {
tableBody += `
<tr>
<td style="font-size: 12px;width:75%;">
<div>${item.productName}</div>
</td>
<td style="font-size: 12px;width:25%;">${item.num}</td>
</tr>
`;
}
}
if (!data.printShop) {
tabHead = "";
tableBody = "";
}
let str = `
</table>
<div style="font-size: 12px;margin-top: 4px;">
<span>快捷收款金额:</span>
<span>${data.quickAmount}</span>
</div>
<div style="font-size: 12px;">
<span>退款金额:</span>
<span>${data.returnAmount}</span>
</div>
<div style="font-size: 12px;">
<span>总收入:</span>
<span>${data.totalAmount}</span>
</div>
<div style="font-size: 12px;">
<span>备用金:</span>
<span>${data.imprest}</span>
</div>
<div style="font-size: 12px;">
<span>应交金额:</span>
<span>${data.payable}</span>
</div>
<div style="margin-top: 20px; font-size: 12px;">
<span>总订单数:</span>
<span>${data.orderNum}</span>
</div>
<div style="font-size: 12px;">
打印时间:${data.printTime}
</div>
<div>.</div>
<div>.</div>
<div>.</div>
<div>.</div>
`;
let lastHtml = `${html}${payInfos}${memberTitle}${memberData}${productCategoriesTabHead}${productCategoriesTableBody}${tabHead}${tableBody}${str}`;
setTimeout(() => {
LODOP.ADD_PRINT_HTM("9mm", "0mm", "RightMargin:0mm", 20, lastHtml);
LODOP.SET_LICENSES("", "DCFF409304DFCEB3E2C644BF96CD0720", "", "");
LODOP.PRINT();
}, 800);
};

View File

@@ -0,0 +1,141 @@
import getLodop from "./LodopFuncs.js";
import { formatDecimal } from "@/utils/index.js";
/**
* 打印订单小票
*/
export default (data) => {
// console.log("需要打印的订单数据===", data);
// console.log("data.deviceName===", data.deviceName);
let LODOP = getLodop();
LODOP.PRINT_INIT("打印小票");
// 设置打印纸大小D
LODOP.SET_PRINT_PAGESIZE(3, "58mm", 20, "");
//设置默认打印机(这里用的是打印机名称)
LODOP.SET_PRINTER_INDEX(data.deviceName);
// 文字内容
let t1 = 40;
let t2 = (100 - t1) / 3;
let html = `
<div style="font-size: 30px;display:flex;justify-content:center;">
${data.shop_name}
</div>
<div style="font-size: 16px;display: flex; justify-content:center;margin-top:6px;">
${data.isBefore ? "预" : ""}结算单【${
data.orderInfo.masterId ? data.orderInfo.masterId : ""
}
</div>
<div style="font-size: 16px;display: flex; justify-content:center;margin-top:20px;">
${data.orderInfo.outNumber ? data.orderInfo.outNumber : ""}
</div>
<div style="margin-top: 30px;font-size: 12px;">
订单号:${data.orderInfo && data.orderInfo.orderNo}
</div>
<div style="margin-top: 4px;font-size: 12px;">
交易时间:${data.createdAt}
</div>
<div style="margin-top: 4px;font-size: 12px;">
收银员:${data.loginAccount}
</div>
<div style="margin-top: 6px;margin-bottom: 6px;width: 100%">
<hr/>
</div>
<table class="table" style="width: 100%;">
<tr>
<td style="font-size: 12px;width:${t1}%;">品名</td>
<td style="font-size: 12px;width:${t2}%;">单价</td>
<td style="font-size: 12px;width:${t2}%;">数量</td>
<td style="font-size: 12px;width:${t2}%;">小计</td>
</tr>
`;
let table = "";
for (let item of data.carts) {
if (item.proGroupInfo) {
table += `
<tr>
<td style="font-size: 12px;width:${t1}%" colspan="3">
<div>${item.name}</div>
</td>
<td style="font-size: 12px;width:${t2}%;">${item.totalAmount}</td>
</tr>
`;
let proGroupInfo = JSON.parse(item.proGroupInfo);
for (let item of proGroupInfo) {
table += `
<tr>
<td style="font-size: 12px;width:${t1}%;">
<div>>${item.proName}</div>
${
item.skuName
? `<div class="sku">规格:${item.skuName}</div>`
: ""
}
</td>
<td style="font-size: 12px;width:${t2}%;">0</td>
<td style="font-size: 12px;width:${t2}%;">${item.number}</td>
<td style="font-size: 12px;width:${t2}%;">0</td>
</tr>
`;
}
} else {
table += `
<tr>
<td style="font-size: 12px;width:${t1}%;">
<div>${item.name}</div>
${item.skuName ? `<div class="sku">规格:${item.skuName}</div>` : ""}
</td>
<td style="font-size: 12px;width:${t2}%;">${item.salePrice}</td>
<td style="font-size: 12px;width:${t2}%;">${item.number}</td>
<td style="font-size: 12px;width:${t2}%;">
${item.totalAmount}
</td>
</tr>
`;
}
}
let str = `
</table>
<div style="margin-top: 6px;margin-bottom: 6px;width: 100%">
<hr/>
</div>
<div style="margin-top: 6px; font-size: 12px;display:flex;justify-content: space-between;">
<span>原价</span>
<span>${data.amount}</span>
</div>
<div style="margin-top: 6px; font-size: 12px;display:flex;justify-content: space-between;">
<span>折扣</span>
<span>-${formatDecimal(data.amount - data.discountAmount)}</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.discountAmount}</span>
</div>
<div style="margin-top: 6px;margin-bottom: 6px;width: 100%">
<hr/>
</div>
<div style="margin-top: 4px; font-size: 16px;font-weight: bold;">备注:${
data.remark
}</div>
<div style="margin-top: 4px; font-size: 12px;">
打印时间:${data.printTime}
</div>
<div>.</div>
<div>.</div>
<div>.</div>
<div>.</div>
`;
let lastHtml = `${html}${table}${str}`;
setTimeout(() => {
LODOP.ADD_PRINT_HTM("9mm", "0mm", "RightMargin:0mm", 20, lastHtml);
LODOP.SET_LICENSES("", "DCFF409304DFCEB3E2C644BF96CD0720", "", "");
LODOP.PRINT();
}, 800);
};

View File

@@ -0,0 +1,96 @@
import getLodop from "./LodopFuncs.js";
/**
* 打印退单小票
*/
export default (data) => {
let LODOP = getLodop();
LODOP.PRINT_INIT("打印小票");
// 设置打印纸大小D
LODOP.SET_PRINT_PAGESIZE(3, "58mm", 20, "");
//设置默认打印机(这里用的是打印机名称)
LODOP.SET_PRINTER_INDEX(data.deviceName);
// 文字内容
let t1 = 40;
let t2 = (100 - t1) / 3;
let html = `
<div style="font-size: 30px;display:flex;justify-content:center;">
${data.shop_name}
</div>
<div style="font-size: 16px;display: flex; justify-content:center;margin-top:6px;">
退款单【${data.orderInfo.masterId ? data.orderInfo.masterId : ""}
</div>
<div style="margin-top: 30px;font-size: 12px;">
订单号:${data.orderInfo && data.orderInfo.orderNo}
</div>
<div style="margin-top: 4px;font-size: 12px;">
交易时间:${data.createdAt}
</div>
<div style="margin-top: 4px;font-size: 12px;">
收银员:${data.loginAccount}
</div>
<div style="margin-top: 6px;margin-bottom: 6px;width: 100%">
<hr/>
</div>
<table class="table" style="width: 100%;">
<tr>
<td style="font-size: 12px;width:${t1}%;">品名</td>
<td style="font-size: 12px;width:${t2}%;">单价</td>
<td style="font-size: 12px;width:${t2}%;">数量</td>
<td style="font-size: 12px;width:${t2}%;">小计</td>
</tr>
`;
let table = "";
for (let item of data.carts) {
table += `
<tr>
<td style="font-size: 12px;width:${t1}%;">
<div>${item.name}</div>
${
item.skuName
? `<div class="sku">规格:${item.skuName}</div>`
: ""
}
</td>
<td style="font-size: 12px;width:${t2}%;">${item.salePrice}</td>
<td style="font-size: 12px;width:${t2}%;">${item.number}</td>
<td style="font-size: 12px;width:${t2}%;">
${item.totalAmount}
</td>
</tr>
`;
}
let str = `
</table>
<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>
</div>
<div style="margin-top: 4px; font-size: 12px;">
<span>余额:</span>
<span>0.00</span>
</div>
<div style="margin-top: 6px;margin-bottom: 6px;width: 100%">
<hr/>
</div>
<div style="margin-top: 4px; font-size: 12px;">
打印时间:${data.printTime}
</div>
<div>.</div>
<div>.</div>
<div>.</div>
<div>.</div>
`;
let lastHtml = `${html}${table}${str}`;
setTimeout(() => {
LODOP.ADD_PRINT_HTM("9mm", "0mm", "RightMargin:0mm", 20, lastHtml);
LODOP.SET_LICENSES("", "DCFF409304DFCEB3E2C644BF96CD0720", "", "");
LODOP.PRINT();
}, 800);
};

View File

@@ -1,21 +1,17 @@
<template> <template>
<div class="drawerbox"> <div class="drawerbox">
<el-drawer <el-drawer size="60%" :with-header="false" direction="rtl" v-model="dialogVisible" style="padding: 0">
size="60%"
:with-header="false"
direction="rtl"
v-model="dialogVisible"
style="padding: 0"
>
<div class="drawerbox_box"> <div class="drawerbox_box">
<div class="drawerbox_bo_top"> <div class="drawerbox_bo_top">
<div class="drawerbox_bo_top_left"> <div class="drawerbox_bo_top_left">
<div class="drawerbox_bo_top_left_one"> <div class="drawerbox_bo_top_left_one" style="font-size: 24px;">
{{ store.userInfo.shopName }} {{ store.userInfo.shopName }}
</div> </div>
<div class="drawerbox_bo_top_left_tow" style="margin-top: 10px"> <div class="drawerbox_bo_top_left_tow" style="margin-top: 10px">
收银员{{ store.userInfo.userCode }} 收银员{{ store.userInfo.loginAccount }}
<span style="color: #666">{{ store.userInfo.loginTime }}</span> </div>
<div>
<span style="color: #666">{{ dayjs(store.userInfo.loginTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</div> </div>
</div> </div>
<!-- <div class="drawerbox_bo_top_ring"> <!-- <div class="drawerbox_bo_top_ring">
@@ -36,13 +32,21 @@
<div class="drawerbox_bo_box"> <div class="drawerbox_bo_box">
<div style="padding: 10px; color: #999; font-weight: bold">系统</div> <div style="padding: 10px; color: #999; font-weight: bold">系统</div>
<div class="drawerbox_bo_box_itemb_felx"> <div class="drawerbox_bo_box_itemb_felx">
<div class="drawerbox_bo_box_itembox"> <!-- <div class="drawerbox_bo_box_itembox">
<div class="drawerbox_bo_box_icon"> <div class="drawerbox_bo_box_icon">
<el-icon size="40"> <el-icon size="40">
<Setting /> <Setting />
</el-icon> </el-icon>
</div> </div>
<div class="drawerbox_bo_box_icontext">设置</div> <div class="drawerbox_bo_box_icontext">设置</div>
</div> -->
<div class="drawerbox_bo_box_itembox" @click="router.push({ name: 'device_list' })">
<div class="drawerbox_bo_box_icon">
<el-icon size="40">
<TurnOff />
</el-icon>
</div>
<div class="drawerbox_bo_box_icontext">设备管理</div>
</div> </div>
<div class="drawerbox_bo_box_itembox" @click="openCallHandle"> <div class="drawerbox_bo_box_itembox" @click="openCallHandle">
<div class="drawerbox_bo_box_icon"> <div class="drawerbox_bo_box_icon">
@@ -52,21 +56,10 @@
</div> </div>
<div class="drawerbox_bo_box_icontext">叫号</div> <div class="drawerbox_bo_box_icontext">叫号</div>
</div> </div>
<div
class="drawerbox_bo_box_itembox"
@click="router.push({ name: 'device_list' })"
>
<div class="drawerbox_bo_box_icon">
<el-icon size="40">
<TurnOff />
</el-icon>
</div>
<div class="drawerbox_bo_box_icontext">设备管理</div>
</div>
<div class="drawerbox_bo_box_itembox" @click="screenref.shows()"> <div class="drawerbox_bo_box_itembox" @click="screenref.shows()">
<div class="drawerbox_bo_box_icon"> <div class="drawerbox_bo_box_icon">
<el-icon size="40"> <el-icon size="40">
<Switch /> <Lock />
</el-icon> </el-icon>
</div> </div>
<div class="drawerbox_bo_box_icontext">锁屏</div> <div class="drawerbox_bo_box_icontext">锁屏</div>
@@ -100,6 +93,7 @@
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import { dayjs } from 'element-plus'
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import screen from "@/components/screen.vue"; import screen from "@/components/screen.vue";
@@ -214,7 +208,7 @@ defineExpose({
.drawerbox_bo_box_icon { .drawerbox_bo_box_icon {
border-radius: 6px; border-radius: 6px;
background: #2196f3; background-color: var(--primary-color);
width: 100px; width: 100px;
height: 100px; height: 100px;
display: flex; display: flex;

View File

@@ -3,17 +3,23 @@
<div class="header"> <div class="header">
<div class="t1"> <div class="t1">
<span class="title">应收:</span> <span class="title">应收:</span>
<span class="num">{{ props.amount }}</span> <span class="num">{{ money }}</span>
</div> </div>
<div class="t2"> <div class="t2">
<span>已付:0.00</span> <span>原价{{ formatDecimal(props.amount) }}</span>
<span>优惠:0.00</span> <span style="margin-left: 20px;">优惠{{ formatDecimal(props.amount - money) }}</span>
<span style="margin-left: 20px;" v-if="props.discount" @click="cancelDiscount">折扣{{
formatDecimal(props.discount * 10, 1, true) }}
<el-icon style="margin-left: 6px;">
<CircleClose />
</el-icon>
</span>
</div> </div>
</div> </div>
<div class="number_wrap"> <div class="number_wrap">
<div class="menus"> <div class="menus">
<div class="item" :class="{ active: payActive == index }" v-for="(item, index) in payList" <div class="item" :class="{ active: payActive == index, disabled: item.disabled }"
:key="item.id" @click="payTypeChange(index, item)"> v-for="(item, index) in payList" :key="item.id" @click="payTypeChange(index, item)">
<div class="icon"> <div class="icon">
<el-image :src="item.icon" class="img"></el-image> <el-image :src="item.icon" class="img"></el-image>
</div> </div>
@@ -22,7 +28,7 @@
</div> </div>
<div class="input_wrap"> <div class="input_wrap">
<div class="input" style="flex: 1;">储值:{{ money }}</div> <div class="input" style="flex: 1;">储值:{{ money }}</div>
<div class="input" v-if="waitPayMoney > 0">待支付:{{ waitPayMoney }}</div> <!-- <div class="input" v-if="waitPayMoney > 0">待支付:{{ waitPayMoney }}</div> -->
</div> </div>
<div class="blance"> <div class="blance">
<!-- 可用余额0.00 --> <!-- 可用余额0.00 -->
@@ -45,19 +51,53 @@
</div> </div>
</div> </div>
</div> </div>
<scanModal ref="scanModalRef" :amount="props.amount" :orderId="props.orderId" :selecttype="props.selecttype" <scanModal ref="scanModalRef" :amount="props.amount" :money="money" :orderId="props.orderId"
:payType="payType" @success="scanCodeSuccess" /> :selecttype="props.selecttype" :payType="payType" @success="scanCodeSuccess" />
<el-dialog :title="`选择会员`" top="3vh" v-model="showDialog" width="80%">
<el-form inline>
<el-form-item>
<el-input placeholder="请输入手机号搜索会员" v-model="tableData.phone" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getMemberList">搜索</el-button>
<el-button @click="resetTable">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData.list" height="440px" border stripe v-loading="tableData.loading">
<el-table-column prop="name" label="昵称" width="120px" />
<el-table-column prop="telephone" label="手机" width="150px" />
<el-table-column prop="code" label="编号" width="120px" />
<el-table-column prop="level" label="等级" />
<el-table-column prop="levelConsume" label="积分" />
<el-table-column prop="amount" label="余额" width="100px">
<template v-slot="scope">
{{ formatDecimal(scope.row.amount) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120px">
<template v-slot="scope">
<el-button type="primary" @click="toHomeMember(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination layout="prev, pager, next, total" background style="margin-top: 20px;"
:total="Number(tableData.total)" v-model:current-page="tableData.page" @current-change="getMemberList" />
</el-dialog>
</template> </template>
<script setup> <script setup>
import { onMounted, ref, computed, watch } from 'vue' import { onMounted, ref, computed, watch, reactive } from 'vue'
import { queryPayType, accountPay, cashPay } from '@/api/pay' import { queryPayType, accountPay, cashPay, vipPay } from '@/api/pay'
import { queryMembermember, createMembermember, membermemberScanPay, accountPaymember } from '@/api/member/index.js' import { queryMembermember, createMembermember, membermemberScanPay, accountPaymember } from '@/api/member/index.js'
import { useUser } from "@/store/user.js" import { useUser } from "@/store/user.js"
import { clearNoNum } from '@/utils' import { clearNoNum, formatDecimal } from '@/utils'
import scanModal from '@/components/payCard/scanModal.vue' import scanModal from '@/components/payCard/scanModal.vue'
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useGlobal } from '@/store/global.js'
import { staffPermission } from '@/api/user.js'
const global = useGlobal()
const store = useUser() const store = useUser()
@@ -74,26 +114,29 @@ const props = defineProps({
type: [String, Number], type: [String, Number],
default: '' default: ''
}, },
member: { discount: {
type: Object, type: [String, Number],
default: {} default: 0
} }
}) })
const emit = defineEmits(['paySuccess']) const emit = defineEmits(['paySuccess', 'cancelDiscount'])
const money = ref('0') const money = ref('0')
const scanModalRef = ref(null) const scanModalRef = ref(null)
watch(props, (value) => { watch(props, (value) => {
money.value = `${props.amount}` money.value = `${formatDecimal(props.amount)}`
if (props.discount > 0) {
money.value = `${formatDecimal(props.amount * props.discount)}`
}
}) })
const waitPayMoney = computed(() => { // const waitPayMoney = computed(() => {
let num = JSON.stringify(props.amount - money.value) // let num = JSON.stringify(props.amount - money.value)
num = Math.floor(num * 100) / 100 // num = Math.floor(num * 100) / 100
return num // return num
}) // })
const payActive = ref(0) const payActive = ref(0)
const payType = ref('') const payType = ref('')
@@ -106,25 +149,37 @@ function scanCodeSuccess() {
} }
// 切换支付类型 // 切换支付类型
function payTypeChange(index, item) { async function payTypeChange(index, item) {
payActive.value = index try {
payType.value = item.payType await staffPermission('yun_xu_shou_kuan')
if (item.payType == 'scanCode') { if (item.disabled) return
scanModalRef.value.show() payActive.value = index
} payType.value = item.payType
if (payList.value[payActive.value].payType == 'deposit' && !props.member.id) { if (item.payType == 'scanCode') {
scanModalRef.value.show() scanModalRef.value.show()
}
if (item.payType == 'vipPay') {
showDialog.value = true
getMemberList()
}
if (payList.value[payActive.value].payType == 'deposit' && !global.orderMemberInfo.id) {
scanModalRef.value.show()
}
} catch (error) {
console.log(error);
} }
} }
// 结算支付 // 结算支付
async function confirmOrder() { async function confirmOrder() {
if (payLoading.value) return
try { try {
await staffPermission('yun_xu_shou_kuan')
if (payLoading.value) return
if (payList.value[payActive.value].payType == 'scanCode') { if (payList.value[payActive.value].payType == 'scanCode') {
scanModalRef.value.show() scanModalRef.value.show()
} else { } else {
if (money.value < props.amount) return // if (money.value < props.amount) return
payLoading.value = true payLoading.value = true
switch (payList.value[payActive.value].payType) { switch (payList.value[payActive.value].payType) {
case 'deposit'://储值卡 case 'deposit'://储值卡
@@ -133,10 +188,10 @@ async function confirmOrder() {
// } else { // } else {
// } // }
if (props.member.id) { if (global.orderMemberInfo.id) {
await accountPay({ await accountPay({
orderId: props.orderId, orderId: props.orderId,
memberId: props.member.id, memberId: global.orderMemberInfo.id,
memberAccount: '' memberAccount: ''
}) })
} else { } else {
@@ -154,16 +209,18 @@ async function confirmOrder() {
}) })
} else { } else {
await cashPay({ await cashPay({
orderId: props.orderId orderId: props.orderId,
payAmount: props.discount > 0 ? money.value : '',
discountAmount: props.discount > 0 ? formatDecimal(props.amount - money.value) : ''
}) })
} }
break; break;
case 'bank'://银行卡 case 'vipPay':
if (props.selecttype == 1) {//1 代表会员 // 会员支付
console.log('使用会员id支付');
} else { payLoading.value = false
showDialog.value = true
} return
break; break;
default: default:
break; break;
@@ -203,8 +260,17 @@ async function queryPayTypeAjax() {
const res = await queryPayType({ const res = await queryPayType({
shopId: store.userInfo.shopId shopId: store.userInfo.shopId
}) })
res.map(item => {
if (props.amount <= 0 && item.payType == 'scanCode') {
item.disabled = true
} else {
item.disabled = false
}
})
payList.value = res payList.value = res
if (res[0].payType == 'scanCode' || res[0].payType == 'deposit') { if ((res[0].payType == 'scanCode' && !res[0].disabled) || res[0].payType == 'deposit') {
scanModalRef.value.show() scanModalRef.value.show()
payType.value = res[0].payType payType.value = res[0].payType
} }
@@ -213,8 +279,72 @@ async function queryPayTypeAjax() {
} }
} }
const showDialog = ref(false)
const tableData = reactive({
phone: '',
loading: false,
list: [],
page: 1,
size: 10,
total: 0
})
// 重置表格
function resetTable() {
tableData.phone = ''
tableData.page = 1
getMemberList()
}
// 获取会员列表
async function getMemberList() {
try {
tableData.loading = true
const res = await queryMembermember({
shopId: store.userInfo.shopId,
phone: tableData.phone,
page: tableData.page,
pageSize: tableData.size,
isFlag: 1
})
tableData.loading = false
tableData.list = res.list
tableData.total = res.total
} catch (error) {
console.log(error);
}
}
// 选择会员去下单
async function toHomeMember(row) {
try {
showDialog.value = false
payLoading.value = true
const res = await vipPay({
orderId: props.orderId,
vipUserId: row.id,
payAmount: props.discount > 0 ? money.value : '',
discountAmount: props.discount > 0 ? formatDecimal(props.amount - money.value) : ''
})
global.setOrderTable()
global.setOrderMember()
payLoading.value = false
ElMessage.success('支付成功')
emit('paySuccess')
} catch (error) {
payLoading.value = false
console.log(error);
}
}
// 取消折扣
function cancelDiscount() {
emit('cancelDiscount')
}
onMounted(() => { onMounted(() => {
money.value = `${props.amount}` money.value = `${formatDecimal(props.amount)}`
queryPayTypeAjax() queryPayTypeAjax()
}) })
@@ -248,9 +378,13 @@ onMounted(() => {
.t2 { .t2 {
display: flex; display: flex;
gap: var(--el-font-size-base);
color: #999; color: #999;
padding-top: 10px; padding-top: 10px;
span {
display: flex;
align-items: center;
}
} }
} }
@@ -274,6 +408,10 @@ onMounted(() => {
position: relative; position: relative;
$lineHeight: 4px; $lineHeight: 4px;
&.disabled {
filter: grayscale(1);
}
&.active { &.active {
&::after { &::after {
content: ""; content: "";

View File

@@ -10,7 +10,7 @@
<div class="right" v-if="!userPayWait"> <div class="right" v-if="!userPayWait">
<div class="amount"> <div class="amount">
<span class="t">扫码支付</span> <span class="t">扫码支付</span>
<span class="n">{{ props.amount }}</span> <span class="n">{{ props.money }}</span>
</div> </div>
<div class="input"> <div class="input">
<el-input ref="inputRef" v-model="scanCode" style="height: calc(var(--el-component-size-large) + 30px)" <el-input ref="inputRef" v-model="scanCode" style="height: calc(var(--el-component-size-large) + 30px)"
@@ -52,11 +52,12 @@
<script setup> <script setup>
import _ from "lodash"; import _ from "lodash";
import { ref } from "vue"; import { onMounted, ref } from "vue";
import icon from "@/assets/icon_scan.png"; import icon from "@/assets/icon_scan.png";
import { scanpay, queryOrder, quickPay, queryQuickPayStatus, accountPay, queryScanPay } from "@/api/pay"; import { scanpay, queryOrder, quickPay, queryQuickPayStatus, accountPay, queryScanPay } from "@/api/pay";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import { useGlobal } from '@/store/global.js' import { useGlobal } from '@/store/global.js'
import { formatDecimal } from '@/utils'
const store = useUser(); const store = useUser();
const global = useGlobal() const global = useGlobal()
import { import {
@@ -88,6 +89,10 @@ const props = defineProps({
payType: { payType: {
type: [Number, String], type: [Number, String],
default: "", default: "",
},
money: {
type: [Number, String],
default: 0,
} }
}); });
@@ -112,6 +117,8 @@ async function submitHandle() {
memberId: props.orderId, memberId: props.orderId,
amount: props.amount, amount: props.amount,
authCode: scanCode.value, authCode: scanCode.value,
// payAmount: props.money < props.amount ? props.money : '',
// discountAmount: props.money < props.amount ? formatDecimal(props.amount - props.money) : ''
}); });
} else { } else {
if (props.fast) { if (props.fast) {
@@ -125,13 +132,17 @@ async function submitHandle() {
await scanpay({ await scanpay({
orderId: props.orderId, orderId: props.orderId,
authCode: scanCode.value, authCode: scanCode.value,
payAmount: props.money < props.amount ? props.money : '',
discountAmount: props.money < props.amount ? formatDecimal(props.amount - props.money) : ''
}); });
} }
if (props.payType == 'deposit') { if (props.payType == 'deposit') {
await accountPay({ await accountPay({
orderId: props.orderId, orderId: props.orderId,
memberId: '', memberId: '',
memberAccount: scanCode.value memberAccount: scanCode.value,
payAmount: props.money < props.amount ? props.money : '',
discountAmount: props.money < props.amount ? formatDecimal(props.amount - props.money) : ''
}) })
} }
} }

View File

@@ -7,8 +7,8 @@
<div class="sku_wrap"> <div class="sku_wrap">
<!-- <div class="item" :class="{ active: val.active }" v-for="(val, i) in item.value" :key="i" <!-- <div class="item" :class="{ active: val.active }" v-for="(val, i) in item.value" :key="i"
@click="selectedSku(index, i)">{{ val.name }}</div> --> @click="selectedSku(index, i)">{{ val.name }}</div> -->
<el-button :plain="!val.active" type="primary" v-for="(val, i) in item.selectSpecResult <el-button :plain="!val.active" type="primary" v-for="(val, i) in item.selectSpecResult
" :key="i" @click="selectedSku(index, i)" class="btn">{{ val.name }}</el-button> " :key="i" :disabled="val.disabled" @click="selectedSku(index, i)" class="btn">{{ val.name }}</el-button>
</div> </div>
</div> </div>
</div> </div>
@@ -51,6 +51,8 @@ const goodsInfo = ref({})
const loading = ref(false) const loading = ref(false)
const selecSkuArray = ref([])
// 确认选择规格 // 确认选择规格
function submitSku() { function submitSku() {
dialogVisible.value = false dialogVisible.value = false
@@ -67,10 +69,48 @@ function submitSku() {
} }
// 选择规格 // 选择规格
function selectedSku(index, i) { function selectedSku(index = 0, i = 0) {
goods.value.selectSpec[index].selectSpecResult.map(item => { goods.value.selectSpec[index].selectSpecResult.map(item => {
item.active = false item.active = false
}) })
if (index == 0) {
selecSkuArray.value = []
}
if (selecSkuArray.value.length - 1 > index) {
// console.log(selecSkuArray.value.length - 1);
// console.log(index);
selecSkuArray.value.splice(index + 1, selecSkuArray.value.length)
}
selecSkuArray.value[index] = goods.value.selectSpec[index].selectSpecResult[i].name
if (index < goods.value.selectSpec.length - 1) {
selectedSkuNum.value = 0
goods.value.selectSpec.map((item, idx) => {
if (index < idx) {
item.selectSpecResult.map(val => {
val.disabled = true
val.active = false
})
}
})
goods.value.selectSpec[index + 1].selectSpecResult.map(item => {
goods.value.groundingSpecInfo.map(val => {
// console.log(val);
// console.log(`${selecSkuArray.value.join(',')},${item.name}`);
// console.log(val.specSnap.indexOf(`${selecSkuArray.value.join(',')},${item.name}`));
if (val.specSnap.indexOf(`${selecSkuArray.value.join(',')},${item.name}`) != -1 && val.isGrounding) {
item.disabled = false
}
})
})
}
if (goods.value.selectSpec[index].selectSpecResult[i].active) { if (goods.value.selectSpec[index].selectSpecResult[i].active) {
goods.value.selectSpec[index].selectSpecResult[i].active = false goods.value.selectSpec[index].selectSpecResult[i].active = false
selectedSkuNum.value-- selectedSkuNum.value--
@@ -78,6 +118,7 @@ function selectedSku(index, i) {
goods.value.selectSpec[index].selectSpecResult[i].active = true goods.value.selectSpec[index].selectSpecResult[i].active = true
selectedSkuNum.value++ selectedSkuNum.value++
} }
selectedSuccess() selectedSuccess()
} }
@@ -99,6 +140,8 @@ function selectedSuccess() {
if (selectedSkuNum.value >= goods.value.selectSpec.length) { if (selectedSkuNum.value >= goods.value.selectSpec.length) {
// 规格选完了 // 规格选完了
queryProductSkuAjax() queryProductSkuAjax()
} else {
goodsInfo.value = {}
} }
} }
@@ -134,14 +177,23 @@ function show(item, t = 'shop') {
goods.value = item goods.value = item
type.value = t type.value = t
goods.value.selectSpec = JSON.parse(goods.value.selectSpec) goods.value.selectSpec = JSON.parse(goods.value.selectSpec)
goods.value.selectSpec.map(item => { goods.value.selectSpec.map((item, index) => {
let arr = [] let arr = []
item.selectSpecResult.map(val => { item.selectSpecResult.map(val => {
switch (type.value) { switch (type.value) {
case 'shop': case 'shop':
let disabled = true
if (index == 0) {
goods.value.groundingSpecInfo.map(item => {
if (item.specSnap.indexOf(val) != -1 && item.isGrounding) {
disabled = false
}
})
}
arr.push({ arr.push({
active: false, active: false,
name: val name: val,
disabled: index == 0 ? disabled : true
}) })
break; break;
case 'cart': case 'cart':
@@ -149,7 +201,8 @@ function show(item, t = 'shop') {
const skus = goods.value.skuName.split(',') const skus = goods.value.skuName.split(',')
arr.push({ arr.push({
active: !!skus.find(item => item === val), active: !!skus.find(item => item === val),
name: val name: val,
disabled: true
}) })
break; break;
default: default:

View File

@@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
<el-button type="primary" style="width: 100%" @click="confirmHandle">确认</el-button> <el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
@@ -69,6 +69,7 @@ function delHandle() {
number.value = number.value.substring(0, number.value.length - 1); number.value = number.value.substring(0, number.value.length - 1);
} }
const loading = ref(false)
// 确认 // 确认
function confirmHandle() { function confirmHandle() {
if (!number.value) return if (!number.value) return
@@ -77,8 +78,12 @@ function confirmHandle() {
ElMessage.error('请输入正确的密码') ElMessage.error('请输入正确的密码')
return return
} else { } else {
loading.value = true
emit("success", number.value); emit("success", number.value);
dialogVisible.value = false; dialogVisible.value = false;
setTimeout(() => {
loading.value = false
}, 1000)
} }
} else { } else {
emit("success", number.value); emit("success", number.value);

View File

@@ -0,0 +1,96 @@
<template>
<el-dialog v-model="showDialog" title="发现新版本" width="500" :close-on-click-modal="false"
:close-on-press-escape="false" :show-close="false">
<div class="message">
{{ updataInfo.message }}
</div>
<div class="progress_wrap" style="padding-top: 20px;">
<el-progress :percentage="uploadPro" :stroke-width="15" striped :striped-flow="uploadPro < 100" />
</div>
<template #footer>
<div class="footer" style="padding: 0 20px 20px;">
<el-button v-if="!updataInfo.isUp && !isUpload" @click="closeHandle">下次更新</el-button>
<el-button type="primary" :loading="isUpload" @click="uplaodHandle">
<template v-if="!uploadSucess">
<template v-if="!isUpload">
立即更新
</template>
<template v-else>
下载中...
</template>
</template>
<template v-else>
立即安装
</template>
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { findVersion } from '@/api/user.js'
import packageData from "../../package.json";
import { ipcRenderer } from 'electron'
import useStorage from '@/utils/useStorage.js'
import { useUser } from "@/store/user.js";
const store = useUser()
const showDialog = ref(false)
const updataInfo = ref({})
const isUpload = ref(false)
const uploadPro = ref(0)
const uploadSucess = ref(false)
const uploadResponse = ref({})
const tempFilePath = ref('')
// 关闭更新弹窗,下次登录在提示
function closeHandle() {
showDialog.value = false
useStorage.set('updateFlag', true)
}
// 检查版本更新
async function findVersionAjax() {
try {
let updateFlag = useStorage.get('updateFlag')
const res = await findVersion()
let reg = /\./g;
if (res.version.replace(reg, '') > packageData.version.replace(reg, '') && res.url && !updateFlag) {
showDialog.value = true
updataInfo.value = res
}
} catch (error) {
console.log(error);
}
}
// 下载新版本
async function uplaodHandle() {
try {
if (!uploadSucess.value) {
isUpload.value = true
ipcRenderer.send('downloadFile', JSON.stringify({ url: updataInfo.value.url }))
// await downloadFile(updataInfo.value.url)
// isUpload.value = false
// uploadSucess.value = true
} else {
// 安装文件
}
} catch (error) {
console.log(error);
}
}
onMounted(() => {
if (store.userInfo) {
findVersionAjax()
}
ipcRenderer.on('updateProgress', (event, res) => {
uploadPro.value = res
})
})
</script>

View File

@@ -0,0 +1,114 @@
<template>
<el-upload ref="uploadRef" v-model:file-list="fileList" :action="uploadUrl" :headers="headers" :list-type="listType"
:multiple="multiple" :limit="limit" :on-exceed="handleExceed" :on-change="handleChange"
:on-progress="handleProgress" :on-success="handleSuccess" :on-error="handleError" :before-upload="beforeUpload"
:accept="accept" :disabled="disabled">
<el-icon>
<Plus />
</el-icon>
<template v-slot:tip>
<div v-if="tip">{{ tip }}</div>
</template>
</el-upload>
</template>
<script setup>
import { ref } from 'vue';
import useStorage from '@/utils/useStorage'
import { ElUpload, ElMessage } from 'element-plus';
const fileList = ref([])
// 定义接收的外部属性
const props = defineProps({
uploadUrl: {
type: String,
default: import.meta.env.MODE == 'development' ? '/api/shopInfo/upload' : import.meta.env.VITE_API_URL + '/shopInfo/upload',
},
headers: {
type: Object,
default: () => ({
token: useStorage.get("token"),
loginName: useStorage.get("userInfo").loginName,
clientType: 'pc'
}),
},
listType: {
type: String,
default: 'picture-card',
},
multiple: {
type: Boolean,
default: false,
},
limit: {
type: Number,
default: 1,
},
tip: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
accept: {
type: String,
default: 'image/jpeg,image/png,image/gif',
},
});
const emits = defineEmits(['success'])
// 内部引用上传组件实例
const uploadRef = ref(null);
// 处理文件超出数量限制的情况
const handleExceed = (files, fileList) => {
ElMessage.warning(`超出最大允许上传图片数量:${props.limit}`);
};
// 处理文件状态改变
const handleChange = (file, fileList) => {
console.log('图片文件状态改变', file, fileList);
};
// 处理上传进度
const handleProgress = (event, file, fileList) => {
console.log('图片上传进度', event.percent);
};
// 处理上传成功
const handleSuccess = (response, file, fileList) => {
ElMessage.success('图片上传成功');
console.log('图片上传成功响应', response);
emits('success', response.data)
};
// 处理上传失败
const handleError = (error, file, fileList) => {
ElMessage.error('图片上传失败');
console.error('图片上传失败原因', error);
};
// 上传前校验,这里主要校验图片格式
const beforeUpload = (file) => {
const isImage = props.accept.split(',').some(format => file.type === format.trim());
if (!isImage) {
ElMessage.error('请选择正确格式的图片文件');
return false;
}
return true;
};
function init(arr) {
fileList.value = arr
}
defineExpose({
init
})
</script>
<style scoped></style>

View File

@@ -57,6 +57,14 @@ const routes = [
}, },
component: () => import("@/views/member/index.vue"), component: () => import("@/views/member/index.vue"),
}, },
{
path: "/queue",
name: "queue",
meta: {
index: 1,
},
component: () => import("@/views/queue/index.vue"),
},
{ {
path: "/work", path: "/work",
name: "work", name: "work",

View File

@@ -5,11 +5,21 @@ export const useGlobal = defineStore({
state: () => ({ state: () => ({
// 是否监听叫号 // 是否监听叫号
isCallNumber: true, isCallNumber: true,
orderMemberInfo: {},
tableInfo: {},
}), }),
actions: { actions: {
// 更新状态 // 更新状态
updateData(state) { updateData(state) {
this.isCallNumber = state; this.isCallNumber = state;
}, },
// 设置订单会员信息
setOrderMember(obj) {
this.orderMemberInfo = obj;
},
// 设置订单台桌信息
setOrderTable(obj) {
this.tableInfo = obj;
},
}, },
}); });

View File

@@ -2,7 +2,12 @@ import { defineStore } from "pinia";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { bySubType } from "@/api/device"; import { bySubType } from "@/api/device";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import { useShop } from "@/store/shop.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import receiptPrint from "@/components/lodop/receiptPrint.js";
import lodopPrintWork from "@/components/lodop/lodopPrintWork.js";
import invoicePrint from "@/components/lodop/invoicePrint.js";
import refundPrint from "@/components/lodop/refundPrint.js";
export const usePrint = defineStore({ export const usePrint = defineStore({
id: "print", id: "print",
@@ -12,6 +17,8 @@ export const usePrint = defineStore({
deviceLableList: [], // 添加的打印机 deviceLableList: [], // 添加的打印机
labelList: [], // 要打印的队列数据 labelList: [], // 要打印的队列数据
printTimer: null, printTimer: null,
receiptList: [], // 小票队列数据
receiptTimer: null,
}), }),
actions: { actions: {
// 获取本地打印机和已添加的可以用打印机列表 // 获取本地打印机和已添加的可以用打印机列表
@@ -58,6 +65,8 @@ export const usePrint = defineStore({
}, },
// 打印标签小票 // 打印标签小票
labelPrint(props) { labelPrint(props) {
const shopInfo = useShop();
if ( if (
this.deviceLableList.length && this.deviceLableList.length &&
this.checkLocalPrint(this.deviceLableList[0].config.deviceName) this.checkLocalPrint(this.deviceLableList[0].config.deviceName)
@@ -91,6 +100,7 @@ export const usePrint = defineStore({
createdAt: dayjs(props.createdAt).format("YYYY-MM-DD HH:mm:ss"), createdAt: dayjs(props.createdAt).format("YYYY-MM-DD HH:mm:ss"),
isPrint: false, isPrint: false,
count: `${count}/${sum}`, count: `${count}/${sum}`,
ticketLogo: shopInfo.info.ticketLogo,
}); });
} }
} }
@@ -98,7 +108,7 @@ export const usePrint = defineStore({
// 执行打印操作 // 执行打印操作
this.startLabelPrint(); this.startLabelPrint();
} else { } else {
console.log("没有打印机"); console.log("没有标签打印机");
} }
}, },
// 开始打印标签数据 // 开始打印标签数据
@@ -119,5 +129,99 @@ export const usePrint = defineStore({
} }
}, 800); }, 800);
}, },
// 添加小票打印对列表数据
pushReceiptData(props, isDevice = true) {
console.log("pushReceiptData===", props);
if (!isDevice) {
// 测试打印,无需校验本地打印机
const store = useUser();
props.shop_name = store.userInfo.shopName;
props.loginAccount = store.userInfo.loginAccount;
props.createdAt = dayjs(props.createdAt).format("YYYY-MM-DD HH:mm:ss");
props.printTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
if (!props.orderInfo.masterId) {
props.orderInfo.masterId = props.orderInfo.tableName;
}
props.orderInfo.outNumber = props.outNumber;
this.receiptList.push(props);
this.startReceiptPrint();
} else {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].config.deviceName)
) {
const store = useUser();
props.deviceName = this.deviceNoteList[0].config.deviceName;
props.shop_name = store.userInfo.shopName;
props.loginAccount = store.userInfo.loginAccount;
props.createdAt = dayjs(props.createdAt).format(
"YYYY-MM-DD HH:mm:ss"
);
props.printTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
if (!props.orderInfo.masterId) {
props.orderInfo.masterId = props.orderInfo.tableName;
}
props.orderInfo.outNumber = props.outNumber;
if (!props.discountAmount) {
props.discountAmount = props.amount;
}
this.receiptList.push(props);
this.startReceiptPrint();
} else {
console.log("订单小票:没有小票打印机");
}
}
},
// 开始打印小票
startReceiptPrint() {
if (this.receiptTimer !== null) return;
this.receiptTimer = setInterval(() => {
if (!this.receiptList.length) {
clearInterval(this.receiptTimer);
this.receiptTimer = null;
} else {
receiptPrint(this.receiptList[0]);
this.receiptList.splice(0, 1);
}
}, 800);
},
// 打印交班小票
printWork(data) {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].config.deviceName)
) {
data.deviceName = this.deviceNoteList[0].config.deviceName;
lodopPrintWork(data);
} else {
console.log("交班小票:没有小票打印机");
}
},
// 打印订单发票
printInvoice(data) {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].config.deviceName)
) {
data.deviceName = this.deviceNoteList[0].config.deviceName;
invoicePrint(data);
} else {
console.log("订单发票:没有小票打印机");
}
},
// 打印退单小票
printRefund(data) {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].config.deviceName)
) {
data.deviceName = this.deviceNoteList[0].config.deviceName;
refundPrint(data);
} else {
console.log("退单小票:没有小票打印机");
}
},
}, },
}); });

22
src/store/shop.js Normal file
View File

@@ -0,0 +1,22 @@
import { defineStore } from "pinia";
import { queryShopInfo } from "@/api/user";
import useStorage from "@/utils/useStorage";
export const useShop = defineStore({
id: "shopInfo",
state: () => ({
info: useStorage.get("shopInfo"),
}),
actions: {
// 获取店铺信息
async queryShopInfo() {
try {
const res = await queryShopInfo();
useStorage.set("shopInfo", res);
this.info = useStorage.get("shopInfo");
} catch (error) {
console.log(error);
}
},
},
});

View File

@@ -1,4 +1,5 @@
import _ from "lodash"; import _ from "lodash";
import { dayjs } from "element-plus";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import { usePrint } from "@/store/print.js"; import { usePrint } from "@/store/print.js";
@@ -33,6 +34,7 @@ export const useSocket = defineStore({
// 关闭ws // 关闭ws
close() { close() {
console.log("关闭ws"); console.log("关闭ws");
this.online = false;
this.ws.close(1000); this.ws.close(1000);
this.ws = null; this.ws = null;
this.clearHeartBeat(); this.clearHeartBeat();
@@ -56,7 +58,14 @@ export const useSocket = defineStore({
if (this.ws == null) { if (this.ws == null) {
console.log("创建新的ws连接"); console.log("创建新的ws连接");
this.ws = new ReconnectingWebSocket(wsUrl);
const protocols = []; // 可选的子协议数组
const options = {
// 自动重新连接的选项(可选)
connectionTimeout: 1000,
maxRetries: 100,
};
this.ws = new ReconnectingWebSocket(wsUrl, protocols, options);
} else { } else {
console.log("重新连接ws"); console.log("重新连接ws");
this.wsReconnect(); this.wsReconnect();
@@ -96,6 +105,7 @@ export const useSocket = defineStore({
if (!this.orderList.some((el) => el == data.orderInfo.orderNo)) { if (!this.orderList.some((el) => el == data.orderInfo.orderNo)) {
// console.log("打印", data); // console.log("打印", data);
printStore.labelPrint(data); printStore.labelPrint(data);
printStore.pushReceiptData(data);
this.orderList.push(data.orderInfo.orderNo); this.orderList.push(data.orderInfo.orderNo);
if (this.orderList.length > 30) { if (this.orderList.length > 30) {
this.orderList.splice(0, 1); this.orderList.splice(0, 1);

View File

@@ -40,9 +40,10 @@ export function clearNoNum(obj) {
* 保留小数n位不进行四舍五入 * 保留小数n位不进行四舍五入
* num你传递过来的数字, * num你传递过来的数字,
* decimal你保留的几位,默认保留小数后两位 * decimal你保留的几位,默认保留小数后两位
* isInt 是否保留0
*/ */
export function formatDecimal(num, decimal = 2) { export function formatDecimal(num, decimal = 2, isInt = false) {
num = num.toString(); num = num.toFixed(3).toString();
const index = num.indexOf("."); const index = num.indexOf(".");
if (index !== -1) { if (index !== -1) {
num = num.substring(0, decimal + index + 1); num = num.substring(0, decimal + index + 1);
@@ -50,5 +51,48 @@ export function formatDecimal(num, decimal = 2) {
num = num.substring(0); num = num.substring(0);
} }
//截取后保留两位小数 //截取后保留两位小数
return parseFloat(num).toFixed(decimal); if (isInt) {
return parseFloat(num);
} else {
return parseFloat(num).toFixed(decimal);
}
}
/**
* 过滤input只能输入整数
* @param {*} value
* @returns
*/
export function inputFilterInt(value) {
if (!value) return;
return value.replace(/[^\d]/g, "");
}
/**
* 过滤input只能输入数字并且最多输入两位小数
* @param {*} value
* @returns
*/
export function inputFilterFloat(value) {
if (!value) return;
// 去除首位小数点
if (value.startsWith(".")) {
value = value.slice(1);
}
// 清除非数字和小数点(除了第一个小数点)
value = value.replace(/[^\d.]/g, "");
// 确保最多只有一个小数点
if (value.split(".").length > 2) {
value = value.split(".").slice(0, 2).join(".");
}
// 限制小数位数为两位
if (value.split(".")[1] && value.split(".")[1].length > 2) {
value = value.split(".")[0] + "." + value.split(".")[1].slice(0, 2);
}
// 限制首位只能输入一个0
if (value.startsWith("0") && value.length > 1 && value[1] === "0") {
// 如果首位是0且第二位也是0将第二个0及之后的内容清空
value = "0";
}
return value;
} }

61
src/utils/request_kp.js Normal file
View File

@@ -0,0 +1,61 @@
import axios from "axios";
import { ElMessage } from "element-plus";
import useStorage from "@/utils/useStorage";
import router from "@/router";
const service = axios.create({
baseURL:
import.meta.env.MODE == "development"
? "/kp/"
: import.meta.env.VITE_API_KP_URL,
// withCredentials: true, // 跨域请求时发送 cookies
timeout: 5000, // 请求超时
});
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
config.headers["pctoken"] = useStorage.get("token");
config.headers["ispc"] = 1;
config.headers["loginName"] = useStorage.get("userInfo").loginName;
config.headers["clientType"] = "pc";
config.headers["shopId"] = useStorage.get("userInfo").shopId;
return config;
},
(error) => {
// 处理请求错误
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
if (+response.status === 200) {
if (+response.data.code == 1) {
return response.data.data;
} else {
// 响应错误
ElMessage.error(response.data.msg);
return Promise.reject(response.data);
}
}
},
(error) => {
// 对响应错误做点什么
if (error.message.indexOf("timeout") != -1) {
ElMessage.error("网络超时");
} else if (error.message == "Network Error") {
ElMessage.error("网络连接错误");
} else {
console.log(error);
if (error.response.data) ElMessage.error(error.response.statusText);
else ElMessage.error("接口路径找不到");
}
return Promise.reject(error);
}
);
export default service;

View File

@@ -7,7 +7,7 @@ const service = axios.create({
baseURL: baseURL:
import.meta.env.MODE == "development" import.meta.env.MODE == "development"
? "/php/" ? "/php/"
: import.meta.env.VITE_API_URL, : import.meta.env.VITE_API_PHP_URL,
// withCredentials: true, // 跨域请求时发送 cookies // withCredentials: true, // 跨域请求时发送 cookies
timeout: 5000, // 请求超时 timeout: 5000, // 请求超时
}); });

View File

@@ -61,11 +61,11 @@
</div> </div>
<div class="menu_wrap"> <div class="menu_wrap">
<div class="print_view"> <div class="print_view">
<div class="title t1">溜溜</div> <div class="title t1">{{ printData.shop_name }}</div>
<div class="title t2">结算单#002</div> <div class="title t2">结算单{{ printData.orderInfo.masterId }}</div>
<div class="row">订单号:202404021023542223445</div> <div class="row">订单号{{ printData.orderInfo.orderNo }}</div>
<div class="row">交易时间:2024-04-02 10:15</div> <div class="row">交易时间{{ printData.createdAt }}</div>
<div class="row">收银员:POS-11</div> <div class="row">收银员{{ printData.loginAccount }}</div>
<div class="line"></div> <div class="line"></div>
<table class="table"> <table class="table">
<tr> <tr>
@@ -74,51 +74,31 @@
<td>数量</td> <td>数量</td>
<td>小计</td> <td>小计</td>
</tr> </tr>
<tr> <tr v-for="(item, index) in printData.carts" :key="index">
<td> <td>
<div>娃哈哈矿泉水</div> <div>{{ item.name }}</div>
<div class="sku">500ml</div> <div class="sku">{{ item.skuName }}</div>
</td> </td>
<td>1.0</td> <td>{{ item.salePrice }}</td>
<td>10</td> <td>{{ item.number }}</td>
<td>10</td> <td>{{ item.totalAmount }}</td>
</tr>
<tr>
<td>
<div>柠檬奶茶</div>
<div class="sku">加冰加珍珠</div>
</td>
<td>10.0</td>
<td>2</td>
<td>20</td>
</tr> </tr>
</table> </table>
<div class="line"></div> <div class="line"></div>
<div class="row between"> <div class="row between">
<span>合计:</span> <span>合计</span>
<span>30.00</span> <span>{{ printData.amount }}</span>
</div> </div>
<div class="row between"> <div class="row between">
<span>合计:</span> <span>余额</span>
<span>30.00</span>
</div>
<div class="row between">
<span>原价:20.00节省了0</span>
</div>
<div class="row between">
<span>积分:</span>
<span>0</span>
</div>
<div class="row between">
<span>余额:</span>
<span>0.00</span> <span>0.00</span>
</div> </div>
<div class="line"></div> <div class="line"></div>
<div class="row">备注:</div> <div class="row">备注{{ printData.remark }}</div>
<div class="row">打印时间:2024-04-02 10:15</div> <div class="row">打印时间{{ printData.printTime }}</div>
<div class="btn_wrap"> <div class="btn_wrap">
<div class="btn"> <div class="btn">
<el-button plain style="width: 100%" @click="printHandle"> <el-button plain style="width: 100%" :loading="printDataLoading" @click="printHandle">
打印测试小票 打印测试小票
</el-button> </el-button>
</div> </div>
@@ -137,7 +117,7 @@
<script setup> <script setup>
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { onMounted, ref } from "vue"; import { onMounted, reactive, ref } from "vue";
import { useRouter, useRoute } from "vue-router"; import { useRouter, useRoute } from "vue-router";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { tbPrintMachinePost, tbPrintMachineDetail } from "@/api/device"; import { tbPrintMachinePost, tbPrintMachineDetail } from "@/api/device";
@@ -175,6 +155,42 @@ const form = ref({
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
}); });
const printDataLoading = ref(false)
const printData = reactive({
shop_name: store.userInfo.shopName,
loginAccount: store.userInfo.loginAccount,
isBefore: true,
carts: [
{
id: 1,
name: '【测试】娃哈哈矿泉水',
skuName: '500ml',
salePrice: '1.0',
number: '10',
totalAmount: '10'
},
{
id: 2,
name: '【测试】柠檬奶茶',
skuName: '加冰、加珍珠',
salePrice: '10',
number: '2',
totalAmount: '20'
}
],
amount: '30.00',
discountAmount: '30.00',
discount: 0,
remark: '给我多放点辣椒,谢谢老板',
orderInfo: {
masterId: '#002',
orderNo: '202404021023542223445'
},
deviceName: '',
createdAt: '2024-04-02 10:15',
printTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
});
// 获取打印机列表 // 获取打印机列表
function getPrintList() { function getPrintList() {
ipcRenderer.send("getPrintList"); ipcRenderer.send("getPrintList");
@@ -189,43 +205,13 @@ function printHandle() {
ElMessage.warning("请选择打印设备"); ElMessage.warning("请选择打印设备");
return; return;
} }
printDataLoading.value = true
// ipcRenderer.send( printData.deviceName = form.value.config.deviceName
// "printStart", printData.printTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
// JSON.stringify({ deviceName: form.value.config.deviceName }) printStore.pushReceiptData(printData, false)
// ); setTimeout(() => {
printDataLoading.value = false
const data = { }, 1500)
shop_name: '溜溜',
carts: [
{
id: 1,
name: '娃哈哈矿泉水',
skuName: '500ml',
salePrice: '1.0',
number: '10',
totalAmount: '10'
},
{
id: 2,
name: '柠檬奶茶',
skuName: '加冰、加珍珠',
salePrice: '10',
number: '2',
totalAmount: '20'
}
],
amount: '30.00',
remark: '',
orderInfo: {
masterId: '#002',
orderNo: '202404021023542223445'
},
deviceName: form.value.config.deviceName,
createdAt: '2024-04-02 10:15',
printTime: '2024-04-02 10:15',
};
ipcRenderer.send("printerInfoSync", JSON.stringify(data));
} }
// 提交打印机 // 提交打印机

View File

@@ -78,7 +78,7 @@
<div class="print_view"> <div class="print_view">
<canvas class="ewm" ref="canvasRef"></canvas> <canvas class="ewm" ref="canvasRef"></canvas>
<div class="header"> <div class="header">
<img class="logo" src="../../assets/prinnt_label_logo.png" /> <img class="logo" :src="shopInfo.info.ticketLogo" />
<!-- <span class="title">双屿Pisces</span> --> <!-- <span class="title">双屿Pisces</span> -->
</div> </div>
<div class="number_wrap"> <div class="number_wrap">
@@ -122,6 +122,7 @@ import { Loading } from "element-plus/es/components/loading/src/service";
import classify from "@/components/classify/index.vue"; import classify from "@/components/classify/index.vue";
import QRCode from 'qrcode' import QRCode from 'qrcode'
import { usePrint } from "@/store/print.js"; import { usePrint } from "@/store/print.js";
import { useShop } from "@/store/shop.js";
const printStore = usePrint(); const printStore = usePrint();
@@ -129,6 +130,7 @@ const store = useUser();
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const shopInfo = useShop();
const classifyRef = ref(null); const classifyRef = ref(null);
const printList = ref([]); const printList = ref([]);
@@ -159,10 +161,11 @@ const canvasRef = ref(null)
const printData = ref({ const printData = ref({
deviceName: '', deviceName: '',
outNumber: '123', outNumber: '123',
name: '甜橙马黛茶', name: '【测试勿管】甜橙马黛茶',
skuName: '加奶、加珍珠', skuName: '测试、加珍珠',
masterId: '#A9', masterId: '#A9',
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss') createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
ticketLogo: shopInfo.info.ticketLogo,
}) })
// 获取打印机列表 // 获取打印机列表
@@ -179,6 +182,7 @@ const printHandle = _.throttle(function () {
ElMessage.error("请选择打印设备"); ElMessage.error("请选择打印设备");
return; return;
} }
printData.value.deviceName = form.value.config.deviceName
ipcRenderer.send( ipcRenderer.send(
"printerTagSync", "printerTagSync",
JSON.stringify(printData.value) JSON.stringify(printData.value)
@@ -283,6 +287,8 @@ onMounted(() => {
.logo { .logo {
$size: 90px; $size: 90px;
width: $size; width: $size;
height: 30px;
object-fit: cover;
} }
.title { .title {

View File

@@ -88,7 +88,7 @@
<div class="intro">用来打印商品标签的打印机</div> <div class="intro">用来打印商品标签的打印机</div>
</div> </div>
</div> </div>
<div class="row"> <!-- <div class="row">
<div class="icon" style="background-color: #8fc783"> <div class="icon" style="background-color: #8fc783">
<el-image :src="icons.kitchen" style="width: 44px; height: 44px"></el-image> <el-image :src="icons.kitchen" style="width: 44px; height: 44px"></el-image>
</div> </div>
@@ -96,7 +96,7 @@
<div class="name">添加出品打印机</div> <div class="name">添加出品打印机</div>
<div class="intro">用来打印商品至厨房或出品台的打印机</div> <div class="intro">用来打印商品至厨房或出品台的打印机</div>
</div> </div>
</div> </div> -->
</div> </div>
</div> </div>
</div> </div>
@@ -141,10 +141,10 @@ const deviceRoute = ref({
}); });
async function statusChange(e, item) { async function statusChange(e, item) {
console.log(e, item);
try { try {
await tbPrintMachinePost(item, "put"); await tbPrintMachinePost(item, "put");
tbPrintMachineGetAjax(); tbPrintMachineGetAjax();
printStore.init();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -0,0 +1,104 @@
<template>
<el-dialog title="绑定门店" v-model="showDialog" width="80%">
<div class="dialog">
<div class="tips">注意门店绑定后无法更改请谨慎选择</div>
<el-table :data="tableData.list" height="240px" border v-loading="tableData.loading">
<el-table-column label="门店名称" prop="poi_name"></el-table-column>
<el-table-column label="门店地址" prop="address"></el-table-column>
<el-table-column label="操作" width="120px">
<template v-slot="scope">
<el-button type="primary" size="small"
@click="bindShopHandle(scope.row.poi_id)">选择门店</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination @current-change="paginationChange" :current-page="tableData.page"
:page-size="tableData.size" layout="total, prev, pager, next, jumper" :total="tableData.total"
background>
</el-pagination>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue';
import { douyinstorelist, douyinbindstore } from '@/api/group.js'
const emits = defineEmits(['success'])
const showDialog = ref(false)
const tableData = reactive({
page: 1,
size: 10,
total: 0,
loading: false,
list: []
})
// 绑定门店
async function bindShopHandle(poi_id) {
try {
await douyinbindstore({
poi_id: poi_id
})
showDialog.value = false
ElMessage.success('绑定成功')
emits('success')
} catch (error) {
console.log(error);
}
}
// 分页变化
function paginationChange(e) {
tableData.page = e
getTableData()
}
// 获取门店列表
async function getTableData() {
try {
tableData.loading = true
const { list, count } = await douyinstorelist({
page: tableData.page,
size: tableData.size
})
tableData.loading = false
tableData.list = list
tableData.total = count
} catch (error) {
console.log(error);
}
}
// 显示
function show() {
showDialog.value = true;
getTableData()
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.tips {
color: var(--el-color-danger);
padding-bottom: 14px;
}
.pagination {
display: flex;
padding: 0 14px;
margin-top: 14px;
}
.dialog {
padding: 14px;
}
</style>

View File

@@ -2,7 +2,7 @@
<template> <template>
<div class="dialog"> <div class="dialog">
<el-dialog :title="`核销${props.title}团购券`" width="600" v-model="dialogVisible" @open="reset"> <el-dialog :title="`核销${props.title}团购券`" width="600" v-model="dialogVisible" @open="reset" @close="close">
<div class="content"> <div class="content">
<div class="left"> <div class="left">
<el-image :src="icon" style="width: 60px; height: 60px"></el-image> <el-image :src="icon" style="width: 60px; height: 60px"></el-image>
@@ -79,8 +79,7 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-table ref="douyin_table" :data="groupDetail.goods" border v-if="props.type == 2" <el-table ref="douyin_table" :data="groupDetail.goods" border v-else>
@selection-change="douyinSelectionChange">
<el-table-column type="selection" width="55" /> <el-table-column type="selection" width="55" />
<el-table-column label="名称" prop="title"></el-table-column> <el-table-column label="名称" prop="title"></el-table-column>
<el-table-column label="价格" prop="amount"></el-table-column> <el-table-column label="价格" prop="amount"></el-table-column>
@@ -93,6 +92,7 @@
@click="groupOrdergroupScanHandle">确认核销</el-button> @click="groupOrdergroupScanHandle">确认核销</el-button>
</div> </div>
</el-dialog> </el-dialog>
<BindShop ref="BindShopRef" @success="submitHandle()" />
</div> </div>
</template> </template>
@@ -100,8 +100,10 @@
import _ from "lodash"; import _ from "lodash";
import { ref } from "vue"; import { ref } from "vue";
import icon from "@/assets/icon_scan.png"; import icon from "@/assets/icon_scan.png";
import { groupOrderorderInfo, groupOrdergroupScan, douyinfulfilmentcertificateprepare, douyincertificateprepare } from '@/api/group' import { groupOrderorderInfo, groupOrdergroupScan, douyinfulfilmentcertificateprepare, douyincertificateprepare, thirdPartyCoupon_list, certificateprepare } from '@/api/group'
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import BindShop from './bindShop.vue'
const BindShopRef = ref(null)
const store = useUser(); const store = useUser();
import { import {
queryMembermember, queryMembermember,
@@ -110,6 +112,10 @@ import {
accountPaymember, accountPaymember,
} from "@/api/member/index.js"; } from "@/api/member/index.js";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useGlobal } from '@/store/global.js'
const global = useGlobal()
const emits = defineEmits(["success"]); const emits = defineEmits(["success"]);
const props = defineProps({ const props = defineProps({
@@ -151,15 +157,39 @@ async function groupOrdergroupScanHandle() {
break; break;
case 2: case 2:
{ {
groupDetailLoading.value = true let encrypted_codes = douyin_table.value.getSelectionRows()
let encrypted_codes = groupDetail.value.goods.map(item => item.encrypted_code) if (encrypted_codes.length) {
const res = await douyincertificateprepare({ groupDetailLoading.value = true
verify_token: groupDetail.value.verify_token, let arr = encrypted_codes.map(item => item.encrypted_code)
encrypted_codes: encrypted_codes.join(','), console.log(encrypted_codes);
id: groupDetail.value.id const res = await douyincertificateprepare({
}) verify_token: groupDetail.value.verify_token,
encrypted_codes: arr.join(','),
id: groupDetail.value.id
})
} else {
ElMessage.error('请选择核销项目')
return
}
} }
break; break;
case 3:
// 美团团购
{
let encrypted_codes = douyin_table.value.getSelectionRows()
if (encrypted_codes.length) {
groupDetailLoading.value = true
let arr = encrypted_codes.map(item => item.encrypted_code)
const res = await certificateprepare({
couponCode: groupDetail.value.couponCode,
num: encrypted_codes.length
})
} else {
ElMessage.error('请选择核销项目')
return
}
}
break
default: default:
break; break;
} }
@@ -171,17 +201,12 @@ async function groupOrdergroupScanHandle() {
emits('succcess') emits('succcess')
} catch (error) { } catch (error) {
groupDetailLoading.value = false groupDetailLoading.value = false
console.log(error); console.log('groupOrdergroupScanHandle.error', error);
} }
} }
const douyin_table = ref(null) const douyin_table = ref(null)
// 选择要核销的券
function douyinSelectionChange(e) {
console.log(e);
}
// 核销券码 // 核销券码
async function submitHandle() { async function submitHandle() {
try { try {
@@ -202,6 +227,24 @@ async function submitHandle() {
const res = await douyinfulfilmentcertificateprepare({ const res = await douyinfulfilmentcertificateprepare({
object_id: decodeURI(scanCode.value), object_id: decodeURI(scanCode.value),
}); });
dialogVisible.value = false
loading.value = false
groupDetail.value = res
detailVisible.value = true
setTimeout(() => {
groupDetail.value.goods.map(item => {
douyin_table.value.toggleRowSelection(item)
})
}, 100)
}
break;
case 3:
{
const res = await thirdPartyCoupon_list({
shopId: store.userInfo.shopId,
code: scanCode.value
});
dialogVisible.value = false
loading.value = false loading.value = false
groupDetail.value = res groupDetail.value = res
detailVisible.value = true detailVisible.value = true
@@ -217,7 +260,10 @@ async function submitHandle() {
} }
} catch (error) { } catch (error) {
loading.value = false loading.value = false
console.log(error); console.log('submitHandle.error', error);
if (error.code == 4399) {
BindShopRef.value.show()
}
} }
} }
@@ -255,6 +301,7 @@ const inputChange = _.debounce(function (e) {
}, 500); }, 500);
function show() { function show() {
global.updateData(false)
dialogVisible.value = true; dialogVisible.value = true;
setTimeout(() => { setTimeout(() => {
inputRef.value.focus(); inputRef.value.focus();
@@ -262,6 +309,7 @@ function show() {
} }
function close() { function close() {
global.updateData(true)
dialogVisible.value = false; dialogVisible.value = false;
} }
@@ -274,6 +322,7 @@ defineExpose({
show, show,
close, close,
loading, loading,
submitHandle
}); });
</script> </script>

View File

@@ -3,7 +3,7 @@
<div class="cart_wrap card"> <div class="cart_wrap card">
<div class="header"> <div class="header">
<div class="left"> <div class="left">
<el-select v-model="tableData.type" placeholder="核销类型"> <el-select v-model="tableData.type" placeholder="核销类型" @change="typeChange">
<el-option v-for="item in typeList" :key="item.value" :value="item.value" <el-option v-for="item in typeList" :key="item.value" :value="item.value"
:label="item.label"></el-option> :label="item.label"></el-option>
</el-select> </el-select>
@@ -19,7 +19,7 @@
@click="resetHandle">重置</el-button> @click="resetHandle">重置</el-button>
</div> </div>
</div> </div>
<el-button type="warning" :icon="FullScreen" @click="scanGroupRef.show()">核销团购券</el-button> <el-button type="warning" :icon="FullScreen" @click="showScanModalHandle">核销团购券</el-button>
</div> </div>
<div class="tab_container"> <div class="tab_container">
<el-table :data="tableData.list" height="540px" v-loading="tableData.loading" <el-table :data="tableData.list" height="540px" v-loading="tableData.loading"
@@ -79,14 +79,16 @@
<template v-slot="scope"> <template v-slot="scope">
<div class="goods_list"> <div class="goods_list">
<div class="row" v-for="item in scope.row.douyinCodeGoods" :key="item.id"> <div class="row" v-for="item in scope.row.douyinCodeGoods" :key="item.id">
<div class="item" style="flex: 1;white-space: nowrap;margin-right: 10px;"> <div class="item" style="width: 240px;">
{{ item.title }} {{ item.title }}
</div> </div>
<div class="item" style="flex: 1;margin-right: 10px;color: var(--primary-color);"> <div class="item" style="width: 100px;color: var(--primary-color);">
{{ item.pay_amount }} {{ item.pay_amount }}
</div> </div>
<div class="item" style="margin-right: 10px;"> <div class="item"
<el-tag type="success" disable-transitions size="default" effect="light" round> style="margin-right: 10px;display: flex;justify-content: flex-end;">
<el-tag :type="typeStatus(item.status)" disable-transitions size="default"
effect="light" round>
{{ item.status_text }} {{ item.status_text }}
</el-tag> </el-tag>
</div> </div>
@@ -99,28 +101,59 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-table height="540px" :data="tableData.list" v-loading="tableData.loading"
v-if="tableData.type == 3">
<el-table-column label="名称" prop="dealTitle"></el-table-column>
<el-table-column label="总金额" prop="couponBuyPrice" width="100">
<template v-slot="scope">
<span style="color: var(--primary-color);">{{ scope.row.couponBuyPrice }}</span>
</template>
</el-table-column>
<el-table-column label="状态" prop="couponStatusDesc" width="150"></el-table-column>
<el-table-column label="使用时间" prop="couponUseTime" width="200"></el-table-column>
<el-table-column label="操作" prop="douyinCodeGoods" width="100">
<template v-slot="scope">
<el-button type="danger" size="small" @click="cacelMeittuanHandle(scope.row)">
撤销
</el-button>
</template>
</el-table-column>
</el-table>
</div> </div>
<div class="pagination"> <div class="pagination">
<el-pagination @current-change="paginationChange" :current-page="tableData.page" <el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.size"
:page-size="tableData.size" layout="total, prev, pager, next, jumper" :total="tableData.total" layout="total, prev, pager, next" :total="tableData.total" background
background> @current-change="paginationChange" @size-change="paginationChange">
</el-pagination> </el-pagination>
</div> </div>
</div> </div>
<scanGroup ref="scanGroupRef" :title="typeList.find(item => item.value == tableData.type).label" <scanGroup ref="scanGroupRef" :title="typeList.find(item => item.value == tableData.type).label"
:type="tableData.type" @succcess="groupOrderlistAjax" /> :type="tableData.type" @succcess="groupOrderlistAjax" />
<refundDialog ref="refundDialogRef" @success="groupOrderlistAjax" /> <refundDialog ref="refundDialogRef" @success="groupOrderlistAjax" />
<el-dialog v-model="showMeituanUrlModal" title="注意">
<span style="font-size: 18px;">您的店铺还未绑定美团请绑定后操作</span>
<template #footer>
<div class="dialog-footer" style="padding: 0 15px 15px;">
<el-button @click="showMeituanUrlModal = false">取消</el-button>
<el-button type="primary" @click="openMeituan">
去绑定
</el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { groupOrderlist, douyinorderlist, douyinfulfilmentcertificatecancel } from '@/api/group' import { groupOrderlist, douyinorderlist, douyinfulfilmentcertificatecancel, thirdPartyCoupon_state, thirdPartyCoupon_bindUrl, meituan_orderlist, meituan_fulfilmentcertificatecancel } from '@/api/group'
import { Search, RefreshRight, FullScreen } from '@element-plus/icons-vue' import { Search, RefreshRight, FullScreen } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import { ref, onMounted, reactive } from 'vue' import { ref, onMounted, reactive } from 'vue'
import scanGroup from './components/scanGroup.vue' import scanGroup from './components/scanGroup.vue'
import refundDialog from './components/refundDialog.vue' import refundDialog from './components/refundDialog.vue'
import { useUser } from "@/store/user.js" import { useUser } from "@/store/user.js"
import BindShop from './components/bindShop.vue'
import { shell } from 'electron'
const store = useUser() const store = useUser()
import { useGlobal } from '@/store/global.js' import { useGlobal } from '@/store/global.js'
@@ -129,10 +162,19 @@ const global = useGlobal()
const scanGroupRef = ref(null) const scanGroupRef = ref(null)
const refundDialogRef = ref(null) const refundDialogRef = ref(null)
function typeStatus(t) {
const m = {
0: 'warning',
1: 'success',
2: 'danger'
}
return m[t]
}
const tableData = reactive({ const tableData = reactive({
resetLoading: false, resetLoading: false,
proName: '', proName: '',
type: 1, type: 3,
status: '', status: '',
loading: false, loading: false,
list: [], list: [],
@@ -159,10 +201,15 @@ const typeList = reactive([
{ {
value: 2, value: 2,
label: '抖音' label: '抖音'
},
{
value: 3,
label: '美团'
} }
]) ])
const statusList = reactive([ // 本店团购订单状态
const originStatus = [
{ {
value: 'unpaid', value: 'unpaid',
label: '待付款' label: '待付款'
@@ -187,16 +234,103 @@ const statusList = reactive([
value: 'cancelled', value: 'cancelled',
label: '已取消' label: '已取消'
} }
]) ]
// 抖音美团
const dmStatus = [
{
value: 0,
label: '等待验券'
},
{
value: 1,
label: '成功'
},
{
value: 2,
label: '失败'
}
]
const statusList = ref([])
// 切换筛选条件
function typeChange(e) {
switch (e) {
case 1:
statusList.value = [...originStatus]
break;
case 2:
statusList.value = [...dmStatus]
break;
case 3:
statusList.value = [...dmStatus]
thirdPartyCoupon_state_ajax()
break;
default:
break;
}
tableData.status = ''
tableData.page = 1
tableData.list = []
groupOrderlistAjax()
}
// 获取美团绑定状态
const meituanStatus = ref(false)
async function thirdPartyCoupon_state_ajax() {
try {
const res = await thirdPartyCoupon_state({
shopId: store.userInfo.shopId
})
if (res.status == 0) {
meituanStatus.value = false
showMeituanUrlModal.value = true
thirdPartyCoupon_bindUrl_ajax()
} else {
meituanStatus.value = true
}
} catch (error) {
console.log(error);
}
}
// 获取美团绑定链接
const meituanURL = ref('')
const showMeituanUrlModal = ref(false)
async function thirdPartyCoupon_bindUrl_ajax() {
try {
const res = await thirdPartyCoupon_bindUrl({
shopId: store.userInfo.shopId
})
meituanURL.value = res
} catch (error) {
console.log(error);
}
}
// 确认打开绑定美团链接
function openMeituan() {
showMeituanUrlModal.value = false
shell.openExternal(meituanURL.value);
}
function showScanModalHandle() {
// 若果是美团并且没有绑定,则需要先绑定
if (tableData.type == 3 && !meituanStatus.value) {
showMeituanUrlModal.value = true
return
}
scanGroupRef.value.show()
}
// 状态 // 状态
function statusFilter(t) { function statusFilter(t) {
return statusList.find(item => item.value == t)?.label return originStatus.find(item => item.value == t)?.label
} }
// 分页变化 // 分页变化
function paginationChange(e) { function paginationChange(e) {
tableData.page = e
groupOrderlistAjax() groupOrderlistAjax()
} }
@@ -220,6 +354,21 @@ function cacelDouyinHandle(item) {
}).catch(() => { }) }).catch(() => { })
} }
// 显示美团团购撤销
function cacelMeittuanHandle(item) {
ElMessageBox.confirm(
'是否撤销该团购?',
'注意').then(async () => {
try {
await meituan_fulfilmentcertificatecancel({ couponCode: item.couponCode })
ElMessage.success('撤销成功')
groupOrderlistAjax()
} catch (error) {
console.log(error);
}
}).catch(() => { })
}
// 获取团购订单数据 // 获取团购订单数据
async function groupOrderlistAjax() { async function groupOrderlistAjax() {
try { try {
@@ -243,7 +392,22 @@ async function groupOrderlistAjax() {
case 2: case 2:
// 获取抖音团购数据 // 获取抖音团购数据
res = await douyinorderlist({ res = await douyinorderlist({
page: tableData.page page: tableData.page,
status: tableData.status,
d_order_id: tableData.proName
})
tableData.resetLoading = false
tableData.loading = false
tableData.list = res.list
tableData.total = res.count
break;
case 3:
// 获取美团购数据
res = await meituan_orderlist({
page: tableData.page,
// status: tableData.status,
// d_order_id: tableData.proName,
date: ''
}) })
tableData.resetLoading = false tableData.resetLoading = false
tableData.loading = false tableData.loading = false
@@ -260,7 +424,7 @@ async function groupOrderlistAjax() {
} }
onMounted(() => { onMounted(() => {
groupOrderlistAjax() typeChange(tableData.type)
}) })
</script> </script>

View File

@@ -6,18 +6,24 @@
</el-icon> </el-icon>
</div> </div>
<div class="item number" @click="props.item.id && takeFoodCodeRef.show()"> <div class="item number" @click="props.item.id && takeFoodCodeRef.show()">
<el-text class="num">{{ props.item.number || 1 }}</el-text> <el-text class="num">{{ formatDecimal(props.item.number || 1, 2, true) }}</el-text>
</div> </div>
<div class="item" @click="numberChange('add')"> <div class="item" @click="numberChange('add')">
<el-icon class="icon add"> <el-icon class="icon add">
<CloseBold /> <CloseBold />
</el-icon> </el-icon>
</div> </div>
<div class="item" :class="{ disabled: (props.item.id && !props.item.tbProductSpec) }" @click="showSkuModal"> <!-- <div class="item" :class="{ disabled: (props.item.id && !props.item.tbProductSpec) }" @click="showSkuModal">
<el-icon class="icon"> <el-icon class="icon">
<Filter /> <Filter />
</el-icon> </el-icon>
<el-text class="t">规格</el-text> <el-text class="t">规格</el-text>
</div> -->
<div class="item" @click="showDiscountModalHandle">
<el-icon class="icon">
<PriceTag />
</el-icon>
<el-text class="t">打折</el-text>
</div> </div>
<div class="item" :class="{ disabled: props.item.isGift == 'true' }" @click="giftPackHandle('isGift')"> <div class="item" :class="{ disabled: props.item.isGift == 'true' }" @click="giftPackHandle('isGift')">
<el-icon class="icon"> <el-icon class="icon">
@@ -25,12 +31,19 @@
</el-icon> </el-icon>
<el-text class="t">赠送</el-text> <el-text class="t">赠送</el-text>
</div> </div>
<div class="item" :class="{ disabled: props.item.isPack == 'true' }" @click="giftPackHandle('isPack')"> <div class="item" :class="{ disabled: props.item.isPack == 'true' }" @click="giftPackHandle('isPack')"
v-if="JSON.parse(shopStore.info.eatModel).some(item => item == 'take-out')">
<el-icon class="icon"> <el-icon class="icon">
<Box /> <Box />
</el-icon> </el-icon>
<el-text class="t">打包</el-text> <el-text class="t">打包</el-text>
</div> </div>
<div class="item" :class="{ disabled: props.item.isPrint == 0 }" @click="kitchenPrint">
<el-icon class="icon">
<DishDot />
</el-icon>
<el-text class="t">免厨</el-text>
</div>
<div class="item" @click="props.item.id && emit('delete', props.item)"> <div class="item" @click="props.item.id && emit('delete', props.item)">
<el-icon class="icon"> <el-icon class="icon">
<Delete /> <Delete />
@@ -53,12 +66,51 @@
<takeFoodCode ref="takeFoodCodeRef" title="修改商品数量" placeholder="请输入商品数量" @success="updateNumber" /> <takeFoodCode ref="takeFoodCodeRef" title="修改商品数量" placeholder="请输入商品数量" @success="updateNumber" />
<!-- 购物车选择规格 --> <!-- 购物车选择规格 -->
<skuModal ref="skuModalRef" @success="skuConfirm" /> <skuModal ref="skuModalRef" @success="skuConfirm" />
<!-- 单品打折 -->
<el-dialog v-model="showDiscountModal" title="单品打折" @open="resetDiscountForm = { ...discountForm }"
@closed="discountModalClose">
<div class="dialog">
<div class="el-popover__title content">
<el-form ref="discountFormRef" :model="discountForm" :rules="discountFormRules" label-width="100px"
label-position="left">
<el-form-item label="价格更改" prop="amount">
<el-input v-model="discountForm.amount" placeholder="减8.88元请输入8.88" @input="priceInput">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="更改原因">
<el-input v-model="discountForm.note" type="textarea" placeholder="请输入自定义备注" />
<div class="remark_list">
<div class="item" v-for="item in noteList" :key="item" @click="addNote(item)">
{{ item }}
</div>
</div>
</el-form-item>
</el-form>
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showDiscountModal = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="discountFormLoading"
@click="discountFormSubmit">确认</el-button>
</div>
</div>
</div>
</el-dialog>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import takeFoodCode from '@/components/takeFoodCode.vue' import takeFoodCode from '@/components/takeFoodCode.vue'
import skuModal from '@/components/skuModal.vue' import skuModal from '@/components/skuModal.vue'
import { useShop } from '@/store/shop.js'
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
import { updatePrice, orderPrint } from '@/api/product.js'
const shopStore = useShop()
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
@@ -83,6 +135,8 @@ function giftPackHandle(key) {
// 加减修改数量 // 加减修改数量
function numberChange(t) { function numberChange(t) {
console.log(props.item);
if (!props.item.id) return if (!props.item.id) return
switch (t) { switch (t) {
case 'sub': case 'sub':
@@ -118,6 +172,114 @@ function skuConfirm(e) {
if (!props.item.id) return if (!props.item.id) return
emit('confirm', e) emit('confirm', e)
} }
/**单品打折 start */
const showDiscountModal = ref(false)
const resetDiscountForm = ref({})
const discountFormRef = ref(null)
const discountFormLoading = ref(false)
const discountForm = ref({
masterId: '',
cartId: '',
amount: '',
note: '',
shopId: ''
})
function validateAmount(rule, value, callback) {
if (value == '') {
callback(new Error('请输入折扣价格'))
} else if (value <= 0) {
callback(new Error('输入价格有误'))
} else {
callback()
}
}
const discountFormRules = ref({
amount: [
{
required: true,
validator: validateAmount,
trigger: 'blur',
}
]
})
const noteList = ref([
'顾客投诉质量...',
'友情打折',
'临时活动',
])
// 显示
function showDiscountModalHandle() {
if (props.item.id) {
showDiscountModal.value = true
}
}
// 过滤价格输入
function priceInput(e) {
setTimeout(() => {
discountForm.value.amount = inputFilterFloat(e)
}, 50)
}
// 关闭
function discountModalClose() {
discountForm.value = { ...resetDiscountForm.value }
discountFormRef.value.resetFields()
}
// 添加快捷备注
function addNote(str) {
if (!discountForm.value.note.length) {
discountForm.value.note += str
} else {
discountForm.value.note += `${str}`
}
}
// 提交
function discountFormSubmit() {
discountFormRef.value.validate(async valid => {
try {
if (valid) {
discountFormLoading.value = true
discountForm.value.masterId = props.item.masterId
discountForm.value.cartId = props.item.id
discountForm.value.shopId = props.item.shopId
await updatePrice(discountForm.value)
discountFormLoading.value = false
showDiscountModal.value = false
ElMessage.success('操作成功')
emit('confirm', { isTemporary: true })
}
} catch (error) {
discountFormLoading.value = false
console.log(error);
}
})
}
/**单品打折 end */
/**免厨打印 start */
async function kitchenPrint() {
try {
const res = await orderPrint({
isPrint: props.item.isPrint ? 0 : 1,
shopId: props.item.shopId,
cartId: props.item.id
})
emit('confirm', { isTemporary: true })
} catch (error) {
console.log(error);
}
}
/**免厨打印 end */
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -125,7 +287,7 @@ function skuConfirm(e) {
padding: 10px; padding: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 15px;
.item { .item {
width: 70px; width: 70px;
@@ -170,4 +332,33 @@ function skuConfirm(e) {
} }
} }
} }
.dialog {
.content {
padding-bottom: 20px;
}
.footer_wrap {
display: flex;
gap: 20px;
.btn {
flex: 1;
}
}
}
.remark_list {
display: flex;
gap: 10px;
margin-top: 10px;
.item {
padding: 0 10px;
border: 1px solid #ddd;
color: #999;
border-radius: 4px;
}
}
</style> </style>

View File

@@ -26,11 +26,19 @@
</el-popover> </el-popover>
</div> </div>
<div class="search_wrap"> <div class="search_wrap">
<div class="input"> <div class="left">
<el-input placeholder="请输入商品名称查询" v-model="commdityName" clearable @focus=" <el-button :type="showEditor ? 'warning' : ''" @click="showEditorChange">
global.updateData(false)" @blur="global.updateData(true)" @input="inputChange"></el-input> {{ showEditor ? '关闭编辑' : '编辑' }}
</el-button>
<el-button type="warning" icon="Food" @click="showTemporaryDish = true">临时菜</el-button>
</div>
<div class="right">
<div class="input">
<el-input placeholder="请输入商品名称查询" v-model="commdityName" clearable @focus="
global.updateData(false)" @blur="global.updateData(true)" @input="inputChange"></el-input>
</div>
<el-button :loading="searchLoading" :icon="Search" @click="searchHandle">搜索</el-button>
</div> </div>
<el-button :loading="searchLoading" :icon="Search" @click="searchHandle">搜索</el-button>
<!-- <el-button :icon="shopListType == 'text' ? 'PictureRounded' : 'PriceTag'" <!-- <el-button :icon="shopListType == 'text' ? 'PictureRounded' : 'PriceTag'"
@click="changeShopListType"></el-button> --> @click="changeShopListType"></el-button> -->
</div> </div>
@@ -40,15 +48,39 @@
<swiper-slide class="slide_item" v-for="(goods, index) in goodsList" :key="index"> <swiper-slide class="slide_item" v-for="(goods, index) in goodsList" :key="index">
<div class="item_wrap" v-for="item in goods" :key="item.id" @click="showSkuHandle(item)"> <div class="item_wrap" v-for="item in goods" :key="item.id" @click="showSkuHandle(item)">
<div class="item"> <div class="item">
<transition name="el-fade-in">
<div class="more" v-if="item.showMore" @click.stop>
<div class="ul">
<template v-if="categorys[categorysActive].id == '-1'">
<div class="li" @click.stop="showPutawayHandle(item)">上架</div>
</template>
<template v-else>
<div class="li" @click.stop="goodEditor(item, 0)">下架</div>
<div class="li" @click.stop="goodEditor(item, 1)">售罄</div>
<div class="li" @click.stop="goodStockNumberHandle(item)">修改库存</div>
</template>
<div class="li" @click.stop="item.showMore = false">取消</div>
</div>
</div>
</transition>
<div class="dot" v-if="item.orderCount">{{ item.orderCount }}</div> <div class="dot" v-if="item.orderCount">{{ item.orderCount }}</div>
<div class="cover" v-if="shopListType == 'img'"> <div class="cover" v-if="shopListType == 'img'">
<el-image :src="`${item.coverImg}?x-oss-process=image/resize,m_lfit,w_150,h_150`" <el-image :src="`${item.coverImg}?x-oss-process=image/resize,m_lfit,w_150,h_150`"
class="el_img" fit="cover"></el-image> class="el_img" fit="cover"></el-image>
<div class="sell_out" v-if="item.isPauseSale == 1">
<img class="sell_out_icon" src="../../../assets/icon_xq.png">
</div>
<div class="weight" v-if="item.type == 'weigh'">称重</div>
</div> </div>
<div class="name"><el-text line-clamp="1">{{ item.name }}</el-text></div> <div class="name"><el-text line-clamp="1">{{ item.name }}</el-text></div>
<div class="item_empty" v-if="shopListType == 'text'"></div> <div class="item_empty" v-if="shopListType == 'text'"></div>
<div class="price"> <div class="price">
<el-text>{{ item.lowPrice }}</el-text> <el-text>{{ item.lowPrice }}</el-text>
<div class="show_more_btn" v-if="showEditor">
<el-icon>
<MoreFilled />
</el-icon>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -60,26 +92,154 @@
</div> </div>
<!-- 选择规格 --> <!-- 选择规格 -->
<skuModal ref="skuModalRef" @success="skuConfirm" /> <skuModal ref="skuModalRef" @success="skuConfirm" />
<!-- 编辑商品 -->
<el-dialog v-model="showGoodEditor" :title="`${goodEditorEmun[goodEditorType]}商品`">
<div class="dialog">
<div class="el-popover__title content">
确定要{{ `${goodEditorEmun[goodEditorType]}商品:${goodEditorItem.name}` }}
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showGoodEditor = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="goodEditorLoading"
@click="goodEditorConfirm">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 修改库存 -->
<el-dialog v-model="showGoodsEditorStock" title="修改库存" width="400px">
<div class="dialog">
<el-form>
<el-form-item label="库存">
<div>
<el-input-number v-model="goodsEditorStockNumber" :min="0"></el-input-number>
<div class="tips">修改前库存{{ goodsEditorStockItem.stockNumber }}</div>
</div>
</el-form-item>
</el-form>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showGoodsEditorStock = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="goodsEditorStockLoading"
@click="goodsEditorStockConfirm">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 关闭售罄 -->
<el-dialog v-model="showCloseSell" title="关闭售罄">
<div class="dialog">
<div class="el-popover__title content">
确定要将{{ `${goodEditorItem.name}` }}关闭售罄吗
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showCloseSell = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="closeSellLoading"
@click="closeSellHandle">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 上架商品 -->
<el-dialog v-model="showPutaway" title="上架商品">
<div class="dialog">
<div class="el-popover__title content">
确定要上架商品{{ `${goodEditorItem.name}` }}
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showPutaway = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="showPutawayLoading"
@click="putawayHandle">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 添加临时菜 -->
<el-dialog v-model="showTemporaryDish" title="添加临时菜" top="3vh" @open="showTemporaryDishOpen"
@closed="showTemporaryDishClosed">
<div class="dialog">
<div class="el-popover__title content">
<el-form ref="temporaryFormRef" :model="temporaryForm" :rules="temporaryFormRules" label-width="100px"
label-position="left">
<el-form-item label="菜品名称" prop="name">
<el-input v-model="temporaryForm.name" placeholder="请输入菜品名称" />
</el-form-item>
<el-form-item label="菜品分类" prop="categoryId">
<el-select v-model="temporaryForm.categoryId" placeholder="请选择菜品分类">
<el-option v-for="item in temporaryCategorys" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model="temporaryForm.price" placeholder="请输入价格" @input="priceInput">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-select v-model="temporaryForm.unit" placeholder="请选择单位">
<el-option v-for="item in units" :key="item.id" :label="item.name"
:value="item.name"></el-option>
</el-select>
</el-form-item>
<el-form-item label="下单数量">
<el-input-number v-model="temporaryForm.num" :min="1" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="temporaryForm.note" type="textarea" placeholder="请输入自定义备注" />
<div class="remark_list">
<div class="item" v-for="item in noteList" :key="item" @click="addNote(item)">
{{ item }}
</div>
</div>
</el-form-item>
</el-form>
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showTemporaryDish = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="temporaryFormLoading"
@click="temporaryFormSubmit">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 称重商品弹窗 -->
<WeightModal ref="WeightModalRef" @success="skuConfirm" />
<!-- 套餐商品弹窗 -->
<GroupModal ref="GroupModalRef" @success="skuConfirm" />
</template> </template>
<script setup> <script setup>
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import _ from 'lodash' import _ from 'lodash'
import useStorage from "@/utils/useStorage"; import useStorage from "@/utils/useStorage";
import skuModal from '@/components/skuModal.vue' import skuModal from '@/components/skuModal.vue'
import WeightModal from './weightModal.vue'
import { queryCategory, queryNewCommodityInfo, queryProductSku } from '@/api/product' import GroupModal from './groupModal.vue'
import { queryCategory, queryNewCommodityInfo, queryProductSku, productStatus, productStock, getUnitList, temporaryDishes } from '@/api/product'
import { useUser } from "@/store/user.js" import { useUser } from "@/store/user.js"
import { Swiper, SwiperSlide } from 'swiper/vue' import { Swiper, SwiperSlide } from 'swiper/vue'
import "swiper/swiper-bundle.css"; import "swiper/swiper-bundle.css";
import { staffPermission } from '@/api/user.js'
import { useGlobal } from '@/store/global.js' import { useGlobal } from '@/store/global.js'
const global = useGlobal() import { inputFilterFloat } from '@/utils/index.js'
const global = useGlobal()
const store = useUser() const store = useUser()
@@ -90,7 +250,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['success']) const emit = defineEmits(['success', 'loading'])
const skuModalRef = ref(null) const skuModalRef = ref(null)
@@ -116,6 +276,136 @@ const inputChange = _.debounce(function () {
searchHandle() searchHandle()
}, 500) }, 500)
/** 添加临时菜 start */
const temporaryCategorys = ref([]) // 分类列表
const showTemporaryDish = ref(false) // 显示添加临时菜
const units = ref([]) // 单位列表
const temporaryFormRef = ref(null)
const resetTemporaryForm = ref({})
const temporaryForm = ref({
masterId: '',
shopId: '',
tableId: '',
name: '',
categoryId: '',
price: '',
unit: '',
num: 1,
note: '',
vipUserId: ''
})
const temporaryFormLoading = ref(false)
const noteList = ref([
'免葱',
'免香菜',
'不要辣',
])
function priceInput(e) {
setTimeout(() => {
temporaryForm.value.price = inputFilterFloat(e)
}, 50)
}
// 添加快捷备注
function addNote(str) {
if (!temporaryForm.value.note.length) {
temporaryForm.value.note += str
} else {
temporaryForm.value.note += `${str}`
}
}
const temporaryFormRules = ref({
name: [
{
required: true,
message: '请输入菜品名称',
trigger: 'blur',
}
],
categoryId: [
{
required: true,
message: '请选择菜品分类',
trigger: 'change',
}
],
price: [
{
required: true,
message: '请输入价格',
trigger: 'blur',
}
],
unit: [
{
required: true,
message: '请选择单位',
trigger: 'change',
}
],
})
// 打开
function showTemporaryDishOpen() {
resetTemporaryForm.value = { ...temporaryForm.value }
}
// 关闭
function showTemporaryDishClosed() {
temporaryForm.value = { ...resetTemporaryForm.value }
temporaryFormRef.value.resetFields()
}
// 获取单位列表
async function getUnitListAjax() {
try {
const res = await getUnitList({
shopId: store.userInfo.shopId,
page: 1,
size: 100,
name: ''
})
units.value = res.records
} catch (error) {
console.log(error);
}
}
// 提交临时菜
function temporaryFormSubmit() {
temporaryFormRef.value.validate(async valid => {
try {
if (valid) {
temporaryFormLoading.value = true
temporaryForm.value.masterId = props.masterId
temporaryForm.value.shopId = store.userInfo.shopId
temporaryForm.value.tableId = global.tableInfo.qrcode
await temporaryDishes(temporaryForm.value)
temporaryFormLoading.value = false
showTemporaryDish.value = false
ElMessage.success('添加成功')
emit('success', { isTemporary: true })
}
} catch (error) {
temporaryFormLoading.value = false
console.log(error);
}
})
}
/** 添加临时菜 end */
/** 套餐 start */
const GroupModalRef = ref(null)
/** 套餐 end */
// 搜索 // 搜索
const searchLoading = ref(false) const searchLoading = ref(false)
function searchHandle() { function searchHandle() {
@@ -132,6 +422,7 @@ function searchHandle() {
// 确认选择规格回调 // 确认选择规格回调
function skuConfirm(params) { function skuConfirm(params) {
emit('loading')
emit('success', params) emit('success', params)
} }
@@ -140,14 +431,77 @@ function showMoreMenu() {
showPopover.value = !showPopover.value showPopover.value = !showPopover.value
} }
// 显示/隐藏编辑
async function showEditorChange() {
try {
await staffPermission('yun_xu_xiu_gai_shang_pin')
if (showEditor.value) {
showEditor.value = false
goodsList.value.map(item => {
item.map(val => {
val.showMore = false
})
})
} else {
showEditor.value = true
}
} catch (error) {
console.log(error);
}
}
// 显示sku // 显示sku
const WeightModalRef = ref(null)
function showSkuHandle(item) { function showSkuHandle(item) {
if (item.typeEnum == 'sku') { if (showEditor.value) {
// 多规格 if (item.isPauseSale == 1) {
skuModalRef.value.show({ ...item }) goodEditorItem.value = item
showCloseSell.value = true
} else {
goodsList.value.map(item => {
item.map(val => {
val.showMore = false
})
})
item.showMore = true
}
} else { } else {
// 单规格 if (item.isPauseSale == 1) {
queryProductSkuAjax(item) ElMessage({
type: 'error',
message: '该商品已售罄',
showClose: true,
})
return
}
if (categorys.value[categorysActive.value].id == '-1') {
ElMessage({
type: 'error',
message: '该商品已下架,请上架后操作',
showClose: true,
})
return
}
if (item.type == 'normal') {
if (item.typeEnum == 'sku') {
// 多规格
skuModalRef.value.show({ ...item })
} else {
// 单规格
loading.value = true
emit('loading')
queryProductSkuAjax(item)
}
} else if (item.type == 'weigh') {
WeightModalRef.value.show(item)
} else if (item.type == 'package' && item.groupType == 1) {
GroupModalRef.value.show(item)
} else if (item.type == 'package' && item.groupType == 0) {
// 固定套餐当做单规格处理
loading.value = true
emit('loading')
queryProductSkuAjax(item)
}
} }
} }
@@ -159,6 +513,7 @@ const queryProductSkuAjax = _.throttle(async function (goods) {
productId: goods.id, productId: goods.id,
spec_tag: '' spec_tag: ''
}) })
loading.value = false
emit('success', res) emit('success', res)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
@@ -223,11 +578,16 @@ async function queryCategoryAjax() {
page: 1, page: 1,
pageSize: 100 pageSize: 100
}) })
temporaryCategorys.value = [...res.list]
categorys.value = res.list categorys.value = res.list
categorys.value.unshift({ categorys.value.unshift({
name: '全部', name: '全部',
id: '' id: ''
}) })
categorys.value.push({
name: '已下架',
id: '-1'
})
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
@@ -243,7 +603,8 @@ async function productqueryCommodityInfoAjax() {
commdityName: commdityName.value, commdityName: commdityName.value,
page: goodsPage.value, page: goodsPage.value,
pageSize: goodsPageSize.value, pageSize: goodsPageSize.value,
masterId: props.masterId masterId: props.masterId,
tableId: global.tableInfo.qrcode || '',
}) })
if (res.list.length < goodsPageSize.value) { if (res.list.length < goodsPageSize.value) {
finish.value = true finish.value = true
@@ -259,6 +620,9 @@ async function productqueryCommodityInfoAjax() {
// clearInterval(loopTimer.value) // clearInterval(loopTimer.value)
// loopTimer.value = null // loopTimer.value = null
// } // }
res.list.map((val, index) => {
val.showMore = false
})
return res return res
} catch (error) { } catch (error) {
loading.value = false loading.value = false
@@ -337,6 +701,170 @@ function clearDot() {
}) })
} }
const showEditor = ref(false)
const goodEditorType = ref(0) // 0 上下架 1售罄
const goodEditorItem = ref({})
const goodEditorLoading = ref(false)
const showGoodEditor = ref(false)
const goodEditorEmun = ref({
0: '下架',
1: '售罄'
})
// 编辑商品
async function goodEditor(item, t) {
try {
if (t == 0) {
await staffPermission('yun_xu_shang_xia_jia_shang_pin')
} else if (t == 1) {
await staffPermission('yun_xu_shou_qing_shang_pin')
}
goodEditorItem.value = item
if (item.isPauseSale == 1) {
} else {
goodEditorType.value = t
showGoodEditor.value = true
}
} catch (error) {
console.log(error);
}
}
// 关闭售罄
const showCloseSell = ref(false)
const closeSellLoading = ref(false)
async function closeSellHandle() {
try {
closeSellLoading.value = true
const res = await productStatus({
shopId: store.userInfo.shopId,
productId: goodEditorItem.value.id,
type: 1,
state: 0
})
closeSellLoading.value = false
showCloseSell.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
// 上架商品
const showPutaway = ref(false)
const showPutawayLoading = ref(false)
async function showPutawayHandle(item) {
try {
await staffPermission('yun_xu_shang_xia_jia_shang_pin')
goodEditorItem.value = item
showPutaway.value = true
} catch (error) {
console.log(error);
}
}
async function putawayHandle(item) {
try {
showPutawayLoading.value = true
const res = await productStatus({
shopId: store.userInfo.shopId,
productId: goodEditorItem.value.id,
type: 0,
state: 1
})
showPutawayLoading.value = false
showPutaway.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
// 确认操作
async function goodEditorConfirm() {
try {
goodEditorLoading.value = true
const res = await productStatus({
shopId: store.userInfo.shopId,
productId: goodEditorItem.value.id,
type: goodEditorType.value,
state: goodEditorType.value == 0 ? 0 : 1
})
goodEditorLoading.value = false
showGoodEditor.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
// 显示修改库存
const goodsEditorStockItem = ref(0)
const showGoodsEditorStock = ref(false)
const goodsEditorStockNumber = ref(0)
const goodsEditorStockLoading = ref(false)
async function goodStockNumberHandle(item) {
// if (item.isDistribute == 0 && item.typeEnum == 'sku') {
// ElMessage({
// type: 'warning',
// message: '未开启共享库存无法修改',
// showClose: true,
// })
// } else {
// goodsEditorStockItem.value = item
// goodsEditorStockNumber.value = item.stockNumber
// showGoodsEditorStock.value = true
// }
try {
await staffPermission('yun_xu_xiu_gai_shang_pin_ku_cun')
goodsEditorStockItem.value = item
goodsEditorStockNumber.value = item.stockNumber
showGoodsEditorStock.value = true
} catch (error) {
console.log(error);
}
}
// 确认修改库存
async function goodsEditorStockConfirm() {
try {
goodsEditorStockLoading.value = true
const res = await productStock({
shopId: store.userInfo.shopId,
productId: goodsEditorStockItem.value.id,
stock: goodsEditorStockNumber.value
})
goodsEditorStockLoading.value = false
showGoodsEditorStock.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
defineExpose({ defineExpose({
updateData, updateData,
clearDot clearDot
@@ -344,6 +872,7 @@ defineExpose({
onMounted(async () => { onMounted(async () => {
localUpdateShopListType() localUpdateShopListType()
getUnitListAjax()
await updateCategoryActive() await updateCategoryActive()
await queryCategoryAjax() await queryCategoryAjax()
}) })
@@ -380,6 +909,10 @@ onMounted(async () => {
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--el-font-size-base); gap: var(--el-font-size-base);
padding: var(--el-font-size-base) 0; padding: var(--el-font-size-base) 0;
:deep(.el-button) {
margin-left: 0;
}
} }
.header { .header {
@@ -456,9 +989,17 @@ onMounted(async () => {
.search_wrap { .search_wrap {
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
gap: 10px;
padding: var(--el-font-size-base); padding: var(--el-font-size-base);
.left {
display: flex;
}
.right {
display: flex;
gap: 10px;
}
} }
.shop_list { .shop_list {
@@ -489,6 +1030,38 @@ onMounted(async () => {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
.more {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 10;
background-color: rgba(0, 0, 0, .6);
backdrop-filter: blur(2px);
border-radius: 10px;
.ul {
height: 100%;
padding: 20px;
display: flex;
justify-content: center;
flex-direction: column;
.li {
display: flex;
align-items: center;
color: #fff;
padding: 8px 0;
&:last-child {
border-top: 1px solid rgba(255 255 255 / 20%);
color: #ececec;
}
}
}
}
&:hover { &:hover {
cursor: pointer; cursor: pointer;
} }
@@ -510,6 +1083,17 @@ onMounted(async () => {
height: 60%; height: 60%;
position: relative; position: relative;
.weight {
position: absolute;
left: 5px;
bottom: 5px;
color: #fff;
font-size: 12px;
padding: 2px 6px;
background-color: var(--el-color-danger);
border-radius: 4px;
}
.el_img { .el_img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -518,6 +1102,27 @@ onMounted(async () => {
top: 0; top: 0;
left: 0; left: 0;
} }
.sell_out {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
// backdrop-filter: blur(2px);
z-index: 10;
.sell_out_icon {
$size: 60px;
width: $size;
height: $size;
object-fit: cover;
}
}
} }
.name { .name {
@@ -540,13 +1145,56 @@ onMounted(async () => {
height: 20%; height: 20%;
padding: 6px 10px; padding: 6px 10px;
background-color: var(--primary-color); background-color: var(--primary-color);
position: relative;
span { span {
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
} }
.show_more_btn {
width: 40px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
right: 0;
z-index: 1;
color: #fff;
}
} }
} }
} }
} }
.dialog {
.content {
padding-bottom: 20px;
}
.footer_wrap {
display: flex;
gap: 20px;
.btn {
flex: 1;
}
}
}
.remark_list {
display: flex;
gap: 10px;
margin-top: 10px;
.item {
padding: 0 10px;
border: 1px solid #ddd;
color: #999;
border-radius: 4px;
}
}
</style> </style>

View File

@@ -0,0 +1,206 @@
<!-- 称重商品组件 -->
<template>
<el-dialog title="可选套餐" width="70%" :close-on-click-modal="false" v-model="dialogVisible" top="10vh">
<div class="row" v-for="(item, index) in goodsItem.proGroupVo" :key="index">
<div class="title_wrap">
<div class="item">规格组名{{ item.title }}</div>
<div class="item"
v-html="`本组菜品<span style='color: var(--el-color-danger)'>${item.count}</span>选<span style='color: var(--el-color-danger)'>${item.number}</span>`">
</div>
</div>
<div class="error">
<span v-if="item.isError">错误请按规格组选择菜品</span>
</div>
<el-table border :data="item.goods" ref="tabRefs" @select="selectChange($event, index)"
@select-all="selectChange($event, index)">
<el-table-column type="selection" width="55" />
<el-table-column label="名称" prop="proName"></el-table-column>
<el-table-column label="规格" prop="skuName"></el-table-column>
<el-table-column label="价格" prop="price">
<template v-slot="scope">
{{ formatDecimal(scope.row.price) }}
</template>
</el-table-column>
<el-table-column label="数量" prop="number">
<template v-slot="scope">
{{ `${scope.row.number}${scope.row.unitName || ''}` }}
</template>
</el-table-column>
</el-table>
</div>
<div class="footer">
<el-button style="width: 100%" @click="dialogVisible = false">
取消
</el-button>
<el-button type="primary" style="width: 100%" :disabled="disabled" @click="confirmHandle">
{{ disabled ? '请选择菜品' : '确认' }}
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
const dialogVisible = ref(false);
const number = ref("");
const goodsItem = ref({})
const emit = defineEmits(["success"]);
const tabRefs = ref([])
function show(item) {
disabled.value = true
dialogVisible.value = true;
goodsItem.value = { ...item }
goodsItem.value.proGroupVo.map(item => {
item.isError = false
})
setTimeout(() => {
tabRefs.value.map(item => {
item.clearSelection()
})
}, 100);
}
// 选择表格触发
function selectChange($event, index) {
let item = goodsItem.value.proGroupVo[index]
let selectNum = tabRefs.value[index].getSelectionRows()
if (selectNum.length != item.number) {
item.isError = true
} else {
item.isError = false
}
let flags = []
goodsItem.value.proGroupVo.map((item, index) => {
let selectNum = tabRefs.value[index].getSelectionRows()
if (selectNum.length != item.number) {
flags.push({ flag: false })
} else {
flags.push({ flag: true })
}
})
const arr = flags.find(item => !item.flag)
if (arr != undefined && !arr.flag) {
disabled.value = true
return
}
disabled.value = false
}
// 确认
const disabled = ref(true)
function confirmHandle() {
let flags = []
goodsItem.value.proGroupVo.map((item, index) => {
let selectNum = tabRefs.value[index].getSelectionRows()
if (selectNum.length != item.number) {
flags.push({ flag: false })
} else {
flags.push({ flag: true })
}
})
const arr = flags.find(item => !item.flag)
if (arr != undefined && !arr.flag) {
disabled.value = true
return
}
disabled.value = false
let goodIds = []
goodsItem.value.proGroupVo.map((item, index) => {
let selectNum = tabRefs.value[index].getSelectionRows()
goodIds.push(selectNum)
})
// 将商品数据转为一维数组返回
emit("success", {
...goodsItem.value,
productId: goodsItem.value.id,
groupProductIdList: goodIds.flat().map(item => item.proId)
});
dialogVisible.value = false;
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.keybord_wrap {
padding: var(--el-font-size-base) 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
gap: var(--el-font-size-base);
:deep(.el-button--large) {
height: 50px;
}
}
.input_wrap {
display: flex;
align-items: center;
gap: 20px;
.item {
display: flex;
flex-direction: column;
gap: 10px;
}
}
.price_item {
font-size: 18px;
font-weight: bold;
color: var(--el-color-danger);
padding: 15px 0;
border-top: 1px solid #ececec;
}
.footer {
display: flex;
}
.row {
margin-bottom: 20px;
.title_wrap {
display: flex;
gap: 30px;
font-size: 16px;
// padding-bottom: 10px;
.item {
span {
margin: 0 4px;
font-weight: bold;
color: var(--el-color-danger);
}
}
}
.error {
height: 20px;
color: var(--el-color-danger);
font-size: 12px;
}
}
</style>

View File

@@ -1,7 +1,6 @@
<!-- 结算订单 --> <!-- 结算订单 -->
<template> <template>
<el-drawer size="100%" :with-header="false" direction="btt" v-model="dialogVisible"> <el-drawer size="100%" :with-header="false" direction="btt" v-model="dialogVisible" @closed="drawerClose">
<div class="drawer_wrap"> <div class="drawer_wrap">
<div class="cart_list"> <div class="cart_list">
<div class="nav_wrap card"> <div class="nav_wrap card">
@@ -13,10 +12,12 @@
<div class="info"> <div class="info">
<div class="master_id"> <div class="master_id">
<span>{{ props.masterId }}</span> <span>{{ props.masterId }}</span>
<span class="member_info" v-if="memberInfo.telephone">会员{{ memberInfo.telephone }}</span> <span class="member_info" v-if="global.orderMemberInfo.telephone">
会员{{ global.orderMemberInfo.telephone }}
</span>
</div> </div>
<div class="btm"> <div class="btm">
<span class="p">服务员{{ store.userInfo.shopName || "暂无" }}</span> <span class="p">服务员{{ store.userInfo.loginAccount || "暂无" }}</span>
<span class="t">{{ <span class="t">{{
props.orderInfo.createdAt && props.orderInfo.createdAt &&
dayjs(props.orderInfo.createdAt).format("MM-DD HH:mm") dayjs(props.orderInfo.createdAt).format("MM-DD HH:mm")
@@ -25,7 +26,7 @@
</div> </div>
</div> </div>
<div class="list_wrap card" style="margin-top: var(--el-font-size-base)"> <div class="list_wrap card" style="margin-top: var(--el-font-size-base)">
<div class="item" v-for="item in props.cart" :key="item.id"> <div class="item" v-for="item in cartList" :key="item.id">
<div class="top"> <div class="top">
<span class="name">{{ item.name }}</span> <span class="name">{{ item.name }}</span>
<span class="n">x{{ item.number }}</span> <span class="n">x{{ item.number }}</span>
@@ -55,14 +56,37 @@
<el-checkbox v-model="isPrint" border label="打印结算小票" style="width: 100%" /> <el-checkbox v-model="isPrint" border label="打印结算小票" style="width: 100%" />
</div> </div>
<div class="print"> <div class="print">
<el-button type="primary" v-loading="printLoading" @click="printHandle">打印预结单</el-button> <el-button type="warning" :loading="discountLoading" @click="showStaffDiscountHandle">添加折扣</el-button>
</div>
<div class="print">
<el-button type="primary" :loading="printLoading" @click="printHandle">打印预结单</el-button>
</div> </div>
</div> </div>
</div> </div>
<div class="pay_wrap"> <div class="pay_wrap">
<payCard :amount="props.amount" :member="props.member" :orderId="props.orderInfo.id" @paySuccess="paySuccess" /> <payCard :amount="props.amount" :discount="propsDiscount" :orderId="props.orderInfo.id" @paySuccess="paySuccess"
@cancelDiscount="propsDiscount = 0" />
</div> </div>
</div> </div>
<el-dialog v-model="showStaffDiscount" title="员工折扣" @close="global.updateData(true)">
<el-form>
<el-form-item label="折扣比例">
<div>
<el-input-number v-model="discount" :min="staffDiscount" :max="0.99" :step="0.1"
:disabled="staffDiscount == 0" />
<div class="tips">最低折扣比例{{ staffDiscount }}</div>
</div>
</el-form-item>
</el-form>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showStaffDiscount = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" @click="discountConfirm">确认</el-button>
</div>
</div>
</el-dialog>
</el-drawer> </el-drawer>
</template> </template>
@@ -72,13 +96,19 @@ import { onMounted, ref } from "vue";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import payCard from "@/components/payCard/payCard.vue"; import payCard from "@/components/payCard/payCard.vue";
import { print } from "@/api/pay"; import { print } from "@/api/pay";
import { orderfindOrder } from '@/api/order/index.js' import { orderfindOrder, getStaffDiscount } from '@/api/order/index.js'
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import dayjs from "dayjs"; import dayjs from "dayjs";
import useStorage from '@/utils/useStorage' import useStorage from '@/utils/useStorage'
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { formatDecimal } from '@/utils/index.js'
import receiptPrint from "@/components/lodop/receiptPrint.js";
import { useGlobal } from '@/store/global.js'
import { usePrint } from '@/store/print.js' import { usePrint } from '@/store/print.js'
import { staffPermission } from '@/api/user.js'
const global = useGlobal()
const printStore = usePrint() const printStore = usePrint()
const store = useUser(); const store = useUser();
@@ -87,6 +117,11 @@ const emit = defineEmits("paySuccess");
const printLoading = ref(false); const printLoading = ref(false);
const showStaffDiscount = ref(false)
const staffDiscount = ref(0)
const discount = ref(0)
const propsDiscount = ref(0)
const dialogVisible = ref(false); const dialogVisible = ref(false);
const props = defineProps({ const props = defineProps({
cart: { cart: {
@@ -115,37 +150,91 @@ const props = defineProps({
} }
}); });
const cartList = ref([])
const isPrint = ref(true); const isPrint = ref(true);
const discountLoading = ref(false)
// 显示员工折扣
async function showStaffDiscountHandle() {
try {
discountLoading.value = true
await staffPermission('yun_xu_da_zhe')
await getStaffDiscountAjax()
discountLoading.value = false
if (staffDiscount.value <= 0) {
ElMessage.error('暂无折扣,请稍后再试')
} else {
showStaffDiscount.value = true
discountLoading.value = false
global.updateData(false)
}
} catch (error) {
discountLoading.value = false
console.log(error);
}
}
// 获取员工折扣
async function getStaffDiscountAjax() {
try {
const res = await getStaffDiscount({
orderId: props.orderInfo.id,
staffId: store.userInfo.staffId
})
staffDiscount.value = res
discount.value = res
} catch (error) {
console.log(error);
}
}
// 确认折扣
function discountConfirm() {
if (discount.value >= staffDiscount.value) {
propsDiscount.value = discount.value
}
showStaffDiscount.value = false
}
// 关闭结算弹窗
function drawerClose() {
propsDiscount.value = 0
}
// 预打印操作 // 预打印操作
const printHandle = _.throttle(async function () { const printHandle = _.throttle(async function () {
try { try {
if (!isPrint.value) return; if (!isPrint.value) return;
printLoading.value = true;
const data = { const data = {
shop_name: store.userInfo.merchantName, shop_name: store.userInfo.shopName,
carts: props.cart, loginAccount: store.userInfo.loginAccount,
amount: props.amount, isBefore: true,
carts: cartList.value,
amount: formatDecimal(props.amount),
discountAmount: propsDiscount.value > 0 ? formatDecimal(props.amount * propsDiscount.value) : formatDecimal(props.amount),
discount: formatDecimal(propsDiscount.value * 10, 1, true),
remark: props.remark, remark: props.remark,
orderInfo: props.orderInfo, orderInfo: props.orderInfo,
createdAt: dayjs(props.orderInfo.createdAt).format( createdAt: dayjs(props.orderInfo.createdAt).format("YYYY-MM-DD HH:mm:ss"),
"YYYY-MM-DD HH:mm:ss"
),
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
}; };
printStore.labelPrint(data) printStore.labelPrint(data)
setTimeout(() => {
try { printLoading.value = false;
printLoading.value = true; }, 1500)
if (printStore.deviceNoteList.length) {
printStore.pushReceiptData(data)
} else {
await print({ await print({
type: "normal", type: "normal",
ispre: true, ispre: true,
orderId: props.orderInfo.id, orderId: props.orderInfo.id,
}); });
printLoading.value = false; printLoading.value = false;
// ElMessage.success("打印成功"); ElMessage.success("打印成功");
} catch (error) {
printLoading.value = false;
console.log(error);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -155,6 +244,7 @@ const printHandle = _.throttle(async function () {
// 打印订单标签 // 打印订单标签
async function printOrderLable() { async function printOrderLable() {
try { try {
if (!isPrint.value) return
const res = await orderfindOrder({ const res = await orderfindOrder({
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
status: '', status: '',
@@ -166,13 +256,19 @@ async function printOrderLable() {
const printLabelOrder = res.list[0] const printLabelOrder = res.list[0]
const data = { const data = {
shop_name: store.userInfo.merchantName, shop_name: store.userInfo.shopName,
loginAccount: store.userInfo.loginAccount,
carts: [], carts: [],
amount: formatDecimal(printLabelOrder.orderAmount),
discountAmount: printLabelOrder.discountRatio > 0 ? formatDecimal(printLabelOrder.orderAmount - printLabelOrder.discountAmount) : formatDecimal(printLabelOrder.orderAmount),
discount: formatDecimal(printLabelOrder.discountRatio * 10, 1, true) || 0,
remark: printLabelOrder.remark,
orderInfo: printLabelOrder, orderInfo: printLabelOrder,
outNumber: printLabelOrder.outNumber, outNumber: printLabelOrder.outNumber,
createdAt: dayjs(printLabelOrder.createdAt).format( createdAt: dayjs(printLabelOrder.createdAt).format(
"YYYY-MM-DD HH:mm:ss" "YYYY-MM-DD HH:mm:ss"
) ),
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
} }
printLabelOrder.skuInfos.map(item => { printLabelOrder.skuInfos.map(item => {
data.carts.push( data.carts.push(
@@ -180,13 +276,28 @@ async function printOrderLable() {
categoryId: item.categoryId, categoryId: item.categoryId,
name: item.productName, name: item.productName,
number: item.num, number: item.num,
skuName: item.productSkuName skuName: item.productSkuName,
salePrice: formatDecimal(item.price),
totalAmount: formatDecimal(item.num * item.price),
proGroupInfo: item.proGroupInfo
} }
) )
}) })
// 打印标签
// console.log('重打标签小票', data);
printStore.labelPrint(data) printStore.labelPrint(data)
if (printStore.deviceNoteList.length) {
// 打印小票
printStore.pushReceiptData(data)
} else {
await print({
type: "normal",
ispre: true,
orderId: props.orderInfo.id,
});
printLoading.value = false;
ElMessage.success("打印成功");
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
@@ -194,37 +305,32 @@ async function printOrderLable() {
// 订单已支付 // 订单已支付
function paySuccess() { function paySuccess() {
useStorage.del('memberInfo') propsDiscount.value = 0
dialogVisible.value = false; dialogVisible.value = false;
global.setOrderMember({})
global.setOrderTable({})
printOrderLable() printOrderLable()
emit("paySuccess"); emit("paySuccess");
} }
function show() { function show() {
dialogVisible.value = true; dialogVisible.value = true;
getLocalMemberInfo() cartList.value = []
props.cart.map(item => {
if (item.info && item.info.length) {
item.info.map(item => {
cartList.value.push({ ...item })
})
} else {
cartList.value.push({ ...item })
}
})
} }
defineExpose({ defineExpose({
show, show,
}); });
const memberInfo = ref('')
// 从本地获取会员信息
function getLocalMemberInfo() {
let localMemberInfo = useStorage.get('memberInfo')
if (localMemberInfo && localMemberInfo.telephone) {
memberInfo.value = localMemberInfo
} else {
memberInfo.value = ''
}
}
onMounted(() => {
getLocalMemberInfo()
});
</script> </script>
<style> <style>
@@ -234,6 +340,15 @@ onMounted(() => {
</style> </style>
<style scoped lang="scss"> <style scoped lang="scss">
.footer_wrap {
display: flex;
gap: 10px;
.btn {
flex: 1;
}
}
.drawer_wrap { .drawer_wrap {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -291,18 +406,23 @@ onMounted(() => {
.p { .p {
color: #999; color: #999;
width: 160px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
} }
} }
.list_wrap { .list_wrap {
padding: var(--el-font-size-base); padding: 0 var(--el-font-size-base);
height: calc(100vh - 200px); height: calc(100vh - 200px);
overflow-y: auto; overflow-y: auto;
.item { .item {
padding-bottom: var(--el-font-size-base); padding: var(--el-font-size-base) 0;
border-bottom: 1px solid #ececec;
.top { .top {
display: flex; display: flex;
@@ -313,11 +433,12 @@ onMounted(() => {
} }
.n { .n {
margin-right: 50px; width: 50px;
color: #555; color: #555;
} }
.p { .p {
width: 50px;
color: #555; color: #555;
} }
} }
@@ -332,8 +453,6 @@ onMounted(() => {
.tag_wrap { .tag_wrap {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
padding-top: 10px;
padding-bottom: 10px;
.tag { .tag {
padding: 2px 6px; padding: 2px 6px;

View File

@@ -0,0 +1,131 @@
<!-- 称重商品组件 -->
<template>
<el-dialog title="称重商品" width="400" :close-on-click-modal="false" v-model="dialogVisible" top="10vh"
@closed="reset">
<div class="input_wrap">
<div class="item">
<div class="title">单价</div>
<el-button type="primary" plain>{{ goodsItem.lowPrice }}/{{ goodsItem.unitName }}</el-button>
</div>
<div class="item">
<div class="title">重量</div>
<el-input v-model="number" readonly placeholder="请输入">
<template #append>{{ goodsItem.unitName }}</template>
</el-input>
</div>
</div>
<div class="keybord_wrap">
<div v-for="item in 9" :key="item">
<el-button plain type="info" style="width: 100%" @click="inputHandle(item)">{{ item }}</el-button>
</div>
<div>
<el-button plain type="info" style="width: 100%" @click="inputHandle('.')">.</el-button>
</div>
<div>
<el-button plain type="info" style="width: 100%" @click="inputHandle(0)">0</el-button>
</div>
<div>
<el-button plain type="info" icon="CloseBold" style="width: 100%" @click="delHandle"></el-button>
</div>
</div>
<div class="price_item">
{{ formatDecimal(goodsItem.lowPrice * number) }}
</div>
<div class="footer">
<el-button style="width: 100%" :loading="loading" @click="dialogVisible = false">
取消
</el-button>
<el-button type="primary" style="width: 100%" :loading="loading" :disabled="number <= 0"
@click="confirmHandle">
确认
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
const dialogVisible = ref(false);
const number = ref("");
const goodsItem = ref({})
const emit = defineEmits(["success"]);
function show(item) {
dialogVisible.value = true;
goodsItem.value = { ...item }
}
function reset() {
goodsItem.value = {}
number.value = ''
}
// 输入
function inputHandle(n) {
// number.value += n;
number.value = inputFilterFloat(number.value += n)
}
// 删除
function delHandle() {
if (!number.value) return;
number.value = number.value.substring(0, number.value.length - 1);
}
const loading = ref(false)
// 确认
function confirmHandle() {
if (!number.value) return
goodsItem.value.productId = goodsItem.value.id
goodsItem.value.number = number.value
emit("success", goodsItem.value);
dialogVisible.value = false;
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.keybord_wrap {
padding: var(--el-font-size-base) 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
gap: var(--el-font-size-base);
:deep(.el-button--large) {
height: 50px;
}
}
.input_wrap {
display: flex;
align-items: center;
gap: 20px;
.item {
display: flex;
flex-direction: column;
gap: 10px;
}
}
.price_item {
font-size: 18px;
font-weight: bold;
color: var(--el-color-danger);
padding: 15px 0;
border-top: 1px solid #ececec;
}
.footer {
display: flex;
}
</style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="content"> <div class="content">
<div class="cart_wrap card"> <div class="cart_wrap card" v-loading="cartLoading">
<div class="menu_top"> <div class="menu_top">
<div class="menu" @click="pendingCartModalRef.show()"> <div class="menu" @click="pendingCartModalRef.show()">
<el-icon class="icon"> <el-icon class="icon">
@@ -11,65 +11,98 @@
<div class="number" @click="takeFoodCodeRef.show()"> <div class="number" @click="takeFoodCodeRef.show()">
<el-text class="t">{{ masterId }}</el-text> <el-text class="t">{{ masterId }}</el-text>
</div> </div>
<div class="select_user" @click="fastCashierRef.show()" v-if="!memberInfo.telephone"> <div class="select_user" @click="quickCashHandle"
<el-icon class="icon"> v-if="!global.orderMemberInfo.telephone && !global.tableInfo.id">
<WalletFilled /> <div class="left">
</el-icon> <el-icon class="icon">
<el-text class="t">快捷收银</el-text> <WalletFilled />
</el-icon>
<el-text class="t">快捷收银</el-text>
</div>
<el-icon class="arrow"> <el-icon class="arrow">
<ArrowRight /> <ArrowRight />
</el-icon> </el-icon>
</div> </div>
<div class="select_user" v-else @click="clearMember"> <div class="select_user" v-else @click="clearMember">
<el-icon class="icon"> <div class="left">
<UserFilled /> <el-icon class="icon">
</el-icon> <UserFilled />
<el-text class="t">{{ memberInfo.telephone }}</el-text> </el-icon>
<div class="t_wrap" :class="{ 'big_text': global.orderMemberInfo.telephone && global.tableInfo.id }">
<div class="t" v-if="global.orderMemberInfo.telephone">
会员{{ global.orderMemberInfo.telephone }}
</div>
<div class="t" v-if="global.tableInfo.id">
台桌{{ global.tableInfo.name }}
<span v-if="global.tableInfo.num">/{{ global.tableInfo.num }}</span>
</div>
</div>
</div>
<el-icon class="arrow"> <el-icon class="arrow">
<Close /> <Close />
</el-icon> </el-icon>
</div> </div>
</div> </div>
<div class="shop_operation" v-loading="cartLoading"> <div class="shop_operation">
<div class="shop_list"> <div class="shop_list">
<div class="item" :class="{ active: cartListActive == index }" v-for="(item, index) in cartList" <template v-for="(arr, index) in cartList" :key="index">
:key="item.id" @click="selectCartItemHandle(item, index)"> <el-divider v-if="arr.placeNum">{{ `${arr.placeNum}次下单` }}</el-divider>
<div class="name_wrap"> <div class="item" :class="{ active: item.active }" :key="item.id" v-for="(item, i) in arr.info"
<span>{{ item.name }}</span> @click="selectCartItemHandle(item, index, i)">
<span>{{ item.salePrice }}</span> <div class="name_wrap">
</div> <span>{{ item.name }}</span>
<div class="sku_list" v-if="item.skuName"> <div class="price">
<div class="tag" v-for="item in item.skuName.split(',')"> <span :class="{ dis: item.discountSaleAmount }">{{ item.salePrice }}</span>
{{ item }} <span v-if="item.discountSaleAmount">
</div> {{ formatDecimal(item.salePrice - item.discountSaleAmount, 2, true) }}</span>
</div>
<div class="num">
<div class="left">
<div class="icon_item" v-if="item.isGift == 'true'" @click="giftPackHandle('isGift', item)">
<el-icon class="icon">
<ShoppingBag />
</el-icon>
</div>
<div class="icon_item" v-if="item.isPack == 'true'" @click="giftPackHandle('isPack', item)">
<el-icon class="icon" style="color: var(--primary-color)">
<Box />
</el-icon>
</div> </div>
</div> </div>
<el-text class="t">X{{ item.number }}</el-text> <div class="sku_list" v-if="item.skuName">
<div class="tag" v-for="item in item.skuName.split(',')">
{{ item }}
</div>
</div>
<div class="grooup_wrap" v-if="item.proGroupInfo">
{{ item.groupType == 0 ? '固定套餐' : '自选套餐' }}
<span>{{ JSON.parse(item.proGroupInfo).map(item => item.proName).join('、') }}</span>
</div>
<div class="num">
<div class="left">
<div class="icon_item zen" v-if="item.isGift == 'true'" @click="giftPackHandle('isGift', item)">
<span class="t"></span>
</div>
<div class="icon_item bao" v-if="item.isPack == 'true'" @click="giftPackHandle('isPack', item)">
<span class="t"></span>
</div>
<div class="icon_item tui" v-if="item.status == 'return'">
<span class="t">退</span>
</div>
<div class="icon_item lin" v-if="item.isTemporary == 1">
<span class="t"></span>
</div>
<div class="icon_item zhe" v-if="item.discountSaleAmount">
<span class="t"></span>
</div>
<div class="icon_item chu" v-if="item.isPrint == 0">
<span class="t">免厨打印</span>
</div>
</div>
<el-text class="t">X{{ formatDecimal(item.number, 2, true) }}</el-text>
</div>
</div> </div>
</div> </template>
<div class="empty"> <div class="empty">
<el-empty description="请选择商品" v-if="!cartList.length" /> <el-empty description="请选择商品" v-if="!cartList.length" />
</div> </div>
</div> </div>
<!-- 购物车操作栏 --> <!-- 购物车操作栏 -->
<cartOperation :item="cartList[cartListActive]" @confirm="(res) => addCart(res, 'edit')" @delete="delCartHandle" <cartOperation :item="cartListActiveItem" @confirm="(res) => addCart(res, 'edit')" @delete="delCartHandle"
@pending="pendingCart" @clearCart="clearCartHandle" /> @pending="pendingCart" @clearCart="clearCartHandle" />
</div> </div>
<div class="footer"> <div class="footer">
<div class="top"> <div class="top">
<div class="left" @click="allSelectedHandle"> <div class="left" @click="allSelectedHandle"
v-if="JSON.parse(shopStore.info.eatModel).some(item => item == 'take-out')">
<div class="selected"> <div class="selected">
<div class="selected_round" v-if="!allSelected"></div> <div class="selected_round" v-if="!allSelected"></div>
<el-icon class="icon" v-else> <el-icon class="icon" v-else>
@@ -78,27 +111,40 @@
</div> </div>
<el-text class="t">打包({{ cartInfo.packAmount || 0 }})</el-text> <el-text class="t">打包({{ cartInfo.packAmount || 0 }})</el-text>
</div> </div>
<div class="left" v-else></div>
<div class="num-wrap"> <div class="num-wrap">
<el-text>{{ cartInfo.productNum || 0 }}种商品{{ <!-- {{ cartInfo.productNum || 0 }}种商品 -->
<el-text>{{
cartInfo.productSum || 0 cartInfo.productSum || 0
}}</el-text> }}</el-text>{{ formatDecimal(cartInfo.totalAmount || 0) }}
</div> </div>
</div> </div>
<div class="btm"> <div class="btm">
<el-button icon="Edit" @click="remarkRef.show()"></el-button> <el-button icon="Edit" @click="remarkRef.show()"></el-button>
<div class="button"> <div class="button">
<el-button type="primary" style="width: 100%" :disabled="!cartList.length" v-loading="createOrderLoading" <div class="btn" v-if="shopStore.info.registerType == 'restaurant'">
@click="createOrderHandle"> <el-button type="primary" style="width: 100%;" :disabled="!cartList.length" v-loading="createOrderLoading"
<span v-if="!createOrderLoading">结算({{ cartInfo.totalAmount || 0 }})</span> @click="createOrderHandle(0)">
<span v-else>下单中...</span> <template v-if="!createOrderLoading">
</el-button> 仅下单</template>
<template v-else>下单中...</template>
</el-button>
</div>
<div class="btn" v-if="shopStore.info.registerType != 'restaurant' || cartList.length">
<el-button type="primary" style="width: 100%;" :disabled="!cartList.length" v-loading="createOrderLoading"
@click="createOrderHandle(1)">
<template v-if="!createOrderLoading">
去结算</template>
<template v-else>下单中...</template>
</el-button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="shop_manage card"> <div class="shop_manage card">
<!-- 分类/商品列表 --> <!-- 分类/商品列表 -->
<goods ref="goodsRef" :masterId="masterId" @success="addCart" /> <goods ref="goodsRef" :masterId="masterId" @success="addCart" @loading="cartLoading = true" />
<!-- ©银收客 v{{ packageData.version }} --> <!-- ©银收客 v{{ packageData.version }} -->
</div> </div>
</div> </div>
@@ -112,11 +158,13 @@
<takeFoodCode ref="takeFoodCodeRef" title="修改取餐号" placeholder="请输入取餐号" @success="takeFoodCodeSuccess" /> <takeFoodCode ref="takeFoodCodeRef" title="修改取餐号" placeholder="请输入取餐号" @success="takeFoodCodeSuccess" />
<!-- 结算订单 --> <!-- 结算订单 -->
<settleAccount ref="settleAccountRef" :cart="cartList" :amount="cartInfo.totalAmount" :remark="remark" <settleAccount ref="settleAccountRef" :cart="cartList" :amount="cartInfo.totalAmount" :remark="remark"
:masterId="masterId" :orderInfo="orderInfo" :member="memberInfo" @paySuccess="createCodeAjax(1)" /> :masterId="masterId" :orderInfo="orderInfo" @paySuccess="createCodeAjax(1)" />
<!-- 快捷收银 --> <!-- 快捷收银 -->
<fastCashier ref="fastCashierRef" type="0" /> <fastCashier ref="fastCashierRef" type="0" />
<!-- 挂起订单 --> <!-- 挂起订单 -->
<pendingCartModal ref="pendingCartModalRef" @select="pendingCartHandle" /> <pendingCartModal ref="pendingCartModalRef" @select="pendingCartHandle" />
<!-- 检查版本升级 -->
<updateDialog />
</template> </template>
<script> <script>
@@ -128,6 +176,9 @@ export default {
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import { useGlobal } from '@/store/global.js'
import updateDialog from '@/components/updateDialog.vue'
import remarkModal from "@/components/remarkModal.vue"; import remarkModal from "@/components/remarkModal.vue";
import takeFoodCode from "@/components/takeFoodCode.vue"; import takeFoodCode from "@/components/takeFoodCode.vue";
import cartOperation from "@/views/home/components/cartOperation.vue"; import cartOperation from "@/views/home/components/cartOperation.vue";
@@ -135,6 +186,7 @@ import settleAccount from "@/views/home/components/settleAccount.vue";
import fastCashier from "@/views/home/components/fastCashier.vue"; import fastCashier from "@/views/home/components/fastCashier.vue";
import pendingCartModal from "@/views/home/components/pendingCartModal.vue"; import pendingCartModal from "@/views/home/components/pendingCartModal.vue";
import useStorage from '@/utils/useStorage' import useStorage from '@/utils/useStorage'
import { formatDecimal } from '@/utils/index.js'
import { import {
createCart, createCart,
@@ -147,9 +199,20 @@ import {
createOrder, createOrder,
} from "@/api/product"; } from "@/api/product";
import { orderChoseCount } from '@/api/table.js'
import { queryShopInfo, staffPermission } from '@/api/user.js'
// 商品列表 // 商品列表
import goods from "@/views/home/components/goods.vue"; import goods from "@/views/home/components/goods.vue";
import member from "@/views/member/index.vue"; import member from "@/views/member/index.vue";
import { ElMessage } from "element-plus";
import { useShop } from '@/store/shop.js'
const shopStore = useShop()
const global = useGlobal()
const route = useRoute() const route = useRoute()
@@ -166,6 +229,7 @@ const allSelected = ref(false);
const remark = ref(""); const remark = ref("");
const cartListActive = ref(0); const cartListActive = ref(0);
const cartListActiveItem = ref({})
const cartList = ref([]); const cartList = ref([]);
const cartInfo = ref({}); const cartInfo = ref({});
const cartLoading = ref(false); const cartLoading = ref(false);
@@ -173,26 +237,56 @@ const cartLoading = ref(false);
const orderInfo = ref({}); const orderInfo = ref({});
const createOrderLoading = ref(false); const createOrderLoading = ref(false);
const memberInfo = ref({})
// 取餐码 // 取餐码
const masterId = ref(""); const masterId = ref("");
// 挂单量 // 挂单量
const pendingCartNum = ref(0); const pendingCartNum = ref(0);
// 快捷收银
async function quickCashHandle() {
try {
await staffPermission('yun_xu_shou_kuan')
fastCashierRef.value.show()
} catch (error) {
console.log(error);
}
}
// 生成订单 // 生成订单
async function createOrderHandle() { async function createOrderHandle(t = 0) {
try { try {
createOrderLoading.value = true; createOrderLoading.value = true;
await staffPermission('yun_xu_xia_dan')
const res = await createOrder({ const res = await createOrder({
masterId: masterId.value, masterId: masterId.value,
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
remark: remark.value, remark: remark.value,
vipUserId: global.orderMemberInfo.id || '',
tableId: global.tableInfo.qrcode || '',
type: t,
seatNum: global.tableInfo.num
}); });
orderInfo.value = res;
settleAccountRef.value.show();
createOrderLoading.value = false; createOrderLoading.value = false;
// 订单数据
orderInfo.value = res;
if (shopStore.info.registerType == 'restaurant' && t == 0) {
ElMessage.success('下单成功')
queryCartAjax()
} else {
settleAccountRef.value.show();
}
// if (global.tableInfo.id && t == 0) {
// ElMessage.success('下单成功')
// global.setOrderTable({})
// createCodeAjax(1)
// } else {
// orderInfo.value = res;
// settleAccountRef.value.show();
// }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
createOrderLoading.value = false; createOrderLoading.value = false;
@@ -205,7 +299,9 @@ async function clearCartHandle() {
await clearCart({ await clearCart({
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
masterId: masterId.value, masterId: masterId.value,
tableId: global.tableInfo.qrcode || ''
}); });
cartListActiveItem.value = {}
queryCartAjax(); queryCartAjax();
// 清除商品所有红点 // 清除商品所有红点
@@ -236,14 +332,22 @@ async function pendingCart(params, status = true) {
masterId: params.masterId, masterId: params.masterId,
status: status, status: status,
uuid: params.uuid, uuid: params.uuid,
vipUserId: global.orderMemberInfo.id || '',
tableId: global.tableInfo.qrcode || '',
orderId: params.orderId
}); });
if (status && cartList.value.length) { if (status && cartList.value.length) {
await createCodeAjax(); await createCodeAjax();
cartLoading.value = false; setTimeout(() => {
cartLoading.value = false;
}, 500);
} else { } else {
cartLoading.value = false; setTimeout(() => {
cartLoading.value = false;
}, 500);
} }
} catch (error) { } catch (error) {
cartLoading.value = false;
console.log(error); console.log(error);
} }
} }
@@ -256,10 +360,12 @@ async function delCartHandle(params) {
masterId: params.masterId, masterId: params.masterId,
cartId: params.id, cartId: params.id,
}); });
cartListActiveItem.value = {}
await queryCartAjax(); await queryCartAjax();
cartLoading.value = false; cartLoading.value = false;
cartListActive.value = 0; cartListActive.value = 0;
} catch (error) { } catch (error) {
cartLoading.value = false;
console.log(error); console.log(error);
} }
} }
@@ -295,30 +401,57 @@ async function takeFoodCodeSuccess(code) {
} }
// 从购物车选择商品 // 从购物车选择商品
function selectCartItemHandle(item, index) { function selectCartItemHandle(row, index, i) {
cartListActive.value = index; cartList.value.map(item => {
item.info.map(val => {
if (val.id == row.id) {
val.active = true
cartListActiveItem.value = val
} else {
val.active = false
}
})
})
} }
// 选择完规格开始添加购物车 // 选择完规格开始添加购物车
async function addCart(params, type = "add") { async function addCart(params = {}, type = "add") {
console.log(params);
try { try {
cartLoading.value = true; cartLoading.value = true;
const res = await createCart({ if (params.isTemporary) {
productId: params.productId, await createCodeAjax()
masterId: masterId.value, cartLoading.value = false;
shopId: store.userInfo.shopId, } else {
skuId: type == "add" ? params.id : params.skuId, let skuId = ''
number: params.number || 1, if (params.skuList && params.skuList.length) {
isPack: params.isPack || "false", skuId = params.skuList[0].id
isGift: params.isGift || "false", } else {
cartId: type == "add" ? "" : params.id, skuId = type == "add" ? params.id : params.skuId
uuid: params.uuid || store.userInfo.uuid, }
type: type,
}); const res = await createCart({
cartLoading.value = false; productId: params.productId,
masterId.value = res; masterId: masterId.value,
goodsRef.value.updateData(); tableId: global.tableInfo.qrcode || '',
queryCartAjax(); vipUserId: global.orderMemberInfo.id || '',
shopId: store.userInfo.shopId,
// skuId: type == "add" ? params.id : params.skuId,
skuId: skuId,
number: params.number || 1,
isPack: params.isPack || "false",
isGift: params.isGift || "false",
cartId: type == "add" ? "" : params.id,
uuid: params.uuid || store.userInfo.uuid,
type: type,
groupProductIdList: params.groupProductIdList || []
});
cartLoading.value = false;
masterId.value = res;
goodsRef.value.updateData();
queryCartAjax();
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
cartLoading.value = false; cartLoading.value = false;
@@ -331,8 +464,33 @@ async function queryCartAjax() {
const res = await queryCart({ const res = await queryCart({
masterId: masterId.value, masterId: masterId.value,
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
tableId: global.tableInfo.qrcode || '',
vipUserId: global.orderMemberInfo.id || ''
}); });
if (!res.list.length) {
cartListActiveItem.value = {}
}
res.list.map((item, index) => {
item.info.map((val, i) => {
if (i == 0 && index == 0) {
val.active = true
if (!cartListActiveItem.value.id) {
cartListActiveItem.value = val
}
} else {
val.active = false
}
})
})
cartList.value = res.list; cartList.value = res.list;
if (cartListActiveItem.value.id) {
selectCartItemHandle(cartListActiveItem.value)
}
cartInfo.value = res.amount; cartInfo.value = res.amount;
pendingCartNum.value = res.num; pendingCartNum.value = res.num;
goodsRef.value.updateData(); goodsRef.value.updateData();
@@ -353,6 +511,20 @@ async function queryCartAjax() {
} }
} }
// 增加点餐人数
async function addTableNum() {
try {
const res = await orderChoseCount({
masterId: masterId.value,
shopId: store.userInfo.shopId,
tableId: global.tableInfo.qrcode,
num: global.tableInfo.num
})
} catch (error) {
console.log(error);
}
}
// 获取取餐码 // 获取取餐码
async function createCodeAjax(type = "0") { async function createCodeAjax(type = "0") {
try { try {
@@ -364,13 +536,21 @@ async function createCodeAjax(type = "0") {
// }) // })
// masterId.value = res.code // masterId.value = res.code
// } // }
const res = await createCode({ if (global.tableInfo.masterId) {
shopId: store.userInfo.shopId, masterId.value = global.tableInfo.masterId
type: type, } else {
}); const res = await createCode({
masterId.value = res.code; shopId: store.userInfo.shopId,
queryCartAjax(); type: type,
getLocalMemberInfo() tableId: global.tableInfo.qrcode || '',
});
masterId.value = res.code;
}
if (global.tableInfo.num) {
await addTableNum()
}
await queryCartAjax();
if (type == 1) { if (type == 1) {
// 结算订单 清楚商品所有红点 // 结算订单 清楚商品所有红点
@@ -381,25 +561,16 @@ async function createCodeAjax(type = "0") {
} }
} }
// 从本地获取会员信息 // 清除本地会员/台桌信息
function getLocalMemberInfo() {
let localMemberInfo = useStorage.get('memberInfo')
if (localMemberInfo && localMemberInfo.telephone) {
memberInfo.value = localMemberInfo
} else {
memberInfo.value = {}
}
}
// 清除本地会员
function clearMember() { function clearMember() {
useStorage.del('memberInfo') global.setOrderMember({})
getLocalMemberInfo() global.setOrderTable({})
createCodeAjax()
} }
onMounted(() => { onMounted(() => {
createCodeAjax(); createCodeAjax()
getLocalMemberInfo() shopStore.queryShopInfo()
}); });
</script> </script>
@@ -418,7 +589,7 @@ onMounted(() => {
.menu { .menu {
background-color: var(--el-color-warning); background-color: var(--el-color-warning);
color: #fff; color: #fff;
width: 100px; width: 60px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -437,7 +608,7 @@ onMounted(() => {
} }
.number { .number {
flex: 1; width: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -450,24 +621,43 @@ onMounted(() => {
} }
.select_user { .select_user {
flex: 1;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
background-color: var(--el-color-info-light-8); background-color: var(--el-color-info-light-8);
padding: 0 var(--el-font-size-base); padding: 0 10px;
.icon { .left {
color: var(--el-color-primary); display: flex;
font-size: 18px; align-items: center;
}
.t { .icon {
font-size: var(--el-font-size-base); color: var(--el-color-primary);
padding: 0 10px; font-size: 20px;
}
.t_wrap {
display: flex;
flex-direction: column;
align-items: flex-start;
&.big_text {
.t {
font-size: 12px;
}
}
}
.t {
font-size: var(--el-font-size-base);
margin-left: 4px;
}
} }
.arrow { .arrow {
color: #999; color: #999;
font-size: 20px; font-size: 16px;
position: relative; position: relative;
top: 2px; top: 2px;
} }
@@ -499,6 +689,13 @@ onMounted(() => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: var(--el-font-size-base); font-size: var(--el-font-size-base);
.dis {
color: #999;
font-size: 12px;
text-decoration: line-through;
margin-right: 4px;
}
} }
.sku_list { .sku_list {
@@ -515,6 +712,17 @@ onMounted(() => {
} }
} }
.grooup_wrap {
padding-top: 10px;
font-size: 12px;
color: #555;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.num { .num {
padding-top: var(--el-font-size-base); padding-top: var(--el-font-size-base);
display: flex; display: flex;
@@ -524,17 +732,52 @@ onMounted(() => {
.left { .left {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 10px;
padding-right: 10px;
.icon_item { .icon_item {
$size: 30px; $size: 20px;
width: $size;
height: $size; height: $size;
border-radius: 4px; padding: 0 6px;
border-radius: 2px;
background-color: #e2e2e2; background-color: #e2e2e2;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-right: 10px; color: #fff;
font-size: 10px;
&.zen {
background-color: #FFB0B1;
color: #FF4D4F;
}
&.bao {
background-color: #52C41A;
}
&.tui {
background-color: var(--el-color-danger);
}
&.lin {
background-color: var(--el-color-warning);
}
&.zhe {
background-color: var(--primary-color);
}
&.chu {
background-color: #ffe7ba;
color: #e69f1c;
}
span {
font-size: inherit;
color: inherit;
}
} }
} }
@@ -607,6 +850,12 @@ onMounted(() => {
.button { .button {
flex: 1; flex: 1;
display: flex;
gap: var(--el-font-size-base);
.btn {
flex: 1;
}
.js { .js {
.t { .t {

View File

@@ -1,271 +1,83 @@
<template> <template>
<!-- <el-button @click="chooseSerial">获取串口列表</el-button> --> <el-dialog v-model="showDialog" title="发现新版本" width="500" :close-on-click-modal="false"
<el-button @click="initWebSocket()">连接ws</el-button> :close-on-press-escape="false" :show-close="false">
<div class="message">
{{ updataInfo.message }}
</div>
<div class="progress_wrap" style="padding-top: 20px;">
<el-progress :percentage="uploadPro" :stroke-width="15" striped :striped-flow="uploadPro < 100" />
</div>
<template #footer>
<div class="footer" style="padding: 0 20px 20px;">
<el-button v-if="!updataInfo.isUp">下次更新</el-button>
<el-button type="primary" :loading="isUpload" @click="uplaodHandle">
<template v-if="!uploadSucess">
<template v-if="!isUpload">
立即更新
</template>
<template v-else>
下载中...
</template>
</template>
<template v-else>
立即安装
</template>
</el-button>
</div>
</template>
</el-dialog>
</template> </template>
<script setup> <script setup>
import _ from 'lodash' import { onMounted, ref } from 'vue'
import { bySubType } from "@/api/device"; import { findVersion } from '@/api/user.js'
import { ElMessage } from "element-plus"; import packageData from "../../../package.json";
import dayjs from 'dayjs'
import { ipcRenderer } from 'electron' import { ipcRenderer } from 'electron'
import { onMounted, ref } from 'vue';
import { useUser } from "@/store/user.js";
import useStorage from '@/utils/useStorage'
const store = useUser();
// 小票打印机列表 const showDialog = ref(false)
const printList = ref([]); const updataInfo = ref({})
// 标签打印机列表 const isUpload = ref(false)
const printLabelList = ref([]); const uploadPro = ref(0)
const localPrintList = ref([]) const uploadSucess = ref(false)
const uploadResponse = ref({})
const tempFilePath = ref('')
// 获取打印机状态 // 检查版本更新
async function bySubTypeAjax() { async function findVersionAjax() {
try { try {
const res1 = await bySubType({ const res = await findVersion()
shopId: store.userInfo.shopId, let reg = /\./g;
contentType: "local", if (res.version.replace(reg, '') > packageData.version.replace(reg, '') && res.url) {
subType: "cash", showDialog.value = true
}); updataInfo.value = res
const res2 = await bySubType({ }
shopId: store.userInfo.shopId,
contentType: "local",
subType: "label",
});
printList.value = res1;
printLabelList.value = res2;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
// 获取本地打印机列表 // 下载新版本
function getPrintList() { async function uplaodHandle() {
ipcRenderer.send("getPrintList"); try {
ipcRenderer.on("printList", (event, arg) => { if (!uploadSucess.value) {
localPrintList.value = arg; isUpload.value = true
// console.log(localPrintList.value); ipcRenderer.send('downloadFile', JSON.stringify({ url: updataInfo.value.url }))
}); // await downloadFile(updataInfo.value.url)
} // isUpload.value = false
// uploadSucess.value = true
// 检查本地打印机是否能正常使用
function checkLocalPrint(deviceName) {
let print = ''
for (let item of localPrintList.value) {
if (item.name == deviceName) {
print = item
}
}
if (!print.name) {
return false
} else {
return true
}
}
// 打印小票
function printBill(props) {
if (!checkLocalPrint(printLabelList.value[0].config.deviceName)) {
ElMessage.error("本地打印机无法使用,请检查打印机是否正确连接");
} else {
const data = {
shop_name: store.userInfo.merchantName,
carts: props.carts,
amount: props.amount,
remark: props.remark,
orderInfo: props.orderInfo,
deviceName: printList.value[0].config.deviceName,
createdAt: dayjs(props.orderInfo.createdAt).format(
"YYYY-MM-DD HH:mm:ss"
),
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
};
ipcRenderer.send("printerInfoSync", JSON.stringify(data));
}
}
// 检测是否打印标签小票
function checkLabelPrint(props) {
if (!checkLocalPrint(printLabelList.value[0].config.deviceName)) {
ElMessage.error("本地打印机无法使用,请检查打印机是否正确连接");
} else {
let pids = printLabelList.value[0].config.categoryList.map(item => item.id)
let labelList = []
props.carts.map(item => {
if (pids.some(el => el == item.categoryId)) {
for (let i = 0; i < item.number; i++) {
labelList.push(
{
outNumber: props.outNumber,
name: item.name,
skuName: item.skuName,
masterId: props.orderInfo.tableName,
deviceName: printLabelList.value[0].config.deviceName,
createdAt: dayjs(props.createdAt).format('YYYY-MM-DD HH:mm:ss')
}
)
}
}
})
printLabel(labelList)
}
}
// 打印标签
let labelCount = ref(0)
let labelPrintTimer = ref(null)
function printLabel(list) {
console.log(list);
if (!checkLocalPrint(printLabelList.value[0].config.deviceName)) {
ElMessage.error("本地打印机无法使用,请检查打印机是否正确连接");
} else {
labelPrintTimer.value = setInterval(() => {
// console.log('labelCount', labelCount.value);
list[labelCount.value].count = `${list.length - labelCount.value}/${list.length}`
ipcRenderer.send('printerTagSync', JSON.stringify(list[labelCount.value]))
labelCount.value++
if (labelCount.value > list.length - 1) {
clearInterval(labelPrintTimer.value)
labelPrintTimer.value = null
labelCount.value = 0
}
}, 1000)
}
}
let ws = ref(null)
let wsIsClose = ref(false)
// 初始化websocket
function initWebSocket(wsUrl = 'wss://wxcashiertest.sxczgkj.cn/client') {
ws.value = new WebSocket(wsUrl);
console.log("websocket:", ws.value);
ws.value.onopen = function () {
console.log('wss连接成功');
// 清除心跳
clearInterval(heartbeatTimer.value)
heartbeatTimer.value = null
startheartbeat()
// 清除重连
clearInterval(reConnectTimer.value)
reConnectTimer.value = null
reConnectCount.value = 0
ws.value.send(JSON.stringify({
type: "connect",
shopId: store.userInfo.shopId,
clientId: useStorage.get('uuid')
}))
};
// 接收消息
ws.value.onmessage = function (e) {
// websocketonmessage(e);
let data = JSON.parse(e.data)
if (data.type == 'order') {
console.log('接收消息', data);
// 接收订单消息,打印小票
// printBill(data)
// 检测是否需要打印标签小票
checkLabelPrint(data)
}
};
// 连接发生错误
ws.value.onerror = function () {
console.log("WebSocket连接发生错误");
// 清除心跳
clearInterval(heartbeatTimer.value)
heartbeatTimer.value = null
// 手动关闭后不在执行自动连接任务
if (!wsIsClose.value) reConnect(wsUrl);
};
// 关闭
ws.value.onclose = function (e) {
console.log('ws关闭了', e);
// 清除心跳
clearInterval(heartbeatTimer.value)
heartbeatTimer.value = null
// 手动关闭后不在执行自动连接任务
if (!wsIsClose.value) reConnect(wsUrl);
};
}
// 启动心跳连接
let heartbeatTimer = ref(null)
function startheartbeat() {
heartbeatTimer.value = setInterval(() => {
ws.value.send(JSON.stringify({ type: 'heartbeat' }))
}, 10000)
}
// 重连最大次数5次
let reConnectCount = ref(0)
let reConnectTimer = ref(null)
function reConnect(wsUrl) {
if (reConnectTimer.value != null) return
reConnectTimer.value = setInterval(() => {
// 自动连接超过5次不在连接需手动出发
console.log('reConnectCount.value===', reConnectCount.value);
if (reConnectCount.value >= 5) {
console.log('重连超过5次不在连接');
clearInterval(reConnectTimer.value)
reConnectTimer.value = null
reConnectCount.value = 0
wsIsClose.value = true
ws.value.close()
} else { } else {
reConnectCount.value++ // 安装文件
initWebSocket(wsUrl)
} }
}, 5000) } catch (error) {
console.log(error);
}
} }
// 打印标签小票
const printTag = () => {
ipcRenderer.send('printerTagSync', JSON.stringify({
deviceName: 'Xprinter XP-365B123'
}))
}
//选择串口设备
const chooseSerial = async () => {
// let printNum = localStorage.getItem('printNum')
// if (!printNum) {
// printNum = 1
// localStorage.setItem('printNum', printNum)
// } else {
// printNum++
// localStorage.setItem('printNum', printNum)
// }
// ipcRenderer.send('printStart', printNum)
// ipcRenderer.send('getSerialPort')
};
onMounted(() => { onMounted(() => {
ipcRenderer.on('seriaportList', (e, a) => { findVersionAjax()
console.log('seriaportList', a); ipcRenderer.on('updateProgress', (event, res) => {
// console.log('updateProgress===', event, res);
uploadPro.value = res
}) })
getPrintList();
bySubTypeAjax();
initWebSocket()
}) })
</script> </script>

View File

@@ -22,7 +22,16 @@
<el-input v-model="form.loginName" placeholder="请输入11位手机号码"></el-input> <el-input v-model="form.loginName" placeholder="请输入11位手机号码"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="登录密码" prop="password"> <el-form-item label="登录密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入登录密码"></el-input> <el-input v-model="form.password" :type="passwordType" placeholder="请输入登录密码">
<template #suffix>
<el-icon class="el-input__icon" v-if="passwordType == 'password'" @click="passwordType = 'text'">
<Hide />
</el-icon>
<el-icon class="el-input__icon" v-else @click="passwordType = 'password'">
<View />
</el-icon>
</template>
</el-input>
</el-form-item> </el-form-item>
<!-- <el-form-item> <!-- <el-form-item>
<div style="width: 100%; display: flex; justify-content: flex-end"> <div style="width: 100%; display: flex; justify-content: flex-end">
@@ -54,7 +63,8 @@
import packageData from "../../package.json"; import packageData from "../../package.json";
import logo from "@/assets/logo.png"; import logo from "@/assets/logo.png";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { reactive, ref } from "vue"; import { Hide, View } from '@element-plus/icons-vue'
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { RandomNumBoth } from "@/utils"; import { RandomNumBoth } from "@/utils";
@@ -62,6 +72,11 @@ import useStorage from "@/utils/useStorage";
import { douyincheckIn } from "@/api/group"; import { douyincheckIn } from "@/api/group";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import { useSocket } from "@/store/socket.js"; import { useSocket } from "@/store/socket.js";
import { useGlobal } from '@/store/global.js'
import { useShop } from '@/store/shop.js'
const global = useGlobal()
const shopInfo = useShop()
const store = useUser(); const store = useUser();
const socket = useSocket(); const socket = useSocket();
@@ -69,6 +84,7 @@ const socket = useSocket();
const router = useRouter(); const router = useRouter();
const formRef = ref(null); const formRef = ref(null);
const loading = ref(false); const loading = ref(false);
const passwordType = ref('password')
const form = reactive({ const form = reactive({
serialNumber: RandomNumBoth(1000, 9999), serialNumber: RandomNumBoth(1000, 9999),
@@ -110,19 +126,23 @@ const submitHandle = () => {
store store
.userlogin(form) .userlogin(form)
.then(async (res) => { .then(async (res) => {
// const douyin = await douyincheckIn({ // 登录成功后保存商户号
// token: res.token, useStorage.set('merchantLoginAccount', form.merchantName)
// loginName: res.loginName,
// clientType: 'pc'
// })
// useStorage.set('douyin', douyin.userInfo)
ElMessage.success("登录成功"); ElMessage.success("登录成功");
socket.init(); socket.init();
await shopInfo.queryShopInfo()
setTimeout(() => { setTimeout(() => {
router.replace({ router.replace({
name: "home", name: "home",
}); });
}, 1000); }, 1000);
const douyin = await douyincheckIn({
token: res.token,
loginName: res.loginName,
clientType: 'pc'
})
useStorage.set('douyin', douyin.userInfo)
global.updateData(true)
}) })
.catch((err) => { .catch((err) => {
loading.value = false; loading.value = false;
@@ -138,6 +158,15 @@ const logout = () => {
}) })
.catch(() => { }); .catch(() => { });
}; };
onMounted(() => {
global.updateData(false)
let merchantLoginAccount = useStorage.get('merchantLoginAccount')
if (merchantLoginAccount) {
form.merchantName = merchantLoginAccount
}
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,40 +1,193 @@
<template> <template>
<div class="box"> <el-dialog v-model="visableDialog" title="余额明细" width="500">
<div class="dialog_footer" v-for="(item, index) in props.flowingwater.list" :key="index"> <div class="box">
<div class="dialog_footer_left"> <div class="box1" v-loading="tableData.loading">
<span>{{ item.biz_name }}</span> <div class="dialog_footer" v-for="(item, index) in tableData.list" :key="index">
<span>{{ dayjs(item.create_time).format("YYYY-MM-DD HH:mm:ss") }}</span> <div class="dialog_footer_left">
<span>{{ item.biz_name }}</span>
<span>{{ dayjs(item.create_time).format("YYYY-MM-DD HH:mm:ss") }}</span>
</div>
<div class="dialog_footer_right">
<span :class="{ active: item.type == '+' }">
<template v-if="item.type == '+'">+</template>
<template v-else>-</template>
{{ formatDecimal(item.amount) }}
</span>
<span>余额{{ formatDecimal(item.balance) }}</span>
</div>
<div class="btm" style="width: 80px;">
<el-button type="primary"
v-if="item.biz_code == 'scanMemberIn' || item.biz_code == 'cashMemberIn'"
@click="showRefundHandle(item)" :disabled="item.is_return == 1">
<template v-if="item.is_return == 0">退款</template>
<template v-if="item.is_return == 1">已退</template>
</el-button>
</div>
</div>
<el-empty description="暂无数据" v-if="!tableData.list.length" />
</div> </div>
<div class="dialog_footer_right"> <div class="page_wrap">
<span :class="{ active: item.type == '+' }"> <el-pagination v-model:current-page="tableData.page" background layout="prev, pager, next, total"
<template v-if="item.type == '+'">+</template> :total="tableData.total" @current-change="memberqueryMemberAccountAjax" />
<template v-else>-</template>
{{ formatDecimal(item.amount) }}
</span>
<span>余额{{ formatDecimal(item.balance) }}</span>
</div> </div>
<el-dialog v-model="showDialog" title="会员充值退款">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100">
<el-form-item label="退款金额" prop="amount">
<el-input-number v-model="form.amount" :min="1" :max="refundItem.amount"
placeholder="请输入退款金额" />
</el-form-item>
<el-form-item label="退款说明">
<el-input v-model="form.remark" placeholder="请输入退款说明" />
</el-form-item>
</el-form>
<template #footer>
<div style="padding: 0 20px 20px;">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="loading" @click="refundHandle">确定</el-button>
</div>
</template>
</el-dialog>
<takeFoodCode ref="takeFoodCodeRef" title="退款密码" :type="2" input-type="password" placeholder="请输入退款密码"
@success="passwordSuccess" />
</div> </div>
<el-empty description="暂无数据" v-if="!props.flowingwater.list.length" /> </el-dialog>
</div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import md5 from "js-md5";
import { dayjs } from 'element-plus' import { onMounted, reactive, ref } from 'vue'
import { dayjs, ElMessage } from 'element-plus'
import { formatDecimal } from '@/utils/index' import { formatDecimal } from '@/utils/index'
import { returnFlow, memberqueryMemberAccount } from '@/api/member/index.js'
import { queryPwdInfo, staffPermission } from '@/api/user.js'
import takeFoodCode from "@/components/takeFoodCode.vue";
const props = defineProps({ const memberId = ref('')
flowingwater: {
type: Object, const visableDialog = ref(false)
default: {}
const tableData = reactive({
page: 1,
total: 0,
loading: false,
list: []
})
// 会员流水
async function memberqueryMemberAccountAjax() {
try {
tableData.loading = true
let res = await memberqueryMemberAccount({
memberId: memberId.value,
page: tableData.page,
pageSize: 10
})
tableData.loading = false
tableData.total = res.total
tableData.list = res.list
} catch (error) {
console.log(error);
} }
}
const emits = defineEmits(['refund'])
const takeFoodCodeRef = ref(null)
const showDialog = ref(false)
const form = reactive({
amount: 1,
remark: ''
})
const rules = reactive({
amount: [
{
trigger: 'blur',
required: true,
message: '请输入退款金额'
}
]
})
const loading = ref(false)
const refundItem = ref({})
const formRef = ref(null)
async function showRefundHandle(item) {
try {
await staffPermission('yun_xu_tui_kuan')
refundItem.value = item
form.amount = item.amount
showDialog.value = true
} catch (error) {
console.log(error);
}
}
// 线上充值退款
function refundHandle() {
formRef.value.validate(async valid => {
try {
if (valid) {
loading.value = true
let res = await queryPwdInfo()
loading.value = false
if (res.isMemberReturn == 1) {
takeFoodCodeRef.value.show();
} else {
passwordSuccess()
}
}
} catch (error) {
loading.value = false
console.log(error);
}
})
}
// 得到退款密码
async function passwordSuccess(e = '') {
try {
loading.value = true
const res = await returnFlow({
flowId: refundItem.value.id,
remark: form.remark,
amount: form.amount,
pwd: e ? md5(e) : '',
})
ElMessage.success('退款成功')
form.amount = 1
form.remark = ''
showDialog.value = false
loading.value = false
emits('refund')
memberqueryMemberAccountAjax()
} catch (error) {
loading.value = false
console.log(error);
}
}
function show(id) {
memberId.value = id
visableDialog.value = true
memberqueryMemberAccountAjax()
}
defineExpose({
show
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.box { .box {
height: 400px;
overflow: auto; .box1 {
height: 350px;
overflow-y: auto;
}
.page_wrap {
padding-top: 20px;
}
.dialog_footer:nth-child(1) { .dialog_footer:nth-child(1) {
margin-top: 0; margin-top: 0;
@@ -43,12 +196,12 @@ const props = defineProps({
.dialog_footer { .dialog_footer {
margin-top: 10px; margin-top: 10px;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ececec;
padding-bottom: 6px; padding-bottom: 6px;
.dialog_footer_left { .dialog_footer_left {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
@@ -67,9 +220,11 @@ const props = defineProps({
} }
.dialog_footer_right { .dialog_footer_right {
width: 150px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
padding-right: 20px;
span:nth-child(1) { span:nth-child(1) {
font-size: 16px; font-size: 16px;

View File

@@ -5,7 +5,7 @@
<div class="demo_tabs_div"> <div class="demo_tabs_div">
<el-input v-model="tableData.phone" placeholder="请输入手机号" @input="inputChange" clearable @focus=" <el-input v-model="tableData.phone" placeholder="请输入手机号" @input="inputChange" clearable @focus="
global.updateData(false)" @blur="global.updateData(true)" /> global.updateData(false)" @blur="global.updateData(true)" />
<el-button style="margin-left: 10px;" type="primary" @click="memberaddshow = true">添加</el-button> <el-button style="margin-left: 10px;" type="primary" @click="addMemberHandle">添加</el-button>
</div> </div>
</div> </div>
<el-table :data="tableData.list" style="width: 100%;margin-top: 10px;height:80%;" <el-table :data="tableData.list" style="width: 100%;margin-top: 10px;height:80%;"
@@ -48,7 +48,7 @@
<div class="orderbox_right_top_item_tow">{{ tableData.list.length != 0 ? <div class="orderbox_right_top_item_tow">{{ tableData.list.length != 0 ?
tableData.list[datarow].levelConsume : '无' }}</div> tableData.list[datarow].levelConsume : '无' }}</div>
</div> </div>
<div class="orderbox_right_top_item" @click="stored = true"> <div class="orderbox_right_top_item" @click="dialogRef.show(tableData.list[datarow].id)">
<div class="orderbox_right_top_item_one"> <div class="orderbox_right_top_item_one">
<el-icon :size="24" style="color:#00b58d ;"> <el-icon :size="24" style="color:#00b58d ;">
<Box /> <Box />
@@ -83,9 +83,9 @@
</div> </div>
<keyboard v-if="props.membershow == '1'" @consumeFees="consumeFees"></keyboard> <keyboard v-if="props.membershow == '1'" @consumeFees="consumeFees"></keyboard>
<div class="orderbox_right_button" v-if="props.membershow == '0'"> <div class="orderbox_right_button" v-if="props.membershow == '0'">
<el-button style="width: 100%;" @click="toHome">创建订单</el-button> <!-- <el-button style="width: 100%;" @click="toHome">创建订单</el-button> -->
<!-- <el-button style="width: 60%;" type="primary" @click="recharge = true">账户充值</el-button> --> <!-- <el-button style="width: 60%;" type="primary" @click="recharge = true">账户充值</el-button> -->
<el-button style="width: 60%;" type="primary" @click="userChargeRef.show()">账户充值</el-button> <el-button style="width: 100%;" type="primary" @click="menberAddNnum">账户充值</el-button>
</div> </div>
<div class="orderbox_right_button" v-if="props.membershow == '1'"> <div class="orderbox_right_button" v-if="props.membershow == '1'">
<router-link to="/" style="width: 35%;"> <router-link to="/" style="width: 35%;">
@@ -94,10 +94,7 @@
<el-button style="width: 60%;" type="primary">确认</el-button> <el-button style="width: 60%;" type="primary">确认</el-button>
</div> </div>
</div> </div>
<add ref="dialogRef" @refund="refundSuccess" @page-change="MemberAccount" />
<el-dialog v-model="stored" title="余额明细" width="500" :before-close="handleClose">
<add :flowingwater='flowingwater' />
</el-dialog>
<el-dialog v-model="memberaddshow" title="添加会员" width="600" :before-close="memberaddshowclose" <el-dialog v-model="memberaddshow" title="添加会员" width="600" :before-close="memberaddshowclose"
@open="membrform = { ...resetMembrform }"> @open="membrform = { ...resetMembrform }">
<el-form ref="formRef" :rules="rules" :model="membrform" label-width="70px" hide-required-asterisk> <el-form ref="formRef" :rules="rules" :model="membrform" label-width="70px" hide-required-asterisk>
@@ -179,6 +176,7 @@ import payCard from '@/components/payCard/payCard.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import useStorage from '@/utils/useStorage' import useStorage from '@/utils/useStorage'
import userCharge from './components/userCharge.vue' import userCharge from './components/userCharge.vue'
import { staffPermission } from '@/api/user.js'
import { useGlobal } from '@/store/global.js' import { useGlobal } from '@/store/global.js'
const global = useGlobal() const global = useGlobal()
@@ -194,6 +192,9 @@ const stored = ref(false)//储值余额
const handleClose = async () => { const handleClose = async () => {
stored.value = !stored.value stored.value = !stored.value
} }
const dialogRef = ref(null)
const emit = defineEmits('paySuccess') const emit = defineEmits('paySuccess')
function paySuccess() { function paySuccess() {
@@ -238,6 +239,32 @@ const payCarddialogVisible = ref(false)
const orderId = ref('') const orderId = ref('')
// 添加会员
async function addMemberHandle() {
try {
await staffPermission('yun_xu_guan_li_hui_yuan_xin_xi')
memberaddshow.value = true
} catch (error) {
console.log(error);
}
}
// 账户充值
async function menberAddNnum() {
try {
await staffPermission('yun_xu_xiu_gai_hui_yuan_yu_e')
userChargeRef.value.show()
} catch (error) {
console.log(error);
}
}
// 退款成功
function refundSuccess() {
asyncqueryMembermember()
MemberAccount()
}
const confirmEvent = async () => {//子组件 确认按钮 const confirmEvent = async () => {//子组件 确认按钮
orderId.value = tableData.list[datarow.value].id orderId.value = tableData.list[datarow.value].id
payCarddialogVisible.value = true payCarddialogVisible.value = true
@@ -262,15 +289,15 @@ const confirmEvent = async () => {//子组件 确认按钮
// } // }
} }
const MemberAccount = async () => {//获取流水 const MemberAccount = async (page = 1) => {//获取流水
try { try {
let res = await memberqueryMemberAccount({ let res = await memberqueryMemberAccount({
memberId: tableData.list[datarow.value].id, memberId: tableData.list[datarow.value].id,
page: 1, page: page,
pageSize: 10 pageSize: 10
}) })
flowingwater.total = res.total flowingwater.total = res.total
flowingwater.list = res.list // flowingwater.list = res.list
} catch (error) { } catch (error) {
} }
@@ -302,7 +329,8 @@ const asyncqueryMembermember = async () => {//会员列表数据
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
page: tableData.page, page: tableData.page,
pageSize: 10, pageSize: 10,
phone: tableData.phone phone: tableData.phone,
isFlag: 0
}) })
if (res) { if (res) {
setTimeout(() => { setTimeout(() => {
@@ -418,8 +446,10 @@ const createMembermemberSubmit = async () => { ///添加会员
} }
const moneys = ref('')// 钱数 const moneys = ref('')// 钱数
// 创建会员订单
const toHome = () => { const toHome = () => {
useStorage.set('memberInfo', tableData.list[datarow.value]) // useStorage.set('memberInfo', tableData.list[datarow.value])
global.setOrderMember(tableData.list[datarow.value])
router.push({ router.push({
name: 'home' name: 'home'
}) })

View File

@@ -7,12 +7,12 @@
<div class="demo_tabs_boxitem_one"> <div class="demo_tabs_boxitem_one">
<div class="" <div class=""
style="width: 100px; height: 70px;border-radius: 4px; background:rgb(186 200 239); display: flex; justify-content: center; align-items: center;"> style="width: 100px; height: 70px;border-radius: 4px; background:rgb(186 200 239); display: flex; justify-content: center; align-items: center;">
<div>{{ item.tableName || "pos" }}</div> <div>{{ item.tableName || "POS" }}</div>
</div> </div>
<!-- <el-image style="width: 100px; height: 70px;border-radius: 10px;" :src="item.imgUrl" fit="scale-down" /> --> <!-- <el-image style="width: 100px; height: 70px;border-radius: 10px;" :src="item.imgUrl" fit="scale-down" /> -->
<div class="demo_tabs_boxitem_oneone"> <div class="demo_tabs_boxitem_oneone">
<div> <div>
{{ dayjs(item.createAt).format(" HH:mm:ss") }} {{ dayjs(item.createAt).format("HH:mm:ss") }}
</div> </div>
<div style="color: #757575;"> <div style="color: #757575;">
{{ dayjs(item.createAt).format("YYYY-MM-DD") }} {{ dayjs(item.createAt).format("YYYY-MM-DD") }}
@@ -20,15 +20,25 @@
</div> </div>
<div class="demo_tabs_boxitem_onetow"> <div class="demo_tabs_boxitem_onetow">
<div style="font-size: 16px; color: #757575;">{{ item.orderNo }}</div> <div style="font-size: 16px; color: #757575;">{{ item.orderNo }}</div>
<div> <div class="order_info">
<span>{{ item.names && item.names[0] }} </span> <span class="sp1">{{
item.names &&
item.names[0] }} </span>
<span style="margin-left: 6px;">{{ item.names && item.names.length }}</span> <span style="margin-left: 6px;">{{ item.names && item.names.length }}</span>
</div> </div>
</div> </div>
</div> </div>
<div class="demo_tabs_boxitem_tow"> <div class="demo_tabs_boxitem_tow">
<div> <div>
{{ item.orderAmount }} <template v-if="item.discountAmount <= 0">
{{ formatDecimal(item.orderAmount) }}
</template>
<template v-else>
<span>{{ formatDecimal(item.orderAmount - item.discountAmount) }}</span>
<span style="text-decoration: line-through;color: #999;font-size: 14px;">
{{ formatDecimal(item.orderAmount) }}
</span>
</template>
</div> </div>
<div style="color:#ff9e01;"> <div style="color:#ff9e01;">
<span v-if="item.status == 'pending'">挂单</span> <span v-if="item.status == 'pending'">挂单</span>
@@ -44,6 +54,7 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { dayjs } from 'element-plus' import { dayjs } from 'element-plus'
import { formatDecimal } from '@/utils/index.js'
const props = defineProps({ const props = defineProps({
ordereData: { ordereData: {
@@ -115,6 +126,19 @@ const clickitemboxshow = (e) => {
justify-content: space-around; justify-content: space-around;
flex: 1; flex: 1;
margin-left: 20px; margin-left: 20px;
.order_info {
display: flex;
align-items: center;
.sp1 {
display: inline-block;
width: 100px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
} }
} }
} }

View File

@@ -0,0 +1,134 @@
<template>
<el-dialog title="选择开票人" width="60%" v-model="dialogVisible">
<el-table :data="tableData.list" border>
<el-table-column label="开票人" prop="name"></el-table-column>
<el-table-column label="操作" width="140">
<template v-slot="scope">
<el-button type="primary" icon="" :loading="scope.row.loading"
@click="selectHandle(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog title="扫描二维码开票" width="330px" v-model="showEwmDialog">
<canvas class="ewm" ref="canvasRef"></canvas>
<div class="footer">
<el-button type="primary" style="width: 100%" :loading="printEwmLoading"
@click="printEwmHandle">打印二维码</el-button>
</div>
</el-dialog>
</template>
<script setup>
import QRCode from 'qrcode'
import { ElMessage } from 'element-plus'
import { reactive, ref } from 'vue'
import { issuedby, carsubinvoicing, syjprintqrcode } from '@/api/invoice.js'
import { useUser } from "@/store/user.js";
import { usePrint } from "@/store/print.js";
const printStore = usePrint();
const store = useUser();
const emits = defineEmits(['loadComplete', 'success'])
const dialogVisible = ref(false)
const showEwmDialog = ref(false)
const canvasRef = ref(null)
const ewmInfo = ref({})
const printEwmLoading = ref(false)
const tableData = reactive({
list: []
})
const orderInfo = ref({})
// 打印二维码
async function printEwmHandle() {
try {
printEwmLoading.value = true
if (printStore.deviceNoteList.length) {
printStore.printInvoice({
url: ewmInfo.value.wechat_url
})
ElMessage.success('打印成功')
showEwmDialog.value = false
setTimeout(() => {
printEwmLoading.value = false
}, 1000)
} else {
const res = await syjprintqrcode({
id: ewmInfo.value.id
})
ElMessage.success('打印成功')
showEwmDialog.value = false
setTimeout(() => {
printEwmLoading.value = false
}, 1000)
}
} catch (error) {
console.log(error);
printEwmLoading.value = false
}
}
// 选择开票
async function selectHandle(row) {
try {
row.loading = true
const res = await carsubinvoicing({
store_id: store.userInfo.loginName,
orderNo: orderInfo.value.id,
dlzh: row.id
})
console.log(res);
row.loading = false
ewmInfo.value = res
dialogVisible.value = false
showEwmDialog.value = true
setTimeout(() => {
QRCode.toCanvas(canvasRef.value, ewmInfo.value.wechat_url, function (error) {
if (error) console.error(error)
// console.log('success!');
})
}, 10)
} catch (error) {
row.loading = false
console.log(error);
}
}
// 获取开票人列表
async function getTableData() {
try {
const res = await issuedby({
store_id: store.userInfo.loginName
})
emits('loadComplete', false)
dialogVisible.value = true
tableData.list = res.map(item => {
item.loading = false
return item
})
} catch (error) {
console.log(error);
emits('loadComplete', false)
}
}
function show(order) {
console.log(order);
orderInfo.value = order
getTableData()
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.ewm {
width: 300px !important;
height: 300px !important;
}
</style>

View File

@@ -15,8 +15,10 @@
<div v-else style="width: 100%; text-align: center; margin: 30px 0"> <div v-else style="width: 100%; text-align: center; margin: 30px 0">
暂无数据 暂无数据
</div> </div>
<el-pagination background v-if="ordereData.list.length" layout="prev, pager, next" <el-pagination background v-if="ordereData.list.length" v-model:current-page="ordereData.page"
style="margin-top: 20px" :total="Number(ordereData.total)" @current-change="handleCurrentChange" /> v-model:page-size="ordereData.size" :page-sizes="[10, 30, 50, 100]" layout="prev, pager, next"
style="margin-top: 20px" :total="Number(ordereData.total)" @size-change="handleCurrentChange"
@current-change="handleCurrentChange" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="销售" name="closed"> <el-tab-pane label="销售" name="closed">
<add :loading="ordereData.loading" :ordereData="ordereData" @emititemboxshow="emititemboxshow" <add :loading="ordereData.loading" :ordereData="ordereData" @emititemboxshow="emititemboxshow"
@@ -25,8 +27,10 @@
<div v-else style="width: 100%; text-align: center; margin: 30px 0"> <div v-else style="width: 100%; text-align: center; margin: 30px 0">
暂无数据 暂无数据
</div> </div>
<el-pagination v-if="ordereData.list.length" background layout="prev, pager, next" <el-pagination background v-if="ordereData.list.length" v-model:current-page="ordereData.page"
style="margin-top: 20px" :total="Number(ordereData.total)" @current-change="handleCurrentChange" /> v-model:page-size="ordereData.size" :page-sizes="[10, 30, 50, 100]" layout="prev, pager, next"
style="margin-top: 20px" :total="Number(ordereData.total)" @size-change="handleCurrentChange"
@current-change="handleCurrentChange" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="退单" name="refund"> <el-tab-pane label="退单" name="refund">
<add :loading="ordereData.loading" :ordereData="ordereData" @emititemboxshow="emititemboxshow" <add :loading="ordereData.loading" :ordereData="ordereData" @emititemboxshow="emititemboxshow"
@@ -35,8 +39,10 @@
<div v-else style="width: 100%; text-align: center; margin: 30px 0"> <div v-else style="width: 100%; text-align: center; margin: 30px 0">
暂无数据 暂无数据
</div> </div>
<el-pagination v-if="ordereData.list.length" background layout="prev, pager, next" <el-pagination background v-if="ordereData.list.length" v-model:current-page="ordereData.page"
style="margin-top: 20px" :total="Number(ordereData.total)" @current-change="handleCurrentChange" /> v-model:page-size="ordereData.size" :page-sizes="[10, 30, 50, 100]" layout="prev, pager, next"
style="margin-top: 20px" :total="Number(ordereData.total)" @size-change="handleCurrentChange"
@current-change="handleCurrentChange" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="快捷收银" name="cash"> <el-tab-pane label="快捷收银" name="cash">
<cashTable /> <cashTable />
@@ -74,9 +80,23 @@
<div class="orderbox_right_item"> <div class="orderbox_right_item">
<span class="span">流水号</span><span class="nunber">{{ orderDetaildata.masterId }}</span> <span class="span">流水号</span><span class="nunber">{{ orderDetaildata.masterId }}</span>
</div> </div>
<div class="orderbox_right_item" v-if="orderDetaildata.discountAmount > 0">
<span class="span">折扣</span><span class="nunber">{{ formatDecimal(orderDetaildata.discountRatio *
10, 1, true) }}</span>
</div>
<div class="orderbox_right_top" style="margin-top: 20px; border-bottom: 2px solid #ccc"> <div class="orderbox_right_top" style="margin-top: 20px; border-bottom: 2px solid #ccc">
<span>合计</span> <span>合计</span>
<span>{{ orderDetaildata.orderAmount }}</span> <div>
<template v-if="orderDetaildata.discountAmount <= 0">
{{ formatDecimal(orderDetaildata.orderAmount) }}
</template>
<template v-else>
<span>{{ formatDecimal(orderDetaildata.orderAmount - orderDetaildata.discountAmount) }}</span>
<span style="text-decoration: line-through;color: #999;font-size: 14px;">
{{ formatDecimal(orderDetaildata.orderAmount) }}
</span>
</template>
</div>
</div> </div>
<div class="orderbox_right_top" style="margin-top: 20px; border-bottom: 2px solid #ccc"> <div class="orderbox_right_top" style="margin-top: 20px; border-bottom: 2px solid #ccc">
<span style="font-size: 16px" v-if="orderDetaildata.status == 'pending'">挂单</span> <span style="font-size: 16px" v-if="orderDetaildata.status == 'pending'">挂单</span>
@@ -102,20 +122,35 @@
</div> </div>
<div class="orderbox_right_list_item" style="margin-top: 20px" <div class="orderbox_right_list_item" style="margin-top: 20px"
v-for="(item, index) in orderDetaildata.detailList" :key="index"> v-for="(item, index) in orderDetaildata.detailList" :key="index">
<div>{{ item.productName }} {{ item.productSkuName }}</div> <div class="orderbox_right_list_item_row" style="display:flex;">
<div style="text-align: center">{{ item.num }}</div> <div>{{ item.productName }} {{ item.productSkuName }}</div>
<div style="text-align: center">{{ item.price }}</div> <div style="text-align: center">{{ item.num }}</div>
<div v-if="item.status == 'refund'"> <div style="text-align: center">{{ item.price }}</div>
<span style="border: 2px solid red; color: red; padding: 4px 2px">已退</span> <div v-if="item.status == 'refund'">
<span style="border: 2px solid red; color: red; padding: 4px 2px">已退</span>
</div>
<div v-else>{{ item.priceAmount }}</div>
</div>
<div class="pro" v-if="item.proGroupInfo" v-for="(val, idx) in JSON.parse(item.proGroupInfo)" :key="idx">
<div>
<span>>{{ val.proName }}</span>
<span v-if="val.skuName">规格:{{ val.skuName }}</span>
</div>
<div>{{ val.number }}</div>
<div>0</div>
<div>0</div>
</div> </div>
<div v-else>{{ item.priceAmount }}</div>
</div> </div>
<div :style="{ height: reforderboxrightbuttonheight + 'px' }"></div> <div :style="{ height: reforderboxrightbuttonheight + 'px' }"></div>
</div> </div>
<div class="orderbox_right_button" ref="reforderboxrightbutton"> <div class="orderbox_right_button" ref="reforderboxrightbutton">
<div class="orderbox_right_buttonbutton"> <div class="orderbox_right_buttonbutton">
<el-button style="width: 100%" type="warning" :loading="callLoading" @click="callNumberHandle"> <el-button style="width: 100%" type="warning" :loading="callLoading" @click="callNumberHandle" v-if="
orderDetaildata.orderType != 'return' &&
(orderDetaildata.status == 'refund' ||
orderDetaildata.status == 'closed')
">
叫号 叫号
</el-button> </el-button>
</div> </div>
@@ -124,11 +159,27 @@
orderDetaildata.orderType != 'return' && orderDetaildata.orderType != 'return' &&
(orderDetaildata.status == 'refund' || (orderDetaildata.status == 'refund' ||
orderDetaildata.status == 'closed') orderDetaildata.status == 'closed')
" type="primary" @click="recharge = true">退单</el-button> " type="primary" @click="returnOrderHandle">退单</el-button>
</div> </div>
<div class="orderbox_right_buttonbutton"> <div class="orderbox_right_buttonbutton">
<el-button @click="print('normal')" style="flex: 1">重打收银打票</el-button> <el-button style="flex: 1" :loading="invoiceLoading" @click="invoiveHandle" v-if="
<el-button @click="print('label')" style="flex: 1">重打标签小票</el-button> orderDetaildata.orderType != 'return' &&
(orderDetaildata.status == 'refund' ||
orderDetaildata.status == 'closed')
">开发票</el-button>
<el-button :loading="normalPrintLoading" @click="print('normal')" style="flex: 1" v-if="
orderDetaildata.orderType != 'return' &&
orderDetaildata.status == 'closed'
">重打小票</el-button>
<el-button :loading="labelPrintLoading" @click="print('label')" style="flex: 1" v-if="
orderDetaildata.orderType != 'return' &&
(orderDetaildata.status == 'refund' ||
orderDetaildata.status == 'closed')
">重打标签</el-button>
<el-button :loading="normalPrintLoading" @click="print('refund')" style="flex: 1"
v-if="orderDetaildata.status == 'refund'">
重打小票
</el-button>
</div> </div>
</div> </div>
</div> </div>
@@ -251,9 +302,12 @@
¥{{ refundamount > 0 ? refundamount : "0.00" }} ¥{{ refundamount > 0 ? refundamount : "0.00" }}
</div> </div>
</div> </div>
<div class="recharge_footer_itemright_botton_boxtow" style="margin-right: 10px;">
<el-button style="width: 100%; height: 100%" @click="payreturnOrderclick(false)">手动退款</el-button>
</div>
<div class="recharge_footer_itemright_botton_boxtow"> <div class="recharge_footer_itemright_botton_boxtow">
<el-button type="primary" style="width: 100%; height: 100%" :loading="buttonloading" <el-button type="primary" style="width: 100%; height: 100%" :loading="buttonloading"
@click="payreturnOrderclick"> @click="payreturnOrderclick(true)">
<span v-if="!buttonloading">支付退回</span> <span v-if="!buttonloading">支付退回</span>
<span v-else>支付退回...</span> <span v-else>支付退回...</span>
</el-button> </el-button>
@@ -263,11 +317,14 @@
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
<takeFoodCode ref="takeFoodCodeRef" title="支付密码" type="password" placeholder="请输入支付密码" @success="passwordSuccess" /> <takeFoodCode ref="takeFoodCodeRef" title="支付密码" type="2" inputType="password" placeholder="请输入支付密码"
@success="passwordSuccess" />
<invoice ref="invoiceRef" @load-complete="invoiceLoading = false" />
</template> </template>
<script setup> <script setup>
import { ref, onMounted, reactive } from "vue"; import { ref, onMounted, reactive } from "vue";
import { issuedby } from '@/api/invoice.js'
import { ElMessage, dayjs } from "element-plus"; import { ElMessage, dayjs } from "element-plus";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import lodash from "lodash"; import lodash from "lodash";
@@ -278,17 +335,23 @@ import {
cloudPrinterprint, cloudPrinterprint,
sendMessage, sendMessage,
} from "@/api/order/index.js"; } from "@/api/order/index.js";
import { queryPwdInfo, staffPermission } from '@/api/user.js'
import add from "@/views/order/components/add.vue"; import add from "@/views/order/components/add.vue";
import cashTable from "@/views/order/components/cashTable.vue"; import cashTable from "@/views/order/components/cashTable.vue";
import { clearNoNum } from "@/utils"; import { clearNoNum, formatDecimal } from "@/utils";
import md5 from "js-md5"; import md5 from "js-md5";
import dateRange from './components/dateRange.vue' import dateRange from './components/dateRange.vue'
import { useGlobal } from "@/store/global.js"; import { useGlobal } from "@/store/global.js";
import takeFoodCode from "@/components/takeFoodCode.vue"; import takeFoodCode from "@/components/takeFoodCode.vue";
import invoice from './components/invoice.vue'
const invoiceLoading = ref(false)
const invoiceRef = ref(null)
const takeFoodCodeRef = ref(null); const takeFoodCodeRef = ref(null);
const labelPrintLoading = ref(false)
const normalPrintLoading = ref(false)
const global = useGlobal(); const global = useGlobal();
const store = useUser(); const store = useUser();
@@ -318,13 +381,33 @@ const handlerecharge = () => {
const buttonloading = ref(); //loading const buttonloading = ref(); //loading
// 显示开票人列表
function invoiveHandle() {
invoiceLoading.value = true
invoiceRef.value.show(printLabelOrder.value)
}
// 确认选择时间 // 确认选择时间
function dateConfirm(time) { function dateConfirm(time) {
ordereData.startTime = time[0] ordereData.startTime = time[0]
ordereData.endTime = time[1] ordereData.endTime = time[1]
ordereData.page = 1
asyncorderfindOrder(); asyncorderfindOrder();
} }
// 显示退单
async function returnOrderHandle() {
try {
await staffPermission('yun_xu_tui_kuan')
recharge.value = true
} catch (error) {
console.log(error);
}
}
// 是否线上退款
const isOnline = ref(true)
// 获取支付密码 // 获取支付密码
async function passwordSuccess(pwd) { async function passwordSuccess(pwd) {
try { try {
@@ -344,18 +427,18 @@ async function passwordSuccess(pwd) {
return item && item; return item && item;
}); });
if (arr.length != 0) { if (arr.length != 0) {
// await payreturnOrder(arr, md5(pwd)); // await payreturnOrder(arr, md5(pwd), isOnline.value);
await payreturnOrder(arr, ''); await payreturnOrder(arr, pwd ? md5(pwd) : '', isOnline.value);
changechecked.value = false; changechecked.value = false;
recharge.value = false; recharge.value = false;
itemboxshow.value = false; itemboxshow.value = false;
refundamount.value = 0; refundamount.value = 0;
ElMessage.success("退款成功!"); ElMessage.success("退款成功!");
buttonloading.value = false; buttonloading.value = false;
asyncorderfindOrder(); await asyncorderfindOrder();
} else { } else {
buttonloading.value = false; buttonloading.value = false;
ElMessage.error("没有退款项目!"); ElMessage.error("没有退款项目!");
} }
} catch (error) { } catch (error) {
buttonloading.value = false; buttonloading.value = false;
@@ -363,15 +446,22 @@ async function passwordSuccess(pwd) {
} }
const payreturnOrderclick = lodash.debounce( const payreturnOrderclick = lodash.debounce(
async () => { async (e) => {
console.log(e);
isOnline.value = e
//搜索手机号 //搜索手机号
if (refundamount.value == 0) { if (refundamount.value == 0) {
buttonloading.value = false; buttonloading.value = false;
ElMessage.error("退款金额不能为0"); ElMessage.error("退款金额不能为0");
return false; return false;
} }
// takeFoodCodeRef.value.show();
passwordSuccess() let res = await queryPwdInfo()
if (res.isReturn == 1) {
takeFoodCodeRef.value.show();
} else {
passwordSuccess()
}
}, },
500, 500,
{ leading: true, trailing: false } { leading: true, trailing: false }
@@ -383,8 +473,10 @@ const print = lodash.throttle(
try { try {
if (e == "label") { if (e == "label") {
// checkLabelPrint(printLabelOrder.value) // checkLabelPrint(printLabelOrder.value)
labelPrintLoading.value = true
const data = { const data = {
shop_name: store.userInfo.merchantName, shop_name: store.userInfo.shopName,
loginAccount: store.userInfo.loginAccount,
carts: [], carts: [],
orderInfo: printLabelOrder.value, orderInfo: printLabelOrder.value,
outNumber: printLabelOrder.value.outNumber, outNumber: printLabelOrder.value.outNumber,
@@ -403,16 +495,103 @@ const print = lodash.throttle(
// console.log('重打标签小票', data); // console.log('重打标签小票', data);
printStore.labelPrint(data); printStore.labelPrint(data);
} else { setTimeout(() => {
await cloudPrinterprint({ labelPrintLoading.value = false
type: e, }, 1000)
orderId: orderDetaildata.value.id, } else if (e == 'normal') {
ispre: false, if (printStore.deviceNoteList.length) {
}); normalPrintLoading.value = true
ElMessage({ const data = {
message: "成功打票", shop_name: store.userInfo.shopName,
type: "success", loginAccount: store.userInfo.loginAccount,
}); carts: [],
amount: formatDecimal(printLabelOrder.value.orderAmount),
discountAmount: printLabelOrder.value.discountAmount > 0 ? formatDecimal(printLabelOrder.value.orderAmount - printLabelOrder.value.discountAmount) : formatDecimal(printLabelOrder.value.orderAmount),
discount: printLabelOrder.value.discountRatio ? formatDecimal(printLabelOrder.value.discountRatio * 10, 1, true) : 0,
remark: printLabelOrder.value.remark,
orderInfo: printLabelOrder.value,
outNumber: printLabelOrder.value.outNumber,
createdAt: dayjs(printLabelOrder.value.createdAt).format(
"YYYY-MM-DD HH:mm:ss"
),
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
}
printLabelOrder.value.skuInfos.map(item => {
data.carts.push(
{
categoryId: item.categoryId,
name: item.productName,
number: item.num,
skuName: item.productSkuName,
salePrice: formatDecimal(item.price),
totalAmount: formatDecimal(item.num * item.price),
proGroupInfo: item.proGroupInfo
}
)
})
printStore.pushReceiptData(data);
setTimeout(() => {
normalPrintLoading.value = false
}, 1000)
} else {
// 云打票
await cloudPrinterprint({
type: e,
orderId: orderDetaildata.value.id,
ispre: false,
});
ElMessage({
message: "成功打票",
type: "success",
});
}
} else if (e == 'refund') {
if (printStore.deviceNoteList.length) {
console.log('本地打退票');
normalPrintLoading.value = true
const data = {
shop_name: store.userInfo.shopName,
loginAccount: store.userInfo.loginAccount,
carts: [],
amount: printLabelOrder.value.orderAmount,
remark: printLabelOrder.value.remark,
orderInfo: printLabelOrder.value,
outNumber: printLabelOrder.value.outNumber,
createdAt: dayjs(printLabelOrder.value.createdAt).format(
"YYYY-MM-DD HH:mm:ss"
),
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
}
printLabelOrder.value.skuInfos.map(item => {
data.carts.push(
{
categoryId: item.categoryId,
name: item.productName,
number: item.num,
skuName: item.productSkuName,
salePrice: formatDecimal(item.priceAmount / item.num),
totalAmount: formatDecimal(item.priceAmount)
}
)
})
printStore.printRefund(data);
setTimeout(() => {
normalPrintLoading.value = false
}, 1000)
} else {
console.log('云打退票');
// 云打票
await cloudPrinterprint({
type: 'normal',
orderId: orderDetaildata.value.id,
ispre: false,
});
ElMessage({
message: "成功打票",
type: "success",
});
}
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -428,7 +607,7 @@ const printLabelOrder = ref("");
const emititemboxshow = async (e) => { const emititemboxshow = async (e) => {
//接收子组件值 并赋值给父组件 //接收子组件值 并赋值给父组件
// console.log('emititemboxshow', e); console.log('emititemboxshow', e);
printLabelOrder.value = e; printLabelOrder.value = e;
loadingboxshow.value = true; loadingboxshow.value = true;
@@ -866,19 +1045,13 @@ onMounted(() => {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
.recharge_footer_itemright_botton_boxoneabsolute { .recharge_footer_itemright_botton_boxoneabsolute {
position: absolute;
top: 0;
left: 0;
color: #c0c0c0; color: #c0c0c0;
} }
.recharge_footer_itemright_botton_boxonetext { .recharge_footer_itemright_botton_boxonetext {
font-size: 26px; font-size: 26px;
height: 60px;
line-height: 60px;
} }
} }
@@ -937,37 +1110,73 @@ onMounted(() => {
} }
.orderbox_right_list_item { .orderbox_right_list_item {
display: flex; .pro {
font-size: 14px; display: flex;
justify-content: space-between; font-size: 14px;
align-items: center; justify-content: space-between;
padding-bottom: 10px;
div:nth-child(1) {
text-align: left;
width: 45%;
}
div:nth-child(2) {
width: 15%;
align-items: center; align-items: center;
padding-bottom: 10px;
div:nth-child(1) {
text-align: left;
width: 45%;
display: flex;
flex-direction: column;
}
div:nth-child(2) {
width: 15%;
display: flex;
justify-content: center;
}
div:nth-child(3) {
width: 15%;
display: flex;
justify-content: center;
}
div:nth-child(4) {
width: 25%;
display: flex;
justify-content: flex-end;
}
} }
div:nth-child(3) { .orderbox_right_list_item_row {
width: 15%; display: flex;
font-size: 14px;
justify-content: space-between;
align-items: center; align-items: center;
} padding-bottom: 10px;
div:nth-child(4) { div:nth-child(1) {
text-align: right; text-align: left;
width: 25%; width: 45%;
}
div:nth-child(2) {
width: 15%;
align-items: center;
}
div:nth-child(3) {
width: 15%;
align-items: center;
}
div:nth-child(4) {
text-align: right;
width: 25%;
}
} }
} }
.tableDataclass { .tableDataclass {
width: 100%; width: 100%;
overflow: auto; overflow-y: auto;
height: 90%; height: 70%;
.orderbox_right_item { .orderbox_right_item {
margin-top: 6px; margin-top: 6px;

View File

@@ -0,0 +1,139 @@
<template>
<el-dialog v-model="showAddTable" :title="addTabForm.id ? '编辑桌型' : '新增桌型'" top="10vh" @closed="addTabFormReset">
<el-form ref="AddTabFormRef" :model="addTabForm" :rules="addTabFormRules" label-position="left"
label-width="100">
<el-form-item label="名称" prop="name">
<el-input v-model="addTabForm.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="描述" prop="note">
<el-input v-model="addTabForm.note" placeholder="请输入描述例如1-2人" />
</el-form-item>
<el-form-item label="等待时间" prop="waitTime">
<el-input v-model="addTabForm.waitTime" placeholder="0">
<template #append>分钟/1</template>
</el-input>
</el-form-item>
<el-form-item label="号码前缀" prop="prefix">
<el-input v-model="addTabForm.prefix" placeholder="请输入英文字母,不支持中文" />
</el-form-item>
<el-form-item label="开始号码" prop="start">
<el-input v-model="addTabForm.start" placeholder="请输入开始号码" />
</el-form-item>
<el-form-item label="过号保留">
<el-input v-model="addTabForm.nearNum" :disabled="!addTabForm.isPostpone" placeholder="临近几桌提醒">
<template #prepend>
<el-checkbox v-model="addTabForm.isPostpone" :true-value="1" :false-value="0" label="开启顺延" />
</template>
<template #append></template>
</el-input>
</el-form-item>
</el-form>
<div class="footer" style="display: flex;">
<el-button style="width: 100%" @click="showAddTable = false">
取消
</el-button>
<el-button type="primary" style="width: 100%" :loading="addTabFormLoading" @click="addTabConfirmHandle">
确认
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { ref, onMounted } from 'vue'
import { addCallTable } from '@/api/queue.js'
import { useUser } from "@/store/user.js"
const store = useUser()
const emits = defineEmits(['success'])
const showAddTable = ref(false)
const addTabFormLoading = ref(false)
const AddTabFormRef = ref(null)
const resetAddTabForm = ref({})
const addTabForm = ref({
name: '',
note: '',
waitTime: '',
prefix: '',
start: '',
isPostpone: 0,
nearNum: ''
})
const addTabFormRules = ref({
name: [
{
required: true,
message: ' ',
trigger: 'blur',
}
],
waitTime: [
{
required: true,
message: ' ',
trigger: 'blur',
}
],
prefix: [
{
required: true,
message: ' ',
trigger: 'blur',
}
],
start: [
{
required: true,
message: ' ',
trigger: 'blur',
}
],
})
// 初始化
function addTabFormReset() {
addTabForm.value = { ...resetAddTabForm.value }
AddTabFormRef.value.resetFields()
}
// 提交
function addTabConfirmHandle() {
AddTabFormRef.value.validate(async valid => {
try {
if (valid) {
addTabFormLoading.value = true
addTabForm.value.shopId = store.userInfo.shopId
if (addTabForm.value.id) {
addTabForm.value.callTableId = addTabForm.value.id
}
const res = await addCallTable(addTabForm.value)
addTabFormLoading.value = false
showAddTable.value = false
ElMessage.success(addTabForm.value.id ? '编辑成功' : '添加成功')
emits('success')
}
} catch (error) {
addTabFormLoading.value = false
console.log(error);
}
})
}
function show(obj) {
if (obj && obj.id) {
addTabForm.value = { ...obj }
}
showAddTable.value = true
}
defineExpose({
show
})
onMounted(() => {
resetAddTabForm.value = { ...addTabForm.value }
})
</script>

View File

@@ -0,0 +1,108 @@
<!-- 取号 -->
<template>
<el-dialog title="取号" v-model="visible" @closed="onClose">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
<el-form-item label="选择桌型">
<el-radio-group v-model="form.callTableId">
<el-radio :value="item.id" border v-for="item in list" :key="item.id">{{ item.name
}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" placeholder="请填写手机号" style="width: 60%;" />
</el-form-item>
</el-form>
<div class="footer" style="display: flex;padding-top: 30px;">
<el-button style="width: 100%" @click="visible = false">
取消
</el-button>
<el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle">
确认
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { onMounted, ref } from 'vue';
import { takeNumber } from '@/api/queue.js'
import { useUser } from "@/store/user.js"
const store = useUser()
const emits = defineEmits(['success'])
const visible = ref(false)
const list = ref([])
const loading = ref(false)
const formRef = ref(null)
const resetForm = ref({})
const form = ref({
callTableId: '',
shopId: '',
phone: '',
note: '',
name: ''
})
const rules = ref({
phone: [
{
required: true,
validator: (rule, value, callback) => {
let reg = /^(?:(?:\+|00)86)?1\d{10}$/
if (!reg.test(form.value.phone)) {
callback(new Error('手机号码不正确'))
} else {
callback()
}
},
trigger: 'blur',
}
]
})
// 提交
function confirmHandle() {
formRef.value.validate(async vaild => {
try {
if (vaild) {
loading.value = true
form.value.shopId = store.userInfo.shopId
form.value.note = list.value.find(item => item.id == form.value.callTableId).note
form.value.name = list.value.find(item => item.id == form.value.callTableId).name
await takeNumber(form.value)
loading.value = false
ElMessage.success('取号成功')
emits('success')
visible.value = false
}
} catch (error) {
loading.value = false
console.log(error);
}
})
}
// 初始化
function onClose() {
form.value = { ...resetForm.value }
formRef.value.resetFields()
}
function show(arr) {
visible.value = true
list.value = [...arr]
form.value.callTableId = list.value[0].id
}
defineExpose({
show
})
onMounted(() => {
resetForm.value = { ...form }
})
</script>

View File

@@ -0,0 +1,99 @@
<!-- 叫号记录 -->
<template>
<el-dialog v-model="dialogVisible" title="叫号记录" @closed="reset" width="80vw">
<el-table :data="tableData.list" border style="height: 84%;" height="50vh">
<el-table-column label="桌号" prop="name"></el-table-column>
<el-table-column label="桌型" prop="note"></el-table-column>
<el-table-column label="手机号" prop="phone"></el-table-column>
<el-table-column label="状态" prop="state">
<template v-slot="scope">
{{ statusList[scope.row.state].text }}
</template>
</el-table-column>
<el-table-column label="时间" prop="callTime" width="200">
<template v-slot="scope">{{ dayjs(scope.row.callTime).format('YYYY-MM-DD HH:mm:ss') }}</template>
</el-table-column>
</el-table>
<div class="pagination" style="padding-top:15px;display: flex;justify-content: flex-end;">
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.size"
layout="total, prev, pager, next" :total="tableData.total" background @current-change="paginationChange"
@size-change="paginationChange">
</el-pagination>
</div>
</el-dialog>
</template>
<script setup>
import { dayjs } from 'element-plus'
import { onMounted, reactive, ref } from 'vue';
import { callRecord } from '@/api/queue.js'
import { useUser } from "@/store/user.js"
const store = useUser()
const dialogVisible = ref(false)
const tableData = reactive({
loading: false,
list: [],
page: 1,
size: 20,
total: 0
})
const statusList = {
'-1': {
type: 'warning',
text: '已取消'
},
0: {
type: 'danger',
text: '排队中'
},
1: {
type: 'success',
text: '叫号中'
},
2: {
type: 'success',
text: '已入座'
},
3: {
type: 'success',
text: '已过号'
}
}
// 分页变化
function paginationChange(e) {
callRecordAjax()
}
// 记录数据
async function callRecordAjax() {
try {
tableData.loading = true
const res = await callRecord({
callTableId: '',
shopId: store.userInfo.shopId,
page: tableData.page,
size: tableData.size
})
tableData.loading = false
tableData.list = res.records
tableData.total = res.total
} catch (error) {
console.log(error);
}
}
function reset() {
tableData.page = 1
}
function show() {
dialogVisible.value = true
callRecordAjax()
}
defineExpose({
show
})
</script>

View File

@@ -0,0 +1,75 @@
<!-- 播报结果 -->
<template>
<el-dialog title="提示" v-model="visible">
<div class="content">
<div style="font-size: 18px;">正在叫号请稍等</div>
<el-alert :title="statusList[item.status].text" :type="statusList[item.status].type" :closable="false" />
</div>
<div class="footer" style="display: flex;">
<el-button style="width: 100%" @click="confirmHandle(2)">完成</el-button>
<el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle(3)">过号</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { updateState } from '@/api/queue.js'
import { useUser } from "@/store/user.js"
const store = useUser()
const emits = defineEmits(['success'])
const visible = ref(false)
const item = ref({})
const loading = ref(false)
const statusList = {
'-1': {
type: 'warning',
text: '用户未订阅'
},
0: {
type: 'danger',
text: '失败'
},
1: {
type: 'success',
text: '成功'
}
}
// 过号修改状态
async function confirmHandle(state) {
try {
await updateState({
shopId: store.userInfo.shopId,
callQueueId: item.value.id,
state: state
})
visible.value = false
emits('success')
} catch (error) {
console.log(error);
}
}
function show(obj) {
visible.value = true
item.value = { ...obj }
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 15px;
padding-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,233 @@
<template>
<el-dialog v-model="dialogVisible" title="基本设置" width="90vw" top="5vh" @closed="emits('success')">
<div class="scroll_y">
<el-form ref="formRef" :model="form" label-position="left" label-width="140">
<el-form-item label="排队页面地址">{{ config.pageAddress }}</el-form-item>
<el-form-item label="线上取号">
<el-switch v-model="form.isOnline" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
<el-form-item label="背景图片">
<UploadImg ref="UploadImgRef" @success="e => form.bgCover = e" />
</el-form-item>
<el-form-item label="桌型">
<el-table :data="tableData.list" border v-loading="tableData.loading">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="描述" prop="note"></el-table-column>
<el-table-column label="等待时间[桌]" prop="waitTime"></el-table-column>
<el-table-column label="号码前缀" prop="prefix"></el-table-column>
<el-table-column label="开始号码" prop="start"></el-table-column>
<el-table-column label="操作">
<template #header>
<el-button type="primary" @click="AddTabRef.show()">添加</el-button>
</template>
<template v-slot="scope">
<div style="display: flex;gap: 10px;">
<el-text type="primary" @click="AddTabRef.show(scope.row)">编辑</el-text>
<el-text type="danger" @click="delCallTableHandle(scope.row)">删除</el-text>
</div>
</template>
</el-table-column>
</el-table>
</el-form-item>
<div class="title" style="padding-bottom: 20px;">通知模板</div>
<el-form-item label="排队成功提醒">
<el-input disabled style="width: 50%;" v-model="config.successMsg"></el-input>
</el-form-item>
<el-form-item label="排队即将排到通知">
<div style="display: flex;flex-direction: column;">
<el-input disabled style="width: 50%;" v-model="config.nearMsg"></el-input>
<el-input v-model="config.nearNum" disabled style="margin-top: 10px;">
<template #prepend>前面等待</template>
<template #append>桌时提醒</template>
</el-input>
</div>
</el-form-item>
<el-form-item label="排队到号提醒">
<el-input disabled style="width: 50%;" v-model="config.callingMsg"></el-input>
</el-form-item>
</el-form>
</div>
<div class="footer">
<el-button style="width: 100%" @click="dialogVisible = false">
取消
</el-button>
<el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle">
确认
</el-button>
</div>
</el-dialog>
<AddTab ref="AddTabRef" @success="getTableAjax" />
</template>
<script setup>
import UploadImg from '@/components/uploadImg.vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import AddTab from './addTab.vue'
import { onMounted, reactive, ref } from 'vue';
import { callTable, delCallTable, callTableConfig, callTableConfigPut } from '@/api/queue.js'
import { useUser } from "@/store/user.js"
const store = useUser()
const dialogVisible = ref(false)
const AddTabRef = ref(null)
const formRef = ref(null)
const loading = ref(false)
const form = ref({
isOnline: 1,
bgCover: '',
nearNum: '',
})
function beforeAvatarUpload(file) {
console.log(2, file);
}
// 桌型
const tableData = reactive({
loading: false,
list: []
})
// 显示基本配置
function show() {
dialogVisible.value = true
getTableAjax()
callTableConfigAjax()
}
// 删除桌型
function delCallTableHandle(row) {
ElMessageBox.confirm('确认要删除吗?', '注意').then(async () => {
try {
tableData.loading = true
const res = await delCallTable({
shopId: store.userInfo.shopId,
callTableId: row.id
})
getTableAjax()
} catch (error) {
console.log(error);
}
}).catch(() => { })
}
// 获取桌型列表
async function getTableAjax() {
try {
tableData.loading = true
const res = await callTable({
page: 1,
size: 100,
shopId: store.userInfo.shopId,
callTableId: '',
state: ''
})
tableData.loading = false
tableData.list = res.records
} catch (error) {
tableData.loading = false
console.log(error);
}
}
// 获取配置信息
const config = ref({})
const UploadImgRef = ref(null)
async function callTableConfigAjax() {
try {
const res = await callTableConfig({ shopId: store.userInfo.shopId })
config.value = res
form.value.nearNum = res.nearNum
if (res.bgCover) {
UploadImgRef.value.init([{ url: res.bgCover }])
}
} catch (error) {
console.log(error);
}
}
// 提交
const emits = defineEmits(['success'])
async function confirmHandle() {
try {
loading.value = true
form.value.shopId = store.userInfo.shopId
const res = await callTableConfigPut(form.value)
loading.value = false
ElMessage.success('保存成功')
dialogVisible.value = false
emits('success')
} catch (error) {
loading.value = false
console.log(error);
}
}
defineExpose({
show
})
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
text-align: center;
}
</style>
<style scoped lang="scss">
.title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.avatar-uploader .avatar {
width: 120px;
height: 120px;
display: block;
}
$btmH: 50px;
.scroll_y {
height: 65vh;
overflow-y: auto;
padding-bottom: $btmH;
}
.footer {
display: flex;
padding: 0 100px 0;
position: relative;
&::before {
content: "";
height: $btmH;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
width: 100%;
position: absolute;
top: $btmH*-1;
left: 0;
z-index: 10;
}
}
</style>

282
src/views/queue/index.vue Normal file
View File

@@ -0,0 +1,282 @@
<template>
<div class="content">
<div class="card" style="flex: 1;">
<div class="tab_head">
<el-radio-group v-model="tabActive" @change="callTableQueueAjax">
<el-radio-button label="全部" value=""></el-radio-button>
<el-radio-button :label="item.name" :value="item.id" v-for="item in tabHeader"
:key="item.id"></el-radio-button>
</el-radio-group>
<div class="btns">
<el-button type="danger" @click="GetNumberRef.show(tabHeader)">取号</el-button>
<el-button type="warning" @click="RecordRef.show()">叫号记录</el-button>
<el-button type="primary" @click="SettingRef.show()">基本设置</el-button>
</div>
</div>
<div class="queue_list" v-loading="queueList.loading">
<div class="item" v-for="item in queueList.list" :key="item.id">
<div class="head">
<div class="row">
<div class="row_item">用户</div>
<div class="row_item">号码</div>
<div class="row_item">桌型</div>
<div class="row_item">等待</div>
</div>
<div class="row">
<div class="row_item">{{ item.phone }}</div>
<div class="row_item">{{ item.callNum }}</div>
<div class="row_item">{{ item.name }}</div>
<div class="row_item">{{ item.waitingCount }}分钟</div>
</div>
</div>
<div class="btm">
<el-button @click="cancaleHandle(item)">取消</el-button>
<el-button type="primary" @click="callHandle(item)">播报</el-button>
</div>
</div>
<div class="empty">
<el-empty description="暂无数据~" v-if="!queueList.list.length" style="width: 100%;" />
</div>
</div>
<div class="pagination">
<el-pagination v-model:current-page="queueList.page" v-model:page-size="queueList.size"
layout="total, prev, pager, next" :total="queueList.total" background
@current-change="queueListChange" @size-change="queueListChange">
</el-pagination>
</div>
</div>
</div>
<!-- 基本设置 -->
<Setting ref="SettingRef" @success="callTableAjax" />
<!-- 叫号记录 -->
<Record ref="RecordRef" />
<!-- 取号 -->
<GetNumber ref="GetNumberRef" @success="callTableQueueAjax" />
<!-- 播报状态 -->
<ResultModal ref="ResultModalRef" @success="callTableQueueAjax" />
</template>
<script setup>
import Speech from 'speak-tts'
import { ElMessageBox, ElMessage } from 'element-plus'
import Setting from './components/setting.vue'
import Record from './components/record.vue'
import GetNumber from './components/getNumber.vue'
import ResultModal from './components/resultModal.vue'
import { onMounted, reactive, ref } from 'vue';
import { callRecord, callTable, callTableQueue, updateState, callTableCall } from '@/api/queue.js'
import { useUser } from "@/store/user.js"
const store = useUser()
const SettingRef = ref(null)
const RecordRef = ref(null)
const GetNumberRef = ref(null)
const ResultModalRef = ref(null)
// 获取桌型列表
const tabHeader = ref([])
const tabActive = ref('')
async function callTableAjax() {
try {
const res = await callTable({
page: 1,
size: 100,
shopId: store.userInfo.shopId,
callTableId: '',
state: ''
})
tabHeader.value = res.records
} catch (error) {
console.log(error);
}
}
// 取号 排队列表获取 start
const queueList = reactive({
loading: false,
list: [],
page: 1,
size: 8,
total: 0
})
function queueListChange() {
callTableQueueAjax()
}
// 获取数据
async function callTableQueueAjax() {
try {
queueList.loading = true
const res = await callTableQueue({
page: queueList.page,
size: queueList.size,
shopId: store.userInfo.shopId,
callTableId: tabActive.value,
state: ''
})
queueList.loading = false
queueList.list = res.records
queueList.total = res.total
} catch (error) {
console.log(error);
queueList.loading = false
}
}
// 取号 排队列表获取 end
// 取消排队
function cancaleHandle(item) {
ElMessageBox.confirm('确定要取消排队吗?', '注意').then(async () => {
try {
await updateState({
shopId: store.userInfo.shopId,
callQueueId: item.id,
state: '-1'
})
ElMessage.success('已取消')
callTableQueueAjax()
} catch (error) {
console.log(error);
}
}).catch(() => { })
}
// 播报
async function callHandle(item) {
try {
startSpeech(`${item.callNum}用餐`)
const res = await callTableCall({
shopId: store.userInfo.shopId,
callQueueId: item.id,
})
ResultModalRef.value.show({
...item,
status: res.state
})
} catch (error) {
console.log(error);
}
}
// 播放声音
const speech = new Speech()
function speechInit(params) {
speech.init({
volume: 1, // 音量
lang: 'zh-CN', // 语言
rate: 1, // 语速1正常语速2倍语速就写2
pitch: 1, // 音调
splitSentences: true, // 在句子结束时暂停
listeners: {
// 事件
onvoiceschanged: voices => {
// console.log('事件声音已更改', voices);
},
},
}).then(data => {
console.log('语音已准备好,声音可用', data);
}).catch(err => {
console.log('初始化发生错误', err);
})
}
// 开始播报
function startSpeech(text) {
speech.speak({
text: text, //这里使用文字或者i18n 都可以 看自己需求
queue: true,
listeners: {
// 开始播放
onstart: () => {
console.log('Start utterance')
},
// 判断播放是否完毕
onend: () => {
console.log('End utterance')
},
// 恢复播放
onresume: () => {
console.log('Resume utterance')
},
}
}).then(() => {
console.log('成功!');
}).catch(e => {
console.error('发生错误:', e);
});
}
onMounted(() => {
callTableAjax()
callTableQueueAjax()
if (speech.hasBrowserSupport()) {
console.log('语音播报加载成功,支持播报');
speechInit()
} else {
console.log('当前浏览器不支持语音播报');
}
})
</script>
<style lang="scss" scoped>
.card {
padding: 15px;
.pagination {
display: flex;
justify-content: flex-start;
}
}
.tab_head {
display: flex;
align-items: center;
justify-content: space-between;
}
.queue_list {
height: 80vh;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(4, 1fr);
grid-column-gap: 15px;
grid-row-gap: 15px;
padding: 15px 0;
position: relative;
.empty {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
}
.item {
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
.head {
.row {
display: flex;
margin-bottom: 10px;
.row_item {
flex: 1;
&:last-child {
flex: 0.5;
}
}
}
}
.btm {
display: flex;
justify-content: flex-end;
}
}
}
</style>

View File

@@ -15,24 +15,92 @@
</el-icon> </el-icon>
<span class="t">{{ status[props.tableInfo.status] }}</span> <span class="t">{{ status[props.tableInfo.status] }}</span>
</div> </div>
<div class="place_order"> <div class="cart" v-loading="payLoading" v-if="props.tableInfo.status == 'using'">
<router-link class="btn" :to="{ name: 'home', query: { table_code: 1 } }"> <div class="cart_list">
<div class="item" v-for="item in cartList" :key="item.id">
<div class="top">
<span class="name">{{ item.name }}</span>
<span class="n">x{{ item.number }}</span>
<span class="p">{{ item.salePrice }}</span>
</div>
<div class="tag_wrap" v-if="item.skuName">
<div class="tag" v-for="item in item.skuName.split(',')">
{{ item }}
</div>
</div>
</div>
</div>
<div class="btn_container">
<div class="btn_wrap">
<el-button type="success" style="width: 100%;" @click="toOrderMeal(1)">加菜/管理</el-button>
</div>
<div class="btn_wrap">
<el-button type="primary" style="width: 100%;" @click="showPayHandle">结算({{
orderInfo.orderAmount || 0 }})</el-button>
</div>
</div>
</div>
<div class="place_order" v-else>
<div class="btn">
<div class="top"> <div class="top">
<el-icon class="icon"> <el-icon class="icon">
<TakeawayBox /> <TakeawayBox />
</el-icon> </el-icon>
<span class="t">点单</span> <!-- <span class="t">点单</span> -->
</div> </div>
<span class="tips">开始新订单</span> <!-- <span class="tips">开始新订单</span> -->
</router-link> <div class="btn_wrap" v-if="props.tableInfo.status == 'idle'">
<el-button type="primary" style="width: 100%;" @click="showPeopleNumHandle">开始新订单</el-button>
</div>
<div class="btn_wrap" v-if="props.tableInfo.status == 'cleaning'">
<el-button type="primary" style="width: 100%;" @click="clearTableStatus">清理完成</el-button>
</div>
</div>
<transition name="el-fade-in">
<div class="people_num_wrap" v-show="showPeopleNum">
<div class="title">应选择就餐人数</div>
<div class="num_btns">
<div class="item" :class="{ active: peopleNum == item }"
v-for="item in props.tableInfo.maxCapacity" @click="peopleNum = item">{{ item }}</div>
<!-- <div class="item" :class="{ active: peopleNum == 'custom' }">
<input class="ipt" @focus="inputFocus" placeholder="自定义" v-model="peopleNumInputValue"
@change="" />
</div> -->
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;"
:disabled="(!peopleNum && !peopleNumInputValue) || (peopleNum == 'custom' && !peopleNumInputValue)"
@click="orderDownHandle">确认</el-button>
</div>
</div>
</transition>
</div> </div>
<!-- 结算订单 -->
<settleAccount ref="settleAccountRef" :cart="cartList" :amount="orderInfo.orderAmount"
:remark="orderInfo.remark" :masterId="orderInfo.masterId" :orderInfo="orderInfo" @paySuccess="paySuccess" />
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, defineEmits } from 'vue' import { ref, reactive, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useUser } from "@/store/user.js"
import { useGlobal } from '@/store/global.js'
import { queryMembermember } from '@/api/member/index.js'
import { orderDetail } from '@/api/order/index.js'
import { formatDecimal } from '@/utils/index.js'
import settleAccount from "@/views/home/components/settleAccount.vue";
import { ElMessage } from 'element-plus'
import { clearTable } from '@/api/table.js'
import { useShop } from '@/store/shop.js'
const emit = defineEmits(['close']) const shopStore = useShop()
const router = useRouter()
const global = useGlobal()
const store = useUser()
const emits = defineEmits(['close', 'success'])
const props = defineProps({ const props = defineProps({
tableInfo: { tableInfo: {
@@ -41,17 +109,145 @@ const props = defineProps({
} }
}) })
const showPeopleNum = ref(false)
const peopleNum = ref(0)
const peopleNumInputValue = ref('')
watch(props, () => {
getOrderDetail()
})
// 自定义人数输入获得焦点
function inputFocus() {
peopleNum.value = 'custom'
}
const settleAccountRef = ref(null)
const orderInfo = ref({})
const cartList = ref([])
const status = ref({ const status = ref({
'subscribe': '预定', 'subscribe': '预定',
'closed': '关台', 'closed': '关台',
'opening': '开台中', 'idle': '空闲',
'using': '开台中',
'pending': '挂单中',
'paying': '结算中',
'cleaning': '台桌清理中' 'cleaning': '台桌清理中'
}) })
const payLoading = ref(false)
// 显示结算页面
function showPayHandle() {
settleAccountRef.value.show()
}
// 显示就就餐人数
function showPeopleNumHandle() {
if (shopStore.info.isTableFee == 1) {
orderDownHandle()
} else {
showPeopleNum.value = true
}
}
// 清理桌台
const clearLoading = ref(false)
async function clearTableStatus() {
try {
clearLoading.value = true
const res = await clearTable({
shopId: store.userInfo.shopId,
tableId: props.tableInfo.qrcode
})
clearLoading.value = false
emits('success')
} catch (error) {
console.log(error);
}
}
// 获取订单详情
async function getOrderDetail() {
try {
if (props.tableInfo.orderId) {
payLoading.value = true
const res = await orderDetail({
shopId: store.userInfo.shopId,
id: props.tableInfo.orderId
})
payLoading.value = false
orderInfo.value = res
cartList.value = res.detailList.map(item => {
let obj = {
name: item.productName,
number: item.num,
salePrice: item.price,
skuName: item.productSkuName
}
return obj
})
}
} catch (error) {
payLoading.value = false
console.log(error);
}
}
// 关闭 // 关闭
function close() { function close() {
emit('close') emits('close')
} }
// 判断数字是不是正整数
function isPositiveInteger(num) {
return Number.isInteger(num) && num > 0;
}
// 选择人数后确认下单
function orderDownHandle() {
// if (peopleNum.value == 'custom') {
// if (!isPositiveInteger(parseFloat(peopleNumInputValue.value))) {
// ElMessage.error('请输入有效的就餐人数')
// return
// }
// }
// 直接点单
global.setOrderTable({
...props.tableInfo,
num: peopleNum.value
})
router.push({
name: 'home',
})
}
// 点单
function toOrderMeal(t) {
if (t == 1) {
// 直接点单
global.setOrderTable(props.tableInfo)
router.push({
name: 'home',
})
} else {
// 选择会员点单
showDialog.value = true
getMemberList()
}
}
function paySuccess() {
getOrderDetail()
emits('success')
}
onMounted(() => {
getOrderDetail()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -97,6 +293,74 @@ function close() {
} }
} }
.cart {
height: calc(100vh - 160px);
display: flex;
flex-direction: column;
.cart_list {
flex: 1;
border-radius: 6px;
background-color: #efefef;
padding: 0 var(--el-font-size-base);
overflow-y: auto;
.item {
padding: var(--el-font-size-base) 0;
&:not(:last-child) {
border-bottom: 1px solid #ddd;
}
.top {
display: flex;
padding-bottom: 6px;
.name {
flex: 1;
padding-right: 10px;
}
.n {
width: 50px;
color: #555;
}
.p {
width: 50px;
color: #555;
}
}
.tag_wrap {
display: flex;
flex-wrap: wrap;
.tag {
padding: 2px 6px;
background-color: var(--el-color-danger);
color: #fff;
margin-right: 10px;
margin-bottom: 10px;
font-size: 12px;
}
}
}
}
.btn_container {
display: flex;
gap: 10px;
.btn_wrap {
flex: 1;
padding-top: var(--el-font-size-base);
}
}
}
.place_order { .place_order {
background-color: #efefef; background-color: #efefef;
height: calc(100vh - 160px); height: calc(100vh - 160px);
@@ -104,17 +368,21 @@ function close() {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative;
overflow: hidden;
.btn { .btn {
display: flex; display: flex;
gap: 10px;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
text-decoration: none; text-decoration: none;
$size: 150px;
.top { .top {
background-color: var(--el-color-danger); background-color: var(--el-color-danger);
width: 130px; width: $size;
height: 130px; height: $size;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -135,6 +403,81 @@ function close() {
color: #999; color: #999;
padding-top: 6px; padding-top: 6px;
} }
.btn_wrap {
width: $size;
}
}
.people_num_wrap {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 9;
background-color: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.title {
padding-bottom: 40px;
font-size: 18px;
font-weight: bold;
}
.num_btns {
width: 80%;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 40px 40px 40px;
gap: 20px;
.item {
color: #fff;
border-radius: 4px;
background-color: var(--el-color-danger);
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: all .1s ease-in-out;
&.active {
font-size: 14px;
background-color: #e96565;
box-shadow: inset 0 4px 5px rgba(0, 0, 0, 0.3);
.ipt {
font-size: 14px;
}
}
.ipt {
width: 100%;
height: 100%;
outline: none;
border: none;
background: transparent;
color: #fff;
text-align: center;
font-size: 16px;
transition: all .1s ease-in-out;
&::-webkit-input-placeholder {
color: #fff;
}
}
}
}
.btn {
width: 80%;
padding-top: 40px;
}
} }
} }
} }

View File

@@ -22,16 +22,26 @@
</div> </div>
<div class="overflow_y" v-loading="loading"> <div class="overflow_y" v-loading="loading">
<div class="tab_list"> <div class="tab_list">
<div class="item" :class="{ active: tableItemActive == index }" <div class="item"
:class="{ active: tableItemActive == index, using: item.status == 'using', closed: item.status == 'closed' }"
v-for="(item, index) in tableList" :key="item.id" @click="slectTableHandle(index, item)"> v-for="(item, index) in tableList" :key="item.id" @click="slectTableHandle(index, item)">
<div class="tab_title" :class="`${item.status}`"> <div class="tab_title" :class="`${item.status}`">
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
<span>0/{{ item.maxCapacity }}</span> <span>0/{{ item.maxCapacity }}</span>
</div> </div>
<div class="tab_cont"> <div class="tab_cont">
<el-icon class="icon"> <el-icon class="icon" v-if="item.status != 'using'">
<CircleClose /> <CircleClose />
</el-icon> </el-icon>
<div class="using" v-else>
<div class="t1">开台中</div>
<!-- <div class="t2">
<el-icon>
<Timer />
</el-icon>
<span>{{ countTime(item.updatedAt) }}</span>
</div> -->
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -45,7 +55,7 @@
<!-- 台桌统计 --> <!-- 台桌统计 -->
<countCard v-if="!slectTable.id" /> <countCard v-if="!slectTable.id" />
<!-- 台桌信息 --> <!-- 台桌信息 -->
<tableInfo v-else :tableInfo="slectTable" @close="slectTableClose" /> <tableInfo v-else :tableInfo="slectTable" @close="slectTableClose" @success="paySuccess" />
</div> </div>
</div> </div>
</template> </template>
@@ -58,6 +68,7 @@ import tableInfo from '@/views/table/components/tableInfo.vue'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useUser } from "@/store/user.js" import { useUser } from "@/store/user.js"
import { dayjs } from 'element-plus'
const store = useUser() const store = useUser()
@@ -65,15 +76,15 @@ const tabActive = ref(0)
const tabAreas = ref([ const tabAreas = ref([
{ {
label: '全部', label: '全部',
type: 0, type: '',
}, },
{ {
label: '空闲', label: '空闲',
type: 1, type: 'idle',
}, },
{ {
label: '使用中', label: '使用中',
type: 2, type: 'using'
}, },
// { // {
// label: '已预订', // label: '已预订',
@@ -96,6 +107,19 @@ const slectTable = ref('')
// 切换类型 // 切换类型
function tabChange(item, index) { function tabChange(item, index) {
tabActive.value = index tabActive.value = index
queryShopTableAjax()
}
// 计算当前的时间差
function countTime(t) {
let ctime = dayjs().valueOf()
return dayjs(ctime - t).format('H小时m分')
}
// 支付成功,刷新状态
async function paySuccess() {
await queryShopTableAjax()
slectTableHandle(tableItemActive.value, tableList.value[tableItemActive.value])
} }
// 选择台桌 // 选择台桌
@@ -134,7 +158,7 @@ async function queryShopTableAjax() {
const res = await queryShopTable({ const res = await queryShopTable({
shopId: store.userInfo.shopId, shopId: store.userInfo.shopId,
areaId: area.value, areaId: area.value,
status: '', status: tabAreas.value[tabActive.value].type,
page: 1, page: 1,
pageSize: 500 pageSize: 500
}) })
@@ -230,13 +254,32 @@ onMounted(() => {
gap: var(--el-font-size-base); gap: var(--el-font-size-base);
.item { .item {
$usingColor: #D2441F;
$subColor: #3274D5;
$closedColor: #aeb8c9;
background-color: #efefef; background-color: #efefef;
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
border: 2px solid #fff; border: 2px solid #fff;
&.active { &.active {
border-color: var(--primary-color); border-color: $subColor;
}
&.using {
&.active {
border-color: $usingColor;
}
}
&.closed {
.tab_title {
color: #555;
}
&.active {
border-color: $closedColor;
}
} }
&:hover { &:hover {
@@ -250,13 +293,22 @@ onMounted(() => {
justify-content: space-between; justify-content: space-between;
padding: 0 10px; padding: 0 10px;
color: #fff; color: #fff;
background-color: #999;
&.subscribe { &.subscribe {
background-color: var(--el-color-success); background-color: $subColor;
} }
&.closed { &.closed {
background-color: #999; background-color: $closedColor;
}
&.idle {
background-color: $subColor;
}
&.using {
background-color: $usingColor;
} }
&.opening { &.opening {
@@ -264,7 +316,7 @@ onMounted(() => {
} }
&.cleaning { &.cleaning {
background-color: var(--el-color-danger); background-color: #999;
} }
} }
@@ -279,6 +331,27 @@ onMounted(() => {
font-size: 30px; font-size: 30px;
transform: rotate(45deg); transform: rotate(45deg);
} }
.using {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-bottom: 10px;
.t2 {
font-size: 12px;
display: flex;
align-items: center;
margin-top: 2px;
span {
margin-left: 4px;
}
}
}
} }
} }
} }

View File

@@ -2,15 +2,15 @@
<el-drawer size="100%" :with-header="false" direction="btt" v-model="dialogVisible"> <el-drawer size="100%" :with-header="false" direction="btt" v-model="dialogVisible">
<div class="box"> <div class="box">
<div class="box_top"> <div class="box_top">
<router-link to="/" class="box_top_left"> <div class="box_top_left" @click="dialogVisible = false">
<el-icon size="20"> <el-icon size="20">
<ArrowLeft /> <ArrowLeft />
</el-icon> </el-icon>
<div style="margin-left: 10px;">返回</div> <div style="margin-left: 4px;">返回</div>
</router-link> </div>
<router-link to='/workrecord' class="box_top_right"> <div class="box_top_right" @click="checkRecord">
交班记录 交班记录
</router-link> </div>
</div> </div>
<div class="box_content"> <div class="box_content">
<div class="box_content_left"> <div class="box_content_left">
@@ -26,7 +26,7 @@
</div> </div>
<div class="box_content_left_top_item_top"> <div class="box_content_left_top_item_top">
<div style="color:#ff5252; font-size: 30px;"> <div style="color:#ff5252; font-size: 30px;">
{{ infoData.amount || 0 }} {{ formatDecimal(infoData.amount || 0) }}
</div> </div>
<div style="margin-top: 6px; color: #666;"> <div style="margin-top: 6px; color: #666;">
营业额 营业额
@@ -36,83 +36,95 @@
<div class="box_content_left_top_item"> <div class="box_content_left_top_item">
<div class="box_content_left_top_item_botton"> <div class="box_content_left_top_item_botton">
<div style=" font-size: 20px;"> <div style=" font-size: 20px;">
{{ infoData.cashAmount || 0 }} {{ formatDecimal(infoData.cashAmount || 0) }}
</div> </div>
<div style="margin-top: 6px;"> <div style="margin-top: 6px;">
现金支付 现金支付
</div> </div>
<div style="margin-top: 6px;font-size: 14px;"> <!-- <div style="margin-top: 6px;font-size: 14px;">
查看详情 查看详情
</div> </div> -->
</div> </div>
<div class="box_content_left_top_item_botton"> <div class="box_content_left_top_item_botton">
<div style=" font-size: 20px;"> <div style=" font-size: 20px;">
{{ infoData.returnAmount || 0 }} {{ formatDecimal(infoData.returnAmount || 0) }}
</div> </div>
<div style="margin-top: 6px;"> <div style="margin-top: 6px;">
退款金额 退款金额
</div> </div>
<div style="margin-top: 6px;font-size: 14px;"> <!-- <div style="margin-top: 6px;font-size: 14px;">
查看详情 查看详情
</div> </div> -->
</div> </div>
</div> </div>
</div> </div>
<div class="box_content_left_bottom"> <div class="box_content_left_bottom">
<el-table :data="infoData.detailList" style="width: 100%;" height="400px"> <el-table :data="infoData.detailList" border style="width: 100%;" height="360px">
<el-table-column prop="productName" label="商品名称" /> <el-table-column prop="productName" label="商品名称" />
<el-table-column prop="skuName" label="规格名称" /> <el-table-column prop="skuName" label="规格名称" />
<el-table-column prop="num" label="商品数量" /> <el-table-column prop="num" label="商品数量" />
<el-table-column prop="amount" label="商品金额" /> <el-table-column prop="amount" label="商品金额">
<template v-slot="scope">
{{ formatDecimal(scope.row.amount || 0) }}
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
</div> </div>
<div class="box_content_right"> <div class="box_content_right">
<div class="box_content_right_tiem"> <div class="top">
<div class="box_content_right_tiemleft"> <div class="box_content_right_tiem">
上岗时间 <div class="box_content_right_tiemleft">
上岗时间
</div>
<div class="box_content_right_tiemright">
{{ dayjs(infoData.loginTime).format("YYYY-MM-DD HH:mm:ss") }}
</div>
</div> </div>
<div class="box_content_right_tiemright"> <div class="box_content_right_tiem">
{{ dayjs(infoData.loginTime).format("YYYY-MM-DD HH:mm:ss") }} <div class="box_content_right_tiemleft">
交班时间
</div>
<div class="box_content_right_tiemright">
{{ dayjs(infoData.loginOutTime).format("YYYY-MM-DD HH:mm:ss") }}
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
终端名称
</div>
<div class="box_content_right_tiemright">
{{ infoData.equipment || '无' }}
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
备用金
</div>
<div class="box_content_right_tiemright">
{{ infoData.pettyCash || '无' }}
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
收营员
</div>
<div class="box_content_right_tiemright">
{{ infoData.userName || '无' }}
</div>
</div> </div>
</div> </div>
<div class="box_content_right_tiem"> <div class="footer">
<div class="box_content_right_tiemleft"> <div class="is_shop">
交班时间 <div class="button">
<el-checkbox v-model="isPrint" border label="是否打印商品销售数据" style="width: 100%" />
</div>
</div> </div>
<div class="box_content_right_tiemright"> <div class="box_content_right_tiembutton" :loading="loading" @click="exit">
{{ dayjs(infoData.loginOutTime).format("YYYY-MM-DD HH:mm:ss") }} <span v-if="!loading">交班/关班</span>
<span v-else>交班/关班中....</span>
</div> </div>
</div> </div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
终端名称
</div>
<div class="box_content_right_tiemright">
{{ infoData.equipment || '无' }}
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
备用金
</div>
<div class="box_content_right_tiemright">
{{ infoData.pettyCash || '无' }}
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
收营员
</div>
<div class="box_content_right_tiemright">
{{ infoData.userName || '无' }}
</div>
</div>
<div class="box_content_right_tiembutton" :loading="loading" @click="exit">
<span v-if="!loading">交班/关班</span>
<span v-else>交班/关班中....</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -122,115 +134,104 @@
<script setup> <script setup>
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ElMessage, dayjs } from 'element-plus' import { ElMessage, dayjs } from 'element-plus'
import { shopInfoqueryDuty, loginlogout, handoverData } from '@/api/work/index.js' import { tglogout } from '@/api/group.js'
import { shopInfoqueryDuty, loginlogout, handoverData, handoverprint } from '@/api/work/index.js'
import useStorage from '@/utils/useStorage' import useStorage from '@/utils/useStorage'
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { bySubType } from "@/api/device"; import { bySubType } from "@/api/device";
import { useUser } from "@/store/user.js"; import { useUser } from "@/store/user.js";
import { useSocket } from "@/store/socket.js"; import { useSocket } from "@/store/socket.js";
import { formatDecimal } from '@/utils/index.js'
import { usePrint } from "@/store/print.js";
import { staffPermission } from '@/api/user.js'
const printStore = usePrint();
const socket = useSocket(); const socket = useSocket();
const store = useUser(); const store = useUser();
const router = useRouter(); const router = useRouter();
const dialogVisible = ref(true) //交班 const dialogVisible = ref(false) //交班
//详情数据 //详情数据
const infoData = ref({}) const infoData = ref({})
const loading = ref(false); const loading = ref(false);
const printList = ref([]); const isPrint = ref(true)
const localPrintList = ref([])
// 获取打印机状态 // 查看交班记录
async function bySubTypeAjax() { async function checkRecord() {
try { try {
const res = await bySubType({ const res = await staffPermission('yun_xu_cha_kan_suo_you_jiao_ban_ji_lu')
shopId: store.userInfo.shopId, router.push('/workrecord')
contentType: "local",
subType: "cash",
});
printList.value = res;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
// 获取本地打印机列表 // 开始交班
function getPrintList() {
ipcRenderer.send("getPrintList");
ipcRenderer.on("printList", (event, arg) => {
localPrintList.value = arg;
// console.log(localPrintList.value);
});
}
// 检查本地打印机是否能正常使用
function checkLocalPrint(deviceName) {
let print = ''
for (let item of localPrintList.value) {
if (item.name == deviceName) {
print = item
}
}
if (!print.name) {
return false
} else {
return true
}
}
const exit = async () => { const exit = async () => {
try { try {
if (printList.value.length) { await staffPermission('yun_xu_jiao_ban')
if (!checkLocalPrint(printList.value[0].config.deviceName)) {
loading.value = true; let data = {}
let res = await loginlogout({ // 获取交班打印小票数据
status: 1 data = await handoverData({
}) id: infoData.value.id
// useStorage.clear() })
useStorage.del('userInfo')
useStorage.del('token') if (printStore.deviceNoteList.length) {
ElMessage.success("交班成功");
setTimeout(() => {
router.replace({
name: "login",
});
}, 1000);
loading.value = false;
} else {
// 获取交班打印小票数据
const data = await handoverData({
id: infoData.value.id
})
data.deviceName = printList.value[0].config.deviceName
data.printTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
ipcRenderer.send("printerWorkSync", JSON.stringify(data));
// return
// useStorage.clear()
useStorage.del('userInfo')
useStorage.del('token')
ElMessage.success("交班成功");
setTimeout(() => {
router.replace({
name: "login",
});
}, 1000);
loading.value = false;
}
} else {
loading.value = true; loading.value = true;
let res = await loginlogout({
data.printTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
data.printShop = isPrint.value
printStore.printWork(data)
// return
await loginlogout({
status: 1 status: 1
}) })
useStorage.del('userInfo')
useStorage.del('token')
useStorage.del('douyin')
useStorage.del('categorysActive')
useStorage.del('updateFlag')
ElMessage.success("交班成功");
setTimeout(() => {
router.replace({
name: "login",
});
}, 1000);
loading.value = false;
} else {
console.log('云打印交班数据');
loading.value = true;
await tglogout()
// 退出登录
await loginlogout({
status: 1
})
// 打印交班数据
await handoverprint({
id: infoData.value.id,
isprintProduct: isPrint.value
})
// useStorage.clear() // useStorage.clear()
useStorage.del('userInfo') useStorage.del('userInfo')
useStorage.del('token') useStorage.del('token')
useStorage.del('douyin')
useStorage.del('categorysActive')
useStorage.del('updateFlag')
ElMessage.success("交班成功"); ElMessage.success("交班成功");
setTimeout(() => { setTimeout(() => {
router.replace({ router.replace({
@@ -241,9 +242,12 @@ const exit = async () => {
} }
socket.close() socket.close()
} catch (error) { } catch (error) {
console.log(error);
loading.value = false; loading.value = false;
} }
} }
// 获取交班打印数据前置
const infoshopInfoqueryDutys = async () => { const infoshopInfoqueryDutys = async () => {
try { try {
let res = await shopInfoqueryDuty({ let res = await shopInfoqueryDuty({
@@ -252,14 +256,23 @@ const infoshopInfoqueryDutys = async () => {
}) })
infoData.value = res infoData.value = res
} catch (error) { } catch (error) {
console.log(error);
} }
} }
onMounted(() => {
getPrintList() function show() {
bySubTypeAjax() dialogVisible.value = true
infoshopInfoqueryDutys() infoshopInfoqueryDutys()
}
defineExpose({
show
})
onMounted(() => {
// getPrintList()
// bySubTypeAjax()
}) })
</script> </script>
@@ -310,7 +323,6 @@ onMounted(() => {
.box_content_left_top { .box_content_left_top {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-bottom: 1px solid #ccc;
padding-bottom: 20px; padding-bottom: 20px;
.box_content_left_top_item { .box_content_left_top_item {
@@ -360,21 +372,17 @@ onMounted(() => {
.box_content_right { .box_content_right {
width: 30%; width: 30%;
height: 100%; height: 100%;
background: #fff;
margin-left: 10px; margin-left: 10px;
border-radius: 6px;
padding: 10px 12px;
position: relative; position: relative;
display: flex;
.box_content_right_tiem:nth-child(1) { flex-direction: column;
margin-top: 0; gap: 10px;
}
.box_content_right_tiem { .box_content_right_tiem {
margin-top: 10px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px;
.box_content_right_tiemleft { .box_content_right_tiemleft {
color: #9e9e9e; color: #9e9e9e;
@@ -383,11 +391,7 @@ onMounted(() => {
} }
.box_content_right_tiembutton { .box_content_right_tiembutton {
position: absolute;
bottom: 10px;
left: 5%;
background: #ba5050; background: #ba5050;
width: 90%;
border-radius: 5px; border-radius: 5px;
color: #fff; color: #fff;
text-align: center; text-align: center;
@@ -398,4 +402,44 @@ onMounted(() => {
} }
} }
} }
.top {
flex: 1;
background-color: #fff;
padding: 10px 0;
border-radius: 6px;
}
.footer {
padding: 10px;
background: #fff;
border-radius: 6px;
}
.is_shop {
.button {
flex: 1;
margin-bottom: 10px;
:deep(.el-checkbox.el-checkbox--large) {
height: var(--el-component-size-large);
background-color: #fff;
}
:deep(.el-checkbox__inner) {
width: 20px;
height: 20px;
&::after {
border-width: 2px;
top: 0;
left: 4px;
}
}
:deep(.el-checkbox__label) {
font-size: var(--el-font-size-base) !important;
}
}
}
</style> </style>

View File

@@ -1,130 +1,132 @@
<template> <template>
<el-drawer size="100%" :with-header="false" direction="btt" v-model="record"> <div class="box">
<div class="box"> <div class="box_top" @click="clickrecord">
<div class="box_top" @click="clickrecord"> <div class="box_top_left" @click="router.back()">
<router-link to='/work' class="box_top_left"> <el-icon size="20">
<el-icon size="20"> <ArrowLeft />
<ArrowLeft /> </el-icon>
</el-icon> <div class="box_top_right">
<div class="box_top_right"> 交班记录
交班记录 </div>
</div>
</router-link>
</div> </div>
<div class="box_content"> </div>
<div class="box_content_left"> <div class="box_content">
<div class="box_content_left_top"> <div class="box_content_left">
<div class="box_content_left_top_item"> <div class="box_content_left_top">
<div class="box_content_left_top_item_top"> <div class="box_content_left_top_item">
<div> <div class="box_content_left_top_item_top">
{{ infoData.total }} <div>
</div> {{ infoData.total }}
<div>
交班数
</div>
</div> </div>
<div class="box_content_left_top_item_top"> <div>
<div> 交班数
{{ infoData.amount }} </div>
</div> </div>
<div> <div class="box_content_left_top_item_top">
总收款 <div>
</div> {{ infoData.amount }}
</div>
<div>
总收款
</div> </div>
</div> </div>
</div> </div>
<div class="box_content_left_bouttom"> </div>
<div class="box_content_left_bouttomox"> <div class="box_content_left_bouttom">
<div class="box_content_left_bouttom_item" v-for="(item, index) in infoData.pageInfo.list" <div class="box_content_left_bouttomox">
:key="index"> <div class="box_content_left_bouttom_item" v-for="(item, index) in infoData.pageInfo.list"
<div class="wbox_content_left_bouttom_item_top"> :key="index">
<div> <div class="wbox_content_left_bouttom_item_top">
{{ dayjs(item.loginTime).format("YYYY-MM-DD HH:mm:ss") }} <div>
</div> {{ dayjs(item.loginTime).format("YYYY-MM-DD HH:mm:ss") }}
<div>
{{ item.amount }}
</div>
</div> </div>
<div class="wbox_content_left_bouttom_item_topone"> <div>
<div style="display: flex;"> {{ item.amount }}
<span>收营员</span>
<span style="font-weight: bold;">{{ item.userName }}</span>
</div>
<div style="display: flex;">
<span>总订单数</span>
<span style="font-weight: bold;">{{ item.orderNum }}</span>
</div>
</div> </div>
<div class="wbox_content_left_bouttom_item_topone"> </div>
<div style="display: flex;"> <div class="wbox_content_left_bouttom_item_topone">
<span>起止时间</span> <div style="display: flex;">
<span style="font-weight: bold;"> {{ dayjs(item.loginTime).format("YYYY-MM-DD HH:mm:ss") }}</span> <span>收营员</span>
</div> <span style="font-weight: bold;">{{ item.userName }}</span>
<div style="display: flex;"> </div>
<span>备用金</span> <div style="display: flex;">
<span style="font-weight: bold;">{{ item.pettyCash }}</span> <span>总订单数</span>
</div> <span style="font-weight: bold;">{{ item.orderNum }}</span>
</div>
</div>
<div class="wbox_content_left_bouttom_item_topone">
<div style="display: flex;">
<span>起止时间</span>
<span style="font-weight: bold;"> {{
dayjs(item.loginTime).format("YYYY-MM-DD HH:mm:ss") }}</span>
</div>
<div style="display: flex;">
<span>备用金</span>
<span style="font-weight: bold;">{{ item.pettyCash }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="box_content_right">
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
上岗时间
</div>
<div class="box_content_right_tiemright">
2024-03-05-19:33
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
交班时间
</div>
<div class="box_content_right_tiemright">
2024-03-05-19:33
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
终端名称
</div>
<div class="box_content_right_tiemright">
POS-1
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
备用金
</div>
<div class="box_content_right_tiemright">
0.00
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
收营员
</div>
<div class="box_content_right_tiemright">
测试
</div>
</div>
<div class="box_content_right_tiembutton">
关班/退出
</div>
</div> -->
</div> </div>
<!-- <div class="box_content_right">
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
上岗时间
</div>
<div class="box_content_right_tiemright">
2024-03-05-19:33
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
交班时间
</div>
<div class="box_content_right_tiemright">
2024-03-05-19:33
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
终端名称
</div>
<div class="box_content_right_tiemright">
POS-1
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
备用金
</div>
<div class="box_content_right_tiemright">
0.00
</div>
</div>
<div class="box_content_right_tiem">
<div class="box_content_right_tiemleft">
收营员
</div>
<div class="box_content_right_tiemright">
测试
</div>
</div>
<div class="box_content_right_tiembutton">
关班/退出
</div>
</div> -->
</div> </div>
</el-drawer> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { shopinfoqueryDutyFlow } from '@/api/work/index.js' import { shopinfoqueryDutyFlow } from '@/api/work/index.js'
import { useUser } from "@/store/user.js" import { useUser } from "@/store/user.js"
import { ElMessage, dayjs } from 'element-plus' import { ElMessage, dayjs } from 'element-plus'
const router = useRouter()
const store = useUser() const store = useUser()
const record = ref(true)//交班记录 const record = ref(true)//交班记录
//详情数据 //详情数据
@@ -150,8 +152,10 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.box { .box {
padding: 16px 0; width: 100%;
height: 100%; padding: 16px;
height: 100vh;
background-color: #efefef;
.box_top { .box_top {
display: flex; display: flex;
@@ -160,7 +164,7 @@ onMounted(() => {
background: #fff; background: #fff;
padding: 6px 10px; padding: 6px 10px;
border-radius: 6px; border-radius: 6px;
/* 取消下划线效果 */ /* 取消下划线效果 */
.box_top_left { .box_top_left {
display: flex; display: flex;
@@ -214,20 +218,23 @@ onMounted(() => {
.box_content_left_bouttom { .box_content_left_bouttom {
width: 100%; width: 100%;
height: 80%; height: 85%;
padding: 0 20px; padding: 0 20px;
overflow-y: auto;
.box_content_left_bouttomox::after { .box_content_left_bouttomox::after {
content: ''; content: '';
display: inline-block; display: inline-block;
width: 32%; width: 32%;
} }
.box_content_left_bouttomox { .box_content_left_bouttomox {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
.box_content_left_bouttom_item { .box_content_left_bouttom_item {
margin-top: 10px; margin-top: 10px;
width: 32%; width: 32%;
background: #eeeeee; background: #eeeeee;

View File

@@ -7,6 +7,8 @@ import path from "path";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => { export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), ""); const env = loadEnv(mode, process.cwd(), "");
// console.log(env.ENV);
return { return {
server: { server: {
proxy: { proxy: {
@@ -20,6 +22,11 @@ export default defineConfig(({ command, mode }) => {
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/php/, ""), rewrite: (path) => path.replace(/^\/php/, ""),
}, },
"/kp": {
target: env.VITE_API_KP_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/kp/, ""),
},
}, },
}, },
plugins: [ plugins: [
@@ -34,5 +41,8 @@ export default defineConfig(({ command, mode }) => {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
}, },
}, },
esbuild: {
drop: env.ENV == "production" ? ["console"] : [],
},
}; };
}); });