Compare commits

...

309 Commits
master ... gyq

Author SHA1 Message Date
gyq 25ae5a7b80 优化结算 2025-10-14 13:34:28 +08:00
gyq 125365f3dc 问题修复 2025-10-13 18:55:30 +08:00
gyq 8e3bc9cc3b 优化 2025-10-13 14:37:55 +08:00
gyq aa82851361 更新 2025-10-13 13:30:26 +08:00
gyq aabbeced98 对接新版结算 2025-10-13 11:15:47 +08:00
gyq b3b788d8d4 1 2025-09-01 10:10:55 +08:00
gyq ec5ae0e7d4 1 2025-09-01 10:10:30 +08:00
gyq 2ba61eab19 优化台桌状态 2025-05-10 15:15:43 +08:00
gyq 4d939b2889 增加支付检测是否锁定 2025-05-10 09:13:13 +08:00
gyq ca3b0eddbc 新增单品备注 2025-04-24 10:40:40 +08:00
gyq 3a11828f3c 优化扫码支付查询支付状态逻辑 2025-04-23 18:08:06 +08:00
gyq 47ccfd0544 1.优化商品菜单选中效果 2.优化打印服务加载弹窗展示效果 2025-04-22 15:25:53 +08:00
gyq b5773cf78c 优化下单 2025-04-21 17:45:05 +08:00
gyq 694cb154a6 新增商品状态售罄,增加删除的库存限制 2025-04-16 18:31:12 +08:00
gyq 0a0f672a02 优化订单打印 2025-04-15 14:47:04 +08:00
gyq f466bdc9f4 优化loading 2025-04-09 17:57:51 +08:00
gyq e443b5b51e 优化 2025-04-09 13:28:50 +08:00
gyq ebf912ecd8 修复优化 2025-04-07 09:37:08 +08:00
gyq 429283e542 优化权限 2025-04-03 16:28:31 +08:00
gyq 117df7d7bd 优化订单打印 新增小票订单数 2025-04-01 14:31:21 +08:00
gyq 0c45fd9de1 优化订单打印 2025-03-31 09:21:08 +08:00
gyq e13727e6ff 1.优化商品分类 2.其他优化 2025-03-27 13:57:15 +08:00
gyq 48280f92df 1.优化购物车删除 2.优化标签小票 2025-03-26 17:03:24 +08:00
gyq dd6189b51b 优化 2025-03-26 10:42:38 +08:00
gyq 95ab7730f5 优化店铺切换商品展示 2025-03-26 09:48:23 +08:00
gyq 4245b5a098 1.优化商品列表全部加载动画 2.退款界面自定义退款使inpt自动获得焦点 2025-03-25 14:42:42 +08:00
gyq 3e84edbb6c 优化添加优惠 2025-03-25 00:31:50 +08:00
gyq a914adc9f8 优化下单折扣 2025-03-20 18:30:32 +08:00
gyq 87e8976353 优化会员支付,已会员价结算 2025-03-19 18:29:46 +08:00
gyq a101cc4fb7 优化密码输入 2025-03-19 10:32:29 +08:00
gyq 177d987769 优化打包、订单云打印 2025-03-18 17:19:59 +08:00
gyq 64f539623f 优化打印 优化退单 2025-03-17 09:47:49 +08:00
gyq d08a629b0a 1.优化会员扫码充值订单状态查询 2025-03-14 17:27:00 +08:00
gyq 745b8675ea 优化小票打印 2025-03-13 18:54:10 +08:00
gyq cfe9f7bb36 优化商品编辑 2025-03-12 18:24:33 +08:00
gyq 48c9f24d4c 优化订单退款 2025-03-12 09:55:07 +08:00
gyq 1e66b2ad1f 优化 2025-03-11 09:41:53 +08:00
gyq 3df0cce9ec 对接支付优惠 2025-03-11 09:10:49 +08:00
gyq 5cf2355d28 1.对接会员列表 2.对接排队叫号 2025-03-06 19:28:01 +08:00
gyq db3fc1f6dc 对接订单打印小票 2025-03-05 09:35:34 +08:00
gyq 573dd88b24 对接订单列表接口 2025-03-01 14:08:24 +08:00
gyq d3ed4ec8e6 对接购物车 2025-02-26 18:06:37 +08:00
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
141 changed files with 36656 additions and 974 deletions

29
.env.development Normal file
View File

@ -0,0 +1,29 @@
# 本地环境
ENV = development
# 测试ws
# VITE_API_WSS = 'wss://sockets.sxczgkj.com/wss'
# 正式ws
# VITE_API_WSS = 'wss://czgeatws.sxczgkj.com/wss'
# 本地ws
VITE_API_WSS = 'ws://192.168.1.42:2348'
# 正式 php
VITE_API_PHP_URL = 'https://newblockwlx.sxczgkj.cn/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.1.42/'
# 线上测试
# VITE_API_URL = 'https://tapi.cashier.sxczgkj.cn'
# 线上正式
# VITE_API_URL = 'https://cashier.sxczgkj.com'

14
.env.production Normal file
View File

@ -0,0 +1,14 @@
# 线上环境
ENV = production
# 正式ws
VITE_API_WSS = 'wss://czgeatws.sxczgkj.com/wss'
# 正式 php
VITE_API_PHP_URL = 'https://newblockwlx.sxczgkj.cn/index.php/api'
# 正式 php 开票
VITE_API_KP_URL = 'https://invoice.sxczgkj.cn/api'
# 线上环境接口地址
VITE_API_URL = 'https://cashier.sxczgkj.com/'

29
.env.test Normal file
View File

@ -0,0 +1,29 @@
# 测试环境
ENV = test
# 测试ws
VITE_API_WSS = 'ws://192.168.1.42:2348'
# 测试ws
# VITE_API_WSS = 'wss://sockets.sxczgkj.com/wss'
# 正式ws
# VITE_API_WSS = 'wss://czgeatws.sxczgkj.com/wss'
# 正式 php
VITE_API_PHP_URL = 'https://newblockwlx.sxczgkj.cn/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'
# 测试Java
# VITE_API_URL = 'https://fv901fw8033.vicp.fun/'
# 正式Java
# VITE_API_URL = 'https://cashier.sxczgkj.com/'
# 本地调试连接
VITE_API_URL = 'http://192.168.1.42/'

2
.gitignore vendored
View File

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

View File

@ -1,9 +1,15 @@
# Vue 3 + Vite # Vue 3 + Vite
npm install
npm install @element-plus/icons-vue
npm run dev 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. 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 ## 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 one or more lines are too long

View File

