Compare commits

...

267 Commits

Author SHA1 Message Date
gyq 44144c5ac7 更改api 2025-02-08 17:10:10 +08:00
gyq 5e4bce25fb 优化台桌样式 2024-12-21 11:48:27 +08:00
gyq 5abb43cc49 优化转桌 2024-12-20 11:26:04 +08:00
gyq bbd99a1942 取消限制 2024-12-19 15:35:29 +08:00
gyq 00172f75e7 新增支付类型挂账 2024-12-19 13:46:57 +08:00
gyq 608c41de4f 更新优化问题 2024-12-18 09:05:10 +08:00
gyq ddd67d0c0a 将排队放出来 2024-12-17 15:11:34 +08:00
gyq 75f32c7b3d Merge branch 'gyq' of e.coding.net:g-cphe0354/pczhuomianduan/cashierdesktop into gyq_zhuan_zhuo 2024-12-17 15:10:35 +08:00
gyq 14ce5cb468 版本增加1.4.26 2024-12-17 13:30:17 +08:00
gyq 456bcf83a5 隐藏排队叫号 2024-12-17 10:00:52 +08:00
gyq 8dfa5b7aac 1.修复购物车清空已下单商品的问题
2.转桌/并桌只显示已下单商品
2024-12-17 09:59:41 +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
gyq aa25c6be3b 1.订单重复打印问题
2.订单增加时间筛选
3.订单增加桌号展示
2024-07-16 16:23:49 +08:00
gyq 4543854d0a 优化台桌页面 2024-07-16 14:06:34 +08:00
gyq 5e7935bb53 优化打印 订单新增桌号和时间筛选 2024-07-16 09:13:02 +08:00
gyq 38366601d4 优化各项 2024-07-15 09:46:40 +08:00
gyq e00feb82ec 提交 2024-07-12 17:54:26 +08:00
gyq 815b6e0a25 优化 2024-07-12 17:12:51 +08:00
gyq 8c1e1d3fbc 优化 2024-07-12 16:36:28 +08:00
gyq bb554a28d2 密码进行md5校验 2024-07-11 14:55:09 +08:00
gyq 2a09a3fd5b 新增退单支付密码、会员充值支付密码 2024-07-11 14:31:15 +08:00
gyq a70fa744b2 优化更多 2024-07-10 13:39:17 +08:00
gyq 91670a440b 将长链接剥离出来 2024-07-10 10:24:45 +08:00
gyq d101ecea41 优化长连接 2024-07-08 09:22:40 +08:00
gyq de8c5e20f4 提交代码 2024-07-07 09:11:37 +08:00
gyq 8e69969c0e 优化 2024-07-05 18:08:21 +08:00
gyq 105e38c726 更新 2024-07-05 09:43:48 +08:00
gyq 1f20c18384 优化 2024-07-04 13:35:59 +08:00
gyq c7758f66d7 优化wss连接 2024-07-04 10:04:01 +08:00
gyq 78672a0cf9 新增抖音团购核销 2024-07-01 09:41:33 +08:00
gyq 105ad4ee52 优化扫码叫号 2024-06-27 13:42:35 +08:00
gyq 89b078daae 优化 2024-06-27 10:17:06 +08:00
gyq b5356d8370 优化问题 2024-06-27 09:16:29 +08:00
gyq f3dfd4a121 优化标签小票打印 2024-06-26 14:01:12 +08:00
gyq 87627e7b35 优化标签小票打印 2024-06-24 16:43:54 +08:00
魏啾 408e0e44c4 Merge branch 'gyq' of https://e.coding.net/g-cphe0354/pczhuomianduan/cashierdesktop into wwz 2024-06-24 09:05:09 +08:00
gyq c4cb307275 优化 2024-06-24 09:04:57 +08:00
gyq ed8b593c1a 优化标签打印 2024-06-23 16:22:55 +08:00
魏啾 776523c1ea 更改订单显示样式 2024-06-22 17:51:48 +08:00
gyq 328b512411 1.手机扫码下单打印标签
2.PC桌面端下单打印标签
3.无需打开叫号窗口扫码叫号取餐
2024-06-22 17:29:34 +08:00
gyq 44495c3ee7 优化打印标签逻辑 2024-06-22 09:15:27 +08:00
gyq 0b2b4b44d0 1.新增全部本地打印标签
2.优化订单重但标签小票
2024-06-21 13:56:16 +08:00
gyq c86fff9691 新增标签打印 2024-06-18 18:36:53 +08:00
gyq 1439f12ee5 优化新增本地打印交班小票 2024-06-14 11:41:46 +08:00
gyq fde0ed0eaf 优化 2024-06-12 10:08:08 +08:00
gyq 883eb268f3 优化 2024-06-11 18:26:06 +08:00
gyq 0b42be9964 优化 2024-06-07 18:26:05 +08:00
gyq a1c8757aaf 优化 2024-06-06 17:44:51 +08:00
gyq b6030331ff 优化 2024-06-06 09:13:53 +08:00
gyq ffb6807a90 优化商品显示 2024-05-31 16:32:15 +08:00
gyq 69a74cc416 优化 2024-05-31 14:39:06 +08:00
gyq bcdf4a5b73 优化 2024-05-31 10:03:36 +08:00
gyq 4e954e42d5 优化 2024-05-31 09:50:08 +08:00
gyq a5e04e3f97 优化config域名 2024-05-30 14:30:19 +08:00
gyq 1f5f910e99 优化会员扫码充值查询 2024-05-30 11:06:50 +08:00
gyq c9dc11fa93 优化 2024-05-29 18:00:26 +08:00
gyq 3b0e6513c7 优化会员下单 2024-05-29 14:01:05 +08:00
gyq bf0614e10b 优化会员充值 2024-05-29 10:04:37 +08:00
gyq 69482f81a7 新增会员下单 2024-05-28 11:58:08 +08:00
gyq 6d7db2d743 优化 2024-05-28 11:05:50 +08:00
gyq c54304f3d6 优化 2024-05-24 17:54:54 +08:00
gyq 6c1e34be7a 优化 2024-05-23 17:38:34 +08:00
gyq b8ea253d6e 新增订单快捷收银 2024-05-23 16:09:11 +08:00
gyq 6df40c8423 优化打印 2024-05-23 14:24:41 +08:00
gyq d08ef6f271 新增团购券订单 2024-05-22 15:37:03 +08:00
gyq 94fb2d2c8e 优化 2024-05-21 17:47:06 +08:00
gyq 443f9ed305 新增团购券 2024-05-21 17:11:10 +08:00
gyq 5c56f5a277 优化 2024-05-20 17:42:57 +08:00
gyq dbc7444e78 优化快捷支付 2024-05-17 18:33:52 +08:00
gyq 6f378cc3c0 优化桌面端 2024-05-16 16:55:55 +08:00
gyq 43ccf82177 优化首页商品卡顿 2024-04-30 14:20:45 +08:00
gyq e26049593f 新增快捷收银 2024-04-18 13:42:17 +08:00
gyq f5974bf482 Merge branch 'wwz' of e.coding.net:g-cphe0354/pczhuomianduan/cashierdesktop into gyq 2024-04-18 09:22:37 +08:00
gyq 5bb73cb3b3 优化打印机 2024-04-18 09:22:30 +08:00
魏啾 3ed7f77508 重打标签小票 2024-04-16 18:25:12 +08:00
gyq 51fe3266ce 优化 2024-04-03 18:07:40 +08:00
gyq f1982a73f6 Merge branch 'wwz' of e.coding.net:g-cphe0354/pczhuomianduan/cashierdesktop into gyq 2024-04-03 17:25:27 +08:00
gyq ca4eec636a 优化打印机 2024-04-03 17:25:22 +08:00
魏啾 0f9dd3ea66 Merge branch 'gyq' of https://e.coding.net/g-cphe0354/pczhuomianduan/cashierdesktop into wwz 2024-04-03 17:24:47 +08:00
魏啾 84c6ef4aae 最大数量 2024-04-03 17:24:25 +08:00
gyq a57b45160e Merge branch 'wwz' of e.coding.net:g-cphe0354/pczhuomianduan/cashierdesktop into gyq 2024-04-03 15:58:04 +08:00
gyq 07d7df0416 新增增加打印机 2024-04-03 15:57:58 +08:00
魏啾 fae65b2e2a Merge branch 'gyq' of https://e.coding.net/g-cphe0354/pczhuomianduan/cashierdesktop into wwz 2024-04-03 15:41:37 +08:00
魏啾 810efcb381 退单 2024-04-03 15:41:25 +08:00
gyq da00851195 版本更新 2024-04-01 10:40:17 +08:00
魏啾 ddd0c8dba8 11 2024-03-26 14:16:25 +08:00
魏啾 6456832019 Merge branch 'gyq' of https://e.coding.net/g-cphe0354/pczhuomianduan/cashierdesktop into wwz 2024-03-25 17:07:43 +08:00
魏啾 8a9889d188 修改会员没有及时刷新 2024-03-25 17:07:31 +08:00
gyq 154a362af6 更新 2024-03-21 14:29:15 +08:00
魏啾 5e3d3c91f1 优化 2024-03-16 14:40:50 +08:00
gyq 56e28681f6 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-16 14:30:12 +08:00
gyq 7f8831c3cd 优化收银全部菜单不可点击 2024-03-16 14:30:07 +08:00
魏啾 fa923f3db5 订单防抖 2024-03-16 14:29:58 +08:00
gyq f36af706d6 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-16 13:44:53 +08:00
gyq d222e76576 版本更新 2024-03-16 13:44:48 +08:00
魏啾 9a45998a65 订单状态修改 2024-03-16 13:44:03 +08:00
魏啾 19b71d754f 訂單 2024-03-16 12:07:44 +08:00
gyq 426e1d59a9 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-16 10:09:41 +08:00
gyq f430eee3c2 版本更新 2024-03-16 10:09:25 +08:00
魏啾 ae2137f468 退款loading 2024-03-16 10:08:48 +08:00
魏啾 e9e2f03acf 改变订单颜色 2024-03-16 10:03:20 +08:00
魏啾 e1756b1a69 处理订单数据 2024-03-16 09:34:28 +08:00
gyq 910194a509 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-15 18:16:59 +08:00
gyq b9004476ec 版本更新 2024-03-15 18:16:53 +08:00
魏啾 fc6aa68ea3 退款成功 2024-03-15 18:16:28 +08:00
魏啾 cb1e356aeb 退单 2024-03-15 18:14:42 +08:00
魏啾 34a04442ac 交班 2024-03-15 15:14:30 +08:00
魏啾 03c6079301 退单选项 2024-03-15 14:01:09 +08:00
魏啾 10ea9d4f68 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-03-15 13:55:01 +08:00
魏啾 b7d35e5a6a 退款 2024-03-15 13:54:50 +08:00
gyq 5dc8848432 版本更新 2024-03-15 11:23:58 +08:00
魏啾 4c6e0cc38a Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-03-15 11:21:22 +08:00
魏啾 1450681d77 11 2024-03-15 11:21:12 +08:00
魏啾 1dec68b589 打印 2024-03-15 11:20:37 +08:00
gyq 2b8910f6a1 优化结算订单 2024-03-15 11:12:55 +08:00
魏啾 2176ca3ab1 订单 2024-03-15 10:32:20 +08:00
魏啾 ba7efac5d1 订单退单 2024-03-15 10:29:33 +08:00
gyq 861e62ce15 版本号变更 2024-03-15 09:00:56 +08:00
gyq e112137239 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-14 17:30:19 +08:00
gyq cacbda0424 优化服务员名称 2024-03-14 17:30:14 +08:00
魏啾 1068fae037 leis 2024-03-14 17:29:39 +08:00
魏啾 3b5d04bc30 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-03-14 17:28:38 +08:00
魏啾 c094b1d673 list 2024-03-14 17:28:23 +08:00
gyq 286f6206f1 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-14 16:24:38 +08:00
gyq ba6f1cdc49 优化结算 2024-03-14 16:24:32 +08:00
魏啾 8d8b2f79b5 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-03-14 16:21:47 +08:00
魏啾 d5942f5453 会员支付扫码枪 2024-03-14 16:21:24 +08:00
gyq dc306aeff0 优化规格选择 2024-03-14 15:58:56 +08:00
gyq d83b593db1 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-13 18:27:02 +08:00
gyq c68b36740f 优化规格 2024-03-13 18:26:57 +08:00
魏啾 ec1fe43f3d 会员支付 2024-03-13 18:26:22 +08:00
gyq e058c97016 优化首页添加购物车 2024-03-13 17:14:48 +08:00
gyq 5e5d854849 优化扫码支付提示 2024-03-13 14:41:25 +08:00
gyq ef18990451 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-13 14:41:05 +08:00
gyq 4ea6ece75a 优化规格选择 2024-03-13 09:04:28 +08:00
魏啾 c1d4405d0e Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-03-11 14:38:01 +08:00
gyq 4adbc125b2 优化样式 2024-03-11 14:37:37 +08:00
魏啾 83a25a2253 1 2024-03-11 14:35:44 +08:00
gyq 8339506c93 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-11 14:34:22 +08:00
gyq 0fbc614c61 优化 2024-03-11 14:34:17 +08:00
魏啾 d407cb81be 11 2024-03-11 14:32:48 +08:00
gyq 11290d1505 优化样式 2024-03-08 15:04:11 +08:00
gyq 6e3a4c5ce3 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-07 18:29:40 +08:00
魏啾 f0b7169a29 完善订单 2024-03-07 18:29:29 +08:00
gyq 58993e00ee 优化新增串口 2024-03-07 18:29:19 +08:00
魏啾 fc810fd02f 版本号 2024-03-07 16:18:36 +08:00
gyq 7dc27dbeef Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-07 16:15:19 +08:00
gyq 31ef882431 优化样式 新增版本号 2024-03-07 16:15:03 +08:00
魏啾 d648781ea3 添加更多 2024-03-07 16:14:53 +08:00
gyq 4a5b52fa45 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-07 09:47:30 +08:00
gyq 396d237710 适配小屏 2024-03-07 09:47:25 +08:00
魏啾 dc2d7124d0 完成交班 2024-03-07 09:47:06 +08:00
gyq b550cf3fd8 优化打印效果 2024-03-07 09:07:22 +08:00
gyq 5d96435125 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-06 10:49:56 +08:00
gyq 91ce993270 解决打包问题 2024-03-06 10:49:45 +08:00
魏啾 d29d8b7f93 11 2024-03-06 10:48:30 +08:00
魏啾 48d6583a03 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-03-05 14:35:53 +08:00
魏啾 8c42d09f38 订单信息完成退单页面 2024-03-05 14:35:37 +08:00
gyq 0d04bfc3d2 调试打包 2024-03-05 14:35:19 +08:00
gyq 4983ae1664 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-05 09:52:26 +08:00
gyq d00612da5a 调试打印 2024-03-05 09:52:20 +08:00
魏啾 b42588ca1c 完成订单 2024-03-05 09:42:39 +08:00
gyq 0fa0d56558 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-04 15:49:23 +08:00
gyq 27721ca096 对接完毕下单 2024-03-04 15:48:40 +08:00
魏啾 4facc5cc99 完成订单 2024-03-04 15:47:34 +08:00
gyq 6e796c1855 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-01 20:31:35 +08:00
gyq 120c478b86 新增结算页面 2024-03-01 20:30:41 +08:00
魏啾 c0caecf0fa 完成会员 2024-03-01 20:26:44 +08:00
gyq 2e783aa36c Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-03-01 16:15:29 +08:00
gyq e925948dcd 完成购物车所有功能 2024-03-01 16:15:22 +08:00
魏啾 f8521dad84 完成添加会员form表单 2024-02-29 20:29:42 +08:00
gyq d2689b226c 优化 2024-02-29 17:36:10 +08:00
gyq 7d30a42cb8 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-02-29 17:34:58 +08:00
gyq 5deec59c38 新增阶结算订单 2024-02-29 17:34:46 +08:00
魏啾 6faf1474bd 1 2024-02-29 17:34:10 +08:00
魏啾 e0ade277dc 11 2024-02-29 17:32:14 +08:00
gyq c198f81483 对接添加购物车 2024-02-29 16:55:18 +08:00
gyq 1aa33cab00 优化 2024-02-29 09:24:39 +08:00
gyq fcf14d27fe 优化全部菜单 2024-02-28 16:43:07 +08:00
gyq d65e3db7c0 对接台桌列表 2024-02-28 15:02:31 +08:00
gyq 56f9d83447 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-02-27 09:02:54 +08:00
魏啾 2ac2a3209a 1 2024-02-27 09:02:37 +08:00
gyq 1525bb1bf9 优化element弹窗 2024-02-27 09:02:31 +08:00
gyq 276e65e9a3 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-02-26 17:24:59 +08:00
gyq 9aa6eee67a 对接规格查询库存价格 2024-02-26 17:24:50 +08:00
魏啾 8e819051b0 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-02-26 16:42:38 +08:00
魏啾 9e3e411b7f Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-02-26 16:36:25 +08:00
gyq 9bbd463852 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-02-26 16:36:02 +08:00
魏啾 4a052300d5 11 2024-02-26 16:35:29 +08:00
gyq 2a0f839988 删除style 2024-02-26 16:35:29 +08:00
超掌柜 aa16193d4f
删除文件 node_modules 2024-02-26 08:20:12 +00:00
gyq bf1428b205 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-02-26 16:16:15 +08:00
gyq 91e2f2ff33 优化 2024-02-26 16:16:02 +08:00
gyq 5bdc900719 啊啊 2024-02-26 16:14:23 +08:00
gyq 1f59f300d0 啊啊 2024-02-26 16:13:48 +08:00
魏啾 158ea7c55a 1 2024-02-26 16:13:19 +08:00
gyq eeac3e3541 优化 2024-02-26 16:12:20 +08:00
gyq a662ae5c01 Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-02-26 14:27:59 +08:00
gyq 6b6caa451e 1.对接商品分类 2.对接商品列表 2024-02-26 14:27:52 +08:00
魏啾 9a060cbfc7 111 2024-02-26 14:27:31 +08:00
魏啾 a1b6f3d030 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-02-26 11:47:24 +08:00
魏啾 1667091380 11 2024-02-26 11:47:08 +08:00
gyq 33225b0aef Merge branch 'wwz' of gitee.com:shaanxi-super-shopkeeper_1/cashierdesktop into gyq 2024-02-26 11:46:27 +08:00
gyq 9744a968bb 新增请求配置,对接登录接口 2024-02-26 11:46:21 +08:00
魏啾 b37811ced5 11 2024-02-23 18:32:41 +08:00
魏啾 08597b3c9f 111 2024-02-23 16:18:07 +08:00
魏啾 b69be492d7 11 2024-02-23 15:29:58 +08:00
魏啾 3cb4828476 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-02-22 16:25:50 +08:00
魏啾 d99575b2b7 wwz 2024-02-22 16:25:34 +08:00
gyq 5298ff2569 新增备注组件,优化el样式变量 2024-02-22 16:24:50 +08:00
魏啾 42bf301417 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-02-22 10:12:15 +08:00
魏啾 1d3ca787ed 11 2024-02-22 10:11:59 +08:00
gyq d151647906 优化主题色、字体 2024-02-22 10:11:12 +08:00
魏啾 1117cba7de Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-02-22 09:45:11 +08:00
gyq 1dc82a11aa 优化 2024-02-22 09:44:51 +08:00
魏啾 9ff98ba4d2 Merge branch 'gyq' of https://gitee.com/shaanxi-super-shopkeeper_1/cashierdesktop into wwz 2024-02-22 09:43:47 +08:00
gyq 5250c581ab 优化redme 2024-02-22 09:43:40 +08:00
魏啾 b0828cc1c0 11 2024-02-22 09:42:57 +08:00
120 changed files with 37566 additions and 905 deletions

41
.env.development Normal file
View File

@ -0,0 +1,41 @@
# 本地环境
ENV = development
# 正式ws
VITE_API_WSS = 'wss://cashier.sxczgkj.cn/client'
#测试ws
# VITE_API_WSS = 'wss://wxcashiertest.sxczgkj.cn/client'
# 阿伟本地ws
# VITE_API_WSS = 'ws://192.168.2.17:9998/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 = 'http://192.168.2.96:10587/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://pre-cashierclient.sxczgkj.cn/cashier-client'
# 张松本地
# VITE_API_URL = 'https://36z1017t45.goho.co/cashier-client'
# 正式
VITE_API_URL = 'https://cashierclient.sxczgkj.cn/cashier-client'

17
.env.production Normal file
View File

@ -0,0 +1,17 @@
# 线上环境
ENV = production
# 正式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 = 'https://invoice.sxczgkj.cn/api'
# 线上环境接口地址
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'

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
release
dist
dist-ssr
*.local

View File

@ -1,9 +1,15 @@
# Vue 3 + Vite
npm install
npm install @element-plus/icons-vue
npm run dev
npm run build
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

20
addVersion.js Normal file
View File