@ -1,14 +1,21 @@
import path from "path";
import { app, BrowserWindow, ipcMain } from "electron"; import { app, BrowserWindow, ipcMain } from "electron";
import axios from "axios";
import os from "os";
import fs from "fs";
import { exec } from "child_process";
let win;
app.whenReady().then(() => { app.whenReady().then(() => {
const win = new BrowserWindow({ win = new BrowserWindow({
title: "Main window", title: "银收客",
width: 1200, width: 1024,
height: 800, height: 768,
fullscreenable: true, fullscreenable: true,
fullscreen: false, fullscreen: process.env.VITE_DEV_SERVER_URL ? false : true,
simpleFullscreen: true, simpleFullscreen: true,
frame: true, frame: process.env.VITE_DEV_SERVER_URL ? true : false,
webPreferences: { webPreferences: {
// 集成网页和 Node.js也就是在渲染进程中可以调用 Node.js 方法 // 集成网页和 Node.js也就是在渲染进程中可以调用 Node.js 方法
nodeIntegration: true, nodeIntegration: true,
@ -19,23 +26,273 @@ app.whenReady().then(() => {
// You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve` // You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve`
if (process.env.VITE_DEV_SERVER_URL) { if (process.env.VITE_DEV_SERVER_URL) {
win.loadURL(process.env.VITE_DEV_SERVER_URL); win.loadURL(process.env.VITE_DEV_SERVER_URL);
// 使用vite开发服务的url路径访问应用
// win.webContents.openDevTools();
} else { } else {
// Load your file win.loadFile(path.resolve(__dirname, "../dist/index.html")); // 打包后使用文件路径访问应用
win.loadFile("dist/index.html");
} }
win.webContents.openDevTools();
// 安装最新版本的exe文件
const installExe = async (exePath) => {
return new Promise((resolve, reject) => {
exec(`${exePath}`, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
};
ipcMain.on("downloadFile", async (event, arg) => {
let _parmas = JSON.parse(arg);
axios({
url: _parmas.url,
method: "get",
responseType: "arraybuffer",
onDownloadProgress: (propessEvent) => {
// 更新进度条
const propress = Math.round(
(propessEvent.loaded / propessEvent.total) * 100
);
win.webContents.send("updateProgress", propress);
},
})
.then(async (response) => {
try {
const tempFilePath = path.join(
app.getPath("temp"),
"temp-exe-file.exe"
);
fs.writeFileSync(tempFilePath, response.data);
setTimeout(() => {
win = null;
app.exit();
}, 1500);
const installResult = await installExe(tempFilePath);
console.log(`安装结果:${installResult}`);
} catch (error) {
console.log("error", error);
}
})
.catch((err) => {
console.log("下载失败", JSON.stringify(err));
});
});
app.on("activate", () => { app.on("activate", () => {
// 在 macOS 系统内, 如果没有已开启的应用窗口 // 在 macOS 系统内, 如果没有已开启的应用窗口
// 点击托盘图标时通常会重新创建一个新窗口 // 点击托盘图标时通常会重新创建一个新窗口
if (BrowserWindow.getAllWindows().length === 0) createWindow(); if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
}); });
ipcMain.on("quitHandler", (_, msg) => { 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(); 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", () => { app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit(); if (process.platform !== "darwin") app.quit();
}); });

View File

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

7685
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +1,82 @@
{ {
"name": "vite-electron", "name": "vite-electron",
"private": true, "private": true,
"version": "0.0.0", "version": "2.0.7",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"scripts": { "scripts": {
"dev": "chcp 65001 && vite", "dev": "chcp 65001 && vite",
"build": "vite build", "build": "node ./addVersion.js && vite build && electron-builder",
"preview": "vite preview" "build:test": "vite build --mode test && electron-builder",
}, "preview": "vite preview",
"dependencies": { "build:win": "node ./addVersion.js && vite build && electron-builder --w",
"@element-plus/icons-vue": "^2.3.1", "postinstall": "electron-builder install-app-deps"
"axios": "^1.6.2", },
"element-plus": "^2.4.3", "dependencies": {
"pinia": "^2.1.7", "@element-plus/icons-vue": "^2.3.1",
"vue": "^3.3.8", "axios": "^1.6.2",
"vue-router": "^4.2.5" "bignumber.js": "^9.3.1",
}, "dayjs": "^1.11.10",
"devDependencies": { "electron-pos-printer": "^1.3.6",
"@vitejs/plugin-vue": "^4.5.0", "electron-pos-printer-vue": "^1.0.9",
"path": "^0.12.7", "element-plus": "^2.4.3",
"sass": "^1.69.5", "js-md5": "^0.8.3",
"sass-loader": "^13.3.2", "lodash": "^4.17.21",
"tree-kill": "^1.2.2", "pinia": "^2.1.7",
"vite": "^5.0.0", "pinia-plugin-persistedstate": "^3.2.1",
"vite-plugin-electron": "^0.15.4", "qrcode": "^1.5.3",
"vite-plugin-electron-renderer": "^0.14.5" "reconnecting-websocket": "^4.4.0",
} "speak-tts": "^2.0.8",
} "swiper": "^11.1.1",
"uuid": "^10.0.0",
"vue": "^3.3.8",
"vue-router": "^4.2.5",
"ysk-utils": "^1.0.47"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.0",
"electron": "^28.3.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 {
font-weight: bold;
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 {
font-weight: bold;
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> <template>
<el-config-provider size="large"> <el-config-provider size="large">
<div class="container"> <div class="container">
<div class="left" v-if="route.path !== 'login'"> <div class="left" v-if="!hideLeftMenu">
<left-menu /> <left-menu ref="leftMenuRef" />
</div> </div>
<div class="view"> <div :class="{ view: !hideLeftMenu }">
<router-view />
<!-- <div class="wrapper"> <!-- <div class="wrapper">
<div class="animation"> <div class="animation"> -->
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition :name="transitionName"> <!-- <transition :name="transitionName"> -->
<component :is="Component"></component> <keep-alive :include="includeList">
</transition> <component :is="Component"></component>
</router-view> </keep-alive>
</div> <!-- </transition> -->
</router-view>
<!-- </div>
</div> --> </div> -->
</div> </div>
</div> </div>
@ -21,12 +22,40 @@
</template> </template>
<script setup> <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 { useRouter, useRoute } from "vue-router";
import { ref } from "vue"; import leftMenu from "@/components/leftMenu.vue";
import { useUser } from "@/store/user.js";
import { 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';
import { useGoods } from '@/store/goods.js'
const route = useRoute() const goodsStore = useGoods()
const socket = useSocket();
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 transitionName = ref();
let router = useRouter(); let router = useRouter();
@ -42,6 +71,129 @@ router.beforeEach((to, from) => {
transitionName.value = ""; 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.id) {
goodsStore.initGoods()
store.getShopInfo()
store.shopPagePermissionMineAjax()
}
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> </script>
<style lang="scss"> <style lang="scss">
@ -53,23 +205,149 @@ router.beforeEach((to, from) => {
} }
:root { :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-color-primary: var(--primary-color) !important;
--el-button-hover-bg-color: 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-light-3: rgb(var(--r-lighter),
--el-color-primary-dark-2: darken(var(--primary-color), 20%) !important; 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: var(--el-font-size-base);
} }
/* /*
高宽分别对应横竖滚动条的尺寸*/ 高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 4px; width: 4px;
height: 2px;
} }
/* /*
内阴影+圆角*/ 内阴影+圆角*/
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background-color: #F5F5F5; background-color: #f5f5f5;
} }
/* /*
@ -79,6 +357,21 @@ router.beforeEach((to, from) => {
background-color: #d3d3d3; 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 { .mt50 {
margin-top: 50px; margin-top: 50px;
@ -90,7 +383,7 @@ router.beforeEach((to, from) => {
.view { .view {
flex: 1; flex: 1;
height: 100vh; height: 100vh;
padding: 20px; padding: var(--el-font-size-base);
background-color: #efefef; background-color: #efefef;
} }
} }
@ -157,4 +450,8 @@ router.beforeEach((to, from) => {
} }
} }
} }
.height_auto {
overflow-y: auto;
}
</style> </style>

417
src/api/account.js Normal file
View File

@ -0,0 +1,417 @@
import request from "@/utils/request.js";
/**
* 商户登录
* @param {*} data
* @returns
*/
export function login(data) {
return request({
method: "post",
url: "/account/admin/auth/login",
data,
});
}
/**
* 退出登录
* @param {*} data
* @returns
*/
export function logout(data) {
return request({
method: "post",
url: "/account/admin/auth/logout",
data,
});
}
/**
* 验证码获取
* @param {*} params
* @returns
*/
export function captcha(params) {
return request({
method: "get",
url: "/account/admin/auth/captcha",
params,
});
}
/**
* 店铺详情
* @param {*} params
* @returns
*/
export function shopInfo_detail(params) {
return request({
method: "get",
url: "/account/admin/shopInfo/detail",
params,
});
}
/**
* 获取当前店铺拓展通过key
* @param {*} params
* @returns
*/
export function shopExtendDetail(params) {
return request({
method: "get",
url: "/account/admin/shopExtend/detail",
params,
});
}
/**
* 获取店铺用户列表
* @param {*} params
* @returns
*/
export function shopUserList(params) {
return request({
method: "get",
url: "/account/admin/shopUser",
params,
});
}
/**
* 获取台桌区域
* @param {*} params
* @returns
*/
export function shopArea(params) {
return request({
method: "get",
url: "/account/admin/shopArea",
params,
});
}
/**
* 获取台桌列表
* @param {*} params
* @returns
*/
export function shopTable(params) {
return request({
method: "get",
url: "/account/admin/shopTable",
params,
});
}
/**
* 支付方式列表
* @param {*} params
* @returns
*/
export function getPayType(params) {
return request({
method: "get",
url: "/account/admin/payType",
params,
});
}
/**
* 当前登录员工信息
*/
export function shopStaffInfo() {
return request({
method: "get",
url: "/account/admin/shopStaff/info",
});
}
/**
* 获取店铺用户充值记录
*/
export function shopUserChargeFlow(params) {
return request({
method: "get",
url: "/account/admin/shopUser/flow",
params,
});
}
/**
* 获取店铺用户充值记录
*/
export function addShopUser(data) {
return request({
method: "post",
url: "/account/admin/shopUser",
data,
});
}
/**
* 获取叫号队列
*/
export function callTableQueue(params) {
return request({
method: "get",
url: "/account/admin/callTable/queue",
params,
});
}
/**
* 获取叫号配置
*/
export function callTableConfig(params) {
return request({
method: "get",
url: "/account/admin/callTable/config",
params,
});
}
/**
* 修改叫号配置
*/
export function callTableConfigPut(data) {
return request({
method: "put",
url: "/account/admin/callTable/config",
data,
});
}
/**
* 叫号桌型新增
*/
export function addCallTable(data) {
return request({
method: data.id ? "put" : "post",
url: "/account/admin/callTable",
data,
});
}
/**
* 叫号桌型删除
*/
export function delCallTable(data) {
return request({
method: "delete",
url: "/account/admin/callTable",
data,
});
}
/**
* 获取桌型列表
*/
export function getCallTable(params) {
return request({
method: "get",
url: "/account/admin/callTable",
params,
});
}
/**
* 新增叫号号码
*/
export function takeNumber(data) {
return request({
method: "post",
url: "/account/admin/callTable/takeNumber",
data,
});
}
/**
* 获取桌型列表
*/
export function callRecord(params) {
return request({
method: "get",
url: "/account/admin/callTable/callRecord",
params,
});
}
/**
* 执行叫号
*/
export function callTableCall(data) {
return request({
method: "post",
url: "/account/admin/callTable/call",
data,
});
}
/**
* 修改叫号队列状态
*/
export function callTableCallState(data) {
return request({
method: "put",
url: "/account/admin/callTable/updateState",
data,
});
}
/**
* 收银机-交班数据统计
*/
export function handoverTotal(params) {
return request({
method: "get",
url: "/account/admin/handoverRecord/total",
params,
});
}
/**
* 收银机-交班
*/
export function handover(isPrint) {
return request({
method: "post",
url: "/account/admin/handoverRecord/handover?isPrint=" + isPrint,
});
}
/**
* 收银机-交班记录-详情
*/
export function handoverData(id) {
return request({
method: "get",
url: `/account/admin/handoverRecord/detail/${id}`,
});
}
/**
* 交班记录-分页
*/
export function handoverRecordPage(params) {
return request({
method: "get",
url: `/account/admin/handoverRecord/page`,
params,
});
}
/**
* 收银机-交班/关班-网络打印机打印交班小票
*/
export function handoverNetworkPrint(id) {
return request({
method: "post",
url: `/account/admin/handoverRecord/network/print/${id}`,
});
}
/**
* 打印机列表
*/
export function printerList(subType = "") {
return request({
method: "get",
url: "/account/admin/printer",
params: {
name: "",
subType: subType,
connectionType: "USB",
page: 1,
size: 100,
},
});
}
/**
* 打印机详情
*/
export function printerDetail(params) {
return request({
method: "get",
url: "/account/admin/printer/detail",
params,
});
}
/**
* 新增打印机
*/
export function printerAdd(data, method = "post") {
return request({
method: method,
url: "/account/admin/printer",
data,
});
}
/**
* 查找优惠券 生成订单后使用
*/
export function findCoupon(params) {
return request({
method: "get",
url: "/market/admin/coupon/findCoupon",
params,
});
}
/**
* 002-获取订单可用积分及抵扣金额支付页面使用
*/
export function calcUsablePoints(params) {
return request({
method: "get",
url: "/account/admin/points/memberPoints/calcUsablePoints",
params,
});
}
/**
* 003-根据积分计算可抵扣金额
*/
export function calcDeductionAmount(params) {
return request({
method: "get",
url: "/account/admin/points/memberPoints/calcDeductionAmount",
params,
});
}
/**
* 获取店铺用户详情
*/
export function shopUserDetail(params) {
return request({
method: "get",
url: "/account/admin/shopUser/detail",
params,
});
}
/**
* 获取当前员工已拥有页面路径
*/
export function shopPagePermissionMine(params) {
return request({
method: "get",
url: "/account/admin/shopPagePermission/mine",
params,
});
}
/**
* 台桌清台
*/
export function shopTableClear(data) {
return request({
method: "put",
url: "/account/admin/shopTable/clear",
data,
});
}

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,
});
}

14
src/api/market.js Normal file
View File

@ -0,0 +1,14 @@
import request from "@/utils/request.js";
/**
* 智慧充值 配置信息获取
* @param {*} data
* @returns
*/
export function shopRecharge(params) {
return request({
method: "get",
url: "/market/admin/shopRecharge",
params,
});
}

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,
});
}

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

@ -0,0 +1,261 @@
import request from "@/utils/request.js";
/**
* 反扫
* @param {*} data
* @returns
*/
export function microPay(data) {
return request({
method: "post",
url: "/order/pay/microPay",
data,
});
}
/**
* 现金支付订单
* @param {*} data
* @returns
*/
export function cashPay(data) {
return request({
method: "post",
url: "/order/pay/cashPay",
data,
});
}
/**
* 查询订单状态
* @param {*} data
* @returns
*/
export function queryOrderStatus(params) {
return request({
method: "get",
url: `/order/pay/queryOrderStatus`,
params,
});
}
/**
* 查询订单列表
* @param {*} data
* @returns
*/
export function orderList(params) {
return request({
method: "get",
url: `/order/admin/order`,
params,
});
}
/**
* 新增订单
* @param {*} data
* @returns
*/
export function createOrder(data) {
return request({
method: "post",
url: "/order/admin/order/createOrder",
data,
});
}
/**
* 历史订单(多次下单使用)
* @param {*} data
* @returns
*/
export function historyOrder(params) {
return request({
method: "get",
url: "/order/admin/order/historyOrder",
params,
});
}
/**
* 订单退款
* @param {*} data
* @returns
*/
export function refundOrder(data) {
return request({
method: "post",
url: "/order/admin/order/refundOrder",
data,
});
}
/**
* 订单详情
* @param {*} data
* @returns
*/
export function getOrderById(params) {
return request({
method: "get",
url: "/order/admin/order/getOrderByIdPrint",
params,
});
}
/**
* 会员充值 - 现金充值
* @param {*} data
* @returns
*/
export function cashPayVip(data) {
return request({
method: "post",
url: "/order/pay/cashPayVip",
data,
});
}
/**
* 会员充值 - 反扫
* @param {*} data
* @returns
*/
export function microPayVip(data) {
return request({
method: "post",
url: "/order/pay/microPayVip",
data,
});
}
/**
* 会员充值 - 会员退款前置接口
* @param {*} data
* @returns
*/
export function refundVipBefore(data) {
return request({
method: "post",
url: "/order/pay/refundVipBefore",
data,
});
}
/**
* 会员充值 - 会员退款
* @param {*} data
* @returns
*/
export function refundVip(data) {
return request({
method: "post",
url: "/order/pay/refundVip",
data,
});
}
/**
* 挂账人 - 分页
* @param {*} data
* @returns
*/
export function buyerPage(params) {
return request({
method: "get",
url: "/order/admin/order/credit/buyer/page",
params,
});
}
/**
* 挂账人 - 付款
* @param {*} data
* @returns
*/
export function creditPay(data) {
return request({
method: "post",
url: "/order/pay/creditPay",
data,
});
}
/**
* 挂账人 - 会员支付订单
* @param {*} data
* @returns
*/
export function vipPay(data) {
return request({
method: "post",
url: "/order/pay/vipPay",
data,
});
}
/**
* 订单打印
* @param {*} data
* @returns
*/
export function orderPrint(data) {
return request({
method: "post",
url: "/order/admin/order/print",
data,
});
}
/**
* 查询会员充值支付状态
* @param {*} data
* @returns
*/
export function queryPayStatus(params) {
return request({
method: "get",
url: "/order/pay/queryPayStatus",
params,
});
}
/**
* 订单转桌
* @param {*} data
* @returns
*/
export function mergeOrder(data) {
return request({
method: "post",
url: "/order/admin/order/mergeOrder",
data,
});
}
/**
* 取消订单
* @param {*} data
* @returns
*/
export function cancelOrder(data) {
return request({
method: "post",
url: "/order/admin/order/cancelOrder",
data,
});
}
/**
* 取消某一次 下单
* @param {*} data
* @returns
*/
export function rmPlaceOrder(data) {
return request({
method: "post",
url: "/order/admin/order/rmPlaceOrder",
data,
});
}

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,
});
}

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

@ -0,0 +1,53 @@
import request from "@/utils/request.js";
/**
* 查询分类信息
* @param {*} params
* @returns
*/
export function categoryList(params) {
return request({
method: "get",
url: "/product/admin/prod/category/list",
params,
});
}
/**
* 查询商品列表
* @param {*} params
* @returns
*/
export function productPage(params) {
return request({
method: "get",
url: "/product/admin/product/list",
params,
});
}
/**
* 商品上下架
* @param {*} data
* @returns
*/
export function productOnOff(data) {
return request({
method: "post",
url: "/product/admin/product/onOff",
data,
});
}
/**
* 商品售罄
* @param {*} data
* @returns
*/
export function markIsSoldOut(data) {
return request({
method: "post",
url: "/product/admin/product/markIsSoldOut",
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,
});
}

12
src/api/system.js Normal file
View File

@ -0,0 +1,12 @@
import request from "@/utils/request.js";
/**
* 获取版本 pc:pc端 type 0 windows1 安卓2 iOS
* @returns
*/
export function findVersion(source = "pc", type = 0) {
return request({
method: "get",
url: `/system/admin/version/${source}/${type}`,
});
}

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

View File

@ -0,0 +1,14 @@
<svg width="121" height="120" viewBox="0 0 121 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5_38)">
<circle cx="60.2939" cy="60" r="58.5" stroke="white" stroke-width="3"/>
<circle cx="60.2938" cy="60" r="51.1997" stroke="white" stroke-width="2" stroke-dasharray="2 2"/>
<line x1="19.8277" y1="50.637" x2="82.4435" y2="27.8676" stroke="white"/>
<line x1="36.7388" y1="93.3029" x2="99.3547" y2="70.5335" stroke="white"/>
<path d="M22.4599 62.0201C23.1265 62.5176 23.7976 63.0902 24.4821 63.7599L32.6011 60.8049L33.2304 62.3625L16.5189 68.445L18.5688 73.5186C20.4655 78.4692 21.3126 82.6787 21.0559 86.1414L19.2488 85.4463C19.4643 82.4581 18.6888 78.6819 16.8999 74.126L14.2207 67.4948L22.7005 64.4084C22.0609 63.8499 21.4032 63.3746 20.7815 62.9883L22.4599 62.0201ZM22.7471 66.8928L24.4471 66.6825C24.4958 67.3794 24.5266 68.0319 24.5574 68.6843L33.3529 65.483L33.9193 66.8849L24.56 70.2914C24.5039 71.9454 24.3398 73.4601 24.0676 74.8353L28.285 73.3004L27.1971 70.6078L28.8209 70.0168L29.9088 72.7093L34.938 70.8789L35.5224 72.3253L30.4932 74.1558L31.5001 76.648L38.2433 74.1937L38.8367 75.6623L32.0935 78.1167L33.514 81.6326L31.8902 82.2236L30.4697 78.7077L22.3282 81.671L21.7348 80.2023L29.8763 77.239L28.8694 74.7468L22.9606 76.8974L22.0649 75.6409C22.4993 74.2831 22.7672 72.7051 22.846 70.9153L19.4405 72.1547L18.8741 70.7528L22.8659 69.2999C22.8353 68.5198 22.7957 67.7175 22.7471 66.8928ZM35.6949 60.0106L41.8292 57.7779C41.8077 56.8924 41.7637 56.015 41.6655 55.1319L43.3928 54.7329C43.464 55.5493 43.5261 56.3435 43.5658 57.1459L55.1127 52.9431L55.742 54.5008L43.5862 58.9252C43.5249 61.1426 43.1977 63.2781 42.6272 65.3235L47.2034 76.65L45.6247 77.2246L41.8217 67.8118C41.3876 68.9143 40.9129 69.9804 40.3616 71.0489L38.8343 70.1499C40.6237 66.7675 41.6063 63.2448 41.8045 59.5736L36.3242 61.5683L35.6949 60.0106ZM45.8068 68.1992L52.3471 65.8187L51.511 63.7492C52.5088 62.5693 53.394 61.3027 54.1711 59.8967L45.6913 62.9831L45.089 61.4922L55.6436 57.6506L56.246 59.1415C55.4777 60.6974 54.5335 62.2662 53.4045 63.8258L53.9709 65.2277L59.609 63.1756L60.2294 64.711L54.5912 66.7631L56.2905 70.9688C56.8209 72.2817 56.414 73.1955 55.0834 73.6798L52.2644 74.7059L51.2245 73.3487C52.0454 73.0754 52.9114 72.7858 53.836 72.4492C54.5126 72.203 54.7296 71.7156 54.4599 71.048L52.9674 67.3541L46.4272 69.7346L45.8068 68.1992ZM57.6942 50.7528L76.9993 43.7263L77.6286 45.284L70.2313 47.9764C70.194 49.0365 70.08 50.099 69.8894 51.1638L70.303 52.1874C74.7565 52.9658 78.7948 54.125 82.4178 55.6651L81.8432 57.3802C78.698 55.8704 75.0971 54.7051 70.9862 53.8786L75.967 66.2065L74.2981 66.8139L69.1555 54.0855C68.0785 57.4384 66.217 60.7705 63.5756 64.0291L61.9582 63.0352C65.9314 58.2709 68.0832 53.4803 68.4271 48.6331L58.3236 52.3105L57.6942 50.7528ZM82.9851 41.1903L97.1482 36.0354L100.205 43.6012L94.1383 45.8093L95.6308 49.5032L103.073 46.7944L103.702 48.3521L96.2601 51.0609L98.1391 55.7116C99.4425 55.4159 100.989 54.955 102.749 54.3147C104.44 53.6991 105.843 53.1374 106.989 52.6438L107.185 54.4102C105.809 54.9109 104.56 55.3401 103.446 55.7201C99.3998 57.1671 96.5271 57.8043 94.796 57.6176C93.2813 57.4542 91.6187 56.6044 89.8622 55.0741C89.8232 57.5387 89.4373 59.7211 88.6821 61.6296L87.1322 60.7388C88.2721 57.6694 88.4342 54.0369 87.5961 49.8496L89.2916 49.5643C89.5381 50.8784 89.7124 52.1423 89.8056 53.3335C91.7197 55.0617 93.4544 55.9618 95.0413 56.0478C95.4336 56.0582 95.8709 56.0522 96.3669 55.9993L92.492 46.4085L86.0419 48.7561L82.9851 41.1903ZM97.9473 42.6873L96.1222 38.17L85.2293 42.1347L87.0544 46.652L97.9473 42.6873Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_5_38">
<rect width="120" height="120" fill="white" transform="translate(0.293854)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,14 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2_36)">
<circle cx="60" cy="60" r="58.5" stroke="white" stroke-width="3"/>
<circle cx="60" cy="60" r="51.1997" stroke="white" stroke-width="2" stroke-dasharray="2 2"/>
<line x1="19.5338" y1="50.637" x2="82.1497" y2="27.8676" stroke="white"/>
<line x1="36.445" y1="93.3029" x2="99.0608" y2="70.5335" stroke="white"/>
<path d="M57.134 66.8373L60.6344 75.5009L58.4994 76.278L57.912 74.8242L41.9748 80.6248L42.5622 82.0786L40.4272 82.8557L36.9269 74.1921L57.134 66.8373ZM41.2076 78.7259L57.1448 72.9253L55.7423 69.4539L39.8051 75.2546L41.2076 78.7259ZM32.5035 63.2439C31.9245 64.8841 31.2192 66.468 30.3698 68.0364L28.8571 66.1707C30.8456 62.2138 31.8241 58.3182 31.7745 54.5246L34.0473 54.1738C34.046 55.1952 34.0028 56.1979 33.8994 57.2225L41.3568 54.5083C40.4981 53.6637 39.6574 52.9487 38.7805 52.3149L40.8618 51.149C41.7145 51.8936 42.5852 52.7679 43.4617 53.7421L52.2121 50.5573L52.9313 52.3374L43.79 55.6646L44.6651 57.8305L52.4833 54.9849L53.1666 56.6761L45.3484 59.5217L46.1875 61.5986L54.0659 58.7311L54.7491 60.4223L46.8708 63.2898L47.7698 65.515L57.3923 62.0127L58.1235 63.8226L35.9919 71.8779L32.5035 63.2439ZM45.6348 66.2921L44.7358 64.0669L36.4364 67.0876L37.3355 69.3128L45.6348 66.2921ZM44.0525 62.3757L43.2134 60.2988L34.914 63.3195L35.7531 65.3964L44.0525 62.3757ZM42.5301 58.6076L41.655 56.4417L33.5661 59.3858C33.5239 59.5373 33.4997 59.6482 33.4875 59.7888L34.2307 61.6283L42.5301 58.6076ZM56.4839 47.471L62.7084 45.2054L62.121 43.7516L64.2259 42.9855L64.8133 44.4393L69.8952 42.5897L70.5185 44.1325L65.4367 45.9821L66.0001 47.3766L70.27 45.8225L70.8454 47.2466L59.6593 51.318L59.0839 49.8939L63.8951 48.1427L63.3317 46.7483L57.1072 49.0138L56.4839 47.471ZM65.8022 52.0091L62.7651 53.1145L63.0168 53.7375C63.1607 54.0936 63.2864 54.4902 63.3701 54.8682L66.4974 53.7299L65.8022 52.0091ZM68.1212 53.1389L70.7674 52.1758L70.0721 50.4549L67.426 51.4181L68.1212 53.1389ZM63.615 56.2425C63.7454 57.7605 63.5032 59.21 62.8703 60.6315L60.9416 59.8701C61.7612 58.1424 61.8294 56.3478 61.0923 54.438L60.3131 52.5095L71.379 48.4818L73.1171 52.784L63.615 56.2425ZM71.9233 40.6944L79.1401 38.0676L80.0991 40.4412C80.2669 40.8566 80.5433 41.0283 80.9342 40.886L83.1894 40.0652L83.8128 41.608L81.1065 42.593C79.9037 43.0308 79.0505 42.6266 78.559 41.4102L78.0795 40.2234L74.2306 41.6243C74.5542 42.4254 74.6495 43.1735 74.5766 43.8466C74.4254 44.5823 74.0097 45.3462 73.3114 46.1789L71.635 45.5299C72.189 44.6816 72.4844 43.9615 72.5694 43.318C72.5942 42.6964 72.3788 41.8218 71.9233 40.6944ZM83.3605 42.7936L83.84 43.9804C83.6458 45.5486 82.9887 47.081 81.8387 48.5886C83.4017 48.5302 85.2234 48.3436 87.3038 48.0288L87.0971 49.9078C84.4457 50.2603 82.2093 50.3595 80.358 50.2166C79.0219 51.5197 77.3552 52.7729 75.358 53.9763L73.9227 52.729C75.6071 51.7756 77.0149 50.8208 78.1882 49.8832C76.5055 49.4747 75.1958 48.7942 74.2712 47.8716L73.0984 48.2984L72.4751 46.7556L83.3605 42.7936ZM75.925 47.2696C76.6817 47.9471 77.9255 48.3793 79.6446 48.5363C80.7641 47.3802 81.5169 46.2553 81.8789 45.1026L75.925 47.2696ZM68.9973 59.4905C68.6716 60.3918 68.2738 61.2853 67.8219 62.1304L65.6343 61.6674C66.8993 59.5054 67.5631 57.392 67.6858 55.3053L69.8683 54.9874C69.8194 55.7199 69.7283 56.4337 69.6134 57.0882L83.8967 51.8895L84.6639 53.7883L77.0862 56.5464L78.177 59.2463L89.6338 55.0764L90.401 56.9753L78.9442 61.1452L80.3468 64.6166L87.2328 62.1103L86.2619 59.707L88.3668 58.9409L90.5605 64.3705L88.4556 65.1366L87.988 63.9795L71.9907 69.802L72.4582 70.9591L70.3533 71.7253L68.1836 66.355L70.2885 65.5889L71.2355 67.9328L78.2118 65.3937L76.8093 61.9223L64.4204 66.4315L63.6532 64.5326L76.0421 60.0234L74.9512 57.3235L68.9973 59.4905Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_2_36">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,14 @@
<svg width="121" height="120" viewBox="0 0 121 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2_35)">
<circle cx="60.2939" cy="60" r="58.5" stroke="white" stroke-width="3"/>
<circle cx="60.2938" cy="60" r="51.1997" stroke="white" stroke-width="2" stroke-dasharray="2 2"/>
<line x1="19.8277" y1="50.637" x2="82.4435" y2="27.8676" stroke="white"/>
<line x1="36.7388" y1="93.3029" x2="99.3547" y2="70.5335" stroke="white"/>
<path d="M26.044 58.3135L28.1301 57.5543L29.6698 61.365L39.3392 57.8456L40.1259 59.7927L30.4565 63.3121L32.4456 68.2354L43.6092 64.1722L44.3958 66.1193L35.0929 69.5053C38.3977 72.3226 42.9265 74.2477 48.6624 75.3187L47.9721 77.6119C41.831 76.0184 36.9817 73.5399 33.4578 70.1004L33.2323 70.1825L38.3569 82.8664L36.2708 83.6257L31.1462 70.9418L30.8925 71.0341C30.7058 76.3346 28.8006 81.3035 25.1994 85.9962L23.2907 84.5532C26.8122 80.4638 28.8237 76.1582 29.3138 71.6087L19.9826 75.005L19.196 73.0579L30.3595 68.9947L28.3703 64.0714L18.8982 67.5189L18.1116 65.5718L27.5837 62.1243L26.044 58.3135ZM44.9692 53.2121L66.9298 45.2191L67.7165 47.1662L63.0086 48.8797L65.9867 56.2508L72.1605 54.0037L72.936 55.923L66.7622 58.1701L71.7407 70.4923L69.6828 71.2414L64.7042 58.9191L56.5571 61.8844C57.4609 64.8417 57.8806 67.401 57.799 69.6003C57.6718 72.3267 56.6263 74.7811 54.6624 76.9636L52.8662 75.6392C54.6327 73.5285 55.5541 71.2469 55.6021 68.8046C55.5422 66.9758 55.1727 64.9407 54.4992 62.6334L47.9871 65.0036L47.2117 63.0843L53.8365 60.6731C53.6904 60.3115 53.5837 59.9675 53.4714 59.6893L50.8866 53.2917L45.7559 55.1592L44.9692 53.2121ZM52.9727 52.5324L55.5575 58.93C55.6923 59.2638 55.799 59.6079 55.9226 59.9138L63.9288 56.9998L60.9507 49.6287L52.9727 52.5324ZM100.444 51.1754L103.725 59.2975L101.724 60.026L101.173 58.6631L86.2321 64.1012L86.7828 65.4641L84.7812 66.1927L81.4997 58.0705L100.444 51.1754ZM85.5128 62.321L100.454 56.8829L99.1391 53.6285L84.198 59.0666L85.5128 62.321ZM77.3528 47.8066C76.8099 49.3442 76.1488 50.8292 75.3524 52.2996L73.9343 50.5504C75.7985 46.8408 76.7158 43.1887 76.6693 39.6322L78.8 39.3034C78.7989 40.261 78.7583 41.201 78.6614 42.1616L85.6527 39.6169C84.8477 38.8251 84.0595 38.1548 83.2374 37.5607L85.1887 36.4676C85.9881 37.1657 86.8043 37.9853 87.626 38.8987L95.8295 35.9128L96.5038 37.5818L87.9338 40.701L88.7542 42.7315L96.0838 40.0638L96.7244 41.6493L89.3948 44.317L90.1815 46.2641L97.5675 43.5758L98.208 45.1613L90.8221 47.8496L91.6649 49.9358L100.686 46.6524L101.371 48.3491L80.6231 55.9009L77.3528 47.8066ZM89.6634 50.6643L88.8205 48.5781L81.0399 51.41L81.8827 53.4962L89.6634 50.6643ZM88.1799 46.9926L87.3933 45.0455L79.6126 47.8774L80.3993 49.8245L88.1799 46.9926ZM86.7527 43.46L85.9323 41.4295L78.349 44.1896C78.3094 44.3317 78.2867 44.4356 78.2753 44.5674L78.972 46.292L86.7527 43.46Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_2_35">
<rect width="120" height="120" fill="white" transform="translate(0.293854)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,14 @@
<svg width="121" height="121" viewBox="0 0 121 121" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2_37)">
<circle cx="60.706" cy="60.1438" r="58.5" stroke="white" stroke-width="3"/>
<circle cx="60.706" cy="60.1438" r="51.1997" stroke="white" stroke-width="2" stroke-dasharray="2 2"/>
<line x1="20.2398" y1="50.7807" x2="82.8557" y2="28.0114" stroke="white"/>
<line x1="37.151" y1="93.4466" x2="99.7668" y2="70.6773" stroke="white"/>
<path d="M41.9891 67.1295L24.2853 73.5732L27.6006 81.7788C28.095 83.0027 29.0019 83.4064 30.2987 82.9344L44.4505 77.7836C45.127 77.5373 45.6234 77.1652 45.9114 76.6775C46.273 75.9717 45.9202 74.0581 44.8531 70.9368L47.0737 70.8305C48.1854 74.3822 48.5377 76.615 48.1534 77.5845C47.7692 78.3943 46.9625 79.0389 45.8009 79.5255L30.5215 85.0867C28.2381 85.9178 26.6385 85.16 25.7169 82.8791L19.8169 68.276L21.8466 67.5372L23.5099 71.6539L39.1557 65.9593L36.2226 58.6994L17.4193 65.5433L16.6326 63.5962L37.4938 56.0033L41.9891 67.1295ZM43.6958 53.746L68.7574 44.6243L69.5553 46.5992L57.3205 51.0523L59.0175 55.2525C63.2099 55.7047 67.5711 56.4146 72.101 57.3821L71.6081 59.6035C66.8697 58.5204 62.9482 57.7781 59.8716 57.3664L66.2549 73.1657L64.1969 73.9147L55.2626 51.8013L44.4937 55.7209L43.6958 53.746ZM86.686 38.737L95.7634 35.4331L99.6406 45.0294L90.5631 48.3333L86.686 38.737ZM96.9649 43.9294L94.5487 37.949L89.3334 39.8473L91.7496 45.8276L96.9649 43.9294ZM75.9761 40.4016L77.9495 39.6834L78.9609 42.1868L84.4581 40.1859C86.3629 44.9805 87.2273 47.9203 87.0456 49.0712C86.8357 50.2324 86.04 51.0644 84.6305 51.5774C84.1795 51.7416 83.6495 51.8707 83.0069 52.0408L81.769 50.4174L83.432 49.9717C84.4357 49.5745 84.9379 48.9769 84.9389 48.1789C84.8947 47.4292 84.3052 45.5699 83.1815 42.6288L79.5167 43.9626C80.5827 48.0415 80.0148 51.5983 77.8133 54.633L75.791 53.8695C77.823 51.2155 78.4015 48.1653 77.5434 44.6809L73.4557 46.1687L72.759 44.4441L76.9876 42.905L75.9761 40.4016ZM77.6697 56.9187L88.8896 52.835L87.9793 50.5819L90.009 49.8431L90.9193 52.0962L102.167 48.0022L102.932 49.8937L93.5441 53.3104C96.3883 54.8277 100.451 55.6783 105.709 55.8065L104.996 58.0441C99.2876 57.4416 94.9159 56.0655 91.8527 53.9261L91.6835 53.9876L95.1337 62.527L93.1039 63.2658L89.6538 54.7264L89.5128 54.7777C88.7079 58.708 86.3138 62.5466 82.3811 66.339L80.4836 64.9239C84.269 61.7275 86.7018 58.5448 87.7932 55.4036L78.4339 58.8101L77.6697 56.9187Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_2_37">
<rect width="120" height="120" fill="white" transform="translate(0.706001 0.143768)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
src/assets/icon_scan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

1
src/assets/no-sale.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 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,126 @@
<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 { onMounted, ref } from "vue";
import { useUser } from "@/store/user.js";
import { useGoods } from '@/store/goods.js'
const goodsStore = useGoods()
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 categoryList();
// res.list.map((item) => {
// item.active = false;
// item.childrenList.map((item) => {
// item.active = false;
// });
// });
categorys.value = goodsStore.originCategoryList.map(item => {
item.active = false
return item
});
// 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,502 @@
<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.phone }}
</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="quick_charge">
<div class="item" :class="{active:chargeActive == index }" v-for="(item,index) in chargeList" :key="item.id" @click="changeCharge(index,item)">
<div class="row">
<span class="icon"></span>
<span class="num">{{ item.amount }}</span>
</div>
<div class="row">
<span class="t1">{{ item.rewardAmount }}</span>
<span class="t1">{{ item.rewardPoints }}积分</span>
</div>
<div class="row">
<span class="t2">{{item.couponCount}}张券</span>
</div>
</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" :chargeId="chargeId" @success="scanCodeSuccess" />
<takeFoodCode ref="takeFoodCodeRef" title="支付密码" :type="2" input-type="password" placeholder="请输入支付密码"
@success="passwordSuccess" />
</template>
<script setup>
import { onMounted, ref } from "vue";
import { shopRecharge } from '@/api/market.js'
import { getPayType } from '@/api/account.js'
import { cashPayVip } from "@/api/order.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";
import { useUser } from '@/store/user.js'
const takeFoodCodeRef = ref(null);
const props = defineProps({
type: {
type: [String, Number],
default: 0, // 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 cashPayVip({
shopId: store.shopInfo.id,
shopUserId: props.userInfo.id,
amount: money.value,
// pwd: e ? md5(e) : '',
pwd: e,
orderId: '',
allPack: '',
rechargeDetailId: chargeId.value
});
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 {
//
await store.getShopInfo()
if (store.shopInfo.isMemberInPwd == 1) {
takeFoodCodeRef.value.show();
} else {
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) });
chargeActive.value = null
chargeId.value = ''
}
//
function delHandle() {
if (!money.value) return;
chargeActive.value = null
chargeId.value = ''
money.value = money.value.substring(0, money.value.length - 1);
if (!money.value) {
money.value = "0";
}
}
//
async function queryPayTypeAjax() {
try {
const res = await getPayType();
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;
chargeActive.value = null;
}
//
const chargeActive = ref(null);
const chargeList = ref([])
const chargeId = ref('')
function changeCharge(index,item) {
chargeActive.value = index
chargeId.value = item.id
money.value = ''
money.value = clearNoNum({ value: (money.value += item.amount) })
}
async function shopRechargeAjax() {
try {
const res = await shopRecharge()
res.rechargeDetailList.map((item) => {
item.couponCount = 0
item.couponInfoList.map(val => {
item.couponCount += val.num;
})
})
chargeList.value = res.rechargeDetailList
console.log(chargeList.value)
} catch (err) {
console.log(err)
}
}
defineExpose({ reset });
onMounted(() => {
queryPayTypeAjax();
shopRechargeAjax()
});
</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: 80px;
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;
}
}
}
.quick_charge {
width: 100%;
height: 100px;
overflow-x: auto;
margin-top: var(--el-font-size-base);
display: flex;
.item {
$activeColor: #FF6300;
display: flex;
gap: 2px;
flex-direction: column;
justify-content: center;
background-color: #F5F5F5;
border-radius: 8px;
padding: var(--el-font-size-base);
margin-right: var(--el-font-size-base);
border: 2px solid #F5F5F5;
&.active {
border-color: $activeColor;
.row {
.icon {
color: $activeColor;
}
.num {
color: $activeColor;
}
.t1 {
color: $activeColor;
}
}
}
.row {
display: flex;
align-items: center;
.icon {
font-size: 12px;
position: relative;
top: 3px;
}
.num {
font-size: 20px;
font-weight: bold;
}
.t2 {
color: #666;
}
}
}
}
.input_wrap {
display: flex;
gap: var(--el-font-size-base);
padding: var(--el-font-size-base) 0;
.input {
display: flex;
align-items: center;
height: 50px;
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) / 5);
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,85 +1,185 @@
<template> <template>
<div class="left_menu_wrap"> <div class="left_menu_wrap">
<div class="item online"> <div class="top_item_wra">
<el-icon class="icon"> <div class="item first" :class="{ online: socketStore.online }" @click="connectWsHandle">
<Monitor /> <el-icon class="icon">
</el-icon> <Monitor />
<el-text type="success">在线</el-text> </el-icon>
<el-text :type="socketStore.online ? 'success' : 'danger'">
{{ socketStore.online ? '在线' : '离线' }}
</el-text>
</div>
<router-link class="item" :class="{ active: route.path == item.path }" v-for="item in store.menus"
:key="item.path" :to="item.path">
<el-icon class="icon">
<component :is="item.icon" />
</el-icon>
<el-text class="text">{{ item.label }}</el-text>
</router-link>
<div class="item" @click="workRef.show()">
<el-icon class="icon">
<component is="SwitchButton" />
</el-icon>
<el-text class="text">交班</el-text>
</div>
</div> </div>
<router-link class="item" :class="{ active: route.path == item.path }" v-for="item in menus" :key="item.path" <div class="item" @click="moreref.show()">
:to="item.path">
<el-icon class="icon">
<component :is="item.icon" />
</el-icon>
<el-text class="text">{{ item.label }}</el-text>
</router-link>
<div class="item more">
<el-icon class="icon"> <el-icon class="icon">
<Operation /> <Operation />
</el-icon> </el-icon>
<el-text class="text">更多</el-text> <el-text class="text">更多</el-text>
</div> </div>
</div> </div>
<!-- 交班 -->
<work ref="workRef" />
<!-- 更多 -->
<more ref="moreref" @openCall="openCall"></more>
<!-- 叫号 -->
<!-- <callNumber ref="callNumberRef" /> -->
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useSocket } from '@/store/socket.js'
import { useUser } from '@/store/user.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() const route = useRoute()
console.log(route.path) const moreref = ref(null)
const menus = ref([ const callNumberRef = ref(null)
{ const workRef = ref(null)
label: '收银', const store = useUser()
path: '/',
icon: 'ShoppingCartFull' //
}, function updateCallNumber() {
{ callNumberRef.value.getsendMessageAjax()
label: '台桌', }
path: '/table',
icon: 'Reading' function openCall() {
}, callNumberRef.value.show()
{ }
label: '订单',
path: '/order', // ws
icon: 'Tickets' function connectWsHandle() {
}, location.reload()
{ }
label: '网络',
path: '/internat', defineExpose({
icon: 'Paperclip' updateCallNumber
}, })
{
label: '会员',
path: '/user',
icon: 'User'
},
{
label: '交班',
path: '/work',
icon: 'SwitchButton'
}
])
</script> </script>
<style scoped lang="scss"> <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 { .left_menu_wrap {
height: 100vh; height: 100vh;
width: 120px; width: 60px;
background-color: #555; background-color: #555;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between;
.top_item_wra {
display: flex;
flex-direction: column;
}
.item { .item {
flex: 1; width: 100%;
height: 70px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-decoration: none; text-decoration: none;
/* 去除下划线 */
&:first-child { color: inherit;
border-bottom: 1px solid #666; /* 继承父元素的颜色 */
} cursor: pointer;
/* 修改鼠标指针样式 */
border: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent;
&.online { &.online {
@ -98,19 +198,15 @@ const menus = ref([
} }
} }
&.more {
margin-top: 30px;
}
.icon { .icon {
color: #999; color: #999;
font-size: 32px; font-size: 22px;
margin-bottom: 4px; margin-bottom: 4px;
} }
.text { .text {
color: #999; 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,174 @@
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: 24px;display:flex;justify-content:center;">
${data.shopName}
</div>
<div style="font-size: 16px;display: flex; justify-content:center;margin-top:6px;">
交班小票
</div>
<div style="font-size: 12px;margin-top:50px;">
当班时间${data.loginTime}
</div>
<div style="font-size: 12px;">
交班时间${data.handoverTime}
</div>
<div style="font-size: 12px;">
收银员${data.staffName}
</div>
<div style="font-size: 12px;margin-top: 4px;">
当班总收入${data.handAmount}
</div>
<div style="font-size: 12px;margin-top: 4px;">
现金收入${data.cashAmount}
</div>
<div style="font-size: 12px;margin-top: 4px;">
微信收入${data.wechatAmount}
</div>
<div style="font-size: 12px;margin-top: 4px;">
支付宝收入${data.alipayAmount}
</div>
<div style="font-size: 12px;margin-top: 4px;">
会员支付${data.vipPay}
</div>
<div style="font-size: 12px;margin-top: 4px;">
会员充值${data.vipRecharge}
</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.categoryDataList && data.categoryDataList.length) {
for (let item of data.categoryDataList) {
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.productDataList && data.productDataList.length) {
for (let item of data.productDataList) {
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.quickInAmount}</span>
</div>
<div style="font-size: 12px;">
<span>退款金额</span>
<span>${data.refundAmount}</span>
</div>
<div style="font-size: 12px;">
<span>总收入</span>
<span>${data.handAmount}</span>
</div>
<div style="font-size: 12px;">
<span>挂账金额</span>
<span>${data.creditAmount}</span>
</div>
<div style="margin-top: 20px; font-size: 12px;">
<span>总订单数</span>
<span>${data.orderCount}</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}`;
let lastHtml = `${html}${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,139 @@
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: 24px;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.orderNum || ''}
</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) {
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 = 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.originAmount}</span>
</div>
<div style="margin-top: 6px; font-size: 12px;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: 6px; font-size: 22px;display:flex;justify-content: space-between;">
<span>实付</span>
<span>${data.amount}</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: 24px;display:flex;justify-content:center;">
${data.shop_name}
</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: 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);
};

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

@ -0,0 +1,244 @@
<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.shopInfo.shopName }}
</div>
<div class="tips" style="margin-top: 4px; color: var(--el-color-warning);" v-if="showTips">注意您的账号将于{{
store.shopInfo.expireTime }}后过期请尽快续期</div>
<div class="drawerbox_bo_top_left_tow" style="margin-top: 10px">
收银员{{ store.userInfo.name }}
</div>
<div>
<span style="color: #999">{{ 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 0; 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.expireTime).subtract(30, 'day')
// 30
showTips.value = now.isBefore(expired)
console.log("computeExpired===", showTips.value);
}
//
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 #47484b;
.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;
padding: 20px 0;
.drawerbox_bo_box_itembox {
display: flex;
align-items: center;
flex-direction: column;
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,542 @@
<template>
<el-dialog v-model="showDialog" title="选择优惠券" top="12vh" width="80%" @closed="resetHandle">
<el-radio-group v-model="querForm.statusActiveIndex" @change="typeChange">
<el-radio-button :label="`商品兑换券(${goodsCoupon.length}`" :value="0"></el-radio-button>
<el-radio-button :label="`折扣优惠券(${discountCoupon.length}`" :value="1"></el-radio-button>
</el-radio-group>
<div style="height: 50vh;overflow-y: auto;">
<div class="table" v-if="querForm.statusActiveIndex == 0">
<div class="title">可用优惠券</div>
<el-table ref="tableRef1" :data="list.canUseCoupons" border stripe v-loading="tableData.loading"
row-key="id">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="抵扣" prop="discountAmount"></el-table-column>
<el-table-column label="限制" prop="fullAmount">
<template v-slot="scope">
{{ scope.row.fullAmount }}{{ scope.row.discountAmount }}
</template>
</el-table-column>
<el-table-column label="描述" prop="useRestrictions">
<template v-slot="scope">
<div v-html="scope.row.useRestrictions"></div>
</template>
</el-table-column>
<el-table-column label="选择" width="100" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.selected" :true-value="1" :false-value="0"
:disabled="scope.row.disabled" @change="selectCoupon($event, scope.row)"></el-checkbox>
</template>
</el-table-column>
</el-table>
<div class="title">不可用优惠券</div>
<el-table ref="tableRef2" :data="list.noCanUseCoupons" border v-loading="tableData.loading">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="不可用原因">
<template v-slot="scope">
{{ scope.row.canuseResult.reason }}
</template>
</el-table-column>
<el-table-column label="描述" prop="useRestrictions">
<template v-slot="scope">
<div v-html="scope.row.useRestrictions"></div>
</template>
</el-table-column>
</el-table>
</div>
<div class="table" v-if="querForm.statusActiveIndex == 1">
<div class="title">可用优惠券</div>
<el-table ref="tableRef1" :data="list.canUseDiscountCoupon" border stripe v-loading="tableData.loading">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="抵扣" prop="discountAmount"></el-table-column>
<el-table-column label="限制" prop="fullAmount">
<template v-slot="scope">
{{ scope.row.fullAmount }}{{ scope.row.discountAmount }}
</template>
</el-table-column>
<el-table-column label="描述" prop="useRestrictions">
<template v-slot="scope">
<div v-html="scope.row.useRestrictions"></div>
</template>
</el-table-column>
<el-table-column label="选择" width="100" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.selected" :true-value="1" :false-value="0"
:disabled="scope.row.disabled" @change="selectCoupon($event, scope.row)"></el-checkbox>
</template>
</el-table-column>
</el-table>
<div class="title">不可用优惠券</div>
<el-table ref="tableRef2" :data="list.noUseDiscountCoupon" border v-loading="tableData.loading">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="不可用原因">
<template v-slot="scope">
{{ scope.row.canuseResult.reason }}
</template>
</el-table-column>
<el-table-column label="描述" prop="useRestrictions">
<template v-slot="scope">
<div v-html="scope.row.useRestrictions"></div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="dialog_footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="selectCouponConfirm">确认</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { useGoods } from '@/store/goods.js'
import { findCoupon } from '@/api/account.js'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as UTILS from '@/utils/coupon-utils.js';
import { useUser } from "@/store/user.js";
const props = defineProps({
orderList: {
type: Array,
default: []
}
})
const emits = defineEmits(['success'])
const store = useUser()
const goodsStore = useGoods()
const tableRef1 = ref(null)
const tableRef2 = ref(null)
const showDialog = ref(false)
const query = ref({
type: 1,
userId: ''
})
const tableData = reactive({
loading: false,
list: []
})
//
function resetHandle() {
query.value.type = 1
tableData.loading = false
tableData.list = []
}
function typeChange() {
tableData.loading = false
// tableData.list = []
list.canUseCoupons = []
list.noCanUseCoupons = []
list.canUseDiscountCoupon = []
list.noUseDiscountCoupon = []
findCouponAjax()
// if (query.type == 2) {
// tableRef2.value.clearSelection()
// }
}
function groupByPropertyAndCount(arr, property) {
//
const propertyMap = {};
//
arr.forEach(item => {
const key = item[property];
const num = item.number || 1;
if (!propertyMap[key]) {
// propertyMap
propertyMap[key] = {
value: item[property],
count: num
};
} else {
//
propertyMap[key].count += num;
}
});
// propertyMap
return Object.values(propertyMap);
}
//
const goodsCoupon = ref([]) //
const discountCoupon = ref([]) //
function selectCoupon($event, e) {
console.log($event);
console.log(e);
if (querForm.value.statusActiveIndex == 0) {
goodsCoupon.value = []
if ($event) {
goodsCoupon.value.push(e)
list.canUseCoupons.map(item => {
if (item.id != e.id) {
item.disabled = true
}
})
} else {
list.canUseCoupons.map(item => {
if (item.id != e.id) {
item.disabled = false
}
})
}
} else {
discountCoupon.value = []
if ($event) {
discountCoupon.value.push(e)
list.canUseDiscountCoupon.map(item => {
if (item.id != e.id) {
item.disabled = true
}
})
} else {
list.canUseDiscountCoupon.map(item => {
if (item.id != e.id) {
item.disabled = false
}
})
}
}
console.log('goodsStore.cartInfo===', goodsStore.cartInfo);
// if (query.value.type == 1) {
// emits('success', { type: query.value.type, couponList: [{ ...row }] })
// } else {
// let goods = [
// ...goodsStore.cartList,
// ...goodsStore.orderList.map(item => item.goods).flat()
// ]
// let arr = tableRef2.value.getSelectionRows()
// if (!arr.length) {
// ElMessage.error('')
// return
// }
// let obj = groupByPropertyAndCount(arr, 'proId')
// let goodsObj = groupByPropertyAndCount(goods, 'product_id')
// console.log(obj);
// console.log(goodsObj);
// for (let val of goodsObj) {
// for (let item of obj) {
// if (val.value == item.value) {
// if (item.count > val.count) {
// ElMessage.error('')
// return
// }
// }
// }
// }
// emits('success', { type: query.value.type, couponList: arr })
// }
// showDialog.value = false
}
let orderPrice = ref(goodsStore.cartInfo.costSummary.goodsRealAmount);
function updateSelCoupon() {
const newval = [...goodsCoupon.value, ...discountCoupon.value];
const user = goodsStore.vipUserInfo;
let shopInfo = store.shopInfo;
if (!shopInfo.isMemberPrice) {
shopInfo = {};
}
const canDikouGoodsArr = UTILS.returnCanDikouGoods(goodsStore.cartInfo.allGoods, [], user);
if (newval.length >= 2) {
let goodsCoupon = newval.filter((v) => v.type == 2);
let otherCoupon = newval.filter((v) => v.type != 2);
goodsCoupon = goodsCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(
canDikouGoodsArr,
v,
user,
orderPrice.value,
[],
shopInfo
);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount,
};
});
otherCoupon = otherCoupon.map((v) => {
const canuseResult = UTILS.returnCouponCanUse({
canDikouGoodsArr,
coupon: v,
orderPrice: orderPrice.value,
user: user,
selCoupon: goodsCoupon,
shopInfo: shopInfo,
});
const discount = UTILS.returnCouponDiscount(
canDikouGoodsArr,
v,
user,
orderPrice.value,
goodsCoupon,
shopInfo
);
return {
...v,
canuseResult,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount,
};
});
if (!otherCoupon[0].canuseResult.canUse) {
ElMessage.warning('已为您自动剔除叠加不可用的优惠券')
discountCoupon.value = [];
goodsCoupon.value = goodsCoupon;
return;
}
goodsCoupon.value = goodsCoupon;
discountCoupon.value = otherCoupon;
}
}
//
function selectCouponConfirm() {
updateSelCoupon();
let ngoodsCoupon = goodsCoupon.value.filter(item => item.discountAmount > 0)
let ndiscountCoupon = discountCoupon.value.filter(item => item.discountAmount > 0)
if (ngoodsCoupon.length != goodsCoupon.value.length || ndiscountCoupon.length != discountCoupon.value.length) {
ElMessage.warning('已为您自动剔除叠加不可用的优惠券')
}
console.log('ngoodsCoupon', ngoodsCoupon);
console.log('ndiscountCoupon', ndiscountCoupon);
const data = [...ngoodsCoupon, ...ndiscountCoupon]
console.log(data);
// return
emits('success', data)
goodsCoupon.value = []
discountCoupon.value = []
showDialog.value = false
}
const querForm = ref({
searchValue: '',
shopId: '',
shopName: '',
statusActiveIndex: 0
});
const list = reactive({
page: 1,
size: 10,
status: 'nomore',
data: [],
noCanUseCoupons: [],
canUseCoupons: [],
noUseDiscountCoupon: [],
canUseDiscountCoupon: []
});
const couponSel = ref({
id: ''
});
const goodsCouponSel = ref({
id: ''
});
const quansSelArr = computed(() => {
return [couponSel.value, goodsCouponSel.value].filter((v) => v.id);
});
//
function formatCoupon() {
let canUseGoodsCoupon = [];
let canUseDiscountCoupon = [];
let noUseGoodsCoupon = [];
let noUseDiscountCoupon = [];
const user = goodsStore.vipUserInfo;
let shopInfo = store.shopInfo;
if (!shopInfo.isMemberPrice) {
shopInfo = {};
}
const goodsOrderPrice = goodsStore.cartInfo.costSummary.goodsRealAmount;
console.log('goodsOrderPrice==========', goodsOrderPrice);
const canDikouGoodsArr = UTILS.returnCanDikouGoods(goodsStore.cartInfo.allGoods, [], user);
for (let i = 0; i < couponList.value.length; i++) {
const coupon = couponList.value[i];
const canuseResult = UTILS.returnCouponCanUse({
canDikouGoodsArr,
coupon,
goodsOrderPrice,
user,
selCoupon: quansSelArr.value,
shopInfo
});
const { canUse, reason } = canuseResult;
if (coupon.type == 2) {
if (canUse || goodsCouponSel.value.id == coupon.id) {
canUseGoodsCoupon.push(coupon);
} else {
noUseGoodsCoupon.push({
...coupon,
canuseResult
});
}
} else {
if (canUse || couponSel.value.id == coupon.id) {
canUseDiscountCoupon.push(coupon);
} else {
noUseDiscountCoupon.push({
...coupon,
canuseResult
});
}
}
}
//
canUseGoodsCoupon = canUseGoodsCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(canDikouGoodsArr, v, user, goodsOrderPrice, quansSelArr.value, shopInfo);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount
};
});
//
canUseDiscountCoupon = canUseDiscountCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(canDikouGoodsArr, v, user, goodsOrderPrice, quansSelArr.value, shopInfo);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount
};
});
if (querForm.value.statusActiveIndex == 0) {
list.noCanUseCoupons = noUseGoodsCoupon;
canUseGoodsCoupon.map(item => {
item.selected = false
item.disabled = false
})
list.canUseCoupons = canUseGoodsCoupon;
} else {
list.noUseDiscountCoupon = noUseDiscountCoupon;
canUseDiscountCoupon.map(item => {
item.selected = false
item.disabled = false
})
list.canUseDiscountCoupon = canUseDiscountCoupon;
}
console.log('canUseGoodsCoupon', canUseGoodsCoupon);
console.log('noUseGoodsCoupon', noUseGoodsCoupon);
console.log('canUseDiscountCoupon', canUseDiscountCoupon);
console.log('noUseDiscountCoupon', noUseDiscountCoupon);
console.log('list===', list);
}
//
const couponList = ref([])
async function findCouponAjax() {
try {
tableData.loading = true
const res = await findCoupon({
shopUserId: query.value.userId
})
couponList.value = res
formatCoupon()
// if (query.value.type == 1) {
// tableData.list = res
// } else {
// let arr = []
// let ids = props.orderList.map(item => item.product_id)
// res && res.map((item, index) => {
// let found = ids.find(val => val == item.proId)
// let result = found !== undefined
// if (result) {
// let pro = props.orderList.find(val => val.product_id == item.proId)
// console.log('pro===', pro);
// let discount = pro.lowPrice
// if (goodsStore.showVipPrice) {
// discount = pro.memberPrice
// }
// if (+pro.discount_sale_amount) {
// discount = pro.discount_sale_amount
// }
// arr.push({
// ...item,
// productName: pro.productName,
// lowPrice: pro.lowPrice,
// discount: discount
// })
// }
// })
// tableData.list = arr
// console.log('tableData.list===', tableData.list);
// }
} catch (error) {
console.log(error);
}
tableData.loading = false
}
function show(userId) {
showDialog.value = true
query.value.userId = userId
findCouponAjax()
console.log('couponModal.orderList===', props.orderList);
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.dialog_footer {
display: flex;
justify-content: flex-end;
padding-top: var(--el-font-size-base);
}
.title {
padding: 20px 0 10px 0;
font-size: 16px;
color: #333;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,515 @@
<!-- 扫码弹窗 -->
<template>
<div class="dialog">
<el-dialog title="扫码支付" width="600" v-model="dialogVisible" :close-on-click-modal="false" :show-close="!userPayWait"
@open="reset" @closed="resetScanCode">
<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">{{ goodsStore.cartInfo.costSummary.finalPayAmount }}</span>
</div>
<div class="input">
<el-input ref="inputRef" v-model="scanCode" :maxlength="18"
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" @click="submitHandle">
立即支付
</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 style="width: 100%" :disabled="!closeState" type="primary" @click="resetScanCode">
<span v-if="!closeState">{{ closeStateTime }}秒后可重新扫码</span>
<span v-else>重新扫码</span>
</el-button>
</div>
<div class="btn">
<el-button style="width: 100%" :disabled="!closeState" @click="closeScanCode">
<span v-if="!closeState">{{ closeStateTime }}秒后可关闭</span>
<span v-else>关闭</span>
</el-button>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import _ from "lodash";
import { ref } from "vue";
import icon from "@/assets/icon_scan.png";
import { quickPay, queryQuickPayStatus, accountPay } from "@/api/pay";
import { useUser } from "@/store/user.js";
import { useGlobal } from '@/store/global.js'
import { formatDecimal } from '@/utils'
import { microPay, queryOrderStatus, microPayVip, vipPay, queryPayStatus } from '@/api/order.js'
import { ElMessage } from "element-plus";
import { useGoods } from "@/store/goods.js";
const goodsStore = useGoods();
const store = useUser();
const emits = defineEmits(["success", 'orderExpired']);
const props = defineProps({
amount: {
type: [Number, String],
default: 0,
},
// 0 1 2
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,
},
payData: {
type: Object,
default: {}
},
chargeId: {
type: [Number, String],
default: "",
}
});
const dialogVisible = ref(false);
const scanCode = ref("");
const inputRef = ref(null);
const loading = ref(false);
const userPayWait = ref(false);
const fastOrder = ref('')
const submitHandle = _.throttle(submitHandleAjax, 200);
const table_code = ref('')
//
async function submitHandleAjax() {
// console.log('props.selecttype===', props.selecttype);
try {
if (!scanCode.value || scanCode.value.length > 18) return;
loading.value = true;
console.log('props.selecttype===', props.selecttype);
console.log('props.payType===', props.payType);
// return;
//
await goodsStore.isOrderLock({
table_code: goodsStore.orderListInfo.tableCode
})
if (props.selecttype == 0) {
//
if (props.payType == 'scanCode') {
await microPay({
...props.payData,
authCode: scanCode.value
});
} else if (props.payType == 'deposit') {
await vipPay({
...props.payData,
payType: 'scanCode',
authCode: scanCode.value
});
}
} else if (props.selecttype == 1) {
//
await microPayVip({
shopId: store.shopInfo.id,
shopUserId: props.orderId,
amount: props.amount,
authCode: scanCode.value,
rechargeDetailId: props.chargeId,
})
} else {
//
if (props.fast) {
await quickPay({
amount: props.amount,
authCode: scanCode.value,
payType: "scanCode",
});
} else {
if (props.payType == 'scanCode') {
if (props.selecttype == 1) {
} else if (props.selecttype == 2) {
}
}
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 = "";
dialogVisible.value = false;
emits("success");
} catch (error) {
console.log(error);
if (error.code === 211) {
//
goodsStore.isOrderLock({
table_code: table_code.value
}, 'pay_lock')
userPayWait.value = true;
fastOrder.value = error.data
autoCheckOrder()
} else if (error.code == 701) {
//
emits('orderExpired')
} else {
scanCode.value = "";
loading.value = false;
console.log(error);
}
}
}
//
function closeScanCode() {
dialogVisible.value = false;
}
const closeState = ref(false)
const closeStateTime = ref(5);
const closeStateTimer = ref(null)
function closeStateTimerFuc() {
closeStateTimer.value = setInterval(() => {
closeStateTime.value--
if (closeStateTime.value <= 0) {
clearInterval(closeStateTimer.value)
closeStateTimer.value = null
closeState.value = true
}
}, 1000)
}
//
const timer = ref(null)
function autoCheckOrder() {
closeStateTimerFuc()
timer.value = setInterval(() => {
//
goodsStore.isOrderLock({
table_code: table_code.value
}, 'pay_lock')
checkPayStauts(false)
}, 2000)
}
//
async function checkPayStauts(tips = true) {
try {
if (props.selecttype == 1) {
//
const res = await queryPayStatus({
shopId: store.shopInfo.id,
payOrderNo: fastOrder.value.payOrderNo,
});
if (res == 'TRADE_SUCCESS') {
userPayWait.value = false
loading.value = false;
scanCode.value = "";
ElMessage.success("支付成功");
dialogVisible.value = false;
clearAutoCheckOrder()
emits("success");
return;
} else if (res == 'TRADE_AWAIT') {
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 queryOrderStatus({ orderId: props.payData.checkOrderPay.orderId });
if (res == "done") {
//
await goodsStore.isOrderLock({
table_code: table_code.value
}, 'pay_unlock')
userPayWait.value = false
loading.value = false;
scanCode.value = "";
dialogVisible.value = false;
clearAutoCheckOrder()
emits("success");
return;
}
if (res == "unpaid") {
if (tips) {
ElMessage.warning("用户支付中...");
}
return;
} else {
clearAutoCheckOrder()
ElMessage.warning(res.msg || '');
return;
}
}
}
} catch (error) {
console.log(error);
}
}
function clearAutoCheckOrder() {
clearInterval(timer.value)
timer.value = null
}
//
function resetScanCode() {
clearInterval(timer.value)
timer.value = null
clearInterval(closeStateTimer.value)
closeStateTimer.value = null
reset()
setTimeout(() => {
inputRef.value.focus();
}, 500)
}
//
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) {
submitHandle();
}, 100);
function show() {
dialogVisible.value = true;
table_code.value = goodsStore.orderListInfo.tableCode
setTimeout(() => {
inputRef.value.focus();
}, 500);
}
function close() {
dialogVisible.value = false;
}
function reset() {
userPayWait.value = false;
loading.value = false;
scanCode.value = "";
closeState.value = false
closeStateTime.value = 5
//
// goodsStore.isOrderLock({
// table_code: table_code.value
// }, 'pay_unlock')
}
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

@ -0,0 +1,118 @@
<template>
<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-select v-model="tableData.isVips" placeholder="是否为会员" style="width: 150px;" @change="changeIsVips">
<el-option label="全部" value=""></el-option>
<el-option label="是" :value="1"></el-option>
<el-option label="否" :value="0"></el-option>
</el-select>
</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="400px" border stripe v-loading="tableData.loading">
<el-table-column prop="nickName" label="昵称" width="150px" />
<el-table-column prop="phone" label="手机" width="150px" />
<el-table-column prop="code" label="编号" width="150px" />
<el-table-column prop="level" label="等级" />
<el-table-column prop="accountPoints" label="积分" />
<el-table-column prop="amount" label="余额" width="120px">
<template v-slot="scope">
{{ formatDecimal(scope.row.amount) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120px" fixed="right">
<template v-slot="scope">
<el-button type="primary" @click="toHomeMember(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination layout="prev, pager, next, total" background style="margin-top: 20px;"
:total="Number(tableData.total)" v-model:current-page="tableData.page" @current-change="getMemberList" />
</el-dialog>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { shopUserList } from "@/api/account.js";
import { formatDecimal } from '@/utils/index.js'
const props = defineProps({
amount: {
type: Number,
default: 0
}
})
const emits = defineEmits(['success'])
const showDialog = ref(false)
const tableData = reactive({
phone: '',
isVips: "",
loading: false,
list: [],
page: 1,
size: 10,
total: 0
})
//
async function toHomeMember(row) {
try {
emits('success', { ...row })
} catch (error) {
console.log(error);
}
showDialog.value = false
}
//
function resetTable() {
tableData.phone = ''
tableData.isVips = ''
tableData.page = 1
getMemberList()
}
function changeIsVips() {
tableData.page = 1
getMemberList()
}
//
async function getMemberList() {
try {
tableData.loading = true
const res = await shopUserList({
key: tableData.phone,
isVips: tableData.isVips,
page: tableData.page,
size: tableData.size,
amount: props.amount
})
tableData.list = res.records
tableData.total = res.totalRow
} catch (error) {
console.log(error);
}
setTimeout(() => {
tableData.loading = false
}, 500);
}
//
function show() {
showDialog.value = true
getMemberList()
}
defineExpose({
show
})
</script>

View File

@ -1,33 +1,44 @@
<template> <template>
<el-dialog title="选择规格" width="800" v-model="show"> <el-dialog :title="goods.name" width="600" v-model="dialogVisible" top="5vh">
<div class="header">选择规格</div> <div class="header">选择规格</div>
<div class="row"> <div v-loading="loading">
<div class="title">规格</div> <div class="row" v-for="(item, index) in goods.selectSpec" :key="index">
<div class="sku_wrap"> <div class="title">{{ item.name }}</div>
<div class="item">默认1人份</div> <div class="sku_wrap">
</div> <!-- <div class="item" :class="{ active: val.active }" v-for="(val, i) in item.value" :key="i"
</div> @click="selectedSku(index, i)">{{ val.name }}</div> -->
<div class="row"> <el-button :plain="!val.active" type="primary" v-for="(val, i) in item.selectSpecResult
<div class="title">温度</div> " :key="i" :disabled="val.disabled" @click="selectedSku(index, i)" class="btn">{{ val.name }}</el-button>
<div class="sku_wrap"> </div>
<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> </div>
</div> </div>
<div class="footer"> <div class="footer">
<div class="btn"> <div class="info">
<el-button plain style="width: 100%;" @click="show = false">取消</el-button> <div class="price" v-if="goodsInfo.id">
<span class="i"></span>
<span class="n">{{ formatDecimal(+goodsInfo.salePrice) }}</span>
</div>
<span>库存{{ stockNumber }}</span>
</div> </div>
<div class="btn"> <div class="btn_wrap">
<el-button type="primary" style="width: 100%;">确认</el-button> <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 || !+goodsInfo.isGrounding || !!+goodsInfo.isSoldStock"
@click="submitSku">
<template v-if="goodsInfo.id && !+goodsInfo.isGrounding">
未上架
</template>
<template v-if="goodsInfo.id && +goodsInfo.isSoldStock">
已售罄
</template>
<template v-if="(+goodsInfo.isGrounding && !+goodsInfo.isPauseSale) || !goodsInfo.id">
确认
</template>
</el-button>
</div>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
@ -35,8 +46,178 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
const show = ref(true) import { formatDecimal } from '@/utils/index.js'
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([])
const stockNumber = ref(0)
//
function submitSku() {
dialogVisible.value = false
// switch (type.value) {
// case 'shop':
// break;
// case 'cart':
// emit('success', goodsInfo.value)
// break;
// default:
// break;
// }
emit('success', goodsInfo.value)
}
//
function selectedSku(index = 0, i = 0) {
goodsInfo.value = {}
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.skuList.map(val => {
// console.log(val);
// console.log(`${selecSkuArray.value.join(',')},${item.name}`);
// console.log(val.specInfo.indexOf(`${selecSkuArray.value.join(',')},${item.name}`));
if (val.specInfo.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() {
goods.value.skuList.map(item => {
if (item.specInfo == selectedSkuTag.value) {
goodsInfo.value = item
}
})
console.log('goodsInfo.value===', goodsInfo.value);
}
//
function show(item, t = 'shop') {
stockNumber.value = item.stockNumber
type.value = t
let arr = []
for (let val in item.selectSpecInfo) {
if (item.selectSpecInfo[val].length) {
switch (type.value) {
case 'shop':
arr.push({
name: val,
selectSpecResult: item.selectSpecInfo[val].map(item => {
return {
active: false,
name: item,
disabled: false
}
})
})
break;
case 'cart':
//
const skus = item.sku_name.split(',')
arr.push({
name: val,
selectSpecResult: item.selectSpecInfo[val].map(item => {
return {
active: !!skus.find(val => val === item),
name: item,
disabled: false
}
})
})
break;
default:
break;
}
}
}
item.selectSpec = arr
//
goodsInfo.value = {}
goods.value = {}
selectedSkuNum.value = 0
dialogVisible.value = true
goods.value = ""
goods.value = item
selectedSuccess()
}
defineExpose({
show
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -47,47 +228,58 @@ const show = ref(true)
.row { .row {
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ececec;
margin-top: 20px; margin-top: var(--el-font-size-base);
.title { .title {
font-size: 20px; font-size: var(--el-font-size-base);
} }
.sku_wrap { .sku_wrap {
display: flex; display: flex;
padding: 14px 0; padding: var(--el-font-size-base) 0;
flex-wrap: wrap;
.item { gap: var(--el-font-size-base);
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;
}
}
} }
} }
.footer { .footer {
display: flex; padding-top: 30px;
padding-top: 100px;
.btn { .info {
flex: 1; height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
&:last-child { .price {
margin-left: 14px; color: var(--el-color-danger);
display: flex;
align-items: center;
font-weight: bold;
.i {
font-size: 14px;
position: relative;
top: 2px;
margin-right: 2px;
}
.n {
font-size: 20px;
}
}
}
.btn_wrap {
display: flex;
.btn {
flex: 1;
&:last-child {
margin-left: 14px;
}
} }
} }
} }

View File

@ -0,0 +1,119 @@
<!-- 取餐号组件 -->
<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" 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="footer">
<el-button type="primary" style="width: 100%" :disabled="number <= 0" :loading="loading"
@click="confirmHandle">确认</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { clearNoNum } from '@/utils/index.js'
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;
number.value = clearNoNum({ value: number.value })
}
//
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 || number.value <= 0) 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,123 @@
<template>
<el-dialog v-model="showDialog" :title="`发现新版本v${newVersionRef}`" top="30vh" width="500" :close-on-click-modal="false"
:close-on-press-escape="false" :show-close="false">
<div class="message" v-html="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.isForce && !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/system.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 newVersionRef = ref('')
//
function closeHandle() {
showDialog.value = false
useStorage.set('updateFlag', true)
}
//
async function findVersionAjax() {
try {
let updateFlag = useStorage.get('updateFlag')
const res = await findVersion()
if (res && res.version) {
const newVersion = res.version;
const oldVersion = packageData.version;
console.log('当前版本:', oldVersion);
console.log('获取版本:', newVersion);
const result = compareVersions(newVersion, oldVersion);
if (result > 0) {
if (!updateFlag) {
newVersionRef.value = newVersion
showDialog.value = true
updataInfo.value = res
}
}
}
} catch (error) {
console.log('检查版本更新===', error);
}
}
//
function compareVersions(version1, version2) {
const v1 = version1.split('.').map(Number);
const v2 = version2.split('.').map(Number);
const maxLength = Math.max(v1.length, v2.length);
for (let i = 0; i < maxLength; i++) {
const num1 = v1[i] || 0;
const num2 = v2[i] || 0;
if (num1 > num2) {
return 1;
} else if (num1 < num2) {
return -1;
}
}
return 0;
}
//
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.shopInfo.id) {
findVersionAjax()
}
ipcRenderer.on('updateProgress', (event, res) => {
uploadPro.value = res
})
})
</script>

View File

@ -0,0 +1,113 @@
<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 userStore from '@/utils/useStorage.js'
import { ElUpload, ElMessage } from 'element-plus';
const fileList = ref([])
//
const props = defineProps({
uploadUrl: {
type: String,
default: import.meta.env.MODE == 'development' ? '/api/account/admin/common/upload' : import.meta.env.VITE_API_URL + '/account/admin/common/upload',
},
headers: {
type: Object,
default: () => ({
token: userStore.get('token'),
shopId: userStore.get('shopInfo').id,
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>

View File

@ -1,10 +1,12 @@
import { createRouter, createWebHashHistory } from "vue-router"; 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 = [ const routes = [
{ {
path: "/", path: "/",
name: "home", name: "home",
// component: test,
component: home, component: home,
}, },
{ {
@ -23,14 +25,82 @@ const routes = [
}, },
component: () => import("@/views/register.vue"), component: () => import("@/views/register.vue"),
}, },
{
path: "/group_buy",
name: "group_buy",
meta: {
index: 1,
},
component: () => import("@/views/group_buy/index.vue"),
},
{ {
path: "/table", path: "/table",
name: "table", name: "table",
meta: { meta: {
index: 1, 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({ const router = createRouter({

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

@ -0,0 +1,176 @@
import { defineStore } from "pinia";
export const useGlobal = defineStore("global", {
state: () => ({
// 是否监听叫号
isCallNumber: true,
orderStatus: [
{
type: "unpaid",
label: "待支付",
},
{
type: "in_production",
label: "制作中",
},
{
type: "wait_out",
label: "待取餐",
},
{
type: "done",
label: "订单完成",
},
{
type: "refunding",
label: "申请退单",
},
{
type: "refund",
label: "退单",
},
{
type: "part_refund",
label: "部分退单",
},
{
type: "cancelled",
label: "取消订单",
},
],
orderType: [
{
type: "cash",
label: "收银",
},
{
type: "miniapp",
label: "小程序",
},
],
platformType: [
{
type: "WX",
label: "微信小程序",
},
{
type: "ALI",
label: "支付宝小程序",
},
{
type: "PC",
label: "收银机客户端",
},
{
type: "WEB",
label: "PC管理端",
},
{
type: "APP",
label: "APP管理端",
},
{
type: "H5",
label: "收款码",
},
],
dineMode: [
{
type: "dine-in",
label: "堂食",
},
{
type: "take-out",
label: "外带",
},
{
type: "take-away",
label: "外卖",
},
],
payType: [
{
type: "main_scan",
label: "主扫",
},
{
type: "back_scan",
label: "被扫",
},
{
type: "wechat_mini",
label: "微信小程序",
},
{
type: "alipay_mini",
label: "支付宝小程序",
},
{
type: "vip_pay",
label: "会员支付",
},
{
type: "cash_pay",
label: "现金支付",
},
{
type: "credit_pay",
label: "挂账支付",
},
],
bizCodes: [
{
type: "cashIn",
label: "现金充值",
},
{
type: "wechatIn",
label: "微信小程序充值",
},
{
type: "alipayIn",
label: "支付宝小程序充值",
},
{
type: "awardIn",
label: "充值奖励",
},
{
type: "rechargeRefund",
label: "充值退款",
},
{
type: "orderPay",
label: "订单消费",
},
{
type: "orderRefund",
label: "订单退款",
},
{
type: "adminIn",
label: "管理员充值",
},
{
type: "adminOut",
label: "管理员消费",
},
],
refundType: [
{
type: "cash",
label: "手动退款",
},
{
type: "payBack",
label: "原路退回",
},
],
}),
actions: {
// 更新状态
updateData(state) {
this.isCallNumber = state;
},
},
});

1058
src/store/goods.js Normal file

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,249 @@
import { defineStore } from "pinia";
import { ipcRenderer } from "electron";
import { useUser } from "@/store/user.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";
import { printerList } from "@/api/account.js";
export const usePrint = defineStore("print", {
state: () => ({
isPrintService: false, // 打印服务是否启动
printServiceTimer: false, // 打印服务定时器
printServiceTimerCount: 0, // 打印服务定时器计数
printServiceTimerMaxCount: 10, // 打印服务定时器最大计数
showPrintNotService: false, // 是否显示重启软件,
localDevices: [], // 本地打印机列表
deviceNoteList: [], // 添加的打印机
deviceLableList: [], // 添加的打印机
labelList: [], // 要打印的队列数据
printTimer: null,
receiptList: [], // 小票队列数据
receiptTimer: null,
}),
actions: {
// 获取本地打印机和已添加的可以用打印机列表
async init() {
// 获取本地打印机
ipcRenderer.send("getPrintList");
ipcRenderer.on("printList", (event, arg) => {
this.localDevices = arg;
});
try {
// 获取已添加的打印机
const res = await printerList();
this.deviceNoteList = res.records.filter(
(item) => item.status && item.subType == "cash"
);
this.deviceLableList = res.records.filter(
(item) => item.status && item.subType == "label"
);
console.log("打印队列初始化成功", {
deviceNoteList: this.deviceNoteList,
deviceLableList: this.deviceLableList,
});
} catch (error) {
console.error("获取已添加的打印机列表失败", error);
}
// 检测打印服务是否启动
this.checkPrintService();
},
// 检测打印组件服务是否启动
checkPrintService() {
this.printServiceTimer = setInterval(() => {
if (
typeof LODOP !== "undefined" &&
LODOP.webskt &&
LODOP.webskt.readyState == 1
) {
// 准备好
this.isPrintService = true;
clearInterval(this.printServiceTimer);
this.printServiceTimer = null;
} else {
this.printServiceTimerCount++;
console.log("打印服务未启动", this.printServiceTimerCount);
if (this.printServiceTimerCount >= this.printServiceTimerMaxCount) {
// 超过最大次数
this.isPrintService = false;
this.showPrintNotService = true;
clearInterval(this.printServiceTimer);
this.printServiceTimer = null;
}
}
console.log("打印服务是否启动:", this.isPrintService);
}, 1000);
},
// 检查本地打印机是否能正常使用
checkLocalPrint(address) {
let print = "";
for (let item of this.localDevices) {
if (item.name == address) {
print = item;
}
}
if (!print.name) {
return false;
} else {
return true;
}
},
// 打印标签小票
labelPrint(props) {
const store = useUser();
if (
this.deviceLableList.length &&
this.checkLocalPrint(this.deviceLableList[0].address)
) {
let pids = this.deviceLableList[0].categoryList;
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].address,
createdAt: dayjs(props.createdAt).format("YYYY-MM-DD HH:mm:ss"),
isPrint: false,
count: `${count}/${sum}`,
ticketLogo: store.shopInfo.ticketLogo,
});
}
}
});
console.log("this.labelList===", this.labelList);
// return;
// 执行打印操作
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);
}
}
}, 2000);
},
// 添加小票打印队列数据
pushReceiptData(props, isDevice = true) {
// console.log("pushReceiptData===", props);
if (!isDevice) {
// 测试打印,无需校验本地打印机
this.receiptList.push(props);
this.startReceiptPrint();
} else {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].address) &&
this.isPrintService
) {
const store = useUser();
props.deviceName = this.deviceNoteList[0].address;
props.shop_name = store.shopInfo.shopName;
props.loginAccount = store.userInfo.name;
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);
}
}, 2000);
},
// 打印交班小票
printWork(data) {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].address)
) {
data.deviceName = this.deviceNoteList[0].address;
lodopPrintWork(data);
} else {
console.log("交班小票:未在本机查询到打印机");
}
},
// 打印订单发票
printInvoice(data) {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].address)
) {
data.deviceName = this.deviceNoteList[0].address;
invoicePrint(data);
} else {
console.log("订单发票:未在本机查询到打印机");
}
},
// 打印退单小票
printRefund(data) {
if (
this.deviceNoteList.length &&
this.checkLocalPrint(this.deviceNoteList[0].address)
) {
data.deviceName = this.deviceNoteList[0].address;
refundPrint(data);
} else {
console.log("退单小票:未在本机查询到打印机");
}
},
},
});

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

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

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

@ -0,0 +1,253 @@
import _ from "lodash";
import { defineStore } from "pinia";
import { useUser } from "@/store/user.js";
import { usePrint } from "@/store/print.js";
import useStorage from "@/utils/useStorage";
import ReconnectingWebSocket from "reconnecting-websocket";
import { useGoods } from "@/store/goods.js";
import { ElMessage } from "element-plus";
import { getOrderByIdAjax, commOrderPrintData } from "@/utils/index.js";
export const useSocket = defineStore("socket", {
state: () => ({
online: false, // 在线状态
ws: null, // websocket实例
heartbeatTimer: null, // 心跳计时器
orderList: [],
orderListTimer: null,
log: false,
isPrinting: false,
}),
actions: {
// 关闭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 }
),
cartInit() {
const store = useUser();
const goodsStore = useGoods();
this.ws.send(
JSON.stringify({
type: "cashier",
account: `cashier_${store.shopInfo.id}`,
operate_type: "init",
shop_id: store.shopInfo.id,
table_code:
goodsStore.tableInfo.tableCode || useStorage.get("tableCode"),
})
);
},
// 初始化
init(wsUrl = import.meta.env.VITE_API_WSS) {
const store = useUser();
const printStore = usePrint();
const goodsStore = useGoods();
if (!store.shopInfo.id) return;
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.cartInit();
this.startheartbeat();
});
this.ws.addEventListener("message", async (e) => {
let data = JSON.parse(e.data);
if (data.operate_type == "init") {
// console.log("接收消息", data);
if (!goodsStore.tableInfo.tableCode) {
useStorage.set("tableCode", data.table_code);
}
goodsStore.getCartList(data.data);
}
this.ws.send(
JSON.stringify({
type: "receipt",
msg_id: data.msg_id,
})
);
if (data.data_type == "cart") {
if (data.status == 1) {
// 返回成功状态
switch (data.operate_type) {
case "add":
// 添加购物车商品
goodsStore.successAddCart(data.data);
break;
case "edit":
// 编辑购物车商品
goodsStore.successEditCart(data.data);
break;
case "del":
// 删除购物车商品
if (data.type && data.type == "bc") {
goodsStore.successDeleteCartItem(data.data);
} else {
goodsStore.successDeleteCartItem();
}
break;
case "cleanup":
// 清空购物车
if (
data.data.table_code == goodsStore.orderListInfo.tableCode &&
!data.type
)
return;
goodsStore.successClearCart();
break;
case "batch":
// 整单打包
this.cartInit();
break;
case "rottable":
// 转桌
useStorage.set("tableCode", data.data.new_table_code);
goodsStore.successClearCart();
goodsStore.historyOrderAjax(data.data.new_table_code);
this.cartInit();
break;
case "clearOrder":
// 清空订单或删除订单
goodsStore.historyOrderAjax(data.data.table_code);
break;
default:
break;
}
} else {
if (data.type == "no_suit_num") {
let product = goodsStore.cartList[goodsStore.cartActiveIndex];
ElMessage.error(
`${product.product_name}库存不足,已删除,请选择其他商品`
);
goodsStore.operateCart(product, "del");
} else {
ElMessage.error(data.msg || "操作失败");
}
}
} else if (data.data_type == "order") {
// 收到订单消息,打印订单小票
let orderInfo = data.data.split("_");
let orderId = orderInfo[0]; // 订单ID
let orderModel = orderInfo[1]; // 订单类型
let orderStatus = orderInfo[2]; // 订单状态
let printList = useStorage.get("printList") || [];
if (goodsStore.orderListInfo.tableCode) {
goodsStore.historyOrderAjax(goodsStore.orderListInfo.tableCode);
}
// 防止重复打印
if (!printList.some((el) => el == orderId) && orderStatus == 1) {
printList.push(orderId);
useStorage.set("printList", _.uniq(printList));
this.orderList.push(orderId);
this.startPrintInterval();
}
} else if (data.data_type == "product_update") {
// 商品更新
this.updateGoods();
}
});
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();
});
},
updateGoods: _.throttle(function () {
const goodsStore = useGoods();
goodsStore.updateGoodsList();
}, 1000),
// 启动心跳连接
startheartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.log) console.log("发送心跳");
this.ws.send(JSON.stringify({ type: "ping_interval", set: "cashier" }));
}, 10000);
},
// 清除心跳
clearHeartBeat() {
// 清除心跳
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
},
// 打印队列开始执行
startPrintInterval() {
if (this.isPrinting) return; // 如果正在打印,直接返回
this.isPrinting = true; // 标记为正在打印
let printTime = 2000; // 定时器
const printStore = usePrint();
const printNextOrder = async () => {
try {
if (!this.orderList.length) {
this.isPrinting = false; // 订单处理完,标记为不在打印
return;
}
const orderInfo = await getOrderByIdAjax(this.orderList[0]);
if (orderInfo.status == "done") {
// 打印订单小票
printStore.pushReceiptData(commOrderPrintData(orderInfo));
// 打印标签小票
printStore.labelPrint(commOrderPrintData(orderInfo));
}
this.orderList.splice(0, 1);
// 递归调用打印下一个订单
setTimeout(printNextOrder, printTime);
} catch (error) {
console.log(error);
// 发生错误时继续尝试下一个订单
// this.orderList.splice(0, 1);
setTimeout(printNextOrder, printTime);
}
};
// 开始打印第一个订单
printNextOrder();
},
},
});