@ -0,0 +1,20 @@
//npm run build打包前执行此段代码
const fs = require('fs')
//返回package的json数据
function getPackageJson() {
let data = fs.readFileSync('./package.json');//fs读取文件
return JSON.parse(data);//转换为json对象
}
let packageData = getPackageJson();//获取package的json
let arr = packageData.version.split('.');//切割后的版本号数组
arr[2] = parseInt(arr[2]) + 1;
packageData.version = arr.join('.');//转换为以"."分割的字符串
//用packageData覆盖package.json内容
fs.writeFile(
'./package.json',
JSON.stringify(packageData, null, "\t"
),
(err) => { }
);

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,21 @@
import path from "path";
import { app, BrowserWindow, ipcMain } from "electron";
import axios from "axios";
import os from "os";
import fs from "fs";
import { exec } from "child_process";
let win;
app.whenReady().then(() => {
const win = new BrowserWindow({
title: "Main window",
width: 1200,
height: 800,
win = new BrowserWindow({
title: "银收客",
width: 1024,
height: 768,
fullscreenable: true,
fullscreen: false,
fullscreen: process.env.VITE_DEV_SERVER_URL ? false : true,
simpleFullscreen: true,
frame: true,
frame: process.env.VITE_DEV_SERVER_URL ? true : false,
webPreferences: {
// 集成网页和 Node.js也就是在渲染进程中可以调用 Node.js 方法
nodeIntegration: true,
@ -19,23 +26,272 @@ app.whenReady().then(() => {
// You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve`
if (process.env.VITE_DEV_SERVER_URL) {
win.loadURL(process.env.VITE_DEV_SERVER_URL);
// 使用vite开发服务的url路径访问应用
// win.webContents.openDevTools();
} else {
// Load your file
win.loadFile("dist/index.html");
win.loadFile(path.resolve(__dirname, "../dist/index.html")); // 打包后使用文件路径访问应用
}
win.webContents.openDevTools();
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", () => {
// 在 macOS 系统内, 如果没有已开启的应用窗口
// 点击托盘图标时通常会重新创建一个新窗口
if (BrowserWindow.getAllWindows().length === 0) createWindow();
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
ipcMain.on("quitHandler", (_, msg) => {
console.log(msg);
win = null;
app.exit();
});
// 给渲染进程返回打印机列表
ipcMain.on("getPrintList", () => {
win.webContents.getPrintersAsync().then((res) => {
// console.log("打印机列表", res);
win.webContents.send("printList", res);
});
});
// 获取本机mac
ipcMain.on("getOSmacSync", () => {
let mac = "";
if (os.networkInterfaces().WLAN) {
mac = os.networkInterfaces().WLAN[0].mac;
console.log("wlan.mac===", mac);
} else {
mac = os.networkInterfaces()["以太网"][0].mac;
console.log("以太网.mac===", mac);
}
win.webContents.send("getOSmacRes", mac);
});
// 创建打印小票子窗口
// const printWin = new BrowserWindow({
// show: false,
// width: 464,
// height: 1726,
// webPreferences: {
// // 集成网页和 Node.js也就是在渲染进程中可以调用 Node.js 方法
// nodeIntegration: true,
// contextIsolation: false,
// },
// });
// if (process.env.VITE_DEV_SERVER_URL) {
// // 加载打印的html文件
// printWin.loadFile(path.join(__dirname, "../public/print.html"));
// } else {
// printWin.loadFile(path.resolve(__dirname, "../dist/print.html")); // 打包后使用文件路径访问应用
// }
// // 接收订单页面发过来的参数发送给打印页
// ipcMain.on("printerInfoSync", (event, arg) => {
// printWin.webContents.send("getParams", arg);
// });
// // 执行打印操作
// ipcMain.on("printStart", (event, 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,
// },
// });
// });
// // 交班小票的窗口
// const workPrintWin = new BrowserWindow({
// show: false,
// width: 464,
// height: 1726,
// webPreferences: {
// nodeIntegration: true,
// contextIsolation: false,
// },
// });
// if (process.env.VITE_DEV_SERVER_URL) {
// // 加载打印的html文件
// workPrintWin.loadFile(path.join(__dirname, "../public/work_print.html"));
// } else {
// workPrintWin.loadFile(path.resolve(__dirname, "../dist/work_print.html")); // 打包后使用文件路径访问应用
// }
// // 接收渲染进程发送的数据
// ipcMain.on("printerWorkSync", (event, arg) => {
// workPrintWin.webContents.send("getParams", arg);
// });
// // 执行交班小票的打印操作
// ipcMain.on("printWorkStart", (event, arg) => {
// // console.log(arg);
// const _parmas = JSON.parse(arg);
// // console.log(_parmas)
// let name = _parmas.deviceName;
// workPrintWin.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,
// },
// });
// });
// 标签小票的窗口
const tagPrintWin = new BrowserWindow({
show: false,
width: 360,
height: 240,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
if (process.env.VITE_DEV_SERVER_URL) {
// 加载打印的html文件
tagPrintWin.loadFile(path.join(__dirname, "../public/tag_print.html"));
} else {
tagPrintWin.loadFile(path.resolve(__dirname, "../dist/tag_print.html")); // 打包后使用文件路径访问应用
}
// 接收渲染进程发送的数据
ipcMain.on("printerTagSync", (event, arg) => {
console.log(arg);
tagPrintWin.webContents.send("getParams", arg);
});
// 执行标签小票的打印操作
ipcMain.on("printTagStart", (event, arg) => {
// console.log(arg);
const _parmas = JSON.parse(arg);
// console.log(_parmas)
let name = _parmas.deviceName;
tagPrintWin.webContents.print({
silent: true,
deviceName: name,
pageSize: {
width: 45000,
height: 30000,
},
scaleFactor: 80,
landscape: false,
margins: {
marginType: "none",
top: 0,
bottom: 0,
left: 0,
right: 0,
},
dpi: {
horizontal: 203,
vertical: 203,
},
});
});
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on("second-instance", (event, commandLine, workingDirectory) => {
// 当运行第二个实例时,将会聚焦到mainWindow这个窗口
if (win) {
if (win.isMinimized()) win.restore();
win.focus();
win.show();
}
});
}
// 阻止默认关闭
win.on("close", (e) => {
e.preventDefault();
win.webContents.send("showCloseDialog");
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});

View File

@ -1,10 +1,11 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en" id="html">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
<meta http-equiv="Content-Security-Policy" />
<title>银收客</title>
</head>
<body>
<div id="app"></div>

View File

@ -1,29 +1,80 @@
{
"name": "vite-electron",
"private": true,
"version": "0.0.0",
"main": "dist-electron/main.js",
"scripts": {
"dev": "chcp 65001 && vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.2",
"element-plus": "^2.4.3",
"pinia": "^2.1.7",
"vue": "^3.3.8",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.0",
"path": "^0.12.7",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
"tree-kill": "^1.2.2",
"vite": "^5.0.0",
"vite-plugin-electron": "^0.15.4",
"vite-plugin-electron-renderer": "^0.14.5"
}
}
"name": "vite-electron",
"private": true,
"version": "1.4.27",
"main": "dist-electron/main.js",
"scripts": {
"dev": "chcp 65001 && vite",
"build": "node ./addVersion.js && vite build && electron-builder",
"build:test": "vite build --mode test && electron-builder",
"preview": "vite preview",
"build:win": "node ./addVersion.js && vite build && electron-builder --w"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.2",
"dayjs": "^1.11.10",
"electron-pos-printer": "^1.3.6",
"electron-pos-printer-vue": "^1.0.9",
"element-plus": "^2.4.3",
"js-md5": "^0.8.3",
"lodash": "^4.17.21",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode": "^1.5.3",
"reconnecting-websocket": "^4.4.0",
"serialport": "^12.0.0",
"speak-tts": "^2.0.8",
"swiper": "^11.1.1",
"uuid": "^10.0.0",
"vue": "^3.3.8",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.0",
"electron": "^28.2.3",
"electron-builder": "^24.13.3",
"electron-rebuild": "^3.2.9",
"path": "^0.12.7",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
"tree-kill": "^1.2.2",
"vite": "^5.0.0",
"vite-plugin-electron": "^0.15.4",
"vite-plugin-electron-renderer": "^0.14.5"
},
"build": {
"appId": "com.cashierdesktop.app",
"productName": "银收客",
"asar": true,
"files": [
"./dist/**/*",
"./dist-electron/**/*"
],
"directories": {
"buildResources": "build",
"output": "release"
},
"win": {
"icon": "./public/logo.ico",
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
]
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./public/logo.ico",
"uninstallerIcon": "./public/logo.ico",
"installerHeaderIcon": "./public/logo.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
}
}

BIN
public/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

79
public/print.css Normal file
View File

@ -0,0 +1,79 @@
* {
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
padding: 0 8mm;
}
.print_view {
padding: 20px 0;
}
.print_view .title {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
margin-bottom: 4px;
}
.print_view .title.t1 {
font-size: 24px;
}
.print_view .title.t2 {
margin-bottom: 15px;
}
.print_view .row {
margin-top: 2px;
font-size: 12px;
}
.print_view .row.between {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.print_view .line {
margin: 10px 0;
border-bottom: 1px solid #000;
}
.print_view .table {
width: 100%;
}
.print_view .table tr {
width: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.print_view .table tr:not(:last-child) {
margin-bottom: 10px;
}
.print_view .table tr td {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
font-size: 12px;
}
.print_view .table tr td:nth-child(1) {
-webkit-box-flex: 2;
-ms-flex: 2;
flex: 2;
}
.print_view .table tr td:not(:first-child) {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.print_view .table tr td .sku {
font-size: 10px;
}

100
public/print.html Normal file
View File

@ -0,0 +1,100 @@
<!--
~ Copyright (c) 2023. Author Hubert Formin <2399270194@qq.com>
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Print preview</title>
<link rel="stylesheet" href="./print.css" />
</head>
<body>
<div id="app">
<div class="print_view">
<div class="title t1">{{data.shop_name}}</div>
<div class="title t2">
结算单【{{data.orderInfo && data.orderInfo.masterId}}】
</div>
<div class="row">
订单号:{{data.orderInfo && data.orderInfo.orderNo}}
</div>
<div class="row">交易时间:{{data.createdAt}}</div>
<div class="row">收银员:【POS-1】1</div>
<div class="line"></div>
<table class="table">
<tr>
<td>品名</td>
<td>单价</td>
<td>数量</td>
<td>小计</td>
</tr>
<tr v-for="item in data.carts" :key="item.id">
<td>
<div>{{item.name}}</div>
<div class="sku">{{item.skuName}}</div>
</td>
<td>{{item.salePrice}}</td>
<td>{{item.number}}</td>
<td>{{item.totalAmount}}</td>
</tr>
</table>
<div class="line"></div>
<div class="row between">
<span>合计:</span>
<span>{{data.amount}}</span>
</div>
<!-- <div class="row between">
<span>合计:</span>
<span>30.00</span>
</div> -->
<div class="row between">
<span>原价:{{data.amount}}节省了0</span>
</div>
<div class="row between">
<span>积分:</span>
<span>0</span>
</div>
<div class="row between">
<span>余额:</span>
<span>0.00</span>
</div>
<div class="line"></div>
<div class="row">备注:</div>
<div class="row">打印时间:{{data.printTime}}</div>
</div>
</div>
<script type="module">
const { ipcRenderer } = require("electron");
import {
createApp,
ref,
onMounted,
} from "../node_modules/vue/dist/vue.esm-browser.js";
createApp({
setup() {
const data = ref({});
onMounted(() => {
ipcRenderer.on("getParams", (event, arg) => {
data.value = JSON.parse(arg);
console.log(data.value);
setTimeout(() => {
ipcRenderer.send(
"printStart",
JSON.stringify({ deviceName: data.value.deviceName })
);
}, 500);
});
});
return {
data,
};
},
}).mount("#app");
</script>
</body>
</html>

71
public/print.scss Normal file
View File

@ -0,0 +1,71 @@
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
padding: 0 8mm;
}
.print_view {
padding: 20px 0;
.title {
display: flex;
justify-content: center;
margin-bottom: 4px;
&.t1 {
font-size: 24px;
}
&.t2 {
margin-bottom: 15px;
}
}
.row {
margin-top: 2px;
font-size: 12px;
&.between {
display: flex;
justify-content: space-between;
}
}
.line {
margin: 10px 0;
border-bottom: 1px solid #000;
}
.table {
width: 100%;
tr {
width: 100%;
display: flex;
&:not(:last-child) {
margin-bottom: 10px;
}
td {
flex: 1;
font-size: 12px;
&:nth-child(1) {
flex: 2;
}
&:not(:first-child) {
display: flex;
justify-content: flex-end;
}
.sku {
font-size: 10px;
}
}
}
}
}

1540
public/qrcode.js Normal file

File diff suppressed because it is too large Load Diff

75
public/tag_print.css Normal file
View File

@ -0,0 +1,75 @@
* {
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-size: 12px;
color: #000;
}
html,
body {
width: 100vw;
height: 100vh;
}
body {
padding: 10px;
}
#app {
width: 100%;
height: 100%;
overflow: hidden;
}
.print_view {
position: relative;
width: 100%;
height: 100%;
padding-left: 12px;
}
.print_view .ewm {
width: 50px;
height: 50px;
position: absolute;
top: 0;
right: 0;
z-index: 99;
}
.print_view .header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.print_view .header .logo {
width: 90px;
height: 30px;
-o-object-fit: cover;
object-fit: cover;
}
.print_view .header .title {
margin-left: 6px;
}
.print_view .number_wrap {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
.print_view .number_wrap .num {
font-size: 14px;
font-weight: bold;
}
.print_view .number_wrap .info {
margin-left: 10px;
padding-bottom: 2px;
}
.print_view .time {
font-weight: bold;
}

80
public/tag_print.html Normal file
View File

@ -0,0 +1,80 @@
<!--
~ Copyright (c) 2023. Author Hubert Formin <2399270194@qq.com>
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Print preview</title>
<link rel="stylesheet" href="./tag_print.css" />
</head>
<body>
<div id="app">
<div class="print_view">
<div class="ewm" id="ewm"></div>
<div class="header">
<img class="logo" :src="data.ticketLogo || './logo.png'" />
<!-- <span class="title">{{data.ticket_logo}}</span> -->
</div>
<div class="number_wrap">
<div class="num" v-if="data.outNumber">{{data.outNumber}}</div>
<div class="info" v-if="data.masterId">座位号:{{data.masterId}}</div>
</div>
<div class="shop_info">
<div class="name">{{data.name}}</div>
<div class="text" v-if="data.skuName">【{{data.skuName}}】</div>
</div>
<div class="time">{{data.createdAt}}</div>
<div class="tips">建议尽快享用,风味更佳 {{ data.count }}</div>
</div>
</div>
<script src="./qrcode.js"></script>
<script type="module">
const { ipcRenderer } = require("electron");
import {
createApp,
ref,
onMounted,
} from "../node_modules/vue/dist/vue.esm-browser.js";
createApp({
setup() {
const data = ref({});
onMounted(() => {
ipcRenderer.on("getParams", (event, arg) => {
data.value = JSON.parse(arg);
// console.log(data.value);
let size = 46;
let qrcode = new QRCode(document.getElementById("ewm"), {
text: data.value.outNumber,
width: size,
height: size,
correctLevel: QRCode.CorrectLevel.H,
});
ipcRenderer.send(
"printTagStart",
JSON.stringify({ deviceName: data.value.deviceName })
);
// setTimeout(() => {
// ipcRenderer.send(
// "printTagStart",
// JSON.stringify({ deviceName: data.value.deviceName })
// );
// }, 100);
});
});
return {
data,
};
},
}).mount("#app");
</script>
</body>
</html>

63
public/tag_print.scss Normal file
View File

@ -0,0 +1,63 @@
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-size: 12px;
color: #000;
}
html,
body {
width: 100vw;
height: 100vh;
}
body {
padding: 10px;
}
#app {
width: 100%;
height: 100%;
overflow: hidden;
}
.print_view {
position: relative;
width: 100%;
height: 100%;
padding-left: 12px;
.ewm {
$size: 50px;
width: $size;
height: $size;
position: absolute;
top: 0;
right: 0;
z-index: 99;
}
.header {
display: flex;
align-items: center;
.logo {
$size: 90px;
width: $size;
height: 30px;
object-fit: cover;
}
.title {
margin-left: 6px;
}
}
.number_wrap {
display: flex;
align-items: flex-end;
.num {
font-size: 14px;
font-weight: bold;
}
.info {
margin-left: 10px;
padding-bottom: 2px;
}
}
.time {
font-weight: bold;
}
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

83
public/work_print.css Normal file
View File

@ -0,0 +1,83 @@
* {
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
padding: 0 8mm;
}
.empty {
height: 20px;
}
.print_view {
padding: 20px 0;
}
.print_view .title {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
margin-bottom: 4px;
}
.print_view .title.t1 {
font-size: 24px;
}
.print_view .title.t2 {
margin-bottom: 15px;
}
.print_view .row {
margin-top: 2px;
font-size: 12px;
}
.print_view .row.between {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.print_view .line {
margin: 10px 0;
border-bottom: 1px solid #000;
}
.print_view .table {
width: 100%;
}
.print_view .table tr {
width: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.print_view .table tr:not(:last-child) {
margin-bottom: 10px;
}
.print_view .table tr td {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
font-size: 12px;
}
.print_view .table tr td:nth-child(1) {
-webkit-box-flex: 2;
-ms-flex: 2;
flex: 2;
}
.print_view .table tr td:not(:first-child) {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.print_view .table tr td .sku {
font-size: 10px;
}

80
public/work_print.html Normal file
View File

@ -0,0 +1,80 @@
<!--
~ Copyright (c) 2023. Author Hubert Formin <2399270194@qq.com>
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Print preview</title>
<link rel="stylesheet" href="./work_print.css" />
</head>
<body>
<div id="app">
<div class="print_view">
<div class="title t1">{{data.merchantName}}</div>
<div class="title t2">交班小票</div>
<div class="row">交班时间:{{data.startTime}}</div>
<div class="row">收银员:{{data.staff}}</div>
<div class="row">当班收入:{{data.totalAmount}}</div>
<div class="row" v-for="(item,index) in data.payInfos" :key="index">
&emsp;&emsp;{{item.payType }}{{item.amount}}
</div>
<div class="row">会员数据</div>
<div class="row" v-for="(item,index) in data.memberData" :key="index">
&emsp;&emsp;{{item.deposit }}{{item.amount}}
</div>
<div class="row">分类数据</div>
<div
class="row"
v-for="(item,index) in data.productCategories"
:key="index"
>
&emsp;&emsp;{{item.categoryName
}}&emsp;{{item.num}}&emsp;{{item.amount}}
</div>
<div class="row">快捷收款金额:{{data.quickAmount}}</div>
<div class="row">退款金额:{{data.returnAmount}}</div>
<div class="row">总收入:{{data.totalAmount}}</div>
<div class="row">备用金:{{data.imprest}}</div>
<div class="row">应交金额:{{data.payable}}</div>
<div class="row">上交金额:{{data.handIn}}</div>
<div class="empty"></div>
<div class="row">总订单数:{{data.orderNum}}</div>
<div class="row">打印时间:{{data.printTime}}</div>
</div>
</div>
<script type="module">
const { ipcRenderer } = require("electron");
import {
createApp,
ref,
onMounted,
} from "../node_modules/vue/dist/vue.esm-browser.js";
createApp({
setup() {
const data = ref({});
onMounted(() => {
ipcRenderer.on("getParams", (event, arg) => {
data.value = JSON.parse(arg);
console.log(data.value);
setTimeout(() => {
ipcRenderer.send(
"printWorkStart",
JSON.stringify({ deviceName: data.value.deviceName })
);
}, 500);
});
});
return {
data,
};
},
}).mount("#app");
</script>
</body>
</html>

74
public/work_print.scss Normal file
View File

@ -0,0 +1,74 @@
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
padding: 0 8mm;
}
.empty {
height: 20px;
}
.print_view {
padding: 20px 0;
.title {
display: flex;
justify-content: center;
margin-bottom: 4px;
&.t1 {
font-size: 24px;
}
&.t2 {
margin-bottom: 15px;
}
}
.row {
margin-top: 2px;
font-size: 12px;
&.between {
display: flex;
justify-content: space-between;
}
}
.line {
margin: 10px 0;
border-bottom: 1px solid #000;
}
.table {
width: 100%;
tr {
width: 100%;
display: flex;
&:not(:last-child) {
margin-bottom: 10px;
}
td {
flex: 1;
font-size: 12px;
&:nth-child(1) {
flex: 2;
}
&:not(:first-child) {
display: flex;
justify-content: flex-end;
}
.sku {
font-size: 10px;
}
}
}
}
}

View File

@ -1,19 +1,20 @@
<template>
<el-config-provider size="large">
<div class="container">
<div class="left" v-if="route.path !== 'login'">
<left-menu />
<div class="left" v-if="!hideLeftMenu">
<left-menu ref="leftMenuRef" />
</div>
<div class="view">
<router-view />
<div :class="{ view: !hideLeftMenu }">
<!-- <div class="wrapper">
<div class="animation">
<router-view v-slot="{ Component }">
<transition :name="transitionName">
<component :is="Component"></component>
</transition>
</router-view>
</div>
<div class="animation"> -->
<router-view v-slot="{ Component }">
<!-- <transition :name="transitionName"> -->
<keep-alive :include="includeList">
<component :is="Component"></component>
</keep-alive>
<!-- </transition> -->
</router-view>
<!-- </div>
</div> -->
</div>
</div>
@ -21,12 +22,42 @@
</template>
<script setup>
import leftMenu from '@/components/leftMenu.vue'
import _ from 'lodash'
import { ref, reactive, watch, onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import { ref } from "vue";
import leftMenu from "@/components/leftMenu.vue";
import useStorage from "@/utils/useStorage";
import { useUser } from "@/store/user.js";
import { dayjs, ElMessage, ElMessageBox } from "element-plus";
import { scanSendMessage } from "@/api/order/index";
import { useGlobal } from "@/store/global.js";
import { useSocket } from "@/store/socket.js";
import { ipcRenderer } from 'electron';
const socket = useSocket();
const route = useRoute()
const global = useGlobal();
const leftMenuRef = ref(null);
const store = useUser();
const route = useRoute();
const includeList = reactive([]);
const hideLeftMenu = ref(false);
watch(route, (to) => {
// console.log(to);
if (to.meta.keepAlive) {
includeList.push(to.name);
}
//
let arr = ["/login", "/device_list", "/add_device", "/add_label", "/webview", '/workrecord'];
if (arr.includes(to.path)) {
hideLeftMenu.value = true;
} else {
hideLeftMenu.value = false;
}
});
let transitionName = ref();
let router = useRouter();
@ -42,6 +73,126 @@ router.beforeEach((to, from) => {
transitionName.value = "";
}
});
//
const nextCodeRef = ref("");
const lastTimeRef = ref("");
const codeRef = ref("");
async function getBarCode(e) {
let nextCode = "";
let nextTime = "";
const lastTime = lastTimeRef.value;
let code = codeRef.value;
if (window.event) {
// IE
nextCode = e.keyCode;
} else if (e.which) {
// Netscape/Firefox/Opera
nextCode = e.which;
}
nextTime = new Date().getTime();
// 0-9 48-57; 0-9 96-105
if (
(nextCode >= 48 && nextCode <= 57) ||
(nextCode >= 96 && nextCode <= 105)
) {
const codes = {
48: 48,
49: 49,
50: 50,
51: 51,
52: 52,
53: 53,
54: 54,
55: 55,
56: 56,
57: 57,
96: 48,
97: 49,
98: 50,
99: 51,
100: 52,
101: 53,
102: 54,
103: 55,
104: 56,
105: 57,
};
nextCode = codes[nextCode];
nextTime = new Date().getTime();
}
//
if (nextTime && lastTime && nextTime - lastTime > 2000) {
code = String.fromCharCode(nextCode);
} else {
code += String.fromCharCode(nextCode);
}
//
nextCodeRef.value = nextCode;
lastTimeRef.value = nextTime;
codeRef.value = code;
// Enter
if (e.which === 13) {
// code
code = code.trim();
if (code.length == 13) {
console.log("A类条码:" + code);
} else if (code.length == 23) {
console.log("B类条码:" + code);
} else if (code.length == 0) {
console.log("请输入条码");
} else {
console.log("条码不合法:" + code);
try {
if (!global.isCallNumber || !code.length) return;
await scanSendMessage({
outNumber: code,
shopId: store.userInfo.shopId,
});
ElMessage.success("叫号成功");
leftMenuRef.value.updateCallNumber();
} catch (error) {
console.log(error);
}
}
// console.log('code', code);
// code
codeRef.value = "";
return false;
}
}
onMounted(() => {
document.addEventListener("keydown", (e) => {
getBarCode(e);
});
//
if (store.userInfo && store.userInfo.shopId) {
socket.init();
}
ipcRenderer.on('showCloseDialog', (event, arg) => {
ElMessageBox.confirm("确定要关闭软件吗?")
.then(() => {
ipcRenderer.send("quitHandler", "退出吧");
})
.catch(() => { });
})
window.addEventListener('online', function () {
console.log('有网络了');
this.location.reload()
})
window.addEventListener('offline', function () {
ElMessage.warning('网络异常')
socket.close()
})
});
</script>
<style lang="scss">
@ -53,23 +204,149 @@ router.beforeEach((to, from) => {
}
:root {
--primary-color: #2FAFA2;
--r: 24;
--g: 124;
--b: 170;
--r-lighter: calc(var(--r) + (255 - var(--r)) * 0.2);
--g-lighter: calc(var(--g) + (255 - var(--g)) * 0.2);
--b-lighter: calc(var(--b) + (255 - var(--b)) * 0.2);
--r-lighter2: calc(var(--r) + (255 - var(--r)) * 0.5);
--g-lighter2: calc(var(--g) + (255 - var(--g)) * 0.5);
--b-lighter2: calc(var(--b) + (255 - var(--b)) * 0.5);
--r-lighter3: calc(var(--r) + (255 - var(--r)) * 0.9);
--g-lighter3: calc(var(--g) + (255 - var(--g)) * 0.9);
--b-lighter3: calc(var(--b) + (255 - var(--b)) * 0.9);
--r-darker: calc(var(--r) * 0.8);
--g-darker: calc(var(--g) * 0.8);
--b-darker: calc(var(--b) * 0.8);
--primary-color: rgb(var(--r), var(--g), var(--b));
--primary-color-hover: rgb(var(--r-lighter3),
var(--g-lighter3),
var(--b-lighter3));
--el-color-primary: var(--primary-color) !important;
--el-button-hover-bg-color: var(--primary-color) !important;
--el-color-primary-light-3: lighten(var(--primary-color), 20%) !important;
--el-color-primary-dark-2: darken(var(--primary-color), 20%) !important;
--el-color-primary-light-3: rgb(var(--r-lighter),
var(--g-lighter),
var(--b-lighter)) !important;
--el-color-primary-dark-2: rgb(var(--r-darker),
var(--g-darker),
var(--b-darker)) !important;
--el-color-primary-light-5: rgb(var(--r-lighter2),
var(--g-lighter2),
var(--b-lighter2)) !important;
--el-font-size-base: 16px !important;
--el-message-close-size: var(--el-font-size-base) !important;
--el-component-size-large: 40px !important;
--el-mask-color: rgba(255, 255, 255, 0.6) !important;
}
@font-face {
font-family: "num";
src: url("@/assets/font/Ignotum-Regular.ttf");
}
html {
font-size: var(--el-font-size-base);
color: #333;
}
.el-divider__text {
white-space: nowrap;
}
.el-dialog__headerbtn {
top: 10px !important;
}
.el-pagination {
justify-content: center;
}
.el-drawer__header {
// padding: 0 !important;
margin-bottom: 5px !important;
}
.el-table .warning-row {
--el-table-tr-bg-color: var(--el-color-warning-light-9);
}
.el-table .success-row {
--el-table-tr-bg-color: var(--el-color-success-light-9);
}
.el-drawer__body {
padding: 0 var(--el-drawer-padding-primary) !important;
}
.el-textarea {
font-size: var(--el-font-size-base) !important;
}
.el-popover__title {
font-size: var(--el-font-size-base) !important;
}
.el-dialog__header {
background-color: #555;
margin-right: 0 !important;
padding-bottom: 20px !important;
border-radius: var(--el-dialog-border-radius) var(--el-dialog-border-radius) 0 0;
}
.el-dialog__title {
color: #fff !important;
}
.el-button--large {
--el-button-size: var(--el-component-size-large) !important;
font-size: var(--el-font-size-base) !important;
}
.el-input--large {
font-size: var(--el-font-size-base) !important;
}
.el-dialog {
padding: 0 !important;
}
.el-dialog__body {
padding: calc(var(--el-dialog-padding-primary) + 10px) var(--el-dialog-padding-primary);
}
.el-dialog__header {
padding: var(--el-dialog-padding-primary);
padding-bottom: 10px;
margin-right: 16px;
}
.el-input__suffix {
font-size: 20px !important;
}
.empty {
display: flex;
justify-content: center;
padding-top: 100px;
}
/*
高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar {
width: 4px;
height: 2px;
}
/*
内阴影+圆角*/
::-webkit-scrollbar-track {
background-color: #F5F5F5;
background-color: #f5f5f5;
}
/*
@ -79,6 +356,21 @@ router.beforeEach((to, from) => {
background-color: #d3d3d3;
}
.scroll-x {
&::-webkit-scrollbar {
width: 0;
height: 0;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
border-radius: 0;
background-color: transparent;
}
}
.mt50 {
margin-top: 50px;
@ -90,7 +382,7 @@ router.beforeEach((to, from) => {
.view {
flex: 1;
height: 100vh;
padding: 20px;
padding: var(--el-font-size-base);
background-color: #efefef;
}
}

77
src/api/device.js Normal file
View File

@ -0,0 +1,77 @@
import request from "@/utils/request.js";
/**
* 新增打印机
* @param {*} data
* @returns
*/
export function tbPrintMachinePost(data, method = "post") {
return request({
method: method,
url: "tbPrintMachine",
data,
});
}
/**
* 分页查询打印机
* @param {*} params
* @returns
*/
export function tbPrintMachineGet(params) {
return request({
method: "get",
url: "/tbPrintMachine",
params,
});
}
/**
* 通过主键查询打印机
* @param {*} params
* @returns
*/
export function tbPrintMachineDetail(id) {
return request({
method: "get",
url: `/tbPrintMachine/${id}`,
});
}
/**
* 删除打印机
* @param {*} data
* @returns
*/
export function tbPrintMachineDelete(params) {
return request({
method: "DELETE",
url: "tbPrintMachine",
params,
});
}
/**
* 根据类型查询打印机列表
* @param {*} params
* @returns
*/
export function bySubType(params) {
return request({
method: "get",
url: "/tbPrintMachine/bySubType",
params,
});
}
/**
* 商品分类列表
* @returns
*/
export function tbShopCategoryGet(params) {
return request({
url: `/product/queryAllCategory`,
method: "get",
params,
});
}

244
src/api/group.js Normal file
View File

@ -0,0 +1,244 @@
import request from "@/utils/request.js";
import request_php from "@/utils/request_php.js";
/**
* 团购卷订单列表(分页)
* @param {*} data
* @returns
*/
export function groupOrderlist(data) {
return request({
method: "post",
url: "/groupOrder/list",
data,
});
}
/**
* 团购卷核销前回显
* @param {*} params
* @returns
*/
export function groupOrderorderInfo(params) {
return request({
method: "get",
url: "/groupOrder/orderInfo",
params,
});
}
/**
* 团购卷核销(仅核销待使用订单)
* @param {*} params
* @returns
*/
export function groupOrdergroupScan(params) {
return request({
method: "get",
url: "/groupOrder/groupScan",
params,
});
}
/**
* 退单
* @param {*} data
* @returns
*/
export function returnGpOrder(data) {
return request({
method: "post",
url: "/pay/returnGpOrder",
data,
});
}
// 注意 抖音核销使用的请求为PHP服务 request_php
/**
* 会员签入
* @param {*} data
* @returns
*/
export function douyincheckIn(data) {
return request_php({
method: "post",
url: "douyin/checkIn",
data,
});
}
/**
* 团购核销准备
* @param {*} data
* @returns
*/
export function douyinfulfilmentcertificateprepare(data) {
return request_php({
method: "post",
url: "douyin/fulfilmentcertificateprepare",
data,
});
}
/**
* 团购核销
* @param {*} data
* @returns
*/
export function douyincertificateprepare(data) {
return request_php({
method: "post",
url: "douyin/certificateprepare",
data,
});
}
/**
* 团购核销记录
* @param {*} data
* @returns
*/
export function douyinorderlist(data) {
return request_php({
method: "post",
url: "douyin/orderlist",
data,
});
}
/**
* 团购核销撤销
* @param {*} data
* @returns
*/
export function douyinfulfilmentcertificatecancel(data) {
return request_php({
method: "post",
url: "douyin/fulfilmentcertificatecancel",
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,
});
}

70
src/api/member/index.js Normal file
View File

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

129
src/api/order/index.js Normal file
View File

@ -0,0 +1,129 @@
import request from "@/utils/request.js";
/**
* 获取订单列表
* @param {*} params
* @returns
*/
export function orderfindOrder(params) {
return request({
method: "get",
url: "order/findOrder",
params,
});
}
/**
* 订单详情
* @param {*} params
* @returns
*/
export function orderorderDetail(params) {
return request({
method: "get",
url: "order/orderDetail",
params,
});
}
/**
* 退单
* @param {*} params
* @returns
*/
export function payreturnOrder(data, pwd, isOnline) {
return request({
method: "post",
url: `pay/returnOrder?pwd=${pwd}&isOnline=${isOnline}`,
data,
});
}
/**
* 打印
* @param {*} params
* @returns
*/
export function cloudPrinterprint(params) {
return request({
method: "get",
url: "cloudPrinter/print",
params,
});
}
/**
* 查询快捷收银订单
* @param {*} params
* @returns
*/
export function queryQuickPay(params) {
return request({
method: "get",
url: "pay/queryQuickPay",
params,
});
}
/**
* 叫号
* @param {*} params
* @returns
*/
export function sendMessage(params) {
return request({
method: "get",
url: "/order/sendMessage",
params,
});
}
/**
* 扫码叫号
* @param {*} params
* @returns
*/
export function scanSendMessage(params) {
return request({
method: "get",
url: "/order/scanSendMessage",
params,
});
}
/**
* 获取叫号记录
* @param {*} params
* @returns
*/
export function getsendMessage(params) {
return request({
method: "get",
url: "/order/getsendMessage",
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,
});
}

170
src/api/pay.js Normal file
View File

@ -0,0 +1,170 @@
import request from "@/utils/request.js";
/**
* 查询分类信息
* @param {*} params
* @returns
*/
export function queryPayType(params) {
return request({
method: "get",
url: "pay/queryPayType",
params,
});
}
/**
* 付款
* @param {*} data
* @returns
*/
export function payOrder(api, data) {
return request({
method: "post",
url: api,
data,
});
}
/**
* 扫码支付
* @param {*} data
* @returns
*/
export function scanpay(data) {
return request({
method: "post",
url: "pay/scanpay",
data,
});
}
/**
* 储值卡付款
* @param {*} data
* @returns
*/
export function accountPay(data) {
return request({
method: "post",
url: "pay/accountPay",
data,
});
}
/**
* 现金付款
* @param {*} data
* @returns
*/
export function cashPay(data) {
return request({
method: "post",
url: "pay/cashPay",
data,
});
}
/**
* 查询订单支付状态
* @param {*} params
* @returns
*/
export function queryOrder(params) {
return request({
method: "get",
url: "pay/queryOrder",
params,
});
}
/**
* 打印
* @param {*} params
* @returns
*/
export function print(params) {
return request({
method: "get",
url: "cloudPrinter/print",
params,
});
}
/**
* 快捷收款
* @param {*} params
* @returns
*/
export function quickPay(params) {
return request({
method: "get",
url: "pay/quickPay",
params,
});
}
/**
* 查询快捷收银订单
* @param {*} params
* @returns
*/
export function queryQuickPayStatus(params) {
return request({
method: "get",
url: "pay/queryQuickPayStatus",
params,
});
}
/**
* 获取会员支付状态查询
* @param {*} params
* @returns
*/
export function queryScanPay(params) {
return request({
method: "get",
url: "member/queryScanPay",
params,
});
}
/**
* 会员余额支付
* @param {*} data
* @returns
*/
export function vipPay(data) {
return request({
method: "post",
url: "/pay/vipPay",
data,
});
}
/**
* 挂账人-分页
* @param {*} params
* @returns
*/
export function buyerPage(params) {
return request({
method: "get",
url: "/credit/buyer/page",
params,
});
}
/**
* 挂账支付
* @param {*} data
* @returns
*/
export function payCreditPay(data) {
return request({
method: "post",
url: "/pay/creditPay",
data,
});
}

261
src/api/product.js Normal file
View File

@ -0,0 +1,261 @@
import request from "@/utils/request.js";
/**
* 查询分类信息
* @param {*} params
* @returns
*/
export function queryCategory(params) {
return request({
method: "get",
url: "product/queryCategory",
params,
});
}
/**
* 查询商品信息
* @param {*} params
* @returns
*/
export function productqueryCommodityInfo(params) {
return request({
method: "get",
url: "product/queryCommodityInfo",
params,
});
}
/**
* 查询商品信息
* @param {*} params
* @returns
*/
export function queryNewCommodityInfo(params) {
return request({
method: "get",
url: "product/queryNewCommodityInfo",
params,
});
}
/**
* 通过选中的商品规格查询价格
* @param {*} params
* @returns
*/
export function queryProductSku(params) {
return request({
method: "get",
url: "product/queryProductSku",
params,
});
}
/**
* 添加购物车
* @param {*} params
* @returns
*/
export function createCart(data) {
return request({
method: "post",
url: "/order/createCart",
data,
});
}
/**
* 获取购物车商品
* @param {*} params
* @returns
*/
export function queryCart(params) {
return request({
method: "get",
url: "order/queryCart",
params,
});
}
/**
* 获取取件码
* @param {*} params
* @returns
*/
export function createCode(params) {
return request({
method: "get",
url: "/order/createCode",
params,
});
}
/**
* 全部打包
* @param {*} params
* @returns
*/
export function packall(data) {
return request({
method: "post",
url: "/order/packall",
data,
});
}
/**
* 删除购物车
* @param {*} params
* @returns
*/
export function delCart(params) {
return request({
method: "get",
url: "/order/delCart",
params,
});
}
/**
* 挂单/j激活购物车
* @param {*} params
* @returns
*/
export function cartStatus(data) {
return request({
method: "post",
url: "/order/cartStatus",
data,
});
}
/**
* 获取挂起购物车列表
* @param {*} params
* @returns
*/
export function getCartList(params) {
return request({
method: "get",
url: "/order/getCartList",
params,
});
}
/**
* 清空购物车
* @param {*} params
* @returns
*/
export function clearCart(data) {
return request({
method: "post",
url: "/order/clearCart",
data,
});
}
/**
* 创建订单
* @param {*} data
* @returns
*/
export function createOrder(data) {
return request({
method: "post",
url: "/order/createOrder",
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,
});
}
/**
* 转台/并台
* @param {*} data
* @returns
*/
export function orderSwitcht(data) {
return request({
method: "PUT",
url: "/order/switch",
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,
});
}

53
src/api/table.js Normal file
View File

@ -0,0 +1,53 @@
import request from "@/utils/request.js";
/**
* 查询台桌分类
* @param {*} params
* @returns
*/
export function queryShopArea(params) {
return request({
method: "get",
url: "shopInfo/queryShopArea",
params,
});
}
/**
* 查询台桌信息
* @param {*} params
* @returns
*/
export function queryShopTable(params) {
return request({
method: "get",
url: "shopInfo/queryShopTable",
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,
});
}

81
src/api/user.js Normal file
View File

@ -0,0 +1,81 @@
import request from "@/utils/request.js";
import { ElMessage } from "element-plus";
export function login(data) {
return request({
method: "post",
url: "login/login",
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();
}
}

66
src/api/work/index.js Normal file
View File

@ -0,0 +1,66 @@
import request from "@/utils/request.js";
/**
* 当前用户交班详情
* @param {*} params
* @returns
*/
export function shopInfoqueryDuty(params) {
return request({
method: "get",
url: "shopInfo/queryDuty",
params,
});
}
/**
* 交班记录
* @param {*} params
* @returns
*/
export function shopinfoqueryDutyFlow(params) {
return request({
method: "get",
url: "shopInfo/queryDutyFlow",
params,
});
}
/**
* 登出
* @param {*} params
* @returns
*/
export function loginlogout(params) {
return request({
method: "post",
url: "login/logout",
params,
});
}
/**
* 获取交班数据
* @param {*} params
* @returns
*/
export function handoverData(params) {
return request({
method: "get",
url: "/data/handoverData",
params,
});
}
/**
* 打印交班数据
* @param {*} params
* @returns
*/
export function handoverprint(params) {
return request({
method: "get",
url: "data/handoverprint",
params,
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

BIN
src/assets/icon_dev1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
src/assets/icon_dev2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
src/assets/icon_dev3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

BIN
src/assets/icon_scan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/assets/icon_xq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,289 @@
<!-- 取餐号组件 -->
<template>
<el-dialog :title="props.title" width="600" v-model="dialogVisible" @open="opne">
<!-- <el-input v-model="number" :placeholder="props.placeholder" readonly></el-input> -->
<!-- <el-input ref="inputRef" v-model="number" :placeholder="props.placeholder" :readonly="loading" clearable
@change="inputChange"></el-input> -->
<div class="tips">注意请扫描标签二维码</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" disabled style="width: 100%;">.</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="list" v-loading="tableData.loading">
<div class="item" v-for="item in tableData.list" :key="item.id">
<div class="top">
<div class="left">
<div class="title">取餐号{{ filterCode(item.outCode) }}</div>
<div class="shop">商品{{ item.productName }}</div>
</div>
<div class="state s1" v-if="item.status == 0">
<div class="dot"></div> 叫号成功
</div>
<div class="state s2" v-if="item.status == 1">
<div class="dot"></div> 叫号失败
</div>
</div>
<div class="btm">
<div class="time">{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
<div class="info" v-if="item.status == 1">
{{ item.remark }}
</div>
</div>
</div>
<el-empty description="暂无记录" :image-size="60" v-if="!tableData.list.length" />
</div>
<div class="page">
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.pageSize" background
layout="total, prev, pager, next" :total="tableData.total" @current-change="handleCurrentChange" />
</div>
<!-- <div class="footer">
<el-button type="primary" style="width: 100%;" :loading="loading" @click="confirmHandle">确认</el-button>
</div> -->
</el-dialog>
</template>
<script setup>
import _ from "lodash";
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue';
import { useUser } from "@/store/user.js";
import { scanSendMessage, getsendMessage } from '@/api/order/index'
import { useGlobal } from '@/store/global.js'
const global = useGlobal()
const store = useUser();
const props = defineProps({
title: {
type: String,
default: '叫号取餐记录'
},
placeholder: {
type: String,
default: '请扫描取餐号'
}
})
const inputRef = ref(null)
const loading = ref(false)
const dialogVisible = ref(false)
const number = ref('')
const tableData = reactive({
loading: false,
page: 1,
pageSize: 10,
total: 0,
list: []
})
const emit = defineEmits(['success'])
//
function filterCode(t, c = '#') {
let n = t.split(c)
return n[1]
}
function show() {
dialogVisible.value = true
getsendMessageAjax()
// setTimeout(() => {
// inputRef.value.focus();
// }, 500);
}
function opne() {
number.value = ''
global.updateData(true)
}
//
function inputHandle(n) {
number.value += n
}
//
function delHandle() {
if (!number.value) return
number.value = number.value.substring(0, number.value.length - 1)
}
const inputChange = _.debounce(function (e) {
// console.log(e);
confirmHandle();
}, 100);
//
function handleCurrentChange() {
getsendMessageAjax()
}
//
async function getsendMessageAjax() {
try {
if (!store.userInfo.shopId) return
tableData.loading = true
const res = await getsendMessage({
page: tableData.page,
pageSize: tableData.pageSize,
shopId: store.userInfo.shopId,
// shopId: 4,
})
tableData.loading = false
tableData.list = res.list
tableData.total = res.total
} catch (error) {
console.log(error);
tableData.loading = false
}
}
//
const confirmHandle = _.throttle(async function () {
try {
if (!number.value) return
loading.value = true
const res = await scanSendMessage({
outNumber: number.value,
shopId: store.userInfo.shopId,
// shopId: 4
})
ElMessage.success('叫号成功')
loading.value = false
number.value = ''
getsendMessageAjax()
setTimeout(() => {
inputRef.value.focus();
}, 500);
} catch (error) {
getsendMessageAjax()
loading.value = false
number.value = ''
console.log(error);
setTimeout(() => {
inputRef.value.focus();
}, 500);
}
}, 800, { leading: true, trailing: false })
defineExpose({
show,
getsendMessageAjax
})
</script>
<style scoped lang="scss">
:deep(.el-input__inner) {
height: 60px;
font-size: 36px;
}
.page {
display: flex;
padding-bottom: 20px;
}
.tips {
padding-top: 4px;
}
.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: 60px;
}
}
.list {
height: 200px;
margin-top: 20px;
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 4px;
overflow-y: auto;
padding: 0 14px;
.item {
padding: 16px 0;
&:not(:last-child) {
border-bottom: 1px solid #ececec;
}
.top {
display: flex;
justify-content: space-between;
.left {
color: #000;
.title {
font-weight: bold;
}
.shop {
color: #555;
}
}
.state {
display: flex;
align-items: center;
.dot {
$size: 6px;
width: $size;
height: $size;
border-radius: 50%;
margin-right: 4px;
}
&.s1 {
color: var(--primary-color);
.dot {
background-color: var(--primary-color);
}
}
&.s2 {
color: var(--el-color-error);
.dot {
background-color: var(--el-color-error);
}
}
}
}
.btm {
display: flex;
justify-content: space-between;
padding-top: 4px;
.time,
.info {
color: #999;
}
}
}
}
</style>

View File

@ -0,0 +1,127 @@
<template>
<el-dialog title="选择分类" v-model="dialogVisible">
<div class="list" v-loading="loading">
<div class="row" v-for="(item, index) in categorys" :key="item.id">
<div class="list_title">{{ item.name }}</div>
<div class="item_wrap">
<el-button :type="item.active ? 'primary' : ''" @click="selectHandle(item)">全部</el-button>
<el-button :type="val.active ? 'primary' : ''" v-for="val in item.childrenList" :key="val.id"
@click="selectHandle(val, index)">{{ val.name }}</el-button>
</div>
</div>
</div>
<span slot="footer" style="display: flex; justify-content: flex-end">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="onSubmitHandle"> </el-button>
</span>
</el-dialog>
</template>
<script setup>
import { tbShopCategoryGet } from "@/api/device";
import { onMounted, ref } from "vue";
import { useUser } from "@/store/user.js";
const store = useUser();
const emit = defineEmits(["success"]);
const dialogVisible = ref(false);
const loading = ref(false);
const categorys = ref([]);
function onSubmitHandle() {
let categorysArr = [];
for (let item of categorys.value) {
if (item.active) {
categorysArr.push({
name: `${item.name}`,
id: item.id,
});
}
if (item.childrenList.length) {
for (let val of item.childrenList) {
if (val.active) {
categorysArr.push({
name: `${val.name}`,
id: val.id,
});
}
}
}
}
emit("success", categorysArr);
dialogVisible.value = false;
}
//
function selectHandle(item, index = -1) {
if (index != -1) {
categorys.value[index].active = false;
} else {
item.childrenList.map((item) => {
item.active = false;
});
}
item.active = !item.active;
}
//
async function tbShopCategoryGetAjax() {
try {
loading.value = true;
const res = await tbShopCategoryGet({
shopId: store.userInfo.shopId,
sort: "sort,desc",
page: 0,
pageSize: 200,
});
// console.log(res);
res.list.map((item) => {
item.active = false;
item.childrenList.map((item) => {
item.active = false;
});
});
categorys.value = res.list;
setTimeout(() => {
loading.value = false;
}, 300);
} catch (error) {
console.log(error);
loading.value = false;
}
}
function show() {
dialogVisible.value = true;
tbShopCategoryGetAjax();
}
defineExpose({
show,
});
onMounted(() => {
tbShopCategoryGetAjax();
});
</script>
<style scoped lang="scss">
.list {
min-height: 200px;
.row {
padding-bottom: 20px;
.list_title {
color: #999;
padding-bottom: 10px;
}
.item_wrap {
display: flex;
}
}
}
</style>

View File

@ -0,0 +1,282 @@
<template>
<div class='keyboard' @click.stop='_handleKeyPress'>
<div class='key-row'>
<div class='key-cell cell_b' data-num='7'>7</div>
<div class='key-cell cell_b' data-num='8'>8</div>
<div class='key-cell cell_b' data-num='9'>9</div>
<div class='key-cell cell_b' data-num='-1'></div>
</div>
<div class='key-row'>
<div class='key-cell cell_b' data-num='4'>4</div>
<div class='key-cell cell_b' data-num='5'>5</div>
<div class='key-cell cell_b' data-num='6'>6</div>
<div class='key-cell cell_b' data-num='-1'></div>
</div>
<div class='key-row'>
<div class='key-cell cell_b' data-num='1'>1</div>
<div class='key-cell cell_b' data-num='2'>2</div>
<div class='key-cell cell_b' data-num='3'>3</div>
<div class='key-cell cell_b' data-num='-1'></div>
</div>
<div class="key-zero-and-point">
<div class="a cell_b zero" data-num='0'>0</div>
<div class="a cell_b point" data-num='.'>.</div>
</div>
<div @touchstart="touchstart" @touchend="touchend" data-num='D' class="key-confirm2">
<text data-num='D'>C</text>
</div>
<div class='key-confirm' :style="{ 'background': btnColor }" data-num='S'>
<div data-num='S' class="">
<div data-num='S' class="title">{{ title }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, reactive } from 'vue'
import { dayjs } from 'element-plus'
const props = defineProps({
title: {
default: '确认',
type: String
},
btnColor: {
default: 'green',
}
})
const emit = defineEmits(["consumeFee", "confirmEvent"])
const this_money = reactive({
money: '',
Cdel: '',
Time: ''
})
watch(() => this_money.money, (newval, oldval) => {
emit('consumeFee', newval)
});
const touchstart = () => {
this_money.Time = setInterval(() => {
console.log(this_money.money);
if (this_money.money == '') {
clearInterval();
}
this_money.money = this_money.money.substring(0, this_money.money.length - 1);
}, 200)
}
const touchend = () => {
clearInterval(this_money.Time);
}
//
const _handleKeyPress = (e) => {
let num = e.target.dataset.num;
//
// -1
if (num == -1) return false;
switch (String(num)) {
//
case '.':
_handleDecimalPoint();
break;
//
case 'D':
_handleDeleteKey();
break;
//
case 'C':
_handleClearKey();
break;
//
case 'S':
_handleConfirmKey();
break;
default:
_handleNumberKey(num);
break;
}
}
//
const _handleDecimalPoint = () => {
//
if (this_money.money.indexOf('.') > -1) return false;
//0
if (!this_money.money.length)
this_money.money = '0.';
//
else
this_money.money = this_money.money + '.';
}
//
const _handleDeleteKey = () => {
let S = this_money.money;
//
if (!S.length) return false;
//
this_money.money = S.substring(0, S.length - 1);
}
//
const _handleClearKey = () => {
this_money.money = '';
}
//
const _handleNumberKey = (num) => {
if (this_money.money.length == 10) {
return
}
let S = this_money.money;
//2
if (S.indexOf('.') > -1 && S.substring(S.indexOf('.') + 1).length < 2)
this_money.money = S + num;
//
if (!(S.indexOf('.') > -1)) {
//0
if (num == 0 && S.length == 0)
this_money.money = '0.';
else {
if (S.length && Number(S.charAt(0)) === 0) return;
this_money.money = S + num;
}
}
}
//
const _handleConfirmKey = () => {
let S = this_money.money;
//
if (!S.length || S == 0) {
uni.showToast({
title: '请输入正确的数值',
icon: 'none',
duration: 1000
});
return false;
}
// 8. 8.00
if (S.indexOf('.') > -1 && S.indexOf('.') == (S.length - 1))
S = Number(S.substring(0, S.length - 1)).toFixed(2);
//
S = Number(S).toFixed(2);
emit('confirmEvent', S); //
}
</script>
<style lang="scss" scoped>
.cell_b {
border-right: 1px solid #d5d5d6;
border-bottom: 1px solid #d5d5d6;
}
.key-container {
width: 100%;
display: flex;
flex-direction: column;
}
.keyboard {
flex: 1;
position: relative;
bottom: 0;
left: 0;
height: 40vh;
width: 100%;
background: #FFFFFF;
}
.keyboard .key-row {
display: flex;
display: -webkit-flex;
position: relative;
height: 10vh;
line-height: 10vh;
}
.keyboard .key-cell {
flex: 1;
-webkit-box-flex: 1;
font-size: 60upx;
display: flex;
justify-content: center;
align-items: center;
}
.keyboard .key-confirm {
position: absolute;
text-align: center;
height: 30vh;
width: 25%;
line-height: 30vh;
color: #FFFFFF;
z-index: 5;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
.keyboard .key-confirm2 {
position: absolute;
height: 10vh;
width: 25%;
line-height: 10vh;
z-index: 9999;
right: 0;
top: 0;
display: flex;
justify-content: center;
align-items: center;
}
.key-zero-and-point {
display: flex;
height: 10vh;
justify-content: center;
align-items: center;
width: 75%;
font-size: 60upx;
.zero {
display: flex;
justify-content: center;
align-items: center;
width: 66.66%;
font-size: 60upx;
text-align: center;
height: 100%;
}
.point {
display: flex;
justify-content: center;
align-items: center;
width: 33.33%;
font-size: 60upx;
text-align: center;
height: 100%;
}
}
.key-cell:active {
color: white;
background: black; //
opacity: 0.1; //
}
.a:active,
.key-confirm2:active {
color: white;
background: black; //
opacity: 0.1; //
}
</style>

View File

@ -0,0 +1,407 @@
<template>
<div class="card">
<div class="header">
<div class="return" @click="returnHandle">
<el-icon class="icon">
<ArrowLeftBold />
</el-icon>
</div>
<div class="right">
<div class="t1" v-if="props.type == 0">
<span class="title">应收:</span>
<span class="num">{{ money }}</span>
</div>
<div class="t1" v-else>
<span class="title">会员:</span>
<span class="num">{{
props.userInfo.id && props.userInfo.telephone
}}</span>
</div>
<div class="t2">
<span>已付:0.00</span>
<span>优惠:0.00</span>
</div>
</div>
</div>
<div class="number_wrap">
<div class="menus">
<div class="item" :class="{ active: payActive == index }" v-for="(item, index) in payList" :key="item.id"
@click="payTypeChange(index, item)">
<div class="icon">
<el-image :src="item.icon" class="img"></el-image>
</div>
<span class="title">{{ item.payName }}</span>
</div>
</div>
<div class="input_wrap">
<div class="input" style="flex: 1">储值:{{ money }}</div>
</div>
<div class="blance">
<!-- 可用余额0.00 -->
</div>
<div class="keybord_wrap">
<div class="left">
<div class="item" v-for="item in 9" :key="item" @click="amountInput(`${item}`)">
{{ item }}
</div>
<div class="item" @click="amountInput('.')">.</div>
<div class="item" @click="amountInput('0')">0</div>
<div class="item" @click="delHandle">
<el-icon>
<CloseBold />
</el-icon>
</div>
</div>
<div class="pay_btn" v-loading="payLoading" @click="confirmOrder">
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<scanModal ref="scanModalRef" fast :amount="money" :money="money" :selecttype="props.type" :orderId="props.userInfo.id"
@success="scanCodeSuccess" />
<takeFoodCode ref="takeFoodCodeRef" title="支付密码" :type="2" input-type="password" placeholder="请输入支付密码"
@success="passwordSuccess" />
</template>
<script setup>
import { onMounted, ref } from "vue";
import { queryPayType, quickPay } from "@/api/pay";
import {
queryMembermember,
createMembermember,
membermemberScanPay,
accountPaymember,
} from "@/api/member/index.js";
import { useUser } from "@/store/user.js";
import { clearNoNum } from "@/utils";
import md5 from "js-md5";
import { queryPwdInfo } from '@/api/user.js'
import scanModal from "@/components/payCard/scanModal.vue";
import { ElMessage } from "element-plus";
import takeFoodCode from "@/components/takeFoodCode.vue";
const takeFoodCodeRef = ref(null);
const props = defineProps({
type: {
type: [String, Number],
default: 0, // 1 2
},
userInfo: {
type: Object,
default: {},
},
});
const store = useUser();
const emit = defineEmits(["paySuccess", "close"]);
const money = ref("0");
const scanModalRef = ref(null);
const payActive = ref(0);
const payList = ref([]);
const payLoading = ref(false);
function returnHandle() {
emit("close");
}
//
function scanCodeSuccess() {
emit("paySuccess");
}
//
function payTypeChange(index, item) {
if (item.payType == "scanCode") {
if (money.value > 0) {
scanModalRef.value.show();
} else {
ElMessage.error("请输入金额");
return;
}
}
payActive.value = index;
}
//
async function passwordSuccess(e = '') {
try {
payLoading.value = true;
await accountPaymember({
shopId: store.userInfo.shopId,
memberId: props.userInfo.id,
amount: money.value,
pwd: e ? md5(e) : '',
});
payLoading.value = false;
ElMessage.success("支付成功");
emit("paySuccess");
} catch (error) {
payLoading.value = false;
console.log(error);
}
}
//
async function confirmOrder() {
if (payLoading.value) return;
try {
if (payList.value[payActive.value].payType == "scanCode") {
if (money.value <= 0) {
ElMessage.error("请输入金额");
return;
}
scanModalRef.value.show();
} else {
if (money.value <= 0) {
ElMessage.error("请输入金额");
return;
}
switch (payList.value[payActive.value].payType) {
case "cash": //
if (props.type == 0) {
payLoading.value = true;
await quickPay({
amount: money.value,
authCode: "",
payType: payList.value[payActive.value].payType,
});
payLoading.value = false;
ElMessage.success("支付成功");
emit("paySuccess");
} else {
//
let res = await queryPwdInfo()
if (res.isMemberIn == 1) {
takeFoodCodeRef.value.show();
} else {
passwordSuccess()
}
// takeFoodCodeRef.value.show();
// // passwordSuccess()
}
break;
default:
break;
}
}
} catch (error) {
console.log(error);
payLoading.value = false;
scanModalRef.value.loading = false;
}
}
//
function amountInput(num) {
money.value = clearNoNum({ value: (money.value += num) });
}
//
function delHandle() {
if (!money.value) return;
money.value = money.value.substring(0, money.value.length - 1);
if (!money.value) {
money.value = "0";
}
}
//
async function queryPayTypeAjax() {
try {
const res = await queryPayType({
shopId: store.userInfo.shopId,
});
const arr = [];
res.map((item) => {
if (item.payType == "cash" || item.payType == "scanCode") {
arr.push(item);
}
});
payList.value = arr;
} catch (error) {
console.log(error);
}
}
function reset() {
money.value = 0;
payActive.value = 0;
}
defineExpose({ reset });
onMounted(() => {
queryPayTypeAjax();
});
</script>
<style scoped lang="scss">
.card {
padding: var(--el-font-size-base);
height: 100%;
}
.header {
padding-bottom: var(--el-font-size-base);
border-bottom: 1px solid #ececec;
display: flex;
align-items: center;
.return {
$size: 50px;
width: $size;
height: $size;
border-radius: 50%;
border: 2px solid #333;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
.icon {
color: #333;
font-size: var(--el-font-size-base);
}
}
.right {
.t1 {
display: flex;
color: var(--el-color-danger);
font-weight: bold;
.title {
font-size: var(--el-font-size-base);
position: relative;
top: 14px;
}
.num {
font-size: 30px;
}
}
.t2 {
display: flex;
gap: var(--el-font-size-base);
color: #999;
padding-top: 10px;
}
}
}
.number_wrap {
padding: var(--el-font-size-base) 0;
.menus {
display: flex;
gap: var(--el-font-size-base);
.item {
height: 130px;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #efefef;
padding: 10px 0;
border-radius: 10px;
position: relative;
$lineHeight: 4px;
&.active {
&::after {
content: "";
width: 50%;
height: $lineHeight;
background-color: var(--primary-color);
position: absolute;
bottom: 0;
left: 25%;
border-radius: $lineHeight;
}
}
.img {
$size: 40px;
width: $size;
height: $size;
}
.title {
padding-top: 10px;
}
}
}
.input_wrap {
display: flex;
gap: var(--el-font-size-base);
padding: var(--el-font-size-base) 0;
.input {
display: flex;
align-items: center;
height: 60px;
border-radius: 6px;
border: 1px solid var(--primary-color);
font-size: calc(var(--el-font-size-base) + 6px);
padding: 0 var(--el-font-size-base);
}
}
.blance {
color: var(--el-color-danger);
font-size: calc(var(--el-font-size-base) + 10px);
}
}
.keybord_wrap {
display: flex;
.left {
--item-height: calc((100vh - 440px) / 4);
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: var(--item-height) var(--item-height) var(--item-height) var(--item-height);
gap: var(--el-font-size-base);
.item {
background-color: #efefef;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
font-size: calc(var(--el-font-size-base) + 10px);
&:active {
background-color: #dbdbdb;
}
}
}
.pay_btn {
flex: 0.3;
border-radius: 6px;
color: #fff;
background-color: var(--el-color-warning);
margin-left: var(--el-font-size-base);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(var(--el-font-size-base) + 10px);
}
}
</style>

View File

@ -1,10 +1,17 @@
<template>
<div class="left_menu_wrap">
<div class="item online">
<div class="item first" :class="{ online: socketStore.online }" @click="connectWsHandle">
<el-icon class="icon">
<Monitor />
</el-icon>
<el-text type="success">在线</el-text>
<el-text :type="socketStore.online ? 'success' : 'danger'">
<template v-if="socketStore.online">
在线
</template>
<template v-else>
离线
</template>
</el-text>
</div>
<router-link class="item" :class="{ active: route.path == item.path }" v-for="item in menus" :key="item.path"
:to="item.path">
@ -13,20 +20,42 @@
</el-icon>
<el-text class="text">{{ item.label }}</el-text>
</router-link>
<div class="item more">
<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()">
<el-icon class="icon">
<Operation />
</el-icon>
<el-text class="text">更多</el-text>
</div>
</div>
<!-- 交班 -->
<work ref="workRef" />
<!-- 更多 -->
<more ref="moreref" @openCall="openCall"></more>
<!-- 叫号 -->
<callNumber ref="callNumberRef" />
</template>
<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { useSocket } from '@/store/socket.js'
import more from '@/components/more.vue'
import callNumber from './callNumber.vue'
import work from '@/views/work/index.vue'
const socketStore = useSocket()
const route = useRoute()
console.log(route.path)
const moreref = ref(null)
const callNumberRef = ref(null)
const workRef = ref(null)
const menus = ref([
{
label: '收银',
@ -38,33 +67,139 @@ const menus = ref([
path: '/table',
icon: 'Reading'
},
{
label: '团购',
path: '/group_buy',
icon: 'Handbag'
},
{
label: '订单',
path: '/order',
icon: 'Tickets'
},
{
label: '网络',
path: '/internat',
icon: 'Paperclip'
},
// {
// label: '',
// path: '/internat',
// icon: 'Paperclip'
// },
{
label: '会员',
path: '/user',
path: '/member',
icon: 'User'
},
{
label: '交班',
path: '/work',
icon: 'SwitchButton'
}
label: '排队',
path: '/queue',
icon: 'Timer'
},
// {
// label: '',
// path: '/work',
// icon: 'SwitchButton'
// }
])
//
function updateCallNumber() {
callNumberRef.value.getsendMessageAjax()
}
function openCall() {
callNumberRef.value.show()
}
// ws
function connectWsHandle() {
// if (socketStore.online) return
location.reload()
}
defineExpose({
updateCallNumber
})
</script>
<style scoped lang="scss">
.drawerbox {
:deep(.el-drawer__body) {
background: #1c1d1f !important;
}
.drawerbox_box {
color: #fff;
.drawerbox_bo_top {
padding: 20px 0;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #ccc;
.drawerbox_bo_top_left {
display: flex;
flex-direction: column;
}
.drawerbox_bo_top_ring {
display: flex;
align-items: center;
.drawerbox_bo_top_ring_tb {
display: flex;
flex-direction: column;
border: 1px solid #ccc;
border-radius: 6px;
padding: 6px 10px;
span {
width: 80px;
margin-top: 5px;
text-align: center;
}
}
}
}
.drawerbox_bo_box {
width: 100%;
.drawerbox_bo_box_itemb_felx {
display: flex;
justify-content: flex-start;
padding: 0 20px;
.drawerbox_bo_box_itembox:nth-child(1) {
margin-left: 0px;
}
.drawerbox_bo_box_itembox {
width: 20%;
display: flex;
flex-direction: column;
align-items: center;
margin-left: 40px;
.drawerbox_bo_box_icon {
border-radius: 6px;
background: #2196f3;
width: 100px;
height: 100px;
}
.drawerbox_bo_box_icontext {
margin-top: 10px;
}
}
}
}
}
}
.left_menu_wrap {
height: 100vh;
width: 120px;
width: 60px;
background-color: #555;
display: flex;
flex-direction: column;
@ -76,10 +211,14 @@ const menus = ref([
justify-content: center;
align-items: center;
text-decoration: none;
&:first-child {
border-bottom: 1px solid #666;
}
/* 去除下划线 */
color: inherit;
/* 继承父元素的颜色 */
cursor: pointer;
/* 修改鼠标指针样式 */
border: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent;
&.online {
@ -99,18 +238,18 @@ const menus = ref([
}
&.more {
margin-top: 30px;
margin-top: 90px;
}
.icon {
color: #999;
font-size: 32px;
font-size: 22px;
margin-bottom: 4px;
}
.text {
color: #999;
font-size: 22px;
font-size: var(--el-font-size-base);
}
}
}

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,144 @@
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:6px;">
桌号${data.orderInfo && data.orderInfo.tableName || ''}
</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,99 @@
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="font-size: 16px;display: flex; justify-content:center;margin-top:6px;">
桌号${data.orderInfo && data.orderInfo.tableName || ''}
</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);
};

242
src/components/more.vue Normal file
View File

@ -0,0 +1,242 @@
<template>
<div class="drawerbox">
<el-drawer size="60%" :with-header="false" direction="rtl" v-model="dialogVisible" style="padding: 0">
<div class="drawerbox_box">
<div class="drawerbox_bo_top">
<div class="drawerbox_bo_top_left" @click="computeExpired">
<div class="drawerbox_bo_top_left_one" style="font-size: 24px;">
{{ store.userInfo.shopName }}
</div>
<div class="tips" style="margin-top: 4px; color: var(--el-color-warning);" v-if="!showTips">注意您的账号将于{{
store.userInfo.expireDate }}后过期请尽快续期</div>
<div class="drawerbox_bo_top_left_tow" style="margin-top: 10px">
收银员{{ store.userInfo.loginAccount }}
</div>
<div>
<span style="color: #666">{{ dayjs(store.userInfo.loginTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</div>
</div>
<!-- <div class="drawerbox_bo_top_ring">
<div class="drawerbox_bo_top_ring_tb">
<el-icon style="margin: 0 auto;" size="20">
<FolderAdd />
</el-icon>
<span>修改密码</span>
</div>
<div class="drawerbox_bo_top_ring_tb" style="margin-left: 10px;">
<el-icon style="margin: 0 auto;" size="20">
<CopyDocument />
</el-icon>
<span>最小化</span>
</div>
</div> -->
</div>
<div class="drawerbox_bo_box">
<div style="padding: 10px; color: #999; font-weight: bold">系统</div>
<div class="drawerbox_bo_box_itemb_felx">
<!-- <div class="drawerbox_bo_box_itembox">
<div class="drawerbox_bo_box_icon">
<el-icon size="40">
<Setting />
</el-icon>
</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 class="drawerbox_bo_box_itembox" @click="openCallHandle">
<div class="drawerbox_bo_box_icon">
<el-icon size="40">
<Bell />
</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_icon">
<el-icon size="40">
<Lock />
</el-icon>
</div>
<div class="drawerbox_bo_box_icontext">锁屏</div>
</div>
<!-- <div class="drawerbox_bo_box_itembox" @click="to('webview', {
url: 'https://cashiernewadmin.sxczgkj.cn/',
title: '后台管理'
})">
<div class="drawerbox_bo_box_icon">
<el-icon size="40">
<Monitor />
</el-icon>
</div>
<div class="drawerbox_bo_box_icontext">
后台管理
</div>
</div> -->
</div>
</div>
</div>
<div class="boxabsolute">
<div>©银收客 v{{ packageData.version }}</div>
<!-- <div>
有效期
</div> -->
</div>
</el-drawer>
</div>
<screen ref="screenref"></screen>
</template>
<script setup>
import { ref } from "vue";
import { dayjs } from 'element-plus'
import { useRouter } from "vue-router";
import { useUser } from "@/store/user.js";
import screen from "@/components/screen.vue";
import packageData from "../../package.json";
const router = useRouter();
const emit = defineEmits(["openCall"]);
const store = useUser();
const screenref = ref(null);
const dialogVisible = ref(false);
const showTips = ref(false)
function show() {
dialogVisible.value = true;
computeExpired()
}
// 30
function computeExpired() {
//
let now = dayjs()
//
let expired = dayjs(store.userInfo.expireDate).subtract(30, 'day')
// 30
showTips.value = now.isBefore(expired)
}
//
function openCallHandle() {
dialogVisible.value = false;
emit("openCall");
}
//
function to(pathName, data) {
router.push({
name: pathName,
query: data,
});
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.drawerbox {
:deep(.el-drawer__body) {
background: #1c1d1f !important;
position: relative;
}
.boxabsolute {
position: absolute;
bottom: 20px;
left: 50%;
transform: translate(-50%);
div {
color: #8c9196;
text-align: center;
}
div:nth-child(2) {
margin-top: 10px;
}
}
.drawerbox_box {
color: #fff;
.drawerbox_bo_top {
padding: 20px 0;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #ccc;
.drawerbox_bo_top_left {
display: flex;
flex-direction: column;
}
.drawerbox_bo_top_ring {
display: flex;
align-items: center;
.drawerbox_bo_top_ring_tb {
display: flex;
flex-direction: column;
border: 1px solid #ccc;
border-radius: 6px;
padding: 6px 10px;
span {
width: 80px;
margin-top: 5px;
text-align: center;
}
}
}
}
.drawerbox_bo_box {
width: 100%;
.drawerbox_bo_box_itemb_felx {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.drawerbox_bo_box_itembox {
width: 20%;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 20px;
margin-bottom: 30px;
.drawerbox_bo_box_icon {
border-radius: 6px;
background-color: var(--primary-color);
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
.drawerbox_bo_box_icontext {
margin-top: 10px;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,632 @@
<template>
<div class="card">
<div class="header">
<div class="t1">
<span class="title">应收:</span>
<span class="num">{{ money }}</span>
</div>
<div class="t2">
<span>原价{{ formatDecimal(props.amount) }}</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 class="number_wrap">
<div class="menus">
<div class="item" :class="{ active: payActive == index, disabled: item.disabled }"
v-for="(item, index) in payList" :key="item.id" @click="payTypeChange(index, item)">
<div class="icon">
<el-image :src="item.icon" class="img"></el-image>
</div>
<span class="title">{{ item.payName }}</span>
</div>
<div class="item" :class="{ active: payActive == 'buyer' }"
@click="payTypeChange('buyer', { payType: 'buyer' })">
<div class="icon">
<div class="img"
style="display: flex;align-items: center;justify-content: center;background-color: var(--el-color-danger);color: #fff;font-size: 24px;border-radius: 11px;">
</div>
</div>
<span class="title">挂账</span>
</div>
</div>
<div class="input_wrap">
<div class="input" style="flex: 1;">储值:{{ money }}</div>
<!-- <div class="input" v-if="waitPayMoney > 0">待支付:{{ waitPayMoney }}</div> -->
</div>
<div class="blance">
<!-- 可用余额0.00 -->
</div>
<div class="keybord_wrap">
<div class="left">
<div class="item" v-for="item in 9" :key="item" @click="amountInput(`${item}`)">{{ item }}</div>
<div class="item" @click="amountInput('.')">.</div>
<div class="item" @click="amountInput('0')">0</div>
<div class="item" @click="delHandle">
<el-icon>
<CloseBold />
</el-icon>
</div>
</div>
<div class="pay_btn" v-loading="payLoading" @click="confirmOrder">
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<scanModal ref="scanModalRef" :amount="props.amount" :money="money" :orderId="props.orderId"
: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>
<!-- 选择挂账人员 -->
<el-dialog title="挂账" top="3vh" v-model="showBuyer" width="90%" @closed="resetBuyerTable">
<el-form inline>
<el-form-item>
<el-input placeholder="请输入挂账人或手机号搜索" v-model="buyerTable.keywords" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getBuyerList">搜索</el-button>
<el-button @click="resetBuyerTable">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="buyerTable.list" height="440px" border stripe v-loading="buyerTable.loading">
<el-table-column prop="debtor" label="挂账人" />
<el-table-column prop="mobile" label="手机" width="150px" />
<el-table-column prop="position" label="职位" width="120px" />
<el-table-column prop="repaymentMethod" label="还款方式" width="160px">
<template v-slot="scope">
<template v-if="scope.row.repaymentMethod == 'total'">按总金额还款</template>
<template v-if="scope.row.repaymentMethod == 'order'">按订单还款</template>
</template>
</el-table-column>
<el-table-column prop="creditAmount" label="挂账额度" width="160px">
<template v-slot="scope">
{{ formatDecimal(scope.row.creditAmount) }}
</template>
</el-table-column>
<el-table-column prop="remainingAmount" label="剩余挂账额度" width="160px">
<template v-slot="scope">
{{ formatDecimal(scope.row.remainingAmount) }}
</template>
</el-table-column>
<el-table-column prop="accumulateAmount" label="累计挂账金额" width="160px">
<template v-slot="scope">
{{ formatDecimal(scope.row.accumulateAmount) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120px" fixed="right">
<template v-slot="scope">
<el-button type="primary" @click="payCreditPayHandle(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination layout="prev, pager, next, total" background style="margin-top: 20px;"
:total="Number(buyerTable.total)" v-model:current-page="buyerTable.page" @current-change="getBuyerList" />
</el-dialog>
</template>
<script setup>
import { onMounted, ref, computed, watch, reactive } from 'vue'
import { queryPayType, accountPay, cashPay, vipPay, buyerPage, payCreditPay } from '@/api/pay'
import { queryMembermember, createMembermember, membermemberScanPay, accountPaymember } from '@/api/member/index.js'
import { useUser } from "@/store/user.js"
import { clearNoNum, formatDecimal } from '@/utils'
import scanModal from '@/components/payCard/scanModal.vue'
import { ElMessage } from "element-plus";
import { useGlobal } from '@/store/global.js'
import { staffPermission } from '@/api/user.js'
import { fa } from 'element-plus/es/locale/index.mjs'
const global = useGlobal()
const store = useUser()
const props = defineProps({
amount: {
type: Number,
default: 0
},
selecttype: {
type: Number,
default: 0
},
orderId: {
type: [String, Number],
default: ''
},
discount: {
type: [String, Number],
default: 0
}
})
const emit = defineEmits(['paySuccess', 'cancelDiscount'])
const money = ref('0')
const scanModalRef = ref(null)
watch(props, (value) => {
money.value = `${formatDecimal(props.amount)}`
if (props.discount > 0) {
money.value = `${formatDecimal(props.amount * props.discount)}`
}
})
// const waitPayMoney = computed(() => {
// let num = JSON.stringify(props.amount - money.value)
// num = Math.floor(num * 100) / 100
// return num
// })
const payActive = ref(0)
const payType = ref('')
const payList = ref([])
const payLoading = ref(false)
// start
const showBuyer = ref(false)
const buyerTable = reactive({
keywords: '',
loading: false,
page: 1,
size: 10,
total: 0,
list: []
})
//
function showBuyerHandle() {
showBuyer.value = true
getBuyerList()
}
//
function resetBuyerTable() {
buyerTable.keywords = ''
buyerTable.page = 1
getBuyerList()
}
//
async function getBuyerList() {
try {
buyerTable.loading = true
const res = await buyerPage({
page: buyerTable.page,
size: buyerTable.size,
shopId: store.userInfo.shopId,
keywords: buyerTable.keywords,
status: 1,
responsiblePerson: '',
repaymentStatus: ''
})
buyerTable.loading = false
buyerTable.list = res.list
buyerTable.total = res.total
} catch (error) {
buyerTable.loading = false
console.log(error);
}
}
//
async function payCreditPayHandle(row) {
try {
payLoading.value = true
buyerTable.loading = true
const res = await payCreditPay({
creditBuyerId: row.id,
orderId: props.orderId,
payAmount: props.discount > 0 ? money.value : '',
discountAmount: props.discount > 0 ? formatDecimal(props.amount - money.value) : ''
})
showBuyer.value = false
payLoading.value = false
buyerTable.loading = false
ElMessage.success('支付成功')
emit('paySuccess')
} catch (error) {
buyerTable.loading = false
payLoading.value = false
console.log(error);
}
}
// end
//
function scanCodeSuccess() {
emit('paySuccess')
}
//
async function payTypeChange(index, item) {
try {
await staffPermission('yun_xu_shou_kuan')
if (item.disabled) return
payActive.value = index
payType.value = item.payType
if (item.payType == 'scanCode') {
scanModalRef.value.show()
}
if (item.payType == 'vipPay') {
showDialog.value = true
getMemberList()
}
if (item.payType == 'buyer') {
showBuyerHandle()
}
if (payActive.value != 'buyer') {
if (payList.value[payActive.value].payType == 'deposit' && !global.orderMemberInfo.id) {
scanModalRef.value.show()
}
}
} catch (error) {
console.log(error);
}
}
//
async function confirmOrder() {
try {
await staffPermission('yun_xu_shou_kuan')
if (payLoading.value) return
if (payActive.value == 'buyer') {
showBuyerHandle()
} else if (payList.value[payActive.value].payType == 'scanCode') {
scanModalRef.value.show()
} else {
// if (money.value < props.amount) return
payLoading.value = true
switch (payList.value[payActive.value].payType) {
case 'deposit'://
// if (props.selecttype == 1) {
// } else {
// }
if (global.orderMemberInfo.id) {
await accountPay({
orderId: props.orderId,
memberId: global.orderMemberInfo.id,
memberAccount: ''
})
} else {
payLoading.value = false
scanModalRef.value.show()
return
}
break;
case 'cash'://
if (props.selecttype == 1) {
await accountPaymember({
shopId: store.userInfo.shopId,
memberId: props.orderId,
amount: props.amount
})
} else {
await cashPay({
orderId: props.orderId,
payAmount: props.discount > 0 ? money.value : '',
discountAmount: props.discount > 0 ? formatDecimal(props.amount - money.value) : ''
})
}
break;
case 'vipPay':
//
console.log('使用会员id支付');
payLoading.value = false
showDialog.value = true
return
break;
default:
break;
}
payLoading.value = false
ElMessage.success('支付成功')
emit('paySuccess')
}
} catch (error) {
console.log(error)
payLoading.value = false
scanModalRef.value.loading = false
}
}
//
function amountInput(num) {
if (money.value + num <= props.amount) {
money.value = clearNoNum({ value: (money.value += num) })
} else {
money.value = clearNoNum({ value: `${props.amount}` })
}
}
//
function delHandle() {
if (!money.value) return
money.value = money.value.substring(0, money.value.length - 1)
if (!money.value) {
money.value = '0'
}
}
//
async function queryPayTypeAjax() {
try {
const res = await queryPayType({
shopId: store.userInfo.shopId
})
res.map(item => {
if (props.amount <= 0 && item.payType == 'scanCode') {
item.disabled = true
} else {
item.disabled = false
}
})
payList.value = res
if ((res[0].payType == 'scanCode' && !res[0].disabled) || res[0].payType == 'deposit') {
scanModalRef.value.show()
payType.value = res[0].payType
}
} catch (error) {
console.log(error)
}
}
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(() => {
money.value = `${formatDecimal(props.amount)}`
queryPayTypeAjax()
})
</script>
<style scoped lang="scss">
.card {
padding: var(--el-font-size-base);
height: 100%;
}
.header {
padding-bottom: var(--el-font-size-base);
border-bottom: 1px solid #ececec;
.t1 {
display: flex;
color: var(--el-color-danger);
font-weight: bold;
.title {
font-size: var(--el-font-size-base);
position: relative;
top: 14px;
}
.num {
font-size: 30px;
}
}
.t2 {
display: flex;
color: #999;
padding-top: 10px;
span {
display: flex;
align-items: center;
}
}
}
.number_wrap {
padding: var(--el-font-size-base) 0;
.menus {
display: flex;
gap: var(--el-font-size-base);
.item {
height: 130px;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #efefef;
padding: 10px 0;
border-radius: 10px;
position: relative;
$lineHeight: 4px;
&.disabled {
filter: grayscale(1);
}
&.active {
&::after {
content: "";
width: 50%;
height: $lineHeight;
background-color: var(--primary-color);
position: absolute;
bottom: 0;
left: 25%;
border-radius: $lineHeight;
}
}
.img {
$size: 40px;
width: $size;
height: $size;
}
.title {
padding-top: 10px;
}
}
}
.input_wrap {
display: flex;
gap: var(--el-font-size-base);
padding: var(--el-font-size-base) 0;
.input {
display: flex;
align-items: center;
height: 60px;
border-radius: 6px;
border: 1px solid var(--primary-color);
font-size: calc(var(--el-font-size-base) + 6px);
padding: 0 var(--el-font-size-base);
}
}
.blance {
color: var(--el-color-danger);
font-size: calc(var(--el-font-size-base) + 10px);
}
}
.keybord_wrap {
display: flex;
.left {
--item-height: calc((100vh - 440px) / 4);
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: var(--item-height) var(--item-height) var(--item-height) var(--item-height);
gap: var(--el-font-size-base);
.item {
background-color: #efefef;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
font-size: calc(var(--el-font-size-base) + 10px);
&:active {
background-color: #dbdbdb;
}
}
}
.pay_btn {
flex: 0.3;
border-radius: 6px;
color: #fff;
background-color: var(--el-color-warning);
margin-left: var(--el-font-size-base);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(var(--el-font-size-base) + 10px);
}
}
</style>

View File

@ -0,0 +1,432 @@
<!-- 扫码弹窗 -->
<template>
<div class="dialog">
<el-dialog title="扫码支付" width="600" v-model="dialogVisible" @open="reset" @close="clearAutoCheckOrder">
<div class="content">
<div class="left">
<el-image :src="icon" style="width: 60px; height: 60px"></el-image>
</div>
<div class="right" v-if="!userPayWait">
<div class="amount">
<span class="t">扫码支付</span>
<span class="n">{{ props.money }}</span>
</div>
<div class="input">
<el-input ref="inputRef" v-model="scanCode" style="height: calc(var(--el-component-size-large) + 30px)"
placeholder="请扫描付款码" clearable @change="inputChange"></el-input>
<div class="tips">注意扫码支付请保证输入框获得焦点输入内容结束后会自动支付请勿重复操作</div>
</div>
<!-- <div class="number_warp">
<div class="item" v-for="item in 9" :key="item" @click="inputHandle(item)">{{ item }}</div>
<div class="item disabled">.</div>
<div class="item" @click="inputHandle(0)">0</div>
<div class="item" @click="delHandle">
<el-icon>
<CloseBold />
</el-icon>
</div>
</div> -->
<div class="btn">
<el-button type="primary" style="width: 100%" v-loading="loading">立即支付</el-button>
</div>
</div>
<div class="pay_wait" v-else>
<div class="loading" v-loading="loading" element-loading-text="用户支付中..."></div>
<div class="btn">
<el-button type="primary" style="width: 100%" v-loading="checkPayStatusLoading" @click="checkPayStauts">
<span v-if="!checkPayStatusLoading">查询用户支付状态</span>
<span v-else>查询中...</span>
</el-button>
</div>
<div class="btn">
<el-button style="width: 100%" @click="resetScanCode">
重新扫码
</el-button>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import _ from "lodash";
import { onMounted, ref } from "vue";
import icon from "@/assets/icon_scan.png";
import { scanpay, queryOrder, quickPay, queryQuickPayStatus, accountPay, queryScanPay } from "@/api/pay";
import { useUser } from "@/store/user.js";
import { useGlobal } from '@/store/global.js'
import { formatDecimal } from '@/utils'
const store = useUser();
const global = useGlobal()
import {
queryMembermember,
createMembermember,
membermemberScanPay,
accountPaymember,
} from "@/api/member/index.js";
import { ElMessage } from "element-plus";
const emits = defineEmits(["success"]);
const props = defineProps({
amount: {
type: [Number, String],
default: 0,
},
selecttype: {
type: [Number, String],
default: 0,
},
orderId: {
type: [Number, String],
default: "",
},
fast: {
type: Boolean,
default: false,
},
payType: {
type: [Number, String],
default: "",
},
money: {
type: [Number, String],
default: 0,
}
});
const dialogVisible = ref(false);
const scanCode = ref("");
const inputRef = ref(null);
const loading = ref(false);
const userPayWait = ref(false);
const checkPayStatusLoading = ref(false);
const fastOrder = ref('')
//
async function submitHandle() {
try {
if (!scanCode.value) return;
loading.value = true;
if (props.selecttype == 1) {
await membermemberScanPay({
shopId: store.userInfo.shopId,
memberId: props.orderId,
amount: props.amount,
authCode: scanCode.value,
// payAmount: props.money < props.amount ? props.money : '',
// discountAmount: props.money < props.amount ? formatDecimal(props.amount - props.money) : ''
});
} else {
if (props.fast) {
await quickPay({
amount: props.amount,
authCode: scanCode.value,
payType: "scanCode",
});
} else {
if (props.payType == 'scanCode') {
await scanpay({
orderId: props.orderId,
authCode: scanCode.value,
payAmount: props.money < props.amount ? props.money : '',
discountAmount: props.money < props.amount ? formatDecimal(props.amount - props.money) : ''
});
}
if (props.payType == 'deposit') {
await accountPay({
orderId: props.orderId,
memberId: '',
memberAccount: scanCode.value,
payAmount: props.money < props.amount ? props.money : '',
discountAmount: props.money < props.amount ? formatDecimal(props.amount - props.money) : ''
})
}
}
}
loading.value = false;
scanCode.value = "";
ElMessage.success("支付成功");
dialogVisible.value = false;
emits("success");
} catch (error) {
console.log(error);
if (error.code === "100015") {
userPayWait.value = true;
fastOrder.value = error.data
autoCheckOrder()
} else {
scanCode.value = "";
loading.value = false;
console.log(error);
}
}
}
const timer = ref(null)
//
function autoCheckOrder() {
timer.value = setInterval(() => {
checkPayStauts(false)
}, 2000)
}
//
function clearAutoCheckOrder() {
clearInterval(timer.value)
timer.value = null
//
global.updateData(true)
}
//
async function checkPayStauts(tips = true) {
try {
if (props.selecttype == 1) {
//
const res = await queryScanPay({ flowId: fastOrder.value.id });
if (res.status == 0) {
userPayWait.value = false
loading.value = false;
scanCode.value = "";
ElMessage.success("支付成功");
dialogVisible.value = false;
clearAutoCheckOrder()
emits("success");
return;
}
if (res.status == 7) {
if (tips) {
ElMessage.warning("用户支付中...");
}
return;
} else {
clearAutoCheckOrder()
ElMessage.error(res.payRemark || "支付失败!");
return;
}
} else {
//
if (props.fast) {
//
const res = await queryQuickPayStatus({ orderId: fastOrder.value.orderNo });
if (res.status == 0) {
userPayWait.value = false
loading.value = false;
scanCode.value = "";
ElMessage.success("支付成功");
dialogVisible.value = false;
clearAutoCheckOrder()
emits("success");
return;
}
if (res.status == 1) {
if (tips) {
ElMessage.warning("用户支付中...");
}
return;
} else {
clearAutoCheckOrder()
ElMessage.error(res.payRemark || "支付失败!");
return;
}
} else {
//
const res = await queryOrder({ orderId: props.orderId });
if (res.status == "closed") {
userPayWait.value = false
loading.value = false;
scanCode.value = "";
ElMessage.success("支付成功");
dialogVisible.value = false;
clearAutoCheckOrder()
emits("success");
return;
}
if (res.status == "paying") {
if (tips) {
ElMessage.warning("用户支付中...");
}
return;
} else {
clearAutoCheckOrder()
ElMessage.error(res.payRemark || "支付失败!");
return;
}
}
}
} catch (error) {
console.log(error);
}
}
//
function resetScanCode() {
clearAutoCheckOrder()
userPayWait.value = false;
loading.value = false;
scanCode.value = "";
inputRef.value.focus();
}
//
function inputHandle(n) {
scanCode.value += n;
inputRef.value.focus();
}
//
function delHandle() {
if (!scanCode.value) return;
scanCode.value = scanCode.value.substring(0, scanCode.value.length - 1);
inputRef.value.focus();
}
//
// function enterHandle() {
// inputRef.value.focus()
// }
const inputChange = _.debounce(function (e) {
// console.log(e);
submitHandle();
}, 100);
function show() {
dialogVisible.value = true;
setTimeout(() => {
inputRef.value.focus();
}, 500);
}
function close() {
dialogVisible.value = false;
}
function reset() {
loading.value = false;
scanCode.value = "";
//
global.updateData(false)
}
defineExpose({
show,
close,
loading,
});
</script>
<style scoped lang="scss">
.tips {
padding-top: 10px;
color: #999;
}
.dialog :deep(.el-dialog__body) {
padding: 0 !important;
}
.content {
display: flex;
.left {
width: 200px;
display: flex;
align-items: center;
justify-content: center;
background-color: #efefef;
}
.right {
flex: 1;
padding: var(--el-font-size-base);
.amount {
display: flex;
height: calc(var(--el-component-size-large) + 20px);
display: flex;
align-items: center;
justify-content: space-between;
color: var(--primary-color);
background-color: #555;
border-radius: 6px;
padding: 0 var(--el-font-size-base);
font-size: calc(var(--el-font-size-base) + 10px);
}
.input {
padding: var(--el-font-size-base) 0;
:deep(.el-input__inner) {
font-size: calc(var(--el-font-size-base) + 10px);
}
}
.number_warp {
--h: 50px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: var(--h) var(--h) var(--h) var(--h);
gap: 8px;
.item {
background-color: #dddddd;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(var(--el-font-size-base) + 10px);
border-radius: 4px;
&.disabled {
color: #999;
background-color: #efefef;
&:active {
background-color: #efefef;
}
}
&:active {
background-color: #b9b9b9;
}
}
}
// .btn {
// padding-top: 20px;
// }
}
.pay_wait {
flex: 1;
padding: 0 var(--el-font-size-base);
height: 400px;
padding-bottom: 100px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.loading {
width: 200px;
height: 200px;
--el-loading-spinner-size: 100px;
:deep(.el-loading-text) {
font-size: 20px;
}
}
.btn {
width: 200px;
padding-top: var(--el-font-size-base);
}
}
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<el-dialog title="备注" width="600" v-model="dialogVisible">
<div class="tag_wrap">
<el-button plain type="primary" v-for="item in tagList" :key="item.remark" @click="remark = item.remark">
{{ item.remark }}
</el-button>
</div>
<div class="content">
<el-input type="textarea" :rows="6" v-model="remark" placeholder="请输入备注" @focus="
global.updateData(false)" @blur="global.updateData(true)"></el-input>
</div>
<div class="footer_wrap">
<div class="btn">
<el-button type="primary" style="width: 100%;" @click="confirmHandle">确认</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { useGlobal } from '@/store/global.js'
const global = useGlobal()
const emit = defineEmits(['success'])
const dialogVisible = ref(false)
const remark = ref('')
const tagList = ref([
{
remark: '味道淡一点'
},
{
remark: '味道大一点'
}
])
function confirmHandle() {
emit('success', remark.value)
dialogVisible.value = false
}
//
const show = () => {
dialogVisible.value = true
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.tag_wrap {
display: flex;
flex-wrap: wrap;
}
.content {
padding: 20px 0;
}
.footer_wrap {
display: flex;
.btn {
flex: 1;
}
}
</style>

50
src/components/screen.vue Normal file
View File

@ -0,0 +1,50 @@
<template>
<el-dialog width="400" v-model="dialogVisible" style="padding: 0; " title="已锁屏" :close-on-click-modal="false" :show-close="false">
<div class="drawerbox_box">
<el-input v-model="loginName" placeholder="请输入登录账号" />
<el-button style="width: 100%; margin-top: 20px;" type="primary" @click="loginNameclick">确认</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { useUser } from "@/store/user.js"
import { ElMessage } from 'element-plus'
const store = useUser()
const loginName = ref()
const loginNameclick = () => {
if (loginName.value == store.userInfo.loginName) {
dialogVisible.value = false
} else {
ElMessage({
message: '输入错误',
type: 'Error',
})
}
}
const dialogVisible = ref(false)
function shows() {
dialogVisible.value = true
}
defineExpose({
shows
})
</script>
<style scoped lang="scss">
.drawerbox {
:deep(.el-drawer__body) {
background: #1c1d1f !important;
}
.drawerbox_box {
color: #fff;
}
}
</style>

View File

@ -1,33 +1,32 @@
<template>
<el-dialog title="选择规格" width="800" v-model="show">
<el-dialog :title="goods.name" width="600" v-model="dialogVisible">
<div class="header">选择规格</div>
<div class="row">
<div class="title">规格</div>
<div class="sku_wrap">
<div class="item">默认1人份</div>
</div>
</div>
<div class="row">
<div class="title">温度</div>
<div class="sku_wrap">
<div class="item"></div>
<div class="item"></div>
</div>
</div>
<div class="row">
<div class="title">糖度</div>
<div class="sku_wrap">
<div class="item">不另外加糖</div>
<div class="item">半糖</div>
<div class="item">标准糖</div>
<div v-loading="loading">
<div class="row" v-for="(item, index) in goods.selectSpec" :key="index">
<div class="title">{{ item.name }}</div>
<div class="sku_wrap">
<!-- <div class="item" :class="{ active: val.active }" v-for="(val, i) in item.value" :key="i"
@click="selectedSku(index, i)">{{ val.name }}</div> -->
<el-button :plain="!val.active" type="primary" v-for="(val, i) in item.selectSpecResult
" :key="i" :disabled="val.disabled" @click="selectedSku(index, i)" class="btn">{{ val.name }}</el-button>
</div>
</div>
</div>
<div class="footer">
<div class="btn">
<el-button plain style="width: 100%;" @click="show = false">取消</el-button>
<div class="info">
<template v-if="goodsInfo.id">
<span>库存{{ goodsInfo.stockNumber }}</span>
<span>{{ goodsInfo.salePrice }}</span>
</template>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;">确认</el-button>
<div class="btn_wrap">
<div class="btn">
<el-button plain style="width: 100%;" @click="dialogVisible = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :disabled="!goodsInfo.id"
@click="submitSku">确认</el-button>
</div>
</div>
</div>
</el-dialog>
@ -35,8 +34,200 @@
<script setup>
import { ref } from 'vue'
const show = ref(true)
import { useUser } from "@/store/user.js"
import { queryProductSku } from '@/api/product'
const store = useUser();
const emit = defineEmits(['success'])
const type = ref('shop')
const dialogVisible = ref(false)
const goods = ref({})
const selectedSkuNum = ref(0)
const selectedSkuTag = ref('')
const goodsInfo = ref({})
const loading = ref(false)
const selecSkuArray = ref([])
//
function submitSku() {
dialogVisible.value = false
switch (type.value) {
case 'shop':
emit('success', goodsInfo.value)
break;
case 'cart':
emit('success', goods.value)
break;
default:
break;
}
}
//
function selectedSku(index = 0, i = 0) {
goods.value.selectSpec[index].selectSpecResult.map(item => {
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) {
goods.value.selectSpec[index].selectSpecResult[i].active = false
selectedSkuNum.value--
} else {
goods.value.selectSpec[index].selectSpecResult[i].active = true
selectedSkuNum.value++
}
selectedSuccess()
}
//
function selectedSuccess() {
let num = 0
let tag = []
goods.value.selectSpec.map(item => {
item.selectSpecResult.map(val => {
if (val.active) {
num++
tag.push(val.name)
}
})
selectedSkuNum.value = num
selectedSkuTag.value = tag.join(',')
})
if (selectedSkuNum.value >= goods.value.selectSpec.length) {
//
queryProductSkuAjax()
} else {
goodsInfo.value = {}
}
}
//
async function queryProductSkuAjax() {
try {
loading.value = true
const res = await queryProductSku({
shopId: store.userInfo.shopId,
productId: type.value == 'shop' ? goods.value.id : goods.value.productId,
spec_tag: selectedSkuTag.value
})
goodsInfo.value = res
if (type.value == 'cart') {
goods.value.skuId = res.id
}
setTimeout(() => {
loading.value = false
}, 100)
} catch (error) {
loading.value = false
console.log(error)
}
}
//
function show(item, t = 'shop') {
goodsInfo.value = {}
goods.value = {}
selectedSkuNum.value = 0
dialogVisible.value = true
goods.value = ""
goods.value = item
type.value = t
goods.value.selectSpec = JSON.parse(goods.value.selectSpec)
goods.value.selectSpec.map((item, index) => {
let arr = []
item.selectSpecResult.map(val => {
switch (type.value) {
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({
active: false,
name: val,
disabled: index == 0 ? disabled : true
})
break;
case 'cart':
//
const skus = goods.value.skuName.split(',')
arr.push({
active: !!skus.find(item => item === val),
name: val,
disabled: true
})
break;
default:
break;
}
})
item.selectSpecResult = arr
})
let arr = []
goods.value.selectSpec.map(item => {
if (item.selectSpecResult.length) {
arr.push({ ...item })
}
})
goods.value.selectSpec = arr
selectedSuccess()
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
@ -47,47 +238,40 @@ const show = ref(true)
.row {
border-bottom: 1px solid #ececec;
margin-top: 20px;
margin-top: var(--el-font-size-base);
.title {
font-size: 20px;
font-size: var(--el-font-size-base);
}
.sku_wrap {
display: flex;
padding: 14px 0;
.item {
color: var(--primary-color);
padding: 8px 16px;
font-size: 18px;
border: 1px solid var(--primary-color);
margin-right: 14px;
border-radius: 2px;
&.active {
background-color: var(--primary-color);
color: #fff;
}
&:hover {
cursor: pointer;
background-color: var(--primary-color);
color: #fff;
}
}
padding: var(--el-font-size-base) 0;
flex-wrap: wrap;
gap: var(--el-font-size-base);
}
}
.footer {
display: flex;
padding-top: 100px;
padding-top: 30px;
.btn {
flex: 1;
.info {
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
}
&:last-child {
margin-left: 14px;
.btn_wrap {
display: flex;
.btn {
flex: 1;
&:last-child {
margin-left: 14px;
}
}
}
}

View File

@ -0,0 +1,116 @@
<!-- 取餐号组件 -->
<template>
<el-dialog :title="props.title" width="600" v-model="dialogVisible" @open="opne">
<el-input :type="props.inputType" v-model="number" :placeholder="props.placeholder" readonly></el-input>
<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" disabled style="width: 100%">.</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="footer">
<el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle">确认</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
const props = defineProps({
type: {
type: [String, Number],
default: 1, // 1 2
},
inputType: {
type: String,
default: 'text'
},
title: {
type: String,
default: "标题",
},
placeholder: {
type: String,
default: "提示",
},
});
const dialogVisible = ref(false);
const number = ref("");
const emit = defineEmits(["success"]);
function show() {
dialogVisible.value = true;
}
function opne() {
number.value = "";
}
//
function inputHandle(n) {
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
if (props.type == 2) {
if (number.value.length < 6) {
ElMessage.error('请输入正确的密码')
return
} else {
loading.value = true
emit("success", number.value);
dialogVisible.value = false;
setTimeout(() => {
loading.value = false
}, 1000)
}
} else {
emit("success", number.value);
dialogVisible.value = false;
}
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
:deep(.el-input__inner) {
height: 60px;
font-size: 36px;
}
.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: 60px;
}
}
</style>

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

@ -1,10 +1,12 @@
import { createRouter, createWebHashHistory } from "vue-router";
import home from "@/views/home.vue";
import home from "@/views/home/index.vue";
import test from "@/views/home/test.vue";
const routes = [
{
path: "/",
name: "home",
// component: test,
component: home,
},
{
@ -23,14 +25,82 @@ const routes = [
},
component: () => import("@/views/register.vue"),
},
{
path: "/group_buy",
name: "group_buy",
meta: {
index: 1,
},
component: () => import("@/views/group_buy/index.vue"),
},
{
path: "/table",
name: "table",
meta: {
index: 1,
},
component: () => import("@/views/table.vue"),
}
component: () => import("@/views/table/index.vue"),
},
{
path: "/order",
name: "order",
meta: {
index: 1,
},
component: () => import("@/views/order/index.vue"),
},
{
path: "/member",
name: "member",
meta: {
index: 1,
},
component: () => import("@/views/member/index.vue"),
},
{
path: "/queue",
name: "queue",
meta: {
index: 1,
},
component: () => import("@/views/queue/index.vue"),
},
{
path: "/work",
name: "work",
meta: {
index: 1,
},
component: () => import("@/views/work/index.vue"),
},
{
path: "/workrecord",
name: "workrecord",
meta: {
index: 1,
},
component: () => import("@/views/work/record.vue"),
},
{
path: "/device_list",
name: "device_list",
component: () => import("@/views/device/index.vue"),
},
{
path: "/add_device",
name: "add_device",
component: () => import("@/views/device/add.vue"),
},
{
path: "/add_label",
name: "add_label",
component: () => import("@/views/device/add_label.vue"),
},
{
path: "/webview",
name: "webview",
component: () => import("@/views/webview/index.vue"),
},
];
const router = createRouter({

25
src/store/global.js Normal file
View File

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

227
src/store/print.js Normal file
View File

@ -0,0 +1,227 @@
import { defineStore } from "pinia";
import { ipcRenderer } from "electron";
import { bySubType } from "@/api/device";
import { useUser } from "@/store/user.js";
import { useShop } from "@/store/shop.js";
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({
id: "print",
state: () => ({
localDevices: [], // 本地打印机列表
deviceNoteList: [], // 添加的打印机
deviceLableList: [], // 添加的打印机
labelList: [], // 要打印的队列数据
printTimer: null,
receiptList: [], // 小票队列数据
receiptTimer: null,
}),
actions: {
// 获取本地打印机和已添加的可以用打印机列表
async init() {
const store = useUser();
// 获取本地打印机
ipcRenderer.send("getPrintList");
ipcRenderer.on("printList", (event, arg) => {
// localPrintList.value = arg;
// console.log(localPrintList.value);
this.localDevices = arg;
});
// 获取已添加的小票打印机
this.deviceNoteList = await bySubType({
shopId: store.userInfo.shopId,
contentType: "local",
subType: "cash",
});
// 获取已添加的标签打印机
this.deviceLableList = await bySubType({
shopId: store.userInfo.shopId,
contentType: "local",
subType: "label",
});
console.log("打印队列初始化成功");
},
// 检查本地打印机是否能正常使用
checkLocalPrint(deviceName) {
let print = "";
for (let item of this.localDevices) {
if (item.name == deviceName) {
print = item;
}
}
if (!print.name) {
return false;
} else {
return true;
}
},
// 打印标签小票
labelPrint(props) {
const shopInfo = useShop();
if (
this.deviceLableList.length &&
this.checkLocalPrint(this.deviceLableList[0].config.deviceName)
) {
let pids = this.deviceLableList[0].config.categoryList.map(
(item) => item.id
);
let count = 0;
let sum = 0;
props.carts.map((item) => {
if (pids.some((el) => el == item.categoryId)) {
for (let i = 0; i < item.number; i++) {
sum++;
}
}
});
props.carts.map((item) => {
if (pids.some((el) => el == item.categoryId)) {
for (let i = 0; i < item.number; i++) {
count++;
this.labelList.push({
outNumber: props.outNumber,
name: item.name,
skuName: item.skuName,
masterId: props.orderInfo.tableName,
deviceName: this.deviceLableList[0].config.deviceName,
// deviceName: "Xprinter XP-T202UA",
createdAt: dayjs(props.createdAt).format("YYYY-MM-DD HH:mm:ss"),
isPrint: false,
count: `${count}/${sum}`,
ticketLogo: shopInfo.info.ticketLogo,
});
}
}
});
// 执行打印操作
this.startLabelPrint();
} else {
console.log("没有标签打印机");
}
},
// 开始打印标签数据
startLabelPrint() {
if (this.printTimer != null) return;
this.printTimer = setInterval(() => {
let item = "";
if (!this.labelList.length) {
clearInterval(this.printTimer);
this.printTimer = null;
} else {
item = this.labelList[0];
if (!item.isPrint) {
ipcRenderer.send("printerTagSync", JSON.stringify(item));
this.labelList[0].isPrint = true;
this.labelList.splice(0, 1);
}
}
}, 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);
}
},
},
});

146
src/store/socket.js Normal file
View File

@ -0,0 +1,146 @@
import _ from "lodash";
import { dayjs } from "element-plus";
import { defineStore } from "pinia";
import { useUser } from "@/store/user.js";
import { usePrint } from "@/store/print.js";
import { v4 as uuidv4 } from "uuid";
import useStorage from "@/utils/useStorage";
import ReconnectingWebSocket from "reconnecting-websocket";
import { ipcRenderer } from "electron";
export const useSocket = defineStore({
id: uuidv4(),
state: () => ({
online: false, // 在线状态
ws: null, // websocket实例
uuid: "", // 长连接唯一id
heartbeatTimer: null, // 心跳计时器
orderList: [],
log: true,
}),
actions: {
// 创建uuid
createUUID() {
if (!useStorage.get("uuid")) {
ipcRenderer.send("getOSmacSync");
// useStorage.set("uuid", uuidv4());
ipcRenderer.on("getOSmacRes", (event, arg) => {
useStorage.set("uuid", arg);
this.uuid = useStorage.get("uuid");
});
} else {
this.uuid = useStorage.get("uuid");
}
},
// 关闭ws
close() {
if (this.log) console.log("关闭ws");
this.online = false;
this.ws.close(1000);
this.ws = null;
this.clearHeartBeat();
},
wsReconnect: _.throttle(
function () {
if (this.ws.readyState == ReconnectingWebSocket.OPEN) return;
this.ws.reconnect();
// console.log("11111");
},
2000,
{ leading: true, trailing: false }
),
// 初始化
init(wsUrl = import.meta.env.VITE_API_WSS) {
this.createUUID();
const store = useUser();
const printStore = usePrint();
printStore.init();
if (this.ws == null) {
if (this.log) console.log("创建新的ws连接");
const protocols = []; // 可选的子协议数组
const options = {
// 自动重新连接的选项(可选)
connectionTimeout: 1000,
maxRetries: 100,
};
this.ws = new ReconnectingWebSocket(wsUrl, protocols, options);
} else {
if (this.log) console.log("重新连接ws");
this.wsReconnect();
}
this.ws.addEventListener("open", (event) => {
if (this.log) console.log("wss连接成功");
this.online = true;
// 清除心跳
this.clearHeartBeat();
if (this.log) console.log(this);
this.ws.send(
JSON.stringify({
type: "connect",
shopId: store.userInfo.shopId,
clientId: this.uuid,
})
);
this.startheartbeat();
});
this.ws.addEventListener("message", (e) => {
let data = JSON.parse(e.data);
if (data.type == "order") {
if (this.log) console.log("接收消息", data);
this.ws.send(
JSON.stringify({
type: "send",
orderNo: data.orderInfo.orderNo,
})
);
// 接收订单消息,打印小票
// printBill(data)
// 打印标签小票
if (!this.orderList.some((el) => el == data.orderInfo.orderNo)) {
// console.log("打印", data);
printStore.labelPrint(data);
printStore.pushReceiptData(data);
this.orderList.push(data.orderInfo.orderNo);
if (this.orderList.length > 30) {
this.orderList.splice(0, 1);
}
}
} else if (data.type == "heartbeat") {
if (this.log) console.log("接收心跳");
}
});
this.ws.addEventListener("error", () => {
if (this.log) console.log("WebSocket连接发生错误");
this.online = false;
this.clearHeartBeat();
});
this.ws.addEventListener("error", (e) => {
if (this.log) console.log("ws关闭了", e);
this.online = false;
this.clearHeartBeat();
});
},
// 启动心跳连接
startheartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.log) console.log("发送心跳");
this.ws.send(JSON.stringify({ type: "heartbeat" }));
}, 10000);
},
// 清除心跳
clearHeartBeat() {
// 清除心跳
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
},
},
});

View File

@ -1,13 +1,24 @@
import { defineStore } from "pinia";
export const useUserStore = defineStore({
import { login } from "@/api/user";
import useStorage from "@/utils/useStorage";
export const useUser = defineStore({
id: "user",
state: () => ({
name: "张三",
token: "sas121sasdADSAD",
userInfo: useStorage.get("userInfo"),
token: useStorage.get("token"),
}),
actions: {
login(data) {
this.name = data;
// 登录
userlogin(param) {
return login(param).then((res) => {
this.userInfo = res;
this.token = res.token;
useStorage.set("token", this.token);
// this.userInfo.shopId = "24";
useStorage.set("userInfo", this.userInfo);
return this.userInfo;
});
},
},
});

View File

@ -1,79 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@ -0,0 +1,98 @@
/**
* 生成范围随机数
* @param {Object} Min
* @param {Object} Max
*/
export function RandomNumBoth(Max, Min = 0) {
var Range = Max - Min;
var Rand = Math.random();
var num = Min + Math.round(Rand * Range); //四舍五入
return num;
}
/**
* 去除字符串中除了数字和点以外的其他字符
* @param {Object} obj
*/
export function clearNoNum(obj) {
//如果用户第一位输入的是小数点,则重置输入框内容
if (obj.value != "" && obj.value.substr(0, 1) == ".") {
obj.value = "";
}
obj.value = obj.value.replace(/^0*(0\.|[1-9])/, "$1"); //粘贴不生效
obj.value = obj.value.replace(/[^\d.]/g, ""); //清除“数字”和“.”以外的字符
obj.value = obj.value.replace(/\.{2,}/g, "."); //只保留第一个. 清除多余的
obj.value = obj.value
.replace(".", "$#$")
.replace(/\./g, "")
.replace("$#$", ".");
obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/, "$1$2.$3"); //只能输入两个小数
if (obj.value.indexOf(".") < 0 && obj.value != "") {
//以上已经过滤,此处控制的是如果没有小数点,首位不能为类似于 01、02的金额
if (obj.value.substr(0, 1) == "0" && obj.value.length == 2) {
obj.value = obj.value.substr(1, obj.value.length);
}
}
return obj.value;
}
/**
* 保留小数n位不进行四舍五入
* num你传递过来的数字,
* decimal你保留的几位,默认保留小数后两位
* isInt 是否保留0
*/
export function formatDecimal(num, decimal = 2, isInt = false) {
num = num.toFixed(3).toString();
const index = num.indexOf(".");
if (index !== -1) {
num = num.substring(0, decimal + index + 1);
} else {
num = num.substring(0);
}
//截取后保留两位小数
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;
}

View File

@ -1,8 +1,68 @@
import axios from "axios";
import { ElMessage } from "element-plus";
import useStorage from '@/utils/useStorage'
import router from '@/router'
const request = axios.create({
baseURL: "http://localhost",
timeout: 100000,
const service = axios.create({
baseURL: import.meta.env.MODE == 'development' ? '/api/' : import.meta.env.VITE_API_URL,
// withCredentials: true, // 跨域请求时发送 cookies
timeout: 5000, // 请求超时
});
request.interceptors.request.use((config) => {});
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
if (useStorage.get("token")) {
// 让每个请求携带 token
// ['X-Token'] 是自定义标题键
// 请根据实际情况修改
config.headers["token"] = useStorage.get("token");
config.headers["loginName"] = useStorage.get("userInfo").loginName;
config.headers["clientType"] = 'pc';
// config.headers['Content-Type'] = 'application/json'
}
return config;
},
(error) => {
// 处理请求错误
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
if (+response.status === 200) {
if (+response.data.code == '0') {
return response.data.data;
} else if (+response.data.code == '9999') {
ElMessage.error('登录已过期,请重新登录')
useStorage.clear()
router.replace("/login")
window.location.reload()
return Promise.reject('登录已过期,请重新登录')
} 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;

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;

60
src/utils/request_php.js Normal file
View File

@ -0,0 +1,60 @@
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"
? "/php/"
: import.meta.env.VITE_API_PHP_URL,
// withCredentials: true, // 跨域请求时发送 cookies
timeout: 5000, // 请求超时
});
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
if (useStorage.get("douyin") && useStorage.get("douyin").token) {
config.headers["bausertoken"] = useStorage.get("douyin").token;
// config.headers['Content-Type'] = 'application/json'
}
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;

15
src/utils/useStorage.js Normal file
View File

@ -0,0 +1,15 @@
export default {
get(key) {
return JSON.parse(localStorage.getItem(key))
},
set(key, value) {
localStorage.setItem(key, JSON.stringify(value))
},
del(key) {
localStorage.removeItem(key)
},
clear() {
localStorage.clear()
}
}

364
src/views/device/add.vue Normal file
View File

@ -0,0 +1,364 @@
<template>
<div class="device_container">
<div class="header" @click="router.back()">
<el-icon style="position: relative; top: 2px; margin-right: 4px" size="22">
<ArrowLeft />
</el-icon>
<el-text>{{ form.id ? "编辑小票打印机" : "添加小票打印机" }}</el-text>
</div>
<div class="d_content">
<div class="d_list">
<el-form :model="form" label-position="left" label-width="60%">
<el-form-item label="设备尺寸">
<el-select v-model="form.config.width">
<el-option label="58mm" value="58"></el-option>
<el-option label="80mm" value="80"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类型">
<el-select v-model="form.connectionType">
<el-option label="USB" value="USB"></el-option>
<el-option label="网络" value="network"></el-option>
</el-select>
</el-form-item>
<el-form-item label="选择设备">
<el-select v-model="form.config.deviceName">
<el-option :label="item.name" :value="item.name" v-for="item in printList" :key="item.name"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="form.name" placeholder="请输入设备名称"></el-input>
</el-form-item>
<el-form-item label="打印份数">
<el-select v-model="form.config.printerNum">
<el-option :label="item" :value="item" v-for="item in 4" :key="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="商品模式">
<el-select v-model="form.config.model">
<el-option label="普通出单" value="normal"></el-option>
<el-option label="分类出单" value="category"></el-option>
</el-select>
</el-form-item>
<el-form-item label="打印子订单">
<el-select v-model="form.config.printSub">
<el-option label="是" :value="0"></el-option>
<el-option label="否" :value="1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="自动切刀">
<el-select v-model="form.config.autoCut">
<el-option label="是" :value="1"></el-option>
<el-option label="否" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="尾部留空">
<el-select v-model="form.config.feet">
<el-option :label="`${item}行`" :value="`${item}`" v-for="item in feets" :key="item"></el-option>
</el-select>
</el-form-item>
</el-form>
</div>
<div class="menu_wrap">
<div class="print_view">
<div class="title t1">{{ printData.shop_name }}</div>
<div class="title t2">预结算单{{ printData.orderInfo.masterId }}</div>
<div class="row">订单号{{ printData.orderInfo.orderNo }}</div>
<div class="row">交易时间{{ printData.createdAt }}</div>
<div class="row">收银员{{ printData.loginAccount }}</div>
<div class="line"></div>
<table class="table">
<tr>
<td>品名</td>
<td>单价</td>
<td>数量</td>
<td>小计</td>
</tr>
<tr v-for="(item, index) in printData.carts" :key="index">
<td>
<div>{{ item.name }}</div>
<div class="sku">{{ item.skuName }}</div>
</td>
<td>{{ item.salePrice }}</td>
<td>{{ item.number }}</td>
<td>{{ item.totalAmount }}</td>
</tr>
</table>
<div class="line"></div>
<div class="row between">
<span>合计</span>
<span>{{ printData.amount }}</span>
</div>
<div class="row between">
<span>余额</span>
<span>0.00</span>
</div>
<div class="line"></div>
<div class="row">备注{{ printData.remark }}</div>
<div class="row">打印时间{{ printData.printTime }}</div>
<div class="btn_wrap">
<div class="btn">
<el-button plain style="width: 100%" :loading="printDataLoading" @click="printHandle">
打印测试小票
</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%" :loading="loading" @click="submitHandle">
保存
</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import dayjs from 'dayjs'
import { ipcRenderer } from "electron";
import { onMounted, reactive, ref } from "vue";
import { useRouter, useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { tbPrintMachinePost, tbPrintMachineDetail } from "@/api/device";
import { useUser } from "@/store/user.js";
import { Loading } from "element-plus/es/components/loading/src/service";
import { usePrint } from "@/store/print.js";
const printStore = usePrint();
const store = useUser();
const router = useRouter();
const route = useRoute();
const printList = ref([]);
const feets = ref([0, 1, 2, 3, 4, 5, 8]);
const loading = ref(false);
const form = ref({
id: "",
contentType: "",
connectionType: "USB",
config: {
deviceName: "",
width: "58", // mm
printerNum: 1, //
categoryList: [], //
model: "normal", // ,
feet: "2",
autoCut: 0,
printSub: 1,
},
name: "小票打印机",
subType: "cash", //
status: 1,
sort: "",
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() {
ipcRenderer.send("getPrintList");
ipcRenderer.on("printList", (event, arg) => {
printList.value = arg;
});
}
//
function printHandle() {
if (!form.value.config.deviceName) {
ElMessage.warning("请选择打印设备");
return;
}
printDataLoading.value = true
printData.deviceName = form.value.config.deviceName
printData.printTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
printStore.pushReceiptData(printData, false)
setTimeout(() => {
printDataLoading.value = false
}, 1500)
}
//
async function submitHandle() {
try {
if (!form.value.config.deviceName) {
ElMessage.warning("请选择打印设备");
return;
}
Loading.value = true;
await tbPrintMachinePost(form.value, form.value.id ? "put" : "post");
Loading.value = false;
ElMessage.success(form.value.id ? "编辑成功" : "添加成功");
printStore.init();
router.back();
} catch (error) {
console.log(error);
}
}
//
async function tbPrintMachineDetailAjax() {
try {
const res = await tbPrintMachineDetail(route.query.id);
form.value = res;
} catch (error) {
console.log(error);
}
}
onMounted(() => {
getPrintList();
if (route.query.id) {
tbPrintMachineDetailAjax(route.query.id);
}
});
</script>
<style scoped lang="scss">
.device_container {
width: 100vw;
height: 100vh;
padding: 15px;
background-color: #f1f1f1;
}
.header {
height: 50px;
background-color: #fff;
border-radius: 10px;
display: flex;
align-items: center;
padding: 0 10px;
}
.d_content {
padding-top: 15px;
display: flex;
height: calc(100vh - 15px * 2 - 50px);
.d_list {
flex: 2;
border-radius: 10px;
background-color: #fff;
padding: 15px;
}
.menu_wrap {
flex: 1.5;
margin-left: 15px;
background-color: #fff;
border-radius: 10px;
padding: 0 15px;
.print_view {
padding: 20px 0;
.title {
display: flex;
justify-content: center;
margin-bottom: 4px;
&.t1 {
font-size: 24px;
}
&.t2 {
margin-bottom: 15px;
}
}
.row {
margin-top: 2px;
&.between {
display: flex;
justify-content: space-between;
}
}
.btn_wrap {
display: flex;
gap: 20px;
padding: 20px 0;
.btn {
flex: 1;
}
}
.line {
margin: 10px 0;
border-bottom: 1px solid #ddd;
}
.table {
width: 100%;
tr {
width: 100%;
display: flex;
&:not(:last-child) {
margin-bottom: 10px;
}
td {
flex: 1;
&:nth-child(1) {
flex: 2;
}
&:not(:first-child) {
display: flex;
justify-content: flex-end;
}
.sku {
font-size: 14px;
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,330 @@
<template>
<div class="device_container">
<div class="header" @click="router.back()">
<el-icon style="position: relative; top: 2px; margin-right: 4px" size="22">
<ArrowLeft />
</el-icon>
<el-text>{{ form.id ? "编辑便签打印机" : "添加便签打印机" }}</el-text>
</div>
<div class="d_content">
<div class="d_list">
<el-form :model="form" label-position="left" label-width="60%">
<!-- <el-form-item label="设备尺寸">
<el-select v-model="form.config.width">
<el-option label="58mm" value="58"></el-option>
<el-option label="80mm" value="80"></el-option>
</el-select>
</el-form-item> -->
<el-form-item label="设备类型">
<el-select v-model="form.connectionType">
<el-option label="USB" value="USB"></el-option>
<el-option label="网络" value="network"></el-option>
</el-select>
</el-form-item>
<el-form-item label="选择设备">
<el-select v-model="form.config.deviceName">
<el-option :label="item.name" :value="item.name" v-for="item in printList" :key="item.name"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="form.name" placeholder="请输入设备名称"></el-input>
</el-form-item>
<el-form-item label="打印份数">
<el-select v-model="form.config.printerNum">
<el-option :label="item" :value="item" v-for="item in 4" :key="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="商品模式">
<el-select v-model="form.config.model">
<el-option label="普通出单" value="normal"></el-option>
<el-option label="分类出单" value="category"></el-option>
</el-select>
</el-form-item>
<el-form-item label="商品分类">
<div style="cursor: pointer" @click="classifyRef.show()">
<span style="color: #409eff" v-for="item in form.config.categoryList">
{{ item.name }},
</span>
<span style="color: #e65d6e" v-if="!form.config.categoryList.length">
请选择分类
</span>
</div>
</el-form-item>
<!-- <el-form-item label="打印子订单">
<el-select v-model="form.config.printSub">
<el-option label="是" :value="0"></el-option>
<el-option label="否" :value="1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="自动切刀">
<el-select v-model="form.config.autoCut">
<el-option label="是" :value="1"></el-option>
<el-option label="否" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="尾部留空">
<el-select v-model="form.config.feet">
<el-option
:label="`${item}行`"
:value="`${item}`"
v-for="item in feets"
:key="item"
></el-option>
</el-select>
</el-form-item> -->
</el-form>
</div>
<div class="menu_wrap">
<div class="print_view">
<canvas class="ewm" ref="canvasRef"></canvas>
<div class="header">
<img class="logo" :src="shopInfo.info.ticketLogo" />
<!-- <span class="title">双屿Pisces</span> -->
</div>
<div class="number_wrap">
<div class="num" v-if="printData.outNumber">{{ printData.outNumber }}</div>
<div class="info" v-if="printData.masterId">座位号{{ printData.masterId }}</div>
</div>
<div class="shop_info">
<div class="name">{{ printData.name }}</div>
<div class="text" v-if="printData.skuName">{{ printData.skuName }}</div>
</div>
<div class="time">{{ printData.createdAt }}</div>
<div class="tips">建议尽快享用风味更佳 {{ printData.count }}</div>
</div>
<div class="btn_wrap">
<div class="btn">
<el-button plain style="width: 100%" @click="printHandle">
打印测试小票
</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%" :loading="loading" @click="submitHandle">
保存
</el-button>
</div>
</div>
</div>
</div>
</div>
<classify ref="classifyRef" @success="(e) => (form.config.categoryList = e)" />
</template>
<script setup>
import _ from 'lodash'
import { ipcRenderer } from "electron";
import { onMounted, ref } from "vue";
import { useRouter, useRoute } from "vue-router";
import { ElMessage, dayjs } from "element-plus";
import { tbPrintMachinePost, tbPrintMachineDetail } from "@/api/device";
import { useUser } from "@/store/user.js";
import { Loading } from "element-plus/es/components/loading/src/service";
import classify from "@/components/classify/index.vue";
import QRCode from 'qrcode'
import { usePrint } from "@/store/print.js";
import { useShop } from "@/store/shop.js";
const printStore = usePrint();
const store = useUser();
const router = useRouter();
const route = useRoute();
const shopInfo = useShop();
const classifyRef = ref(null);
const printList = ref([]);
const feets = ref([0, 1, 2, 3, 4, 5, 8]);
const loading = ref(false);
const form = ref({
id: "",
contentType: "",
connectionType: "USB",
config: {
deviceName: "",
width: "40", // mm
printerNum: 1, //
categoryList: [], //
model: "normal", // ,
feet: "2",
autoCut: 0,
printSub: 1,
},
name: "标签打印机",
subType: "label", //
status: 1,
sort: "",
shopId: store.userInfo.shopId,
});
const canvasRef = ref(null)
const printData = ref({
deviceName: '',
outNumber: '123',
name: '【测试勿管】甜橙马黛茶',
skuName: '测试、加珍珠',
masterId: '#A9',
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
ticketLogo: shopInfo.info.ticketLogo,
})
//
function getPrintList() {
ipcRenderer.send("getPrintList");
ipcRenderer.on("printList", (event, arg) => {
printList.value = arg;
});
}
//
const printHandle = _.throttle(function () {
if (!form.value.config.deviceName) {
ElMessage.error("请选择打印设备");
return;
}
printData.value.deviceName = form.value.config.deviceName
ipcRenderer.send(
"printerTagSync",
JSON.stringify(printData.value)
);
}, 1500, { leading: true, trailing: false })
//
async function submitHandle() {
try {
if (!form.value.config.deviceName) {
ElMessage.warning("请选择打印设备");
return;
}
Loading.value = true;
await tbPrintMachinePost(form.value, form.value.id ? "put" : "post");
Loading.value = false;
ElMessage.success(form.value.id ? "编辑成功" : "添加成功");
printStore.init();
router.back();
} catch (error) {
console.log(error);
}
}
//
async function tbPrintMachineDetailAjax() {
try {
const res = await tbPrintMachineDetail(route.query.id);
form.value = res;
printData.value.deviceName = res.config.deviceName
} catch (error) {
console.log(error);
}
}
onMounted(() => {
getPrintList();
if (route.query.id) {
tbPrintMachineDetailAjax(route.query.id);
}
QRCode.toCanvas(canvasRef.value, printData.value.outNumber, function (error) {
if (error) console.error(error)
// console.log('success!');
})
});
</script>
<style scoped lang="scss">
.device_container {
width: 100vw;
height: 100vh;
padding: 15px;
background-color: #f1f1f1;
}
.header {
height: 50px;
background-color: #fff;
border-radius: 10px;
display: flex;
align-items: center;
padding: 0 10px;
}
.d_content {
padding-top: 15px;
display: flex;
height: calc(100vh - 15px * 2 - 50px);
.d_list {
flex: 2;
border-radius: 10px;
background-color: #fff;
padding: 15px;
}
.menu_wrap {
flex: 1.5;
margin-left: 15px;
background-color: #fff;
border-radius: 10px;
padding: 0 15px;
.print_view {
position: relative;
.ewm {
$size: 50px;
width: $size;
height: $size;
position: absolute;
top: 0;
right: 0;
z-index: 99;
}
.header {
display: flex;
align-items: center;
.logo {
$size: 90px;
width: $size;
height: 30px;
object-fit: cover;
}
.title {
margin-left: 6px;
}
}
.number_wrap {
display: flex;
align-items: flex-end;
.num {
font-size: 18px;
font-weight: bold;
}
.info {
margin-left: 12px;
padding-bottom: 4px;
}
}
.time {
font-weight: bold;
}
}
.btn_wrap {
display: flex;
gap: 20px;
padding: 20px 0;
.btn {
flex: 1;
}
}
}
}
</style>

View File

@ -0,0 +1,8 @@
import icon_dev1 from '@/assets/icon_dev1.png'
import icon_dev2 from '@/assets/icon_dev2.png'
import icon_dev3 from '@/assets/icon_dev3.png'
export default {
cash: icon_dev2,
label: icon_dev1,
kitchen: icon_dev3
}

325
src/views/device/index.vue Normal file
View File

@ -0,0 +1,325 @@
<template>
<div class="device_container">
<div class="header" @click="router.back()">
<el-icon style="position: relative; top: 2px; margin-right: 4px" size="22">
<ArrowLeft />
</el-icon>
<el-text>设备管理</el-text>
</div>
<div class="d_content">
<div class="d_list">
<div class="row_title">打印设备</div>
<div class="row_list">
<div class="item" v-for="item in list" :key="item.id">
<div class="left">
<div class="icon">
<el-image :src="icons[item.subType]" style="width: 40px; height: 40px"></el-image>
</div>
<div class="info">
<div class="name">{{ item.name }}</div>
<div class="xh">{{ item.config.deviceName }}</div>
</div>
</div>
<div class="right">
<div class="switch">
<el-switch v-model="item.status" inline-prompt active-text="" inactive-text="" :active-value="1"
:inactive-value="0" width="90" @change="statusChange($event, item)" />
</div>
<div class="editor">
<el-text type="primary" @click="
router.push({
name: deviceRoute[item.subType],
query: { id: item.id },
})
">
编辑
</el-text>
<el-text type="primary" @click="showDelete(item)">删除</el-text>
</div>
</div>
</div>
</div>
<!-- <div class="row_title">云打印设备</div>
<div class="row_list">
<div class="item" v-for="item in list" :key="item.id">
<div class="left">
<div class="icon"></div>
<div class="info">
<div class="name">{{ item.name }}</div>
<div class="xh">{{ item.xh }}</div>
</div>
</div>
<div class="right">
<div class="switch">
<el-switch
v-model="item.state"
inline-prompt
active-text="开"
inactive-text="关"
:active-value="1"
:inactive-value="0"
width="90"
/>
</div>
<div class="editor">
<el-text type="primary">编辑</el-text>
<el-text type="primary">删除</el-text>
</div>
</div>
</div>
</div> -->
</div>
<div class="menu_wrap">
<div class="row" @click="router.push({ name: 'add_device' })">
<div class="icon" style="background-color: var(--primary-color)">
<el-image :src="icons.cash" style="width: 36px; height: 36px"></el-image>
</div>
<div class="info">
<div class="name">添加小票打印机</div>
<div class="intro">用来打印客户收银小票的打印机</div>
</div>
</div>
<div class="row" @click="router.push({ name: 'add_label' })">
<div class="icon" style="background-color: #79c3d5">
<el-image :src="icons.label" style="width: 38px; height: 38px"></el-image>
</div>
<div class="info">
<div class="name">添加标签打印机</div>
<div class="intro">用来打印商品标签的打印机</div>
</div>
</div>
<!-- <div class="row">
<div class="icon" style="background-color: #8fc783">
<el-image :src="icons.kitchen" style="width: 44px; height: 44px"></el-image>
</div>
<div class="info">
<div class="name">添加出品打印机</div>
<div class="intro">用来打印商品至厨房或出品台的打印机</div>
</div>
</div> -->
</div>
</div>
</div>
<el-dialog v-model="dialogVisible" title="注意" width="500">
<span class="dialog_content">确定删除该打印机吗</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="delLoading" @click="tbPrintMachineDeleteAjax">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import {
tbPrintMachineGet,
tbPrintMachineDelete,
tbPrintMachinePost,
} from "@/api/device";
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { useUser } from "@/store/user.js";
import { ElMessage } from "element-plus";
import icons from "./icons";
import { usePrint } from "@/store/print.js";
const printStore = usePrint();
const store = useUser();
const router = useRouter();
const list = ref([]);
const dialogVisible = ref(false);
const deleteId = ref("");
const delLoading = ref(false);
const deviceRoute = ref({
cash: "add_device",
label: "add_label",
kitchen: "add_kitchen",
});
async function statusChange(e, item) {
try {
await tbPrintMachinePost(item, "put");
tbPrintMachineGetAjax();
printStore.init();
} catch (error) {
console.log(error);
}
}
//
function showDelete(item) {
deleteId.value = item.id;
dialogVisible.value = true;
}
//
async function tbPrintMachineDeleteAjax() {
try {
delLoading.value = true;
await tbPrintMachineDelete({ id: deleteId.value });
delLoading.value = false;
dialogVisible.value = false;
ElMessage.success("删除成功");
tbPrintMachineGetAjax();
printStore.init();
} catch (error) {
console.log(error);
}
}
//
async function tbPrintMachineGetAjax() {
try {
const res = await tbPrintMachineGet({
shopId: store.userInfo.shopId,
page: 0,
pageSize: 100,
});
list.value = res.list;
} catch (error) {
console.log(error);
}
}
onMounted(() => {
tbPrintMachineGetAjax();
});
</script>
<style scoped lang="scss">
.dialog_content {
font-size: var(--el-font-size-base);
}
.dialog-footer {
padding: 0 var(--el-font-size-base) var(--el-font-size-base);
}
.device_container {
width: 100vw;
height: 100vh;
padding: 15px;
background-color: #f1f1f1;
}
.header {
height: 50px;
background-color: #fff;
border-radius: 10px;
display: flex;
align-items: center;
padding: 0 10px;
}
.d_content {
padding-top: 15px;
display: flex;
height: calc(100vh - 15px * 2 - 50px);
.d_list {
flex: 2;
border-radius: 10px;
background-color: #fff;
padding: 15px;
.row_title {
color: #555;
margin-bottom: 15px;
}
.row_list {
.item {
border-radius: 6px;
background-color: #f1f1f1;
margin-bottom: 10px;
padding: 15px;
display: flex;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.info {
display: flex;
flex-direction: column;
padding-left: 15px;
.xh {
color: #999;
padding-top: 6px;
}
}
}
.right {
display: flex;
flex-direction: column;
align-items: center;
.editor {
display: flex;
gap: 20px;
}
}
}
}
}
.menu_wrap {
flex: 1.5;
margin-left: 15px;
background-color: #fff;
border-radius: 10px;
padding: 0 15px;
.row {
display: flex;
align-items: center;
padding: 20px 0;
&:not(:last-child) {
border-bottom: 1px solid #ececec;
}
.icon {
--size: 60px;
width: var(--size);
height: var(--size);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.info {
display: flex;
flex-direction: column;
justify-content: center;
padding-left: 15px;
.name {
font-size: 20px;
}
.intro {
color: #999;
padding-top: 8px;
}
}
}
}
}
</style>

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

@ -0,0 +1,111 @@
<template>
<el-dialog title="退款" v-model="showDialog" width="400px" @close="reset">
<el-form ref="refundFormRef" :model="refundForm" :rules="refundFormRules" label-position="top">
<el-form-item label="退单数" prop="num">
<el-select v-model="refundForm.num" placeholder="请选择退单数" style="width: 100%;" @change="refundNumChange">
<el-option :label="item" :value="item" v-for="item in refundNumList" :key="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="退单金额">
<el-input v-model="refundForm.refundAmount" disabled placeholder="请选择退单数"></el-input>
</el-form-item>
<el-form-item label="退款原因">
<el-input v-model="refundForm.refundReason" type="textarea" placeholder="请输入退款原因"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="showDialog = false" style="width: 100%;">取消</el-button>
<el-button type="primary" :loading="refundLoading" @click="refundConfirm" style="width: 100%;">
</el-button>
</span>
</el-dialog>
</template>
<script setup>
import { returnGpOrder } from '@/api/group'
import { ref, reactive } from 'vue'
import { ElMessage } from "element-plus";
const emits = defineEmits(["success"]);
const newRow = ref('')
const showDialog = ref(false)
const refundLoading = ref(false)
const refundFormRef = ref(null)
const refundForm = reactive({
num: '',
orderId: '',
refundAmount: '',
refundDesc: '',
refundReason: ''
})
const refundFormRules = reactive({
num: [
{
required: true,
message: ' ',
trigger: 'change'
}
]
})
const refundNumList = ref([])
// 退
function refundConfirm() {
refundFormRef.value.validate(async valid => {
if (valid) {
try {
refundLoading.value = true
const res = await returnGpOrder(refundForm)
ElMessage.success('退单成功')
refundLoading.value = false
showDialog.value = false
emits('succcess')
} catch (error) {
console.log(error);
refundLoading.value = false
}
}
})
}
// 退
function refundNumChange(e) {
refundForm.refundAmount = Math.floor(newRow.value.orderAmount / newRow.value.number * e * 100) / 100
}
//
function show(row) {
showDialog.value = true;
newRow.value = row
let arr = []
for (let i = 1; i <= row.number - row.refundNumber; i++) {
arr.push(i)
}
refundNumList.value = arr
refundForm.orderId = row.id
}
//
function reset() {
newRow.value = ''
refundNumList.value = []
refundForm.orderId = ''
for (let key in refundForm) {
refundForm[key] = ''
}
refundFormRef.value.resetFields()
}
defineExpose({
show
});
</script>
<style scoped lang="scss">
.dialog-footer {
display: flex;
}
</style>

View File

@ -0,0 +1,525 @@
<!-- 扫码弹窗 -->
<template>
<div class="dialog">
<el-dialog :title="`核销${props.title}团购券`" width="600" v-model="dialogVisible" @open="reset" @close="close">
<div class="content">
<div class="left">
<el-image :src="icon" style="width: 60px; height: 60px"></el-image>
</div>
<div class="right" v-if="!userPayWait">
<div class="amount">
<span class="t">扫码核销</span>
<!-- <span class="n">{{ props.amount }}</span> -->
</div>
<div class="input">
<el-input ref="inputRef" v-model="scanCode"
style="height: calc(var(--el-component-size-large) + 30px)" placeholder="请扫描团购券" clearable
@change="inputChange"></el-input>
<div class="tips">注意扫码支付请保证输入框获得焦点输入内容结束后会自动核销请勿重复操作</div>
</div>
<!-- <div class="number_warp">
<div class="item" v-for="item in 9" :key="item" @click="inputHandle(item)">{{ item }}</div>
<div class="item disabled">.</div>
<div class="item" @click="inputHandle(0)">0</div>
<div class="item" @click="delHandle">
<el-icon>
<CloseBold />
</el-icon>
</div>
</div> -->
<div class="btn">
<el-button type="primary" style="width: 100%" v-loading="loading">立即核销</el-button>
</div>
</div>
<div class="pay_wait" v-else>
<div class="loading" v-loading="loading" element-loading-text="用户支付中..."></div>
<div class="btn">
<el-button type="primary" style="width: 100%" v-loading="checkPayStatusLoading"
@click="checkPayStauts">
<span v-if="!checkPayStatusLoading">查询用户支付状态</span>
<span v-else>查询中...</span>
</el-button>
</div>
<div class="btn">
<el-button style="width: 100%" @click="resetScanCode">
重新扫码
</el-button>
</div>
</div>
</div>
</el-dialog>
<el-dialog title="团购券详情" width="600" v-model="detailVisible">
<div class="group_detil">
<div class="shop_info" v-if="props.type == 1">
<el-image :src="groupDetail.images[0]" style="width: 50px;height: 50px;"></el-image>
<div class="info">
<div class="name">{{ groupDetail.productName }}</div>
<div class="price">
<span class="p">{{ groupDetail.salePrice }}</span>
<span class="o">{{ groupDetail.originPrice }}</span>
</div>
</div>
</div>
<div class="table">
<el-table :data="groupDetail.productList" border v-if="props.type == 1">
<el-table-column label="名称" prop="title"></el-table-column>
<el-table-column label="数量" prop="number"></el-table-column>
<el-table-column label="商品信息">
<template v-slot="scope">
<div class="shop_list">
<div class="item" v-for="(item, index) in scope.row.goods" :key="item.id">
<span class="dot"></span>
<div class="name">
<div class="t">{{ item.name }}</div>
<div class="t">x{{ item.groupNum }}</div>
</div>
</div>
</div>
</template>
</el-table-column>
</el-table>
<el-table ref="douyin_table" :data="groupDetail.goods" border v-else>
<el-table-column type="selection" width="55" />
<el-table-column label="名称" prop="title"></el-table-column>
<el-table-column label="价格" prop="amount"></el-table-column>
</el-table>
</div>
</div>
<div class="footer">
<el-button style="width: 100%;" @click="detailVisible = false">取消</el-button>
<el-button type="primary" style="width: 100%;" :loading="groupDetailLoading"
@click="groupOrdergroupScanHandle">确认核销</el-button>
</div>
</el-dialog>
<BindShop ref="BindShopRef" @success="submitHandle()" />
</div>
</template>
<script setup>
import _ from "lodash";
import { ref } from "vue";
import icon from "@/assets/icon_scan.png";
import { groupOrderorderInfo, groupOrdergroupScan, douyinfulfilmentcertificateprepare, douyincertificateprepare, thirdPartyCoupon_list, certificateprepare } from '@/api/group'
import { useUser } from "@/store/user.js";
import BindShop from './bindShop.vue'
const BindShopRef = ref(null)
const store = useUser();
import {
queryMembermember,
createMembermember,
membermemberScanPay,
accountPaymember,
} from "@/api/member/index.js";
import { ElMessage } from "element-plus";
import { useGlobal } from '@/store/global.js'
const global = useGlobal()
const emits = defineEmits(["success"]);
const props = defineProps({
title: {
type: String,
default: ''
},
type: {
type: Number,
default: 1
}
});
const dialogVisible = ref(false);
const scanCode = ref("");
const inputRef = ref(null);
const loading = ref(false);
const userPayWait = ref(false);
const checkPayStatusLoading = ref(false);
const fastOrder = ref('')
const groupDetailLoading = ref(false)
const groupDetail = ref({})
const detailVisible = ref(false)
// (使)
async function groupOrdergroupScanHandle() {
try {
switch (props.type) {
case 1:
{
groupDetailLoading.value = true
const res = await groupOrdergroupScan({
id: groupDetail.value.id
})
}
break;
case 2:
{
let encrypted_codes = douyin_table.value.getSelectionRows()
if (encrypted_codes.length) {
groupDetailLoading.value = true
let arr = encrypted_codes.map(item => item.encrypted_code)
console.log(encrypted_codes);
const res = await douyincertificateprepare({
verify_token: groupDetail.value.verify_token,
encrypted_codes: arr.join(','),
id: groupDetail.value.id
})
} else {
ElMessage.error('请选择核销项目')
return
}
}
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:
break;
}
groupDetailLoading.value = false
detailVisible.value = false
scanCode.value = ''
inputRef.value.focus();
ElMessage.success('核销成功')
emits('succcess')
} catch (error) {
groupDetailLoading.value = false
console.log('groupOrdergroupScanHandle.error', error);
}
}
const douyin_table = ref(null)
//
async function submitHandle() {
try {
loading.value = true
switch (props.type) {
case 1:
{
const res = await groupOrderorderInfo({
coupon: scanCode.value,
});
loading.value = false
groupDetail.value = res
detailVisible.value = true
}
break;
case 2:
{
const res = await douyinfulfilmentcertificateprepare({
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
groupDetail.value = res
detailVisible.value = true
setTimeout(() => {
groupDetail.value.goods.map(item => {
douyin_table.value.toggleRowSelection(item)
})
}, 100)
}
break;
default:
break;
}
} catch (error) {
loading.value = false
console.log('submitHandle.error', error);
if (error.code == 4399) {
BindShopRef.value.show()
}
}
}
//
function resetScanCode() {
userPayWait.value = false;
loading.value = false;
scanCode.value = "";
inputRef.value.focus();
}
//
function inputHandle(n) {
scanCode.value += n;
inputRef.value.focus();
}
//
function delHandle() {
if (!scanCode.value) return;
scanCode.value = scanCode.value.substring(0, scanCode.value.length - 1);
inputRef.value.focus();
}
//
// function enterHandle() {
// inputRef.value.focus()
// }
const inputChange = _.debounce(function (e) {
// console.log(e);
if (scanCode.value) {
submitHandle();
}
}, 500);
function show() {
global.updateData(false)
dialogVisible.value = true;
setTimeout(() => {
inputRef.value.focus();
}, 500);
}
function close() {
global.updateData(true)
dialogVisible.value = false;
}
function reset() {
loading.value = false;
scanCode.value = "";
}
defineExpose({
show,
close,
loading,
submitHandle
});
</script>
<style scoped lang="scss">
.footer {
display: flex;
padding: 0 14px 14px;
}
.group_detil {
padding: 14px;
.shop_info {
display: flex;
.info {
flex: 1;
padding-left: 10px;
.name {
font-size: 18px;
font-weight: bold;
color: #000;
}
.price {
.p {
font-size: 20px;
font-weight: bold;
color: red;
}
.o {
color: #999;
text-decoration: line-through;
margin-left: 10px;
}
}
}
}
.table {
padding-top: 14px;
}
.shop_list {
.item {
display: flex;
align-items: center;
&:not(:first-child) {
margin-top: 6px;
}
.dot {
$size: 6px;
width: $size;
height: $size;
border-radius: 50%;
background-color: #1890FF;
}
.name {
flex: 1;
display: flex;
margin-left: 10px;
align-items: center;
.t {
color: #333;
font-size: 14px;
width: 100px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-right: 10px;
}
}
.del {
font-size: 14px;
color: #999;
&:hover {
cursor: pointer;
color: #555;
}
}
}
}
}
.tips {
padding-top: 10px;
color: #999;
}
.dialog :deep(.el-dialog__body) {
padding: 0 !important;
}
.content {
display: flex;
.left {
width: 200px;
display: flex;
align-items: center;
justify-content: center;
background-color: #efefef;
}
.right {
flex: 1;
padding: var(--el-font-size-base);
.amount {
display: flex;
height: calc(var(--el-component-size-large) + 20px);
display: flex;
align-items: center;
justify-content: space-between;
color: var(--primary-color);
background-color: #555;
border-radius: 6px;
padding: 0 var(--el-font-size-base);
font-size: calc(var(--el-font-size-base) + 10px);
}
.input {
padding: var(--el-font-size-base) 0;
:deep(.el-input__inner) {
font-size: calc(var(--el-font-size-base) + 10px);
}
}
.number_warp {
--h: 50px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: var(--h) var(--h) var(--h) var(--h);
gap: 8px;
.item {
background-color: #dddddd;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(var(--el-font-size-base) + 10px);
border-radius: 4px;
&.disabled {
color: #999;
background-color: #efefef;
&:active {
background-color: #efefef;
}
}
&:active {
background-color: #b9b9b9;
}
}
}
// .btn {
// padding-top: 20px;
// }
}
.pay_wait {
flex: 1;
padding: 0 var(--el-font-size-base);
height: 400px;
padding-bottom: 100px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.loading {
width: 200px;
height: 200px;
--el-loading-spinner-size: 100px;
:deep(.el-loading-text) {
font-size: 20px;
}
}
.btn {
width: 200px;
padding-top: var(--el-font-size-base);
}
}
}
</style>

View File

@ -0,0 +1,595 @@
<template>
<div class="content">
<div class="cart_wrap card">
<div class="header">
<div class="left">
<el-select v-model="tableData.type" placeholder="核销类型" @change="typeChange">
<el-option v-for="item in typeList" :key="item.value" :value="item.value"
:label="item.label"></el-option>
</el-select>
<el-input placeholder="搜索订单" v-model="tableData.proName" @focus="
global.updateData(false)" @blur="global.updateData(true)"></el-input>
<el-select v-model="tableData.status" placeholder="订单状态">
<el-option v-for="item in statusList" :key="item.value" :value="item.value"
:label="item.label"></el-option>
</el-select>
<div class="btn">
<el-button type="primary" :icon="Search" @click="groupOrderlistAjax">搜索</el-button>
<el-button :icon="RefreshRight" :loading="tableData.resetLoading"
@click="resetHandle">重置</el-button>
</div>
</div>
<el-button type="warning" :icon="FullScreen" @click="showScanModalHandle">核销团购券</el-button>
</div>
<div class="tab_container">
<el-table :data="tableData.list" height="540px" v-loading="tableData.loading"
v-if="tableData.type == 1">
<el-table-column label="订单号" prop="orderNo" width="100px"></el-table-column>
<el-table-column label="优惠卷" prop="proImg" width="200px">
<template v-slot="scope">
<div class="info_wrap">
<el-image :src="scope.row.proImg" style="width: 40px;height: 40px;flex-shrink: 0;" />
<div class="t">{{ scope.row.proName }}</div>
</div>
</template>
</el-table-column>
<!-- <el-table-column label="支付方式" prop="payType">
<template v-slot="scope">
{{ payTypeFilter(scope.row.payType) }}
</template>
</el-table-column> -->
<el-table-column label="订单金额" prop="orderAmount">
<template v-slot="scope">
{{ scope.row.orderAmount }}
</template>
</el-table-column>
<el-table-column label="实付金额" prop="payAmount">
<template v-slot="scope">
{{ scope.row.payAmount }}
</template>
</el-table-column>
<el-table-column label="下单数量" prop="number">
<template v-slot="scope">
{{ scope.row.number }}
</template>
</el-table-column>
<el-table-column label="状态" prop="status">
<template v-slot="scope">
{{ statusFilter(scope.row.status) }}
</template>
</el-table-column>
<el-table-column label="操作" fixed="right">
<template v-slot="scope">
<el-button type="primary" size="small"
v-if="scope.row.refundAble == 1 && scope.row.status == 'unused'"
@click="refundDialogRef.show(scope.row)">退款</el-button>
<el-button type="primary" size="small" disabled v-else>退款</el-button>
</template>
</el-table-column>
</el-table>
<el-table :data="tableData.list" height="540px" v-loading="tableData.loading"
v-if="tableData.type == 2">
<el-table-column label="抖音订单号" prop="d_order_id" width="240"></el-table-column>
<el-table-column label="总金额" prop="pay_amount" width="100">
<template v-slot="scope">
<span style="color: var(--primary-color);">{{ scope.row.pay_amount }}</span>
</template>
</el-table-column>
<el-table-column label="商品信息" prop="douyinCodeGoods">
<template v-slot="scope">
<div class="goods_list">
<div class="row" v-for="item in scope.row.douyinCodeGoods" :key="item.id">
<div class="item" style="width: 240px;">
{{ item.title }}
</div>
<div class="item" style="width: 100px;color: var(--primary-color);">
{{ item.pay_amount }}
</div>
<div class="item"
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 }}
</el-tag>
</div>
<div class="item" style="flex: 1;display: flex;justify-content: flex-end;">
<el-button type="danger" size="small" v-if="item.is_show_cacel_banner"
@click="cacelDouyinHandle(item)">撤销</el-button>
</div>
</div>
</div>
</template>
</el-table-column>
</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 class="pagination">
<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>
</div>
<scanGroup ref="scanGroupRef" :title="typeList.find(item => item.value == tableData.type).label"
:type="tableData.type" @succcess="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>
</template>
<script setup>
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 { ElMessageBox, ElMessage } from 'element-plus'
import { ref, onMounted, reactive } from 'vue'
import scanGroup from './components/scanGroup.vue'
import refundDialog from './components/refundDialog.vue'
import { useUser } from "@/store/user.js"
import BindShop from './components/bindShop.vue'
import { shell } from 'electron'
const store = useUser()
import { useGlobal } from '@/store/global.js'
const global = useGlobal()
const scanGroupRef = ref(null)
const refundDialogRef = ref(null)
function typeStatus(t) {
const m = {
0: 'warning',
1: 'success',
2: 'danger'
}
return m[t]
}
const tableData = reactive({
resetLoading: false,
proName: '',
type: 3,
status: '',
loading: false,
list: [],
page: 1,
size: 10,
total: 0
})
//
function payTypeFilter(t) {
const m = {
wechatPay: '微信支付',
aliPay: '支付宝支付'
}
return m[t]
}
//
const typeList = reactive([
{
value: 1,
label: '自营'
},
{
value: 2,
label: '抖音'
},
{
value: 3,
label: '美团'
}
])
//
const originStatus = [
{
value: 'unpaid',
label: '待付款'
},
{
value: 'unused',
label: '待使用'
},
{
value: 'closed',
label: '已完成'
},
{
value: 'refunding',
label: '退款中'
},
{
value: 'refund',
label: '已退款'
},
{
value: 'cancelled',
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) {
return originStatus.find(item => item.value == t)?.label
}
//
function paginationChange(e) {
groupOrderlistAjax()
}
//
function resetHandle() {
tableData.proName = ''
tableData.status = ''
tableData.page = 1
tableData.resetLoading = true
groupOrderlistAjax()
}
//
function cacelDouyinHandle(item) {
ElMessageBox.confirm(
'是否撤销该团购?',
'注意').then(async () => {
await douyinfulfilmentcertificatecancel({ verify_id: item.verify_id, certificate_id: item.certificate_id })
ElMessage.success('撤销成功')
groupOrderlistAjax()
}).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() {
try {
tableData.loading = true
let res = ''
switch (tableData.type) {
case 1:
//
res = await groupOrderlist({
shopId: store.userInfo.shopId,
proName: tableData.proName,
status: tableData.status,
page: tableData.page,
size: tableData.size
})
tableData.resetLoading = false
tableData.loading = false
tableData.list = res.list
tableData.total = res.total
break;
case 2:
//
res = await douyinorderlist({
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.loading = false
tableData.list = res.list
tableData.total = res.count
break;
default:
break;
}
} catch (error) {
tableData.loading = false
console.log(error);
}
}
onMounted(() => {
typeChange(tableData.type)
})
</script>
<style scoped lang="scss">
.pagination {
display: flex;
padding: 0 14px;
}
.info_wrap {
display: flex;
align-items: flex-start;
.t {
width: 100px;
margin-left: 10px;
}
}
.cart_wrap {
flex: 2;
}
.right_card {
flex: 1;
height: 100%;
margin-left: var(--el-font-size-base);
}
.header {
display: flex;
padding: 14px;
justify-content: space-between;
border-bottom: 1px solid #ececec;
.left {
display: flex;
gap: 10px;
.btn {
display: flex;
}
}
.menus {
display: flex;
padding: 0 10px;
.item {
padding: 0 10px;
display: flex;
align-items: center;
position: relative;
span {
font-size: var(--el-font-size-base);
}
&.active {
&::after {
content: "";
width: 70%;
height: 4px;
border-radius: 4px;
position: absolute;
bottom: 0;
left: 15%;
background-color: var(--primary-color);
}
span {
color: var(--primary-color);
}
}
}
}
.all {
display: flex;
align-items: center;
}
}
.tab_container {
padding: 0 var(--el-font-size-base) var(--el-font-size-base);
.tab_head {
padding-bottom: var(--el-font-size-base);
}
.overflow_y {
height: calc(100vh - 225px);
overflow-y: auto;
}
.tab_list {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: auto;
gap: var(--el-font-size-base);
.item {
background-color: #efefef;
border-radius: 6px;
overflow: hidden;
border: 2px solid #fff;
&.active {
border-color: var(--primary-color);
}
&:hover {
cursor: pointer;
}
.tab_title {
height: var(--el-component-size-large);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
color: #fff;
&.subscribe {
background-color: var(--el-color-success);
}
&.closed {
background-color: #999;
}
&.opening {
background-color: var(--primary-color);
}
&.cleaning {
background-color: var(--el-color-danger);
}
}
.tab_cont {
height: 120px;
display: flex;
align-items: center;
justify-content: center;
.icon {
color: #555;
font-size: 30px;
transform: rotate(45deg);
}
}
}
}
}
.goods_list {
.row {
display: flex;
align-items: center;
&:not(:first-child) {
margin-top: 10px;
}
}
}
</style>

View File

@ -1,568 +0,0 @@
<template>
<div class="content">
<div class="cart_wrap card">
<div class="menu_top">
<div class="menu">
<el-icon class="icon">
<TakeawayBox />
</el-icon>
<el-text class="t">(1)</el-text>
</div>
<div class="number">
<el-text class="t">#2</el-text>
</div>
<div class="select_user">
<el-icon class="icon">
<UserFilled />
</el-icon>
<el-text class="t">选择会员</el-text>
<el-icon class="arrow">
<ArrowRight />
</el-icon>
</div>
</div>
<div class="shop_operation">
<div class="shop_list">
<div class="item" v-for="item in 3" :key="item">
<div class="name_wrap">
<span>鲜肉</span>
<span>1.00</span>
</div>
<div class="sku_list">
<div class="tag">默认1份</div>
<div class="tag"></div>
<div class="tag">半糖</div>
</div>
<div class="num">
<el-text>X1</el-text>
</div>
</div>
</div>
<div class="operation_wrap">
<div class="item">
<el-icon class="icon">
<SemiSelect />
</el-icon>
</div>
<div class="item number">
<el-text class="num">1</el-text>
</div>
<div class="item">
<el-icon class="icon add">
<CloseBold />
</el-icon>
</div>
<div class="item">
<el-icon class="icon">
<Filter />
</el-icon>
<el-text class="t">规格</el-text>
</div>
<div class="item">
<el-icon class="icon">
<ShoppingBag />
</el-icon>
<el-text class="t">赠送</el-text>
</div>
<div class="item">
<el-icon class="icon">
<Box />
</el-icon>
<el-text class="t">打包</el-text>
</div>
<div class="item">
<el-icon class="icon">
<Delete />
</el-icon>
<el-text class="t">删除</el-text>
</div>
<div class="item">
<el-icon class="icon">
<Sell />
</el-icon>
<el-text class="t">挂单</el-text>
</div>
<div class="item">
<el-icon class="icon">
<RefreshRight />
</el-icon>
<el-text class="t">清空</el-text>
</div>
</div>
</div>
<div class="footer">
<div class="top">
<div class="left">
<div class="selected">
<el-icon class="icon">
<CircleCheckFilled />
</el-icon>
</div>
<el-text class="t">打包(0.00)</el-text>
</div>
<div class="num-wrap">
<el-text>共6种商品7</el-text>
</div>
</div>
<div class="btm">
<div class="editor">
<el-icon>
<Edit />
</el-icon>
</div>
<div class="button">
<div class="js">
<el-text class="t">3.09</el-text>
<el-text class="t">结算</el-text>
</div>
</div>
</div>
</div>
</div>
<div class="shop_manage card">
<div class="header">
<div class="menus">
<div class="item active">
<el-text>全部</el-text>
</div>
<div class="item">
<el-text>咖啡</el-text>
</div>
<div class="item">
<el-text>测试</el-text>
</div>
<div class="item">
<el-text>套餐</el-text>
</div>
</div>
<div class="all">
<el-icon class="icon">
<Menu />
</el-icon>
</div>
</div>
<div class="search_wrap">
<div class="input">
<el-input placeholder="商品名称或首字母简称" prefix-icon="Search"></el-input>
</div>
<el-button :icon="shopListType == 'text' ? 'PictureRounded' : 'PriceTag'" @click="changeShopListType"></el-button>
</div>
<div class="shop_list" :class="{ img: shopListType == 'img' }">
<div class="item_wrap" v-for="item in 7" :key="item">
<div class="item">
<div class="dot">2</div>
<div class="cover" v-if="shopListType == 'img'">
<el-image src="https://img1.baidu.com/it/u=2183780444,2807225961&fm=253&fmt=auto&app=138&f=JPEG?w=600&h=400"
style="width: 100%;height: 100%;" fit="cover"></el-image>
</div>
<div class="name"><el-text>酱香拿铁</el-text></div>
<div class="empty" v-if="shopListType == 'text'"></div>
<div class="price">
<el-text>0.03</el-text>
</div>
</div>
</div>
</div>
</div>
</div>
<skuModal />
</template>
<script setup>
import skuModal from '@/components/skuModal.vue'
import { ref } from 'vue'
const shopListType = ref('text')
//
function changeShopListType() {
if (shopListType.value == 'text') {
shopListType.value = 'img'
} else {
shopListType.value = 'text'
}
}
</script>
<style scoped lang="scss">
.cart_wrap {
width: 550px;
height: 100%;
display: flex;
flex-direction: column;
.menu_top {
flex-shrink: 0;
display: flex;
height: 60px;
$fs: 20px;
.menu {
background-color: var(--el-color-warning);
color: #fff;
width: 100px;
display: flex;
align-items: center;
justify-content: center;
.icon {
font-size: 24px;
position: relative;
top: 2px;
}
.t {
color: #fff;
margin-left: 4px;
font-size: $fs;
}
}
.number {
flex: 1;
display: flex;
align-items: center;
background-color: var(--el-color-info-light-7);
padding-left: 20px;
.t {
font-size: $fs;
}
}
.select_user {
display: flex;
align-items: center;
background-color: var(--el-color-info-light-8);
padding: 0 20px;
.icon {
color: var(--el-color-primary);
font-size: 24px;
}
.t {
font-size: $fs;
padding: 0 10px;
}
.arrow {
color: #999;
font-size: 20px;
position: relative;
top: 2px;
}
}
}
.shop_operation {
flex: 1;
display: flex;
.shop_list {
flex: 1;
height: calc(100vh - 40px - 60px - 130px);
overflow-y: auto;
border-right: 1px solid #ececec;
.item {
padding: 20px;
&:not(:last-child) {
border-bottom: 1px solid #ececec;
}
.name_wrap {
display: flex;
justify-content: space-between;
font-size: 20px;
}
.sku_list {
display: flex;
padding-top: 10px;
.tag {
padding: 4px 10px;
background-color: var(--el-color-danger);
color: #fff;
margin-right: 10px;
}
}
.num {
padding-top: 10px;
display: flex;
justify-content: flex-end;
}
}
}
.operation_wrap {
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
.item {
width: 100px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: #efefef;
border-radius: 6px;
&.number {
background-color: transparent;
height: auto;
}
.num {
color: #333;
font-size: 18px;
}
.icon {
color: #333;
}
.add {
transform: rotate(-45deg);
}
.t {
margin-left: 4px;
}
}
}
}
}
.footer {
padding: 20px;
border-top: 1px solid #ececec;
.left {
display: flex;
align-items: center;
.selected {
.icon {
display: block;
font-size: 18px;
}
}
.t {
margin-left: 4px;
}
}
.top {
display: flex;
justify-content: space-between;
}
.btm {
$h: 70px;
display: flex;
height: $h;
padding-top: 20px;
gap: 20px;
.editor {
width: $h;
border: 1px solid #ececec;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #555;
font-size: 22px;
}
.button {
flex: 1;
display: flex;
align-items: center;
border-radius: 6px;
background-color: var(--primary-color);
padding: 0 20px;
&:hover {
cursor: pointer;
background-color: var(--el-color-primary-light-3);
}
&:active {
background-color: var(--el-color-primary-dark-2);
}
.js {
width: 100%;
display: flex;
justify-content: space-between;
.t {
color: #fff;
font-size: 20px;
}
:deep(span) {
width: auto;
display: inline;
}
}
}
}
}
.shop_manage {
flex: 1;
margin-left: 20px;
height: 100%;
.header {
display: flex;
height: 80px;
justify-content: space-between;
border-bottom: 1px solid #ececec;
.menus {
display: flex;
padding: 0 10px;
.item {
padding: 0 10px;
display: flex;
align-items: center;
position: relative;
span {
font-size: 24px;
}
&.active {
&::after {
content: "";
width: 70%;
height: 2px;
border-radius: 4px;
position: absolute;
bottom: 0;
left: 15%;
background-color: var(--primary-color);
}
span {
color: #333;
}
}
}
}
.all {
width: 80px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&::before {
content: "";
height: 60%;
border-left: 1px solid #ececec;
position: absolute;
left: 0;
top: 20%;
}
.icon {
color: #555;
font-size: 20px;
}
}
}
.search_wrap {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 20px;
}
.shop_list {
max-height: calc(100vh - 40px - 80px - 80px);
overflow-y: auto;
display: flex;
flex-wrap: wrap;
padding: 0 10px;
&.img {
.item_wrap {
width: 25%;
}
}
.item_wrap {
width: 20%;
padding: 0 10px;
padding-bottom: 20px;
}
.item {
border: 1px solid #ececec;
border-radius: 10px;
overflow: hidden;
position: relative;
&:hover {
cursor: pointer;
}
.dot {
padding: 0 8px;
background-color: var(--el-color-danger);
color: #fff;
border-radius: 20px;
position: absolute;
top: 4px;
right: 4px;
z-index: 1;
font-size: 12px;
}
.cover {
width: 100%;
height: 300px;
}
.name {
padding: 14px 10px;
span {
font-weight: bold;
font-size: 18px;
}
}
.empty {
height: 50px;
}
.price {
padding: 10px 20px;
background-color: var(--primary-color);
span {
color: #fff;
font-weight: bold;
font-size: 18px;
}
}
}
}
}
</style>

View File

@ -0,0 +1,379 @@
<template>
<div class="operation_wrap">
<div class="item" :class="{ disabled: props.item.number <= 1 || !props.item.id }" @click="numberChange('sub')">
<el-icon class="icon">
<SemiSelect />
</el-icon>
</div>
<div class="item number" @click="props.item.id && takeFoodCodeRef.show()">
<el-text class="num">{{ formatDecimal(props.item.number || 1, 2, true) }}</el-text>
</div>
<div class="item" @click="numberChange('add')">
<el-icon class="icon add">
<CloseBold />
</el-icon>
</div>
<!-- <div class="item" :class="{ disabled: (props.item.id && !props.item.tbProductSpec) }" @click="showSkuModal">
<el-icon class="icon">
<Filter />
</el-icon>
<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 class="item" :class="{ disabled: props.item.isGift == 'true' }" @click="giftPackHandle('isGift')">
<el-icon class="icon">
<ShoppingBag />
</el-icon>
<el-text class="t">赠送</el-text>
</div>
<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">
<Box />
</el-icon>
<el-text class="t">打包</el-text>
</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)">
<el-icon class="icon">
<Delete />
</el-icon>
<el-text class="t">删除</el-text>
</div>
<div class="item" @click="props.item.id && emit('pending', props.item)">
<el-icon class="icon">
<Sell />
</el-icon>
<el-text class="t">挂单</el-text>
</div>
<div class="item" @click="tableMergingHandle"
v-if="shopStore.info.registerType == 'restaurant' && props.item.tableId && props.item.orderId">
<el-icon class="icon">
<EditPen />
</el-icon>
<el-text class="t">转桌</el-text>
</div>
<div class="item" @click="props.item.id && emit('clearCart')">
<el-icon class="icon">
<RefreshRight />
</el-icon>
<el-text class="t">清空</el-text>
</div>
</div>
<takeFoodCode ref="takeFoodCodeRef" title="修改商品数量" placeholder="请输入商品数量" @success="updateNumber" />
<!-- 购物车选择规格 -->
<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>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import takeFoodCode from '@/components/takeFoodCode.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({
item: {
type: Object,
default: {}
}
})
const emit = defineEmits(['confirm', 'delete', 'pending', 'clearCart', 'merging'])
const takeFoodCodeRef = ref(null)
const skuModalRef = ref([])
//
function giftPackHandle(key) {
if (!props.item.id) return
if (props.item[key] == 'true') {
props.item[key] = false
} else {
props.item[key] = true
}
emit('confirm', props.item)
}
//
function numberChange(t) {
console.log(props.item);
if (!props.item.id) return
switch (t) {
case 'sub':
if (props.item.number <= 1) return
props.item.number--
break;
case 'add':
props.item.number++
break;
default:
break;
}
emit('confirm', props.item)
}
//
function updateNumber(num) {
if (!props.item.id) return
props.item.number = num
emit('confirm', props.item)
}
//
function showSkuModal() {
if (!props.item.id) return
if (props.item.tbProductSpec && props.item.tbProductSpec.specList) {
skuModalRef.value.show(props.item, 'cart')
}
}
//
function skuConfirm(e) {
if (!props.item.id) return
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 */
//
function tableMergingHandle() {
if (props.item.id) {
emit('merging')
}
}
</script>
<style scoped lang="scss">
.operation_wrap {
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
.item {
width: 70px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
background-color: #efefef;
border-radius: 6px;
&.disabled {
.t {
color: #999;
}
.icon {
color: #999;
}
}
&.number {
background-color: transparent;
height: auto;
}
.num {
color: #333;
font-size: 18px;
}
.icon {
color: #333;
}
.add {
transform: rotate(-45deg);
}
.t {
margin-left: 4px;
}
}
}
.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>

View File

@ -0,0 +1,95 @@
<!-- 分类 -->
<template>
</template>
<script setup>
import { queryCategory } from '@/api/product'
import { ref, onMounted } from 'vue'
import { useUser } from "@/store/user.js"
const store = useUser()
const categorys = ref([])
const categorysActive = ref(0)
//
async function queryCategoryAjax() {
try {
const res = await queryCategory({
shopId: store.userInfo.shopId,
page: 1,
pageSize: 100
})
categorys.value = res.list
} catch (error) {
console.log(error)
}
}
onMounted(() => {
queryCategoryAjax()
})
</script>
<style scoped lang="scss">
.header {
display: flex;
height: 80px;
justify-content: space-between;
border-bottom: 1px solid #ececec;
}
.menus {
display: flex;
padding: 0 10px;
.item {
padding: 0 10px;
display: flex;
align-items: center;
position: relative;
span {
font-size: 24px;
}
&.active {
&::after {
content: "";
width: 70%;
height: 2px;
border-radius: 4px;
position: absolute;
bottom: 0;
left: 15%;
background-color: var(--primary-color);
}
span {
color: #333;
}
}
}
}
.all {
width: 80px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&::before {
content: "";
height: 60%;
border-left: 1px solid #ececec;
position: absolute;
left: 0;
top: 20%;
}
.icon {
color: #555;
font-size: 20px;
}
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<el-drawer
size="100%"
:with-header="false"
direction="btt"
v-model="dialogVisible"
>
<div class="drawer_wrap">
<div class="pay_wrap">
<fastPayCard
ref="fastPayCardRef"
@paySuccess="paySuccess"
@close="dialogVisible = false"
/>
</div>
</div>
</el-drawer>
</template>
<script setup>
import { ref } from "vue";
import fastPayCard from "@/components/fastPayCard.vue";
const emit = defineEmits(["paySuccess"]);
const dialogVisible = ref(false);
const fastPayCardRef = ref(null);
//
function paySuccess() {
dialogVisible.value = false;
emit("paySuccess");
}
function show() {
dialogVisible.value = true;
fastPayCardRef.value.reset();
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.drawer_wrap {
width: 100%;
height: 100%;
display: flex;
padding: var(--el-font-size-base) 0;
.pay_wrap {
flex: 1;
}
}
</style>

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,200 @@
<template>
<div class="orderbox_right_inputkeyboard">
<div class='keyboard' @click.stop='_handleKeyPress'>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='1'>1</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='2'>2</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='3'>3</el-button>
</div>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='4'>4</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='5'>5</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='6'>6</el-button>
</div>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='7'>7</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='8'>8</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='9'>9</el-button>
</div>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='.'>.</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='0'>0</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='D'>c</el-button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import { useRoute } from "vue-router"
import { ElMessage } from 'element-plus'
import lodash_ from 'lodash'
const moneys = reactive({
money: ''
})
const emit = defineEmits(["consumeFees"])
watch(() => moneys.money, (newval, oldval) => {
console.log(newval)
emit('consumeFees', newval)
});
const _handleKeyPress = (e) => {
console.log('点击传e', e.target.dataset.num);
let num = e.target.dataset.num;
//
// -1
if (num == -1) return false;
switch (String(num)) {
//
case '.':
_handleDecimalPoint();
break;
//
case 'D':
_handleDeleteKey();
break;
default:
_handleNumberKey(num);
break;
}
}
//
const _handleNumberKey = (num) => {
if (num == undefined) {
return
}
if (moneys.money.length == 10) {
return
}
console.log(num, 247)
let S = moneys.money;
//2
if (S.indexOf('.') > -1 && S.substring(S.indexOf('.') + 1).length < 2)
moneys.money = S + num;
//
if (!(S.indexOf('.') > -1)) {
//0
if (num == 0 && S.length == 0)
moneys.money = '0.';
else {
if (S.length && Number(S.charAt(0)) === 0) return;
moneys.money = S + num;
}
}
}
//
const _handleDecimalPoint = () => {
//
if (moneys.money.indexOf('.') > -1) return false;
//0
if (!moneys.money.length) {
moneys.money = '0.';
} else {
//
moneys.money = moneys.money + '.';
}
}
//
const _handleDeleteKey = () => {
let S = moneys.money;
//
if (!S.length) return false;
//
moneys.money = S.substring(0, S.length - 1);
}
</script>
<style scoped lang="scss">
.orderbox_right_top {
color: #fff;
width: 100%;
background: #8b008b;
padding: 10px;
border-radius: 10px;
.orderbox_right_topdiv:nth-child(1) {
margin-top: 0px;
}
.orderbox_right_topdiv {
display: flex;
margin-top: 10px;
justify-content: space-between;
}
.orderbox_right_top_item {
position: relative;
background: #fff;
padding: 6px 10px;
display: flex;
margin-top: 10px;
border-radius: 10px;
justify-content: space-between;
align-items: center;
.orderbox_right_top_item_one {
display: flex;
align-items: center;
.orderbox_right_top_item_onespan {
color: black;
margin-left: 10px;
font-size: 16px;
}
}
.orderbox_right_top_item_tow {
color: black;
margin-left: 10px;
font-size: 14px;
font-weight: bold;
}
}
}
.orderbox_right_input {
width: 100%;
margin-top: 10px;
}
.orderbox_right_inputkeyboard {
margin-top: 20px;
.keyboard {
width: 100%;
background: #FFFFFF;
.key-row {
display: flex;
display: -webkit-flex;
position: relative;
height: 8vh;
line-height: 8vh;
}
}
.keyboard .key-cell {
flex: 1;
-webkit-box-flex: 1;
font-size: 30px;
display: flex;
justify-content: center;
align-items: center;
}
}
.orderbox_right_button {
position: absolute;
width: 90%;
left: 50%;
bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
transform: translateX(-50%) !important;
}
</style>

View File

@ -0,0 +1,481 @@
<template>
<div class="orderbox">
<div class="orderbox_left">
<el-table :data="tableData" height="90%" style="width: 100%;margin-top: 10px;">
<el-table-column prop="date" label="昵称" width="" />
<el-table-column prop="name" label="手机" width="" />
<el-table-column prop="address" label="编号" />
<el-table-column prop="address" label="等级" />
<el-table-column prop="address" label="积分" />
<el-table-column prop="address" label="余额" />
</el-table>
</div>
<div class="orderbox_right">
<div class="orderbox_right_top">
<div class="orderbox_right_topdiv">
<span>会员昵称</span>
<span>admin</span>
</div>
<div class="orderbox_right_topdiv">
<span>手机号码</span>
<span>1999999999999</span>
</div>
<div class="orderbox_right_topdiv">
<span>会员编号</span>
<span>1245</span>
</div>
<div class="orderbox_right_topdiv">
<span>会员等级</span>
<span>未设置</span>
</div>
<div class="orderbox_right_top_item">
<div class="orderbox_right_top_item_one">
<el-icon :size="24" style="color:#ffbc42 ;">
<Money />
</el-icon>
<span class="orderbox_right_top_item_onespan">会员积分</span>
</div>
<div class="orderbox_right_top_item_tow">0</div>
</div>
<div class="orderbox_right_top_item" @click="stored = true">
<div class="orderbox_right_top_item_one">
<el-icon :size="24" style="color:#00b58d ;">
<Box />
</el-icon>
<span class="orderbox_right_top_item_onespan">储值余额</span>
</div>
<div class="orderbox_right_top_item_tow">
<span>0</span>
<el-icon size="10">
<ArrowRight />
</el-icon>
</div>
</div>
</div>
<keyboard ></keyboard>
<!-- <div class="orderbox_right_input">
<el-input placeholder="请输入会员手机号或者编号" v-model="moneys.money" clearable @input="inputChange"></el-input>
</div>
<div class="orderbox_right_inputkeyboard">
<div class='keyboard' @click.stop='_handleKeyPress'>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='1'>1</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='2'>2</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='3'>3</el-button>
</div>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='4'>4</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='5'>5</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='6'>6</el-button>
</div>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='7'>7</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='8'>8</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='9'>9</el-button>
</div>
<div class='key-row'>
<el-button type="primary" plain class='key-cell cell_b' data-num='.'>.</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='0'>0</el-button>
<el-button type="primary" plain class='key-cell cell_b' data-num='D'>c</el-button>
</div>
</div>
</div> -->
<div class="orderbox_right_button">
<el-button style="width: 35%;" @click="recharge = true">添加会员</el-button>
<el-button style="width: 60%;" type="primary" @click="onSubmit">确认</el-button>
</div>
</div>
<el-dialog v-model="stored" title="余额明细" width="500" :before-close="handleClose">
<div class="dialog_footer" v-for="(iten, index) in 6" :key="index">
<div class="dialog_footer_left">
<span>微信用户</span>
<span>2021-02-22 18:05:53</span>
</div>
<div class="dialog_footer_right">
<span>19000</span>
<span>26300</span>
</div>
</div>
</el-dialog>
<el-dialog v-model="recharge" title="会员充值" width="800" :before-close="handlerecharge">
<div class="recharge_footer">
<div class="recharge_footer_item">
qq
</div>
<div class="recharge_footer_itemright">
<div class="recharge_footer_itemright_input">
<div>充值金额</div>
<div v-if="moneys.money">{{ moneys.money ? moneys.money : '请输入充值金额' }}</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import { useRoute } from "vue-router"
import { ElMessage } from 'element-plus'
import lodash_ from 'lodash'
import keyboard from '@/views/home/components/keyboard.vue'
const stored = ref(false)//
// const inputChange = lodash_.debounce(function (e) {
// productqueryCommodityInfoAjax()
// }, 500)
const handleClose = () => {
stored.value = !stored.value
}
const recharge = ref(false)//
const onSubmit = () => {
ElMessage({
message: '请输入订单号查询',
type: 'warning',
})
}
const handlerecharge = () => {
recharge.value = !recharge.value
}
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: '1',
},
{
date: '2016-05-02',
name: 'Tom',
address: '1',
},
{
date: '2016-05-04',
name: 'Tom',
address: '1',
},
{
date: '2016-05-01',
name: 'Tom',
address: '1',
},
]
const moneys = reactive({
money: ''
})
const _handleKeyPress = (e) => {
console.log('点击传e', e.target.dataset.num);
let num = e.target.dataset.num;
//
// -1
if (num == -1) return false;
switch (String(num)) {
//
case '.':
_handleDecimalPoint();
break;
//
case 'D':
_handleDeleteKey();
break;
default:
_handleNumberKey(num);
break;
}
}
watch(() => moneys.money, (newVal, oldVal) => {
console.log(`New: ${newVal}, Old: ${oldVal}`)
})
//
const _handleNumberKey = (num) => {
if (num == undefined) {
return
}
if (moneys.money.length == 10) {
return
}
console.log(num, 247)
let S = moneys.money;
//2
if (S.indexOf('.') > -1 && S.substring(S.indexOf('.') + 1).length < 2)
moneys.money = S + num;
//
if (!(S.indexOf('.') > -1)) {
//0
if (num == 0 && S.length == 0)
moneys.money = '0.';
else {
if (S.length && Number(S.charAt(0)) === 0) return;
moneys.money = S + num;
}
}
}
//
const _handleDecimalPoint = () => {
//
if (moneys.money.indexOf('.') > -1) return false;
//0
if (!moneys.money.length) {
moneys.money = '0.';
} else {
//
moneys.money = moneys.money + '.';
}
}
//
const _handleDeleteKey = () => {
let S = moneys.money;
//
if (!S.length) return false;
//
moneys.money = S.substring(0, S.length - 1);
}
</script>
<style scoped lang="scss">
.orderbox {
display: flex;
height: 100%;
.orderbox_left {
width: 70%;
height: 100%;
background: #fff;
border-radius: 10px;
padding: 16px 0;
}
.orderbox_right {
position: relative;
width: 30%;
padding: 20px;
margin-left: 10px;
background: #fff;
border-radius: 10px;
.orderbox_right_top {
color: #fff;
width: 100%;
background: #8b008b;
padding: 10px;
border-radius: 10px;
.orderbox_right_topdiv:nth-child(1) {
margin-top: 0px;
}
.orderbox_right_topdiv {
display: flex;
margin-top: 10px;
justify-content: space-between;
}
.orderbox_right_top_item {
position: relative;
background: #fff;
padding: 6px 10px;
display: flex;
margin-top: 10px;
border-radius: 10px;
justify-content: space-between;
align-items: center;
.orderbox_right_top_item_one {
display: flex;
align-items: center;
.orderbox_right_top_item_onespan {
color: black;
margin-left: 10px;
font-size: 16px;
}
}
.orderbox_right_top_item_tow {
color: black;
margin-left: 10px;
font-size: 14px;
font-weight: bold;
}
}
}
.orderbox_right_input {
margin-top: 10px;
}
.orderbox_right_inputkeyboard {
margin-top: 20px;
.keyboard {
width: 100%;
background: #FFFFFF;
.key-row {
display: flex;
display: -webkit-flex;
position: relative;
height: 8vh;
line-height: 8vh;
}
}
.keyboard .key-cell {
flex: 1;
-webkit-box-flex: 1;
font-size: 30px;
display: flex;
justify-content: center;
align-items: center;
}
}
.orderbox_right_button {
position: absolute;
width: 90%;
left: 50%;
bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
transform: translateX(-50%) !important;
}
}
.dialog_footer:nth-child(1) {
margin-top: 0;
}
.dialog_footer {
margin-top: 10px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #ccc;
padding-bottom: 6px;
.dialog_footer_left {
display: flex;
flex-direction: column;
align-items: flex-start;
span:nth-child(1) {
font-size: 18px;
font-weight: 500;
}
span:nth-child(2) {
margin-top: 10px;
color: #333;
}
}
.dialog_footer_right {
display: flex;
flex-direction: column;
align-items: flex-end;
span:nth-child(1) {
color: #fc3d3d;
font-size: 16px;
}
span:nth-child(2) {
margin-top: 10px;
font-size: 14px;
}
}
}
.recharge_footer {
display: flex;
justify-content: space-between;
.recharge_footer_item {
width: 50%;
}
.recharge_footer_itemright {
width: 50%;
position: relative;
bottom: 0;
left: 0;
.recharge_footer_itemright_input {
width: 100%;
background: #333333;
border-radius: 10px;
padding: 0 6px;
display: flex;
height: 60px;
justify-content: space-between;
align-items: center;
div:nth-child(1) {
color: #56792e;
font-size: 16px;
}
div:nth-child(2) {
color: #88937c;
font-size: 20px;
}
}
.key-zero-and-point {
display: flex;
height: 10vh;
justify-content: center;
align-items: center;
width: 75%;
font-size: 30px;
.zero {
display: flex;
justify-content: center;
align-items: center;
width: 66.66%;
font-size: 30px;
text-align: center;
height: 100%;
}
.point {
display: flex;
justify-content: center;
align-items: center;
width: 33.33%;
font-size: 30px;
text-align: center;
height: 100%;
}
}
.key-cell:active {
color: white;
background: black; //
opacity: 0.1; //
}
.a:active,
.key-confirm2:active {
color: white;
background: black; //
opacity: 0.1; //
}
}
}
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<el-dialog title="恢复挂起的订单" width="800" v-model="dialogVisible">
<div class="pending_carts">
<div class="item" v-for="(item, index) in cartList" :key="index" @click="select(item)">
<div class="time">
<el-icon class="icon">
<Clock />
</el-icon>
<span>{{ dayjs(item.pendingAt).format('HH:mm') }}</span>
</div>
<div class="info">
<span class="name">{{ item.productName }}</span>
<span class="p">{{ item.totalAmount }}</span>
</div>
</div>
<div class="empty">
<el-empty description="暂无挂单" v-if="!cartList.length" />
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { dayjs } from 'element-plus'
import { getCartList } from "@/api/product";
import { useUser } from "@/store/user"
const emit = defineEmits(['select'])
const store = useUser()
const dialogVisible = ref(false)
const loading = ref(false)
const cartList = ref([])
//
function select(item) {
emit('select', item)
dialogVisible.value = false
}
function show() {
dialogVisible.value = true
getCartListAjax()
}
//
async function getCartListAjax() {
try {
loading.value = true
const res = await getCartList({
shopId: store.userInfo.shopId
})
cartList.value = res
loading.value = false
} catch (error) {
console.log(error)
}
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.pending_carts {
height: 50vh;
overflow-y: auto;
.item {
display: flex;
align-items: center;
background-color: #efefef;
border-radius: 6px;
overflow: hidden;
&:not(:first-child) {
margin-top: 20px;
}
.time {
padding: 20px 20px;
display: flex;
align-items: center;
background-color: var(--el-color-warning);
color: #fff;
.icon {
font-size: calc(var(--el-font-size-base) + 6px);
}
span {
font-size: var(--el-font-size-base);
margin-left: 4px;
}
}
.info {
flex: 1;
padding: 0 20px;
display: flex;
justify-content: space-between;
font-size: var(--el-font-size-base);
.p {
color: var(--el-color-warning);
}
}
}
}
</style>

View File

@ -0,0 +1,529 @@
<!-- 结算订单 -->
<template>
<el-drawer size="100%" :with-header="false" direction="btt" v-model="dialogVisible" @closed="drawerClose">
<div class="drawer_wrap">
<div class="cart_list">
<div class="nav_wrap card">
<div class="return" @click="dialogVisible = false">
<el-icon class="icon">
<ArrowLeftBold />
</el-icon>
</div>
<div class="info">
<div class="master_id">
<span>{{ props.masterId }}</span>
<span class="member_info" v-if="global.orderMemberInfo.telephone">
会员{{ global.orderMemberInfo.telephone }}
</span>
</div>
<div class="btm">
<span class="p">服务员{{ store.userInfo.loginAccount || "暂无" }}</span>
<span class="t">{{
props.orderInfo.createdAt &&
dayjs(props.orderInfo.createdAt).format("MM-DD HH:mm")
}}</span>
</div>
</div>
</div>
<div class="list_wrap card" style="margin-top: var(--el-font-size-base)">
<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="gift_wrap" v-if="item.isGift == 'true'">
<span>[赠送]</span>
<span>-{{ 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 class="packge_Wrap" v-if="item.isPack == 'true'">
<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>
<div class="footer">
<!-- <el-button icon="Edit"></el-button> -->
<div class="button">
<el-checkbox v-model="isPrint" border label="打印结算小票" style="width: 100%" />
</div>
<div class="print">
<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 class="pay_wrap">
<payCard :amount="props.amount" :discount="propsDiscount" :orderId="props.orderInfo.id" @paySuccess="paySuccess"
@cancelDiscount="propsDiscount = 0" />
</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>
</template>
<script setup>
import _ from 'lodash'
import { onMounted, ref } from "vue";
import { useUser } from "@/store/user.js";
import payCard from "@/components/payCard/payCard.vue";
import { print } from "@/api/pay";
import { orderfindOrder, getStaffDiscount } from '@/api/order/index.js'
import { ElMessage } from "element-plus";
import dayjs from "dayjs";
import useStorage from '@/utils/useStorage'
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 { staffPermission } from '@/api/user.js'
const global = useGlobal()
const printStore = usePrint()
const store = useUser();
const emit = defineEmits("paySuccess");
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 props = defineProps({
cart: {
type: Array,
default: [],
},
amount: {
type: [Number, String],
default: 0,
},
remark: {
type: String,
default: "",
},
orderInfo: {
type: Object,
default: "",
},
masterId: {
type: String,
default: "",
},
member: {
type: Object,
default: {}
}
});
const cartList = ref([])
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 () {
try {
if (!isPrint.value) return;
printLoading.value = true;
const data = {
shop_name: store.userInfo.shopName,
loginAccount: store.userInfo.loginAccount,
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,
orderInfo: props.orderInfo,
createdAt: dayjs(props.orderInfo.createdAt).format("YYYY-MM-DD HH:mm:ss"),
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
};
printStore.labelPrint(data)
setTimeout(() => {
printLoading.value = false;
}, 1500)
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) {
console.log(error);
}
}, 1500, { leading: true, trailing: false })
//
async function printOrderLable() {
try {
if (!isPrint.value) return
const res = await orderfindOrder({
shopId: store.userInfo.shopId,
status: '',
size: 10,
page: 1,
orderNo: props.orderInfo.orderNo
})
const printLabelOrder = res.list[0]
const data = {
shop_name: store.userInfo.shopName,
loginAccount: store.userInfo.loginAccount,
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,
outNumber: printLabelOrder.outNumber,
createdAt: dayjs(printLabelOrder.createdAt).format(
"YYYY-MM-DD HH:mm:ss"
),
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
}
printLabelOrder.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.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) {
console.log(error);
}
}
//
function paySuccess() {
propsDiscount.value = 0
dialogVisible.value = false;
global.setOrderMember({})
global.setOrderTable({})
printOrderLable()
emit("paySuccess");
}
function show() {
dialogVisible.value = true;
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({
show,
});
</script>
<style>
.el-drawer {
background-color: #efefef !important;
}
</style>
<style scoped lang="scss">
.footer_wrap {
display: flex;
gap: 10px;
.btn {
flex: 1;
}
}
.drawer_wrap {
width: 100%;
height: 100%;
display: flex;
padding: var(--el-font-size-base) 0;
.cart_list {
flex: 1;
.nav_wrap {
display: flex;
align-items: center;
padding: 0 var(--el-font-size-base);
.return {
$size: 50px;
width: $size;
height: $size;
border-radius: 50%;
border: 2px solid #333;
display: flex;
align-items: center;
justify-content: center;
.icon {
color: #333;
font-size: var(--el-font-size-base);
}
}
.info {
flex: 1;
padding-left: var(--el-font-size-base);
$padding: 10px;
.master_id {
font-size: calc(var(--el-font-size-base) + 10px);
border-bottom: 1px solid #ececec;
padding: $padding 0;
display: flex;
align-items: center;
justify-content: space-between;
.member_info {
color: #999;
font-size: 16px;
}
}
.btm {
display: flex;
align-items: center;
justify-content: space-between;
padding: $padding 0;
.p {
color: #999;
width: 160px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.list_wrap {
padding: 0 var(--el-font-size-base);
height: calc(100vh - 200px);
overflow-y: auto;
.item {
padding: var(--el-font-size-base) 0;
border-bottom: 1px solid #ececec;
.top {
display: flex;
padding-bottom: 6px;
.name {
flex: 1;
}
.n {
width: 50px;
color: #555;
}
.p {
width: 50px;
color: #555;
}
}
.gift_wrap {
display: flex;
justify-content: space-between;
color: #999;
font-size: 16px;
}
.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;
}
}
.packge_Wrap {
display: flex;
align-items: center;
.icon_item {
$size: 40px;
width: $size;
height: $size;
background-color: #e2e2e2;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
}
}
}
.footer {
display: flex;
padding-top: var(--el-font-size-base);
gap: var(--el-font-size-base);
.editor {
border: 1px solid #ececec;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #555;
}
.button {
flex: 1;
: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: 3px;
left: 7px;
}
}
:deep(.el-checkbox__label) {
font-size: var(--el-font-size-base) !important;
}
}
}
}
.pay_wrap {
flex: 1.5;
padding-left: 20px;
}
}
</style>

View File

@ -0,0 +1,203 @@
<!-- 合并/转桌 -->
<template>
<el-dialog title="转桌/并桌" width="700px" v-model="visible" @closed="onClose" top="10vh">
<div class="scroll_y">
<el-form :model="form" ref="formRef" :rules="rules" label-position="top">
<el-form-item label="转入台桌" prop="targetTableId">
<el-select v-model="form.targetTableId" style="width: 200px;" placeholder="请选择目标台桌">
<el-option :label="item.name" :value="item.qrcode" v-for="item in tableList"
:key="item.qrcode"></el-option>
</el-select>
</el-form-item>
<el-form-item label="转入类型">
<el-radio-group v-model="form.isFull">
<el-radio :value="false" border>转桌可将部分商品转入</el-radio>
<el-radio :value="true" border>并桌并台会将全部购物车商品转入</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="转入商品" prop="cartIds" v-if="!form.isFull">
<div v-for="item in props.data" style="width: 100%;">
<div>{{ `${item.placeNum}次下单` }}</div>
<el-table ref="tableRefs" :data="item.info" border>
<el-table-column type="selection" align="center" width="50px"></el-table-column>
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="数量" prop="number"></el-table-column>
<el-table-column label="规格" prop="skuName"></el-table-column>
<el-table-column label="价格" prop="salePrice"></el-table-column>
</el-table>
</div>
</el-form-item>
</el-form>
</div>
<div class="footer" style="display: flex;">
<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 { onMounted, reactive, ref } from 'vue'
import { queryShopTable } from '@/api/table.js'
import { orderSwitcht } from '@/api/product.js'
import { useUser } from "@/store/user.js"
import { useGlobal } from '@/store/global.js'
import { ElMessage } from 'element-plus'
const store = useUser()
const global = useGlobal()
const visible = ref(false)
const props = reactive({
data: []
})
const emits = defineEmits(['success'])
const tableRefs = ref([])
const list = ref([])
const loading = ref(false)
const formRef = ref(null)
const resetForm = ref({})
const form = ref({
shopId: store.userInfo.shopId,
masterId: '',
orderId: '',
cartIds: [],
isFull: false,
currentTableId: '',
targetTableId: '',
})
const rules = ref({
targetTableId: [
{
required: true,
message: ' ',
trigger: 'change',
}
],
cartIds: [
{
required: true,
validator: (rule, value, callback) => {
let arr = []
props.data.map((item, index) => {
arr.push(...tableRefs.value[index].getSelectionRows())
})
if (!arr.length) {
ElMessage.error('至少选择一个商品')
callback(new Error('至少选择一个商品'))
} else {
callback()
}
},
trigger: 'blur',
}
]
})
const tableList = ref([])
//
async function queryShopTableAjax() {
try {
const res = await queryShopTable({
shopId: store.userInfo.shopId,
areaId: '',
status: '',
page: 1,
pageSize: 100
})
tableList.value = res.list.filter(item => item.qrcode != props.data[0].info[0].tableId && item.status == 'using')
} catch (error) {
console.log(error)
}
}
//
function confirmHandle() {
formRef.value.validate(async valid => {
try {
if (valid) {
loading.value = true
form.value.masterId = props.data[0].info[0].masterId
form.value.orderId = props.data[0].info[0].orderId
form.value.currentTableId = props.data[0].info[0].tableId
if (!form.value.isFull) {
let arr = []
props.data.map((item, index) => {
arr.push(...tableRefs.value[index].getSelectionRows())
})
form.value.cartIds = arr.map(item => item.id)
}
await orderSwitcht(form.value)
loading.value = false
//
global.setOrderTable(tableList.value.find(item => item.qrcode == form.value.targetTableId))
visible.value = false
emits('success', { isTemporary: true })
}
} catch (error) {
loading.value = false
console.log(error);
}
})
}
function onClose() {
form.value = { ...resetForm.value }
formRef.value.resetFields()
}
function show(data) {
props.data = data
visible.value = true
queryShopTableAjax()
}
defineExpose({
show
})
onMounted(() => {
resetForm.value = { ...form.value }
})
</script>
<style scoped lang="scss">
$btmH: 50px;
.scroll_y {
height: 50vh;
overflow-y: auto;
padding-bottom: $btmH;
}
.footer {
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>

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>

887
src/views/home/index.vue Normal file
View File

@ -0,0 +1,887 @@
<template>
<div class="content">
<div class="cart_wrap card" v-loading="cartLoading">
<div class="menu_top">
<div class="menu" @click="pendingCartModalRef.show()">
<el-icon class="icon">
<TakeawayBox />
</el-icon>
<el-text class="t">({{ pendingCartNum }})</el-text>
</div>
<div class="number" @click="takeFoodCodeRef.show()">
<el-text class="t">{{ masterId }}</el-text>
</div>
<div class="select_user" @click="quickCashHandle"
v-if="!global.orderMemberInfo.telephone && !global.tableInfo.id">
<div class="left">
<el-icon class="icon">
<WalletFilled />
</el-icon>
<el-text class="t">快捷收银</el-text>
</div>
<el-icon class="arrow">
<ArrowRight />
</el-icon>
</div>
<div class="select_user" v-else @click="clearMember">
<div class="left">
<el-icon class="icon">
<UserFilled />
</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">
<Close />
</el-icon>
</div>
</div>
<div class="shop_operation">
<div class="shop_list">
<template v-for="(arr, index) in cartList" :key="index">
<el-divider v-if="arr.placeNum">{{ `${arr.placeNum}次下单` }}</el-divider>
<div class="item" :class="{ active: item.active }" :key="item.id" v-for="(item, i) in arr.info"
@click="selectCartItemHandle(item, index, i)">
<div class="name_wrap">
<span>{{ item.name }}</span>
<div class="price">
<span :class="{ dis: item.discountSaleAmount }">{{ item.salePrice }}</span>
<span v-if="item.discountSaleAmount">
{{ formatDecimal(item.salePrice - item.discountSaleAmount, 2, true) }}</span>
</div>
</div>
<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>
</template>
<div class="empty">
<el-empty description="请选择商品" v-if="!cartList.length" />
</div>
</div>
<!-- 购物车操作栏 -->
<cartOperation :item="cartListActiveItem" @confirm="(res) => addCart(res, 'edit')" @delete="delCartHandle"
@pending="pendingCart" @clearCart="clearCartHandle" @merging="showTableMerging" />
</div>
<div class="footer">
<div class="top">
<div class="left" @click="allSelectedHandle"
v-if="JSON.parse(shopStore.info.eatModel).some(item => item == 'take-out')">
<div class="selected">
<div class="selected_round" v-if="!allSelected"></div>
<el-icon class="icon" v-else>
<CircleCheckFilled />
</el-icon>
</div>
<el-text class="t">打包({{ cartInfo.packAmount || 0 }})</el-text>
</div>
<div class="left" v-else></div>
<div class="num-wrap">
<!-- {{ cartInfo.productNum || 0 }}种商品 -->
<el-text>{{
cartInfo.productSum || 0
}}</el-text>{{ formatDecimal(cartInfo.totalAmount || 0) }}
</div>
</div>
<div class="btm">
<el-button icon="Edit" @click="remarkRef.show()"></el-button>
<div class="button">
<div class="btn" v-if="shopStore.info.registerType == 'restaurant'">
<el-button type="primary" style="width: 100%;" :disabled="!cartList.length" v-loading="createOrderLoading"
@click="createOrderHandle(0)">
<template v-if="!createOrderLoading">
仅下单</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 class="shop_manage card">
<!-- 分类/商品列表 -->
<goods ref="goodsRef" :masterId="masterId" @success="addCart" @loading="cartLoading = true" />
<!-- ©银收客 v{{ packageData.version }} -->
</div>
</div>
<!-- 备注 -->
<remarkModal ref="remarkRef" @success="(e) => (remark = e)" />
<!-- 修改取餐号 -->
<takeFoodCode />
<el-drawer v-model="membershow" :with-header="true" size="90%" title="选择会员">
<member :membershow="'1'"></member>
</el-drawer>
<takeFoodCode ref="takeFoodCodeRef" title="修改取餐号" placeholder="请输入取餐号" @success="takeFoodCodeSuccess" />
<!-- 结算订单 -->
<settleAccount ref="settleAccountRef" :cart="cartList" :amount="cartInfo.totalAmount" :remark="remark"
:masterId="masterId" :orderInfo="orderInfo" @paySuccess="createCodeAjax(1)" />
<!-- 快捷收银 -->
<fastCashier ref="fastCashierRef" type="0" />
<!-- 挂起订单 -->
<pendingCartModal ref="pendingCartModalRef" @select="pendingCartHandle" />
<!-- 检查版本升级 -->
<updateDialog />
<!-- 合并/转桌 -->
<tableMerging ref="tableMergingRef" @success="addCart" />
</template>
<script>
export default {
name: "home",
};
</script>
<script setup>
import { onMounted, ref } from "vue";
import { useRoute } from 'vue-router'
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 takeFoodCode from "@/components/takeFoodCode.vue";
import cartOperation from "@/views/home/components/cartOperation.vue";
import settleAccount from "@/views/home/components/settleAccount.vue";
import fastCashier from "@/views/home/components/fastCashier.vue";
import pendingCartModal from "@/views/home/components/pendingCartModal.vue";
import tableMerging from '@/views/home/components/tableMerging.vue'
import useStorage from '@/utils/useStorage'
import { formatDecimal } from '@/utils/index.js'
import {
createCart,
queryCart,
createCode,
packall,
delCart,
cartStatus,
clearCart,
createOrder,
} 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 member from "@/views/member/index.vue";
import { ElMessage } from "element-plus";
import { useShop } from '@/store/shop.js'
import TableMerging from "./components/tableMerging.vue";
const shopStore = useShop()
const global = useGlobal()
const route = useRoute()
const membershow = ref(false);
const store = useUser();
const remarkRef = ref(null);
const takeFoodCodeRef = ref(null);
const goodsRef = ref(null);
const pendingCartModalRef = ref(null);
const settleAccountRef = ref(null);
const fastCashierRef = ref(null);
const tableMergingRef = ref(null)
const allSelected = ref(false);
const remark = ref("");
const cartListActive = ref(0);
const cartListActiveItem = ref({})
const cartList = ref([]);
const cartInfo = ref({});
const cartLoading = ref(false);
const orderInfo = ref({});
const createOrderLoading = ref(false);
//
const masterId = ref("");
//
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(t = 0) {
try {
createOrderLoading.value = true;
await staffPermission('yun_xu_xia_dan')
const res = await createOrder({
masterId: masterId.value,
shopId: store.userInfo.shopId,
remark: remark.value,
vipUserId: global.orderMemberInfo.id || '',
tableId: global.tableInfo.qrcode || '',
type: t,
seatNum: global.tableInfo.num
});
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) {
console.log(error);
createOrderLoading.value = false;
}
}
//
async function clearCartHandle() {
try {
await clearCart({
shopId: store.userInfo.shopId,
masterId: masterId.value,
tableId: global.tableInfo.qrcode || ''
});
cartListActiveItem.value = {}
queryCartAjax();
//
goodsRef.value.clearDot()
} catch (error) {
console.log(error);
}
}
//
async function pendingCartHandle(item) {
const nItem = { ...item };
if (cartList.value.length) {
//
await pendingCart({ masterId: masterId.value });
}
masterId.value = nItem.masterId;
await pendingCart(nItem, false);
await queryCartAjax();
}
//
async function pendingCart(params, status = true) {
try {
cartLoading.value = true;
await cartStatus({
shopId: store.userInfo.shopId,
masterId: params.masterId,
status: status,
uuid: params.uuid,
vipUserId: global.orderMemberInfo.id || '',
tableId: global.tableInfo.qrcode || '',
orderId: params.orderId
});
if (status && cartList.value.length) {
await createCodeAjax();
setTimeout(() => {
cartLoading.value = false;
}, 500);
} else {
setTimeout(() => {
cartLoading.value = false;
}, 500);
}
} catch (error) {
cartLoading.value = false;
console.log(error);
}
}
//
async function delCartHandle(params) {
try {
cartLoading.value = true;
await delCart({
masterId: params.masterId,
cartId: params.id,
});
cartListActiveItem.value = {}
await queryCartAjax();
cartLoading.value = false;
cartListActive.value = 0;
} catch (error) {
cartLoading.value = false;
console.log(error);
}
}
//
function giftPackHandle(key, item) {
item[key] = false;
addCart(item, "edit");
}
//
const allSelectedHandle = async () => {
if (!cartList.value.length) return;
allSelected.value = !allSelected.value;
await packall({
shopId: store.userInfo.shopId,
status: allSelected.value,
masterId: masterId.value,
});
queryCartAjax();
};
//
async function takeFoodCodeSuccess(code) {
if (cartList.value.length) {
await pendingCart({
masterId: masterId.value,
uuid: cartList.value[0].uuid,
});
}
masterId.value = `#${code}`;
queryCartAjax();
}
//
function selectCartItemHandle(row, index, i) {
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") {
console.log(params);
try {
cartLoading.value = true;
if (params.isTemporary) {
await createCodeAjax()
cartLoading.value = false;
} else {
let skuId = ''
if (params.skuList && params.skuList.length) {
skuId = params.skuList[0].id
} else {
skuId = type == "add" ? params.id : params.skuId
}
const res = await createCart({
productId: params.productId,
masterId: masterId.value,
tableId: global.tableInfo.qrcode || '',
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) {
console.log(error);
cartLoading.value = false;
}
}
//
async function queryCartAjax() {
try {
const res = await queryCart({
masterId: masterId.value,
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;
if (cartListActiveItem.value.id) {
selectCartItemHandle(cartListActiveItem.value)
}
cartInfo.value = res.amount;
pendingCartNum.value = res.num;
// goodsRef.value.updateData();
let i = 0;
res.list.map((item) => {
if (item.isPack == "true") {
i++;
}
});
if (i == res.list.length) {
allSelected.value = true;
} else {
allSelected.value = false;
}
} catch (error) {
console.log("获取购物车商品", error);
}
}
//
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") {
try {
// if (!process.env.VITE_DEV_SERVER_URL) {
// masterId.value = '#20'
// } else {
// const res = await createCode({
// shopId: store.userInfo.shopId
// })
// masterId.value = res.code
// }
if (global.tableInfo.masterId) {
masterId.value = global.tableInfo.masterId
} else {
const res = await createCode({
shopId: store.userInfo.shopId,
type: type,
tableId: global.tableInfo.qrcode || '',
});
masterId.value = res.code;
}
if (global.tableInfo.num) {
await addTableNum()
}
await queryCartAjax();
if (type == 1) {
//
goodsRef.value.clearDot()
}
} catch (error) {
console.log(error);
}
}
// /
function clearMember() {
global.setOrderMember({})
global.setOrderTable({})
createCodeAjax()
}
// /
function showTableMerging() {
let data = cartList.value.filter(item => item.placeNum)
tableMergingRef.value.show(data)
}
onMounted(() => {
createCodeAjax()
shopStore.queryShopInfo()
});
</script>
<style scoped lang="scss">
.cart_wrap {
flex: 1.5;
height: 100%;
display: flex;
flex-direction: column;
.menu_top {
flex-shrink: 0;
display: flex;
height: var(--el-component-size-large);
.menu {
background-color: var(--el-color-warning);
color: #fff;
width: 60px;
display: flex;
align-items: center;
justify-content: center;
.icon {
font-size: var(--el-font-size-base);
position: relative;
top: 2px;
}
.t {
color: #fff;
margin-left: 4px;
font-size: var(--el-font-size-base);
}
}
.number {
width: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--el-color-info-light-7);
// padding-left: var(--el-font-size-base);
.t {
font-size: var(--el-font-size-base);
}
}
.select_user {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--el-color-info-light-8);
padding: 0 10px;
.left {
display: flex;
align-items: center;
.icon {
color: var(--el-color-primary);
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 {
color: #999;
font-size: 16px;
position: relative;
top: 2px;
}
}
}
.shop_operation {
flex: 1;
display: flex;
.shop_list {
flex: 1;
height: calc(100vh - 40px - 60px - 80px);
overflow-y: auto;
border-right: 1px solid #ececec;
.item {
padding: var(--el-font-size-base);
&.active {
background-color: var(--primary-color-hover);
}
&:not(:last-child) {
border-bottom: 1px solid #ececec;
}
.name_wrap {
display: flex;
justify-content: space-between;
font-size: var(--el-font-size-base);
.dis {
color: #999;
font-size: 12px;
text-decoration: line-through;
margin-right: 4px;
}
}
.sku_list {
display: flex;
flex-wrap: wrap;
padding-top: 10px;
.tag {
padding: 2px 6px;
background-color: var(--el-color-danger);
color: #fff;
margin-right: 10px;
margin-bottom: 10px;
}
}
.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;
line-clamp: 2;
}
.num {
padding-top: var(--el-font-size-base);
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
padding-right: 10px;
.icon_item {
$size: 20px;
height: $size;
padding: 0 6px;
border-radius: 2px;
background-color: #e2e2e2;
display: flex;
align-items: center;
justify-content: center;
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;
}
}
}
.t {
font-size: var(--el-font-size-base);
}
}
}
}
}
}
.footer {
padding: var(--el-font-size-base);
border-top: 1px solid #ececec;
.left {
display: flex;
align-items: center;
.selected {
width: 16px;
height: 16px;
display: flex;
align-items: center;
position: relative;
top: 1px;
.icon {
display: block;
font-size: var(--el-font-size-base);
color: var(--primary-color);
}
.selected_round {
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid #ddd;
}
}
.t {
margin-left: 6px;
}
}
.top {
display: flex;
justify-content: space-between;
}
.btm {
$h: 70px;
display: flex;
height: $h;
padding-top: var(--el-font-size-base);
gap: var(--el-font-size-base);
.editor {
width: $h;
border: 1px solid #ececec;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #555;
font-size: var(--el-font-size-base);
}
.button {
flex: 1;
display: flex;
gap: var(--el-font-size-base);
.btn {
flex: 1;
}
.js {
.t {
color: #fff;
font-size: var(--el-font-size-base);
}
}
}
}
}
.shop_manage {
flex: 3;
margin-left: var(--el-font-size-base);
height: 100%;
}
</style>

83
src/views/home/test.vue Normal file
View File

@ -0,0 +1,83 @@
<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">下次更新</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'
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('')
//
async function findVersionAjax() {
try {
const res = await findVersion()
let reg = /\./g;
if (res.version.replace(reg, '') > packageData.version.replace(reg, '') && res.url) {
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(() => {
findVersionAjax()
ipcRenderer.on('updateProgress', (event, res) => {
// console.log('updateProgress===', event, res);
uploadPro.value = res
})
})
</script>

View File

@ -4,107 +4,115 @@
<el-image :src="logo" style="width: 180px"></el-image>
</div>
<div class="form-wrap">
<div class="reg-wrap">
<div style="flex: 1">
<!-- <div class="reg-wrap">
<router-link :to="{ name: 'register' }">
<el-link type="primary">注册</el-link>
</router-link>
</div>
<div class="header">
<span class="t1">银收客</span>
<span class="t2">收银库存营销支付等业务一体化解决方案</span>
</div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
size="large"
hide-required-asterisk
>
<el-form-item label="注册商户号" prop="shopCode">
<el-input
v-model="form.shopCode"
placeholder="请输入注册商户号"
></el-input>
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input
v-model="form.phone"
placeholder="请输入11位手机号码"
></el-input>
</el-form-item>
<el-form-item label="登录密码" prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入登录密码"
></el-input>
</el-form-item>
<el-form-item>
<div style="width: 100%; display: flex; justify-content: flex-end">
<router-link :to="{ name: 'register' }">
<el-link type="info">忘记密码</el-link>
</router-link>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
style="width: 100%"
:loading="loading"
@click="submitHandle"
>
登录
</el-button>
</el-form-item>
<el-form-item>
<el-button plain style="width: 100%" @click="logout">退出</el-button>
</el-form-item>
</el-form>
<div class="version">
<el-text size="large">Ver2.1.6</el-text>
<el-text size="large">陕西超掌柜科技有限公司</el-text>
</div> -->
<div class="header">
<span class="t1">银收客</span>
<span class="t2">收银库存营销支付等业务一体化解决方案</span>
</div>
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" hide-required-asterisk>
<el-form-item label="商户号" prop="merchantName">
<el-input v-model="form.merchantName" placeholder="请输入注册商户号"></el-input>
</el-form-item>
<el-form-item label="手机号码" prop="loginName">
<el-input v-model="form.loginName" placeholder="请输入11位手机号码"></el-input>
</el-form-item>
<el-form-item label="登录密码" prop="password">
<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>
<div style="width: 100%; display: flex; justify-content: flex-end">
<router-link :to="{ name: 'register' }">
<el-link type="info">忘记密码</el-link>
</router-link>
</div>
</el-form-item> -->
<el-form-item>
<el-button type="primary" style="width: 100%" :loading="loading" @click="submitHandle">
<span v-if="!loading">登录</span>
<span v-else>登录中...</span>
</el-button>
</el-form-item>
<el-form-item>
<el-button plain style="width: 100%" @click="logout">退出</el-button>
</el-form-item>
</el-form>
<div class="version">
<el-text size="large">Ver{{ packageData.version }}</el-text>
<el-text size="large">陕西超掌柜科技有限公司</el-text>
</div>
</div>
</div>
</div>
</template>
<script setup>
import packageData from "../../package.json";
import logo from "@/assets/logo.png";
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 { ipcRenderer } from "electron";
import { RandomNumBoth } from "@/utils";
import useStorage from "@/utils/useStorage";
import { douyincheckIn } from "@/api/group";
import { useUser } from "@/store/user.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 socket = useSocket();
const router = useRouter();
const formRef = ref(null);
const loading = ref(false);
const passwordType = ref('password')
const form = reactive({
shopCode: "",
phone: "",
serialNumber: RandomNumBoth(1000, 9999),
clientType: "pc",
merchantName: "",
loginName: "",
password: "",
});
const rules = reactive({
shopCode: [
merchantName: [
{
required: true,
message: "请输入注册商户号",
message: " ",
trigger: "blur",
},
],
phone: [
loginName: [
{
required: true,
message: "请输入11位手机号码",
message: " ",
trigger: "blur",
},
],
password: [
{
required: true,
message: "请输入登录密码",
message: " ",
trigger: "blur",
},
],
@ -112,16 +120,33 @@ const rules = reactive({
//
const submitHandle = () => {
formRef.value.validate((valid) => {
formRef.value.validate(async (valid) => {
if (valid) {
loading.value = true;
ElMessage.success("登录成功");
localStorage.setItem("token", "skk918sjakajhjjqhw19jsdkandkahk");
setTimeout(() => {
router.replace({
name: "home",
store
.userlogin(form)
.then(async (res) => {
//
useStorage.set('merchantLoginAccount', form.merchantName)
ElMessage.success("登录成功");
socket.init();
await shopInfo.queryShopInfo()
setTimeout(() => {
router.replace({
name: "home",
});
}, 1000);
const douyin = await douyincheckIn({
token: res.token,
loginName: res.loginName,
clientType: 'pc'
})
useStorage.set('douyin', douyin.userInfo)
global.updateData(true)
})
.catch((err) => {
loading.value = false;
});
}, 1500);
}
});
};
@ -131,14 +156,30 @@ const logout = () => {
.then(() => {
ipcRenderer.send("quitHandler", "退出吧");
})
.catch(() => {});
.catch(() => { });
};
onMounted(() => {
global.updateData(false)
let merchantLoginAccount = useStorage.get('merchantLoginAccount')
if (merchantLoginAccount) {
form.merchantName = merchantLoginAccount
}
})
</script>
<style scoped lang="scss">
.login-container {
width: 100vw;
display: flex;
height: 100vh;
background-color: #efefef;
:deep(.el-form-item__label) {
font-size: var(--el-font-size-base);
}
.logo {
flex: 1;
height: inherit;
@ -147,32 +188,40 @@ const logout = () => {
align-items: center;
justify-content: center;
}
.form-wrap {
flex: 1;
flex: 1.5;
height: inherit;
padding: 50px;
padding: 0 50px;
display: flex;
align-items: center;
}
.reg-wrap {
display: flex;
justify-content: flex-end;
padding-bottom: 100px;
padding-bottom: 30px;
}
.header {
display: flex;
flex-direction: column;
padding-bottom: 100px;
padding-bottom: 20px;
.t1 {
font-size: 52px;
font-size: 42px;
color: #999;
}
.t2 {
font-size: 24px;
font-size: var(--el-font-size-base);
color: #999;
}
}
}
.version {
padding-top: 50px;
padding-top: 20px;
display: flex;
flex-direction: column;
justify-content: center;

View File

@ -0,0 +1,246 @@
<template>
<el-dialog v-model="visableDialog" title="余额明细" width="500">
<div class="box">
<div class="box1" v-loading="tableData.loading">
<div class="dialog_footer" v-for="(item, index) in tableData.list" :key="index">
<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 class="page_wrap">
<el-pagination v-model:current-page="tableData.page" background layout="prev, pager, next, total"
:total="tableData.total" @current-change="memberqueryMemberAccountAjax" />
</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>
</el-dialog>
</template>
<script setup>
import md5 from "js-md5";
import { onMounted, reactive, ref } from 'vue'
import { dayjs, ElMessage } from 'element-plus'
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 memberId = ref('')
const visableDialog = ref(false)
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>
<style scoped lang="scss">
.box {
.box1 {
height: 350px;
overflow-y: auto;
}
.page_wrap {
padding-top: 20px;
}
.dialog_footer:nth-child(1) {
margin-top: 0;
}
.dialog_footer {
margin-top: 10px;
display: flex;
align-items: center;
border-bottom: 1px solid #ececec;
padding-bottom: 6px;
.dialog_footer_left {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
span:nth-child(1) {
font-size: 18px;
font-weight: 500;
}
span:nth-child(2) {
margin-top: 10px;
color: #999;
font-size: 12px;
}
}
.dialog_footer_right {
width: 150px;
display: flex;
flex-direction: column;
align-items: flex-end;
padding-right: 20px;
span:nth-child(1) {
font-size: 16px;
&.active {
color: #fc3d3d;
}
}
span:nth-child(2) {
margin-top: 10px;
font-size: 14px;
color: #999;
}
}
}
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<el-drawer size="100%" :with-header="false" direction="btt" v-model="dialogVisible">
<div class="drawer_wrap">
<div class="pay_wrap">
<fastPayCard ref="fastPayCardRef" type="1" :userInfo="userInfo" @paySuccess="paySuccess"
@close="dialogVisible = false" />
</div>
</div>
</el-drawer>
</template>
<script setup>
import { ref } from "vue";
import fastPayCard from "@/components/fastPayCard.vue";
const props = defineProps({
userInfo: {
type: Object,
default: {}
}
})
const emit = defineEmits(["paySuccess"]);
const dialogVisible = ref(false);
const fastPayCardRef = ref(null);
//
function paySuccess() {
dialogVisible.value = false;
emit("paySuccess");
}
function show() {
dialogVisible.value = true;
fastPayCardRef.value.reset();
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.drawer_wrap {
width: 100%;
height: 100%;
display: flex;
padding: var(--el-font-size-base) 0;
.pay_wrap {
flex: 1;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More