View File

@ -1,13 +1,174 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
export const useUserStore = defineStore({ import {
id: "user", login,
shopStaffInfo,
shopInfo_detail,
logout,
shopExtendDetail,
shopPagePermissionMine,
} from "@/api/account.js";
import useStorage from "@/utils/useStorage";
import { useSocket } from "@/store/socket.js";
import { useRoute, useRouter } from "vue-router";
export const useUser = defineStore("user", {
state: () => ({ state: () => ({
name: "张三", userInfo: useStorage.get("userInfo"),
token: "sas121sasdADSAD", shopInfo: useStorage.get("shopInfo"),
token: useStorage.get("token"),
loginType: useStorage.get("loginType") || 0,
menus: [
{
label: "收银",
path: "/",
state: 0,
icon: "ShoppingCartFull",
},
],
menuList: [
{
label: "台桌",
path: "/table",
icon: "Reading",
},
{
label: "团购",
path: "/group_buy",
icon: "Handbag",
},
{
label: "订单",
path: "/order",
icon: "Tickets",
},
{
label: "会员",
path: "/member",
icon: "User",
},
{
label: "排队",
path: "/queue",
icon: "Timer",
},
],
route: null,
router: null,
}), }),
actions: { actions: {
login(data) { // 登录
this.name = data; userlogin(param) {
return login(param).then(async (res) => {
this.token = res.tokenInfo.tokenValue;
useStorage.set("token", this.token);
const logo = shopExtendDetail({ autoKey: "ticket_logo" });
useStorage.set("shopInfo", {
...res.shopInfo,
ticketLogo: logo ? logo.value : "",
});
this.shopInfo = useStorage.get("shopInfo");
useStorage.set("loginType", param.loginType);
this.shopPagePermissionMineAjax(param.loginType);
return await this.shopStaffInfo();
});
},
// 初始化路由信息
initRoute() {
this.route = useRoute();
this.router = useRouter();
},
// 获取当前员工已拥有页面路径
async shopPagePermissionMineAjax(loginType = useStorage.get("loginType")) {
try {
if (this.route == null) {
this.initRoute();
}
if (loginType == 0) {
this.menus[0].state = 1;
this.menus.push(...this.menuList);
} else {
const res = await shopPagePermissionMine();
let pathFlag = true;
res.map((item) => {
if (item.path == "/") {
this.menus[0].state = 1;
}
if (this.route.path != item.path) {
pathFlag = false;
}
this.menuList.map((menu) => {
if (item.path === menu.path) {
this.menus.push(menu);
}
});
});
console.log("当前路由===", this.route);
if (!pathFlag) {
this.router.push("/");
}
}
} catch (error) {
console.log("获取当前员工已拥有页面路径失败===", error);
}
},
// 获取用户信息
async shopStaffInfo() {
try {
const res = await shopStaffInfo();
useStorage.set("userInfo", res);
this.userInfo = useStorage.get("userInfo");
} catch (error) {
console.log(error);
}
},
// 更新店铺信息
async getShopInfo() {
try {
const res = await shopInfo_detail();
// 获取标签小票的logo
const logo = await shopExtendDetail({ autoKey: "ticket_logo" });
useStorage.set("shopInfo", {
...res,
ticketLogo: logo ? logo.value : "",
});
this.shopInfo = useStorage.get("shopInfo");
} catch (error) {
console.log(error);
}
},
// 退出登录
async logout() {
try {
const socket = useSocket();
await logout();
socket.close();
useStorage.del("userInfo");
useStorage.del("shopInfo");
useStorage.del("token");
useStorage.del("douyin");
useStorage.del("categoryIndex");
useStorage.del("tableCode");
this.userInfo = {};
this.shopInfo = {};
this.token = "";
this.menus = [
{
label: "收银",
path: "/",
state: 0,
icon: "ShoppingCartFull",
},
];
} catch (error) {
console.log(error);
}
}, },
}, },
}); });

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;
}
}

499
src/utils/coupon-utils.js Normal file
View File

@ -0,0 +1,499 @@
import { BigNumber } from "bignumber.js";
import _ from "lodash";
/**
* 返回商品单价
* @param goods 商品
* @param user 用户信息
* @param {Object} shopInfo
*/
export function returnGoodsPrice(goods, user, shopInfo) {
if (!goods) {
return 0;
}
if (goods.discount_sale_amount * 1 > 0) {
return goods.discount_sale_amount;
}
if (shopInfo && !shopInfo.isMemberPrice) {
return goods.salePrice;
}
if (user.isVip && goods.memberPrice * 1 <= goods.salePrice * 1 && goods.memberPrice * 1 > 0) {
return goods.memberPrice;
}
return goods.salePrice;
}
/**
* 返回商品分组
* @param arr 商品列表
*/
export function returnGoodsGroupMap(arr) {
let map = {};
arr.forEach((v) => {
const key = v.productId + "_" + v.skuId;
if (!map[key]) {
map[key] = [];
}
map[key].push(v);
});
return map;
}
/**
* 优惠券类型1-满减券2-商品兑换券3-折扣券4-第二件半价券5-消费送券6-买一送一券7-固定价格券8-免配送费券
* @param coupon
*/
export function returnCoupType(coupon) {
const couponTypes = {
1: "满减券",
2: "商品券",
3: "折扣券",
4: "第二件半价券",
5: "消费送券",
6: "买一送一券",
7: "固定价格券",
8: "免配送费券",
};
return couponTypes[coupon.type] || "未知类型";
}
/**
* 返回商品券抵扣后的商品列表
* @param canDikouGoodsArr 可抵扣商品列表
* @param selCoupon 已选择的优惠券列表
* @param user 用户信息
*/
export function returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user) {
const types = [2, 4, 6];
// 收集已抵扣商品并关联对应的优惠券类型
const goodsCouponGoods = selCoupon
.filter((v) => types.includes(v.type))
.reduce((prev, cur) => {
// 给每个抵扣商品添加所属优惠券类型
const goodsWithType = cur.discount.hasDiscountGoodsArr.map((goods) => ({
...goods,
couponType: cur.type, // 记录该商品是被哪种类型的优惠券抵扣的
}));
prev.push(...goodsWithType);
return prev;
}, []);
const arr = _.cloneDeep(canDikouGoodsArr)
.map((v) => {
const findCart = goodsCouponGoods.find((carts) => carts.id == v.id);
if (findCart) {
// 根据优惠券类型判断扣减数量
if ([4, 6].includes(findCart.couponType)) {
// 类型4第二件半价或6买一送一数量减2
v.num -= 2;
} else {
// 其他类型如类型2商品券按原逻辑扣减对应数量
v.num -= findCart.num;
}
}
return v;
})
.filter((v) => v.num > 0); // 过滤掉数量<=0的商品
return arr;
}
/**
* 判断优惠券是否可使用并返回不可用原因
*
* @param {Object} args - 函数参数集合
* @param {Array} args.canDikouGoodsArr - 可参与抵扣的商品列表
* @param {Object} args.coupon - 优惠券信息对象
* @param {boolean} args.coupon.use - 优惠券是否启用
* @param {Array} args.coupon.useFoods - 优惠券适用的商品ID列表
* @param {number} args.coupon.fullAmount - 优惠券使用门槛金额
* @param {number} args.coupon.type - 优惠券类型
* @param {number} args.goodsOrderPrice - 订单中所有商品的总金额
* @param {Object} args.user - 用户信息对象
* @param {Object} args.selCoupon - 已经选择的优惠券信息对象
* @param {Object} args.shopInfo
* @returns {Object} - { canUse: boolean, reason: string } 可用状态及不可用原因
*/
export function returnCouponCanUse(args) {
let { canDikouGoodsArr, coupon, goodsOrderPrice, user, selCoupon, shopInfo } = args;
// 优惠券未启用
if (!coupon.use) {
return {
canUse: false,
reason: coupon.noUseRestrictions || "不在可用时间段内",
};
}
// 计算门槛金额
let fullAmount = goodsOrderPrice;
canDikouGoodsArr = returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user, shopInfo);
//优惠券指定门槛商品列表
let canCalcGoodsArr = [...canDikouGoodsArr];
//部分商品参与门槛计算
if (coupon.thresholdFoods.length) {
canCalcGoodsArr = canDikouGoodsArr.filter((v) => {
return coupon.thresholdFoods.find((food) => food.id == v.productId);
});
fullAmount = canCalcGoodsArr.reduce((pre, cur) => {
return pre + returnGoodsPrice(cur, user, shopInfo) * cur.num;
}, 0);
}
// 是否全部商品可用
const isDikouAll = coupon.useFoods.length === 0;
// 订单可用商品列表
let canUseGoodsArr = [];
if (!isDikouAll) {
canUseGoodsArr = canDikouGoodsArr.filter((v) => {
return coupon.useFoods.find((food) => food.id == v.productId);
});
}
if (user.isVip && !coupon.vipPriceShare) {
return {
canUse: false,
reason: "非会员可用",
};
}
if (selCoupon.length > 0 && !selCoupon[0].otherCouponShare) {
return {
canUse: false,
reason: "当前选中的券不可与其他券同享",
};
}
if (selCoupon.length > 0 && !coupon.otherCouponShare) {
return {
canUse: false,
reason: "当前选中的券不可与其他券同享",
};
}
// 满减券和折扣券计算门槛金额是否满足
if ([1, 3].includes(coupon.type)) {
if (canCalcGoodsArr.length <= 0) {
return {
canUse: false,
reason: "没有可参与计算门槛的商品",
};
}
// 不满足门槛金额
if (fullAmount < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
}
// 商品兑换券,第二件半价和买一送一判断是否有可用商品
if ([2, 4, 5].includes(coupon.type)) {
if (coupon.type == 2 && fullAmount < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
// 没有符合条件的商品
if (isDikouAll && canDikouGoodsArr.length === 0) {
return {
canUse: false,
reason: "没有符合条件的商品",
};
}
if (!isDikouAll && canUseGoodsArr.length === 0) {
return {
canUse: false,
reason: "没有符合条件的商品",
};
}
}
//商品兑换券是否达到门槛金额
if (coupon.type == 2 && goodsOrderPrice < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
// 买一送一券特殊验证
if (coupon.type === 6) {
let canUse = false;
if (isDikouAll) {
canUse = canDikouGoodsArr.some((v) => v.num >= 2);
} else if (canUseGoodsArr.length > 0) {
canUse = canUseGoodsArr.some((v) => v.num >= 2);
}
if (!canUse) {
return {
canUse: false,
reason: "需要购买至少2件相同的商品才能使用",
};
}
}
// 第二件半价券特殊验证
if (coupon.type === 4) {
let canUse = false;
if (isDikouAll) {
canUse = canDikouGoodsArr.some((v) => v.num >= 2);
} else if (canUseGoodsArr.length > 0) {
canUse = canUseGoodsArr.some((v) => v.num >= 2);
}
if (!canUse) {
return {
canUse: false,
reason: "需要购买至少2件相同的商品才能使用",
};
}
}
// 所有条件都满足
return {
canUse: true,
reason: "",
};
}
/**
* 计算抵扣商品金额
* @param discountGoodsArr 可抵扣商品列表
* @param discountNum 抵扣数量
* @param user 用户信息
* @param {Object} shopInfo 店铺信息
*/
export function calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, shopInfo) {
let hasCountNum = 0;
let discountPrice = 0;
let hasDiscountGoodsArr = [];
for (let i = 0; i < discountGoodsArr.length; i++) {
if (hasCountNum >= discountNum) {
break;
}
const goods = discountGoodsArr[i];
const shengyuNum = discountNum - hasCountNum;
const num = Math.min(goods.num, shengyuNum);
discountPrice += returnGoodsPrice(goods, user, shopInfo) * num;
hasCountNum += num;
hasDiscountGoodsArr.push({
...goods,
num,
});
}
return {
discountPrice,
hasDiscountGoodsArr,
};
}
/**
* 计算优惠券抵扣金额
* @param arr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param goodsOrderPrice 商品订单金额
* @param selCoupon 已选择的优惠券列表
* @param shopInfo 店铺信息
*/
export function returnCouponDiscount(arr, coupon, user, goodsOrderPrice, selCoupon, shopInfo) {
arr=returnCanDikouGoods(arr,user,shopInfo)
const canDikouGoodsArr = returnCanDikouGoodsArr(arr, selCoupon, user);
console.log('canDikouGoodsArr',canDikouGoodsArr)
if (coupon.type == 2) {
return returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shopInfo);
}
if (coupon.type == 6) {
const result = returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopInfo);
return result;
}
if (coupon.type == 4) {
return returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo);
}
if (coupon.type == 3) {
return returnCouponZhekouDiscount(canDikouGoodsArr, coupon, user, goodsOrderPrice, selCoupon);
}
}
/**
* 折扣券抵扣金额
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param goodsOrderPrice 商品订单金额
* @param selCoupon 已选择的优惠券列表
*
*/
export function returnCouponZhekouDiscount(
canDikouGoodsArr,
coupon,
user,
goodsOrderPrice,
selCoupon
) {
const { discountRate, maxDiscountAmount } = coupon;
// 计算商品优惠券折扣总和使用BigNumber避免精度问题
const goodsCouponDiscount = selCoupon
.filter((v) => v.type == 2)
.reduce((prve, cur) => {
return new BigNumber(prve).plus(new BigNumber(cur.discount.discountPrice));
}, new BigNumber(0));
// 将商品订单价格转换为BigNumber并减去优惠券折扣
const adjustedGoodsOrderPrice = new BigNumber(goodsOrderPrice).minus(goodsCouponDiscount);
// 计算优惠比例:(100 - 折扣率) / 100
const discountAmountRatio = new BigNumber(100).minus(discountRate).dividedBy(100);
// 计算折扣金额:调整后的商品订单金额 × 优惠比例
let discountPrice = adjustedGoodsOrderPrice
.times(discountAmountRatio)
.decimalPlaces(2, BigNumber.ROUND_FLOOR)
.toNumber();
// 应用最大折扣金额限制
if (maxDiscountAmount !== 0) {
discountPrice = discountPrice >= maxDiscountAmount ? maxDiscountAmount : discountPrice;
}
return {
discountPrice, // 折扣抵扣金额(即优惠的金额)
hasDiscountGoodsArr: [],
};
}
/**
* 商品券抵扣金额
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
*/
export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
const { useFoods, discountNum, useRule } = coupon;
//抵扣商品数组
let discountGoodsArr = [];
//抵扣全部商品
if (useFoods.length === 0) {
if (useRule == "price_asc") {
discountGoodsArr = canDikouGoodsArr.slice(discountNum * -1).reverse();
} else {
discountGoodsArr = canDikouGoodsArr.slice(0, discountNum);
}
} else {
//抵扣选中商品
const discountSelGoodsArr = canDikouGoodsArr.filter((v) =>
useFoods.find((food) => food.id == v.productId)
);
if (useRule == "price_asc") {
discountGoodsArr = discountSelGoodsArr.slice(discountNum * -1).reverse();
} else {
discountGoodsArr = discountSelGoodsArr.slice(0, discountNum);
}
}
const result = calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, shopInfo);
return result;
}
// 返回买一送一券抵扣详情
/**
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
*/
function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods = undefined;
//符合买一送一条件的商品
const canUseGoods = canDikouGoodsArr.filter((v) => v.num >= 2);
//抵扣全部商品
if (useFoods.length === 0) {
if (useRule == "price_asc") {
discountGoods = canUseGoods[canUseGoods.length - 1];
} else {
discountGoods = canUseGoods[0];
}
} else {
//符合抵扣条件的商品
const canUseGoods1 = canUseGoods.filter((v) => useFoods.find((food) => food.id == v.productId));
if (useRule == "price_asc") {
discountGoods = canUseGoods1[canUseGoods1.length - 1];
} else {
discountGoods = canUseGoods1[0];
}
}
let discountPrice = 0;
let hasDiscountGoodsArr = [];
console.log("returnCouponBuyOneGiveOneDiscount:discountGoods", discountGoods);
if (discountGoods) {
discountPrice = returnGoodsPrice(discountGoods, user, shopInfo);
hasDiscountGoodsArr = [discountGoods];
}
return {
discountPrice: discountPrice <= 0 ? 0 : discountPrice,
hasDiscountGoodsArr,
};
}
/**
* 返回第二件半价券抵扣详情
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
*/
function returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods = undefined;
//符合条件的商品
const canUseGoods = canDikouGoodsArr.filter((v) => v.num >= 2);
//抵扣全部商品
if (useFoods.length === 0) {
if (useRule == "price_asc") {
discountGoods = canUseGoods[canUseGoods.length - 1];
} else {
discountGoods = canUseGoods[0];
}
} else {
//符合抵扣条件的商品
const canUseGoods1 = canUseGoods.filter((v) => useFoods.find((food) => food.id == v.productId));
if (useRule == "price_asc") {
discountGoods = canUseGoods1[canUseGoods1.length - 1];
} else {
discountGoods = canUseGoods1[0];
}
}
let discountPrice = 0;
let hasDiscountGoodsArr = [];
if (discountGoods) {
discountPrice = returnGoodsPrice(discountGoods, user, shopInfo);
hasDiscountGoodsArr = [discountGoods];
}
//返回半价价格
return {
discountPrice: discountPrice <= 0 ? 0 : new BigNumber(discountPrice).dividedBy(2).toNumber(),
hasDiscountGoodsArr,
};
}
/**
* 返回可以抵扣优惠券的商品列表,过滤掉赠品临时商品,价格从高到低排序
* @param arr 商品列表
* @param user 用户信息
* @param shopInfo 店铺信息
*/
export function returnCanDikouGoods(arr, user, shopInfo) {
const result = arr
.filter((v) => {
return v.is_temporary != 1 && v.is_gift != 1;
})
.filter((v) => {
return v.num > 0;
})
.sort((a, b) => {
return returnGoodsPrice(b, user, shopInfo) - returnGoodsPrice(a, user, shopInfo);
});
return result;
}

View File

@ -0,0 +1,205 @@
import { getOrderById } from "@/api/order.js";
import { useUser } from "@/store/user.js";
import dayjs from "dayjs";
/**
* 生成范围随机数
* @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 = "";
}
// 去除数字前面多余的 0但保留 0. 这种情况
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;
}
/**
* 将手机号中间四位替换为*
* @param {*} phone
* @returns
*/
export function formatPhoneNumber(phone, isFormat = true) {
if (isFormat) {
return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
} else {
return phone;
}
}
/**
* 获取订单详情
* @param {*} orderId
* @returns
*/
export async function getOrderByIdAjax(orderId) {
try {
if (!orderId) return;
const res = await getOrderById({ orderId: orderId });
let arr = [];
for (let key in res.detailMap) {
arr.push(res.detailMap[key]);
}
arr = arr.flat();
arr.map((item) => {
if (item.productType == "package" && item.proGroupInfo) {
item.proGroupInfo = JSON.parse(item.proGroupInfo).flat();
}
});
res.cartList = arr;
return Promise.resolve(res);
} catch (error) {
console.log(error);
return Promise.reject();
}
}
/**
* 将订单小票打印的数据组合起来
* @param {*} orderDetail
*/
export function commOrderPrintData(orderInfo) {
const userStore = useUser();
let data = {
isBefore: orderInfo.isBefore || false,
shop_name: userStore.shopInfo.shopName,
loginAccount: userStore.userInfo.name,
carts: [],
amount: formatDecimal(+orderInfo.payAmount),
originAmount: formatDecimal(+orderInfo.originAmount),
discountAmount:
orderInfo.status == "unpaid"
? "0.00"
: formatDecimal(orderInfo.originAmount - orderInfo.orderAmount),
discount: orderInfo.discountRatio,
remark: orderInfo.remark,
orderInfo: orderInfo,
outNumber: orderInfo.tableCode,
createdAt: orderInfo.createTime,
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
};
orderInfo.cartList.map((item) => {
data.carts.push({
categoryId: item.categoryId,
name: item.productName,
number: item.num,
skuName: item.skuName,
salePrice: formatDecimal(+item.price),
totalAmount: formatDecimal(+item.payAmount),
proGroupInfo: item.proGroupInfo
? item.proGroupInfo.map((item) => item.goods).flat()
: "",
});
});
return data;
}
/**
* 校验手机号
* @param {*} phone
* @returns
*/
export function regPhone(phone) {
let reg = /^(?:(?:\+|00)86)?1\d{10}$/;
return reg.test(phone);
}

View File

@ -1,8 +1,76 @@
import axios from "axios"; import axios from "axios";
import { ElMessage } from "element-plus";
import useStorage from "@/utils/useStorage";
import router from "@/router";
const request = axios.create({ const service = axios.create({
baseURL: "http://localhost", baseURL:
timeout: 100000, import.meta.env.MODE == "development"
? "/api/"
: import.meta.env.VITE_API_URL,
// withCredentials: true, // 跨域请求时发送 cookies
timeout: 20000, // 请求超时
}); });
request.interceptors.request.use((config) => {}); // 请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
config.headers["platformType"] = "PC";
if (useStorage.get("token")) {
// 让每个请求携带 token
// ['X-Token'] 是自定义标题键
// 请根据实际情况修改
config.headers["token"] = useStorage.get("token");
if (useStorage.get("shopInfo") && useStorage.get("shopInfo").id) {
config.headers["shopId"] = useStorage.get("shopInfo").id;
}
// config.headers["loginName"] = useStorage.get("userInfo").loginName;
// 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 == 200) {
return response.data.data;
} else if (+response.data.code == 501) {
useStorage.del("token");
useStorage.del("userInfo");
useStorage.del("shopInfo");
useStorage.del("douyin");
ElMessage.error("登录已过期,请重新登录");
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()
}
}

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

@ -0,0 +1,378 @@
<template>
<div class="device_container" v-loading="requestLoading">
<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-input v-model="form.name" placeholder="请输入设备名称"></el-input>
</el-form-item>
<!-- <el-form-item label="打印机品牌">
<el-input v-model="form.contentType" placeholder="请输入打印机品牌"></el-input>
</el-form-item> -->
<el-form-item label="设备尺寸">
<el-select v-model="form.receiptSize">
<el-option label="58mm" value="58mm"></el-option>
<el-option label="80mm" value="80mm"></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.address">
<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-select v-model="form.printQty">
<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">
预结算单 <span style="margin-left: 6px;">#{{ printData.orderInfo.orderNum }}</span>
</div>
<div class="title t2" style="margin-bottom: 20px;">
桌号
</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.originAmount }}</span>
</div>
<div class="row between">
<span>折扣</span>
<span>-0.00</span>
</div>
<div class="line"></div>
<div class="row between" style="font-size: 24px;">
<span>实付</span>
<span>{{ printData.amount }}</span>
</div>
<div class="line"></div>
<div class="row" style="font-weight: bold;">备注{{ printData.remark }}</div>
<div class="row" style="font-size: 14px;">打印时间{{ printData.printTime }}</div>
</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>
</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 { useUser } from "@/store/user.js";
import { usePrint } from "@/store/print.js";
import { printerAdd, printerDetail } from "@/api/account.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 requestLoading = ref(false);
const form = ref({
id: "",
name: '', //
connectionType: 'USB', // USB
address: '', //
port: '', //
subType: 'cash', // labelcashkitchen
contentType: '', //
categoryIds: [], // Id
categoryList: [], //
sort: '',
receiptSize: '58mm', // 58mm 80mm
classifyPrint: 1, // 0- 1- 2-
printQty: '', // c1m1^2 = +[2] m1^1 = [1] c1^1[1] c2m1^32+1[3]
printMethod: 'all', // all- normal-one-queue-
printType: [], // JSON refund-退 handover- queue-
status: 1
});
const printDataLoading = ref(false);
const printData = reactive({
shop_name: '',
loginAccount: '',
isBefore: true,
carts: [
{
id: 1,
name: "【测试勿管】娃哈哈矿泉水",
skuName: "500ml",
salePrice: "1.0",
number: "10",
totalAmount: "10",
},
],
amount: "10.00",
originAmount: '10.00',
discountAmount: "0.00",
discount: 0,
remark: "给我多放点辣椒,谢谢老板",
orderInfo: {
masterId: "",
orderNo: "202404021023542223445",
orderNum: '12'
},
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.address) {
ElMessage.error("请选择打印设备");
return;
}
printDataLoading.value = true;
printData.shop_name = store.shopInfo.shopName
printData.loginAccount = store.userInfo.name
printData.deviceName = form.value.address;
printData.printTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
printStore.pushReceiptData(printData, false);
setTimeout(() => {
printDataLoading.value = false;
}, 2500);
}
//
async function submitHandle() {
try {
if (!form.value.name) {
ElMessage.error("请输入设备名称");
return;
}
if (!form.value.address) {
ElMessage.error("请选择打印设备");
return;
}
loading.value = true;
await printerAdd(form.value, form.value.id ? "put" : "post");
ElMessage.success(form.value.id ? "编辑成功" : "添加成功");
printStore.init();
router.back();
} catch (error) {
console.log(error);
}
loading.value = false;
}
//
async function tbPrintMachineDetailAjax(id) {
try {
requestLoading.value = true;
const res = await printerDetail({ id: id });
form.value = res;
} catch (error) {
console.log(error);
}
requestLoading.value = false;
}
onMounted(() => {
printData.shop_name = store.shopInfo.shopName
printData.loginAccount = store.userInfo.name
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;
flex-direction: column;
display: flex;
margin-left: 15px;
background-color: #fff;
border-radius: 10px;
padding: 0 15px;
.btn_wrap {
display: flex;
gap: var(--el-font-size-base);
padding: var(--el-font-size-base) 0;
.btn {
flex: 1;
}
}
.print_view {
flex: 1;
padding: 20px 0;
.title {
display: flex;
justify-content: center;
margin-bottom: 4px;
&.t1 {
font-size: 24px;
}
}
.row {
margin-top: 2px;
&.between {
display: flex;
justify-content: space-between;
}
}
.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,344 @@
<template>
<div class="device_container" v-loading="requestLoading">
<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-input v-model="form.name" placeholder="请输入设备名称"></el-input>
</el-form-item>
<el-form-item label="设备尺寸">
<el-select v-model="form.receiptSize">
<el-option label="58mm" value="58mm"></el-option>
<el-option label="80mm" value="80mm"></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.address">
<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="商品分类">
<div style="cursor: pointer" @click="classifyRef.show()">
<span style="color: #409eff" v-if="form.categoryList.length">
{{form.categoryList.map(item => item.name).join(',')}}
</span>
<span style="color: #e65d6e" v-else>
请选择分类
</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="printData.ticketLogo || logo" />
<!-- <span class="title">双屿Pisces</span> -->
</div>
<div class="number_wrap">
<!-- <div class="num" v-if="printData.outNumber">{{ printData.outNumber }}</div> -->
<div class="info">座位号{{ 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%" :loading="printLoading" @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.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 { printerAdd, printerDetail } from "@/api/account.js";
import { useUser } from "@/store/user.js";
import classify from "@/components/classify/index.vue";
import QRCode from 'qrcode'
import { usePrint } from "@/store/print.js";
import { useGoods } from '@/store/goods.js'
import logo from '@/assets/prinnt_label_logo.png';
const goodsStore = useGoods()
const printStore = usePrint();
const store = useUser();
const router = useRouter();
const route = useRoute();
const printLoading = ref(false);
const classifyRef = ref(null);
const printList = ref([]);
const feets = ref([0, 1, 2, 3, 4, 5, 8]);
const loading = ref(false);
const requestLoading = ref(false);
const form = ref({
id: "",
name: '', //
connectionType: 'USB', // USB
address: '', //
port: '', //
subType: 'label', // labelcashkitchen
contentType: '', //
categoryIds: [], // Id
categoryList: [], //
sort: '',
receiptSize: '58mm', // 58mm 80mm
classifyPrint: 1, // 0- 1- 2-
printQty: '', // c1m1^2 = +[2] m1^1 = [1] c1^1[1] c2m1^32+1[3]
printMethod: 'all', // all- normal-one-queue-
printType: [], // JSON refund-退 handover- queue-
status: 1
});
const canvasRef = ref(null)
const printData = ref({
deviceName: '',
outNumber: '123',
name: '【测试勿管】甜橙马黛茶',
skuName: '测试、加珍珠',
masterId: '#A9',
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
ticketLogo: store.shopInfo.ticketLogo,
})
//
function getPrintList() {
ipcRenderer.send("getPrintList");
ipcRenderer.on("printList", (event, arg) => {
printList.value = arg;
});
}
//
const printHandle = _.throttle(function () {
if (!form.value.address) {
ElMessage.error("请选择打印设备");
return;
}
printLoading.value = true;
printData.value.deviceName = form.value.address
ipcRenderer.send(
"printerTagSync",
JSON.stringify(printData.value)
);
setTimeout(() => {
printLoading.value = false;
}, 2500);
}, 1500, { leading: true, trailing: false })
//
async function submitHandle() {
try {
if (!form.value.name) {
ElMessage.error("请输入设备名称");
return;
}
if (!form.value.address) {
ElMessage.error("请选择打印设备");
return;
}
if (!form.value.categoryList.length) {
ElMessage.error("请选择商品分类");
return;
}
loading.value = true;
form.value.categoryIds = form.value.categoryList.map(item => item.id)
await printerAdd(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 {
requestLoading.value = true;
const res = await printerDetail({ id: route.query.id });
form.value = res;
let arr = []
goodsStore.originCategoryList.map(item => {
res.categoryList.map(val => {
if (item.id == val) {
arr.push({
id: item.id,
name: item.name
})
}
})
})
form.value.categoryList = arr
} catch (error) {
console.log(error);
}
requestLoading.value = false;
}
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 {
padding-bottom: 4px;
font-weight: bold;
}
}
.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
}

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

@ -0,0 +1,315 @@
<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.address }}</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 { printerList, printerAdd } from '@/api/account.js'
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import icons from "./icons";
import { usePrint } from "@/store/print.js";
const printStore = usePrint();
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 printerAdd(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 printerAdd({ id: deleteId.value }, 'delete');
dialogVisible.value = false;
ElMessage.success("删除成功");
tbPrintMachineGetAjax();
printStore.init();
} catch (error) {
console.log(error);
}
delLoading.value = false;
}
//
async function tbPrintMachineGetAjax() {
try {
const res = await printerList();
list.value = res.records;
} 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) {
console.log('groupOrdergroupScanHandle.error', error);
}
groupDetailLoading.value = false
}
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) {
console.log('submitHandle.error', error);
if (error.code == 4399) {
BindShopRef.value.show()
}
}
loading.value = false
}
//
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">
<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,287 @@
<template>
<div class="cart_item" :class="{ active: props.item.active, border: props.border }"
@click="selectCartItemHandle(index)">
<div class="name_wrap">
<span>{{ props.item.product_name }}</span>
<template v-if="props.item.is_gift">
<div class="price">
<span class="dis" v-if="props.item.is_temporary">
{{ formatDecimal(+props.item.discount_sale_amount, 2, true) }}
</span>
<span class="dis" v-else>
{{ formatDecimal(goodsStore.showVipPrice ? +props.item.memberPrice || +props.item.lowPrice :
+props.item.lowPrice, 2,
true) }}
</span>
<span v-if="props.item.discount_sale_amount">
0.00
</span>
</div>
</template>
<template v-else>
<div class="price" v-if="props.item.is_temporary">
<span>
{{ formatDecimal(+props.item.discount_sale_amount, 2, true) }}
</span>
</div>
<div class="price" v-else>
<template v-if="+props.item.discount_sale_amount">
<span class="dis">
{{ formatDecimal(goodsStore.showVipPrice ? +props.item.memberPrice || +props.item.lowPrice
: +props.item.lowPrice,
2, true) }}
</span>
<span>
{{ formatDecimal(+props.item.discount_sale_amount, 2, true) }}
</span>
</template>
<div class="flex" v-else>
<template v-if="goodsStore.showVipPrice">
<span class="dis">
{{ formatDecimal(+props.item.lowPrice, 2, true) }}
</span>
</template>
<span>
{{ formatDecimal(goodsStore.showVipPrice ? +props.item.memberPrice || +props.item.lowPrice
:
+props.item.lowPrice,
2,
true) }}
</span>
</div>
</div>
</template>
</div>
<div class="remark_wrap" @click="showRemark(props.item)">
备注<span class="t">{{ props.item.remark }}</span><el-icon class="icon" v-if="props.type == 'cart'">
<EditPen />
</el-icon>
</div>
<div class="sku_list" v-if="props.item.sku_name">
<div class="tag" v-for="item in item.sku_name.split(',')">
{{ item }}
</div>
</div>
<div class="grooup_wrap" v-if="props.item.goods_type == 'package' && props.item.group_text">
{{ props.item.group_type == 0 ? '固定套餐:' : '自选套餐:' }}
<span>{{ props.item.group_text }}</span>
</div>
<div class="num">
<div class="left">
<div class="icon_item zen" v-if="props.item.is_gift">
<span class="t"></span>
</div>
<div class="icon_item bao" v-if="+props.item.pack_number">
<span class="t">打包({{ +props.item.pack_number }})</span>
</div>
<div class="icon_item tui" v-if="props.item.returnNum">
<span class="t">退({{ props.item.returnNum }})</span>
</div>
<div class="icon_item lin" v-if="props.item.is_temporary == 1">
<span class="t"></span>
</div>
<div class="icon_item zhe" v-if="props.item.discount_sale_amount > 0 && !props.item.is_temporary">
<span class="t"></span>
</div>
<div class="icon_item chu" v-if="props.item.is_print == 0">
<span class="t">免厨打印</span>
</div>
</div>
<el-text class="t">x{{ formatDecimal(+props.item.number, 2, true) }}</el-text>
</div>
</div>
<!-- 备注 -->
<remarkModal ref="remarkRef" @success="remarkSuccess" />
</template>
<script setup>
import { ref } from 'vue'
import remarkModal from "@/components/remarkModal.vue";
import { useGoods } from '@/store/goods.js'
import { formatDecimal } from '@/utils/index.js'
const goodsStore = useGoods()
const props = defineProps({
type: {
type: String,
default: 'cart'
},
item: {
type: Object,
default: {}
},
index: {
type: [String, Number],
default: 0
},
i: {
type: [String, Number],
default: 0
},
border: {
type: Boolean,
default: true
}
})
//
function selectCartItemHandle() {
if (props.type == 'cart') {
goodsStore.selectCartItemHandle(props.index)
} else if (props.type == 'order') {
goodsStore.selectOrderItem(props.index, props.i)
}
}
//
const remarkRef = ref(null)
function showRemark() {
if (props.type == 'cart') {
remarkRef.value.show()
}
}
//
function remarkSuccess(e) {
goodsStore.operateCart({ ...props.item, remark: e }, 'edit')
}
</script>
<style scoped lang="scss">
.remark_wrap {
font-size: 14px;
color: #999;
padding-top: 10px;
.icon {
position: relative;
top: 2px;
margin-left: 4px;
}
}
.cart_item {
padding: var(--el-font-size-base);
&.active {
background-color: var(--primary-color-hover);
}
&.border {
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;
}
.flex {
display: flex;
align-items: center;
}
.vip {
color: var(--el-color-danger);
margin-left: 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);
}
}
}
</style>

View File

@ -0,0 +1,709 @@
<template>
<div class="operation_wrap">
<template v-if="goodsStore.cartType == 'cart'">
<div class="item"
:class="{ disabled: goodsStore.cartList.length && (!goodsStore.cartList[goodsStore.cartActiveIndex].id || (goodsStore.cartList[goodsStore.cartActiveIndex].goods_type == 'package' && goodsStore.cartList[goodsStore.cartActiveIndex].group_type == 1)) }"
@click="numberChange('sub')">
<el-icon class="icon">
<SemiSelect />
</el-icon>
</div>
<div class="item number"
:class="{ disabled: goodsStore.cartList.length && (goodsStore.cartList[goodsStore.cartActiveIndex].goods_type == 'package' && goodsStore.cartList[goodsStore.cartActiveIndex].group_type == 1) }"
@click="showEditNumber">
<el-text class="num">
{{ formatDecimal(goodsStore.cartList.length ?
+goodsStore.cartList[goodsStore.cartActiveIndex].number :
1, 2, true) }}
</el-text>
</div>
<div class="item"
:class="{ disabled: goodsStore.cartList.length && (goodsStore.cartList[goodsStore.cartActiveIndex].goods_type == 'package' && goodsStore.cartList[goodsStore.cartActiveIndex].group_type == 1) || (goodsStore.cartList.length && goodsStore.cartList[goodsStore.cartActiveIndex].number >= goodsStore.cartList[goodsStore.cartActiveIndex].stockNumber && goodsStore.cartList[goodsStore.cartActiveIndex].isStock) }"
@click="numberChange('add')">
<el-icon class="icon add">
<CloseBold />
</el-icon>
</div>
<div class="item"
:class="{ disabled: (goodsStore.cartList.length && goodsStore.cartList[goodsStore.cartActiveIndex].id && goodsStore.cartList[goodsStore.cartActiveIndex].goods_type != 'sku' && goodsStore.cartList[goodsStore.cartActiveIndex].group_type != 1) }"
@click="showSkuModal">
<el-icon class="icon">
<Filter />
</el-icon>
<el-text class="t">规格</el-text>
</div>
<div class="item"
:class="{ disabled: goodsStore.cartList.length && goodsStore.cartList[goodsStore.cartActiveIndex].is_temporary || goodsStore.cartList.length && goodsStore.cartList[goodsStore.cartActiveIndex].is_gift }"
@click="showDiscountModalHandle">
<el-icon class="icon">
<PriceTag />
</el-icon>
<el-text class="t">改价</el-text>
</div>
<div class="item"
:class="{ disabled: goodsStore.cartList.length && goodsStore.cartList[goodsStore.cartActiveIndex].is_gift }"
@click="giftPackHandle('is_gift')">
<el-icon class="icon">
<ShoppingBag />
</el-icon>
<el-text class="t">赠送</el-text>
</div>
<div class="item" :class="{ disabled: goodsStore.allSelected }" @click="packHandle">
<el-icon class="icon">
<Box />
</el-icon>
<el-text class="t">打包</el-text>
</div>
<div class="item"
:class="{ disabled: goodsStore.cartList.length && goodsStore.cartList[goodsStore.cartActiveIndex].is_print == 0 }"
@click="giftPackHandle('is_print')">
<el-icon class="icon">
<DishDot />
</el-icon>
<el-text class="t">免厨</el-text>
</div>
<div class="item" @click="deleteHandle">
<el-icon class="icon">
<Delete />
</el-icon>
<el-text class="t">删除</el-text>
</div>
<div class="item" @click="pendingOrderHandle">
<el-icon class="icon">
<Sell />
</el-icon>
<el-text class="t">挂单</el-text>
</div>
<div class="item" @click="tableMergingRef.show()">
<el-icon class="icon">
<EditPen />
</el-icon>
<el-text class="t">转桌</el-text>
</div>
<div class="item" @click="clearCart">
<el-icon class="icon">
<RefreshRight />
</el-icon>
<el-text class="t">清空</el-text>
</div>
</template>
<template v-if="goodsStore.cartType == 'order'">
<div class="item disabled">
<el-icon class="icon">
<SemiSelect />
</el-icon>
</div>
<div class="item number disabled">
<el-text class="num">
{{ formatDecimal(goodsStore.cartList.length ?
+goodsStore.cartList[goodsStore.cartActiveIndex].number :
1, 2, true) }}
</el-text>
</div>
<div class="item disabled">
<el-icon class="icon add">
<CloseBold />
</el-icon>
</div>
<div class="item disabled">
<el-icon class="icon">
<Filter />
</el-icon>
<el-text class="t">规格</el-text>
</div>
<div class="item disabled">
<el-icon class="icon">
<PriceTag />
</el-icon>
<el-text class="t">改价</el-text>
</div>
<div class="item disabled">
<el-icon class="icon">
<ShoppingBag />
</el-icon>
<el-text class="t">赠送</el-text>
</div>
<div class="item disabled">
<el-icon class="icon">
<Box />
</el-icon>
<el-text class="t">打包</el-text>
</div>
<div class="item disabled">
<el-icon class="icon">
<DishDot />
</el-icon>
<el-text class="t">免厨</el-text>
</div>
<div class="item"
:class="{ disabled: goodsStore.cartOrderItem.returnNum >= goodsStore.cartOrderItem.number }"
@click="returnOrderItemHandle">
<el-icon class="icon">
<Delete />
</el-icon>
<el-text class="t">退菜</el-text>
</div>
<div class="item" @click="pendingOrderHandle">
<el-icon class="icon">
<Sell />
</el-icon>
<el-text class="t">挂单</el-text>
</div>
<div class="item" @click="tableMergingRef.show()">
<el-icon class="icon">
<EditPen />
</el-icon>
<el-text class="t">转桌</el-text>
</div>
<div class="item disabled">
<el-icon class="icon">
<RefreshRight />
</el-icon>
<el-text class="t">清空</el-text>
</div>
</template>
</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="discount_sale_amount">
<el-input v-model="discountForm.discount_sale_amount" placeholder="请输入改价金额" @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>
<!-- 修改打包数量 -->
<el-dialog v-model="showPackModal" title="打包数量" @open="packModalOpen" width="350">
<div class="dialog">
<div class="el-popover__title content">
<el-form ref="packNumerFormRef" :model="packNumberForm" :rules="packNumberFormRules" label-width="100px"
label-position="left">
<el-form-item :label="`数量(${packItem.unitName}`" prop="number">
<!-- <el-input v-model="packNumberForm.number"
:placeholder="`最多输入${+packItem.number}${packItem.unitName}`" @input="packNumberInput">
<template #append></template>
</el-input> -->
<el-input-number v-model="packNumberForm.number" :min="1"
:max="+packItem.number"></el-input-number>
</el-form-item>
</el-form>
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showPackModal = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" @click="packFormSubmit">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 合并/转桌 -->
<tableMerging ref="tableMergingRef" @success="" />
<!-- 退菜数量 -->
<el-dialog
:title="`退菜:${goodsStore.cartOrderItem.product_name} x ${goodsStore.cartOrderItem.number - goodsStore.cartOrderItem.returnNum}`"
v-model="showReturnForm" width="350" top="20%">
<el-form :model="returnForm" label-width="0" label-position="left">
<el-form-item prop="num">
<el-input-number v-model="returnForm.num" :min="1"
:max="goodsStore.cartOrderItem.number - goodsStore.cartOrderItem.returnNum" style="width: 100%;">
</el-input-number>
</el-form-item>
</el-form>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showReturnForm = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="returnFormLoading"
@click="returnFormSubmit">确认</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import takeFoodCode from '@/components/takeFoodCode.vue'
import TableMerging from './tableMerging.vue'
import skuModal from '@/components/skuModal.vue'
import { useGoods } from '@/store/goods.js'
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
import { refundOrder } from '@/api/order.js'
import { useSocket } from '@/store/socket.js'
const goodsStore = useGoods()
const socket = useSocket()
const tableMergingRef = ref(null)
const props = defineProps({
item: {
type: Object,
default: {}
}
})
const emit = defineEmits(['confirm', 'delete', 'pending', 'clearCart', 'merging', 'showPackage'])
const takeFoodCodeRef = ref(null)
const skuModalRef = ref([])
const showReturnForm = ref(false)
const returnFormLoading = ref(false)
const returnForm = ref({
num: 1
})
//
function pendingOrderHandle() {
let cart = goodsStore.cartList;
let order = goodsStore.orderList;
if (cart.length || order.length) {
goodsStore.pendingCart()
goodsStore.successClearCart();
socket.cartInit();
ElMessage.success('挂单成功')
}
}
// 退
async function returnOrderItemHandle() {
if (goodsStore.cartOrderItem.returnNum >= goodsStore.cartOrderItem.number) return
if (goodsStore.cartOrderItem.number == 1) {
returnOrderItemAjax()
} else {
showReturnForm.value = true
}
}
// 退
async function returnFormSubmit() {
try {
returnFormLoading.value = true
await returnOrderItemAjax(returnForm.value.num)
showReturnForm.value = false
ElMessage.success('退菜成功')
} catch (error) {
console.log('退菜失败了');
}
returnFormLoading.value = false
}
// 退
async function returnOrderItemAjax(num = 1) {
try {
let data = {
orderId: goodsStore.orderListInfo.id,
refundAmount: 0,
modify: 0,
cash: false,
refundReason: '',
refundDetails: [
{
id: goodsStore.cartOrderItem.id,
returnAmount: goodsStore.cartOrderItem.lowPrice,
num: num
}
]
}
await refundOrder(data)
goodsStore.cartOrderItem.returnNum += num
goodsStore.calcCartInfo()
// await goodsStore.historyOrderAjax('', data.orderId)
} catch (error) {
console.log(error);
}
}
//
function packHandle() {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
if (item && item.id && !goodsStore.allSelected) {
if (!item.pack_number || item.pack_number <= 0) {
if (item.number > 1 && item.goods_type != 'weight') {
// 1
showPackModal.value = true
} else {
// 1
goodsStore.operateCart({ ...item, pack_number: 1 }, 'edit')
}
} else {
//
goodsStore.operateCart({ ...item, pack_number: 0 }, 'edit')
}
}
}
//
function giftPackHandle(key) {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
if (item && item.id) {
if (key == 'is_gift' && goodsStore.cartList[goodsStore.cartActiveIndex] == 0) {
goodsStore.cartList[goodsStore.cartActiveIndex].discount_sale_amount = 0
}
if (goodsStore.cartList[goodsStore.cartActiveIndex][key] == 0) {
goodsStore.cartList[goodsStore.cartActiveIndex][key] = 1
} else {
goodsStore.cartList[goodsStore.cartActiveIndex][key] = 0
}
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex] }, 'edit')
goodsStore.calcCartInfo()
}
}
//
function showEditNumber() {
let item = goodsStore.cartList.length ? goodsStore.cartList[goodsStore.cartActiveIndex] : ''
if (!item || (item.goods_type == 'package' && item.group_type == 1)) return
takeFoodCodeRef.value.show()
}
//
function numberChange(t) {
let item = goodsStore.cartList.length ? goodsStore.cartList[goodsStore.cartActiveIndex] : ''
if (!item || (item.goods_type == 'package' && item.group_type == 1)) return
let number = +goodsStore.cartList[goodsStore.cartActiveIndex].number
switch (t) {
case 'sub':
if (item.number - 1 < item.suitNum) {
goodsStore.deleteCartItem()
} else {
if (goodsStore.allSelected) {
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex], pack_number: number - 1, number: number - 1 }, 'edit')
} else {
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex], number: number - 1 }, 'edit')
}
}
break;
case 'add':
if (goodsStore.allSelected) {
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex], pack_number: number + 1, number: number + 1 }, 'edit')
} else {
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex], number: +number + 1 }, 'edit')
}
break;
default:
break;
}
console.log('numberChange===', goodsStore.cartList[goodsStore.cartActiveIndex].number);
}
//
function updateNumber(num) {
if (goodsStore.allSelected) {
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex], pack_number: num, number: num }, 'edit')
} else {
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex], number: num }, 'edit')
}
}
//
function showSkuModal() {
let item = goodsStore.cartList.length ? goodsStore.cartList[goodsStore.cartActiveIndex] : ''
if (!item || (item.goods_type == 'package' && item.group_type == 0)) return
switch (item.goods_type) {
case 'sku':
skuModalRef.value.show({ ...goodsStore.cartList[goodsStore.cartActiveIndex] }, 'cart')
break;
case 'package':
emit('showPackage', item)
break;
default:
break;
}
}
//
function skuConfirm(e) {
if (goodsStore.cartList[goodsStore.cartActiveIndex].sku_id != e.id) {
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex], sku_id: e.id, sku_name: e.specInfo }, 'edit')
}
}
/**单品改价 start */
const showDiscountModal = ref(false)
const resetDiscountForm = ref({})
const discountFormRef = ref(null)
const discountFormLoading = ref(false)
const discountForm = ref({
discount_sale_amount: '',
note: ''
})
function validateAmount(rule, value, callback) {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
let lowPrice = 0
if (goodsStore.showVipPrice) {
lowPrice = +item.memberPrice
} else {
lowPrice = +item.discount_sale_amount || +item.lowPrice
}
if (value == '') {
callback(new Error('请输入折扣价格'))
} else if (value <= 0 || value >= lowPrice) {
callback(new Error(`输入大于0小于${lowPrice}的数字`))
} else {
callback()
}
}
const discountFormRules = ref({
discount_sale_amount: [
{
required: true,
validator: validateAmount,
trigger: 'blur',
}
]
})
const noteList = ref([
'顾客投诉质量...',
'友情打折',
'临时活动',
])
//
function showDiscountModalHandle() {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
if ((item && item.id) && (!item.is_temporary && !item.is_gift)) {
//
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
goodsStore.operateCart({
...goodsStore.cartList[goodsStore.cartActiveIndex],
discount_sale_amount: discountForm.value.discount_sale_amount,
discount_sale_note: discountForm.value.note
}, 'edit')
goodsStore.cartList[goodsStore.cartActiveIndex].discount_sale_amount = discountForm.value.discount_sale_amount
goodsStore.cartList[goodsStore.cartActiveIndex].discount_sale_note = discountForm.value.note
showDiscountModal.value = false
goodsStore.calcCartInfo()
}
} catch (error) {
console.log(error);
}
discountFormLoading.value = false
})
}
/**单品打折 end */
//
function deleteHandle() {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
if (item && item.id) {
goodsStore.deleteCartItem()
}
}
//
function clearCart() {
goodsStore.clearCart()
}
/** 修改打包数量 start */
const showPackModal = ref(false)
const packNumerFormRef = ref(null)
const packItem = ref('')
const packNumberForm = ref({
number: 1
})
const packNumberFormRules = {
number: [
{
required: true,
validator: validatePackNumber,
trigger: 'blur',
}
]
}
//
function validatePackNumber(rule, value, callback) {
if (!packNumberForm.value.number) {
callback(new Error('请输入打包数量'))
} else if (packNumberForm.value.number < 1) {
callback(new Error('输入有误'))
} else if (packNumberForm.value.number > packItem.value.number) {
callback(new Error(`最多输入${+packItem.value.number}${packItem.value.unitName}`))
} else {
callback()
}
}
// modal
function packModalOpen() {
packNumberForm.value.number = 1
packItem.value = goodsStore.cartList[goodsStore.cartActiveIndex]
}
//
function packFormSubmit() {
packNumerFormRef.value.validate(valid => {
if (valid) {
showPackModal.value = false
goodsStore.operateCart({ ...packItem.value, pack_number: packNumberForm.value.number }, 'edit')
}
})
}
/** 修改打包数量 end */
</script>
<style scoped lang="scss">
.operation_wrap {
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
.item {
width: 70px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
background-color: #efefef;
border-radius: 6px;
&:active {
background-color: #d3d3d3;
}
&.disabled {
.t {
color: #999;
}
.icon {
color: #999;
}
.num {
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;
}
}
}
.footer_wrap {
display: flex;
gap: 20px;
.btn {
flex: 1;
}
}
.dialog {
.content {
padding-bottom: 20px;
}
}
.remark_list {
display: flex;
gap: 10px;
margin-top: 10px;
.item {
padding: 0 10px;
border: 1px solid #ddd;
color: #999;
border-radius: 4px;
}
}
</style>

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