Compare commits

..

404 Commits

Author SHA1 Message Date
黄伟杰 8d92aa4f59 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 hour ago
黄伟杰 49f810c365 style:模具组-维护-点检/保养-项目表单字段映射 2 hours ago
黄伟杰 10e1b25761 feat:物料档案-详情模块 2 hours ago
zhongwenkai 5dce2ad2f3 feat: 模具管理-上下模弹窗目标产线改为自动填充 4 hours ago
黄伟杰 6b0ce95093 style:模具组-维护-更换压网-子模具多选 4 hours ago
黄伟杰 d623f79b6a style:产品物料-选择供应商/包装方案-是否默认逻辑优化 9 hours ago
黄伟杰 8772c1303f fix:模具管理-点检记录-保存后没刷新问题修复 9 hours ago
liutao f1982f2555 文件上传的大小及格式 11 hours ago
liutao 13019bca0a Merge remote-tracking branch 'origin/main' 11 hours ago
黄伟杰 2806f9ce44 feat:模具组-维护-点检、保养 1 day ago
黄伟杰 6503b17814 feat:模具组-维护-点检/保养添加检验结果审批流程 1 day ago
黄伟杰 4f16b2a847 feat:产品物料信息-新增/编辑添加接口入参 1 day ago
黄伟杰 84dc36c5d7 feat:新增仓储管理-包装方案模块 1 day ago
黄伟杰 f7ee7787d2 style:模具管理-点检记录-字段名调整 1 day ago
黄伟杰 152838e14b fix:模具出入库bug修复 2 days ago
liutao e3001c67ba Merge remote-tracking branch 'origin/main' 2 days ago
黄伟杰 a049c54d7d style:模具组-搜索框入参字段调整 2 days ago
黄伟杰 48bceb74d2 style:上下模-操作人/操作时间必填 2 days ago
黄伟杰 156e532b14 fix:模具组-修复第一次进入上模/下模页面没调用接口bug 2 days ago
黄伟杰 d9a1b07f07 feat:产品物料信息-添加树状结构 2 days ago
黄伟杰 73dd73a1f4 style:模具组-返回按钮样式调整 2 days ago
黄伟杰 6a47258947 style:模具入库/模具出库-列表字段名调整 2 days ago
黄伟杰 6be0168ff5 style:修改上下模-产线字段 2 days ago
黄伟杰 e12004a43a feat:模具出库/模具入库-详情添加模具组/子模具列表 2 days ago
liutao a7a57f0c73 设备台账优化 2 days ago
liutao f62a08eaae Merge remote-tracking branch 'origin/main' 4 days ago
liutao 2cdf6e377c 修改关键件bug 4 days ago
黄伟杰 4330bbb9ce feat:模具组-产品型号多选 5 days ago
黄伟杰 9e2bcf038d fix:修复中英文报错 5 days ago
黄伟杰 0164129b95 feat:模具组-维护-点检/保养 5 days ago
黄伟杰 4ee05ff904 feat:模具组-子模具-添加压网记录 5 days ago
黄伟杰 601e22ef86 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 5 days ago
黄伟杰 c70bae738e feat:模具组-维护-更换压网模块 5 days ago
liutao 7a1c92ce86 Merge remote-tracking branch 'origin/main' 5 days ago
liutao b8857bb126 设备台账优化 5 days ago
黄伟杰 7e606054b0 feat:模具组-维护模块 5 days ago
黄伟杰 8769d1e5af style:模具组-详情-安装记录修改入参 5 days ago
黄伟杰 bc21277e3c style:模具组-新增/编辑-产品型号添加产品过滤 5 days ago
黄伟杰 5501c7983e style:模具组-详情-维修记录更换接口 5 days ago
黄伟杰 2e6c78fb11 feat:模具组-详情-安装记录添加操作类型字段 5 days ago
黄伟杰 49e47512d9 style:模具组-二维码/操作栏样式调整 5 days ago
黄伟杰 e80a187986 feat:模具组-上下模模块组件抽离 6 days ago
黄伟杰 4c7abf1c50 style:模具组-详情-二维码样式调整 6 days ago
黄伟杰 eb64e35f92 feat:模具出库/模具入库-新增/编辑-清单重构成模具组/子模具 6 days ago
黄伟杰 62ca6a1b45 fix:模具管理-点检模板新增报错修复 6 days ago
黄伟杰 1f73696db9 feat:模具组-添加子模数列 6 days ago
黄伟杰 ea8bd96abb style:上下模新增弹框逻辑调整 6 days ago
黄伟杰 b5caf770ee perf:模具组-详情交互优化 6 days ago
黄伟杰 13d77d76eb style:模具组模块适配中英文 6 days ago
黄伟杰 89968406a5 fix:修复构建报错 1 week ago
黄伟杰 87392bbc15 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 week ago
黄伟杰 3c257f3e8a feat:添加模具组-上下模模块 1 week ago
黄伟杰 b8d4cfc7e0 feat:模具组详情页 1 week ago
黄伟杰 e6617ceed6 style:上下模-模具列表接口更换 1 week ago
黄伟杰 e1b7550695 style:模具管理-维修单/点检记录-模具列表更换 1 week ago
黄伟杰 25954461e3 feat:模具组页面重构 1 week ago
黄伟杰 e2e2bbe383 style:产品物料分类-展开/折叠按钮隐藏 1 week ago
黄伟杰 04150a392f feat:模具管理-维修单模块重构 1 week ago
liutao 45f9df08f0 Merge remote-tracking branch 'origin/main' 1 week ago
liutao 69b8379249 解决冲突 1 week ago
黄伟杰 4c98bb463a Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 week ago
黄伟杰 057df9cdb9 style:产品BOM模块-产品下拉框添加产品筛选 1 week ago
黄伟杰 f887843d41 feat:产品物料信息-添加产品/物料类型分类 1 week ago
liutao 3349593e4e Merge remote-tracking branch 'origin/main' 1 week ago
liutao 1f0a8387b1 解决冲突 1 week ago
zhongwenkai 19ff5a4775 style: 修改系统管理中菜单管理的新增/编辑弹框样式 1 week ago
黄伟杰 e8d0130a17 feat:产品物料分类-添加产品/物料类型 1 week ago
zhongwenkai 951a851d1a style: 修改模具管理中模具管理的新增/编辑弹框样式 1 week ago
黄伟杰 772cbdbb32 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 week ago
黄伟杰 114b499dd0 style:模具管理-编辑接口去除templateJson入参 1 week ago
zhongwenkai 57757afe60 style: 仅修改设备管理中设备台账的新增弹框样式 1 week ago
zhongwenkai f82a2485a8 style: 修改基础设施中用户反馈的新增/编辑弹框样式 1 week ago
liutao d2f713d260 设备台账编辑页面调整及角色菜单权限样式调整 2 weeks ago
liutao 35cac74912 Merge remote-tracking branch 'origin/main' 2 weeks ago
zhongwenkai 0158dd2be6 style: 修改模具管理中模具入库的新增/编辑弹框样式 2 weeks ago
zhongwenkai 3386ed0b8c style: 修改模具管理中模具出库的新增/编辑弹框样式 2 weeks ago
liutao 0829d8551d Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/views/mes/criticalComponent/CriticalComponentForm.vue
#	src/views/mes/criticalComponent/index.vue
2 weeks ago
zhongwenkai 4902ca4958 style: 修改仓储管理中出库单据的新增/编辑弹框样式 2 weeks ago
zhongwenkai 3671db55fc style: 修改备件管理中备件出库的新增/编辑弹框样式 2 weeks ago
zhongwenkai e7abc6cfc7 style: 修改备件管理中备件入库的新增/编辑弹框样式 2 weeks ago
liutao 601f852b28 界面优化 2 weeks ago
liutao e35779413c update 2 weeks ago
liutao 03327f6581 update 2 weeks ago
zhongwenkai 97d450e8aa style: 修改设备管理中设备关键件的新增/编辑弹框样式 2 weeks ago
黄伟杰 c70a196ebb Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 weeks ago
黄伟杰 9f44955c5f style:设备管理-维修单添加故障等级 2 weeks ago
zhongwenkai e7ff7f128f style: 修改仓储管理中库位信息的新增/编辑弹框样式 2 weeks ago
黄伟杰 cb9387ec5f Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 weeks ago
黄伟杰 7a435e1d2f feat:设备台账-去除型号、生产日期字段,新增SN、品牌、出厂日期字段 2 weeks ago
zhongwenkai 4f6bdec21d style: 修改仓储管理中入库单据的新增/编辑弹框样式 2 weeks ago
zhongwenkai 157db7551d Merge remote-tracking branch 'origin/main' 2 weeks ago
zhongwenkai 6c2e5bc96a style: 修改工厂建模中编码规则的新增/编辑弹框样式 2 weeks ago
黄伟杰 d448a29dac Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 weeks ago
黄伟杰 ca007733bf feat:角色管理添加app端 2 weeks ago
zhongwenkai 39ca00a09c style: 修改工厂建模中产品BOM的新增/编辑弹框样式 2 weeks ago
zhongwenkai 56a959bdd1 style:修改产品物流信息的维修单-新增、编辑、维修弹框样式 2 weeks ago
liutao 5c81e130b0 update 2 weeks ago
黄伟杰 e1886d0ebb Merge remote-tracking branch 'remotes/origin/test' 2 weeks ago
zhongwenkai 732ef1de33 style:修改模具管理的维修单-新增、编辑、维修弹框样式 2 weeks ago
黄伟杰 e4439acc7a feat:设备管理-维修单字段调整 2 weeks ago
黄伟杰 f2c5dac7be feat:工厂建模-模板配置管理调整 2 weeks ago
liutao 2534340dda update 2 weeks ago
liutao 5325880ecb 优化 2 weeks ago
黄伟杰 bae3219146 style:打印/报表-模板管理添加纸张尺寸保存 2 weeks ago
黄伟杰 bc75b20ab8 style:点检任务-添加设备筛选框 2 weeks ago
黄伟杰 9a4b39e915 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 weeks ago
黄伟杰 4c90ad974d style:维修单-弹框字段调整 2 weeks ago
黄伟杰 55d978bef1 style:修改维修单-新增、编辑、维修弹框样式 2 weeks ago
黄伟杰 f180d30cbb feat:维修单模块修改 2 weeks ago
liutao def762ae78 设备台账编辑页面路由跳转 2 weeks ago
liutao 74088e67d5 关键件取消数量 2 weeks ago
liutao 0c7e1a7bce 关键件取消数量 2 weeks ago
liutao db458ccc5d 设备台账详情增加图片 2 weeks ago
黄伟杰 28c7322d71 style:设备管理-维修项目-是否启用字段回显 2 weeks ago
黄伟杰 7707a320c3 style:设备台账-详情调整样式 2 weeks ago
黄伟杰 d618de40dc feat:设备台账-详情-添加资料tabs 2 weeks ago
黄伟杰 c7ce945f64 style:设备台账-编辑-备件/关键件回显调整 2 weeks ago
黄伟杰 0b0ab3df48 feat:模板管理-报表配置模板弹框添加图标组件 2 weeks ago
黄伟杰 dfd2d7d829 feat:打印模板管理-配置模板弹框添加图标组件 2 weeks ago
黄伟杰 27d1afaa86 feat:设备台账-详情二维码添加json回显 2 weeks ago
黄伟杰 2dc320f9ff Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 weeks ago
黄伟杰 a7917fd8e2 style:接口请求超时时间改为300秒 2 weeks ago
黄伟杰 d066d024c6 style:设备台账-生产日期/入厂日期不必填、资料上传添加loading 2 weeks ago
HuangHuiKang a45bce5027 fix:修改nginx超时时间 2 weeks ago
HuangHuiKang 576a9fa5e6 Merge remote-tracking branch 'origin/main' into main 2 weeks ago
黄伟杰 47432c5c63 style:设备台账-产线分类-树状结果添加“全部” 2 weeks ago
黄伟杰 0c54297178 style:菜单管理-终端类型添加判断,当菜单类型为目录才展示 2 weeks ago
黄伟杰 02a8a7225e Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 weeks ago
黄伟杰 7e5252d990 feat:菜单管理-app端菜单添加终端类型 2 weeks ago
黄伟杰 8333b589d8 feat:设备台账-左侧树状结构添加产线分类 2 weeks ago
必硕 f31f527b0f 更新 '.env.dev'
修改dev配置,适配线上环境
3 weeks ago
liutao e894c06316 配置ngix文件 3 weeks ago
liutao 3f6d40cf13 Merge remote-tracking branch 'origin/main' 3 weeks ago
黄伟杰 1459ce3460 style:模板管理-编辑-模板类型/模板分类回显 3 weeks ago
liutao b37c5220d0 Merge remote-tracking branch 'origin/main' 3 weeks ago
黄伟杰 3a6de6b49b style:模板管理-报表元素样式优化 3 weeks ago
liutao adc92cf08f 图片写死在前端 3 weeks ago
liutao d3d668c7f2 设备采集汇总 3 weeks ago
黄伟杰 72ce5a82c3 feat:模板配置-报表模板添加报表元素 3 weeks ago
黄伟杰 86c68adf72 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 3 weeks ago
黄伟杰 9d700a6ece feat:模板管理-报表模板添加工具栏 3 weeks ago
liutao 63e565bb51 页面优化 3 weeks ago
liutao 06e27720c8 Merge remote-tracking branch 'origin/main' 3 weeks ago
liutao 0aaa3b35a0 分类增加导入功能 3 weeks ago
黄伟杰 0d75fe9994 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 3 weeks ago
黄伟杰 f66c4afd29 feat:菜单管理-添加web端/app端tab栏 3 weeks ago
liutao db1e813950 时间限制 3 weeks ago
liutao 9b82f41b37 能耗概览的中英文切换 4 weeks ago
liutao 3745ef5bbc 能源概览 4 weeks ago
黄伟杰 13133c28be feat:添加打印模板页面-二维码样例 4 weeks ago
黄伟杰 2d20ec5344 feat:备件信息-二维码模板回显 4 weeks ago
黄伟杰 ae6ef128ee feat:模具管理-二维码模板回显 4 weeks ago
黄伟杰 4f6ee86798 feat:设备关键件-二维码模板回显 4 weeks ago
黄伟杰 5ff4d7e41a feat:设备台账-二维码模板回显 4 weeks ago
黄伟杰 288d10f3c7 feat:产品物料信息-二维码模板回显 4 weeks ago
黄伟杰 f3be54dd9f feat:添加打印模板管理模块 4 weeks ago
黄伟杰 ad79342616 style:甘特图-条块拖拽超出时间轴提示 1 month ago
黄伟杰 e472d4cb86 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 month ago
黄伟杰 4648bdca93 feat:报表管理-添加“展开/收起”按钮 1 month ago
liutao 1418015844 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/api/iot/deviceOperationOverview/index.ts
1 month ago
liutao a28acd5991 补充文件 1 month ago
黄伟杰 68c960317d feat:质量管理-添加“展开/收起”按钮 1 month ago
黄伟杰 88797b7760 style:产能报表-添加时间筛选入参 1 month ago
黄伟杰 0d7ae49a10 feat:模具管理-添加“展开/收起”按钮 1 month ago
黄伟杰 e6a3e05220 feat:备件模块-添加“展开/收起”按钮 1 month ago
黄伟杰 a12ed938c1 style:设备管理-添加“展开/折叠”按钮 1 month ago
黄伟杰 63d7ab3c8d style:产能报表-字段调整 1 month ago
黄伟杰 e99351440d feat:能耗类型-添加“展开/折叠”按钮 1 month ago
黄伟杰 1461cb2b25 feat:仓储管理-添加“展开/折叠”按钮 1 month ago
黄伟杰 985e92ee2b feat:产能报表-添加报工产能弹框 1 month ago
黄伟杰 aa3111131d feat:IOT模块添加展开/收起按钮 1 month ago
黄伟杰 a6d2265622 style:修改展开/收起按钮样式 1 month ago
黄伟杰 9f7a3c81b6 style:任务单排产-产能来源字段逻辑调整 1 month ago
黄伟杰 2a6b73e566 fix:修复runoverview api文件缺失问题 1 month ago
黄伟杰 1bdd5c276f style:产能报表-删除状态字段 1 month ago
黄伟杰 e68b934593 feat:生产管理模块-筛选条件添加“更多”按钮 1 month ago
黄伟杰 cb0ade38e0 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 month ago
黄伟杰 152a906c16 feat:添加产能报表页面 1 month ago
黄伟杰 e13fdef1f6 feat:工厂建模模块-筛选条件添加“更多”按钮 1 month ago
liutao 2295788977 Merge remote-tracking branch 'origin/main' 1 month ago
liutao d9b062419d 设备管理调整 1 month ago
liutao d0db5f5a90 设备管理调整 1 month ago
黄伟杰 80e2cda424 style:搜索菜单按钮,点击后自动获取焦点 1 month ago
黄伟杰 176a3dc607 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 month ago
黄伟杰 182e9e9712 feat:排产-甘特图添加双击弹框-调整任务 1 month ago
liutao 84101b7866 设备运行概览 1 month ago
liutao 2872f52d57 设备运行概览调整 1 month ago
黄伟杰 8c3f7a5aec feat:排产-甘特图添加锁定、撤回操作按钮 1 month ago
黄伟杰 fc6f348ad6 style:排产甘特图i18适配 1 month ago
黄伟杰 0ff6eaef69 style:甘特图-计划明细字段调整 1 month ago
liutao f88765448b Merge remote-tracking branch 'origin/main' 1 month ago
liutao bedd8d9bca 切换时真加点击事件 1 month ago
liutao 4abb93deab 样式优化 1 month ago
黄伟杰 21fdceb26f Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 month ago
黄伟杰 d8a88b6fd7 feat:甘特图修改-多个计划展示在一行显示 1 month ago
liutao e42314146e 点检修改bug 1 month ago
liutao ad290b9d4a 解决冲突 1 month ago
liutao 2f9cd9e0d0 Merge remote-tracking branch 'origin/main' 1 month ago
liutao afc7685e89 设备运行概览 1 month ago
黄伟杰 0dbdf57204 style:任务制单-查询条件样式优化 1 month ago
黄伟杰 da56f8d359 style:设备台账-移除每日报工平均值、数据采集产能 1 month ago
黄伟杰 85b759406d style:任务单排产-排产-产能来源改成从字典获取 1 month ago
黄伟杰 e5c9c61943 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 month ago
黄伟杰 79280c5ace feat:设备台账-添加详情页面 1 month ago
必硕 7e206e0de1 Merge pull request '增加能耗概览页面' (#5) from ck into main
Reviewed-on: #5
1 month ago
ck-chenkang 1aad86f8cb 增加能耗概览页面 1 month ago
黄伟杰 b0c7be253f feat:生产报表-添加详情页 1 month ago
黄伟杰 c09828701a Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 1 month ago
liutao 45f4f464a2 调整菜单栏样式 1 month ago
liutao 4835eddb3e 采集设备修改bug 1 month ago
黄伟杰 3d42371613 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
# Conflicts:
#	src/locales/en.ts
1 month ago
liutao 77e5439b9e 调整全局table的样式 1 month ago
必硕 27268ea534 Merge pull request '调整英文菜单显示不全的问题' (#4) from ck into main
Reviewed-on: #4
1 month ago
ck-chenkang dcde3b475a 调整英文菜单显示不全的问题 1 month ago
必硕 b9c0d8c121 Merge pull request 'ck' (#3) from ck into main
Reviewed-on: #3
1 month ago
ck-chenkang ed1979a8fd Merge branch 'ck' of https://git.ngsk.tech/linweidong/besure_web into ck 1 month ago
ck-chenkang 50c8cf86de 产品物料信息页面增加编码搜索条件 1 month ago
liutao 57bbd4b4ad 设备管理调整 1 month ago
必硕 c1b08534b0 Merge pull request 'git commit -m "stop tracking vscode settings"' (#2) from ck into main
Reviewed-on: #2
1 month ago
ck-chenkang d9ec0af7fe git commit -m "stop tracking vscode settings" 1 month ago
ck-chenkang 875a4c7c46 git commit -m "stop tracking vscode settings" 1 month ago
liutao 15e7b7bcef 修改弹出框组件及模具弹出框 1 month ago
liutao 4121523db9 历史分析界面优化 1 month ago
liutao 2dfbe31f0d 历史数据分析优化 1 month ago
liutao c83a78d3a3 Merge remote-tracking branch 'origin/main' 1 month ago
必硕 f2e2bcb99e Merge pull request '增加ck开发环境配置' (#1) from ck into main
Reviewed-on: #1
1 month ago
ck-chenkang 8c8fcaf8d5 增加ck开发环境配置 1 month ago
黄伟杰 b0099e4314 style:产品物料信息-新增/编辑-编码输入框大小调整 2 months ago
黄伟杰 8a0c72e6eb feat:仓库信息-添加详情弹框 2 months ago
黄伟杰 efaf527d80 feat:库区信息/库位信息-新增页面 2 months ago
liutao 91c2ef7e80 记录分析 2 months ago
liutao 65001fe851 采集设备点位界面优化 2 months ago
liutao c09cb6fe14 实时监控数据调整 2 months ago
liutao 1625515607 Merge remote-tracking branch 'origin/main' 2 months ago
黄伟杰 4e134b787a style:任务单排产-排产甘特图样式优化 2 months ago
黄伟杰 55cc7b8d67 style:甘特图菜单样式调整 2 months ago
黄伟杰 4f481974a5 style:甘特图适配中英文 2 months ago
黄伟杰 5416bdbdac Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 1cd7cbf82a style:生产报表页面适配中英文 2 months ago
黄伟杰 b7883659b1 style:出库单据-默认展示tabs修改 2 months ago
黄伟杰 0de4716c80 style:生产报表-质检信息字段调整 2 months ago
黄伟杰 006c03b747 style:任务单排产-create-batch接口添加领料人/是否计算损耗入参 2 months ago
黄伟杰 2bd0fcde8d style:生产计划/任务单排产生产报表-进度条样式修改 2 months ago
黄伟杰 fb969128df style:生产计划-检验明细添加分页 2 months ago
黄伟杰 223ab3367c feat:生产报表-质检信息对接 2 months ago
黄伟杰 61c3663e96 feat:甘特图菜单页添加刷新按钮 2 months ago
黄伟杰 1cf5a724c1 style:任务单排产-进度条样式修改 2 months ago
黄伟杰 1d8a70de9b feat:甘特图菜单页-添加排产看板 2 months ago
黄伟杰 5653fa98c0 style:生产计划-检验按钮保存后刷新列表 2 months ago
黄伟杰 3175a1c3b6 style:检验记录-新增/编辑-生产计划选项框调整 2 months ago
liutao 88874df91e 厂区结构增加设备弹窗 2 months ago
黄伟杰 f41bdb850f style:检验记录-编辑添加显示判断 2 months ago
黄伟杰 f39ce1aec2 style:检验模板-新增/编辑-检验方案添加productId传参 2 months ago
黄伟杰 90403a88a6 style:任务单排产-任务单明细-是否计算损耗字段 2 months ago
黄伟杰 fae274dfa0 style:检验模板-编辑回显 2 months ago
黄伟杰 307542d9cd style:任务单排产-任务单明细-新增计划添加领料人、是否完成质检字段 2 months ago
黄伟杰 bce8cfea56 style:生产报表-报工信息-添加日期筛选条件 2 months ago
黄伟杰 8eab07d761 style:生产报表-列表添加刷新按钮 2 months ago
黄伟杰 242d933c62 style:生产报表-基础信息-查看产品信息字段修改 2 months ago
liutao 974cf1b3dd 组件及字段优化 2 months ago
liutao 2400608c78 Merge remote-tracking branch 'origin/main' 2 months ago
黄伟杰 b865238720 feat:生产计划-已开工-检验按钮逻辑修改 2 months ago
黄伟杰 7574ca1324 style:修复甘特图保存后弹框报错 2 months ago
黄伟杰 0ac051e4fc style:生产投料-数量不填的默认为0 2 months ago
黄伟杰 9361794f43 style:产品物料信息-设备-所属车间字段更改 2 months ago
黄伟杰 fc8e5367fb feat:检验模板-添加关联产品 2 months ago
黄伟杰 11b3d14603 style:产品物料信息-关联设备选项接口添加deviceStatus=0入参 2 months ago
黄伟杰 7e32b70a6d style:产品物料信息-状态字段位置更改 2 months ago
黄伟杰 8863d6fd57 style:任务制单-技术要求、备注样式修改 2 months ago
黄伟杰 99acfc1f15 style:任务制单-明细-编辑-打包数量字段自动填充 2 months ago
黄伟杰 95d8398b0b style:产品物料信息-去掉字段:采购价格、最低价格、销售价格 2 months ago
黄伟杰 1d312fd646 style:产品BOM-产品BOM明细-单位自动带入 2 months ago
黄伟杰 5a5080877f style:生产计划-添加“所有”tabs 2 months ago
黄伟杰 77ba830e70 style:甘特图菜单添加设备筛选框 2 months ago
黄伟杰 cef7a40033 style:排产甘特图日期调整 2 months ago
黄伟杰 c4fd3bbbb1 feat:菜单甘特图页面/一键排查-甘特图拆开两个组件 2 months ago
黄伟杰 6d57b13a75 style:排产弹框-任务单汇总明细-未计划数为0的,不能勾选 2 months ago
黄伟杰 99aeeb54fd style:排产弹框适配中英文 2 months ago
黄伟杰 20b54fbd70 style:检验记录-新增添加单号自动生成 2 months ago
黄伟杰 b22631a612 style:产品BOM-产品筛选框接口更改 2 months ago
黄伟杰 40967ed200 style:任务单排产-排产弹框样式调整 2 months ago
liutao f9575447af TableSelectDialog增加查询插槽 2 months ago
黄伟杰 b7d52c84a3 style:任务制单-父列表项的status值为2,7,8,9,10时,子列表不展示编辑和删除按钮 2 months ago
黄伟杰 bae356efe8 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 e905668923 style:任务单排产-生产进度条样式修改 2 months ago
黄伟杰 20dee1da73 feat:添加生产报表页面 2 months ago
黄伟杰 75ab2d74d4 style:生产计划-暂停状态操作栏添加开工按钮 2 months ago
liutao c104147447 Merge remote-tracking branch 'origin/main' 2 months ago
liutao 39aa6ef858 采集设备九宫格优化 2 months ago
黄伟杰 53c40b9689 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 398fa8e367 style:厂区结构-关联采集设备字段回显 2 months ago
黄伟杰 ad0a1ee6c4 feat:待入库-入库按钮添加仓库选择 2 months ago
黄伟杰 1966a2c10e style:厂区结构-新增/编辑-关联采集设备选项接口更换 2 months ago
黄伟杰 6ab008b512 feat:甘特图添加颜色区分 2 months ago
liutao a4af8de233 入库优化 2 months ago
liutao 250c873c11 Merge remote-tracking branch 'origin/main' 2 months ago
黄伟杰 a99abc9e01 feat:添加报工记录页面 2 months ago
黄伟杰 6fc887d9f2 style:甘特图菜单页添加每日报工平均值、数据采集产能字段 2 months ago
黄伟杰 824e5e77d9 style:甘特图显示样式优化 2 months ago
黄伟杰 02ba88a454 style:任务单排产-新增-计划开始、计划结束、最晚开工时间添加时分秒 2 months ago
黄伟杰 b452e320ef style:任务制单-新增-交货日期不能选往日 2 months ago
黄伟杰 41cacd4d1d style:任务单排产-添加生产进度条 2 months ago
黄伟杰 159b24755f style:任务制单-tabs项更换 2 months ago
黄伟杰 4183ccda7c style:任务单排产-新增计划-数量值添加校验(不能大于未计划数量) 2 months ago
liutao 4ad01986c6 上下模选择弹出框 2 months ago
黄伟杰 40a68e3c1f style:甘特图tooltip隐藏逻辑优化 2 months ago
黄伟杰 b15065bf4d Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 a0c5768efa style:修改任务单排产-新增计划及生产计划详情字段 2 months ago
黄伟杰 3ac5ee7183 style:任务制单-任务单明细-新增按钮显示逻辑修改 2 months ago
黄伟杰 0b375d85b3 style:任务单排产-tabs更换 2 months ago
黄伟杰 61ea3336d4 style:任务单排产-未计划为0的,不显示新增计划按钮 2 months ago
黄伟杰 d0d936ca2d style:任务制单-任务单明细-新增按钮显示逻辑修改 2 months ago
liutao b1a8879442 优化调整 2 months ago
黄伟杰 4d097227a5 style:生产计划-报工后刷新报工记录列表 2 months ago
黄伟杰 3a2f65d4a2 style:生产计划-详情弹框字段与排产的新增计划弹框字段同步 2 months ago
黄伟杰 ec0a48b5c8 style:生产投料-新增-生产计划status值修改 2 months ago
黄伟杰 f8a711a73b style:删除甘特图菜单无用代码 2 months ago
黄伟杰 b57ed07ff8 feat:甘特图组件封装 2 months ago
黄伟杰 dc9cf5eeea style:生产计划列表添加任务编码 2 months ago
黄伟杰 fa8d0482bd style:产品BOM-编辑-bomDetails集合里不传createTime字段 2 months ago
liutao 4d4c2652d5 Merge remote-tracking branch 'origin/main' 2 months ago
liutao 38ad2fbe79 弹出框取消全选按钮 2 months ago
黄伟杰 3dbe0bf81b Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 b188f09241 style:排产甘特图样式优化 2 months ago
liutao ae293a5d46 Merge remote-tracking branch 'origin/main' 2 months ago
liutao 4bd0065f8a 检验模板调整 2 months ago
黄伟杰 b71877f08e style:排产弹框-添加是否跳过节假日switch 2 months ago
黄伟杰 0c9adde9d2 style:排产甘特图样式调整 2 months ago
黄伟杰 0412d411b4 style:检验记录-编辑-ticketType回显 2 months ago
黄伟杰 6f296e186d style:检验记录-生产计划传参修改 2 months ago
黄伟杰 9198c8c345 style:任务单排产-关联设备保存后刷新列表 2 months ago
黄伟杰 663b99800c feat:排产弹框-新增设备名称列/关联设备按钮 2 months ago
黄伟杰 972bf17c56 feat:生产计划-添加已开工tabs 2 months ago
黄伟杰 fd59c97e0e style:任务单排产-新增计划-计划开始时间不能晚于结束时间 2 months ago
liutao b4367cea76 设备管理调整 2 months ago
黄伟杰 d0a900560c style:设备台账-是否排产勾选后展示额定产能、每日报工平均值、数据采集产能 2 months ago
黄伟杰 d0b6a5942e Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 beec34f669 style:处理设备台账代码合并问题 2 months ago
liutao a5e321bdb5 样式优化 2 months ago
黄伟杰 41c328bc09 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
# Conflicts:
#	src/views/mes/deviceledger/DeviceLedgerForm.vue
#	src/views/mes/deviceledger/index.vue
2 months ago
黄伟杰 cca089efc4 style:甘特图查询时间默认本月 2 months ago
黄伟杰 2fb7265888 feat:新增甘特图页面 2 months ago
黄伟杰 5bedb9b6dd style:任务单排产-排产弹框-复选框逻辑优化 2 months ago
黄伟杰 e3493e1f27 style:任务单排产-甘特图样式修改 2 months ago
黄伟杰 411e913d8e style:任务单排产-一键排查接口字段调整 2 months ago
liutao 53267baa92 二维码显示出来 2 months ago
liutao f356485d74 上传文件及弹出框Form 2 months ago
黄伟杰 93f84dd379 style:任务单排产字段调整 2 months ago
黄伟杰 062bdce1df style:甘特图样式修改 2 months ago
黄伟杰 95b81fce39 style:任务单排产-新增计划-taskDetailId传参调整 2 months ago
黄伟杰 5c3cd4de38 style:产品物料信息-新增/编辑-设备列表添加已排产过滤 2 months ago
黄伟杰 aca355dbc2 style:排产接口入参添加产品编码 2 months ago
黄伟杰 a124030f49 feat:甘特图弹框抽离组件 2 months ago
黄伟杰 f7a7f529d9 style:任务单排产-甘特图保存字段调整 2 months ago
黄伟杰 4df1714eef style:生产计划-甘特图样式修改 2 months ago
黄伟杰 a16beee544 feat:添加甘特图预览 2 months ago
黄伟杰 ae7d8389ed style:设备台账-新增/编辑-是否排产字段修改 2 months ago
黄伟杰 215fab75ef style:产品物料信息-阻止表单原生事件 2 months ago
黄伟杰 c65523c48b style:设备台账-是否排产字段修改 2 months ago
黄伟杰 e21b5e3246 feat:任务单排产-列表表头调整、排产弹框对接 2 months ago
liutao dd64b05572 增加图片上传 2 months ago
黄伟杰 3a6a164960 style:表管理-计算规则添加文案提示 2 months ago
黄伟杰 dd34e163a7 feat:采集设备-点位规则添加四则运算规则 2 months ago
liutao 7b26f5c34a 新增页面增加字段 2 months ago
liutao b9e6b355b2 Merge remote-tracking branch 'origin/main' 2 months ago
黄伟杰 4fc27674a8 style:采集设备-添加新增产量按钮 2 months ago
黄伟杰 ff25737400 style:任务单排产-新增计划刷新列表 2 months ago
黄伟杰 7137ca46df Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 6bbcf3b0b8 style:任务单排产-列表添加“是否完成排产”列 2 months ago
liutao 662e1beef1 编辑关键件和备件改成弹出的表单 2 months ago
黄伟杰 49c28b2425 style:产品BOM-新增-清除单位默认值 2 months ago
黄伟杰 24e2a156a4 feat:任务制单-添加是否急单字段 2 months ago
黄伟杰 4863831a0c feat:产品管理-产品物料信息-产品分类时添加关联设备、关联模具字段 2 months ago
liutao b3d07e3c89 Merge remote-tracking branch 'origin/main' 2 months ago
liutao 4ec226416b 采集设备九宫格增加弹出框 2 months ago
黄伟杰 b8dc35bc23 feat:设备台账-添加是否排产、额定产能字段;状态改成switch切换 2 months ago
liutao 0de3432089 Merge remote-tracking branch 'origin/main' 2 months ago
黄伟杰 b3c22ec032 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web 2 months ago
黄伟杰 e49c478b8f style:调整告警登记字典名称 2 months ago
liutao 425e5af352 产线大屏增加放大缩小按钮 2 months ago
liutao 1f85c45a1c 大屏增加全屏按钮 2 months ago
liutao f3443fd186 添加大屏不需要路由不重定向白名单 2 months ago
liutao f8c3516604 调整列表宽度 2 months ago
liutao 98e08f1a23 调整列表宽度及修改dictag组件 2 months ago
黄伟杰 92fcd9b9e7 style:设备台账-编辑-二维码组件调用 2 months ago
黄伟杰 9561ce6177 style:增加打包内存限制 3 months ago
黄伟杰 341fd0aa5d style:修复报错 3 months ago
黄伟杰 513b6030b7 Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
# Conflicts:
#	src/views/mes/deviceledger/index.vue
3 months ago
黄伟杰 77a1bc3faa style:采集设备-设备属性-新增-点位编码添加格式校验 3 months ago
黄伟杰 a5ba206e72 feat:设备台账、模具台账、备件、设备关键件模块二维码统一调用组件 3 months ago
liutao 6554ca02f4 下拉值转英文 3 months ago
黄伟杰 7baec6c279 feat:接入vue-plugin-hiprint打印组件、二维码预览打印组件封装 3 months ago
liutao 47c8d2e0c6 Merge branch 'liutao_branch'
# Conflicts:
#	src/views/mes/deviceledger/index.vue
3 months ago
黄伟杰 76d3a0a5c6 style:修改产品物料、备件、设备、模具管理二维码样式 3 months ago
黄伟杰 a68b1949fe style:设备关键件-二维码添加预览按钮 3 months ago
黄伟杰 2fc6bc08ae style:设备台账-详情-二维码样式调整 3 months ago
黄伟杰 bbee7c8cd2 feat:设备台账、关键件、备件、物料、模具添加二维码功能 3 months ago
黄伟杰 3708e99cf8 style:编码规则-新增/编辑弹框添加码类型字段 3 months ago
黄伟杰 887495f8b2 style:编码规则添加码类型列 3 months ago
黄伟杰 643b3b9f99 style:模具详情-适配条形码样式 3 months ago
黄伟杰 0640129cda style:采集设备-编辑-数据类型无法更改 3 months ago
黄伟杰 88469adf69 style:模具详情-二维码调整 3 months ago
黄伟杰 3f2502915c style:模具管理-删除接口更改 3 months ago
HuangHuiKang d66c79e6fe fix:修改nginx 3 months ago

@ -1,34 +1,22 @@
# 开发环境本地只启动前端项目依赖开发环境后端、APP
NODE_ENV=production NODE_ENV=production
VITE_DEV=true VITE_DEV=true
# 请求路径 VITE_BASE_URL=' '
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server VITE_UPLOAD_TYPE=server
VITE_UPLOAD_URL='/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=false VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true VITE_SOURCEMAP=true
# 打包路径
VITE_BASE_PATH=/ VITE_BASE_PATH=/
# 输出路径
VITE_OUT_DIR=dist VITE_OUT_DIR=dist
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn' VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# 验证码的开关 VITE_APP_CAPTCHA_ENABLE=false
VITE_APP_CAPTCHA_ENABLE=true

@ -8,8 +8,9 @@ VITE_DEV=true
# 线上环境 # 线上环境
# VITE_BASE_URL='https://besure.ngsk.tech:7001' # VITE_BASE_URL='https://besure.ngsk.tech:7001'
# 本地联调 # 本地联调
# VITE_BASE_URL='http://192.168.5.107:48081' VITE_BASE_URL='http://192.168.5.160:48081'
VITE_BASE_URL='http://localhost:48081' # VITE_BASE_URL='http://192.168.43.233:48081'
# VITE_BASE_URL='http://192.168.5.5:48081'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务 # 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务

4
.gitignore vendored

@ -9,3 +9,7 @@ auto-*.d.ts
.history .history
pnpm-lock.yaml pnpm-lock.yaml
/.vscode /.vscode
.env.cklocal
.vscode/*
.vscode/settings.json
launch.json

@ -1,144 +0,0 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"prettier.printWidth": 100, //
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": {
"@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echart",
"echarts",
"esnext",
"esno",
"iconify",
"INTLIFY",
"lintstagedrc",
"logicflow",
"nprogress",
"pinia",
"pnpm",
"qrcode",
"sider",
"sortablejs",
"stylelint",
"svgs",
"unocss",
"unplugin",
"unref",
"videojs",
"VITE",
"vitejs",
"vueuse",
"wangeditor",
"xingyu",
"yudao",
"zxcvbn"
],
//
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
}

@ -1,16 +1,16 @@
# 使用轻量级的 Nginx 镜像 # 使用轻量级的 Nginx 镜像
FROM nginx:alpine FROM nginx:alpine
# 复制自定义 Nginx 配置(如果有) ARG NGINX_CONF=nginx.conf
RUN rm -f /etc/nginx/conf.d/default.conf RUN rm -f /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY ${NGINX_CONF} /etc/nginx/conf.d/default.conf
# 将本地 dist 目录复制到容器中
COPY dist /usr/share/nginx/html COPY dist /usr/share/nginx/html
# 暴露 8088 端口
EXPOSE 8088 EXPOSE 8088
# 创建启动脚本 # 创建启动脚本
RUN echo -e '#!/bin/sh\n\ RUN echo -e '#!/bin/sh\n\
echo "Waiting for MySQL/Redis/MinIO to start..."\n\ echo "Waiting for MySQL/Redis/MinIO to start..."\n\

@ -0,0 +1,121 @@
style产品物料信息-新增/编辑-编码输入框大小调整
feat仓库信息-添加详情弹框
feat库区信息/库位信息-新增页面
style任务单排产-排产甘特图样式优化
style甘特图菜单样式调整
style甘特图适配中英文
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style生产报表页面适配中英文
style出库单据-默认展示tabs修改
style生产报表-质检信息字段调整
style任务单排产-create-batch接口添加领料人/是否计算损耗入参
style生产计划/任务单排产生产报表-进度条样式修改
style生产计划-检验明细添加分页
feat生产报表-质检信息对接
feat甘特图菜单页添加刷新按钮
style任务单排产-进度条样式修改
feat甘特图菜单页-添加排产看板
style生产计划-检验按钮保存后刷新列表
style检验记录-新增/编辑-生产计划选项框调整
style检验记录-编辑添加显示判断
style检验模板-新增/编辑-检验方案添加productId传参
style任务单排产-任务单明细-是否计算损耗字段
style检验模板-编辑回显
style任务单排产-任务单明细-新增计划添加领料人、是否完成质检字段
style生产报表-报工信息-添加日期筛选条件
style生产报表-列表添加刷新按钮
style生产报表-基础信息-查看产品信息字段修改
feat生产计划-已开工-检验按钮逻辑修改
style修复甘特图保存后弹框报错
style生产投料-数量不填的默认为0
style产品物料信息-设备-所属车间字段更改
feat检验模板-添加关联产品
style产品物料信息-关联设备选项接口添加deviceStatus=0入参
style产品物料信息-状态字段位置更改
style任务制单-技术要求、备注样式修改
style任务制单-明细-编辑-打包数量字段自动填充
style产品物料信息-去掉字段:采购价格、最低价格、销售价格
style产品BOM-产品BOM明细-单位自动带入
style生产计划-添加“所有”tabs
style甘特图菜单添加设备筛选框
style排产甘特图日期调整
feat菜单甘特图页面/一键排查-甘特图拆开两个组件
style排产弹框-任务单汇总明细-未计划数为0的不能勾选
style排产弹框适配中英文
style检验记录-新增添加单号自动生成
style产品BOM-产品筛选框接口更改
style任务单排产-排产弹框样式调整
style任务制单-父列表项的status值为2,7,8,9,10时子列表不展示编辑和删除按钮
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style任务单排产-生产进度条样式修改
feat添加生产报表页面
style生产计划-暂停状态操作栏添加开工按钮
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style厂区结构-关联采集设备字段回显
feat待入库-入库按钮添加仓库选择
style厂区结构-新增/编辑-关联采集设备选项接口更换
feat甘特图添加颜色区分
feat添加报工记录页面
style甘特图菜单页添加每日报工平均值、数据采集产能字段
style甘特图显示样式优化
style任务单排产-新增-计划开始、计划结束、最晚开工时间添加时分秒
style任务制单-新增-交货日期不能选往日
style任务单排产-添加生产进度条
style任务制单-tabs项更换
style任务单排产-新增计划-数量值添加校验(不能大于未计划数量)
style甘特图tooltip隐藏逻辑优化
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style修改任务单排产-新增计划及生产计划详情字段
style任务制单-任务单明细-新增按钮显示逻辑修改
style任务单排产-tabs更换
style任务单排产-未计划为0的不显示新增计划按钮
style任务制单-任务单明细-新增按钮显示逻辑修改
style生产计划-报工后刷新报工记录列表
style生产计划-详情弹框字段与排产的新增计划弹框字段同步
style生产投料-新增-生产计划status值修改
style删除甘特图菜单无用代码
feat甘特图组件封装
style生产计划列表添加任务编码
style产品BOM-编辑-bomDetails集合里不传createTime字段
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style排产甘特图样式优化
style排产弹框-添加是否跳过节假日switch
style排产甘特图样式调整
style检验记录-编辑-ticketType回显
style检验记录-生产计划传参修改
style任务单排产-关联设备保存后刷新列表
feat排产弹框-新增设备名称列/关联设备按钮
feat生产计划-添加已开工tabs
style任务单排产-新增计划-计划开始时间不能晚于结束时间
style设备台账-是否排产勾选后展示额定产能、每日报工平均值、数据采集产能
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style处理设备台账代码合并问题
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style甘特图查询时间默认本月
feat新增甘特图页面
style任务单排产-排产弹框-复选框逻辑优化
style任务单排产-甘特图样式修改
style任务单排产-一键排查接口字段调整
style任务单排产字段调整
style甘特图样式修改
style任务单排产-新增计划-taskDetailId传参调整
style产品物料信息-新增/编辑-设备列表添加已排产过滤
style排产接口入参添加产品编码
feat甘特图弹框抽离组件
style任务单排产-甘特图保存字段调整
style生产计划-甘特图样式修改
feat添加甘特图预览
style设备台账-新增/编辑-是否排产字段修改
style产品物料信息-阻止表单原生事件
style设备台账-是否排产字段修改
feat任务单排产-列表表头调整、排产弹框对接
style表管理-计算规则添加文案提示
feat采集设备-点位规则添加四则运算规则
style采集设备-添加新增产量按钮
style任务单排产-新增计划刷新列表
Merge branch 'main' of https://git.ngsk.tech/linweidong/besure_web
style任务单排产-列表添加“是否完成排产”列
style产品BOM-新增-清除单位默认值
feat任务制单-添加是否急单字段
feat产品管理-产品物料信息-产品分类时添加关联设备、关联模具字段
feat设备台账-添加是否排产、额定产能字段状态改成switch切换

@ -0,0 +1,130 @@
server {
listen 8088;
server_name localhost;
# 允许上传最大100MB
client_max_body_size 100m;
# =========================
# 后端接口代理DEV
# =========================
location /admin-api/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /api/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /jmreport/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /v3/api-docs/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /doc.html {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /swagger-ui/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /webjars/ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location ~ /v3/api-docs/.*\.json$ {
proxy_pass http://besure_server_dev:48081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# =========================
# 前端静态页面
# =========================
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# =========================
# 错误页
# =========================
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
# =========================
# FRP / 转发代理
# =========================
server {
listen 39002;
server_name localhost;
location /besure/ {
proxy_pass http://ngsk.tech:39001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_buffering off;
}
}

@ -5,6 +5,18 @@ server {
# 关键允许上传最大100MB # 关键允许上传最大100MB
client_max_body_size 100m; client_max_body_size 100m;
# 超时时间
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
client_body_timeout 300s;
send_timeout 300s;
# 防止大文件上传被缓冲卡死
proxy_request_buffering off;
proxy_buffering off;
location /admin-api/ { location /admin-api/ {
proxy_pass http://besure_server:48081; proxy_pass http://besure_server:48081;
proxy_set_header Host $host; proxy_set_header Host $host;
@ -92,16 +104,3 @@ server {
} }
} }
server {
listen 39002;
server_name localhost;
location /besure/ {
proxy_pass http://ngsk.tech:39001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_buffering off;
}
}

@ -10,11 +10,11 @@
"pro": "vite --mode env.prod", "pro": "vite --mode env.prod",
"dev-server": "vite --mode dev", "dev-server": "vite --mode dev",
"ts:check": "vue-tsc --noEmit", "ts:check": "vue-tsc --noEmit",
"build:local": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build", "build:local": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build",
"build:dev": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode dev", "build:dev": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode dev",
"build:test": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode test", "build:test": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode stage", "build:stage": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode stage",
"build:prod": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode prod", "build:prod": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode prod",
"serve:dev": "vite preview --mode dev", "serve:dev": "vite preview --mode dev",
"serve:prod": "vite preview --mode prod", "serve:prod": "vite preview --mode prod",
"preview": "pnpm build:local && vite preview", "preview": "pnpm build:local && vite preview",
@ -44,6 +44,7 @@
"cropperjs": "^1.6.1", "cropperjs": "^1.6.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"dhtmlx-gantt": "^9.1.3",
"diagram-js": "^12.8.0", "diagram-js": "^12.8.0",
"driver.js": "^1.3.1", "driver.js": "^1.3.1",
"echarts": "^5.5.0", "echarts": "^5.5.0",
@ -72,6 +73,7 @@
"vue": "3.5.12", "vue": "3.5.12",
"vue-dompurify-html": "^4.1.4", "vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.10.2", "vue-i18n": "9.10.2",
"vue-plugin-hiprint": "^0.0.60",
"vue-router": "4.4.5", "vue-router": "4.4.5",
"vue-types": "^5.1.1", "vue-types": "^5.1.1",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",

@ -62,6 +62,9 @@ importers:
dayjs: dayjs:
specifier: ^1.11.10 specifier: ^1.11.10
version: 1.11.19 version: 1.11.19
dhtmlx-gantt:
specifier: ^9.1.3
version: 9.1.3
diagram-js: diagram-js:
specifier: ^12.8.0 specifier: ^12.8.0
version: 12.8.1 version: 12.8.1
@ -146,6 +149,9 @@ importers:
vue-i18n: vue-i18n:
specifier: 9.10.2 specifier: 9.10.2
version: 9.10.2(vue@3.5.12(typescript@5.3.3)) version: 9.10.2(vue@3.5.12(typescript@5.3.3))
vue-plugin-hiprint:
specifier: ^0.0.60
version: 0.0.60
vue-router: vue-router:
specifier: 4.4.5 specifier: 4.4.5
version: 4.4.5(vue@3.5.12(typescript@5.3.3)) version: 4.4.5(vue@3.5.12(typescript@5.3.3))
@ -911,6 +917,11 @@ packages:
'@carbon/icons@11.71.0': '@carbon/icons@11.71.0':
resolution: {integrity: sha512-PwOJ7C2MNQbHvaOsPhiKHNikJ9flGtyVeGIsOHIkyNndcxAUOleppHcGofOo4NvCeIUZwnr+EN8fMaU+ZdsH9w==} resolution: {integrity: sha512-PwOJ7C2MNQbHvaOsPhiKHNikJ9flGtyVeGIsOHIkyNndcxAUOleppHcGofOo4NvCeIUZwnr+EN8fMaU+ZdsH9w==}
'@claviska/jquery-minicolors@2.3.6':
resolution: {integrity: sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==}
peerDependencies:
jquery: '>= 1.7.x'
'@codemirror/autocomplete@6.20.0': '@codemirror/autocomplete@6.20.0':
resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
@ -1658,6 +1669,9 @@ packages:
'@sinclair/typebox@0.27.8': '@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@swc/core-darwin-arm64@1.15.7': '@swc/core-darwin-arm64@1.15.7':
resolution: {integrity: sha512-+hNVUfezUid7LeSHqnhoC6Gh3BROABxjlDNInuZ/fie1RUxaEX4qzDwdTgozJELgHhvYxyPIg1ro8ibnKtgO4g==} resolution: {integrity: sha512-+hNVUfezUid7LeSHqnhoC6Gh3BROABxjlDNInuZ/fie1RUxaEX4qzDwdTgozJELgHhvYxyPIg1ro8ibnKtgO4g==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1882,6 +1896,9 @@ packages:
'@types/qs@6.14.0': '@types/qs@6.14.0':
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
'@types/raf@3.4.3':
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
'@types/semver@7.7.1': '@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
@ -2342,6 +2359,10 @@ packages:
slate: ^0.72.0 slate: ^0.72.0
snabbdom: ^3.1.0 snabbdom: ^3.1.0
'@wtto00/html2canvas@1.4.3':
resolution: {integrity: sha512-jwsb+xL8N+gjrSNABSaFdxmWtE4c7RNFjP20lo1G7gs63Qqo1phhxVBTzxc/apDVh6LgXsU2l5bwKtXd9uz65w==}
engines: {node: '>=8.0.0'}
'@xmldom/xmldom@0.8.11': '@xmldom/xmldom@0.8.11':
resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@ -2523,6 +2544,10 @@ packages:
balanced-match@2.0.0: balanced-match@2.0.0:
resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
base@0.11.2: base@0.11.2:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2593,9 +2618,18 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
btoa@1.2.1:
resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==}
engines: {node: '>= 0.4.0'}
hasBin: true
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
bwip-js@4.8.0:
resolution: {integrity: sha512-gUDkDHSTv8/DJhomSIbO0fX/Dx0MO/sgllLxJyJfu4WixCQe9nfGJzmHm64ZCbxo+gUYQEsQcRmqcwcwPRwUkg==}
hasBin: true
cac@6.7.14: cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2640,6 +2674,10 @@ packages:
caniuse-lite@1.0.30001761: caniuse-lite@1.0.30001761:
resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==}
canvg@3.0.11:
resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==}
engines: {node: '>=10.0.0'}
chalk@1.1.3: chalk@1.1.3:
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2867,6 +2905,9 @@ packages:
resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==}
engines: {node: '>=12 || >=16'} engines: {node: '>=12 || >=16'}
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
css-select@4.3.0: css-select@4.3.0:
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
@ -3126,6 +3167,9 @@ packages:
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
hasBin: true hasBin: true
dhtmlx-gantt@9.1.3:
resolution: {integrity: sha512-3FjvcsGVLFgpZgQKjzKnw1vZh2dIKwLt1Zm6G9Oo/bW2Ogm6/PeQ2cNHO033MWicuxJZn2mpLmg6yssUK3RAiQ==}
diagram-js-direct-editing@3.2.0: diagram-js-direct-editing@3.2.0:
resolution: {integrity: sha512-+pyxeQGBSdLiZX0/tmmsm2qZSvm9YtVzod5W3RMHSTR7VrkUMD6E7EX/W9JQv3ebxO7oIdqFmytmNDDpSHnYEw==} resolution: {integrity: sha512-+pyxeQGBSdLiZX0/tmmsm2qZSvm9YtVzod5W3RMHSTR7VrkUMD6E7EX/W9JQv3ebxO7oIdqFmytmNDDpSHnYEw==}
peerDependencies: peerDependencies:
@ -3197,6 +3241,9 @@ packages:
resolution: {integrity: sha512-rmvrrmWQPD/X1A/nPBfIVg4r05792QdG9Z4Prk6oQG0F9zBMDkr0GKAdds1wjb2dq1rTz/ywc4ZxpZbgz0tttg==} resolution: {integrity: sha512-rmvrrmWQPD/X1A/nPBfIVg4r05792QdG9Z4Prk6oQG0F9zBMDkr0GKAdds1wjb2dq1rTz/ywc4ZxpZbgz0tttg==}
engines: {node: '>=18'} engines: {node: '>=18'}
dompurify@2.5.9:
resolution: {integrity: sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==}
dompurify@3.3.1: dompurify@3.3.1:
resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
@ -3270,6 +3317,13 @@ packages:
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
engine.io-client@6.6.4:
resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==}
engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'}
entities@1.1.2: entities@1.1.2:
resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
@ -3518,6 +3572,9 @@ packages:
feelin@3.2.0: feelin@3.2.0:
resolution: {integrity: sha512-GFDbHsTYk7YXO1tyw1dOjb7IODeAZvNIosdGZThUwPx5XcD/XhO0hnPZXsIbAzSsIdrgGlTEEdby9fZ2gixysA==} resolution: {integrity: sha512-GFDbHsTYk7YXO1tyw1dOjb7IODeAZvNIosdGZThUwPx5XcD/XhO0hnPZXsIbAzSsIdrgGlTEEdby9fZ2gixysA==}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-entry-cache@11.1.1: file-entry-cache@11.1.1:
resolution: {integrity: sha512-TPVFSDE7q91Dlk1xpFLvFllf8r0HyOMOlnWy7Z2HBku5H3KhIeOGInexrIeg2D64DosVB/JXkrrk6N/7Wriq4A==} resolution: {integrity: sha512-TPVFSDE7q91Dlk1xpFLvFllf8r0HyOMOlnWy7Z2HBku5H3KhIeOGInexrIeg2D64DosVB/JXkrrk6N/7Wriq4A==}
@ -3803,6 +3860,10 @@ packages:
html-void-elements@2.0.1: html-void-elements@2.0.1:
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
htmlparser2@3.10.1: htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
@ -4109,6 +4170,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true hasBin: true
jquery@3.7.1:
resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==}
js-base64@2.6.4: js-base64@2.6.4:
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
@ -4131,6 +4195,9 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true hasBin: true
jsbarcode@3.12.3:
resolution: {integrity: sha512-CuHU9hC6dPsHF5oVFMo8NW76uQVjH4L22CsP4hW+dNnGywJHC/B0ThA1CTDVLnxKLrrpYdicBLnd2xsgTfRnvg==}
jsencrypt@3.5.4: jsencrypt@3.5.4:
resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==} resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==}
@ -4174,6 +4241,9 @@ packages:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0} engines: {'0': node >= 0.2.0}
jspdf@2.5.2:
resolution: {integrity: sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==}
katex@0.16.27: katex@0.16.27:
resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==}
hasBin: true hasBin: true
@ -4603,6 +4673,9 @@ packages:
nth-check@2.1.1: nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
nzh@1.0.14:
resolution: {integrity: sha512-wKgaqCSZdrySvB4RWop5g+v6IDv2IErsT6rjq06Bg0yiT9hiHYZO12GMGx/xweGVLcO2lDjX5RqWD0S/Jy9z5Q==}
object-assign@4.1.1: object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4764,6 +4837,9 @@ packages:
perfect-debounce@1.0.0: perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -4971,6 +5047,9 @@ packages:
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
raf@3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
rd@2.0.1: rd@2.0.1:
resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==} resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==}
@ -5000,6 +5079,9 @@ packages:
regenerate@1.4.2: regenerate@1.4.2:
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
regenerator-runtime@0.14.1: regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
@ -5084,6 +5166,10 @@ packages:
rfdc@1.4.1: rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rgbcolor@1.0.1:
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
engines: {node: '>= 0.8.15'}
rimraf@3.0.2: rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported deprecated: Rimraf versions prior to v4 are no longer supported
@ -5263,6 +5349,14 @@ packages:
resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
socket.io-client@4.8.3:
resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==}
engines: {node: '>=10.0.0'}
socket.io-parser@4.2.6:
resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==}
engines: {node: '>=10.0.0'}
sortablejs@1.14.0: sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
@ -5310,6 +5404,10 @@ packages:
resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'}
static-extend@0.1.2: static-extend@0.1.2:
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -5438,6 +5536,10 @@ packages:
svg-baker@1.7.0: svg-baker@1.7.0:
resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==} resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==}
svg-pathdata@6.0.3:
resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
engines: {node: '>=12.0.0'}
svg-tags@1.0.0: svg-tags@1.0.0:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
@ -5476,6 +5578,9 @@ packages:
resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==}
engines: {node: '>=8'} engines: {node: '>=8'}
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
text-table@0.2.0: text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@ -5690,6 +5795,9 @@ packages:
util-deprecate@1.0.2: util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
uuid@10.0.0: uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true hasBin: true
@ -5801,6 +5909,10 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
vue-plugin-hiprint@0.0.60:
resolution: {integrity: sha512-a5uOMn6Nr4qlYYaVNbQKwRZJa8UcNMTflfi6J430/NDtySJB+5ArE8I8+NLjgVV56x3/qdUBs/GWuZCX5Umv1w==}
engines: {node: '>=16'}
vue-router@4.4.5: vue-router@4.4.5:
resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==} resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==}
peerDependencies: peerDependencies:
@ -5913,6 +6025,18 @@ packages:
resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
ws@8.18.3:
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xml-js@1.6.11: xml-js@1.6.11:
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
hasBin: true hasBin: true
@ -5921,6 +6045,10 @@ packages:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'} engines: {node: '>=12'}
xmlhttprequest-ssl@2.1.2:
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
engines: {node: '>=0.4.0'}
y18n@4.0.3: y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
@ -6757,6 +6885,10 @@ snapshots:
dependencies: dependencies:
'@ibm/telemetry-js': 1.10.2 '@ibm/telemetry-js': 1.10.2
'@claviska/jquery-minicolors@2.3.6(jquery@3.7.1)':
dependencies:
jquery: 3.7.1
'@codemirror/autocomplete@6.20.0': '@codemirror/autocomplete@6.20.0':
dependencies: dependencies:
'@codemirror/language': 6.12.1 '@codemirror/language': 6.12.1
@ -7459,6 +7591,8 @@ snapshots:
'@sinclair/typebox@0.27.8': {} '@sinclair/typebox@0.27.8': {}
'@socket.io/component-emitter@3.1.2': {}
'@swc/core-darwin-arm64@1.15.7': '@swc/core-darwin-arm64@1.15.7':
optional: true optional: true
@ -7673,6 +7807,8 @@ snapshots:
'@types/qs@6.14.0': {} '@types/qs@6.14.0': {}
'@types/raf@3.4.3': {}
'@types/semver@7.7.1': {} '@types/semver@7.7.1': {}
'@types/svgo@2.6.4': '@types/svgo@2.6.4':
@ -8383,6 +8519,11 @@ snapshots:
slate: 0.72.8 slate: 0.72.8
snabbdom: 3.6.3 snabbdom: 3.6.3
'@wtto00/html2canvas@1.4.3':
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
'@xmldom/xmldom@0.8.11': {} '@xmldom/xmldom@0.8.11': {}
'@zxcvbn-ts/core@3.0.4': '@zxcvbn-ts/core@3.0.4':
@ -8558,6 +8699,8 @@ snapshots:
balanced-match@2.0.0: {} balanced-match@2.0.0: {}
base64-arraybuffer@1.0.2: {}
base@0.11.2: base@0.11.2:
dependencies: dependencies:
cache-base: 1.0.1 cache-base: 1.0.1
@ -8660,8 +8803,12 @@ snapshots:
node-releases: 2.0.27 node-releases: 2.0.27
update-browserslist-db: 1.2.3(browserslist@4.28.1) update-browserslist-db: 1.2.3(browserslist@4.28.1)
btoa@1.2.1: {}
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
bwip-js@4.8.0: {}
cac@6.7.14: {} cac@6.7.14: {}
cache-base@1.0.1: cache-base@1.0.1:
@ -8717,6 +8864,17 @@ snapshots:
caniuse-lite@1.0.30001761: {} caniuse-lite@1.0.30001761: {}
canvg@3.0.11:
dependencies:
'@babel/runtime': 7.28.4
'@types/raf': 3.4.3
core-js: 3.47.0
raf: 3.4.1
regenerator-runtime: 0.13.11
rgbcolor: 1.0.1
stackblur-canvas: 2.7.0
svg-pathdata: 6.0.3
chalk@1.1.3: chalk@1.1.3:
dependencies: dependencies:
ansi-styles: 2.2.1 ansi-styles: 2.2.1
@ -8952,6 +9110,10 @@ snapshots:
css-functions-list@3.2.3: {} css-functions-list@3.2.3: {}
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
css-select@4.3.0: css-select@4.3.0:
dependencies: dependencies:
boolbase: 1.0.0 boolbase: 1.0.0
@ -9232,6 +9394,8 @@ snapshots:
detect-libc@1.0.3: detect-libc@1.0.3:
optional: true optional: true
dhtmlx-gantt@9.1.3: {}
diagram-js-direct-editing@3.2.0(diagram-js@14.11.3): diagram-js-direct-editing@3.2.0(diagram-js@14.11.3):
dependencies: dependencies:
diagram-js: 14.11.3 diagram-js: 14.11.3
@ -9322,6 +9486,9 @@ snapshots:
domify@2.0.0: {} domify@2.0.0: {}
dompurify@2.5.9:
optional: true
dompurify@3.3.1: dompurify@3.3.1:
optionalDependencies: optionalDependencies:
'@types/trusted-types': 2.0.7 '@types/trusted-types': 2.0.7
@ -9430,6 +9597,20 @@ snapshots:
emojis-list@3.0.0: {} emojis-list@3.0.0: {}
engine.io-client@6.6.4:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.4.3
engine.io-parser: 5.2.3
ws: 8.18.3
xmlhttprequest-ssl: 2.1.2
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
engine.io-parser@5.2.3: {}
entities@1.1.2: {} entities@1.1.2: {}
entities@2.2.0: {} entities@2.2.0: {}
@ -9816,6 +9997,8 @@ snapshots:
lezer-feel: 1.9.0 lezer-feel: 1.9.0
luxon: 3.7.2 luxon: 3.7.2
fflate@0.8.2: {}
file-entry-cache@11.1.1: file-entry-cache@11.1.1:
dependencies: dependencies:
flat-cache: 6.1.19 flat-cache: 6.1.19
@ -10112,6 +10295,12 @@ snapshots:
html-void-elements@2.0.1: {} html-void-elements@2.0.1: {}
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
optional: true
htmlparser2@3.10.1: htmlparser2@3.10.1:
dependencies: dependencies:
domelementtype: 1.3.1 domelementtype: 1.3.1
@ -10390,6 +10579,8 @@ snapshots:
jiti@2.6.1: {} jiti@2.6.1: {}
jquery@3.7.1: {}
js-base64@2.6.4: {} js-base64@2.6.4: {}
js-beautify@1.15.4: js-beautify@1.15.4:
@ -10410,6 +10601,8 @@ snapshots:
dependencies: dependencies:
argparse: 2.0.1 argparse: 2.0.1
jsbarcode@3.12.3: {}
jsencrypt@3.5.4: {} jsencrypt@3.5.4: {}
jsesc@3.1.0: {} jsesc@3.1.0: {}
@ -10445,6 +10638,18 @@ snapshots:
jsonparse@1.3.1: {} jsonparse@1.3.1: {}
jspdf@2.5.2:
dependencies:
'@babel/runtime': 7.28.4
atob: 2.1.2
btoa: 1.2.1
fflate: 0.8.2
optionalDependencies:
canvg: 3.0.11
core-js: 3.47.0
dompurify: 2.5.9
html2canvas: 1.4.1
katex@0.16.27: katex@0.16.27:
dependencies: dependencies:
commander: 8.3.0 commander: 8.3.0
@ -10896,6 +11101,8 @@ snapshots:
dependencies: dependencies:
boolbase: 1.0.0 boolbase: 1.0.0
nzh@1.0.14: {}
object-assign@4.1.1: {} object-assign@4.1.1: {}
object-copy@0.1.0: object-copy@0.1.0:
@ -11047,6 +11254,8 @@ snapshots:
perfect-debounce@1.0.0: {} perfect-debounce@1.0.0: {}
performance-now@2.1.0: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@2.3.1: {} picomatch@2.3.1: {}
@ -11241,6 +11450,10 @@ snapshots:
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
raf@3.4.1:
dependencies:
performance-now: 2.1.0
rd@2.0.1: rd@2.0.1:
dependencies: dependencies:
'@types/node': 10.17.60 '@types/node': 10.17.60
@ -11276,6 +11489,8 @@ snapshots:
regenerate@1.4.2: {} regenerate@1.4.2: {}
regenerator-runtime@0.13.11: {}
regenerator-runtime@0.14.1: {} regenerator-runtime@0.14.1: {}
regex-not@1.0.2: regex-not@1.0.2:
@ -11349,6 +11564,8 @@ snapshots:
rfdc@1.4.1: {} rfdc@1.4.1: {}
rgbcolor@1.0.1: {}
rimraf@3.0.2: rimraf@3.0.2:
dependencies: dependencies:
glob: 7.2.3 glob: 7.2.3
@ -11591,6 +11808,24 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
socket.io-client@4.8.3:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.4.3
engine.io-client: 6.6.4
socket.io-parser: 4.2.6
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
socket.io-parser@4.2.6:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.4.3
transitivePeerDependencies:
- supports-color
sortablejs@1.14.0: {} sortablejs@1.14.0: {}
sortablejs@1.15.6: {} sortablejs@1.15.6: {}
@ -11628,6 +11863,8 @@ snapshots:
stable@0.1.8: {} stable@0.1.8: {}
stackblur-canvas@2.7.0: {}
static-extend@0.1.2: static-extend@0.1.2:
dependencies: dependencies:
define-property: 0.2.5 define-property: 0.2.5
@ -11813,6 +12050,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
svg-pathdata@6.0.3: {}
svg-tags@1.0.0: {} svg-tags@1.0.0: {}
svg.js@2.7.1: {} svg.js@2.7.1: {}
@ -11857,6 +12096,10 @@ snapshots:
text-extensions@2.4.0: {} text-extensions@2.4.0: {}
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
text-table@0.2.0: {} text-table@0.2.0: {}
through@2.3.8: {} through@2.3.8: {}
@ -12124,6 +12367,10 @@ snapshots:
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
utrie@1.0.2:
dependencies:
base64-arraybuffer: 1.0.2
uuid@10.0.0: {} uuid@10.0.0: {}
vary@1.1.2: {} vary@1.1.2: {}
@ -12257,6 +12504,23 @@ snapshots:
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4
vue: 3.5.12(typescript@5.3.3) vue: 3.5.12(typescript@5.3.3)
vue-plugin-hiprint@0.0.60:
dependencies:
'@claviska/jquery-minicolors': 2.3.6(jquery@3.7.1)
'@wtto00/html2canvas': 1.4.3
bwip-js: 4.8.0
canvg: 3.0.11
jquery: 3.7.1
jsbarcode: 3.12.3
jspdf: 2.5.2
lodash: 4.17.21
nzh: 1.0.14
socket.io-client: 4.8.3
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
vue-router@4.4.5(vue@3.5.12(typescript@5.3.3)): vue-router@4.4.5(vue@3.5.12(typescript@5.3.3)):
dependencies: dependencies:
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4
@ -12400,12 +12664,16 @@ snapshots:
imurmurhash: 0.1.4 imurmurhash: 0.1.4
signal-exit: 4.1.0 signal-exit: 4.1.0
ws@8.18.3: {}
xml-js@1.6.11: xml-js@1.6.11:
dependencies: dependencies:
sax: 1.4.3 sax: 1.4.3
xml-name-validator@4.0.0: {} xml-name-validator@4.0.0: {}
xmlhttprequest-ssl@2.1.2: {}
y18n@4.0.3: {} y18n@4.0.3: {}
y18n@5.0.8: {} y18n@5.0.8: {}

@ -52,6 +52,8 @@ export interface AutocodeRuleVO {
id: number // ID id: number // ID
ruleCode: string // 规则编码 ruleCode: string // 规则编码
ruleName: string // 规则名称 ruleName: string // 规则名称
barcodeType?: number | string
barCodeType?: number | string
ruleDesc: string // 描述 ruleDesc: string // 描述
maxLength: number // 最大长度 maxLength: number // 最大长度
isPadded: string // 是否补齐 isPadded: string // 是否补齐

@ -6,13 +6,26 @@ export interface MoldBrandVO {
code: string // 型号编码 code: string // 型号编码
name: string // 型号名称 name: string // 型号名称
moldType: string // 规格 moldType: string // 规格
productId: number // 产品ID productId?: number // 产品ID
useTime: number // 预期寿命(小时) productName?: string // 产品名称
maintainType: number // 维保模式 productIds?: Array<number> | string // 产品ID列表
maintainTime: number // 维保周期 products?: any[]
images?: string // 图片
version?: string // 版本
status?: number // 状态
currentPosition?: string // 当前位置
currentDevice?: string // 当前设备
machineName?: string // 当前设备
useTime?: number // 预期寿命(小时)
maintainType?: number // 维保模式
maintainTime?: number // 维保周期
moldSize: number // 模具系数 moldSize: number // 模具系数
remark: string // 备注 remark?: string // 备注
isEnable: boolean // 是否启用 isEnable: boolean // 是否启用
isCode?: boolean // 是否自动生成编码
orgType?: string
createTime?: string
qrCodeUrl?: string
} }
export interface MoldBrandTreeVO extends MoldBrandVO { export interface MoldBrandTreeVO extends MoldBrandVO {
@ -26,6 +39,7 @@ export interface MoldBrandTreeVO extends MoldBrandVO {
export interface MoldVO { export interface MoldVO {
id: number // ID id: number // ID
code: string // 模具编码 code: string // 模具编码
isCode?: boolean
name: string // 模具名称 name: string // 模具名称
unitId: number // 单位ID unitId: number // 单位ID
machineId: number // 机台ID machineId: number // 机台ID
@ -34,6 +48,7 @@ export interface MoldVO {
status: number // 状态 status: number // 状态
images: string // 模具图片 images: string // 模具图片
qrcodeUrl?: string qrcodeUrl?: string
templateJson?: string | any
fileUrl?: string fileUrl?: string
remark: string // 备注 remark: string // 备注
isEnable: boolean // 是否启用 isEnable: boolean // 是否启用
@ -125,6 +140,9 @@ export const MoldBrandApi = {
// 获得模具 // 获得模具
getMold: async (id: number) => { getMold: async (id: number) => {
return await request.get({ url: `/erp/mold-brand/mold/get?id=` + id }) return await request.get({ url: `/erp/mold-brand/mold/get?id=` + id })
},
regenerateCode: async (id: number, code: string) => {
return await request.post({ url: `/erp/mold-brand/regenerate-code?id=${id}&code=${encodeURIComponent(code)}` })
}, },
// 根据状态获得模具 // 根据状态获得模具
getMoldListByStatus: async (status: number) => { getMoldListByStatus: async (status: number) => {
@ -155,6 +173,22 @@ export const MoldBrandApi = {
// 获得模具产品 // 获得模具产品
getMoldBrandProduct: async (id: number) => { getMoldBrandProduct: async (id: number) => {
return await request.get({ url: `/erp/mold-brand/mold-brand-product/get?id=` + id }) return await request.get({ url: `/erp/mold-brand/mold-brand-product/get?id=` + id })
},
// ==================== 压网记录 ====================
// 新增压网记录
createPressureNetRecord: async (data: any) => {
return await request.post({ url: `/erp/mold-pressure-net-record/create`, data })
},
// 批量增压网记录
batchCreatePressureNetRecord: async (data: any) => {
return await request.post({ url: `/erp/mold-pressure-net-record/batch-create`, data })
},
// 查询压网记录分页
getPressureNetRecordPage: async (params: any) => {
return await request.get({ url: `/erp/mold-pressure-net-record/page`, params })
} }
} }

@ -8,18 +8,24 @@ export interface ProductCategoryVO {
code: string // 分类编码 code: string // 分类编码
sort: number // 分类排序 sort: number // 分类排序
status: number // 开启状态 status: number // 开启状态
type: number // 类型
} }
// ERP 产品分类 API // ERP 产品分类 API
export const ProductCategoryApi = { export const ProductCategoryApi = {
// 查询产品分类分组树
getProductCategoryGroupTree: async () => {
return await request.get({ url: `/erp/product-category/group-tree` })
},
// 查询产品分类列表 // 查询产品分类列表
getProductCategoryList: async () => { getProductCategoryList: async (type?: number) => {
return await request.get({ url: `/erp/product-category/list` }) return await request.get({ url: `/erp/product-category/list`, params: { type } })
}, },
// 查询产品分类精简列表 // 查询产品分类精简列表
getProductCategorySimpleList: async () => { getProductCategorySimpleList: async (type?: number) => {
return await request.get({ url: `/erp/product-category/simple-list` }) return await request.get({ url: `/erp/product-category/simple-list`, params: { type } })
}, },
// 查询产品分类详情 // 查询产品分类详情

@ -5,6 +5,9 @@ export interface ProductVO {
id: number // 产品编号 id: number // 产品编号
name: string // 产品名称 name: string // 产品名称
barCode: string // 产品条码 barCode: string // 产品条码
isCode?: boolean
qrcodeUrl?: string
templateJson?: string | any
categoryId: number // 产品类型编号 categoryId: number // 产品类型编号
subCategoryId: number // 产品类型子类编号 subCategoryId: number // 产品类型子类编号
subCategoryName: string // 产品类型子类名称 subCategoryName: string // 产品类型子类名称
@ -18,6 +21,12 @@ export interface ProductVO {
purchasePrice: number // 采购价格,单位:元 purchasePrice: number // 采购价格,单位:元
salePrice: number // 销售价格,单位:元 salePrice: number // 销售价格,单位:元
minPrice: number // 最低价格,单位:元 minPrice: number // 最低价格,单位:元
deviceIds?: string // 关联设备ID列表
moldIds?: string // 关联模具ID列表
devices?: { id: number; name: string }[] // 关联设备列表
molds?: { id: number; name: string }[] // 关联模具列表
deviceList?: any[]
moldList?: any[]
} }
// ERP 产品 API // ERP 产品 API
@ -40,8 +49,8 @@ export const ProductApi = {
return await request.get({ url: `/erp/product/simple-list-order` }) return await request.get({ url: `/erp/product/simple-list-order` })
}, },
// 查询所有商品精简列表 // 查询所有商品精简列表
getMesProductSimpleList: async () => { getMesProductSimpleList: async (params?: { categoryType?: number }) => {
return await request.get({ url: `/erp/product/simple-list-product` }) return await request.get({ url: `/erp/product/simple-list-product`, params })
}, },
// 查询备件精简列表 // 查询备件精简列表
getComponentSimpleList: async () => { getComponentSimpleList: async () => {
@ -63,6 +72,13 @@ export const ProductApi = {
return await request.put({ url: `/erp/product/update`, data }) return await request.put({ url: `/erp/product/update`, data })
}, },
// 刷新产品二维码
regenerateCode: async (id: number, code: string) => {
return await request.post({
url: `/erp/product/regenerate-code?id=${id}&code=${encodeURIComponent(code)}`
})
},
// 删除产品 // 删除产品
deleteProduct: async (id: number) => { deleteProduct: async (id: number) => {
return await request.delete({ url: `/erp/product/delete?id=` + id }) return await request.delete({ url: `/erp/product/delete?id=` + id })

@ -56,4 +56,9 @@ export const ProductUnitApi = {
getProductUnitListByFlag: async () => { getProductUnitListByFlag: async () => {
return await request.get({ url: `/erp/product-unit/flag-list`}) return await request.get({ url: `/erp/product-unit/flag-list`})
}, },
// 下载用户导入模板
importUserTemplate: async () => {
return await request.download({ url: '/erp/product-unit/get-import-template' })
}
} }

@ -38,7 +38,7 @@ export const StockInApi = {
// 更新其它入库单的状态 // 更新其它入库单的状态
updateStockInStatus: async (id: number, status: number) => { updateStockInStatus: async (id: number, status: number) => {
return await request.put({ return await request.put({
url: `/erp/stock-in/update-status`, url: `/erp/stock-in/update-mold-status`,
params: { params: {
id, id,
status status

@ -38,7 +38,7 @@ export const StockOutApi = {
// 更新其它出库单的状态 // 更新其它出库单的状态
updateStockOutStatus: async (id: number, status: number) => { updateStockOutStatus: async (id: number, status: number) => {
return await request.put({ return await request.put({
url: `/erp/stock-out/update-status`, url: `/erp/stock-out/update-mold-status`,
params: { params: {
id, id,
status status

@ -0,0 +1,42 @@
import request from '@/config/axios'
// ERP 包装方案 VO
export interface PackagingSchemeVO {
id: number // 包装方案编号
code: string // 方案编码
name: string // 方案名称
packageQuantity: number // 每包数量(件)
palletPackageQuantity: number // 每托包数(包)
palletTotalQuantity: number // 每托总数量(件)
remark: string // 备注
status: number // 状态
createTime: string // 创建时间
}
// ERP 包装方案 API
export const PackagingSchemeApi = {
// 查询包装方案分页
getPackagingSchemePage: async (params: any) => {
return await request.get({ url: `/erp/packaging-scheme/page`, params })
},
// 查询包装方案详情
getPackagingScheme: async (id: number) => {
return await request.get({ url: `/erp/packaging-scheme/get?id=` + id })
},
// 新增包装方案
createPackagingScheme: async (data: PackagingSchemeVO) => {
return await request.post({ url: `/erp/packaging-scheme/create`, data })
},
// 修改包装方案
updatePackagingScheme: async (data: PackagingSchemeVO) => {
return await request.put({ url: `/erp/packaging-scheme/update`, data })
},
// 删除包装方案
deletePackagingScheme: async (id: number) => {
return await request.delete({ url: `/erp/packaging-scheme/delete?id=` + id })
}
}

@ -12,6 +12,33 @@ export interface WarehouseVO {
truckagePrice: number // 搬运费,单位:元 truckagePrice: number // 搬运费,单位:元
status: number // 开启状态 status: number // 开启状态
defaultStatus: boolean // 是否默认 defaultStatus: boolean // 是否默认
warehouseId:number
areaList?: Array<{
id: number
warehouseId: number
areaCode: string
areaName: string
areaSize: number
description: string
status: number
createTime: number
}>
locationList?: Array<{
id: number
warehouseId: number
areaId: number
code: string
name: string
areaSize: number
maxLoadWeight: number
positionX: number
positionY: number
positionZ: number
allowProductMix: boolean
allowBatchMix: boolean
status: number
createTime: number
}>
} }
// ERP 仓库 API // ERP 仓库 API

@ -0,0 +1,34 @@
import request from '@/config/axios'
export interface WarehouseAreaVO {
id: number
warehouseId: number
areaCode: string
areaName: string
areaSize: number
description: string
status: number
createTime: string
}
export const WarehouseAreaApi = {
getWarehouseAreaPage: async (params: any) => {
return await request.get({ url: `/erp/warehouse-area/page`, params })
},
getWarehouseArea: async (id: number) => {
return await request.get({ url: `/erp/warehouse-area/get?id=` + id })
},
createWarehouseArea: async (data: WarehouseAreaVO) => {
return await request.post({ url: `/erp/warehouse-area/create`, data })
},
updateWarehouseArea: async (data: WarehouseAreaVO) => {
return await request.put({ url: `/erp/warehouse-area/update`, data })
},
deleteWarehouseArea: async (id: number) => {
return await request.delete({ url: `/erp/warehouse-area/delete?id=` + id })
}
}

@ -0,0 +1,40 @@
import request from '@/config/axios'
export interface WarehouseLocationVO {
id: number
warehouseId: number
areaId: number
code: string
name: string
areaSize: number
maxLoadWeight: number
positionX: number
positionY: number
positionZ: number
allowProductMix: boolean
allowBatchMix: boolean
status: number
createTime: string
}
export const WarehouseLocationApi = {
getWarehouseLocationPage: async (params: any) => {
return await request.get({ url: `/erp/warehouse-location/page`, params })
},
getWarehouseLocation: async (id: number) => {
return await request.get({ url: `/erp/warehouse-location/get?id=` + id })
},
createWarehouseLocation: async (data: WarehouseLocationVO) => {
return await request.post({ url: `/erp/warehouse-location/create`, data })
},
updateWarehouseLocation: async (data: WarehouseLocationVO) => {
return await request.put({ url: `/erp/warehouse-location/update`, data })
},
deleteWarehouseLocation: async (id: number) => {
return await request.delete({ url: `/erp/warehouse-location/delete?id=` + id })
}
}

@ -27,6 +27,7 @@ export interface DeviceVO {
certificate?: string // 证书 certificate?: string // 证书
secretKey?: string // 秘钥 secretKey?: string // 秘钥
collectionTime?: string | number collectionTime?: string | number
images: string,//图片
} }
export interface DeviceConnectParams { export interface DeviceConnectParams {
@ -86,8 +87,8 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/page`, params }) return await request.get({ url: `/iot/device/page`, params })
}, },
// 查询物联设备 // 查询物联设备
getDeviceList: async () => { getDeviceList: async (params?: any) => {
return await request.get({ url: `/iot/device/deviceList` }) return await request.get({ url: `/iot/device/deviceList`, params })
}, },
// 查询物联设备详情 // 查询物联设备详情
getDevice: async (id: number) => { getDevice: async (id: number) => {
@ -99,6 +100,12 @@ export const DeviceApi = {
getDeviceList2ByNoUsed: async (id: number) => { getDeviceList2ByNoUsed: async (id: number) => {
return await request.get({ url: `/iot/device/noUsedlist2?id=` + id }) return await request.get({ url: `/iot/device/noUsedlist2?id=` + id })
}, },
getAvailableList: async () => {
return await request.get({ url: `/iot/device/available-list` })
},
getAvailableListPage: async (params: any) => {
return await request.get({ url: `/iot/device/available-page` , params})
},
// 新增物联设备 // 新增物联设备
createDevice: async (data: DeviceVO) => { createDevice: async (data: DeviceVO) => {
return await request.post({ url: `/iot/device/create`, data }) return await request.post({ url: `/iot/device/create`, data })
@ -144,6 +151,10 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/singleDevice`, params }) return await request.get({ url: `/iot/device/singleDevice`, params })
}, },
getSingleDeviceFrom: async (params: SingleDeviceParams) => {
return await request.get({ url: `/iot/device/singleDeviceFrom`, params })
},
getHistoryRecord: async (params: HistoryRecordParams) => { getHistoryRecord: async (params: HistoryRecordParams) => {
return await request.get({ return await request.get({
url: `/iot/device/historyRecord`, url: `/iot/device/historyRecord`,
@ -152,6 +163,15 @@ export const DeviceApi = {
}) })
}, },
getHistoryAnalyse: async (params: HistoryRecordParams) => {
return await request.get({
url: `/iot/device/historyAnalyse`,
params,
paramsSerializer: (p) => qs.stringify(p, { allowDots: true, arrayFormat: 'repeat' })
})
},
devicePointList: async () => { devicePointList: async () => {
return await request.get({ url: `/iot/device/devicePointList` }) return await request.get({ url: `/iot/device/devicePointList` })
}, },
@ -167,6 +187,10 @@ export const DeviceApi = {
getDeviceAttributePage: async (params) => { getDeviceAttributePage: async (params) => {
return await request.get({ url: `/iot/device/device-attribute/page`, params }) return await request.get({ url: `/iot/device/device-attribute/page`, params })
}, },
// 获得设备属性按类型分租
getDeviceAttributeGroupList: async (params) => {
return await request.get({ url: `/iot/device/device-attribute/groupList`, params })
},
// 获得设备属性列表 // 获得设备属性列表
getDeviceAttributeList: async (deviceId: number | string) => { getDeviceAttributeList: async (deviceId: number | string) => {
return await request.get({ url: `/iot/device/device-attribute/list?deviceId=` + deviceId }) return await request.get({ url: `/iot/device/device-attribute/list?deviceId=` + deviceId })

@ -0,0 +1,62 @@
import request from '@/config/axios'
export interface DeviceOperationOverviewParams {
ids?: string
startTime?: string
endTime?: string
timelinePageNo?: number
timelinePageSize?: number
}
export interface DeviceOperationOverviewMetricVO {
key: string
icon: string
value: number
unit: string
change: number
}
export interface DeviceOperationOverviewHourlyStatusVO {
hour: string
running: number
standby: number
fault: number
offline: number
}
export interface DeviceOperationOverviewSummaryVO {
status: 'running' | 'standby' | 'fault' | 'offline'
percent: number
hours: number
}
export interface DeviceOperationOverviewTimelineSegmentVO {
status: 'running' | 'standby' | 'fault' | 'offline'
startHour: number
endHour: number
}
export interface DeviceOperationOverviewTimelineRowVO {
id: string
name: string
utilizationRate: number
segments: DeviceOperationOverviewTimelineSegmentVO[]
}
export interface DeviceOperationOverviewRespVO {
metrics: DeviceOperationOverviewMetricVO[]
hourlyStatus: DeviceOperationOverviewHourlyStatusVO[]
summary: DeviceOperationOverviewSummaryVO[]
summaryTotalHours: number
timelineRows: DeviceOperationOverviewTimelineRowVO[]
totalDevices: number
}
export const DeviceOperationOverviewApi = {
getRunOverview: async (params: DeviceOperationOverviewParams) => {
return await request.get<DeviceOperationOverviewRespVO>({
url: `/iot/device-operation-record/runOverview`,
params
})
}
}

@ -42,6 +42,10 @@ export const DeviceAttributeTypeApi = {
}, },
// 获得采集点分类列表 // 获得采集点分类列表
importTemplate: async () => {
return await request.download({ url: `/iot/device-attribute-type/get-import-template` })
},
getDeviceAttributeTypeList: async () => { getDeviceAttributeTypeList: async () => {
return await request.get({ url: `/iot/device-attribute-type/list` }) return await request.get({ url: `/iot/device-attribute-type/list` })
}, },

@ -3,11 +3,28 @@ import request from '@/config/axios'
// 报工 VO // 报工 VO
export interface BaogongRecordVO { export interface BaogongRecordVO {
id: number // id id: number // id
planId: number // 关联计划id planId: number // 关联计划 id
num: number // 派工数量 num: number // 派工数量
baogongTime: Date // 派工时间 baogongTime: Date // 派工时间
} }
// 报工统计 VO
export interface BaogongRecordStatVO {
id: number // ID
taskCode: string // 任务单编码
planCode: string // 计划单编码
employeeId: string // 员工 ID
employeeName: string // 员工姓名
productName: string // 产品名称
productCode: string // 产品编码
baogongNum: number // 报工数量
passNum: number // 合格数量
noPassNum: number // 不合格数量
passRate: number // 合格率 (%)
reason: string // 原因
baogongTime: Date // 报工时间
}
// 报工 API // 报工 API
export const BaogongRecordApi = { export const BaogongRecordApi = {
// 查询报工分页 // 查询报工分页
@ -15,6 +32,11 @@ export const BaogongRecordApi = {
return await request.get({ url: `/mes/baogong-record/page`, params }) return await request.get({ url: `/mes/baogong-record/page`, params })
}, },
// 查询报工统计分页
getBaogongRecordStatPage: async (params: any) => {
return await request.get({ url: `/mes/baogong-record/stat-page`, params })
},
// 查询报工详情 // 查询报工详情
getBaogongRecord: async (id: number) => { getBaogongRecord: async (id: number) => {
return await request.get({ url: `/mes/baogong-record/get?id=` + id }) return await request.get({ url: `/mes/baogong-record/get?id=` + id })
@ -40,4 +62,13 @@ export const BaogongRecordApi = {
return await request.download({ url: `/mes/baogong-record/export-excel`, params }) return await request.download({ url: `/mes/baogong-record/export-excel`, params })
}, },
// 导出报工统计 Excel
exportBaogongRecordStat: async (params) => {
return await request.download({ url: `/mes/baogong-record/export-stat-excel`, params })
},
// 导出报工统计 Excel (新)
statExportExcel: async (params) => {
return await request.download({ url: `/mes/baogong-record/stat-export-excel`, params })
},
} }

@ -49,5 +49,9 @@ export const BomApi = {
// 获得产品BOM明细列表 // 获得产品BOM明细列表
getBomDetailListByBomId: async (bomId) => { getBomDetailListByBomId: async (bomId) => {
return await request.get({ url: `/mes/bom/bom-detail/list-by-bom-id?bomId=` + bomId }) return await request.get({ url: `/mes/bom/bom-detail/list-by-bom-id?bomId=` + bomId })
},
getBomByProductId: async (productId: number) => {
return await request.get({ url: `/mes/bom/getByProductId`, params: { productId } })
} }
} }

@ -3,11 +3,15 @@ import request from '@/config/axios'
export interface CriticalComponentVO { export interface CriticalComponentVO {
id: number id: number
code: string code: string
isCode?: boolean
name: string name: string
description?: string description?: string
count?: number count?: number
remark?: string remark?: string
qrcodeUrl?: string
templateJson?: string | any
createTime?: string createTime?: string
images?: string
} }
export const CriticalComponentApi = { export const CriticalComponentApi = {
@ -17,6 +21,9 @@ export const CriticalComponentApi = {
getCriticalComponentList: async () => { getCriticalComponentList: async () => {
return await request.get({ url: `/mes/critical-component/list`, }) return await request.get({ url: `/mes/critical-component/list`, })
}, },
getCriticalComponent: async (id: number) => {
return await request.get({ url: `/mes/critical-component/get?id=` + id })
},
createCriticalComponent: async (data: Partial<CriticalComponentVO>) => { createCriticalComponent: async (data: Partial<CriticalComponentVO>) => {
return await request.post({ url: `/mes/critical-component/create`, data }) return await request.post({ url: `/mes/critical-component/create`, data })
}, },
@ -25,6 +32,12 @@ export const CriticalComponentApi = {
return await request.put({ url: `/mes/critical-component/update`, data }) return await request.put({ url: `/mes/critical-component/update`, data })
}, },
regenerateCode: async (id: number, code: string) => {
return await request.post({
url: `/mes/critical-component/regenerate-code?id=${id}&code=${encodeURIComponent(code)}`
})
},
deleteCriticalComponent: async (ids: string) => { deleteCriticalComponent: async (ids: string) => {
return await request.delete({ url: `/mes/critical-component/delete?ids=` + ids }) return await request.delete({ url: `/mes/critical-component/delete?ids=` + ids })
}, },

@ -0,0 +1,47 @@
import request from '@/config/axios'
export interface CapacityReportVO {
id: number
deviceCode: string
deviceName: string
typeName: string
deviceStatus: number
ratedCapacity: number
reportCapacity: number
actualCapacity: number
workshopName: string
}
export interface CapacityReportQuery {
pageNo: number
pageSize: number
deviceCode?: string
deviceName?: string
deviceType?: string
deviceStatus?: string
workshop?: string
ids?: string
}
export const DeviceLedgerApi = {
/**
*
*/
getCapacityReportPage: async (params: CapacityReportQuery) => {
return await request.get({ url: '/mes/device-ledger/capacity-report/page', params })
},
/**
*
*/
exportCapacityReport: async (params: { ids?: string }) => {
return await request.download({ url: '/mes/device-ledger/capacity-report/export-excel', params })
},
/**
*
*/
updateDeviceLedger: async (data: { id: number; deviceStatus: number }) => {
return await request.put({ url: '/mes/device-ledger/update', data })
}
}

@ -6,12 +6,17 @@ import { MoldVO } from '@/api/erp/mold'
export interface DeviceLedgerVO { export interface DeviceLedgerVO {
id: number // id id: number // id
deviceCode: string // 设备编号 deviceCode: string // 设备编号
isCode?: boolean
deviceName: string // 设备名称 deviceName: string // 设备名称
deviceStatus: number // 设备状态 (0-正常, 1-停用, 2-维修, 3-报废) deviceStatus: number // 设备状态 (0-正常, 1-停用, 2-维修, 3-报废)
deviceBrand: string // 设备品牌 deviceBrand: string // 设备品牌
sn?: string // 序列号
deviceModel: string // 设备型号 deviceModel: string // 设备型号
deviceSpec: string // 设备规格 deviceSpec: string // 设备规格
deviceType: string | number // 设备类型 deviceType: string | number // 设备类型
deviceLine?: string | number // 设备产线
topCategoryId?: number // 顶级分类 id
topCategoryName?: string // 顶级分类名称
deviceTypeName?: string // 设备类型名称 deviceTypeName?: string // 设备类型名称
supplier: string // 供应商 supplier: string // 供应商
workshop: string // 所属车间 workshop: string // 所属车间
@ -20,10 +25,16 @@ export interface DeviceLedgerVO {
useDept?: string // 使用部门 useDept?: string // 使用部门
deviceManager: string // 设备负责人 deviceManager: string // 设备负责人
productionDate: string | number | Date // 设备生产日期 productionDate: string | number | Date // 设备生产日期
outgoingTime?: string | number | Date // 出厂日期
factoryEntryDate: string | number | Date // 设备入厂日期 factoryEntryDate: string | number | Date // 设备入厂日期
deviceRemark: string // 设备备注 deviceRemark: string // 设备备注
remark: string // 备注 remark: string // 备注
fileUrl?: string // 附件下载 fileUrl?: string // 附件下载
qrcodeUrl?: string
templateJson?: string | any
isSchedueld?: number
isScheduled?: number
ratedCapacity?: number
componentId?: string // 关键件ids集合 componentId?: string // 关键件ids集合
componentList?: CriticalComponentVO[] componentList?: CriticalComponentVO[]
beijianId?: string // 备件ids集合 beijianId?: string // 备件ids集合
@ -37,6 +48,7 @@ export interface DeviceLedgerVO {
createTime?: string | number | Date createTime?: string | number | Date
updateTime?: string | number | Date updateTime?: string | number | Date
sort: number // 排序 sort: number // 排序
images?: string // 图片
} }
// 设备类型 API // 设备类型 API
@ -73,6 +85,10 @@ export const DeviceLedgerApi = {
return await request.put({ url: `/mes/device-ledger/update`, data }) return await request.put({ url: `/mes/device-ledger/update`, data })
}, },
regenerateCode: async (id: number, code: string) => {
return await request.post({ url: `/mes/device-ledger/regenerate-code?id=${id}&code=${encodeURIComponent(code)}` })
},
// 删除设备类型 // 删除设备类型
deleteDeviceLedger: async (ids: string) => { deleteDeviceLedger: async (ids: string) => {
return await request.delete({ url: `/mes/device-ledger/delete?ids=` + ids }) return await request.delete({ url: `/mes/device-ledger/delete?ids=` + ids })
@ -88,5 +104,19 @@ export const DeviceLedgerApi = {
exportMold: async (params) => { exportMold: async (params) => {
return await request.download({ url: `/mes/device-ledger/export-mold`, params }) return await request.download({ url: `/mes/device-ledger/export-mold`, params })
} },
// 查询设备关键件明细
getDeviceCriticalComponent: async (id: number) => {
return await request.get({ url: `/mes/device-critical-component/getDetails?deviceId=` + id })
},
exportDeviceComponent: async (params: any) => {
return await request.download({ url: `/mes/device-critical-component/export-excel`, params })
},
// 删除设备关键件明细
deleteDeviceCriticalComponent: async (id: string) => {
return await request.delete({ url: `/mes/device-critical-component/delete?id=` + id })
},
} }

@ -0,0 +1,45 @@
import request from '@/config/axios'
export interface DeviceLineVO {
id: number
code: string
isCode?: boolean
qrcodeUrl?: string
name: string
remark: string
sort: number
parentId: number
parentChain: string
createTime?: string
}
export interface DeviceLineTreeVO extends DeviceLineVO {
children?: DeviceLineTreeVO[]
leaf?: boolean
}
export const DeviceLineApi = {
getDeviceLine: async (id: number) => {
return await request.get({ url: `/mes/device-line/get?id=` + id })
},
getDeviceLineTree: async () => {
return await request.get({ url: `/mes/device-line/tree` })
},
regenerateCode: async (id: number, code: string) => {
return await request.post({ url: `/mes/device-line/regenerate-code?id=${id}&code=${encodeURIComponent(code)}` })
},
createDeviceLine: async (data: DeviceLineVO) => {
return await request.post({ url: `/mes/device-line/create`, data })
},
updateDeviceLine: async (data: DeviceLineVO) => {
return await request.put({ url: `/mes/device-line/update`, data })
},
deleteDeviceLine: async (id: number) => {
return await request.delete({ url: `/mes/device-line/delete?id=` + id })
}
}

@ -15,8 +15,20 @@ export interface DvRepairVO {
finishDate: Date // 完成日期 finishDate: Date // 完成日期
confirmDate: Date // 验收日期 confirmDate: Date // 验收日期
repairResult: string // 维修结果 repairResult: string // 维修结果
repairStatus?: string // 维修结果状态
faultPhenomenon?: string // 故障现象
faultDescription?: string // 故障描述
faultImages?: string // 故障图片,逗号分隔
downtimeDuration?: string // 停机时长
faultReason?: string // 故障原因
handlingMeasures?: string // 处理措施
replacementParts?: string // 更换配件
repairContent?: string // 维修内容
repairedImages?: string // 维修后图片,逗号分隔
acceptedBy: string // 维修人员 acceptedBy: string // 维修人员
confirmBy: string // 验收人员 confirmBy: string // 验收人员
faultLevel?: string // 故障等级
isShutdown?: boolean // 是否停机
status: string // 单据状态 status: string // 单据状态
remark: string // 备注 remark: string // 备注
} }

@ -11,6 +11,7 @@ export interface DvSubjectVO {
isEnable: string // 是否启用 isEnable: string // 是否启用
inspectionMethod: string // 检验方式 inspectionMethod: string // 检验方式
valueType: string // 值类型 valueType: string // 值类型
jobType:string // 作业类型
judgmentCriteria: string // 判定基准 judgmentCriteria: string // 判定基准
creator?: string // 创建人 creator?: string // 创建人
createTime?: string | number | Date // 创建时间 createTime?: string | number | Date // 创建时间

@ -42,6 +42,61 @@ export interface EnergyDeviceDataRecordVO {
latestDataTime?: string latestDataTime?: string
} }
export interface EnergyOverviewMetricVO {
key: string
label: string
value: string
unit?: string
subLabel?: string
subValue?: string
change?: string
down?: boolean
}
export interface EnergyOverviewTrendVO {
unit?: string
xAxis: string[]
data: string[]
}
export interface EnergyOverviewRegionItemVO {
name: string
value: string
percent: string
}
export interface EnergyOverviewRegionVO {
unit?: string
totalValue: string
items: EnergyOverviewRegionItemVO[]
}
export interface EnergyOverviewRankVO {
id: number
name: string
region: string
value: string
}
export interface EnergyOverviewDetailVO {
id: number
name: string
energyType: string
region: string
value: string
startTime: string
endTime: string
}
export interface EnergyOverviewRespVO {
metrics: EnergyOverviewMetricVO[]
trendChart: EnergyOverviewTrendVO
regionChart: EnergyOverviewRegionVO
rankList: EnergyOverviewRankVO[]
total: number
detailList: EnergyOverviewDetailVO[]
}
// 能源设备 API // 能源设备 API
export const EnergyDeviceApi = { export const EnergyDeviceApi = {
// 查询能源设备分页 // 查询能源设备分页
@ -66,6 +121,10 @@ export const EnergyDeviceApi = {
return await request.get({ url: `/mes/energy-device/queryDataRecords`, params }) return await request.get({ url: `/mes/energy-device/queryDataRecords`, params })
}, },
queryOverviewData: async (params: any) => {
return await request.get({ url: `/mes/energy-device/queryOverviewData`, params })
},
exportQueryDataRecords: async (params: any) => { exportQueryDataRecords: async (params: any) => {
return await request.download({ url: `/mes/energy-device/record-export-excel`, params }) return await request.download({ url: `/mes/energy-device/record-export-excel`, params })
}, },

@ -4,10 +4,12 @@ import request from '@/config/axios'
export interface MoldOperateVO { export interface MoldOperateVO {
id: number // ID id: number // ID
operateType: string // 操作类型 operateType: string // 操作类型
moldId: number | string // 关联模具id(上模/下模剩余模具,支持逗号分隔) moldId: number // 关联模具id
deviceId: number // 关联设备id deviceId: number // 关联设备id
lineId: number // 产线
operateTime: string // 操作时间
operatorId: number // 操作人
remark: string // 备注 remark: string // 备注
lowerMoldId?: number | string // 下模的模具id
} }
// 模具上下模 API // 模具上下模 API

@ -1,6 +1,36 @@
import request from '@/config/axios' import request from '@/config/axios'
import {ItemRequisitionVO} from "@/api/mes/itemrequisition"; import {ItemRequisitionVO} from "@/api/mes/itemrequisition";
// 计划记录状态枚举
export enum OperateStatusEnum {
SCHEDULED = '1', // 已排产
PAUSED = '3', // 暂停
PENDING_WAREHOUSE = '4', // 待入库
WAREHOUSED = '5', // 已入库
STARTED = '8' // 已开工
}
// 计划记录状态映射
export const OPERATE_STATUS_MAP: Record<string, string> = {
'1': '已排产',
'3': '暂停',
'4': '待入库',
'5': '已入库',
'8': '已开工'
}
// 计划记录 VO
export interface PlanRecordVO {
id: number // ID
taskId: number // 任务ID
planId: number // 计划ID
operateStatus: string // 操作状态 ('1' | '3' | '4' | '5' | '8')
operateTime: number // 操作时间戳(毫秒)
remark: string // 备注
isEnable: boolean // 是否启用
createTime: number // 创建时间戳
}
// 生产计划 VO // 生产计划 VO
export interface PlanVO { export interface PlanVO {
id: number // ID id: number // ID
@ -26,6 +56,27 @@ export interface PlanVO {
feedingPipelineName: string feedingPipelineName: string
wangongNumber: number wangongNumber: number
passRate: number passRate: number
passNumber?: number // 合格数量
noPassNumber?: number // 不合格数量
deviceName?: string // 设备名称
planRecordList?: PlanRecordVO[] // 计划记录列表
}
export interface DevicePlanGanttPlanVO {
planId: number
planStartTime: string
planEndTime: string
latestStartTime: string
planNumber: number
productName: string
deviceName: string
}
export interface DevicePlanGanttRespVO {
deviceId: number
deviceName: string
deviceCode: string
plans: DevicePlanGanttPlanVO[]
} }
// 生产计划 API // 生产计划 API
@ -63,6 +114,9 @@ export const PlanApi = {
saveBatchPlan: async (data: any) => { saveBatchPlan: async (data: any) => {
return await request.post({ url: `/mes/plan/saveBatchPlan`, data }) return await request.post({ url: `/mes/plan/saveBatchPlan`, data })
}, },
createBatch: async (data:any) => {
return await request.post({ url: `/mes/plan/create-batch`, data })
},
// 派工生产计划 // 派工生产计划
arrangePlan: async (data: ItemRequisitionVO) => { arrangePlan: async (data: ItemRequisitionVO) => {
return await request.put({ url: `/mes/plan/paigong`, data }) return await request.put({ url: `/mes/plan/paigong`, data })
@ -116,5 +170,16 @@ export const PlanApi = {
}, },
getLastSevenDaysCompletedCount: async (params?: any) => { getLastSevenDaysCompletedCount: async (params?: any) => {
return await request.get({ url: `/mes/plan/getLastSevenDaysCompletedCount`, params }) return await request.get({ url: `/mes/plan/getLastSevenDaysCompletedCount`, params })
},
getGanttByDevice: async (params: { startTime: string; endTime: string }) => {
return await request.get<DevicePlanGanttRespVO[]>({ url: `/mes/plan/gantt-by-device`, params })
},
getPlanPageByTask: async (params: any) => {
return await request.get({ url: `/mes/plan/page-by-task`, params })
},
getProductCapacityPage: async (params: any) => {
return await request.get({ url: `/mes/plan/product-capacity-page`, params })
} }
} }

@ -0,0 +1,34 @@
import request from '@/config/axios'
export interface PrintTemplateVO {
id: number
templateCode: string
templateName: string
templateType: number
templateBizType: number
templateJson: string
remark: string
isEnable: boolean
createTime: string
}
export const PrintTemplateApi = {
getPrintTemplatePage: async (params: any) => {
return await request.get({ url: `/mes/print-template/page`, params })
},
getPrintTemplate: async (id: number) => {
return await request.get({ url: `/mes/print-template/get?id=` + id })
},
createPrintTemplate: async (data: PrintTemplateVO) => {
return await request.post({ url: `/mes/print-template/create`, data })
},
updatePrintTemplate: async (data: PrintTemplateVO) => {
return await request.put({ url: `/mes/print-template/update`, data })
},
deletePrintTemplate: async (id: number) => {
return await request.delete({ url: `/mes/print-template/delete?id=` + id })
},
exportPrintTemplate: async (params) => {
return await request.download({ url: `/mes/print-template/export-excel`, params })
},
}

@ -10,7 +10,7 @@ export interface RepairItemVO {
inspectionMethod?: string inspectionMethod?: string
valueType?: string valueType?: string
judgmentCriteria?: string judgmentCriteria?: string
isEnable: string isEnable: boolean
deviceName?: string deviceName?: string
componentName?: string componentName?: string
creatorName?: string creatorName?: string

@ -6,6 +6,7 @@ export interface TaskVO {
code: string // 编码 code: string // 编码
orderDate: Date // 下达日期 orderDate: Date // 下达日期
deliveryDate: Date // 交货日期 deliveryDate: Date // 交货日期
isUrgent: string // 是否急单0-否 1-是
status: number // 状态 status: number // 状态
processInstanceId: string // 流程实例的编号 processInstanceId: string // 流程实例的编号
remark: string // 备注 remark: string // 备注
@ -155,4 +156,8 @@ export const TaskApi = {
generatePlanByTaskDetail: async (params) => { generatePlanByTaskDetail: async (params) => {
return await request.get({ url: `/mes/task/generate-plan/byTaskDetail`, params }) return await request.get({ url: `/mes/task/generate-plan/byTaskDetail`, params })
}, },
// 一键排产
oneClickSchedule: async (data: any) => {
return await request.post({ url: `/mes/task/one-click-schedule`, data })
},
} }

@ -44,6 +44,10 @@ export const ZjTaskApi = {
return await request.get({ url: `/mes/zj-task/list`, params }) return await request.get({ url: `/mes/zj-task/list`, params })
}, },
getZjTaskPageByTicket: async (params: any) => {
return await request.get({ url: `/mes/zj-task/page-by-ticket`, params })
},
createZjTask: async (data: ZjTaskVO) => { createZjTask: async (data: ZjTaskVO) => {
return await request.post({ url: `/mes/zj-task/create`, data }) return await request.post({ url: `/mes/zj-task/create`, data })
}, },

@ -62,6 +62,10 @@ export const MoldRepairApi = {
return await request.get({ url: `/mes/mold-repair/getRepairListByMoldId`, params }) return await request.get({ url: `/mes/mold-repair/getRepairListByMoldId`, params })
}, },
getMoldRepairList: async (params: any) => {
return await request.get({ url: `/mes/mold-repair/list`, params })
},
exportRepairExcel: async (params: { moldId: number; startTime?: string; endTime?: string }) => { exportRepairExcel: async (params: { moldId: number; startTime?: string; endTime?: string }) => {
return await request.download({ url: `/mes/mold-repair/export-repair-excel`, params }) return await request.download({ url: `/mes/mold-repair/export-repair-excel`, params })
} }

@ -29,6 +29,11 @@ export const TaskManagementApi = {
return await request.post({ url: `/mes/mold-task-management/createMoldTicket`, params: { id } }) return await request.post({ url: `/mes/mold-task-management/createMoldTicket`, params: { id } })
}, },
// 直接创建模具任务工单(点检/保养)
createMoldTicketDirect: async (data: any) => {
return await request.post({ url: `/mes/mold-task-management/createMoldTicketDirect`, data })
},
createTaskManagement: async (data: TaskManagementVO) => { createTaskManagement: async (data: TaskManagementVO) => {
return await request.post({ url: `/mes/mold-task-management/create`, data }) return await request.post({ url: `/mes/mold-task-management/create`, data })
}, },

@ -16,12 +16,14 @@ export interface MenuVO {
visible: boolean visible: boolean
keepAlive: boolean keepAlive: boolean
alwaysShow?: boolean alwaysShow?: boolean
clientType?: number
terminalType?: number
createTime: Date createTime: Date
} }
// 查询菜单(精简)列表 // 查询菜单(精简)列表
export const getSimpleMenusList = () => { export const getSimpleMenusList = (clientType?: number) => {
return request.get({ url: '/system/menu/simple-list' }) return request.get({ url: '/system/menu/simple-list', params: { clientType } })
} }
// 查询菜单列表 // 查询菜单列表
@ -35,12 +37,12 @@ export const getMenu = (id: number) => {
} }
// 新增菜单 // 新增菜单
export const createMenu = (data: MenuVO) => { export const createMenu = (data: Partial<MenuVO>) => {
return request.post({ url: '/system/menu/create', data }) return request.post({ url: '/system/menu/create', data })
} }
// 修改菜单 // 修改菜单
export const updateMenu = (data: MenuVO) => { export const updateMenu = (data: Partial<MenuVO>) => {
return request.put({ url: '/system/menu/update', data }) return request.put({ url: '/system/menu/update', data })
} }

@ -17,8 +17,11 @@ export interface PermissionAssignRoleDataScopeReqVO {
} }
// 查询角色拥有的菜单权限 // 查询角色拥有的菜单权限
export const getRoleMenuList = async (roleId: number) => { export const getRoleMenuList = async (roleId: number, clientType?: number) => {
return await request.get({ url: '/system/permission/list-role-menus?roleId=' + roleId }) return await request.get({
url: '/system/permission/list-role-menus',
params: { roleId, clientType }
})
} }
// 赋予角色菜单权限 // 赋予角色菜单权限

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -9,13 +9,14 @@ const props = defineProps({
modelValue: propTypes.bool.def(false), modelValue: propTypes.bool.def(false),
title: propTypes.string.def('Dialog'), title: propTypes.string.def('Dialog'),
fullscreen: propTypes.bool.def(true), fullscreen: propTypes.bool.def(true),
initialFullscreen: propTypes.bool.def(false),
width: propTypes.oneOfType([String, Number]).def('40%'), width: propTypes.oneOfType([String, Number]).def('40%'),
scroll: propTypes.bool.def(false), // maxHeight scroll: propTypes.bool.def(false),
maxHeight: propTypes.oneOfType([String, Number]).def('400px') maxHeight: propTypes.oneOfType([String, Number]).def('400px')
}) })
const getBindValue = computed(() => { const getBindValue = computed(() => {
const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody'] const delArr: string[] = ['fullscreen', 'initialFullscreen', 'title', 'maxHeight', 'appendToBody']
const attrs = useAttrs() const attrs = useAttrs()
const obj = { ...attrs, ...props } const obj = { ...attrs, ...props }
for (const key in obj) { for (const key in obj) {
@ -26,7 +27,7 @@ const getBindValue = computed(() => {
return obj return obj
}) })
const isFullscreen = ref(false) const isFullscreen = ref(props.initialFullscreen)
const toggleFull = () => { const toggleFull = () => {
isFullscreen.value = !unref(isFullscreen) isFullscreen.value = !unref(isFullscreen)

@ -4,7 +4,8 @@ import { isHexColor } from '@/utils/color'
import { ElTag } from 'element-plus' import { ElTag } from 'element-plus'
import { DictDataType, getDictOptions } from '@/utils/dict' import { DictDataType, getDictOptions } from '@/utils/dict'
import { isArray, isBoolean, isNumber, isString } from '@/utils/is' import { isArray, isBoolean, isNumber, isString } from '@/utils/is'
import {useLocaleStoreWithOut} from "@/store/modules/locale";
const localeStore = useLocaleStoreWithOut()
export default defineComponent({ export default defineComponent({
name: 'DictTag', name: 'DictTag',
props: { props: {
@ -68,6 +69,7 @@ export default defineComponent({
if (dict.colorType + '' === 'primary' || dict.colorType + '' === 'default') { if (dict.colorType + '' === 'primary' || dict.colorType + '' === 'default') {
dict.colorType = '' dict.colorType = ''
} }
const label = localeStore.getCurrentLocale?.lang === 'en' ? (dict.labelEn || dict.label) : dict.label
return ( return (
// //
<ElTag <ElTag
@ -76,7 +78,7 @@ export default defineComponent({
color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''} color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
disableTransitions={true} disableTransitions={true}
> >
{dict?.label} {label}
</ElTag> </ElTag>
) )
} }

@ -0,0 +1,397 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="92%" :fullscreen="false" top="4vh">
<div class="hiprint-preview">
<div class="hiprint-toolbar">
<div class="hiprint-paper">
<el-button
v-for="(value, type) in paperTypes"
:key="type"
size="small"
:type="curPaperType === type ? 'primary' : 'default'"
@click="setPaper(type, value)"
>
{{ type }}
</el-button>
<el-popover placement="bottom-start" :width="260" trigger="click" v-model:visible="paperPopVisible">
<template #reference>
<el-button size="small" :type="curPaperType === 'other' ? 'primary' : 'default'">自定义纸张</el-button>
</template>
<div class="paper-pop">
<div class="paper-pop-title">设置纸张宽高(mm)</div>
<div class="paper-pop-form">
<el-input-number v-model="paperWidth" :precision="1" :min="1" controls-position="right" />
<span>x</span>
<el-input-number v-model="paperHeight" :precision="1" :min="1" controls-position="right" />
</div>
<el-button class="mt-8px" size="small" type="primary" @click="setPaperOther"></el-button>
</div>
</el-popover>
</div>
<div class="hiprint-zoom">
<el-button size="small" @click="changeScale(false)">
<Icon icon="ep:zoom-out" />
</el-button>
<div class="zoom-value">{{ (scaleValue * 100).toFixed(0) }}%</div>
<el-button size="small" @click="changeScale(true)">
<Icon icon="ep:zoom-in" />
</el-button>
</div>
<el-button type="primary" size="small" @click="handlePrint">
<Icon icon="ep:printer" class="mr-4px" />
浏览器打印
</el-button>
</div>
<div class="hiprint-body">
<div class="hiprint-left">
<div class="hiprint-title">基础元素</div>
<div :id="dragContainerId" class="hiprint-drag-wrap">
<div v-for="item in baseElements" :key="item.tid" class="ep-draggable-item hiprint-item" :tid="item.tid">
<span>{{ item.label }}</span>
</div>
</div>
</div>
<div class="hiprint-center">
<div :id="designerContainerId"></div>
</div>
<div class="hiprint-right">
<div :id="settingContainerId"></div>
</div>
</div>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { defaultElementTypeProvider, hiprint } from 'vue-plugin-hiprint'
interface HiprintPreviewOpenOptions {
title?: string
printData?: Record<string, any> | Record<string, any>[]
withDefaultQrcodeLayout?: boolean
templateJson?: Record<string, any>
paperSize?: {
width: number
height: number
}
}
const message = useMessage()
const baseElements = [
{ tid: 'defaultModule.text', label: '文本' },
{ tid: 'defaultModule.image', label: '图片' },
{ tid: 'defaultModule.longText', label: '长文' },
{ tid: 'defaultModule.table', label: '表格' },
{ tid: 'defaultModule.html', label: 'HTML' },
{ tid: 'defaultModule.hline', label: '横线' },
{ tid: 'defaultModule.vline', label: '竖线' },
{ tid: 'defaultModule.rect', label: '矩形' },
{ tid: 'defaultModule.oval', label: '圆形' }
]
const dialogVisible = ref(false)
const dialogTitle = ref('打印预览')
const currentPrintData = ref<Record<string, any> | Record<string, any>[]>({})
const withDefaultQrcodeLayout = ref(false)
const currentTemplateJson = ref<Record<string, any> | undefined>(undefined)
const instanceId = `hiprint-preview-${Math.random().toString(36).slice(2)}`
const dragContainerId = `${instanceId}-drag`
const designerContainerId = `${instanceId}-designer`
const settingContainerId = `${instanceId}-setting`
const paperTypes = {
A3: { width: 420, height: 296.6 },
A4: { width: 210, height: 296.6 },
A5: { width: 210, height: 147.6 },
B3: { width: 500, height: 352.6 },
B4: { width: 250, height: 352.6 },
B5: { width: 250, height: 175.6 }
}
const curPaper = ref({
type: 'A4',
width: 210,
height: 296.6
})
const paperPopVisible = ref(false)
const paperWidth = ref(220)
const paperHeight = ref(80)
const curPaperType = computed(() => {
let type = 'other'
for (const [key, value] of Object.entries(paperTypes)) {
if (value.width === curPaper.value.width && value.height === curPaper.value.height) {
type = key
break
}
}
return type
})
const scaleValue = ref(1)
const scaleMax = 5
const scaleMin = 0.5
let hiprintInited = false
let hiprintTemplate: any
const ensureInit = () => {
if (hiprintInited) {
return
}
hiprint.init({
providers: [defaultElementTypeProvider()]
})
hiprintInited = true
}
const buildLeftElement = () => {
const jquery = (window as any).$
if (!jquery) {
message.warning('未检测到 jQuery无法加载拖拽元素')
return
}
hiprint.PrintElementTypeManager.buildByHtml(jquery(`#${dragContainerId} .ep-draggable-item`))
}
const buildDesigner = () => {
const jquery = (window as any).$
if (!jquery) {
message.warning('未检测到 jQuery无法初始化打印设计器')
return
}
jquery(`#${designerContainerId}`).empty()
const template =
currentTemplateJson.value || (withDefaultQrcodeLayout.value ? buildDefaultQrcodeTemplateJson() : undefined)
hiprintTemplate = new hiprint.PrintTemplate({
template,
settingContainer: `#${settingContainerId}`
})
hiprintTemplate.design(`#${designerContainerId}`)
setPaper(curPaperType.value, { width: curPaper.value.width, height: curPaper.value.height })
hiprintTemplate.zoom(scaleValue.value)
}
const buildDefaultQrcodeTemplateJson = () => ({
panels: [
{
index: 0,
name: 1,
width: 80,
height: 60,
paperHeader: 0,
paperFooter: 170.07874015748033,
printElements: [
{
options: {
left: 20,
top: 6,
height: 36,
width: 36,
textType: 'qrcode',
title: '二维码',
field: 'qrcodeContent',
testData: 'XS888888888'
},
printElementType: {
title: '二维码',
type: 'text'
}
},
{
options: {
left: 8,
top: 46,
height: 8,
width: 60,
title: 'ID',
field: 'printId',
testData: 'ID: 1001',
textAlign: 'center',
fontSize: 10
},
printElementType: {
title: '文本',
type: 'text'
}
}
],
paperNumberLeft: 196,
paperNumberTop: 148,
paperNumberDisabled: true,
paperNumberContinue: true,
watermarkOptions: {},
panelLayoutOptions: {}
}
]
})
const setPaper = (type: string, value: { width: number; height: number }) => {
if (!hiprintTemplate) {
return
}
const width = Number(value.width)
const height = Number(value.height)
curPaper.value = { type, width, height }
hiprintTemplate.setPaper(width, height)
}
const setPaperOther = () => {
paperPopVisible.value = false
setPaper('other', { width: Number(paperWidth.value), height: Number(paperHeight.value) })
}
const changeScale = (isZoomIn: boolean) => {
if (!hiprintTemplate) {
return
}
let nextScale = scaleValue.value
if (isZoomIn) {
nextScale += 0.1
if (nextScale > scaleMax) nextScale = scaleMax
} else {
nextScale -= 0.1
if (nextScale < scaleMin) nextScale = scaleMin
}
scaleValue.value = nextScale
hiprintTemplate.zoom(nextScale)
}
const handlePrint = () => {
if (!hiprintTemplate) {
return
}
hiprintTemplate.print(currentPrintData.value, { leftOffset: -1, topOffset: -1 })
}
const resetState = () => {
scaleValue.value = 1
curPaper.value = {
type: 'A4',
width: 210,
height: 296.6
}
paperWidth.value = 220
paperHeight.value = 80
paperPopVisible.value = false
}
const open = async (options: HiprintPreviewOpenOptions = {}) => {
dialogTitle.value = options.title || '打印预览'
currentPrintData.value = options.printData || {}
withDefaultQrcodeLayout.value = !!options.withDefaultQrcodeLayout
currentTemplateJson.value = options.templateJson
resetState()
if (options.paperSize?.width && options.paperSize?.height) {
const width = Number(options.paperSize.width)
const height = Number(options.paperSize.height)
curPaper.value = {
type: 'other',
width,
height
}
paperWidth.value = width
paperHeight.value = height
}
dialogVisible.value = true
await nextTick()
ensureInit()
buildLeftElement()
buildDesigner()
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.hiprint-preview {
width: 100%;
}
.hiprint-toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.hiprint-paper {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.hiprint-zoom {
display: flex;
align-items: center;
gap: 4px;
}
.zoom-value {
width: 56px;
text-align: center;
font-size: 13px;
color: var(--el-text-color-regular);
}
.paper-pop-title {
font-size: 14px;
font-weight: 600;
}
.paper-pop-form {
margin-top: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.hiprint-body {
height: 75vh;
display: grid;
grid-template-columns: 220px 1fr 300px;
gap: 12px;
}
.hiprint-left,
.hiprint-center,
.hiprint-right {
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
overflow: auto;
}
.hiprint-center {
padding: 16px;
}
.hiprint-right {
padding: 12px;
}
.hiprint-title {
padding: 10px 10px 0;
font-weight: 600;
}
.hiprint-drag-wrap {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding: 10px;
}
.hiprint-item {
min-height: 56px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
border: 1px solid var(--el-border-color);
background: var(--el-fill-color-light);
color: var(--el-text-color-primary);
cursor: grab;
}
</style>

@ -1,4 +1,5 @@
import Icon from './src/Icon.vue' import Icon from './src/Icon.vue'
import IconSelect from './src/IconSelect.vue' import IconSelect from './src/IconSelect.vue'
import AppIconSelect from './src/AppIconSelect.vue'
export { Icon, IconSelect } export { Icon, IconSelect, AppIconSelect }

@ -0,0 +1,280 @@
<script lang="ts" setup>
import { uniIconsList, uviewIconsList, uniIconsUnicodeMap, uviewIconsUnicodeMap } from './appIconData'
defineOptions({ name: 'AppIconSelect' })
const props = defineProps({
modelValue: {
require: false,
type: String
},
clearable: {
require: false,
type: Boolean
}
})
const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
const visible = ref(false)
const inputValue = toRef(props, 'modelValue')
const currentActiveType = ref('uni-icons')
const filterValue = ref('')
const iconDataMap: Record<string, string[]> = {
'uni-icons': uniIconsList,
'uview-plus': uviewIconsList
}
const unicodeMapMap: Record<string, Record<string, string>> = {
'uni-icons': uniIconsUnicodeMap,
'uview-plus': uviewIconsUnicodeMap
}
const fontFamilyMap: Record<string, string> = {
'uni-icons': 'UniIconsFontFamily',
'uview-plus': 'uview-iconfont'
}
const tabsList = [
{ label: 'uni-icons', name: 'uni-icons' },
{ label: 'uview-plus', name: 'uview-plus' }
]
const pageSize = ref(96)
const currentPage = ref(1)
const currentIconList = computed(() => {
return iconDataMap[currentActiveType.value] || []
})
const currentUnicodeMap = computed(() => {
return unicodeMapMap[currentActiveType.value] || {}
})
const currentFontFamily = computed(() => {
return fontFamilyMap[currentActiveType.value] || 'UniIconsFontFamily'
})
const filteredList = computed(() => {
return currentIconList.value.filter((v) =>
v.toLowerCase().includes(filterValue.value.toLowerCase())
)
})
const pageList = computed(() => {
if (currentPage.value === 1) {
return filteredList.value.slice(0, pageSize.value)
}
return filteredList.value.slice(
pageSize.value * (currentPage.value - 1),
pageSize.value * (currentPage.value - 1) + pageSize.value
)
})
const iconCount = computed(() => filteredList.value.length)
function parseIconValue(value: string): { prefix: string; name: string } | null {
if (!value || value.indexOf(':') < 0) return null
const idx = value.indexOf(':')
return {
prefix: value.substring(0, idx),
name: value.substring(idx + 1)
}
}
function getUnicode(name: string): string {
return currentUnicodeMap.value[name] || ''
}
function getSelectedFontFamily(fullValue: string): string {
const parsed = parseIconValue(fullValue)
if (!parsed) return 'UniIconsFontFamily'
return fontFamilyMap[parsed.prefix] || 'UniIconsFontFamily'
}
function getSelectedUnicode(fullValue: string): string {
const parsed = parseIconValue(fullValue)
if (!parsed) return ''
const map = unicodeMapMap[parsed.prefix]
return map ? map[parsed.name] || '' : ''
}
function handleClick({ props }: any) {
currentPage.value = 1
currentActiveType.value = props.name
}
function onChangeIcon(item: string) {
emit('update:modelValue', currentActiveType.value + ':' + item)
visible.value = false
}
function onCurrentChange(page: number) {
currentPage.value = page
}
function clearIcon() {
emit('update:modelValue', '')
visible.value = false
}
watch(
() => props.modelValue,
(val) => {
if (val) {
const parsed = parseIconValue(val)
if (parsed && fontFamilyMap[parsed.prefix]) {
currentActiveType.value = parsed.prefix
}
}
}
)
watch(
() => filterValue.value,
() => {
currentPage.value = 1
}
)
</script>
<template>
<div class="selector">
<ElInput v-model="inputValue" @click="visible = !visible" :clearable="props.clearable" @clear="clearIcon">
<template #append>
<ElPopover
:visible="visible"
:width="355"
popper-class="pure-popper"
trigger="click"
>
<template #reference>
<div
class="h-32px w-40px flex cursor-pointer items-center justify-center"
@click="visible = !visible"
>
<span
v-if="inputValue && getSelectedUnicode(inputValue)"
class="app-icon-font"
:style="{ fontFamily: getSelectedFontFamily(inputValue), fontSize: '18px' }"
>{{ getSelectedUnicode(inputValue) }}</span>
<span v-else class="text-12px color-gray-500">选择</span>
</div>
</template>
<ElInput v-model="filterValue" class="p-2" clearable placeholder="搜索图标" />
<ElDivider border-style="dashed" />
<ElTabs v-model="currentActiveType" @tab-click="handleClick">
<ElTabPane
v-for="(pane, index) in tabsList"
:key="index"
:label="pane.label"
:name="pane.name"
>
<ElDivider border-style="dashed" class="tab-divider" />
<ElScrollbar height="220px">
<ul class="ml-2 flex flex-wrap">
<li
v-for="(item, key) in pageList"
:key="key"
:style="inputValue === currentActiveType + ':' + item ? { borderColor: 'var(--el-color-primary)', color: 'var(--el-color-primary)' } : {}"
:title="item"
class="icon-item mr-2 mt-1 w-1/10 flex cursor-pointer items-center justify-center border border-solid p-2"
@click="onChangeIcon(item)"
>
<span
class="app-icon-font"
:style="{ fontFamily: currentFontFamily }"
>{{ getUnicode(item) }}</span>
</li>
</ul>
</ElScrollbar>
</ElTabPane>
</ElTabs>
<ElDivider border-style="dashed" />
<ElPagination
:current-page="currentPage"
:page-size="pageSize"
:total="iconCount"
background
class="h-10 flex items-center justify-center"
layout="prev, pager, next"
size="small"
@current-change="onCurrentChange"
/>
</ElPopover>
</template>
</ElInput>
</div>
</template>
<style lang="scss">
@font-face {
font-family: 'UniIconsFontFamily';
src: url('@/assets/fonts/uniicons.ttf') format('truetype');
font-display: swap;
}
@font-face {
font-family: 'uview-iconfont';
src: url('@/assets/fonts/uview-icons.ttf') format('truetype');
font-display: swap;
}
</style>
<style lang="scss" scoped>
.app-icon-font {
font-style: normal;
font-size: 20px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.el-divider--horizontal {
margin: 1px auto !important;
}
.tab-divider.el-divider--horizontal {
margin: 0 !important;
}
.icon-item {
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
transform: scaleX(1.05);
transition: all 0.4s;
}
}
:deep(.el-tabs__nav-next) {
font-size: 15px;
line-height: 32px;
box-shadow: -5px 0 5px -6px #ccc;
}
:deep(.el-tabs__nav-prev) {
font-size: 15px;
line-height: 32px;
box-shadow: 5px 0 5px -6px #ccc;
}
:deep(.el-input-group__append) {
padding: 0;
}
:deep(.el-tabs__item) {
height: 30px;
font-size: 12px;
font-weight: normal;
line-height: 30px;
}
:deep(.el-tabs__header),
:deep(.el-tabs__nav-wrap) {
position: static;
margin: 0;
}
</style>

@ -15,6 +15,16 @@ const props = defineProps({
clearable: { clearable: {
require: false, require: false,
type: Boolean type: Boolean
},
persistent: {
require: false,
type: Boolean,
default: true
},
teleported: {
require: false,
type: Boolean,
default: true
} }
}) })
const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>() const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
@ -102,6 +112,10 @@ function clearIcon() {
visible.value = false visible.value = false
} }
onBeforeUnmount(() => {
visible.value = false
})
watch( watch(
() => { () => {
return props.modelValue return props.modelValue
@ -133,6 +147,8 @@ watch(
}" }"
:visible="visible" :visible="visible"
:width="355" :width="355"
:persistent="props.persistent"
:teleported="props.teleported"
popper-class="pure-popper" popper-class="pure-popper"
trigger="click" trigger="click"
> >

@ -0,0 +1,382 @@
const uniIconsUnicodeMap: Record<string, string> = {
'arrow-down': '\ue6be',
'arrow-left': '\ue6bc',
'arrow-right': '\ue6bb',
'arrow-up': '\ue6bd',
'auth': '\ue6ab',
'auth-filled': '\ue6cc',
'back': '\ue6b9',
'bars': '\ue627',
'calendar': '\ue6a0',
'calendar-filled': '\ue6c0',
'camera': '\ue65a',
'camera-filled': '\ue658',
'cart': '\ue631',
'cart-filled': '\ue6d0',
'chat': '\ue65d',
'chat-filled': '\ue659',
'chatboxes': '\ue696',
'chatboxes-filled': '\ue692',
'chatbubble': '\ue697',
'chatbubble-filled': '\ue694',
'checkbox': '\ue62b',
'checkbox-filled': '\ue62c',
'checkmarkempty': '\ue65c',
'circle': '\ue65b',
'circle-filled': '\ue65e',
'clear': '\ue66d',
'close': '\ue673',
'closeempty': '\ue66c',
'cloud-download': '\ue647',
'cloud-download-filled': '\ue646',
'cloud-upload': '\ue645',
'cloud-upload-filled': '\ue648',
'color': '\ue6cf',
'color-filled': '\ue6c9',
'compose': '\ue67f',
'contact': '\ue693',
'contact-filled': '\ue695',
'down': '\ue6b8',
'bottom': '\ue6b8',
'download': '\ue68d',
'download-filled': '\ue681',
'email': '\ue69e',
'email-filled': '\ue69a',
'eye': '\ue651',
'eye-filled': '\ue66a',
'eye-slash': '\ue6b3',
'eye-slash-filled': '\ue6b4',
'fire': '\ue6a1',
'fire-filled': '\ue6c5',
'flag': '\ue65f',
'flag-filled': '\ue660',
'folder-add': '\ue6a9',
'folder-add-filled': '\ue6c8',
'font': '\ue6a3',
'forward': '\ue6ba',
'gear': '\ue664',
'gear-filled': '\ue661',
'gift': '\ue6a4',
'gift-filled': '\ue6c4',
'hand-down': '\ue63d',
'hand-down-filled': '\ue63c',
'hand-up': '\ue63f',
'hand-up-filled': '\ue63e',
'headphones': '\ue630',
'heart': '\ue639',
'heart-filled': '\ue641',
'help': '\ue679',
'help-filled': '\ue674',
'home': '\ue662',
'home-filled': '\ue663',
'image': '\ue670',
'image-filled': '\ue678',
'images': '\ue650',
'images-filled': '\ue64b',
'info': '\ue669',
'info-filled': '\ue649',
'left': '\ue6b7',
'link': '\ue6a5',
'list': '\ue644',
'location': '\ue6ae',
'location-filled': '\ue6af',
'locked': '\ue66b',
'locked-filled': '\ue668',
'loop': '\ue633',
'mail-open': '\ue643',
'mail-open-filled': '\ue63a',
'map': '\ue667',
'map-filled': '\ue666',
'map-pin': '\ue6ad',
'map-pin-ellipse': '\ue6ac',
'medal': '\ue6a2',
'medal-filled': '\ue6c3',
'mic': '\ue671',
'mic-filled': '\ue677',
'micoff': '\ue67e',
'micoff-filled': '\ue6b0',
'minus': '\ue66f',
'minus-filled': '\ue67d',
'more': '\ue64d',
'more-filled': '\ue64e',
'navigate': '\ue66e',
'navigate-filled': '\ue67a',
'notification': '\ue6a6',
'notification-filled': '\ue6c1',
'paperclip': '\ue652',
'paperplane': '\ue672',
'paperplane-filled': '\ue675',
'person': '\ue699',
'person-filled': '\ue69d',
'personadd': '\ue69f',
'personadd-filled': '\ue698',
'phone': '\ue69c',
'phone-filled': '\ue69b',
'plus': '\ue676',
'plus-filled': '\ue6c7',
'plusempty': '\ue67b',
'pulldown': '\ue632',
'pyq': '\ue682',
'qq': '\ue680',
'redo': '\ue64a',
'redo-filled': '\ue655',
'refresh': '\ue657',
'refresh-filled': '\ue656',
'refreshempty': '\ue6bf',
'reload': '\ue6b2',
'right': '\ue6b5',
'scan': '\ue62a',
'search': '\ue654',
'settings': '\ue653',
'settings-filled': '\ue6ce',
'shop': '\ue62f',
'shop-filled': '\ue6cd',
'smallcircle': '\ue67c',
'smallcircle-filled': '\ue665',
'sound': '\ue684',
'sound-filled': '\ue686',
'spinner-cycle': '\ue68a',
'staff': '\ue6a7',
'staff-filled': '\ue6cb',
'star': '\ue688',
'star-filled': '\ue68f',
'starhalf': '\ue683',
'trash': '\ue687',
'trash-filled': '\ue685',
'tune': '\ue6aa',
'tune-filled': '\ue6ca',
'undo': '\ue64f',
'undo-filled': '\ue64c',
'up': '\ue6b6',
'top': '\ue6b6',
'upload': '\ue690',
'upload-filled': '\ue68e',
'videocam': '\ue68c',
'videocam-filled': '\ue689',
'vip': '\ue6a8',
'vip-filled': '\ue6c6',
'wallet': '\ue6b1',
'wallet-filled': '\ue6c2',
'weibo': '\ue68b',
'weixin': '\ue691'
}
const uviewIconsUnicodeMap: Record<string, string> = {
'level': '\ue693',
'column-line': '\ue68e',
'checkbox-mark': '\ue807',
'folder': '\ue7f5',
'movie': '\ue7f6',
'star-fill': '\ue669',
'star': '\ue65f',
'phone-fill': '\ue64f',
'phone': '\ue622',
'apple-fill': '\ue881',
'chrome-circle-fill': '\ue885',
'backspace': '\ue67b',
'attach': '\ue632',
'cut': '\ue948',
'empty-car': '\ue602',
'empty-coupon': '\ue682',
'empty-address': '\ue646',
'empty-favor': '\ue67c',
'empty-permission': '\ue686',
'empty-news': '\ue687',
'empty-search': '\ue664',
'github-circle-fill': '\ue887',
'rmb': '\ue608',
'person-delete-fill': '\ue66a',
'reload': '\ue788',
'order': '\ue68f',
'server-man': '\ue6bc',
'search': '\ue62a',
'fingerprint': '\ue955',
'more-dot-fill': '\ue630',
'scan': '\ue662',
'share-square': '\ue60b',
'map': '\ue61d',
'map-fill': '\ue64e',
'tags': '\ue629',
'tags-fill': '\ue651',
'bookmark-fill': '\ue63b',
'bookmark': '\ue60a',
'eye': '\ue613',
'eye-fill': '\ue641',
'mic': '\ue64a',
'mic-off': '\ue649',
'calendar': '\ue66e',
'calendar-fill': '\ue634',
'trash': '\ue623',
'trash-fill': '\ue658',
'play-left': '\ue66d',
'play-right': '\ue610',
'minus': '\ue618',
'plus': '\ue62d',
'info': '\ue653',
'info-circle': '\ue7d2',
'info-circle-fill': '\ue64b',
'question': '\ue715',
'error': '\ue6d3',
'close': '\ue685',
'checkmark': '\ue6a8',
'android-circle-fill': '\ue67e',
'android-fill': '\ue67d',
'ie': '\ue87b',
'IE-circle-fill': '\ue889',
'google': '\ue87a',
'google-circle-fill': '\ue88a',
'setting-fill': '\ue872',
'setting': '\ue61f',
'minus-square-fill': '\ue855',
'plus-square-fill': '\ue856',
'heart': '\ue7df',
'heart-fill': '\ue851',
'camera': '\ue7d7',
'camera-fill': '\ue870',
'more-circle': '\ue63e',
'more-circle-fill': '\ue645',
'chat': '\ue620',
'chat-fill': '\ue61e',
'bag-fill': '\ue617',
'bag': '\ue619',
'error-circle-fill': '\ue62c',
'error-circle': '\ue624',
'close-circle': '\ue63f',
'close-circle-fill': '\ue637',
'checkmark-circle': '\ue63d',
'checkmark-circle-fill': '\ue635',
'question-circle-fill': '\ue666',
'question-circle': '\ue625',
'share': '\ue631',
'share-fill': '\ue65e',
'shopping-cart': '\ue621',
'shopping-cart-fill': '\ue65d',
'bell': '\ue609',
'bell-fill': '\ue640',
'list': '\ue650',
'list-dot': '\ue616',
'zhihu': '\ue6ba',
'zhihu-circle-fill': '\ue709',
'zhifubao': '\ue6b9',
'zhifubao-circle-fill': '\ue6b8',
'weixin-circle-fill': '\ue6b1',
'weixin-fill': '\ue6b2',
'twitter-circle-fill': '\ue6ab',
'twitter': '\ue6aa',
'taobao-circle-fill': '\ue6a7',
'taobao': '\ue6a6',
'weibo-circle-fill': '\ue6a5',
'weibo': '\ue6a4',
'qq-fill': '\ue6a1',
'qq-circle-fill': '\ue6a0',
'moments-circel-fill': '\ue69a',
'moments': '\ue69b',
'qzone': '\ue695',
'qzone-circle-fill': '\ue696',
'baidu-circle-fill': '\ue680',
'baidu': '\ue681',
'facebook-circle-fill': '\ue68a',
'facebook': '\ue689',
'car': '\ue60c',
'car-fill': '\ue636',
'warning-fill': '\ue64d',
'warning': '\ue694',
'clock-fill': '\ue638',
'clock': '\ue60f',
'edit-pen': '\ue612',
'edit-pen-fill': '\ue66b',
'email': '\ue611',
'email-fill': '\ue642',
'minus-circle': '\ue61b',
'minus-circle-fill': '\ue652',
'plus-circle': '\ue62e',
'plus-circle-fill': '\ue661',
'file-text': '\ue663',
'file-text-fill': '\ue665',
'pushpin': '\ue7e3',
'pushpin-fill': '\ue86e',
'grid': '\ue673',
'grid-fill': '\ue678',
'play-circle': '\ue647',
'play-circle-fill': '\ue655',
'pause-circle-fill': '\ue654',
'pause': '\ue8fa',
'pause-circle': '\ue643',
'eye-off': '\ue648',
'eye-off-outline': '\ue62b',
'gift-fill': '\ue65c',
'gift': '\ue65b',
'rmb-circle-fill': '\ue657',
'rmb-circle': '\ue677',
'kefu-ermai': '\ue656',
'server-fill': '\ue751',
'coupon-fill': '\ue8c4',
'coupon': '\ue8ae',
'integral': '\ue704',
'integral-fill': '\ue703',
'home-fill': '\ue964',
'home': '\ue965',
'hourglass-half-fill': '\ue966',
'hourglass': '\ue967',
'account': '\ue628',
'plus-people-fill': '\ue626',
'minus-people-fill': '\ue615',
'account-fill': '\ue614',
'thumb-down-fill': '\ue726',
'thumb-down': '\ue727',
'thumb-up': '\ue733',
'thumb-up-fill': '\ue72f',
'lock-fill': '\ue979',
'lock-open': '\ue973',
'lock-opened-fill': '\ue974',
'lock': '\ue97a',
'red-packet-fill': '\ue690',
'photo-fill': '\ue98b',
'photo': '\ue98d',
'volume-off-fill': '\ue659',
'volume-off': '\ue644',
'volume-fill': '\ue670',
'volume': '\ue633',
'red-packet': '\ue691',
'download': '\ue63c',
'arrow-up-fill': '\ue6b0',
'arrow-down-fill': '\ue600',
'play-left-fill': '\ue675',
'play-right-fill': '\ue676',
'rewind-left-fill': '\ue679',
'rewind-right-fill': '\ue67a',
'arrow-downward': '\ue604',
'arrow-leftward': '\ue601',
'arrow-rightward': '\ue603',
'arrow-upward': '\ue607',
'arrow-down': '\ue60d',
'arrow-right': '\ue605',
'arrow-left': '\ue60e',
'arrow-up': '\ue606',
'skip-back-left': '\ue674',
'skip-forward-right': '\ue672',
'rewind-right': '\ue66f',
'rewind-left': '\ue671',
'arrow-right-double': '\ue68d',
'arrow-left-double': '\ue68c',
'wifi-off': '\ue668',
'wifi': '\ue667',
'empty-data': '\ue62f',
'empty-history': '\ue684',
'empty-list': '\ue68b',
'empty-page': '\ue627',
'empty-order': '\ue639',
'man': '\ue697',
'woman': '\ue69c',
'man-add': '\ue61c',
'man-add-fill': '\ue64c',
'man-delete': '\ue61a',
'man-delete-fill': '\ue66a',
'zh': '\ue70a',
'en': '\ue692'
}
const uniIconsList = Object.keys(uniIconsUnicodeMap)
const uviewIconsList = Object.keys(uviewIconsUnicodeMap)
export { uniIconsList, uviewIconsList, uniIconsUnicodeMap, uviewIconsUnicodeMap }

@ -0,0 +1,460 @@
<template>
<div class="qrcode-action-card">
<el-image
v-if="imageUrl"
:src="imageUrl"
:preview-src-list="[imageUrl]"
preview-teleported
fit="cover"
class="qrcode-action-card__img"
>
<template #error>
<div class="qrcode-action-card__error">{{ errorText || emptyText }}</div>
</template>
</el-image>
<div v-else class="qrcode-action-card__error">{{ emptyText }}</div>
<div v-if="showActionMask" class="qrcode-action-card__mask">
<el-button v-if="showPreview" circle :disabled="!imageUrl" @click="handlePreview">
<Icon icon="ep:zoom-in" />
</el-button>
<el-button v-if="showPrint" circle :disabled="!imageUrl || printDisabled" @click="handlePrint">
<Icon icon="ep:printer" />
</el-button>
<el-button
v-if="showRefresh"
circle
:loading="refreshLoading"
:disabled="refreshDisabled || refreshLoading"
@click="handleRefresh"
>
<Icon icon="ep:refresh" />
</el-button>
</div>
</div>
<HiprintPreviewDialog ref="hiprintPreviewDialogRef" />
</template>
<script setup lang="ts">
import request from '@/config/axios'
import HiprintPreviewDialog from '@/components/HiprintPreviewDialog/index.vue'
import { createImageViewer } from '@/components/ImageViewer'
import { ElLoading } from 'element-plus'
import { getSimpleDictDataList } from '@/api/system/dict/dict.data'
const { t } = useI18n()
const message = useMessage()
const props = withDefaults(
defineProps<{
imageUrl?: string
printId?: string | number
printTitle?: string
emptyText?: string
refreshUrl?: string
refreshMethod?: 'post' | 'put' | 'get'
refreshDisabled?: boolean
printDisabled?: boolean
refreshConfirmText?: string
errorText?: string
showPreview?: boolean
showPrint?: boolean
showRefresh?: boolean
printAdaptive?: boolean
printPaperWidth?: number
printPaperHeight?: number
printMaxWidth?: number
printMaxHeight?: number
templateJsonUrl?: string
templateJson?: any
printData?: Record<string, any>
printTemplateType?: string | number
}>(),
{
imageUrl: '',
printId: '',
printTitle: '二维码打印预览',
emptyText: '',
refreshUrl: '',
refreshMethod: 'post',
refreshDisabled: false,
printDisabled: false,
refreshConfirmText: '',
errorText: '',
showPreview: true,
showPrint: true,
showRefresh: true,
printAdaptive: true,
printPaperWidth: 80,
printPaperHeight: 80,
printMaxWidth: 180,
printMaxHeight: 120,
templateJsonUrl: '',
templateJson: undefined,
printData: () => ({}),
printTemplateType: undefined
}
)
const emit = defineEmits<{
(e: 'refresh-success', data: any): void
}>()
const hiprintPreviewDialogRef = ref()
const refreshLoading = ref(false)
type TemplateFieldMap = {
qrcodeField?: string
nameField?: string
codeField?: string
}
let printTemplateDictCache: any[] | undefined
const showActionMask = computed(() => props.showPreview || props.showPrint || props.showRefresh)
const parseTemplateFieldMap = (remark: any): TemplateFieldMap | undefined => {
const parts = String(remark || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean)
if (!parts.length) return undefined
return {
qrcodeField: parts[0],
nameField: parts[1],
codeField: parts[2] || parts[1]
}
}
const getTemplateFieldMap = async (): Promise<TemplateFieldMap | undefined> => {
if (props.printTemplateType === undefined || props.printTemplateType === null || props.printTemplateType === '') {
return undefined
}
if (!printTemplateDictCache) {
printTemplateDictCache = await getSimpleDictDataList()
}
const matched = (printTemplateDictCache || []).find(
(item: any) => item.dictType === 'print_template_type' && String(item.value) === String(props.printTemplateType)
)
return parseTemplateFieldMap(matched?.remark)
}
const buildQrcodeTemplateJson = (
qrcodeUrl: string,
printData: Record<string, any>,
imageWidth: number,
imageHeight: number,
paperHeight: number,
fieldMap?: TemplateFieldMap
) => ({
panels: [
{
index: 0,
name: 1,
width: imageWidth,
height: paperHeight,
paperHeader: 0,
paperFooter: 0,
printElements: [
{
options: {
left: 0,
top: 0,
width: imageWidth,
height: imageHeight,
src: qrcodeUrl,
field: fieldMap?.qrcodeField || 'qrcodeUrl',
title: '二维码图片',
testData: qrcodeUrl
},
printElementType: {
title: '图片',
type: 'image'
}
},
...buildDefaultTextElements(printData, imageWidth, imageHeight, fieldMap)
],
paperNumberDisabled: true,
paperNumberContinue: true,
watermarkOptions: {},
panelLayoutOptions: {}
}
]
})
const buildDefaultTextElements = (
printData: Record<string, any>,
imageWidth: number,
imageHeight: number,
fieldMap?: TemplateFieldMap
) => {
const fields = [
{ title: '名称', field: fieldMap?.nameField },
{ title: '编码', field: fieldMap?.codeField }
].filter((item) => item.field)
if (!fields.length) {
const printId = printData.printId === undefined || printData.printId === null ? '' : String(printData.printId)
if (!printId) return []
return [
{
options: {
left: 0,
top: imageHeight + 2,
height: 10,
width: imageWidth,
testData: printId,
title: 'ID',
field: 'printId',
textAlign: 'center',
fontSize: 10
},
printElementType: {
title: '文本',
type: 'text'
}
}
]
}
return fields.map((item, index) => ({
options: {
left: 0,
top: imageHeight + 2 + index * 12,
height: 10,
width: imageWidth,
testData: printData[item.field!] === undefined || printData[item.field!] === null ? '' : String(printData[item.field!]),
title: item.title,
field: item.field,
textAlign: 'center',
fontSize: 10
},
printElementType: {
title: '文本',
type: 'text'
}
}))
}
const getImageSize = (src: string) =>
new Promise<{ width: number; height: number }>((resolve, reject) => {
const img = new Image()
img.onload = () => {
const width = img.naturalWidth || img.width
const height = img.naturalHeight || img.height
if (!width || !height) {
reject(new Error('invalid image size'))
return
}
resolve({ width, height })
}
img.onerror = () => reject(new Error('image load failed'))
img.src = src
})
const resolvePrintSize = async (src: string) => {
const fallbackWidth = Math.max(20, Number(props.printPaperWidth) || 80)
const fallbackHeight = Math.max(20, Number(props.printPaperHeight) || 80)
if (!props.printAdaptive) {
return { width: fallbackWidth, height: fallbackHeight }
}
try {
const size = await getImageSize(src)
const ratio = size.width / size.height
if (!Number.isFinite(ratio) || ratio <= 0) {
return { width: fallbackWidth, height: fallbackHeight }
}
if (ratio >= 1) {
const height = fallbackHeight
const width = Math.min(Math.max(fallbackWidth, Math.round(height * ratio)), Number(props.printMaxWidth) || 180)
return { width, height }
}
const width = fallbackWidth
const height = Math.min(Math.max(fallbackHeight, Math.round(width / ratio)), Number(props.printMaxHeight) || 120)
return { width, height }
} catch {
return { width: fallbackWidth, height: fallbackHeight }
}
}
const handlePreview = () => {
if (!props.imageUrl) return
createImageViewer({
zIndex: 9999999,
urlList: [props.imageUrl]
})
}
const resolveElementFieldKey = (element: any, fieldMap?: TemplateFieldMap) => {
const fieldKey = typeof element?.options?.field === 'string' ? element.options.field.trim() : ''
const qidKey = typeof element?.options?.qid === 'string' ? element.options.qid.trim() : ''
const title = String(element?.options?.title || element?.printElementType?.title || '').trim()
if (fieldMap) {
if (title.includes('二维码')) return fieldMap.qrcodeField || fieldKey || qidKey
if (title.includes('名称')) return fieldMap.nameField || fieldKey || qidKey
if (title.includes('编码') || title.includes('条码') || title.includes('文本')) return fieldMap.codeField || fieldKey || qidKey
}
return fieldKey || qidKey
}
const replaceTemplateValues = (templateJson: any, printData: Record<string, any>, fieldMap?: TemplateFieldMap) => {
if (!templateJson?.panels) return templateJson
return {
...templateJson,
panels: templateJson.panels.map((panel: any) => ({
...panel,
printElements: panel.printElements?.map((element: any) => {
const valueKey = resolveElementFieldKey(element, fieldMap)
if (!valueKey) return element
const value = printData[valueKey]
if (value === undefined || value === null) return element
const newOptions = { ...element.options }
if (element?.printElementType?.type === 'image') {
newOptions.src = value
newOptions.testData = value
} else {
newOptions.field = valueKey
newOptions.testData = String(value)
}
return {
...element,
options: newOptions
}
}) || []
}))
}
}
const handlePrint = async () => {
if (!props.imageUrl || !props.showPrint) return
let templateJson: any
let printData: Record<string, any>
const templateFieldMap = await getTemplateFieldMap()
printData = {
qrcodeUrl: props.imageUrl,
printId: props.printId === undefined || props.printId === null ? '' : String(props.printId),
...props.printData
}
if (props.templateJson) {
templateJson = replaceTemplateValues(props.templateJson, printData, templateFieldMap)
} else if (props.templateJsonUrl) {
try {
const response = await request.get({ url: props.templateJsonUrl })
templateJson = typeof response.data === 'string' ? JSON.parse(response.data) : response.data
templateJson = replaceTemplateValues(templateJson, printData, templateFieldMap)
} catch (error) {
console.error('获取打印模板失败', error)
message.error('获取打印模板失败')
return
}
} else {
const printSize = await resolvePrintSize(props.imageUrl)
const imageWidth = printSize.width
const imageHeight = printSize.height
const printId = props.printId === undefined || props.printId === null ? '' : String(props.printId)
const hasMappedFields = Boolean(templateFieldMap?.nameField || templateFieldMap?.codeField)
const textLineCount = hasMappedFields ? [templateFieldMap?.nameField, templateFieldMap?.codeField].filter(Boolean).length : (printId ? 1 : 0)
const paperHeight = imageHeight + textLineCount * 12 + (textLineCount ? 4 : 0)
templateJson = buildQrcodeTemplateJson(props.imageUrl, printData, imageWidth, imageHeight, paperHeight, templateFieldMap)
}
const paperSize = templateJson?.panels?.[0] ? {
width: templateJson.panels[0].width,
height: templateJson.panels[0].height
} : { width: 80, height: 80 }
hiprintPreviewDialogRef.value?.open({
title: props.printTitle,
printData,
templateJson,
withDefaultQrcodeLayout: false,
paperSize
})
}
const handleRefresh = async () => {
if (!props.refreshUrl || props.refreshDisabled || !props.showRefresh) return
try {
await message.confirm(props.refreshConfirmText || '确认刷新二维码吗?')
} catch {
return
}
refreshLoading.value = true
const loading = ElLoading.service({
lock: true,
text: t('common.loading'),
background: 'rgba(0, 0, 0, 0.3)'
})
try {
let data
if (props.refreshMethod === 'get') {
data = await request.get({ url: props.refreshUrl })
} else if (props.refreshMethod === 'put') {
data = await request.put({ url: props.refreshUrl })
} else {
data = await request.post({ url: props.refreshUrl })
}
emit('refresh-success', data)
message.success(t('common.updateSuccess'))
} finally {
loading.close()
refreshLoading.value = false
}
}
</script>
<style scoped lang="scss">
.qrcode-action-card {
position: relative;
height: 150px;
width: fit-content;
min-width: 150px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.qrcode-action-card__img {
height: 150px;
width: auto;
display: block;
}
.qrcode-action-card__error {
height: 150px;
min-width: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 0 12px;
}
.qrcode-action-card__mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
background: rgba(0, 0, 0, 0.35);
opacity: 0;
transition: opacity 0.2s ease;
}
.qrcode-action-card:hover .qrcode-action-card__mask {
opacity: 1;
}
</style>

@ -17,9 +17,10 @@
/> />
</el-select> </el-select>
</ElDialog> </ElDialog>
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch"> <div v-else class="custom-hover" @click.stop="toggleTopSearch">
<Icon icon="ep:search" /> <Icon icon="ep:search" />
<el-select <el-select
ref="topSelectRef"
@click.stop @click.stop
filterable filterable
:reserve-keyword="false" :reserve-keyword="false"
@ -51,7 +52,8 @@ defineProps({
const router = useRouter() // const router = useRouter() //
const showSearch = ref(false) // const showSearch = ref(false) //
const showTopSearch = ref(false) // const showTopSearch = ref(false) //
const value: Ref = ref('') // const value: Ref = ref('')
const topSelectRef = ref<InstanceType<typeof ElSelect>>()
const routers = router.getRoutes() // const routers = router.getRoutes() //
const options = computed(() => { const options = computed(() => {
@ -86,6 +88,15 @@ function hiddenTopSearch() {
showTopSearch.value = false showTopSearch.value = false
} }
function toggleTopSearch() {
showTopSearch.value = !showTopSearch.value
if (showTopSearch.value) {
setTimeout(() => {
topSelectRef.value?.focus()
}, 600)
}
}
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', listenKey) window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch) window.addEventListener('click', hiddenTopSearch)

@ -0,0 +1,241 @@
<template>
<Dialog :title="title" v-model="dialogVisible" :appendToBody="true" width="1080">
<ContentWrap>
<!-- 新增查询表单区域 -->
<div v-if="$slots.header" class="search-container">
<slot name="header"></slot>
</div>
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
:row-key="resolveRowKey"
:show-overflow-tooltip="true"
:stripe="true"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column
v-for="column in columns"
:key="column.prop"
:label="column.label"
:prop="column.prop"
:width="column.width"
:min-width="column.minWidth"
:align="column.align || 'center'"
/>
</el-table>
<Pagination
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<template #footer>
<el-button type="primary" @click="submitSelection"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import type { ElTable } from 'element-plus'
defineOptions({ name: 'TableSelectDialog' })
type TableColumn = {
label: string
prop: string
width?: string | number
minWidth?: string | number
align?: 'left' | 'center' | 'right'
}
const props = withDefaults(
defineProps<{
title: string
columns: TableColumn[]
fetchApi: (params: Record<string, any>) => Promise<{ list: any[]; total: number }>
selectionType?: 'single' | 'multiple'
rowKey?: string
pageSize?: number
initialRows?: any[]
queryParams?: Record<string, any> //
/** ✅ 新增 */
defaultSelectedKeys?: (string | number)[]
}>(),
{
selectionType: 'multiple',
rowKey: 'id',
pageSize: 10,
initialRows: () => [],
queryParams: () => ({}), //
defaultSelectedKeys: () => []
}
)
const emit = defineEmits<{
(e: 'confirm', value: { ids: (number | string)[]; rows: any[] }): void
}>()
const dialogVisible = ref(false)
const loading = ref(false)
const list = ref<any[]>([])
const total = ref(0)
const tableRef = ref<InstanceType<typeof ElTable>>()
const queryParams = reactive({
pageNo: 1,
pageSize: props.pageSize
})
const selectedMap = ref(new Map<number | string, any>())
const syncingSelection = ref(false)
const isSingleSelect = computed(() => props.selectionType === 'single')
const resolveRowKey = (row: Record<string, any>) => row[props.rowKey]
const refreshSelectionOnTable = async () => {
await nextTick()
const table = tableRef.value
if (!table) return
syncingSelection.value = true
try {
table.clearSelection()
const selectedIds = selectedMap.value
list.value.forEach((row) => {
const rowId = resolveRowKey(row)
if (selectedIds.has(rowId)) {
table.toggleRowSelection(row, true)
}
})
} finally {
syncingSelection.value = false
}
}
/**
* 新增根据 defaultSelectedKeys 初始化选中状态
*/
const initDefaultSelection = () => {
if (!props.defaultSelectedKeys.length) return
props.defaultSelectedKeys.forEach((key) => {
selectedMap.value.set(key, { [props.rowKey]: key })
})
}
const getList = async () => {
loading.value = true
try {
const params = getQueryParams()
const data = await props.fetchApi(params)
list.value = data.list || []
total.value = data.total || 0
await refreshSelectionOnTable()
} finally {
loading.value = false
}
}
const handleSelectionChange = (rows: any[]) => {
if (syncingSelection.value) return
if (isSingleSelect.value) {
selectedMap.value.clear()
const latestRow = rows.length ? rows[rows.length - 1] : undefined
if (latestRow) {
selectedMap.value.set(resolveRowKey(latestRow), latestRow)
}
void refreshSelectionOnTable()
return
}
const pageIdSet = new Set(list.value.map((item) => resolveRowKey(item)))
pageIdSet.forEach((id) => {
selectedMap.value.delete(id)
})
rows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
}
const handleRowClick = (row: any) => {
if (!isSingleSelect.value) return
const table = tableRef.value
if (!table) return
syncingSelection.value = true
try {
table.clearSelection()
table.toggleRowSelection(row, true)
} finally {
syncingSelection.value = false
}
selectedMap.value.clear()
selectedMap.value.set(resolveRowKey(row), row)
}
const open = async (rows?: any[]) => {
/* selectedMap.value.clear()
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()*/
selectedMap.value.clear()
// 使 defaultSelectedKeys
if (props.defaultSelectedKeys.length) {
initDefaultSelection()
}
// initialRows / rows
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()
}
const submitSelection = () => {
const rows = Array.from(selectedMap.value.values())
const ids = rows.map((row) => resolveRowKey(row))
emit('confirm', { ids, rows })
dialogVisible.value = false
}
//
const reload =async () => {
loading.value = true
//
await getList()
}
// 3. queryParams
const getQueryParams = () => {
return {
pageNo: queryParams.pageNo || 1,
pageSize: queryParams.pageSize || 10,
...props.queryParams //
}
}
// 4.
watch(() => props.queryParams, async () => {
await getList()
}, { deep: true })
defineExpose({ open, reload })
</script>
<style scoped>
.search-container {
margin-bottom: 10px;
padding: 0 0px;
}
</style>

@ -9,6 +9,7 @@
:disabled="disabled" :disabled="disabled"
:drag="drag" :drag="drag"
:http-request="httpRequest" :http-request="httpRequest"
:accept="acceptTypes"
:limit="props.limit" :limit="props.limit"
:multiple="props.limit > 1" :multiple="props.limit > 1"
:on-error="excelUploadError" :on-error="excelUploadError"
@ -79,7 +80,7 @@ import { UploadFile } from 'element-plus/es/components/upload/src/upload'
defineOptions({ name: 'UploadFile' }) defineOptions({ name: 'UploadFile' })
const message = useMessage() // const message = useMessage() //
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue', 'uploading-change'])
const props = defineProps({ const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired, modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
@ -97,10 +98,44 @@ const uploadRef = ref<UploadInstance>()
const uploadList = ref<UploadUserFile[]>([]) const uploadList = ref<UploadUserFile[]>([])
const fileList = ref<UploadUserFile[]>([]) const fileList = ref<UploadUserFile[]>([])
const uploadNumber = ref<number>(0) const uploadNumber = ref<number>(0)
const uploadPendingCount = ref<number>(0)
const nameMap = ref<Record<string, string>>({}) const nameMap = ref<Record<string, string>>({})
const { uploadUrl, httpRequest } = useUpload() const { uploadUrl, httpRequest } = useUpload()
const acceptTypes = computed(() =>
props.fileType
.map((type: string) => {
const normalizedType = String(type).trim()
if (!normalizedType) return ''
if (normalizedType.startsWith('.') || normalizedType.includes('/')) return normalizedType
return `.${normalizedType}`
})
.filter(Boolean)
.join(',')
)
const emitUploadingChange = () => {
emit('uploading-change', uploadPendingCount.value > 0)
}
const finalizeUploadBatch = () => {
if (uploadList.value.length) {
fileList.value.push(...uploadList.value)
uploadList.value = []
emitUpdateModelValue()
}
uploadNumber.value = 0
}
const finishSingleUpload = () => {
uploadPendingCount.value = Math.max(uploadPendingCount.value - 1, 0)
if (uploadPendingCount.value === 0) {
finalizeUploadBatch()
}
emitUploadingChange()
}
// //
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => { const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) { if (fileList.value.length >= props.limit) {
@ -126,6 +161,9 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
} }
message.success('正在上传文件,请稍候...') message.success('正在上传文件,请稍候...')
uploadNumber.value++ uploadNumber.value++
uploadPendingCount.value++
emitUploadingChange()
return true
} }
// //
// const handleFileChange = (uploadFile: UploadFile): void => { // const handleFileChange = (uploadFile: UploadFile): void => {
@ -153,6 +191,7 @@ const handleFileSuccess: UploadProps['onSuccess'] = (res: any, uploadFile): void
const url = getUrlFromRes(res) const url = getUrlFromRes(res)
if (!url) { if (!url) {
message.error('上传返回数据异常') message.error('上传返回数据异常')
finishSingleUpload()
return return
} }
const name = getNameFromRes(res, url) const name = getNameFromRes(res, url)
@ -165,12 +204,7 @@ const handleFileSuccess: UploadProps['onSuccess'] = (res: any, uploadFile): void
fileList.value.splice(index, 1) fileList.value.splice(index, 1)
} }
uploadList.value.push({ name, url }) uploadList.value.push({ name, url })
if (uploadList.value.length === uploadNumber.value) { finishSingleUpload()
fileList.value.push(...uploadList.value)
uploadList.value = []
uploadNumber.value = 0
emitUpdateModelValue()
}
} }
// //
const handleExceed: UploadProps['onExceed'] = (): void => { const handleExceed: UploadProps['onExceed'] = (): void => {
@ -179,6 +213,7 @@ const handleExceed: UploadProps['onExceed'] = (): void => {
// //
const excelUploadError: UploadProps['onError'] = (): void => { const excelUploadError: UploadProps['onError'] = (): void => {
message.error('导入数据失败,请您重新上传!') message.error('导入数据失败,请您重新上传!')
finishSingleUpload()
} }
// //
const handleRemove = (file: UploadFile) => { const handleRemove = (file: UploadFile) => {
@ -293,6 +328,10 @@ watch(
(val: string | string[]) => { (val: string | string[]) => {
if (!val) { if (!val) {
fileList.value = [] // fix fileList.value = [] // fix
uploadList.value = []
uploadNumber.value = 0
uploadPendingCount.value = 0
emitUploadingChange()
return return
} }

@ -119,18 +119,27 @@ interface UploadEmits {
} }
const emit = defineEmits<UploadEmits>() const emit = defineEmits<UploadEmits>()
const unwrapUploadPayload = (res: any) => {
if (!res) return undefined
const payload = res.data ?? res
if (payload && typeof payload === 'object' && payload.data) {
return payload.data
}
return payload
}
const getUrlFromRes = (res: any): string => { const getUrlFromRes = (res: any): string => {
if (!res) return '' if (!res) return ''
if (typeof res === 'string') return res if (typeof res === 'string') return res
if (typeof res.data === 'string') return res.data if (typeof res.data === 'string') return res.data
const data = res.data || res const data = unwrapUploadPayload(res)
if (typeof data === 'string') return data if (typeof data === 'string') return data
if (data.fileUrl) return data.fileUrl if (data.fileUrl) return data.fileUrl
return '' return ''
} }
const getNameFromRes = (res: any, url: string): string => { const getNameFromRes = (res: any, url: string): string => {
const data = res && res.data ? res.data : res const data = unwrapUploadPayload(res)
if (data && typeof data === 'object' && data.fileName) return data.fileName if (data && typeof data === 'object' && data.fileName) return data.fileName
if (!url) return '' if (!url) return ''
const idx = url.lastIndexOf('/') const idx = url.lastIndexOf('/')

@ -16,7 +16,7 @@ const config: {
/** /**
* *
*/ */
request_timeout: 30000, request_timeout: 300000,
/** /**
* *

@ -202,6 +202,35 @@ $prefix-cls: #{$namespace}-menu;
.#{$elNamespace}-menu-item { .#{$elNamespace}-menu-item {
padding-right: 0; padding-right: 0;
} }
&:not(.#{$elNamespace}-menu--collapse) {
.#{$elNamespace}-sub-menu__title,
.#{$elNamespace}-menu-item {
display: flex;
align-items: flex-start;
/* min-height: var(--el-menu-item-height);*/
height: auto;
padding-top: 10px;
padding-bottom: 10px;
white-space: normal;
}
/* .#{$elNamespace}-sub-menu__icon-arrow {
margin-top: 8px;
}*/
.#{$prefix-cls}__title {
display: block;
flex: 1;
min-width: 0;
overflow: visible;
text-overflow: initial;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
line-height: 1.4;
}
}
} }
} }
@ -228,6 +257,10 @@ $prefix-cls: #{$namespace}-menu;
} }
.#{$prefix-cls}__title { .#{$prefix-cls}__title {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* stylelint-disable-next-line */ /* stylelint-disable-next-line */
max-height: calc(var(--top-tool-height) - 2px) !important; max-height: calc(var(--top-tool-height) - 2px) !important;
/* stylelint-disable-next-line */ /* stylelint-disable-next-line */

@ -10,14 +10,10 @@ export const useRenderMenuTitle = () => {
return icon ? ( return icon ? (
<> <>
<Icon icon={meta.icon}></Icon> <Icon icon={meta.icon}></Icon>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap"> <span class="v-menu__title">{t(title as string)}</span>
{t(title as string)}
</span>
</> </>
) : ( ) : (
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap"> <span class="v-menu__title">{t(title as string)}</span>
{t(title as string)}
</span>
) )
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -41,6 +41,8 @@ import '@/plugins/tongji' // 百度统计
import Logger from '@/utils/Logger' import Logger from '@/utils/Logger'
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患 import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
import { hiPrintPlugin } from 'vue-plugin-hiprint'
import 'vue-plugin-hiprint/dist/print-lock.css'
// 创建实例 // 创建实例
const setupAll = async () => { const setupAll = async () => {
@ -56,6 +58,9 @@ const setupAll = async () => {
setupFormCreate(app) setupFormCreate(app)
app.use(hiPrintPlugin, '$hiprint', false)
hiPrintPlugin.disAutoConnect()
setupRouter(app) setupRouter(app)
// directives 指令 // directives 指令

@ -79,7 +79,9 @@ const whiteList = [
'/auth-redirect', '/auth-redirect',
'/bind', '/bind',
'/register', '/register',
'/oauthLogin/gitee' '/oauthLogin/gitee',
'/iot/report/dashboardPage/Dashboard8',
'/iot/report/dashboardPage/Dashboard1'
] ]
// 路由加载前 // 路由加载前

@ -605,6 +605,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
name: 'ErpCenter', name: 'ErpCenter',
meta: { hidden: true }, meta: { hidden: true },
children: [ children: [
{
path: 'mold-brand/detail/:id',
name: 'ErpMoldBrandDetail',
meta: {
title: '模具组详情',
noCache: true,
hidden: true,
activeMenu: '/erp/mold'
},
component: () => import('@/views/erp/mold/detail/brand.vue')
},
{ {
path: 'mold/detail/:id', path: 'mold/detail/:id',
name: 'ErpMoldDetail', name: 'ErpMoldDetail',
@ -642,6 +653,28 @@ const remainingRouter: AppRouteRecordRaw[] = [
canTo: true canTo: true
} }
}, },
{
path: '/iot',
component: Layout,
name: 'IotHiddenPages',
meta: {
hidden: true
},
children: [
{
path: 'runoverview',
component: () => import('@/views/iot/runoverview/index.vue'),
name: 'IotRunOverview',
meta: {
title: t('DataCollection.RunOverview.moduleName'),
hidden: true,
noTagsView: false,
canTo: true,
activeMenu: '/iot/runoverview'
}
}
]
},
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
@ -672,6 +705,39 @@ const remainingRouter: AppRouteRecordRaw[] = [
activeMenu: '/mes/plan' activeMenu: '/mes/plan'
}, },
component: () => import('@/views/mes/plan/index.vue') component: () => import('@/views/mes/plan/index.vue')
},
{
path: 'production-report/detail/:id',
name: 'MesProductionReportDetail',
meta: {
title: '生产报工详情',
noCache: true,
hidden: true,
activeMenu: '/mes/productionReport'
},
component: () => import('@/views/mes/productionReport/detail/index.vue')
},
{
path: 'device-ledger/detail/:id',
name: 'MesDeviceLedgerDetail',
meta: {
title: '设备台账详情',
noCache: true,
hidden: true,
activeMenu: '/mes/deviceledger'
},
component: () => import('@/views/mes/deviceledger/detail/index.vue')
},
{
path: 'device-ledger/detail/editIndex:id',
name: 'MesDeviceLedgerEditDetail',
meta: {
title: '编辑设备台账',
noCache: true,
hidden: true,
activeMenu: '/mes/deviceledger'
},
component: () => import('@/views/mes/deviceledger/detail/editIndex.vue')
} }
] ]
} }

@ -20,3 +20,7 @@
.icon-user-o:before { .icon-user-o:before {
content: '\f2c0'; content: '\f2c0';
} }
.el-table--small .cell {
padding: 0 2px;
}

@ -35,3 +35,19 @@
border-left-color: var(--el-color-primary); border-left-color: var(--el-color-primary);
} }
} }
/* 全局生效,所有表格的悬停色都会改变 */
.el-table--enable-row-hover .el-table__body tr:hover > td {
background-color: #f0f9eb !important;
}
/* 2. 关键补全:针对展开行内的 hover 状态 */
/* 展开行tr.expanded内部的 td 需要单独设置 */
.el-table__body .el-table__expanded-cell:hover > td {
background-color: #f0f9eb !important;
}
/* 3. 针对展开后触发的 hover-row 类(某些版本生效) */
.el-table__body tr.hover-row > td {
background-color: #f0f9eb !important;
}

@ -1,7 +1,7 @@
:root { :root {
--login-bg-color: #293146; --login-bg-color: #293146;
--left-menu-max-width: 200px; --left-menu-max-width: 220px;
--left-menu-min-width: 64px; --left-menu-min-width: 64px;

@ -142,6 +142,7 @@ export enum DICT_TYPE {
// ========== INFRA 模块 ========== // ========== INFRA 模块 ==========
INFRA_BOOLEAN_STRING = 'infra_boolean_string', INFRA_BOOLEAN_STRING = 'infra_boolean_string',
FAILURE_LEVEL = 'failure_level',
INFRA_JOB_STATUS = 'infra_job_status', INFRA_JOB_STATUS = 'infra_job_status',
INFRA_JOB_LOG_STATUS = 'infra_job_log_status', INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status', INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
@ -227,12 +228,15 @@ export enum DICT_TYPE {
ERP_AUDIT_STATUS = 'erp_audit_status',// ERP 审批状态 ERP_AUDIT_STATUS = 'erp_audit_status',// ERP 审批状态
ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type',// 库存明细的业务类型 ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type',// 库存明细的业务类型
ERP_MOLD_STATUS = 'erp_mold_status',// ERP 模具状态 ERP_MOLD_STATUS = 'erp_mold_status',// ERP 模具状态
SUBMOLD_TYPE = 'submold_type', // 子模具类型
ERP_MAINTAIN_TYPE = 'maintain_type',// ERP 保养类型 ERP_MAINTAIN_TYPE = 'maintain_type',// ERP 保养类型
ERP_AUTOCODE_IS_PADDING = "erp_autocode_is_padding", ERP_AUTOCODE_IS_PADDING = "erp_autocode_is_padding",
ERP_AUTOCODE_PADDING_TYPE = "erp_autocode_padding_type", ERP_AUTOCODE_PADDING_TYPE = "erp_autocode_padding_type",
ERP_AUTOCODE_CYCLEMETHOD = "erp_autocode_cyclemethod", ERP_AUTOCODE_CYCLEMETHOD = "erp_autocode_cyclemethod",
ERP_AUTOCODE_PARTTYPE = "erp_autocode_parttype", ERP_AUTOCODE_PARTTYPE = "erp_autocode_parttype",
MATERIAL_CLASSIFICATION_TYPE = 'material_classification_type',
SPARE_PARTS_LEVEL = 'spare_parts_level',
// ========== MES - 生产管理模块 ========== // ========== MES - 生产管理模块 ==========
@ -268,6 +272,7 @@ export enum DICT_TYPE {
MES_ZJ_PRODUCT = "mes_zj_product", MES_ZJ_PRODUCT = "mes_zj_product",
FILE_STATUS = "file_status", FILE_STATUS = "file_status",
Classification = "classification", Classification = "classification",
MES_JOB_TYPE = "mes_job_type",
//====iot //====iot
IOT_SIEMENS_TYPE = "iot_siemens_type", IOT_SIEMENS_TYPE = "iot_siemens_type",
IOT_MODBUS_TYPE = "iot_modbus_type", IOT_MODBUS_TYPE = "iot_modbus_type",
@ -290,7 +295,7 @@ export enum DICT_TYPE {
IOT_ALERT_LEVEL = "iot_alert_level", IOT_ALERT_LEVEL = "iot_alert_level",
IOT_PROTOCOL = "iot_protocol", IOT_PROTOCOL = "iot_protocol",
IOT_DEVICE_ATTRIBUTE_UNIT = "iot_device_attribute_unit", IOT_DEVICE_ATTRIBUTE_UNIT = "iot_device_attribute_unit",
IOT_ALARM_REGISTRATION = " alarm_registration", IOT_ALARM_REGISTRATION = "alarm_registration",
PRIMARY_FLAG = "primary_flag" PRIMARY_FLAG = "primary_flag"
} }

@ -1,83 +1,58 @@
<template> <template>
<div class="dv-repair-page">
<template v-if="!formVisible">
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<el-form <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px">
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
min-label-width="68px"
>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleCodeLabel')" prop="ruleCode"> <el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleCodeLabel')" prop="ruleCode">
<el-input <el-input v-model="queryParams.ruleCode"
v-model="queryParams.ruleCode" :placeholder="t('FactoryModeling.AutocodeRule.searchRuleCodePlaceholder')" clearable
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleCodePlaceholder')" @keyup.enter="handleQuery" class="!w-240px" />
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleNameLabel')" prop="ruleName"> <el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleNameLabel')" prop="ruleName">
<el-input <el-input v-model="queryParams.ruleName"
v-model="queryParams.ruleName" :placeholder="t('FactoryModeling.AutocodeRule.searchRuleNamePlaceholder')" clearable
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleNamePlaceholder')" @keyup.enter="handleQuery" class="!w-240px" />
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleDescLabel')" prop="ruleDesc"> <el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleDescLabel')" prop="ruleDesc">
<el-input <el-input v-model="queryParams.ruleDesc"
v-model="queryParams.ruleDesc" :placeholder="t('FactoryModeling.AutocodeRule.searchRuleDescPlaceholder')" clearable
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleDescPlaceholder')" @keyup.enter="handleQuery" class="!w-240px" />
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRemarkLabel')" prop="remark"> <el-form-item :label="t('FactoryModeling.AutocodeRule.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input <el-input v-model="queryParams.remark" :placeholder="t('FactoryModeling.AutocodeRule.searchRemarkPlaceholder')"
v-model="queryParams.remark" clearable @keyup.enter="handleQuery" class="!w-240px" />
:placeholder="t('FactoryModeling.AutocodeRule.searchRemarkPlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchIsEnableLabel')" prop="isEnable"> <el-form-item :label="t('FactoryModeling.AutocodeRule.searchIsEnableLabel')" prop="isEnable"
<el-select v-show="showAllFilters">
v-model="queryParams.isEnable" <el-select v-model="queryParams.isEnable"
:placeholder="t('FactoryModeling.AutocodeRule.searchIsEnablePlaceholder')" :placeholder="t('FactoryModeling.AutocodeRule.searchIsEnablePlaceholder')" clearable class="!w-240px">
clearable <el-option v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" :key="dict.value"
class="!w-240px" :label="dict.label" :value="dict.value" />
>
<el-option
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.searchButtonText') }}</el-button> <el-button @click="handleQuery">
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.resetButtonText') }}</el-button> <Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.searchButtonText') }}
<el-button </el-button>
type="primary" <el-button @click="resetQuery">
plain <Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.resetButtonText') }}
@click="openForm('create')" </el-button>
v-hasPermi="['erp:autocode-rule:create']" <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:autocode-rule:create']">
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.addButtonText') }} <Icon icon="ep:plus" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.addButtonText') }}
</el-button> </el-button>
<el-button <el-button type="success" plain @click="handleExport" :loading="exportLoading"
type="success" v-hasPermi="['erp:autocode-rule:export']">
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:autocode-rule:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.exportButtonText') }} <Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.exportButtonText') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
@ -98,17 +73,27 @@
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column label="ID" align="center" prop="id" /> --> <!-- <el-table-column label="ID" align="center" prop="id" /> -->
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleCodeColumn')" align="center" prop="ruleCode" sortable/> <el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleCodeColumn')" align="center" prop="ruleCode"
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleNameColumn')" align="center" prop="ruleName" sortable/> width="260px" sortable />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleNameColumn')" align="center" prop="ruleName"
width="200px" sortable />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableBarcodeTypeColumn')" align="center">
<template #default="scope">
<el-tag type="primary">{{ getBarcodeTypeLabel(scope.row.barcodeType) }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleDescColumn')" align="center" prop="ruleDesc" /> <el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleDescColumn')" align="center" prop="ruleDesc" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableMaxLengthColumn')" align="center" prop="maxLength" /> <el-table-column :label="t('FactoryModeling.AutocodeRule.tableMaxLengthColumn')" align="center"
prop="maxLength" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableIsPaddedColumn')" align="center" prop="isPadded"> <el-table-column :label="t('FactoryModeling.AutocodeRule.tableIsPaddedColumn')" align="center" prop="isPadded">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_AUTOCODE_IS_PADDING" :value="scope.row.isPadded" /> <dict-tag :type="DICT_TYPE.ERP_AUTOCODE_IS_PADDING" :value="scope.row.isPadded" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedCharColumn')" align="center" prop="paddedChar" /> <el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedCharColumn')" align="center"
<el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedMethodColumn')" align="center" prop="paddedMethod"> prop="paddedChar" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedMethodColumn')" align="center"
prop="paddedMethod">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_AUTOCODE_PADDING_TYPE" :value="scope.row.paddedMethod" /> <dict-tag :type="DICT_TYPE.ERP_AUTOCODE_PADDING_TYPE" :value="scope.row.paddedMethod" />
</template> </template>
@ -120,46 +105,31 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableOperateColumn')" align="center" fixed="right" width="200"> <el-table-column :label="t('FactoryModeling.AutocodeRule.tableOperateColumn')" align="center" fixed="right"
width="200">
<template #default="scope"> <template #default="scope">
<el-button <el-button link type="primary" @click="openForm('update', scope.row.id)"
link v-hasPermi="['erp:autocode-rule:update']">
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:autocode-rule:update']"
>
{{ t('FactoryModeling.AutocodeRule.tableEditAction') }} {{ t('FactoryModeling.AutocodeRule.tableEditAction') }}
</el-button> </el-button>
<el-button <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:autocode-rule:delete']">
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:autocode-rule:delete']"
>
{{ t('FactoryModeling.AutocodeRule.tableDeleteAction') }} {{ t('FactoryModeling.AutocodeRule.tableDeleteAction') }}
</el-button> </el-button>
<el-button <el-button link type="primary" @click="handleTestCode(scope.row.ruleCode)"
link v-hasPermi="['erp:autocode-rule:update']">
type="primary"
@click="handleTestCode(scope.row.ruleCode)"
v-hasPermi="['erp:autocode-rule:update']"
>
{{ t('FactoryModeling.AutocodeRule.tableTestCodeAction') }} {{ t('FactoryModeling.AutocodeRule.tableTestCodeAction') }}
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
:total="total" @pagination="getList" />
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap> </ContentWrap>
</template>
<!-- 表单弹窗添加/修改 --> <AutocodeRuleForm v-else ref="formRef" @success="getList" @closed="formVisible = false" />
<AutocodeRuleForm ref="formRef" @success="getList" /> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -173,12 +143,12 @@ import AutocodePartList from './components/AutocodePartList.vue'
/** 编码规则 列表 */ /** 编码规则 列表 */
defineOptions({ name: 'AutocodeRule' }) defineOptions({ name: 'AutocodeRule' })
const message = useMessage() // const message = useMessage()
const { t } = useI18n() // const { t } = useI18n()
const loading = ref(true) // const loading = ref(true)
const list = ref<AutocodeRuleVO[]>([]) // const list = ref<AutocodeRuleVO[]>([])
const total = ref(0) // const total = ref(0)
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
@ -193,8 +163,23 @@ const queryParams = reactive({
isEnable: undefined, isEnable: undefined,
createTime: [] createTime: []
}) })
const queryFormRef = ref() // const queryFormRef = ref()
const exportLoading = ref(false) // const exportLoading = ref(false)
const showAllFilters = ref(false)
const filterCount = 5
const formVisible = ref(false)
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const getBarcodeTypeLabel = (value: any) => {
const str = value === undefined || value === null ? '' : String(value)
if (str === '1') return '条形码'
if (str === '2') return '二维码'
return '-'
}
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
@ -223,38 +208,34 @@ const resetQuery = () => {
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
formVisible.value = true
nextTick(() => {
formRef.value.open(type, id) formRef.value.open(type, id)
})
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
//
await message.delConfirm() await message.delConfirm()
//
await AutocodeRuleApi.deleteAutocodeRule(id) await AutocodeRuleApi.deleteAutocodeRule(id)
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
//
await getList() await getList()
} catch {} } catch { }
} }
/** 测试编码按钮操作 */ /** 测试编码按钮操作 */
const handleTestCode = async (ruleCode: string) => { const handleTestCode = async (ruleCode: string) => {
try { try {
//
const data = await AutocodeRuleApi.getTestCode(ruleCode) const data = await AutocodeRuleApi.getTestCode(ruleCode)
message.success(data) message.success(data)
} catch { }
} catch {}
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {
//
await message.exportConfirm() await message.exportConfirm()
//
exportLoading.value = true exportLoading.value = true
const data = await AutocodeRuleApi.exportAutocodeRule(queryParams) const data = await AutocodeRuleApi.exportAutocodeRule(queryParams)
download.excel(data, t('FactoryModeling.AutocodeRule.exportFilename')) download.excel(data, t('FactoryModeling.AutocodeRule.exportFilename'))

@ -1,11 +1,17 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1200"> <div class="dv-repair-panel">
<div class="dv-repair-panel__header">
<div class="dv-repair-panel__title">{{ dialogTitle }}</div>
<el-button text @click="closeForm">
<Icon icon="ep:close" />
</el-button>
</div>
<div class="dv-repair-dialog" v-loading="formLoading">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
v-loading="formLoading"
> >
<el-form-item :label="t('FactoryModeling.AutocodeRule.dialogRuleCodeLabel')" prop="ruleCode"> <el-form-item :label="t('FactoryModeling.AutocodeRule.dialogRuleCodeLabel')" prop="ruleCode">
<el-input <el-input
@ -20,6 +26,12 @@
:placeholder="t('FactoryModeling.AutocodeRule.dialogRuleNamePlaceholder')" :placeholder="t('FactoryModeling.AutocodeRule.dialogRuleNamePlaceholder')"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.dialogBarcodeTypeLabel')" prop="barcodeType">
<el-radio-group v-model="formData.barcodeType">
<el-radio :label="1">{{ t('FactoryModeling.AutocodeRule.barcodeTypeBarcodeLabel') }}</el-radio>
<el-radio :label="2">{{ t('FactoryModeling.AutocodeRule.barcodeTypeQrcodeLabel') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.dialogRuleDescLabel')" prop="ruleDesc"> <el-form-item :label="t('FactoryModeling.AutocodeRule.dialogRuleDescLabel')" prop="ruleDesc">
<el-input <el-input
v-model="formData.ruleDesc" v-model="formData.ruleDesc"
@ -85,11 +97,13 @@
<AutocodePartForm ref="autocodePartFormRef" :rule-id="formData.id" /> <AutocodePartForm ref="autocodePartFormRef" :rule-id="formData.id" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<template #footer> </div>
<div class="dv-repair-footer">
<el-button @click="closeForm">{{ t('common.cancel') }}</el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('common.ok') }}</el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('common.ok') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button> </div>
</template> </div>
</Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict' import { getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
@ -99,17 +113,17 @@ import AutocodePartForm from './components/AutocodePartForm.vue'
/** 编码规则 表单 */ /** 编码规则 表单 */
defineOptions({ name: 'AutocodeRuleForm' }) defineOptions({ name: 'AutocodeRuleForm' })
const { t } = useI18n() // const { t } = useI18n()
const message = useMessage() // const message = useMessage()
const dialogVisible = ref(false) // const dialogTitle = ref('')
const dialogTitle = ref('') // const formLoading = ref(false)
const formLoading = ref(false) // 12 const formType = ref('')
const formType = ref('') // create - update -
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
ruleCode: undefined, ruleCode: undefined,
ruleName: undefined, ruleName: undefined,
barcodeType: 1,
ruleDesc: undefined, ruleDesc: undefined,
maxLength: undefined, maxLength: undefined,
isPadded: undefined, isPadded: undefined,
@ -123,47 +137,65 @@ const formRules = reactive({
{ required: true, message: t('FactoryModeling.AutocodeRule.validatorIsEnableRequired'), trigger: 'blur' } { required: true, message: t('FactoryModeling.AutocodeRule.validatorIsEnableRequired'), trigger: 'blur' }
] ]
}) })
const formRef = ref() // Ref const formRef = ref()
/** 子表的表单 */ /** 子表的表单 */
const subTabsName = ref('autocodePart') const subTabsName = ref('autocodePart')
const autocodePartFormRef = ref() const autocodePartFormRef = ref()
let openRequestId = 0
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
dialogVisible.value = true const currentOpenId = ++openRequestId
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm() resetForm()
//
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await AutocodeRuleApi.getAutocodeRule(id) const data = await AutocodeRuleApi.getAutocodeRule(id)
const barcodeType = Number(data?.barcodeType ?? data?.barCodeType) === 2 ? 2 : 1
formData.value = {
...data,
barcodeType
}
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} }
} }
defineExpose({ open }) // open
defineExpose({ open })
const emit = defineEmits(['success', 'closed'])
const closeForm = () => {
openRequestId++
emit('closed')
}
onBeforeUnmount(() => {
openRequestId++
})
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => { const submitForm = async () => {
//
await formRef.value.validate() await formRef.value.validate()
//
try { try {
await autocodePartFormRef.value.validate() await autocodePartFormRef.value.validate()
} catch (e) { } catch (e) {
subTabsName.value = 'autocodePart' subTabsName.value = 'autocodePart'
return return
} }
//
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as AutocodeRuleVO const barcodeType = Number(formData.value.barcodeType) === 2 ? 2 : 1
// const data = {
...formData.value,
barcodeType,
barCodeType: barcodeType
} as unknown as AutocodeRuleVO
data.autocodeParts = autocodePartFormRef.value.getData() data.autocodeParts = autocodePartFormRef.value.getData()
if (formType.value === 'create') { if (formType.value === 'create') {
await AutocodeRuleApi.createAutocodeRule(data) await AutocodeRuleApi.createAutocodeRule(data)
@ -172,9 +204,8 @@ const submitForm = async () => {
await AutocodeRuleApi.updateAutocodeRule(data) await AutocodeRuleApi.updateAutocodeRule(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false
//
emit('success') emit('success')
closeForm()
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -186,6 +217,7 @@ const resetForm = () => {
id: undefined, id: undefined,
ruleCode: undefined, ruleCode: undefined,
ruleName: undefined, ruleName: undefined,
barcodeType: 1,
ruleDesc: undefined, ruleDesc: undefined,
maxLength: undefined, maxLength: undefined,
isPadded: undefined, isPadded: undefined,
@ -197,3 +229,58 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style lang="scss" scoped>
.dv-repair-panel {
background: #fff;
border-radius: 12px;
box-shadow: var(--el-box-shadow-light);
}
.dv-repair-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
}
.dv-repair-panel__title {
color: #1f2937;
font-size: 18px;
font-weight: 600;
}
.dv-repair-dialog {
max-height: calc(100vh - 220px);
padding: 20px 20px 0;
padding-right: 16px;
overflow-y: auto;
}
.dv-repair-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
width: 100%;
padding: 16px 20px 20px;
border-top: 1px solid #ebeef5;
}
@media (max-width: 768px) {
.dv-repair-panel__header {
padding: 14px 16px;
}
.dv-repair-dialog {
max-height: none;
padding: 16px 16px 0;
overflow-y: visible;
}
.dv-repair-footer {
padding: 16px;
}
}
</style>

@ -1,11 +1,17 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1080"> <div class="stock-in-panel">
<div class="stock-in-panel__header">
<div class="stock-in-panel__title">{{ dialogTitle }}</div>
<el-button text @click="closeForm">
<Icon icon="ep:close" />
</el-button>
</div>
<div class="stock-in-dialog" v-loading="formLoading">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="110px" label-width="110px"
v-loading="formLoading"
:disabled="disabled" :disabled="disabled"
> >
<el-row :gutter="20"> <el-row :gutter="20">
@ -88,13 +94,15 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</ContentWrap> </ContentWrap>
<template #footer> </div>
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">
<div class="stock-in-footer">
<el-button @click="closeForm">{{ t('common.cancel') }}</el-button>
<el-button v-if="!disabled" @click="submitForm" type="primary" :disabled="formLoading">
{{ t('common.ok') }} {{ t('common.ok') }}
</el-button> </el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button> </div>
</template> </div>
</Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { StockInApi, StockInVO } from '@/api/erp/stock/in' import { StockInApi, StockInVO } from '@/api/erp/stock/in'
@ -107,7 +115,6 @@ defineOptions({ name: 'StockInForm' })
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - detail - const formType = ref('') // create - update - detail -
@ -158,7 +165,6 @@ const itemFormRef = ref()
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm() resetForm()
@ -177,8 +183,12 @@ const open = async (type: string, id?: number) => {
} }
defineExpose({ open }) // open defineExpose({ open }) // open
const closeForm = () => {
emit('closed')
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success', 'closed']) // success closed
const submitForm = async () => { const submitForm = async () => {
// //
await formRef.value.validate() await formRef.value.validate()
@ -194,9 +204,9 @@ const submitForm = async () => {
await StockInApi.updateStockIn(data) await StockInApi.updateStockIn(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false
// //
emit('success') emit('success')
closeForm()
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -215,3 +225,57 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style lang="scss" scoped>
.stock-in-panel {
background: #fff;
border-radius: 12px;
box-shadow: var(--el-box-shadow-light);
}
.stock-in-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
}
.stock-in-panel__title {
color: #1f2937;
font-size: 18px;
font-weight: 600;
}
.stock-in-dialog {
max-height: calc(100vh - 220px);
padding: 20px 20px 0;
padding-right: 16px;
overflow-y: auto;
}
.stock-in-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
width: 100%;
padding: 16px 20px 20px;
border-top: 1px solid #ebeef5;
}
@media (max-width: 768px) {
.stock-in-panel__header {
padding: 14px 16px;
}
.stock-in-dialog {
max-height: none;
padding: 16px 16px 0;
overflow-y: visible;
}
.stock-in-footer {
padding: 16px;
}
}
</style>

@ -21,6 +21,7 @@
v-model="row.warehouseId" v-model="row.warehouseId"
clearable clearable
filterable filterable
disabled="true"
:placeholder="t('SparePartsManagement.SpareIn.placeholderWarehouse')" :placeholder="t('SparePartsManagement.SpareIn.placeholderWarehouse')"
@change="onChangeWarehouse($event, row)" @change="onChangeWarehouse($event, row)"
> >
@ -34,6 +35,29 @@
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemProductCode')" min-width="150">
<template #default="{ row, $index }">
<!-- <el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>-->
<el-form-item :prop="`${$index}.productBarCode`" class="mb-0px!">
<el-select
v-model="row.productBarCode"
clearable
filterable
@change="onChangeProductCode($event, row)"
:placeholder="t('SparePartsManagement.SpareIn.placeholderProduct')"
>
<el-option
v-for="item in productList"
:key="item.barCode"
:label="item.barCode"
:value="item.barCode"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemProductName')" min-width="180"> <el-table-column :label="t('SparePartsManagement.SpareIn.itemProductName')" min-width="180">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!"> <el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
@ -61,13 +85,6 @@
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemProductCode')" min-width="150">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemUnitName')" min-width="80"> <el-table-column :label="t('SparePartsManagement.SpareIn.itemUnitName')" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-form-item class="mb-0px!"> <el-form-item class="mb-0px!">
@ -230,7 +247,7 @@ const getSummaries = (param: SummaryMethodProps) => {
const handleAdd = () => { const handleAdd = () => {
const row = { const row = {
id: undefined, id: undefined,
warehouseId: defaultWarehouse.value?.id, warehouseId: 10,
productId: undefined, productId: undefined,
productUnitName: undefined, // productUnitName: undefined, //
productBarCode: undefined, // productBarCode: undefined, //
@ -266,6 +283,19 @@ const onChangeProduct = (productId, row) => {
setStockCount(row) setStockCount(row)
} }
/** 处理产品变更 */
const onChangeProductCode = (productBarCode, row) => {
const product = productList.value.find((item) => item.barCode === productBarCode)
if (product) {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
row.productId = product.id
}
//
setStockCount(row)
}
/** 加载库存 */ /** 加载库存 */
const setStockCount = async (row) => { const setStockCount = async (row) => {
if (!row.productId || !row.warehouseId) { if (!row.productId || !row.warehouseId) {

@ -1,5 +1,6 @@
<template> <template>
<div class="stock-in-page">
<template v-if="!formVisible">
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<el-form <el-form
@ -52,7 +53,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareIn.warehouse')" prop="warehouseId"> <el-form-item :label="t('SparePartsManagement.SpareIn.warehouse')" prop="warehouseId" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.warehouseId" v-model="queryParams.warehouseId"
clearable clearable
@ -68,7 +69,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareIn.remark')" prop="remark"> <el-form-item :label="t('SparePartsManagement.SpareIn.remark')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" v-model="queryParams.remark"
:placeholder="t('SparePartsManagement.SpareIn.placeholderRemark')" :placeholder="t('SparePartsManagement.SpareIn.placeholderRemark')"
@ -77,6 +78,12 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -212,9 +219,10 @@
@pagination="getList" @pagination="getList"
/> />
</ContentWrap> </ContentWrap>
</template>
<!-- 表单弹窗添加/修改 --> <StockInForm v-else ref="formRef" @success="getList" @closed="formVisible = false" />
<StockInForm ref="formRef" @success="getList" /> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -253,6 +261,11 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 5
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) // const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) // const warehouseList = ref<WarehouseVO[]>([]) //
const supplierList = ref<SupplierVO[]>([]) // const supplierList = ref<SupplierVO[]>([]) //
@ -285,8 +298,12 @@ const resetQuery = () => {
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const formVisible = ref(false)
formRef.value.open(type, id)
const openForm = async (type: string, id?: number) => {
formVisible.value = true
await nextTick()
formRef.value?.open(type, id)
} }
/** 删除按钮操作 */ /** 删除按钮操作 */

@ -1,11 +1,17 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1080"> <div class="stock-out-panel">
<div class="stock-out-panel__header">
<div class="stock-out-panel__title">{{ dialogTitle }}</div>
<el-button text @click="closeForm">
<Icon icon="ep:close" />
</el-button>
</div>
<div class="stock-out-dialog" v-loading="formLoading">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
v-loading="formLoading"
:disabled="disabled" :disabled="disabled"
> >
<el-row :gutter="20"> <el-row :gutter="20">
@ -72,13 +78,15 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</ContentWrap> </ContentWrap>
<template #footer> </div>
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">
{{ t('action.save') }} <div class="stock-out-footer">
<el-button @click="closeForm">{{ t('common.cancel') }}</el-button>
<el-button v-if="!disabled" @click="submitForm" type="primary" :disabled="formLoading">
{{ t('common.ok') }}
</el-button> </el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button> </div>
</template> </div>
</Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { StockOutApi, StockOutVO } from '@/api/erp/stock/out' import { StockOutApi, StockOutVO } from '@/api/erp/stock/out'
@ -92,7 +100,6 @@ defineOptions({ name: 'StockOutForm' })
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - detail - const formType = ref('') // create - update - detail -
@ -146,7 +153,6 @@ const itemFormRef = ref()
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm() resetForm()
@ -163,8 +169,12 @@ const open = async (type: string, id?: number) => {
} }
defineExpose({ open }) // open defineExpose({ open }) // open
const closeForm = () => {
emit('closed')
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success', 'closed']) // success closed
const submitForm = async () => { const submitForm = async () => {
// //
await formRef.value.validate() await formRef.value.validate()
@ -180,9 +190,9 @@ const submitForm = async () => {
await StockOutApi.updateStockOut(data) await StockOutApi.updateStockOut(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false
// //
emit('success') emit('success')
closeForm()
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -201,3 +211,57 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style lang="scss" scoped>
.stock-out-panel {
background: #fff;
border-radius: 12px;
box-shadow: var(--el-box-shadow-light);
}
.stock-out-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
}
.stock-out-panel__title {
color: #1f2937;
font-size: 18px;
font-weight: 600;
}
.stock-out-dialog {
max-height: calc(100vh - 220px);
padding: 20px 20px 0;
padding-right: 16px;
overflow-y: auto;
}
.stock-out-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
width: 100%;
padding: 16px 20px 20px;
border-top: 1px solid #ebeef5;
}
@media (max-width: 768px) {
.stock-out-panel__header {
padding: 14px 16px;
}
.stock-out-dialog {
max-height: none;
padding: 16px 16px 0;
overflow-y: visible;
}
.stock-out-footer {
padding: 16px;
}
}
</style>

@ -1,5 +1,6 @@
<template> <template>
<div class="stock-out-page">
<template v-if="!formVisible">
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<el-form <el-form
@ -45,7 +46,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareOut.warehouse')" prop="warehouseId"> <el-form-item :label="t('SparePartsManagement.SpareOut.warehouse')" prop="warehouseId" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.warehouseId" v-model="queryParams.warehouseId"
clearable clearable
@ -61,7 +62,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareOut.remark')" prop="remark"> <el-form-item :label="t('SparePartsManagement.SpareOut.remark')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" v-model="queryParams.remark"
:placeholder="t('SparePartsManagement.SpareOut.placeholderRemark')" :placeholder="t('SparePartsManagement.SpareOut.placeholderRemark')"
@ -70,6 +71,12 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -205,9 +212,10 @@
@pagination="getList" @pagination="getList"
/> />
</ContentWrap> </ContentWrap>
</template>
<!-- 表单弹窗添加/修改 --> <StockOutForm v-else ref="formRef" @success="getList" @closed="formVisible = false" />
<StockOutForm ref="formRef" @success="getList" /> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -248,6 +256,11 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 5
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) // const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) // const warehouseList = ref<WarehouseVO[]>([]) //
@ -278,8 +291,12 @@ const resetQuery = () => {
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const formVisible = ref(false)
formRef.value.open(type, id)
const openForm = async (type: string, id?: number) => {
formVisible.value = true
await nextTick()
formRef.value?.open(type, id)
} }
/** 删除按钮操作 */ /** 删除按钮操作 */

@ -11,7 +11,24 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="barCode"> <el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="barCode">
<el-input v-model="formData.barCode" :placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')" /> <el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
v-model="formData.barCode"
:placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')"
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -19,6 +36,11 @@
<el-input v-model="formData.name" :placeholder="t('SparePartsManagement.SpareInfo.placeholderName')" /> <el-input v-model="formData.name" :placeholder="t('SparePartsManagement.SpareInfo.placeholderName')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.standard')" prop="standard">
<el-input v-model="formData.standard" :placeholder="t('SparePartsManagement.SpareInfo.placeholderStandard')" />
</el-form-item>
</el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.unit')" prop="unitId"> <el-form-item :label="t('SparePartsManagement.SpareInfo.unit')" prop="unitId">
<el-select v-model="formData.unitId" clearable :placeholder="t('SparePartsManagement.SpareInfo.placeholderUnit')" class="w-1/1"> <el-select v-model="formData.unitId" clearable :placeholder="t('SparePartsManagement.SpareInfo.placeholderUnit')" class="w-1/1">
@ -31,11 +53,11 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <!-- <el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.standard')" prop="standard"> <el-form-item :label="t('SparePartsManagement.SpareInfo.standard')" prop="standard">
<el-input v-model="formData.standard" :placeholder="t('SparePartsManagement.SpareInfo.placeholderStandard')" /> <el-input v-model="formData.standard" :placeholder="t('SparePartsManagement.SpareInfo.placeholderStandard')" />
</el-form-item> </el-form-item>
</el-col> </el-col>-->
<!-- <el-col :span="12"> <!-- <el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.expiryDay')" prop="expiryDay"> <el-form-item :label="t('SparePartsManagement.SpareInfo.expiryDay')" prop="expiryDay">
<el-input-number <el-input-number
@ -82,6 +104,27 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-if="formType === 'update'" :span="24">
<el-form-item :label="t('SparePartsManagement.SpareInfo.qrcode')" prop="qrcodeUrl">
<QrcodeActionCard
:image-url="formData.qrcodeUrl"
:print-id="formData.id"
:print-template-type="5"
:print-title="`${formData.name || '备件'}码打印预览`"
:print-paper-width="80"
:print-paper-height="80"
:print-max-width="220"
:empty-text="t('SparePartsManagement.SpareInfo.qrcodeEmpty')"
:error-text="t('SparePartsManagement.SpareInfo.qrcodeLoadError')"
:refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!formData.id || !formData.barCode"
refresh-confirm-text="确认刷新该备件二维码吗?"
:template-json="formData.templateJson"
:print-data="buildPrintData()"
@refresh-success="handleQrcodeRefreshSuccess"
/>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item :label="t('SparePartsManagement.SpareInfo.remark')" prop="remark"> <el-form-item :label="t('SparePartsManagement.SpareInfo.remark')" prop="remark">
<el-input type="textarea" v-model="formData.remark" :placeholder="t('SparePartsManagement.SpareInfo.placeholderRemark')"/> <el-input type="textarea" v-model="formData.remark" :placeholder="t('SparePartsManagement.SpareInfo.placeholderRemark')"/>
@ -98,8 +141,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ProductApi, ProductVO } from '@/api/erp/product/product' import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit' import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
/** ERP 产品 表单 */ /** ERP 产品 表单 */
@ -116,6 +159,9 @@ const formData = ref({
id: undefined, id: undefined,
name: undefined, name: undefined,
barCode: undefined, barCode: undefined,
isCode: undefined,
qrcodeUrl: undefined,
templateJson: undefined,
categoryId: undefined, categoryId: undefined,
unitId: undefined, unitId: undefined,
status: undefined, status: undefined,
@ -128,9 +174,20 @@ const formData = ref({
minPrice: undefined, minPrice: undefined,
safetyNumber: undefined safetyNumber: undefined
}) })
const validateBarCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('SparePartsManagement.SpareInfo.validatorCodeRequired')))
return
}
callback()
}
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorNameRequired'), trigger: 'blur' }], name: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorNameRequired'), trigger: 'blur' }],
barCode: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCodeRequired'), trigger: 'blur' }], barCode: [{ validator: validateBarCode, trigger: ['blur', 'change'] }],
categoryId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCategoryRequired'), trigger: 'blur' }], categoryId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCategoryRequired'), trigger: 'blur' }],
unitId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorUnitRequired'), trigger: 'blur' }], unitId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorUnitRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorStatusRequired'), trigger: 'blur' }] status: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorStatusRequired'), trigger: 'blur' }]
@ -148,7 +205,15 @@ const open = async (type: string, id?: number) => {
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await ProductApi.getProduct(id) const productData = await ProductApi.getProduct(id)
const templateJson = productData?.templateJson
const parsedTemplateJson = typeof templateJson === 'string'
? JSON.parse(templateJson)
: templateJson
formData.value = {
...productData,
templateJson: parsedTemplateJson
}
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -158,6 +223,43 @@ const open = async (type: string, id?: number) => {
} }
defineExpose({ open }) // open defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.barCode = undefined
}
formRef.value?.clearValidate('barCode')
}
const getQrcodeRefreshUrl = () => {
if (!formData.value.id || !formData.value.barCode) return ''
return `/erp/product/regenerate-code?id=${formData.value.id}&code=${encodeURIComponent(String(formData.value.barCode))}`
}
const buildPrintData = () => {
return {
id: formData.value.id,
barCode: formData.value.barCode,
name: formData.value.name,
standard: formData.value.standard,
unitId: formData.value.unitId,
safetyNumber: formData.value.safetyNumber,
status: formData.value.status,
remark: formData.value.remark,
qrcodeUrl: formData.value.qrcodeUrl
}
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!formData.value.id) return
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
return
}
const productData = await ProductApi.getProduct(formData.value.id)
formData.value.qrcodeUrl = productData?.qrcodeUrl
formData.value.barCode = productData?.barCode ?? formData.value.barCode
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const submitForm = async () => { const submitForm = async () => {
@ -189,6 +291,9 @@ const resetForm = () => {
id: undefined, id: undefined,
name: undefined, name: undefined,
barCode: undefined, barCode: undefined,
isCode: true,
qrcodeUrl: undefined,
templateJson: undefined,
categoryId: undefined, categoryId: undefined,
unitId: undefined, unitId: undefined,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
@ -203,3 +308,4 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style scoped lang="scss"></style>

@ -11,6 +11,15 @@
:inline="true" :inline="true"
min-label-width="68px" min-label-width="68px"
> >
<el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="code">
<el-input
v-model="queryParams.code"
:placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareInfo.name')" prop="name"> <el-form-item :label="t('SparePartsManagement.SpareInfo.name')" prop="name">
<el-input <el-input
v-model="queryParams.name" v-model="queryParams.name"
@ -20,6 +29,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -55,9 +65,10 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('SparePartsManagement.SpareInfo.code')" align="center" prop="barCode" sortable /> <el-table-column :label="t('SparePartsManagement.SpareInfo.code')" align="center" prop="barCode" width="240px" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.name')" align="left" prop="name" width="220px" sortable /> <el-table-column :label="t('SparePartsManagement.SpareInfo.name')" align="left" prop="name" width="220px" sortable />
<el-table-column v-if="checkPermi(['erp:component:standard'])" :label="t('SparePartsManagement.SpareInfo.standard')" align="center" prop="standard" /> <el-table-column :label="t('SparePartsManagement.SpareInfo.standard')" align="center" prop="standard" />
<!-- <el-table-column v-if="checkPermi(['erp:component:standard'])" :label="t('SparePartsManagement.SpareInfo.deviceSpec')" align="center" prop="deviceSpec" sortable />-->
<el-table-column :label="t('SparePartsManagement.SpareInfo.category')" align="center" prop="categoryName" sortable /> <el-table-column :label="t('SparePartsManagement.SpareInfo.category')" align="center" prop="categoryName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.unit')" align="center" prop="unitName" sortable /> <el-table-column :label="t('SparePartsManagement.SpareInfo.unit')" align="center" prop="unitName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.safetyNumber')" align="center" prop="safetyNumber" /> <el-table-column :label="t('SparePartsManagement.SpareInfo.safetyNumber')" align="center" prop="safetyNumber" />
@ -176,6 +187,7 @@ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
code: undefined,
categoryId: undefined categoryId: undefined
}) })
const queryFormRef = ref() // const queryFormRef = ref() //

@ -52,7 +52,7 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareRecord.createTime')" prop="createTime"> <el-form-item :label="t('SparePartsManagement.SpareRecord.createTime')" prop="createTime" v-show="showAllFilters">
<el-date-picker <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
@ -63,6 +63,12 @@
class="!w-220px" class="!w-220px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -91,7 +97,7 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('SparePartsManagement.SpareRecord.product')" align="left" sortable prop="productName" width="210px"/> <el-table-column :label="t('SparePartsManagement.SpareRecord.product')" align="left" sortable prop="productName" width="300px"/>
<el-table-column :label="t('SparePartsManagement.SpareRecord.unit')" align="center" prop="unitName" sortable /> <el-table-column :label="t('SparePartsManagement.SpareRecord.unit')" align="center" prop="unitName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareRecord.warehouse')" align="center" prop="warehouseName" sortable /> <el-table-column :label="t('SparePartsManagement.SpareRecord.warehouse')" align="center" prop="warehouseName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareRecord.bizType')" align="center" prop="bizType" min-width="120" sortable> <el-table-column :label="t('SparePartsManagement.SpareRecord.bizType')" align="center" prop="bizType" min-width="120" sortable>
@ -173,6 +179,11 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 4
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) // const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) // const warehouseList = ref<WarehouseVO[]>([]) //

@ -43,6 +43,12 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -118,6 +124,11 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 2
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) // const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) // const warehouseList = ref<WarehouseVO[]>([]) //

@ -1,210 +1,245 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible"> <Dialog :title="dialogTitle" v-model="dialogVisible" width="720px">
<el-form <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
ref="formRef" <el-row :gutter="16">
:model="formData" <el-col :span="12">
:rules="formRules" <el-form-item :label="t('MoldManagement.MoldBrandFormPage.code')" prop="code">
label-width="100px" <el-row :gutter="20" style="width: 100%">
v-loading="formLoading" <el-col :span="18">
> <el-input v-model="formData.code" :placeholder="t('MoldManagement.MoldBrandFormPage.placeholderCode')" :disabled="Boolean(formData.isCode) || formType === 'update'" />
<el-form-item :label="t('MoldManagement.MoldBrand.code')" prop="code"> </el-col>
<el-input <el-col :span="6">
v-model="formData.code" <div>
:placeholder="t('MoldManagement.MoldBrand.placeholderCode')" <el-switch v-model="formData.isCode" :disabled="formType === 'update'" @change="handleCodeAutoChange" />
:disabled="formType == 'update'" </div>
/> </el-col>
</el-row>
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.name')" prop="name"> </el-col>
<el-input <el-col :span="12">
v-model="formData.name" <el-form-item :label="t('MoldManagement.MoldBrandFormPage.name')" prop="name">
:placeholder="t('MoldManagement.MoldBrand.placeholderName')" <el-input v-model="formData.name" :placeholder="t('MoldManagement.MoldBrandFormPage.placeholderName')" />
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.moldType')" prop="moldType"> </el-col>
<el-input <el-col :span="12">
v-model="formData.moldType" <el-form-item :label="t('MoldManagement.MoldBrandFormPage.productName')" prop="productIds">
:placeholder="t('MoldManagement.MoldBrand.placeholderMoldType')" <el-select v-model="formData.productIds" multiple filterable clearable :placeholder="t('MoldManagement.MoldBrandFormPage.placeholderProduct')" class="w-1/1" @change="handleProductChange">
/> <el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item prop="orgType"> </el-col>
<template #label> <el-col :span="12">
<span> <el-form-item :label="t('MoldManagement.MoldBrandFormPage.version')" prop="version">
{{ t('MoldManagement.MoldBrand.orgType') }} <el-input v-model="formData.version" :placeholder="t('MoldManagement.MoldBrandFormPage.placeholderVersion')" @input="handleVersionInput" />
<el-tooltip :content="t('MoldManagement.MoldBrand.orgTypeTooltip')" placement="top"> </el-form-item>
<Icon icon="ep:question-filled" /> </el-col>
</el-tooltip> <el-col :span="12">
</span> <el-form-item :label="t('MoldManagement.MoldBrandFormPage.status')" prop="status">
</template> <el-select v-model="formData.status" :placeholder="t('MoldManagement.MoldBrandFormPage.placeholderStatus')" class="w-1/1">
<el-select <el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
v-model="formData.orgType"
:placeholder="t('MoldManagement.MoldBrand.placeholderOrgType')"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.MES_ORG_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
<el-form-item :label="t('MoldManagement.MoldBrand.moldSize')" prop="moldSize"> <el-col :span="12">
<el-input <el-form-item :label="t('MoldManagement.MoldBrandFormPage.moldSize')" prop="moldSize">
v-model="formData.moldSize" <el-input-number v-model="formData.moldSize" :min="1" :precision="0" class="w-1/1" />
:placeholder="t('MoldManagement.MoldBrand.placeholderMoldSize')"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.useTime')" prop="useTime"> </el-col>
<el-input-number <el-col :span="12">
v-model="formData.useTime" <el-form-item :label="t('MoldManagement.MoldBrandFormPage.useTime')" prop="useTime">
:min="0" <el-input-number v-model="formData.useTime" :min="0" class="w-1/1" />
class="!w-1/1"
:placeholder="t('MoldManagement.MoldBrand.placeholderUseTime')"
/>
</el-form-item> </el-form-item>
<!-- <el-form-item label="维保模式" prop="maintainType"> </el-col>
<el-select v-model="formData.maintainType" placeholder="请选择维保模式"> <el-col :span="24">
<el-option <el-form-item :label="t('MoldManagement.MoldBrandFormPage.images')" prop="images">
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_MAINTAIN_TYPE)" <UploadImg v-model="formData.images" />
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="维保周期" prop="maintainTime"> </el-col>
<el-input-number <el-col :span="24">
v-model="formData.maintainTime" <el-form-item :label="t('MoldManagement.MoldBrandFormPage.remark')" prop="remark">
:min="0" <el-input v-model="formData.remark" type="textarea" :placeholder="t('MoldManagement.MoldBrandFormPage.placeholderRemark')" />
:precision="2" </el-form-item>
class="!w-1/1" </el-col>
placeholder="输入维保周期" <el-col :span="24">
/> <el-form-item :label="t('MoldManagement.MoldBrandFormPage.isEnable')" prop="isEnable">
</el-form-item> --> <el-radio-group v-model="formData.isEnable">
<el-radio :label="true">{{ t('MoldManagement.MoldBrandFormPage.enable') }}</el-radio>
<el-form-item :label="t('MoldManagement.MoldBrand.remark')" prop="remark"> <el-radio :label="false">{{ t('MoldManagement.MoldBrandFormPage.disable') }}</el-radio>
<el-input </el-radio-group>
v-model="formData.remark"
:placeholder="t('MoldManagement.MoldBrand.placeholderRemark')"
type="textarea"
/>
</el-form-item> </el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> <el-button type="primary" :disabled="formLoading" @click="submitForm">{{ t('MoldManagement.MoldBrandFormPage.save') }}</el-button>
{{ t('action.save') }} <el-button @click="dialogVisible = false">{{ t('MoldManagement.MoldBrandFormPage.close') }}</el-button>
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, DICT_TYPE, getStrDictOptions } from '@/utils/dict' import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { MoldBrandApi, MoldBrandVO } from '@/api/erp/mold' import { ProductApi, type ProductVO } from '@/api/erp/product/product'
import { ProductApi, ProductVO } from '@/api/erp/product/product' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
/** 模具型号 表单 */ import { useDictStoreWithOut } from '@/store/modules/dict'
defineOptions({ name: 'MoldBrandForm' }) defineOptions({ name: 'MoldBrandForm' })
const { t } = useI18n() // const { t } = useI18n()
const message = useMessage() // const message = useMessage()
const productList = ref<ProductVO[]>([]) // const dictStore = useDictStoreWithOut()
const dialogVisible = ref(false) // const productList = ref<ProductVO[]>([])
const dialogTitle = ref('') // const dialogVisible = ref(false)
const formLoading = ref(false) // 12 const dialogTitle = ref('')
const formType = ref('') // create - update - const formLoading = ref(false)
const formData = ref({ const formType = ref('')
id: undefined, const formRef = ref()
code: undefined, const statusOptions = computed(() => getIntDictOptions(DICT_TYPE.ERP_MOLD_STATUS))
name: undefined, const formData = ref<MoldBrandVO>({
moldType: undefined, id: undefined as unknown as number,
code: '',
name: '',
moldType: '',
productId: undefined, productId: undefined,
useTime: undefined, productName: '',
productIds: [],
images: '',
version: '',
status: 0,
useTime: 0,
maintainType: undefined, maintainType: undefined,
maintainTime: undefined, maintainTime: undefined,
moldSize: undefined, moldSize: 1,
remark: undefined, remark: '',
isEnable: undefined isEnable: true,
isCode: true
}) })
const validateCode = (_rule: any, value: any, callback: any) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('MoldManagement.MoldBrandFormPage.validatorCodeRequired')))
return
}
callback()
}
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.code = ''
}
formRef.value?.clearValidate('code')
}
const handleVersionInput = (value: string) => {
formData.value.version = value.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1')
}
const stripVersionPrefix = (val: any): string => {
if (!val) return ''
return String(val).replace(/^V/i, '')
}
const addVersionPrefix = (val: any): string => {
if (!val) return ''
const raw = String(val).replace(/^V/i, '')
return raw ? `V${raw}` : ''
}
const formRules = reactive({ const formRules = reactive({
code: [ code: [{ validator: validateCode, trigger: 'blur' }],
{ required: true, message: t('MoldManagement.MoldBrand.validatorCodeRequired'), trigger: 'blur' } name: [{ required: true, message: t('MoldManagement.MoldBrandFormPage.validatorNameRequired'), trigger: 'blur' }],
], moldType: [{ required: true, message: t('MoldManagement.MoldBrandFormPage.validatorNameRequired'), trigger: 'blur' }],
name: [ productIds: [{ required: true, message: t('MoldManagement.MoldBrandFormPage.validatorProductRequired'), trigger: 'change' }],
{ required: true, message: t('MoldManagement.MoldBrand.validatorNameRequired'), trigger: 'blur' } moldSize: [{ required: true, message: t('MoldManagement.MoldBrandFormPage.validatorMoldSizeRequired'), trigger: 'blur' }],
], isEnable: [{ required: true, message: t('MoldManagement.MoldBrandFormPage.validatorIsEnableRequired'), trigger: 'change' }]
moldType: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorMoldTypeRequired'), trigger: 'blur' }
],
productId: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorProductRequired'), trigger: 'blur' }
],
moldSize: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorMoldSizeRequired'), trigger: 'blur' }
]
}) })
const formRef = ref() // Ref
/** 打开弹窗 */ const resetForm = () => {
formData.value = {
id: undefined as unknown as number,
code: '',
name: '',
moldType: '',
productId: undefined,
productName: '',
productIds: [],
images: '',
version: '',
status: 1,
useTime: 0,
maintainType: undefined,
maintainTime: undefined,
moldSize: 1,
remark: '',
isEnable: true,
isCode: true
}
formRef.value?.resetFields()
}
const handleProductChange = (value?: number[] | number) => {
const ids = Array.isArray(value) ? value : value ? [value] : []
formData.value.productIds = ids
const names = productList.value.filter((item) => ids.includes(item.id)).map((p) => p.name)
formData.value.productName = names.join(',')
}
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = type === 'create' ? t('MoldManagement.MoldBrandFormPage.createTitle') : t('MoldManagement.MoldBrandFormPage.updateTitle')
formType.value = type formType.value = type
resetForm() resetForm()
// await dictStore.setDictMap()
if (id) { productList.value = await ProductApi.getMesProductSimpleList({ categoryType: 1 })
if (!id) return
formLoading.value = true formLoading.value = true
try { try {
formData.value = await MoldBrandApi.getMoldBrand(id) const data = await MoldBrandApi.getMoldBrand(id)
formData.value = {
...formData.value,
...data,
isCode: (data as any)?.isCode ?? false,
version: stripVersionPrefix(data?.version),
productIds: Array.isArray(data?.productIds)
? data.productIds
: data?.productId
? [data.productId]
: []
}
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
}
//
productList.value = await ProductApi.getMesProductSimpleList()
} }
defineExpose({ open }) // open defineExpose({ open })
const emit = defineEmits(['success'])
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => { const submitForm = async () => {
//
await formRef.value.validate() await formRef.value.validate()
//
formLoading.value = true formLoading.value = true
try { try {
formData.value.isEnable = true handleProductChange(formData.value.productIds)
const data = formData.value as unknown as MoldBrandVO const payload: MoldBrandVO = {
...formData.value,
version: addVersionPrefix(formData.value.version),
productIds: Array.isArray(formData.value.productIds) ? formData.value.productIds : [],
isEnable: Boolean(formData.value.isEnable)
}
if (formType.value === 'create') { if (formType.value === 'create') {
await MoldBrandApi.createMoldBrand(data) await MoldBrandApi.createMoldBrand(payload)
message.success(t('common.createSuccess')) message.success(t('MoldManagement.MoldBrandFormPage.createSuccess'))
} else { } else {
await MoldBrandApi.updateMoldBrand(data) await MoldBrandApi.updateMoldBrand(payload)
message.success(t('common.updateSuccess')) message.success(t('MoldManagement.MoldBrandFormPage.updateSuccess'))
} }
dialogVisible.value = false dialogVisible.value = false
//
emit('success') emit('success')
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} }
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
code: undefined,
name: undefined,
moldType: undefined,
productId: undefined,
useTime: undefined,
maintainType: undefined,
maintainTime: undefined,
moldSize: undefined,
remark: undefined,
isEnable: undefined
}
formRef.value?.resetFields()
}
</script> </script>

@ -1,18 +1,31 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible"> <Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="120px"
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item :label="t('MoldManagement.Mold.code')" prop="code"> <el-form-item :label="t('MoldManagement.Mold.code')" prop="code">
<el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input <el-input
v-model="formData.code" v-model="formData.code"
:placeholder="t('MoldManagement.Mold.placeholderCode')" :placeholder="t('MoldManagement.Mold.placeholderCode')"
:disabled="formType == 'update'" :disabled="formData.isCode == true || formType === 'update'"
/> />
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.Mold.name')" prop="name"> <el-form-item :label="t('MoldManagement.Mold.name')" prop="name">
<el-input <el-input
@ -20,6 +33,17 @@
:placeholder="t('MoldManagement.Mold.placeholderName')" :placeholder="t('MoldManagement.Mold.placeholderName')"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.MoldListPage.type')" prop="type">
<el-select v-model="formData.type" clearable :placeholder="t('MoldManagement.MoldListPage.type')" class="w-1/1">
<el-option v-for="dict in getStrDictOptions(DICT_TYPE.SUBMOLD_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldListPage.installLocation')" prop="installLocation">
<el-input v-model="formData.installLocation" :placeholder="t('MoldManagement.MoldListPage.installLocation')" />
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldListPage.material')" prop="material">
<el-input v-model="formData.material" :placeholder="t('MoldManagement.MoldListPage.material')" />
</el-form-item>
<el-form-item :label="t('MoldManagement.Mold.unit')" prop="unitId"> <el-form-item :label="t('MoldManagement.Mold.unit')" prop="unitId">
<el-select <el-select
v-model="formData.unitId" v-model="formData.unitId"
@ -52,24 +76,24 @@
:placeholder="t('MoldManagement.Mold.placeholderInTime')" :placeholder="t('MoldManagement.Mold.placeholderInTime')"
/> />
</el-form-item> </el-form-item>
<el-form-item prop="status"> <el-form-item v-if="formType === 'update'" :label="t('MoldManagement.Mold.qrcode')" prop="qrcodeUrl">
<template #label> <QrcodeActionCard
<span> :image-url="formData.qrcodeUrl"
{{ t('MoldManagement.Mold.status') }} :print-id="formData.id"
<el-tooltip :content="t('MoldManagement.Mold.statusTooltip')" placement="top"> :print-template-type="4"
<Icon icon="ep:question-filled" /> :print-title="t('MoldManagement.MoldBrandDetail.qrcodePrintTitle', { name: formData.name || t('MoldManagement.MoldBrandDetail.qrcodeTitle') })"
</el-tooltip> :print-paper-width="80"
</span> :print-paper-height="80"
</template> :print-max-width="220"
<el-radio-group v-model="formData.status" :disabled = "formType == 'create'"> :empty-text="t('MoldManagement.Mold.qrcodeEmpty')"
<el-radio :error-text="t('MoldManagement.Mold.qrcodeLoadError')"
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_MOLD_STATUS)" :refresh-url="getQrcodeRefreshUrl()"
:key="dict.value" :refresh-disabled="!formData.id || !formData.code"
:label="dict.value" :refresh-confirm-text="t('MoldManagement.MoldBrandDetail.qrcodeRefreshConfirm')"
> :template-json="formData.templateJson"
{{ dict.label }} :print-data="buildPrintData()"
</el-radio> @refresh-success="handleQrcodeRefreshSuccess"
</el-radio-group> />
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.Mold.images')" prop="images"> <el-form-item :label="t('MoldManagement.Mold.images')" prop="images">
<UploadImg v-model="formData.images" /> <UploadImg v-model="formData.images" />
@ -97,17 +121,18 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="closeForm">{{ t('dialog.close') }}</el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> <el-button @click="submitForm" type="primary" :disabled="formLoading">
{{ t('action.save') }} {{ t('action.save') }}
</el-button> </el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, DICT_TYPE, getBoolDictOptions } from '@/utils/dict' import { getIntDictOptions, getStrDictOptions, DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
import { MoldBrandApi } from '@/api/erp/mold' import { MoldBrandApi } from '@/api/erp/mold'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit' import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
const unitList = ref<ProductUnitVO[]>([]) // const unitList = ref<ProductUnitVO[]>([]) //
const { t } = useI18n() // const { t } = useI18n() //
@ -120,21 +145,38 @@ const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
code: undefined, code: undefined,
isCode: undefined,
name: undefined, name: undefined,
type: undefined,
installLocation: undefined,
material: undefined,
unitId: undefined, unitId: undefined,
machineId: undefined, machineId: undefined,
useTime: undefined, useTime: undefined,
inTime: undefined, inTime: undefined,
status: undefined, status: undefined,
images: undefined, images: undefined,
qrcodeUrl: undefined,
templateJson: undefined,
remark: undefined, remark: undefined,
isEnable: undefined, isEnable: undefined,
fileUrl: '', fileUrl: '',
brandId: undefined brandId: undefined
}) })
const validateCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('MoldManagement.Mold.validatorCodeRequired')))
return
}
callback()
}
const formRules = reactive({ const formRules = reactive({
code: [ code: [
{ required: true, message: t('MoldManagement.Mold.validatorCodeRequired'), trigger: 'blur' } { validator: validateCode, trigger: ['blur', 'change'] }
], ],
name: [ name: [
{ required: true, message: t('MoldManagement.Mold.validatorNameRequired'), trigger: 'blur' } { required: true, message: t('MoldManagement.Mold.validatorNameRequired'), trigger: 'blur' }
@ -165,7 +207,15 @@ const open = async (type: string, id?: number, brandId: number) => {
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await MoldBrandApi.getMold(id) const moldData = await MoldBrandApi.getMold(id)
const templateJson = moldData?.templateJson
const parsedTemplateJson = typeof templateJson === 'string'
? JSON.parse(templateJson)
: templateJson
formData.value = {
...moldData,
templateJson: parsedTemplateJson
}
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -176,8 +226,46 @@ const open = async (type: string, id?: number, brandId: number) => {
} }
defineExpose({ open }) // open defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.code = undefined
}
formRef.value?.clearValidate('code')
}
const getQrcodeRefreshUrl = () => {
if (!formData.value.id || !formData.value.code) return ''
return `/erp/mold-brand/regenerate-code?id=${formData.value.id}&code=${encodeURIComponent(String(formData.value.code))}`
}
const buildPrintData = () => {
return {
id: formData.value.id,
code: formData.value.code,
name: formData.value.name,
unitId: formData.value.unitId,
machineId: formData.value.machineId,
useTime: formData.value.useTime,
inTime: formData.value.inTime,
status: formData.value.status,
remark: formData.value.remark,
qrcodeUrl: formData.value.qrcodeUrl
}
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!formData.value.id) return
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
return
}
const moldData = await MoldBrandApi.getMold(formData.value.id)
formData.value.qrcodeUrl = moldData?.qrcodeUrl
formData.value.code = moldData?.code ?? formData.value.code
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success', 'closed']) // success closed
const submitForm = async () => { const submitForm = async () => {
// //
await formRef.value.validate() await formRef.value.validate()
@ -189,10 +277,11 @@ const submitForm = async () => {
await MoldBrandApi.createMold(data) await MoldBrandApi.createMold(data)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
} else { } else {
await MoldBrandApi.updateMold(data) const { templateJson, ...updateData } = data
await MoldBrandApi.updateMold(updateData)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false closeForm()
// //
emit('success') emit('success')
} finally { } finally {
@ -200,11 +289,17 @@ const submitForm = async () => {
} }
} }
const closeForm = () => {
dialogVisible.value = false
emit('closed')
}
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: undefined,
code: undefined, code: undefined,
isCode: true,
name: undefined, name: undefined,
unitId: undefined, unitId: undefined,
machineId: undefined, machineId: undefined,
@ -212,6 +307,8 @@ const resetForm = () => {
inTime: undefined, inTime: undefined,
status: 3, status: 3,
images: undefined, images: undefined,
qrcodeUrl: undefined,
templateJson: undefined,
remark: undefined, remark: undefined,
isEnable: true, isEnable: true,
fileUrl: '', fileUrl: '',
@ -219,4 +316,7 @@ const resetForm = () => {
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style lang="scss" scoped>
</style>

@ -1,91 +1,109 @@
<template> <template>
<!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-form :model="queryParams" ref="queryFormRef" :inline="true" class="-mb-15px">
<el-form-item :label="t('MoldManagement.MoldListPage.code')" prop="code"><el-input v-model="queryParams.code"
:placeholder="t('MoldManagement.MoldListPage.placeholderCode')" clearable @keyup.enter="handleQuery"
class="!w-200px" /></el-form-item>
<el-form-item :label="t('MoldManagement.MoldListPage.name')" prop="name"><el-input v-model="queryParams.name"
:placeholder="t('MoldManagement.MoldListPage.placeholderName')" clearable @keyup.enter="handleQuery"
class="!w-200px" /></el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('MoldManagement.MoldListPage.query') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('MoldManagement.MoldListPage.reset') }}
</el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:mold-brand:create']"> <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:mold-brand:create']">
<Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }} <Icon icon="ep:plus" class="mr-5px" /> {{ t('MoldManagement.MoldListPage.addSubMold') }}
</el-button> </el-button>
<el-button <el-button type="success" plain @click="handleExport" :loading="exportLoading"
type="success" v-hasPermi="['erp:mold-brand:export']">
plain <Icon icon="ep:download" class="mr-5px" /> {{ t('MoldManagement.MoldListPage.export') }}
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:mold-brand:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button> </el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.Mold.code')" align="center" prop="code" sortable /> <el-table-column :label="t('MoldManagement.MoldListPage.index')" type="index" width="70" align="center" />
<el-table-column :label="t('MoldManagement.Mold.name')" align="left" prop="name" sortable /> <el-table-column :label="t('MoldManagement.MoldListPage.subMoldName')" prop="name" min-width="160" />
<!-- <el-table-column label="单位" align="center" prop="unitName" sortable /> --> <el-table-column :label="t('MoldManagement.MoldListPage.type')" min-width="120">
<el-table-column :label="t('MoldManagement.Mold.useTime')" align="center" prop="useTime" sortable />
<el-table-column :label="t('MoldManagement.Mold.inTime')" align="center" prop="inTime" :formatter="dateFormatter" sortable />
<el-table-column :label="t('MoldManagement.Mold.status')" align="center" prop="status" sortable>
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.SUBMOLD_TYPE" :value="scope.row.type" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('MoldManagement.Mold.machineName')" align="center" prop="machineName" sortable /> <el-table-column :label="t('MoldManagement.MoldListPage.installLocation')" min-width="140">
<!-- <el-table-column label="模具图片" align="center" prop="images" /> -->
<el-table-column :label="t('MoldManagement.Mold.remark')" align="center" prop="remark" />
<el-table-column :label="t('MoldManagement.Mold.isEnable')" align="center" prop="isEnable">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.isEnable" /> {{ scope.row.installLocation || scope.row.installPosition || scope.row.currentPosition ||
scope.row.machineName || '-' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('MoldManagement.Mold.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable /> <el-table-column :label="t('MoldManagement.MoldListPage.material')" min-width="120">
<el-table-column :label="t('MoldManagement.Mold.operate')" align="center" fixed="right" width="200px">
<template #default="scope"> <template #default="scope">
<el-button link @click="openDetail(scope.row.id)"> {{ scope.row.material || '-' }}
{{ t('MoldManagement.Mold.detail') }} </template>
</el-button> </el-table-column>
<!-- <el-button <el-table-column :label="t('MoldManagement.MoldListPage.quantity')" width="90" align="center">
link <template #default="scope">
type="success" {{ scope.row.quantity || scope.row.count || 1 }}
@click="openRecordForm('create', scope.row.id, scope.row.brandId)" </template>
v-hasPermi="['mes:mold-record:update']" </el-table-column>
> <el-table-column :label="t('MoldManagement.MoldListPage.pressureNetInfo')" min-width="180">
维保 <template #default="scope">
</el-button> --> <div>{{ t('MoldManagement.MoldListPage.lastReplace') }}{{ scope.row.pressureNetTime }}</div>
<el-button <div>{{ t('MoldManagement.MoldListPage.usedDays') }}{{ scope.row.pressureNetDays }}{{
link t('MoldManagement.MoldListPage.days') }}</div>
type="primary" <div v-if="scope.row.remark">{{ t('MoldManagement.MoldListPage.remark') }}{{ scope.row.remark }}</div>
@click="openForm('update', scope.row.id)" </template>
v-hasPermi="['erp:mold-brand:update']" </el-table-column>
> <el-table-column :label="t('MoldManagement.MoldListPage.operate')" fixed="right" width="220">
{{ t('action.edit') }} <template #default="scope">
</el-button> <el-button link type="primary" @click="openForm('update', scope.row.id)">{{
<el-button t('MoldManagement.MoldListPage.edit') }}</el-button>
link <el-button link type="danger" @click="handleDelete(scope.row.id)">{{ t('MoldManagement.MoldListPage.delete')
type="danger" }}</el-button>
@click="handleDelete(scope.row.id)" <el-button link type="warning" @click="openPressureNetRecord(scope.row.id)">{{
v-hasPermi="['erp:mold-brand:delete']" t('MoldManagement.MoldListPage.pressureNetRecord') }}</el-button>
>
{{ t('action.del') }}
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 分页 --> <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
<Pagination
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" /> @pagination="getList" />
</ContentWrap> </ContentWrap>
<!-- 表单弹窗添加/修改 -->
<MoldForm ref="formRef" @success="getList" /> <MoldForm ref="formRef" @success="getList" />
<!-- 表单弹窗添加/修改 --> <!-- 压网记录弹框 -->
<Dialog v-model="pressureNetDialogVisible" :title="t('MoldManagement.MoldListPage.pressureNetRecord')" width="700px">
<div class="pressure-net-dialog-content">
<el-table v-loading="pressureNetLoading" :data="pressureNetRecordList" :stripe="true"
:show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.MoldListPage.subMoldName')" prop="moldName" min-width="120" />
<el-table-column :label="t('MoldManagement.MoldListPage.pressureNetTime')" prop="pressureNetTime"
min-width="160" :formatter="dateFormatter" />
<el-table-column :label="t('MoldManagement.MoldListPage.createTime')" prop="createTime" min-width="160"
:formatter="dateFormatter" />
<el-table-column :label="t('MoldManagement.MoldListPage.remark')" prop="remark" min-width="160">
<template #default="scope">
{{ scope.row.remark || '-' }}
</template>
</el-table-column>
</el-table>
<Pagination :total="pressureNetTotal" v-model:page="pressureNetPageParams.pageNo"
v-model:limit="pressureNetPageParams.pageSize" @pagination="getPressureNetRecordList" />
</div>
</Dialog>
<MoldRecordForm ref="recordFormRef" @success="getList" /> <MoldRecordForm ref="recordFormRef" @success="getList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict' import { defineAsyncComponent, nextTick } from 'vue'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { dateFormatter } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { MoldBrandApi } from '@/api/erp/mold' import { MoldBrandApi } from '@/api/erp/mold'
import MoldForm from './MoldForm.vue'
import MoldRecordForm from "@/views/erp/mold/components/MoldRecordForm.vue";
const { t } = useI18n() // const { t } = useI18n()
const message = useMessage() // const message = useMessage() //
const MoldForm = defineAsyncComponent(() => import('./MoldForm.vue') as any)
const MoldRecordForm = defineAsyncComponent(() => import('@/views/erp/mold/components/MoldRecordForm.vue') as any)
const props = defineProps<{ const props = defineProps<{
brandId?: number // id brandId?: number // id
@ -97,8 +115,11 @@ const exportLoading = ref(false) // 导出的加载中
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
brandId: undefined as unknown brandId: undefined as unknown,
code: undefined,
name: undefined
}) })
const queryFormRef = ref()
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
@ -118,6 +139,12 @@ const handleQuery = () => {
getList() getList()
} }
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */ /** 监听主表的关联字段的变化,加载对应的子表数据 */
watch( watch(
() => props.brandId, () => props.brandId,
@ -126,16 +153,24 @@ watch(
return return
} }
queryParams.brandId = val queryParams.brandId = val
handleQuery() queryParams.code = undefined
queryParams.name = undefined
queryParams.pageNo = 1
getList()
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }
) )
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = async (type: string, id?: number) => {
if (!props.brandId) { if (!props.brandId) {
message.error('请选择一个模具型号') message.error(t('MoldManagement.MoldListPage.selectBrandTip'))
return
}
await nextTick()
if (!formRef.value || typeof formRef.value.open !== 'function') {
message.error(t('MoldManagement.MoldListPage.formNotReady'))
return return
} }
formRef.value.open(type, id, props.brandId) formRef.value.open(type, id, props.brandId)
@ -157,7 +192,7 @@ const handleDelete = async (id: number) => {
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
if (!props.brandId) { if (!props.brandId) {
message.error('请选择一个模具型号') message.error(t('MoldManagement.MoldListPage.selectBrandTip'))
return return
} }
try { try {
@ -167,7 +202,7 @@ const handleExport = async () => {
...queryParams, ...queryParams,
brandId: props.brandId brandId: props.brandId
}) })
download.excel(data, '模具.xls') download.excel(data, t('MoldManagement.MoldListPage.exportFilename'))
} catch { } catch {
} finally { } finally {
exportLoading.value = false exportLoading.value = false
@ -179,10 +214,41 @@ const recordFormRef = ref()
const openRecordForm = (type: string, id?: number, brandId?: number) => { const openRecordForm = (type: string, id?: number, brandId?: number) => {
recordFormRef.value.open(type, id, brandId) recordFormRef.value.open(type, id, brandId)
} }
const { push } = useRouter()
const openDetail = (id: number) => { /** 压网记录弹框 */
push({ name: 'ErpMoldDetail', params: { id } }) const pressureNetDialogVisible = ref(false)
const pressureNetLoading = ref(false)
const pressureNetRecordList = ref<any[]>([])
const pressureNetTotal = ref(0)
const pressureNetPageParams = reactive({
pageNo: 1,
pageSize: 10,
moldId: undefined as number | undefined
})
const openPressureNetRecord = (moldId: number) => {
pressureNetPageParams.moldId = moldId
pressureNetPageParams.pageNo = 1
pressureNetDialogVisible.value = true
getPressureNetRecordList()
}
const getPressureNetRecordList = async () => {
pressureNetLoading.value = true
try {
const data = await MoldBrandApi.getPressureNetRecordPage(pressureNetPageParams)
pressureNetRecordList.value = data.list ?? []
pressureNetTotal.value = data.total ?? 0
} finally {
pressureNetLoading.value = false
}
} }
</script> </script>
<style lang="scss" scoped>
.pressure-net-dialog-content {
max-height: 500px;
overflow-y: auto;
padding: 10px;
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,379 @@
<template>
<div class="mold-operate-page">
<div class="mold-operate-page__header">
<el-button @click="emit('back')">
<Icon icon="ep:arrow-left" class="mr-5px" /> {{ t('MoldManagement.MoldBrandPage.back') }}
</el-button>
</div>
<!-- 模具信息卡片 -->
<div class="mold-operate-page__info-card" v-if="mold">
<el-image v-if="getImageList(mold.images).length" :src="getImageList(mold.images)[0]"
fit="cover" class="mold-operate-page__info-image" />
<div class="mold-operate-page__info-empty" v-else></div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.moldCode') }}</span>
<span class="mold-operate-page__info-value">{{ mold.code }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.moldName') }}</span>
<span class="mold-operate-page__info-value">{{ mold.name }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.productName') }}</span>
<span class="mold-operate-page__info-value">{{ mold.productName }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.moldSize') }}</span>
<span class="mold-operate-page__info-value">{{ mold.moldSize }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.status') }}</span>
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="mold.status" />
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.currentDevice') }}</span>
<span class="mold-operate-page__info-value">{{ mold.currentDevice || '-' }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.lastChangeTime') }}</span>
<span class="mold-operate-page__info-value">{{ mold.lastChangeTime ?
dateFormatter(mold.lastChangeTime) : '-' }}</span>
</div>
</div>
<div class="mold-operate-page__body">
<!-- 左侧表单 -->
<div class="mold-operate-page__form">
<el-form ref="operateFormRef" :model="operateFormData" :rules="operateFormRules" label-width="100px">
<el-form-item :label="t('MoldManagement.MoldBrandPage.targetLine')">
<el-input v-model="displayTopCategory" disabled :placeholder="'选择目标设备后自动显示'" class="!w-full" />
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.targetDevice')" prop="deviceId">
<el-select v-model="operateFormData.deviceId" filterable clearable
:placeholder="t('MoldManagement.MoldBrandPage.placeholderTargetDevice')" class="!w-full">
<el-option v-for="device in deviceOptions" :key="device.id"
:label="`${device.deviceName}${device.deviceCode}`" :value="device.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.operateTime')" prop="operateTime">
<el-date-picker v-model="operateFormData.operateTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('MoldManagement.MoldBrandPage.placeholderOperateTime')" class="!w-full" />
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.operator')" prop="operatorId">
<el-select v-model="operateFormData.operatorId" filterable clearable
:placeholder="t('MoldManagement.MoldBrandPage.placeholderOperator')" class="!w-full">
<el-option v-for="item in operatorOptions" :key="String(item.id)" :label="item.nickname"
:value="String(item.id)" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.remark')" prop="remark">
<el-input v-model="operateFormData.remark"
:placeholder="t('MoldManagement.MoldBrandPage.placeholderRemark')" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<div class="mold-operate-page__form-actions">
<el-button @click="emit('back')">{{ t('MoldManagement.MoldBrandPage.cancel') }}</el-button>
<el-button type="primary" @click="submitOperateForm" :loading="operateLoading">{{
t('MoldManagement.MoldBrandPage.submit') }}</el-button>
</div>
</div>
<!-- 右侧记录列表 -->
<div class="mold-operate-page__history">
<div class="mold-operate-page__history-header">
<span class="mold-operate-page__history-title">{{ t('MoldManagement.MoldBrandPage.recentRecords')
}}</span>
</div>
<el-table :data="recentOperateList" :stripe="true" :show-overflow-tooltip="true" v-loading="recentLoading">
<el-table-column :label="t('MoldManagement.MoldBrandPage.operateType')">
<template #default="scope">
<span :class="scope.row.operateType === '1' ? 'text-primary' : 'text-success'">
{{ scope.row.operateType === '1' ? t('MoldManagement.MoldBrandPage.installUp') :
t('MoldManagement.MoldBrandPage.uninstallDown') }}
</span>
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandPage.moldName')" prop="moldName" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.lineName')" prop="lineName" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.operateTime')" prop="createTime" width="160"
:formatter="dateFormatter" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.operator')" prop="operatorName" width="100" />
</el-table>
<el-empty v-if="!recentOperateList.length" :description="t('MoldManagement.MoldBrandPage.noRecords')" />
<Pagination :total="recentTotal" v-model:page="recentPageNo" v-model:limit="recentPageSize"
@pagination="fetchRecentOperateList" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { DeviceLedgerApi, type DeviceLedgerVO } from '@/api/mes/deviceledger'
import { MoldOperateApi, type MoldOperateVO } from '@/api/mes/moldoperate'
import { DeviceLineApi, type DeviceLineTreeVO } from '@/api/mes/deviceline'
import { getSimpleUserList, type UserVO } from '@/api/system/user'
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
defineOptions({ name: 'MoldOperateView' })
const props = defineProps<{
mold: MoldBrandVO | null
type: number // 1: , 2:
deviceOptions: DeviceLedgerVO[]
}>()
const emit = defineEmits<{
(e: 'back'): void
(e: 'success'): void
}>()
const { t } = useI18n()
const message = useMessage()
const operateLoading = ref(false)
const operateFormRef = ref()
//
const operateFormData = reactive({
lineId: undefined as string | undefined,
deviceId: undefined as number | undefined,
operateTime: undefined as string | undefined,
operatorId: undefined as string | undefined,
remark: undefined as string | undefined
})
// 线
const operateFormRules = reactive({
deviceId: [{ required: true, message: t('MoldManagement.MoldBrandPage.validatorTargetDeviceRequired'), trigger: 'blur' }],
operateTime: [{ required: true, message: t('MoldManagement.MoldBrandPage.validatorOperateTimeRequired'), trigger: 'blur' }],
operatorId: [{ required: true, message: t('MoldManagement.MoldBrandPage.validatorOperatorRequired'), trigger: 'blur' }]
})
//
const deviceLineTree = ref<DeviceLineTreeVO[]>([])
const displayTopCategory = ref('')
const operatorOptions = ref<UserVO[]>([])
//
const recentOperateList = ref<MoldOperateVO[]>([])
const recentPageNo = ref(1)
const recentPageSize = ref(5)
const recentTotal = ref(0)
const recentLoading = ref(false)
const getImageList = (images?: string) => {
if (!images) return []
return String(images)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const initOperateOptions = async () => {
try {
const data = await DeviceLineApi.getDeviceLineTree()
deviceLineTree.value = Array.isArray(data) ? data : []
} catch {
deviceLineTree.value = []
}
try {
operatorOptions.value = (await getSimpleUserList()) ?? []
} catch {
operatorOptions.value = []
}
}
// 线
watch(() => operateFormData.deviceId, async (deviceId) => {
displayTopCategory.value = ''
if (!deviceId) return
try {
const detail = await DeviceLedgerApi.getDeviceLedger(deviceId)
if (detail?.deviceLine != null) {
operateFormData.lineId = detail.deviceLine
}
//
displayTopCategory.value = detail?.topCategoryName || ''
// fallback: 线
if (!displayTopCategory.value && detail?.deviceLine != null && deviceLineTree.value.length) {
const lineId = Number(detail.deviceLine)
const containsId = (nodes: DeviceLineTreeVO[] | undefined, id: number): boolean => {
if (!nodes) return false
return nodes.some((n) => n.id === id || containsId(n.children, id))
}
for (const root of deviceLineTree.value) {
if (root.id === lineId || containsId(root.children, lineId)) {
displayTopCategory.value = root.name
break
}
}
}
} catch {}
})
const fetchRecentOperateList = async () => {
if (!props.mold?.id) return
recentLoading.value = true
try {
const data = await MoldOperateApi.getMoldOperatePage({
pageNo: recentPageNo.value,
pageSize: recentPageSize.value,
moldId: props.mold.id
})
recentOperateList.value = data?.list ?? []
recentTotal.value = data?.total ?? 0
} catch {
recentOperateList.value = []
recentTotal.value = 0
} finally {
recentLoading.value = false
}
}
const submitOperateForm = async () => {
if (!operateFormRef.value) return
try {
await operateFormRef.value.validate()
operateLoading.value = true
const submitData = {
operateType: props.type,
moldId: props.mold?.id,
lineId: operateFormData.lineId,
lineName: displayTopCategory.value,
deviceId: operateFormData.deviceId,
remark: operateFormData.remark,
operateTime: operateFormData.operateTime,
operatorId: operateFormData.operatorId
}
await MoldOperateApi.createMoldOperate(submitData)
message.success(props.type === 1 ? t('MoldManagement.MoldBrandPage.moldUpSuccess') : t('MoldManagement.MoldBrandPage.moldDownSuccess'))
fetchRecentOperateList()
emit('success')
} catch (error) {
console.error(t('MoldManagement.MoldBrandPage.submitFailed'), error)
} finally {
operateLoading.value = false
}
}
/** 对外暴露 open 方法,用于初始化 */
const open = async () => {
operateFormData.lineId = undefined
operateFormData.deviceId = undefined
operateFormData.operateTime = undefined
operateFormData.operatorId = undefined
operateFormData.remark = undefined
displayTopCategory.value = ''
await initOperateOptions()
recentPageNo.value = 1
await fetchRecentOperateList()
}
defineExpose({ open })
</script>
<style scoped>
.mold-operate-page__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.mold-operate-page__info-card {
display: flex;
align-items: center;
gap: 24px;
padding: 20px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
margin-bottom: 20px;
}
.mold-operate-page__info-image {
width: 80px;
height: 80px;
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
flex-shrink: 0;
}
.mold-operate-page__info-empty {
width: 80px;
height: 80px;
border-radius: 8px;
border: 1px dashed var(--el-border-color-lighter);
flex-shrink: 0;
}
.mold-operate-page__info-item {
flex: 1;
min-width: 0;
}
.mold-operate-page__info-label {
display: block;
color: var(--el-text-color-secondary);
font-size: 12px;
margin-bottom: 4px;
}
.mold-operate-page__info-value {
display: block;
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mold-operate-page__body {
display: flex;
gap: 20px;
}
.mold-operate-page__form {
flex: 1;
background: #fff;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.mold-operate-page__form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--el-border-color-lighter);
}
.mold-operate-page__history {
width: 30vw;
background: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
max-height: 500px;
overflow-y: auto;
}
.mold-operate-page__history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.mold-operate-page__history-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>

@ -0,0 +1,922 @@
<template>
<ContentWrap>
<div v-loading="detailLoading" class="mold-brand-detail">
<div class="mold-brand-detail__header">
<el-button @click="goBack">
<Icon icon="ep:arrow-left" class="mr-5px" /> {{ t('MoldManagement.MoldBrandDetail.back') }}
</el-button>
</div>
<div class="mold-brand-detail__hero">
<div class="mold-brand-detail__left">
<div class="mold-brand-detail__image-card">
<el-image v-if="imageList.length" :src="imageList[0]" :preview-src-list="imageList" fit="cover"
preview-teleported class="mold-brand-detail__image" />
<el-empty v-else :description="t('MoldManagement.MoldBrandDetail.noImage')" />
</div>
<div class="mold-brand-detail__qr-card">
<div class="mold-brand-detail__qr-title">{{ t('MoldManagement.MoldBrandDetail.qrcodeTitle') }}</div>
<QrcodeActionCard :image-url="detailData?.qrCodeUrl" :print-id="detailData?.id" :print-template-type="4"
:print-title="t('MoldManagement.MoldBrandDetail.qrcodePrintTitle', { name: detailData?.name || t('MoldManagement.MoldBrandDetail.qrcodeTitle') })" :print-paper-width="80" :print-paper-height="80"
:print-max-width="220" :empty-text="t('MoldManagement.MoldBrandDetail.qrcodeEmpty')" :error-text="t('MoldManagement.MoldBrandDetail.qrcodeLoadError')" :refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!detailData?.id || !detailData?.code" :refresh-confirm-text="t('MoldManagement.MoldBrandDetail.qrcodeRefreshConfirm')"
@refresh-success="handleQrcodeRefreshSuccess" />
<div class="mold-brand-detail__qr-code">{{ detailData?.code || '-' }}</div>
</div>
</div>
<div class="mold-brand-detail__main">
<div class="mold-brand-detail__topbar">
<div class="mold-brand-detail__title-row">
<span class="mold-brand-detail__title">{{ detailData?.name || '-' }}<span v-if="detailData?.version">{{
detailData.version }}</span></span>
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="detailData?.status" />
</div>
</div>
<div class="mold-brand-detail__grid">
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.code') }}</span><strong>{{ detailData?.code || '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.currentDevice') }}</span><strong>{{ detailData?.currentDevice ||
detailData?.machineName
|| '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.productName') }}</span><strong>{{ detailData?.productName || '-' }}</strong>
</div>
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.version') }}</span><strong>{{ detailData?.version || '-' }}</strong></div>
<div class="mold-brand-detail__field mold-brand-detail__field--life">
<span>{{ t('MoldManagement.MoldBrandDetail.lifeStatus') }}</span>
<div v-if="lifeRate !== null" class="mold-brand-detail__life">
<el-progress :percentage="lifeRate" :stroke-width="10" :show-text="true" />
</div>
<strong v-else>-</strong>
</div>
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.childMoldCount') }}</span><strong>{{ childMolds.length || detailData?.moldSize
|| 0
}}</strong></div>
</div>
<div class="mold-brand-detail__tabs">
<el-tabs v-model="activeTab">
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabMolds')" name="molds">
<component :is="MoldListComp" :brand-id="brandId" />
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabInspection')" name="inspection">
<div v-loading="inspectionLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="inspectionDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true"
style="width: 340px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryInspection">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetInspection">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
<el-button type="success" plain :loading="inspectionExportLoading"
@click="handleExportInspection">{{ t('MoldManagement.MoldBrandDetail.export') }}</el-button>
</el-form-item>
</el-form>
<el-empty v-if="!inspectionStepGroups.length" />
<el-steps v-else direction="vertical" :active="inspectionStepGroups.length"
class="device-ledger-history-steps">
<el-step v-for="group in inspectionStepGroups" :key="group.key">
<template #title>
<div class="device-ledger-history-title">
<span class="device-ledger-history-time">[{{ group.time }}]</span>
<span class="device-ledger-history-operator">{{ t('MoldManagement.MoldBrandDetail.operator') }}: {{ group.operator }}</span>
</div>
</template>
<template #description>
<div class="device-ledger-history-items">
<div v-for="item in group.items" :key="item.key" class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag :type="getResultTagType(item.result)">{{ getResultLabel(item.result) }}</el-tag>
<span class="device-ledger-history-item-text">{{ item.name }}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.inspectionMethod') }}</span><span
class="device-ledger-history-item-value"><dict-tag type="Inspection_method"
:value="item.method" /></span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.criteria') }}</span><span
class="device-ledger-history-item-value">{{ item.criteria ?? '-' }}</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.inspectionTime') }}</span><span
class="device-ledger-history-item-value">{{ formatHistoryTime(item.taskTime) }}</span>
</div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.remark') }}</span><span
class="device-ledger-history-item-value">{{ item.remark ?? '-' }}</span></div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabRepair')" name="repair">
<div v-loading="repairLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.repairCode')">
<el-input v-model="repairQueryCode" :placeholder="t('MoldManagement.MoldBrandDetail.placeholderRepairCode')" clearable
@keyup.enter="handleQueryRepair" class="!w-200px" />
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandDetail.repairStatus')">
<el-select v-model="repairQueryStatus" :placeholder="t('MoldManagement.MoldBrandDetail.selectRepairStatus')"
clearable class="!w-150px">
<el-option :label="t('MoldManagement.MoldBrandDetail.repairStatusPending')" value="0" />
<el-option :label="t('MoldManagement.MoldBrandDetail.repairStatusDone')" value="1" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="repairDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true"
style="width: 340px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryRepair">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetRepair">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
<el-button type="success" plain :loading="repairExportLoading"
@click="handleExportRepair">{{ t('MoldManagement.MoldBrandDetail.export') }}</el-button>
</el-form-item>
</el-form>
<el-empty v-if="!repairList.length" />
<el-collapse v-else v-model="repairActiveNames" class="device-ledger-repair-collapse">
<el-collapse-item v-for="row in repairList" :key="row.id ?? row.repairCode" :name="String(row.id ?? row.repairCode)">
<template #title>
<div class="device-ledger-repair-title">
<span class="device-ledger-repair-name">{{ row.repairCode ?? '-' }}</span>
</div>
</template>
<div class="device-ledger-history-items">
<div class="device-ledger-history-item">
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.moldCode') }}</span><span
class="device-ledger-history-item-value">{{ row.moldCode ?? '-' }}</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.repairStatus') }}</span><span
class="device-ledger-history-item-value"><el-tag
:type="getRepairStatusTagType(row.status)">{{
getRepairStatusLabel(row.status) }}</el-tag></span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('EquipmentManagement.DvRepair.repairStatus') }}</span><span
class="device-ledger-history-item-value"><el-tag
:type="getRepairResultTagType(row.repairStatus)">{{
getRepairResultLabel(row.repairStatus) }}</el-tag></span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.requireDate') }}</span><span
class="device-ledger-history-item-value">{{ formatHistoryTime(row.requireDate) }}</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.finishDate') }}</span><span
class="device-ledger-history-item-value">{{ formatHistoryTime(row.finishDate) }}</span></div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabMaintain')" name="maintain">
<div v-loading="maintainLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="maintainDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true"
style="width: 340px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryMaintain">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetMaintain">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
<el-button type="success" plain :loading="maintainExportLoading"
@click="handleExportMaintain">{{ t('MoldManagement.MoldBrandDetail.export') }}</el-button>
</el-form-item>
</el-form>
<el-empty v-if="!maintainStepGroups.length" />
<el-steps v-else direction="vertical" :active="maintainStepGroups.length"
class="device-ledger-history-steps">
<el-step v-for="group in maintainStepGroups" :key="group.key">
<template #title>
<div class="device-ledger-history-title">
<span class="device-ledger-history-time">[{{ group.time }}]</span>
<span class="device-ledger-history-operator">{{ t('MoldManagement.MoldBrandDetail.operator') }}: {{ group.operator }}</span>
</div>
</template>
<template #description>
<div class="device-ledger-history-items">
<div v-for="item in group.items" :key="item.key" class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag :type="getResultTagType(item.result)">{{ getResultLabel(item.result) }}</el-tag>
<span class="device-ledger-history-item-text">{{ item.name }}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.maintainMethod') }}</span><span
class="device-ledger-history-item-value"><dict-tag type="Inspection_method"
:value="item.method" /></span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.criteria') }}</span><span
class="device-ledger-history-item-value">{{ item.criteria ?? '-' }}</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.maintainTime') }}</span><span
class="device-ledger-history-item-value">{{
String(formatHistoryTime(item.taskTime)).split(' ')[0] }}</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.remark') }}</span><span
class="device-ledger-history-item-value">{{ item.remark ?? '-' }}</span></div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabInstall')" name="install">
<div v-loading="installLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.operateType')">
<el-select v-model="installOperateType" :placeholder="t('MoldManagement.MoldBrandDetail.selectOperateType')"
clearable class="!w-150px">
<el-option :label="t('MoldManagement.MoldBrandDetail.moldUp')" value="1" />
<el-option :label="t('MoldManagement.MoldBrandDetail.moldDown')" value="2" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="installDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryInstall">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetInstall">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
</el-form-item>
</el-form>
<el-table :data="installRecords" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.MoldBrandDetail.moldName')" prop="moldName" min-width="150" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.deviceName')" prop="deviceName" min-width="120" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.operateType')" prop="operateType" min-width="100" align="center">
<template #default="scope">
<span v-if="scope.row.operateType === 1 || scope.row.operateType === '1'">{{ t('MoldManagement.MoldBrandDetail.moldUp') }}</span>
<span v-else-if="scope.row.operateType === 2 || scope.row.operateType === '2'">{{ t('MoldManagement.MoldBrandDetail.moldDown') }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandDetail.creatorName')" prop="creatorName" min-width="100" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.createTime')" prop="createTime" min-width="180" sortable>
<template #default="scope">{{ formatHistoryTime(scope.row.createTime) }}</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandDetail.remark')" prop="remark" min-width="140" />
</el-table>
<el-empty v-if="!installRecords.length" :description="t('MoldManagement.MoldBrandDetail.noInstallRecords')" />
<Pagination :total="installTotal" v-model:page="installPageNo" v-model:limit="installPageSize"
@pagination="fetchInstallRecords" />
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import download from '@/utils/download'
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { TicketManagementApi } from '@/api/mold/ticketManagement'
import { MoldRepairApi } from '@/api/mold/moldrepair'
import { MoldOperateApi } from '@/api/mes/moldoperate'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
defineOptions({ name: 'ErpMoldBrandDetail' })
const { t } = useI18n()
const message = useMessage()
const route = useRoute()
const router = useRouter()
const MoldListComp = defineAsyncComponent(() => import('../components/MoldList.vue') as any)
const brandId = computed(() => Number(route.params.id))
const detailLoading = ref(false)
const detailData = ref<(MoldBrandVO & Record<string, any>) | null>(null)
const childMolds = ref<any[]>([])
const activeTab = ref('molds')
const loadedTabs = ref<Set<string>>(new Set(['molds']))
const inspectionHistory = ref<any[]>([])
const maintainHistory = ref<any[]>([])
const repairList = ref<any[]>([])
const repairQueryCode = ref<string | undefined>()
const repairQueryStatus = ref<string | undefined>()
const installRecords = ref<any[]>([])
const installTotal = ref(0)
const installPageNo = ref(1)
const installPageSize = ref(10)
const installMoldId = ref<number | undefined>()
const installRemark = ref<string | undefined>()
const installDateRange = ref<string[] | undefined>()
const installOperateType = ref<string | undefined>()
const moldList = ref<any[]>([])
const repairActiveNames = ref<string[]>([])
const inspectionDateRange = ref<string[] | undefined>()
const maintainDateRange = ref<string[] | undefined>()
const repairDateRange = ref<string[] | undefined>()
const inspectionExportLoading = ref(false)
const maintainExportLoading = ref(false)
const repairExportLoading = ref(false)
const inspectionLoading = ref(false)
const repairLoading = ref(false)
const maintainLoading = ref(false)
const installLoading = ref(false)
const imageList = computed(() => parseImages(detailData.value?.images))
const lifeRate = computed<number | null>(() => {
const raw = detailData.value?.lifeRate ?? detailData.value?.lifeStatus ?? detailData.value?.useRate
if (raw === undefined || raw === null || raw === '') return null
const value = Number(raw)
return Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : null
})
const formatSimpleDate = (value: any) => {
if (!value) return '-'
const date = new Date(value)
return Number.isNaN(date.getTime()) ? String(value) : formatDate(date, 'YYYY-MM-DD')
}
const formatHistoryTime = (value: any) => {
const raw = value ?? '-'
if (Array.isArray(raw) && raw.length >= 3) {
const [y, m, d, hh, mm, ss] = raw
const pad = (n: any) => String(n).padStart(2, '0')
if (hh !== undefined) return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
return `${y}-${pad(m)}-${pad(d)}`
}
const asDate = new Date(raw)
if (!Number.isNaN(asDate.getTime())) return formatDate(asDate, 'YYYY-MM-DD HH:mm:ss')
return String(raw)
}
const parseImages = (value: any): string[] => {
if (!value) return []
if (Array.isArray(value)) return value.map(String).filter(Boolean)
return String(value)
.replace(/[`'\"]/g, '')
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const getResultLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (!v) return '-'
const upper = v.toUpperCase()
if (v === '0') return t('MoldManagement.MoldBrandDetail.pending')
if (v === '1' || upper === 'OK') return t('MoldManagement.MoldBrandDetail.pass')
if (v === '2' || upper === 'NG') return t('MoldManagement.MoldBrandDetail.fail')
return v
}
const getResultTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (!v) return 'info'
const upper = v.toUpperCase()
if (v === '1' || upper === 'OK') return 'success'
if (v === '2' || upper === 'NG') return 'danger'
return 'info'
}
const getRepairStatusLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '0') return t('MoldManagement.MoldBrandDetail.repairStatusPending')
if (v === '1') return t('MoldManagement.MoldBrandDetail.repairStatusDone')
return '-'
}
const getRepairStatusTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '1') return 'success'
if (v === '0') return 'warning'
return 'info'
}
const getRepairResultLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '0') return t('MoldManagement.MoldBrandDetail.repairResultPending')
if (v === '1') return t('MoldManagement.MoldBrandDetail.repairResultOk')
if (v === '2') return t('MoldManagement.MoldBrandDetail.repairResultNg')
return '-'
}
const getRepairResultTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '1') return 'success'
if (v === '2') return 'danger'
if (v === '0') return 'info'
return 'info'
}
type HistoryStepItem = {
key: string
name: string
result: any
method?: any
criteria?: any
remark?: any
taskTime?: any
}
type HistoryStepGroup = { key: string; time: string; operator: string; items: HistoryStepItem[] }
const buildStepGroups = (
rows: any[],
options: { timeField: string; nameFieldCandidates: string[]; resultFieldCandidates: string[] }
) => {
const groups = new Map<string, HistoryStepGroup>()
for (const row of rows ?? []) {
const time = formatHistoryTime(row?.taskTime ?? row?.[options.timeField] ?? row?.createTime)
const operator = String(row?.operator ?? row?.creatorName ?? row?.creator ?? '-')
const groupKey = `${row?.managementId ?? ''}__${time}__${operator}`
const name =
options.nameFieldCandidates
.map((key) => row?.[key])
.find((item) => item !== undefined && item !== null && String(item).trim() !== '') ?? '-'
const result =
options.resultFieldCandidates.map((key) => row?.[key]).find((item) => item !== undefined && item !== null) ?? undefined
const item: HistoryStepItem = {
key: String(row?.id ?? `${groupKey}_${name}`),
name: String(name),
result,
method: row?.inspectionMethod,
criteria: row?.judgmentCriteria,
remark: row?.remark,
taskTime: row?.taskTime
}
if (!groups.has(groupKey)) {
groups.set(groupKey, { key: groupKey, time, operator, items: [item] })
} else {
groups.get(groupKey)!.items.push(item)
}
}
return Array.from(groups.values()).sort((a, b) => String(b.time).localeCompare(String(a.time)))
}
const inspectionStepGroups = computed(() =>
buildStepGroups(inspectionHistory.value, {
timeField: 'inspectionTime',
nameFieldCandidates: ['inspectionItemName', 'name'],
resultFieldCandidates: ['inspectionResult']
})
)
const maintainStepGroups = computed(() =>
buildStepGroups(maintainHistory.value, {
timeField: 'inspectionTime',
nameFieldCandidates: ['maintainItemName', 'inspectionItemName', 'name'],
resultFieldCandidates: ['maintainResult', 'inspectionResult']
})
)
const getQrcodeRefreshUrl = () => {
if (!detailData.value?.id || !detailData.value?.code) return ''
return `/erp/mold-brand/regenerate-code?id=${detailData.value.id}&code=${encodeURIComponent(String(detailData.value.code))}`
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!detailData.value?.id) return
if (data?.qrcodeUrl) {
detailData.value.qrcodeUrl = data.qrcodeUrl
return
}
const moldData = await MoldBrandApi.getMoldBrand(detailData.value.id)
detailData.value.qrcodeUrl = moldData?.qrcodeUrl
detailData.value.code = moldData?.code ?? detailData.value.code
}
const goBack = () => {
router.push('/mold/mold-brand')
}
const goOperate = (type: number) => {
router.push({ path: '/mold/mold-brand', query: { operateType: String(type), moldId: String(detailData.value?.id) } })
}
const handleReservedAction = (action: string) => {
message.info(t('MoldManagement.MoldBrandDetail.reservedAction', { action }))
}
let getChildMoldsPromise: Promise<any[]> | null = null
const getChildMolds = async () => {
if (!brandId.value) return []
if (getChildMoldsPromise) return getChildMoldsPromise
getChildMoldsPromise = (async () => {
const pageSize = 10
const allList: any[] = []
let pageNo = 1
let total = 0
while (true) {
const data = await MoldBrandApi.getMoldPage({ pageNo, pageSize, brandId: brandId.value })
const list = data?.list ?? []
allList.push(...list)
total = Number(data?.total ?? allList.length)
if (allList.length >= total || list.length < pageSize) break
pageNo++
}
childMolds.value = allList
return childMolds.value
})()
try {
return await getChildMoldsPromise
} finally {
getChildMoldsPromise = null
}
}
const collectByChildMolds = async (worker: (moldId: number) => Promise<any>, mergeList = true) => {
const molds = childMolds.value.length ? childMolds.value : await getChildMolds()
const ids = molds.map((item) => Number(item.id)).filter(Boolean)
if (!ids.length) return []
const results = await Promise.all(ids.map((id) => worker(id).catch(() => [])))
return mergeList ? results.flat() : results
}
const fetchInspectionHistory = async () => {
if (!brandId.value) return
inspectionLoading.value = true
try {
const params: any = { moldId: brandId.value }
if (inspectionDateRange.value && inspectionDateRange.value.length === 2) {
params.startTime = inspectionDateRange.value[0]
params.endTime = inspectionDateRange.value[1]
}
const data = await TicketManagementApi.getInspectionByMoldId(params)
inspectionHistory.value = Array.isArray(data) ? data : []
} finally {
inspectionLoading.value = false
}
}
const fetchMaintainHistory = async () => {
if (!brandId.value) return
maintainLoading.value = true
try {
const params: any = { moldId: brandId.value }
if (maintainDateRange.value && maintainDateRange.value.length === 2) {
params.startTime = maintainDateRange.value[0]
params.endTime = maintainDateRange.value[1]
}
const data = await TicketManagementApi.getMaintenanceByMoldId(params)
maintainHistory.value = Array.isArray(data) ? data : []
} finally {
maintainLoading.value = false
}
}
const fetchRepairHistory = async () => {
if (!brandId.value) return
repairLoading.value = true
try {
const params: any = { moldId: brandId.value }
if (repairQueryCode.value) params.repairCode = repairQueryCode.value
if (repairQueryStatus.value) params.status = repairQueryStatus.value
if (repairDateRange.value && repairDateRange.value.length === 2) {
params.requireDate = [repairDateRange.value[0], repairDateRange.value[1]]
}
const data = await MoldRepairApi.getMoldRepairList(params)
repairList.value = Array.isArray(data) ? data : []
repairActiveNames.value = repairList.value.map((row: any) => String(row.id ?? row.repairCode))
} finally {
repairLoading.value = false
}
}
const fetchInstallRecords = async () => {
if (!brandId.value) return
installLoading.value = true
try {
moldList.value = await MoldBrandApi.getBrandList()
const params: any = { pageNo: installPageNo.value, pageSize: installPageSize.value, moldId: brandId.value }
if (installRemark.value) params.remark = installRemark.value
if (installOperateType.value) params.operateType = installOperateType.value
if (installDateRange.value && installDateRange.value.length === 2) {
params.createTime = [installDateRange.value[0], installDateRange.value[1]]
}
const data = await MoldOperateApi.getMoldOperatePage(params)
installRecords.value = data?.list ?? []
installTotal.value = data?.total ?? 0
} finally {
installLoading.value = false
}
}
const handleQueryInstall = () => {
installPageNo.value = 1
fetchInstallRecords()
}
const handleResetInstall = () => {
installMoldId.value = undefined
installRemark.value = undefined
installOperateType.value = undefined
installDateRange.value = undefined
installPageNo.value = 1
fetchInstallRecords()
}
const handleQueryInspection = async () => {
await fetchInspectionHistory()
}
const handleResetInspection = async () => {
inspectionDateRange.value = undefined
await fetchInspectionHistory()
}
const handleExportInspection = async () => {
inspectionExportLoading.value = true
message.info(t('MoldManagement.MoldBrandDetail.inspectionExportTip'))
inspectionExportLoading.value = false
}
const handleQueryMaintain = async () => {
await fetchMaintainHistory()
}
const handleResetMaintain = async () => {
maintainDateRange.value = undefined
await fetchMaintainHistory()
}
const handleExportMaintain = async () => {
maintainExportLoading.value = true
message.info(t('MoldManagement.MoldBrandDetail.maintainExportTip'))
maintainExportLoading.value = false
}
const handleQueryRepair = async () => {
await fetchRepairHistory()
}
const handleResetRepair = async () => {
repairQueryCode.value = undefined
repairQueryStatus.value = undefined
repairDateRange.value = undefined
await fetchRepairHistory()
}
const handleExportRepair = async () => {
repairExportLoading.value = true
message.info(t('MoldManagement.MoldBrandDetail.repairExportTip'))
repairExportLoading.value = false
}
const getDetail = async () => {
if (!brandId.value) {
message.error(t('MoldManagement.MoldBrandDetail.missingBrandId'))
return
}
detailLoading.value = true
try {
detailData.value = await MoldBrandApi.getMoldBrand(brandId.value)
} finally {
detailLoading.value = false
}
}
const loadTabData = async (tab: string) => {
if (loadedTabs.value.has(tab)) return
loadedTabs.value.add(tab)
switch (tab) {
case 'inspection':
await getChildMolds()
fetchInspectionHistory()
break
case 'repair':
await getChildMolds()
fetchRepairHistory()
break
case 'maintain':
await getChildMolds()
fetchMaintainHistory()
break
case 'install':
await getChildMolds()
fetchInstallRecords()
break
}
}
watch(activeTab, (val) => {
loadTabData(val)
})
onMounted(() => {
getDetail()
})
</script>
<style scoped>
.mold-brand-detail__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.mold-brand-detail__crumb {
margin-bottom: 16px;
color: var(--el-text-color-secondary);
font-size: 13px;
}
.mold-brand-detail__hero {
display: grid;
grid-template-columns: 320px minmax(0, 1fr);
gap: 20px;
margin-bottom: 20px;
}
.mold-brand-detail__left {
display: flex;
flex-direction: column;
gap: 16px;
}
.mold-brand-detail__image-card,
.mold-brand-detail__qr-card {
padding: 16px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.mold-brand-detail__qr-card {
display: flex;
flex-direction: column;
align-items: center;
}
.mold-brand-detail__image {
width: 100%;
height: 280px;
border-radius: 12px;
}
.mold-brand-detail__qr-title {
margin-bottom: 12px;
color: var(--el-text-color-primary);
font-weight: 600;
text-align: center;
}
.mold-brand-detail__qr {
width: 180px;
height: 180px;
margin: 0 auto;
display: block;
}
.mold-brand-detail__qr-code {
margin-top: 12px;
text-align: center;
font-weight: 600;
}
.mold-brand-detail__main {
padding: 16px 20px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.mold-brand-detail__topbar {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.mold-brand-detail__title-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.mold-brand-detail__title {
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
}
.mold-brand-detail__actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.mold-brand-detail__grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px 24px;
}
.mold-brand-detail__field {
display: flex;
flex-direction: column;
gap: 6px;
}
.mold-brand-detail__field span {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.mold-brand-detail__field strong {
color: var(--el-text-color-primary);
font-size: 18px;
font-weight: 600;
}
.mold-brand-detail__field--life strong {
font-size: 14px;
}
.mold-brand-detail__tabs {
margin-top: 30px;
padding: 8px 12px 0;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.device-ledger-tab-toolbar {
margin-bottom: 16px;
}
.device-ledger-history-steps,
.device-ledger-repair-collapse {
margin-top: 10px;
}
.device-ledger-history-title,
.device-ledger-repair-title {
display: flex;
gap: 16px;
align-items: center;
flex-wrap: wrap;
}
.device-ledger-history-time,
.device-ledger-repair-name {
font-weight: 600;
}
.device-ledger-history-items {
display: flex;
flex-direction: column;
gap: 12px;
}
.device-ledger-history-item {
padding: 12px 14px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 12px;
}
.device-ledger-history-item-head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.device-ledger-history-item-body {
display: grid;
gap: 8px;
}
.device-ledger-history-item-row {
display: grid;
grid-template-columns: 88px minmax(0, 1fr);
gap: 12px;
}
.device-ledger-history-item-label {
color: var(--el-text-color-secondary);
}
@media (max-width: 1024px) {
.mold-brand-detail__hero {
grid-template-columns: 1fr;
}
.mold-brand-detail__grid {
grid-template-columns: 1fr;
}
}
</style>

@ -325,12 +325,6 @@ const maintainDateRange = ref<string[] | undefined>(undefined)
const repairExportLoading = ref(false) const repairExportLoading = ref(false)
const repairDateRange = ref<string[] | undefined>(undefined) const repairDateRange = ref<string[] | undefined>(undefined)
const openQrCode = () => {
const url = detailData.value?.qrcodeUrl
if (!url) return
window.open(String(url), '_blank')
}
const formatDetailDate = (value: any) => { const formatDetailDate = (value: any) => {
if (!value) return '' if (!value) return ''
return formatDate(new Date(value), 'YYYY-MM-DD') return formatDate(new Date(value), 'YYYY-MM-DD')
@ -720,7 +714,7 @@ onMounted(async () => {
} }
.mold-qrcode-img { .mold-qrcode-img {
width: 150px; /* width: 150px; */
height: 150px; height: 150px;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;

@ -1,292 +1,505 @@
<template> <template>
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 列表页 -->
<el-form <template v-if="!operateFormVisible && !maintainFormVisible">
class="-mb-15px" <div class="mold-brand-page__header">
:model="queryParams" <div v-for="card in statusCards" :key="card.key" class="mold-brand-page__stat"
ref="queryFormRef" :class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.status)">
:inline="true" <div class="mold-brand-page__stat-title">{{ card.label }}</div>
min-label-width="68px" <div class="mold-brand-page__stat-value">{{ card.value }}</div>
> </div>
<el-form-item :label="t('MoldManagement.MoldBrand.code')" prop="code"> </div>
<el-input
v-model="queryParams.code" <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="-mb-15px mt-16px">
:placeholder="t('MoldManagement.MoldBrand.placeholderCode')" <el-form-item prop="keyword">
clearable <el-input v-model="inputKeyword" :placeholder="t('MoldManagement.MoldBrandPage.placeholderKeyword')"
@keyup.enter="handleQuery" clearable class="!w-240px" @keyup.enter="handleQuery" @clear="onKeywordClear" />
class="!w-240px"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.name')" prop="name"> <el-form-item prop="status">
<el-input <el-select v-model="queryParams.status" :placeholder="t('MoldManagement.MoldBrandPage.placeholderStatus')"
v-model="queryParams.name" clearable class="!w-180px">
:placeholder="t('MoldManagement.MoldBrand.placeholderName')" <el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label"
clearable :value="Number(dict.value)" />
@keyup.enter="handleQuery" </el-select>
class="!w-240px"
/>
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.orgType')" prop="orgType"> <el-form-item prop="productIds">
<el-select <el-select v-model="queryParams.productIds" multiple :placeholder="t('MoldManagement.MoldBrandPage.placeholderProduct')"
v-model="queryParams.orgType" clearable filterable class="!w-180px">
:placeholder="t('MoldManagement.MoldBrand.placeholderOrgType')" <el-option v-for="product in productOptions" :key="product.id" :label="product.name" :value="product.id" />
clearable
class="!w-240px"
>
<el-option
v-for="dict in orgTypeOptions"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- <el-form-item label="维保模式" prop="maintainType"> <el-form-item prop="currentDevice">
<el-select <el-select v-model="queryParams.currentDevice"
v-model="queryParams.maintainType" :placeholder="t('MoldManagement.MoldBrandPage.placeholderDevice')" clearable filterable class="!w-180px">
placeholder="请选择维保模式" <el-option v-for="device in deviceOptions" :key="device.id"
clearable :label="`${device.deviceName}${device.deviceCode}`" :value="device.deviceName" />
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_MAINTAIN_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> --> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button> <el-button @click="resetQuery">{{ t('MoldManagement.MoldBrandPage.reset') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button> <el-button type="primary" @click="handleQuery">{{ t('MoldManagement.MoldBrandPage.query') }}</el-button>
<el-button <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:mold-brand:create']">
type="primary" <Icon icon="ep:plus" class="mr-5px" /> {{ t('MoldManagement.MoldBrandPage.add') }}
plain
@click="openForm('create')"
v-hasPermi="['erp:mold-brand:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }}
</el-button> </el-button>
<el-button <el-button type="success" plain @click="handleExport" :loading="exportLoading"
type="success" v-hasPermi="['erp:mold-brand:export']">
plain <Icon icon="ep:download" class="mr-5px" /> {{ t('MoldManagement.MoldBrandPage.export') }}
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:mold-brand:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap>
<!-- 列表 --> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id"
<ContentWrap> style="margin-top: 16px;">
<el-table <el-table-column :label="t('MoldManagement.MoldBrandPage.image')" align="center" width="120">
ref="tableRef"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
highlight-current-row
@current-change="handleCurrentChange"
@selection-change="handleSelectionChange"
>
<!-- <el-table-column type="selection" width="55" fixed="left" reserve-selection /> -->
<el-table-column :label="t('MoldManagement.MoldBrand.code')" align="center" prop="code" sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.name')" align="left" prop="name" sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.moldType')" align="center" prop="moldType" sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.orgType')" align="center" prop="orgType" sortable>
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.MES_ORG_TYPE" :value="scope.row.orgType" /> <el-image v-if="getImageList(scope.row.images).length" :src="getImageList(scope.row.images)[0]"
:preview-src-list="getImageList(scope.row.images)" fit="cover" preview-teleported
class="mold-brand-page__thumb" />
<span v-else class="mold-brand-page__empty">-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrand.useTime')" align="center" prop="useTime" sortable /> <el-table-column :label="t('MoldManagement.MoldBrandPage.code')" prop="code" min-width="150" sortable>
<!-- <el-table-column label="维保模式" align="center" prop="maintainType" sortable>
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_MAINTAIN_TYPE" :value="scope.row.maintainType" /> <span class="mold-brand-page__code-link" @click="previewQrcode(scope.row)">{{ scope.row.code }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="维保周期" align="center" prop="maintainTime" sortable /> --> <el-table-column :label="t('MoldManagement.MoldBrandPage.name')" prop="name" min-width="160" sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.moldSize')" align="center" prop="moldSize" /> <el-table-column :label="t('MoldManagement.MoldBrandPage.productName')" prop="productName" min-width="140" />
<el-table-column :label="t('MoldManagement.MoldBrand.remark')" align="center" prop="remark" /> <el-table-column :label="t('MoldManagement.MoldBrandPage.version')" prop="version" width="100" />
<el-table-column <el-table-column :label="t('MoldManagement.MoldBrandPage.status')" prop="status" width="100" align="center">
:label="t('MoldManagement.MoldBrand.createTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.operate')" align="center" fixed="right" width="150px">
<template #default="scope"> <template #default="scope">
<el-button <dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="scope.row.status" />
link </template>
type="primary" </el-table-column>
@click="openForm('update', scope.row.id)" <el-table-column :label="t('MoldManagement.MoldBrandPage.currentDevice')" min-width="140">
v-hasPermi="['erp:mold-brand:update']" <template #default="scope">
> {{ scope.row.deviceName }}
{{ t('action.edit') }} </template>
</el-button> </el-table-column>
<el-button <el-table-column :label="t('MoldManagement.MoldBrandPage.moldSize')" prop="moldSize" width="90"
link align="center" />
type="danger" <el-table-column :label="t('MoldManagement.MoldBrandPage.childMoldCount')" prop="childMoldCount" width="90"
@click="handleDelete(scope.row.id)" align="center" />
v-hasPermi="['erp:mold-brand:delete']" <el-table-column :label="t('MoldManagement.MoldBrandPage.createTime')" prop="createTime"
> :formatter="dateFormatter" width="160" sortable />
{{ t('action.del') }} <el-table-column :label="t('MoldManagement.MoldBrandPage.operate')" fixed="right" width="310" align="center">
</el-button> <template #default="scope">
<el-button link type="primary" @click="openDetail(scope.row.id)">{{ t('MoldManagement.MoldBrandPage.detail')
}}</el-button>
<el-button link type="primary" @click="openOperateForm(1, scope.row)" v-if="scope.row.status === 1">{{
t('MoldManagement.MoldBrandPage.moldUp') }}</el-button>
<el-button link type="primary" @click="openOperateForm(2, scope.row)" v-if="scope.row.status === 0">{{
t('MoldManagement.MoldBrandPage.moldDown') }}</el-button>
<el-button link type="warning" @click="openMaintainForm(scope.row)">{{
t('MoldManagement.MoldBrandPage.maintain') }}</el-button>
<el-button link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['erp:mold-brand:update']">{{ t('MoldManagement.MoldBrandPage.edit') }}</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:mold-brand:delete']">{{
t('MoldManagement.MoldBrandPage.delete') }}</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 分页 -->
<Pagination <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
:total="total" @pagination="getList" />
v-model:page="queryParams.pageNo" </template>
v-model:limit="queryParams.pageSize"
@pagination="getList" <!-- 上下模操作表单视图 -->
<template v-else-if="operateFormVisible">
<MoldOperateViewComp
ref="moldOperateViewRef"
:mold="currentMold"
:type="operateType"
:device-options="deviceOptions"
@back="closeOperateForm"
@success="onOperateSuccess"
/> />
</template>
<!-- 维护操作表单视图 -->
<template v-else-if="maintainFormVisible">
<MoldMaintainViewComp
ref="moldMaintainViewRef"
:mold="currentMold"
:device-options="deviceOptions"
@back="closeMaintainForm"
@success="onMaintainSuccess"
/>
</template>
</ContentWrap> </ContentWrap>
<!-- 表单弹窗添加/修改 --> <Dialog v-model="qrcodeVisible" :title="t('MoldManagement.MoldBrandPage.qrcodeDialogTitle')" width="360px">
<MoldBrandForm ref="formRef" @success="getList" /> <div class="mold-brand-page__qr-card">
<!-- 子表的列表 --> <div class="mold-brand-page__qr-title">{{ t('MoldManagement.MoldBrandPage.qrcodeTitle') }}</div>
<!-- <ContentWrap v-if="currentBrandId"> <QrcodeActionCard :image-url="currentQrcodeRow?.qrCodeUrl" :print-id="currentQrcodeRow?.id" :print-template-type="4"
<div class="mb-10px text-14px font-600"> :print-title="t('MoldManagement.MoldBrandPage.qrcodePrintTitle', { name: currentQrcodeRow?.name || t('MoldManagement.MoldBrandPage.mold') })"
{{ currentBrandDisplay }} :print-paper-width="80" :print-paper-height="80" :print-max-width="220"
:empty-text="t('MoldManagement.MoldBrandPage.qrcodeEmpty')"
:error-text="t('MoldManagement.MoldBrandPage.qrcodeLoadError')" :refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!currentQrcodeRow?.id || !currentQrcodeRow?.code"
:refresh-confirm-text="t('MoldManagement.MoldBrandPage.qrcodeRefreshConfirm')"
@refresh-success="handleQrcodeRefreshSuccess" />
<div class="mold-brand-page__qr-code">{{ currentQrcodeRow?.code || '-' }}</div>
</div> </div>
<el-tabs model-value="mold"> </Dialog>
<el-tab-pane :label="t('MoldManagement.MoldBrand.tabMold')" name="mold">
<MoldList :brand-id="currentBrandId" /> <component :is="MoldBrandFormComp" ref="formRef" @success="getList" />
</el-tab-pane> -->
<!-- <el-tab-pane label="产品" name="moldBrandProduct">
<MoldBrandProductList :brand-id="currentBrandId" />
</el-tab-pane> -->
<!-- </el-tabs>
</ContentWrap> -->
<!-- <ContentWrap v-else>
<el-empty :description="t('MoldManagement.MoldBrand.emptyTip')" />
</ContentWrap> -->
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict' import { defineAsyncComponent } from 'vue'
import { ProductApi, type ProductVO } from '@/api/erp/product/product'
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { DeviceLedgerApi, type DeviceLedgerVO } from '@/api/mes/deviceledger'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { MoldBrandApi, MoldBrandVO } from '@/api/erp/mold' import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
import MoldBrandForm from './MoldBrandForm.vue'
import MoldList from './components/MoldList.vue'
import MoldBrandProductList from './components/MoldBrandProductList.vue'
/** 模具型号 列表 */
defineOptions({ name: 'MoldBrand' }) defineOptions({ name: 'MoldBrand' })
const message = useMessage() // const { t } = useI18n()
const { t } = useI18n() // const message = useMessage()
const loading = ref(true) // const { push } = useRouter()
const list = ref<MoldBrandVO[]>([]) // const route = useRoute()
const total = ref(0) // const loading = ref(false)
const exportLoading = ref(false)
const list = ref<MoldBrandVO[]>([])
const total = ref(0)
const productOptions = ref<ProductVO[]>([])
const deviceOptions = ref<DeviceLedgerVO[]>([])
const currentStatusKey = ref('all')
const qrcodeVisible = ref(false)
const currentQrcodeRow = ref<MoldBrandVO | null>(null)
const statusOptions = computed(() => getIntDictOptions(DICT_TYPE.ERP_MOLD_STATUS))
const counters = reactive({
allCount: 0,
onMachineCount: 0,
standbyCount: 0,
repairingCount: 0,
scrappedCount: 0
})
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
code: undefined, keyword: undefined as string | undefined,
name: undefined, productIds: [] as number[],
orgType: undefined, status: undefined as number | undefined,
moldType: undefined, currentDevice: undefined as string | undefined,
productId: undefined, currentPosition: undefined as string | undefined
useTime: [],
maintainType: undefined,
maintainTime: [],
moldSize: undefined,
remark: undefined,
isEnable: undefined,
createTime: []
}) })
const queryFormRef = ref() // const queryFormRef = ref()
const exportLoading = ref(false) // const formRef = ref()
const orgTypeOptions = computed(() => getStrDictOptions(DICT_TYPE.MES_ORG_TYPE)) const MoldBrandFormComp = defineAsyncComponent(() => import('./MoldBrandForm.vue') as any)
const MoldOperateViewComp = defineAsyncComponent(() => import('./components/MoldOperateView.vue') as any)
const MoldMaintainViewComp = defineAsyncComponent(() => import('./components/MoldMaintainView.vue') as any)
//
const operateFormVisible = ref(false)
const operateType = ref(1) // 1: , 2:
const currentMold = ref<MoldBrandVO | null>(null)
const moldOperateViewRef = ref()
//
const maintainFormVisible = ref(false)
const moldMaintainViewRef = ref()
const inputKeyword = ref('')
watch(inputKeyword, (val) => {
queryParams.keyword = val?.trim() || undefined
})
const onKeywordClear = () => {
inputKeyword.value = ''
}
const statusCards = computed(() => [
{ key: 'all', label: t('MoldManagement.MoldBrandPage.all'), value: counters.allCount, status: undefined },
{ key: 'onMachine', label: t('MoldManagement.MoldBrandPage.onMachine'), value: counters.onMachineCount, status: 1 },
{ key: 'standby', label: t('MoldManagement.MoldBrandPage.standby'), value: counters.standbyCount, status: 0 },
{ key: 'repairing', label: t('MoldManagement.MoldBrandPage.repairing'), value: counters.repairingCount, status: 2 },
{ key: 'scrapped', label: t('MoldManagement.MoldBrandPage.scrapped'), value: counters.scrappedCount, status: 3 }
])
const getImageList = (images?: string) => {
if (!images) return []
return String(images)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const tableRef = ref() const normalizePageResult = (data: any) => {
const selectedIds = ref<Array<number | string>>([]) const pageResult = data?.pageResult ?? data ?? {}
const handleSelectionChange = (rows: any[]) => { list.value = pageResult.list ?? []
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined && id !== null && id !== '') ?? [] total.value = pageResult.total ?? 0
counters.allCount = Number(data?.allCount ?? pageResult.total ?? 0)
counters.onMachineCount = Number(data?.onMachineCount ?? 0)
counters.standbyCount = Number(data?.standbyCount ?? 0)
counters.repairingCount = Number(data?.repairingCount ?? 0)
counters.scrappedCount = Number(data?.scrappedCount ?? 0)
} }
/** 查询列表 */
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await MoldBrandApi.getMoldBrandPage(queryParams) const selected = productOptions.value.filter((item) => (queryParams.productIds || []).includes(item.id))
list.value = data.list if (selected.length > 0) {
total.value = data.total queryParams.productName = selected.map((p) => p.name).join(',')
}
const data = await MoldBrandApi.getMoldBrandPage({
...queryParams,
currentDevice: queryParams.currentDevice
})
normalizePageResult(data)
} finally { } finally {
loading.value = false loading.value = false
} }
} }
/** 搜索按钮操作 */
const handleQuery = () => { const handleQuery = () => {
queryParams.pageNo = 1 queryParams.pageNo = 1
getList() getList()
} }
/** 重置按钮操作 */
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value?.resetFields()
inputKeyword.value = ''
currentStatusKey.value = 'all'
queryParams.status = undefined
queryParams.productIds = []
queryParams.productName = undefined
handleQuery()
}
const changeStatus = (key: string, status?: number) => {
currentStatusKey.value = key
queryParams.status = status
handleQuery() handleQuery()
} }
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
} }
/** 删除按钮操作 */
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
//
await message.delConfirm() await message.delConfirm()
//
await MoldBrandApi.deleteMoldBrand(id) await MoldBrandApi.deleteMoldBrand(id)
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
//
await getList() await getList()
} catch {} } catch { }
} }
/** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {
//
await message.exportConfirm() await message.exportConfirm()
//
exportLoading.value = true exportLoading.value = true
const data = await MoldBrandApi.exportMoldBrand({ parseKeyword()
...queryParams, const data = await MoldBrandApi.exportMoldBrand(queryParams)
...(selectedIds.value.length ? { ids: selectedIds.value.join(',') } : {}) download.excel(data, t('MoldManagement.MoldBrandPage.exportFilename'))
})
download.excel(data, '模具型号.xls')
} catch {
} finally { } finally {
exportLoading.value = false exportLoading.value = false
} }
} }
/** 选中行操作 */ const handleReservedAction = (action: string) => {
const currentRow = ref<MoldBrandVO | null>(null) // message.info(t('MoldManagement.MoldBrandPage.reservedAction', { action }))
const currentBrandId = computed(() => currentRow.value?.id) }
const currentBrandDisplay = computed(() => {
if (!currentRow.value) return '' const previewQrcode = (row: MoldBrandVO) => {
return `${currentRow.value.name}-${currentRow.value.code}` currentQrcodeRow.value = row
qrcodeVisible.value = true
}
const getQrcodeRefreshUrl = () => {
if (!currentQrcodeRow.value?.id || !currentQrcodeRow.value?.code) return ''
return `/erp/mold-brand/regenerate-code?id=${currentQrcodeRow.value.id}&code=${encodeURIComponent(String(currentQrcodeRow.value.code))}`
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!currentQrcodeRow.value?.id) return
if (data?.qrcodeUrl) {
currentQrcodeRow.value.qrCodeUrl = data.qrcodeUrl
return
}
const moldData = await MoldBrandApi.getMoldBrand(currentQrcodeRow.value.id)
currentQrcodeRow.value.qrCodeUrl = moldData?.qrCodeUrl
currentQrcodeRow.value.code = moldData?.code ?? currentQrcodeRow.value.code
}
const openDetail = (id: number) => {
push({ name: 'ErpMoldBrandDetail', params: { id } })
}
//
const openOperateForm = (type: number, row: MoldBrandVO) => {
operateType.value = type
currentMold.value = row
operateFormVisible.value = true
}
// operateFormVisible + moldOperateViewRef open
watch([operateFormVisible, moldOperateViewRef], ([visible, ref]) => {
if (visible && ref) {
ref.open()
}
})
const closeOperateForm = () => {
operateFormVisible.value = false
currentMold.value = null
}
const onOperateSuccess = () => {
//
}
//
const openMaintainForm = (row: MoldBrandVO) => {
currentMold.value = row
maintainFormVisible.value = true
}
// maintainFormVisible + moldMaintainViewRef open
watch([maintainFormVisible, moldMaintainViewRef], ([visible, ref]) => {
if (visible && ref) {
ref.open()
}
}) })
const handleCurrentChange = (row: MoldBrandVO | null) => {
currentRow.value = row const closeMaintainForm = () => {
maintainFormVisible.value = false
currentMold.value = null
}
const onMaintainSuccess = () => {
//
} }
/** 初始化 **/
onMounted(async () => { onMounted(async () => {
productOptions.value = await ProductApi.getMesProductSimpleList()
deviceOptions.value = await DeviceLedgerApi.getDeviceLedgerList()
await getList() await getList()
//
const { operateType: queryOperateType, moldId: queryMoldId } = route.query
if (queryOperateType && queryMoldId) {
const moldRow = list.value.find((item) => item.id === Number(queryMoldId))
if (moldRow) {
await openOperateForm(Number(queryOperateType), moldRow)
}
// query
push({ path: route.path })
}
}) })
</script> </script>
<style scoped>
.mold-brand-page__header {
display: grid;
grid-template-columns: repeat(5, 10vw);
gap: 12px;
}
.mold-brand-page__stat {
width: 10vw;
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
background: linear-gradient(180deg, #fff 0%, #f8fbff 100%);
transition: all 0.2s ease;
}
.mold-brand-page__stat.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
.mold-brand-page__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.mold-brand-page__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
.mold-brand-page__thumb {
width: 56px;
height: 56px;
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
}
.mold-brand-page__empty {
display: inline-flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
color: var(--el-text-color-placeholder);
}
.mold-brand-page__code-link {
color: var(--el-color-primary);
cursor: pointer;
text-decoration: underline;
}
.mold-brand-page__code-link:hover {
color: var(--el-color-primary-light-3);
}
.mold-brand-page__qrcode-wrap {
display: flex;
justify-content: center;
}
.mold-brand-page__qrcode {
width: 220px;
height: 220px;
}
.mold-brand-page__qr-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.mold-brand-page__qr-title {
margin-bottom: 12px;
color: var(--el-text-color-primary);
font-weight: 600;
text-align: center;
}
.mold-brand-page__qr-code {
margin-top: 12px;
text-align: center;
font-weight: 600;
}
@media (max-width: 1200px) {
.mold-brand-page__header {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.mold-brand-page__header {
grid-template-columns: 1fr;
}
}
</style>

@ -1,5 +1,5 @@
<template> <template>
<div class="device-ledger-layout"> <div v-show="!formVisible" class="device-ledger-layout">
<ContentWrap class="device-ledger-left"> <ContentWrap class="device-ledger-left">
<el-tree <el-tree
v-loading="typeTreeLoading" :data="brandTreeData" node-key="id" highlight-current :props="typeTreeProps" v-loading="typeTreeLoading" :data="brandTreeData" node-key="id" highlight-current :props="typeTreeProps"
@ -54,7 +54,7 @@ type="success" plain @click="handleExport" :loading="exportLoading"
</ContentWrap> </ContentWrap>
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.Mold.code')" align="center" prop="code" sortable /> <el-table-column :label="t('MoldManagement.Mold.code')" align="center" prop="code" sortable width="200px"/>
<el-table-column :label="t('MoldManagement.Mold.name')" align="left" prop="name" sortable /> <el-table-column :label="t('MoldManagement.Mold.name')" align="left" prop="name" sortable />
<el-table-column :label="t('MoldManagement.Mold.useTime')" align="center" prop="useTime" sortable /> <el-table-column :label="t('MoldManagement.Mold.useTime')" align="center" prop="useTime" sortable />
<el-table-column :label="t('MoldManagement.Mold.status')" align="center" prop="status" sortable> <el-table-column :label="t('MoldManagement.Mold.status')" align="center" prop="status" sortable>
@ -101,9 +101,8 @@ type="success" plain @click="handleExport" :loading="exportLoading"
</div> </div>
</div> </div>
<!-- 表单弹窗添加/修改 --> <!-- 表单添加/修改 -->
<!-- 表单弹窗添加/修改 --> <MoldForm v-show="formVisible" ref="formRef" @success="getList" @closed="formVisible = false" />
<MoldForm ref="formRef" @success="getList" />
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<MoldRecordForm ref="recordFormRef" @success="getList" /> <MoldRecordForm ref="recordFormRef" @success="getList" />
@ -223,11 +222,14 @@ const resetQuery = () => {
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const formVisible = ref(false)
const openForm = async (type: string, id?: number) => {
if (type == 'create' && !queryParams.brand) { if (type == 'create' && !queryParams.brand) {
message.error('请选择一个模具型号') message.error('请选择一个模具型号')
return return
} }
formVisible.value = true
await nextTick()
formRef.value.open(type, id, queryParams.brand) formRef.value.open(type, id, queryParams.brand)
} }
@ -241,8 +243,8 @@ const handleDelete = async (ids: number | number[]) => {
// //
await message.delConfirm() await message.delConfirm()
// //
const idsParam = buildIdsParam(ids) const idList = Array.isArray(ids) ? ids : [ids]
await DeviceLedgerApi.deleteDeviceLedger(idsParam) await Promise.all(idList.map((id) => MoldBrandApi.deleteMold(id)))
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
selectedIds.value = [] selectedIds.value = []
tableRef.value?.clearSelection?.() tableRef.value?.clearSelection?.()
@ -336,6 +338,7 @@ const openDetail = (id: number) => {
} }
.device-ledger-tab-toolbar { .device-ledger-tab-toolbar {
display: flex;
margin-bottom: 8px; margin-bottom: 8px;
text-align: right; text-align: right;
} }

@ -7,6 +7,17 @@
label-width="120px" label-width="120px"
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item :label="t('FactoryModeling.ProductCategory.dialogTypeLabel')" prop="type">
<el-radio-group v-model="formData.type" @change="handleTypeChange">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductCategory.dialogParentLabel')" prop="parentId"> <el-form-item :label="t('FactoryModeling.ProductCategory.dialogParentLabel')" prop="parentId">
<el-tree-select <el-tree-select
v-model="formData.parentId" v-model="formData.parentId"
@ -66,24 +77,37 @@ const formData = ref({
name: undefined, name: undefined,
code: undefined, code: undefined,
sort: undefined, sort: undefined,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE,
type: undefined
}) })
const formRules = reactive({ const formRules = reactive({
parentId: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorParentRequired'), trigger: 'blur' }], parentId: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorParentRequired'), trigger: 'blur' }],
name: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorNameRequired'), trigger: 'blur' }], name: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorNameRequired'), trigger: 'blur' }],
code: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorCodeRequired'), trigger: 'blur' }], code: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorCodeRequired'), trigger: 'blur' }],
sort: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorSortRequired'), trigger: 'blur' }], sort: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorSortRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorStatusRequired'), trigger: 'blur' }] status: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorStatusRequired'), trigger: 'blur' }],
type: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorTypeRequired'), trigger: 'change' }]
}) })
const formRef = ref() // Ref const formRef = ref() // Ref
const productCategoryTree = ref() // const productCategoryTree = ref() //
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number, defaultData?: any) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm() resetForm()
//
if (defaultData) {
if (defaultData.type !== undefined) formData.value.type = defaultData.type
if (defaultData.parentId !== undefined) formData.value.parentId = defaultData.parentId
}
if (!id && !defaultData) {
const typeOptions = getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)
if (typeOptions.length > 0) {
formData.value.type = typeOptions[0].value
}
}
// //
if (id) { if (id) {
formLoading.value = true formLoading.value = true
@ -129,7 +153,8 @@ const resetForm = () => {
name: undefined, name: undefined,
code: undefined, code: undefined,
sort: undefined, sort: undefined,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE,
type: undefined
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
@ -137,9 +162,15 @@ const resetForm = () => {
/** 获得产品分类树 */ /** 获得产品分类树 */
const getProductCategoryTree = async () => { const getProductCategoryTree = async () => {
productCategoryTree.value = [] productCategoryTree.value = []
const data = await ProductCategoryApi.getProductCategoryList() const data = await ProductCategoryApi.getProductCategoryList(formData.value.type)
const root: Tree = { id: 0, name: t('FactoryModeling.ProductCategory.treeRootName'), children: [] } const root: Tree = { id: 0, name: t('FactoryModeling.ProductCategory.treeRootName'), children: [] }
root.children = handleTree(data, 'id', 'parentId') root.children = handleTree(data, 'id', 'parentId')
productCategoryTree.value.push(root) productCategoryTree.value.push(root)
} }
/** 类型变更时刷新上级编号树 */
const handleTypeChange = async () => {
formData.value.parentId = undefined
await getProductCategoryTree()
}
</script> </script>

@ -52,15 +52,23 @@
> >
<Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.ProductCategory.exportButtonText') }} <Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.ProductCategory.exportButtonText') }}
</el-button> </el-button>
<el-button type="danger" plain @click="toggleExpandAll"> <!-- <el-button type="danger" plain @click="toggleExpandAll">
<Icon icon="ep:sort" class="mr-5px" /> {{ t('FactoryModeling.ProductCategory.expandButtonText') }} <Icon icon="ep:sort" class="mr-5px" /> {{ t('FactoryModeling.ProductCategory.expandButtonText') }}
</el-button> </el-button> -->
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane
v-for="dict in getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)"
:key="dict.value"
:label="dict.label"
:name="String(dict.value)"
/>
</el-tabs>
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="list" :data="list"
@ -136,9 +144,11 @@ const { t } = useI18n() // 国际化
const loading = ref(true) // const loading = ref(true) //
const list = ref<ProductCategoryVO[]>([]) // const list = ref<ProductCategoryVO[]>([]) //
const activeTab = ref('') // tab
const queryParams = reactive({ const queryParams = reactive({
name: undefined, name: undefined,
status: undefined status: undefined,
type: undefined
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
@ -147,7 +157,7 @@ const exportLoading = ref(false) // 导出的加载中
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await ProductCategoryApi.getProductCategoryList(queryParams) const data = await ProductCategoryApi.getProductCategoryList(queryParams.type)
list.value = handleTree(data, 'id', 'parentId') list.value = handleTree(data, 'id', 'parentId')
} finally { } finally {
loading.value = false loading.value = false
@ -160,6 +170,12 @@ const handleQuery = () => {
getList() getList()
} }
/** Tab 切换操作 */
const handleTabChange = (name: string) => {
queryParams.type = name ? Number(name) : undefined
getList()
}
/** 重置按钮操作 */ /** 重置按钮操作 */
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
@ -212,6 +228,11 @@ const toggleExpandAll = async () => {
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
const typeOptions = getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)
if (typeOptions.length > 0) {
activeTab.value = String(typeOptions[0].value)
queryParams.type = typeOptions[0].value
}
getList() getList()
}) })
</script> </script>

File diff suppressed because it is too large Load Diff

@ -1,210 +1,456 @@
<!-- ERP 产品列表 --> <!-- ERP 产品列表 -->
<template> <template>
<div class="dv-repair-page">
<template v-if="!formVisible">
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto" @submit.prevent>
<el-form <el-form-item :label="t('FactoryModeling.ProductInformation.searchCodeLabel')" prop="barCode">
class="-mb-15px" <el-input v-model="queryParams.barCode"
:model="queryParams" :placeholder="t('FactoryModeling.ProductInformation.searchCodePlaceholder')" clearable
ref="queryFormRef" @keyup.enter="handleQuery" class="!w-240px" />
:inline="true" </el-form-item>
label-width="120px"
>
<el-form-item :label="t('FactoryModeling.ProductInformation.searchNameLabel')" prop="name"> <el-form-item :label="t('FactoryModeling.ProductInformation.searchNameLabel')" prop="name">
<el-input <el-input v-model="queryParams.name"
v-model="queryParams.name" :placeholder="t('FactoryModeling.ProductInformation.searchNamePlaceholder')" clearable
:placeholder="t('FactoryModeling.ProductInformation.searchNamePlaceholder')" @keyup.enter="handleQuery" class="!w-240px" />
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button> <el-button @click="handleQuery">
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button> <Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.searchButtonText') }}
<el-button </el-button>
type="primary" <el-button @click="resetQuery">
plain <Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.resetButtonText') }}
@click="openForm('create')" </el-button>
v-hasPermi="['erp:product:create']" <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:product:create']">
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.addButtonText') }} <Icon icon="ep:plus" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.addButtonText') }}
</el-button> </el-button>
<el-button <el-button type="success" plain @click="handleExport" :loading="exportLoading"
type="success" v-hasPermi="['erp:product:export']">
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:product:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.exportButtonText') }} <Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.exportButtonText') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <div class="product-page-layout">
<!-- 左侧分类树 -->
<ContentWrap class="product-page-left">
<div class="tree-header">
<span class="tree-title">{{ t('FactoryModeling.ProductInformation.categoryTree') }}</span>
<div class="tree-header-actions">
<el-button link type="primary" size="small" @click="fetchGroupTree">
<Icon icon="ep:refresh" /> {{ t('FactoryModeling.ProductInformation.refreshTree') }}
</el-button>
<el-button link type="primary" size="small" @click="openCategoryForm('create')" v-hasPermi="['erp:product-category:create']">
<Icon icon="ep:plus" /> {{ t('FactoryModeling.ProductInformation.addCategory') }}
</el-button>
</div>
</div>
<el-tree
ref="treeRef"
:data="treeData"
:props="{ children: 'children', label: 'name' }"
node-key="nodeKey"
highlight-current
default-expand-all
@node-click="handleTreeNodeClick"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span class="tree-node-label">{{ data.name }}</span>
<span class="tree-node-actions">
<el-button link type="primary" size="small" @click.stop="openCategoryForm('create', data.isType ? { type: data.type } : { type: data.type, parentId: data.id })"
v-hasPermi="['erp:product-category:create']">
<Icon icon="ep:plus" />
</el-button>
<template v-if="!data.isType">
<el-button link type="primary" size="small" @click.stop="openCategoryForm('update', data)"
v-hasPermi="['erp:product-category:update']">
<Icon icon="ep:edit" />
</el-button>
<el-button link type="danger" size="small" @click.stop="handleCategoryDelete(data)"
v-hasPermi="['erp:product-category:delete']">
<Icon icon="ep:delete" />
</el-button>
</template>
</span>
</span>
</template>
</el-tree>
</ContentWrap>
<!-- 右侧产品表格 -->
<div class="product-page-right">
<ContentWrap> <ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" :row-class-name="getRowClassName">
<!-- 使用 v-for 动态生成 el-tab-pane --> <el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode"
<el-tab-pane sortable />
v-for="item in parentList" <el-table-column :label="t('FactoryModeling.ProductInformation.dialogCategoryTypeLabel')" align="center" prop="categoryType" sortable>
:key="item.id" <template #default="scope">
:label="item.name" <dict-tag :type="DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE" :value="scope.row.categoryType" />
:name="item.id.toString()" </template>
/> </el-table-column>
<!-- <el-tab-pane label="产品" name="2" /> <el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name"
<el-tab-pane label="原料" name="1" /> width="220px" sortable />
<el-tab-pane label="备件" name="5" /> <el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center"
<el-tab-pane label="工具" name="3" /> prop="standard" sortable />
<el-tab-pane label="耗材" name="4" /> <el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center"
<el-tab-pane label="办公室用品" name="6" /> prop="subCategoryName" sortable />
<el-tab-pane label="其他" name="0" /> --> <el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName"
</el-tabs> sortable />
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status"
<el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode" sortable/> sortable>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name" width="220px" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center" prop="standard" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center" prop="subCategoryName" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status" sortable>
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column :label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')" align="center"
:label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
align="center" <el-table-column :label="t('FactoryModeling.ProductInformation.tableOperateColumn')" align="center" width="210px">
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable
/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableOperateColumn')" align="center" width="150px">
<template #default="scope"> <template #default="scope">
<!-- <el-button--> <el-button link type="primary" @click="handleShowDetail(scope.row.id)">
<!-- v-if="scope.row.categoryId ===2"--> 详情
<!-- link--> </el-button>
<!-- type="primary"--> <el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product:update']">
<!-- @click="openBomForm('detail', scope.row.id)"-->
<!-- >-->
<!-- BOM-->
<!-- </el-button>-->
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:product:update']"
>
{{ t('FactoryModeling.ProductInformation.tableEditAction') }} {{ t('FactoryModeling.ProductInformation.tableEditAction') }}
</el-button> </el-button>
<el-button <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product:delete']">
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:product:delete']"
>
{{ t('FactoryModeling.ProductInformation.tableDeleteAction') }} {{ t('FactoryModeling.ProductInformation.tableDeleteAction') }}
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 分页 --> <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
<Pagination @pagination="getList" />
:total="total" </ContentWrap>
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize" <ContentWrap v-if="detailVisible" class="product-detail-wrap" v-loading="detailLoading">
@pagination="getList" <template v-if="productDetail">
<div class="detail-header">
<div class="detail-title">物料详情</div>
<el-button text @click="closeDetail">
<Icon icon="ep:close" />
</el-button>
</div>
<div class="detail-card">
<div class="detail-image-box">
<div class="detail-image-item">
<div class="detail-image-label">{{ t('FactoryModeling.ProductInformation.qrcode') }}</div>
<el-image
v-if="productDetail.qrcodeUrl"
class="detail-image"
:src="productDetail.qrcodeUrl"
fit="contain"
:preview-src-list="[productDetail.qrcodeUrl]"
preview-teleported
/>
<el-empty v-else :image-size="64" :description="t('FactoryModeling.ProductInformation.qrcodeEmpty')" />
</div>
</div>
<div class="detail-info">
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogBarCodeLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.barCode) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogCategoryTypeLabel') }}</span>
<span class="field-value">{{ formatCategoryType(productDetail.categoryType) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogNameLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.name) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogCategoryLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.subCategoryName) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogUnitLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.unitName) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogStandardLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.standard || productDetail.deviceSpec || productDetail.model) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogExpiryDayLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.expiryDay) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogStatusLabel') }}</span>
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="productDetail.status" />
</div>
<!-- 产品类型关联设备关联模具 -->
<template v-if="productDetail.categoryType === 1">
<div class="detail-field">
<span class="field-label">关联设备</span>
<span class="field-value">{{ formatDeviceText(productDetail) }}</span>
</div>
<div class="detail-field">
<span class="field-label">关联模具</span>
<span class="field-value">{{ formatMoldText(productDetail) }}</span>
</div>
</template>
<!-- 物料/备件类型供应商 -->
<template v-if="productDetail.categoryType === 2 || productDetail.categoryType === 3">
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogSupplierLabel') }}</span>
<span class="field-value">{{ formatSupplierText(productDetail) }}</span>
</div>
</template>
<!-- 备件类型备件等级是否易损件采购周期品牌 -->
<template v-if="productDetail.categoryType === 3">
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogSparePartLevelLabel') }}</span>
<span class="field-value">{{ formatSparePartLevel(productDetail.sparePartLevel) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogFragileFlagLabel') }}</span>
<span class="field-value">{{ productDetail.fragileFlag === 1 ? t('common.yes') : productDetail.fragileFlag === 0 ? t('common.no') : '-' }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogPurchaseCycleLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.purchaseCycle) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogBrandLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.brand) }}</span>
</div>
</template>
<div class="detail-field detail-field--full">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogRemarkLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.remark) }}</span>
</div>
</div>
<div class="detail-summary">
<div class="section-title">{{ detailSummaryTitle }}</div>
<!-- 产品类型包装方案摘要 -->
<template v-if="productDetail.categoryType === 1">
<div class="summary-item">
<Icon icon="ep:box" class="summary-icon" />
<span>{{ t('FactoryModeling.ProductInformation.dialogPackagingSchemeLabel') }}{{ packagingSchemeCount }} </span>
</div>
<div class="summary-item">
<Icon icon="ep:document-checked" class="summary-icon" />
<span>默认参考方案{{ defaultPackagingSchemeText }}</span>
</div>
</template>
<!-- 物料/备件类型供应商摘要 -->
<template v-if="productDetail.categoryType === 2 || productDetail.categoryType === 3">
<div class="summary-item">
<Icon icon="ep:office-building" class="summary-icon" />
<span>{{ t('FactoryModeling.ProductInformation.dialogSupplierLabel') }}{{ supplierCount }} </span>
</div>
<div class="summary-item">
<Icon icon="ep:star" class="summary-icon" />
<span>默认供应商{{ defaultSupplierText }}</span>
</div>
</template>
<div class="detail-image-item" style="margin-top: 12px;">
<div class="detail-image-label">{{ t('FactoryModeling.ProductInformation.images') }}</div>
<el-image
v-if="productDetail.images"
class="detail-image"
:src="productDetail.images"
fit="contain"
:preview-src-list="[productDetail.images]"
preview-teleported
/> />
<el-empty v-else :image-size="64" description="暂无图片" />
</div>
</div>
<!-- 产品类型包装方案表格 -->
<div v-if="productDetail.categoryType === 1" class="detail-table-box">
<div class="detail-table-title">
<span>{{ t('FactoryModeling.ProductInformation.dialogPackagingSchemeLabel') }}</span>
<span> {{ packagingSchemeCount }} </span>
</div>
<el-table :data="productDetail.packagingSchemes || []" border size="small" :show-overflow-tooltip="true">
<el-table-column :label="t('ErpStock.PackagingScheme.name')" prop="packagingSchemeName" min-width="130" />
<el-table-column :label="t('ErpStock.PackagingScheme.packageQuantity')" prop="packageQuantity" min-width="100" />
<el-table-column :label="t('ErpStock.PackagingScheme.palletPackageQuantity')" prop="palletPackageQuantity" min-width="100" />
</el-table>
</div>
<!-- 物料/备件类型供应商表格 -->
<div v-else-if="productDetail.categoryType === 2 || productDetail.categoryType === 3" class="detail-table-box">
<div class="detail-table-title">
<span>{{ t('FactoryModeling.ProductInformation.dialogSupplierLabel') }}</span>
<span> {{ supplierCount }} </span>
</div>
<el-table :data="productDetail.suppliers || []" border size="small" :show-overflow-tooltip="true">
<el-table-column :label="t('ErpPurchase.Supplier.name')" prop="supplierName" min-width="160" />
<el-table-column :label="t('ErpPurchase.Supplier.contact')" prop="supplierContact" min-width="100" />
<el-table-column :label="t('ErpPurchase.Supplier.mobile')" prop="supplierMobile" min-width="120" />
</el-table>
</div>
</div>
</template>
</ContentWrap> </ContentWrap>
</div>
</div>
</template>
<!-- 表单弹窗添加/修改 --> <ProductForm v-else-if="formType === 'product'" ref="formRef" @success="getList" @closed="handleFormClosed" />
<ProductForm ref="formRef" @success="getList" /> <BomForm v-else-if="formType === 'bom'" ref="bomFormRef" @success="getList" @closed="handleFormClosed" />
<!-- 表单弹窗添加/修改 -->
<BomForm ref="bomFormRef" @success="getList" />
<!-- 分类表单弹窗 -->
<ProductCategoryForm ref="categoryFormRef" @success="handleCategorySuccess" />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter, formatDate } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { ProductApi, ProductVO } from '@/api/erp/product/product' import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category' import { ProductCategoryApi } from '@/api/erp/product/category'
import ProductForm from './ProductForm.vue' import ProductForm from './ProductForm.vue'
import { DICT_TYPE } from '@/utils/dict' import ProductCategoryForm from '../category/ProductCategoryForm.vue'
import { handleTree } from '@/utils/tree' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import BomForm from "@/views/mes/bom/BomForm.vue"; import BomForm from "@/views/mes/bom/BomForm.vue";
/** ERP 产品列表 */ /** ERP 产品列表 */
defineOptions({ name: 'ErpProduct' }) defineOptions({ name: 'ErpProduct' })
const message = useMessage() // const message = useMessage()
const { t } = useI18n() // const { t } = useI18n()
const loading = ref(true) // const loading = ref(true)
const list = ref<ProductVO[]>([]) // const list = ref<ProductVO[]>([])
const total = ref(0) // const total = ref(0)
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
categoryId: undefined barCode: undefined,
categoryId: undefined,
categoryType: undefined
}) })
const queryFormRef = ref() // const queryFormRef = ref()
const exportLoading = ref(false) // const exportLoading = ref(false)
const categoryList = ref<ProductCategoryVO[]>([]) // const formVisible = ref(false)
const parentList = ref<ProductCategoryVO[]>([]) const formType = ref('')
const detailVisible = ref(false)
const detailLoading = ref(false)
const currentDetailId = ref<number>()
const productDetail = ref<any>()
/** 分类树数据 */
const groupTreeData = ref<any[]>([])
const treeRef = ref()
const typeDict = computed(() => getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE))
const treeData = computed(() => {
return (groupTreeData.value || []).map((group: any) => {
const dictItem = typeDict.value.find((d: any) => d.value === group.type)
const name = dictItem?.label || String(group.type)
return {
nodeKey: `type_${group.type}`,
name,
type: group.type,
isType: true,
children: (group.children || []).map((child: any) => ({
...child,
nodeKey: `cat_${child.id}`,
isType: false
}))
}
})
})
const fetchGroupTree = async () => {
const res = await ProductCategoryApi.getProductCategoryGroupTree()
groupTreeData.value = res
}
/** 树节点点击 - 筛选产品 */
const handleTreeNodeClick = (data: any) => {
if (data.isType) {
queryParams.categoryType = data.type
queryParams.categoryId = undefined
} else {
queryParams.categoryType = data.type
queryParams.categoryId = data.id
}
handleQuery()
}
/** 分类增删改 */
const categoryFormRef = ref()
const openCategoryForm = (type: string, data?: any) => {
const treeData = type === 'update' ? data : undefined
const defaultData = data && !treeData ? { ...data } : undefined
nextTick(() => {
categoryFormRef.value.open(type, treeData?.id, defaultData)
})
}
const handleCategoryDelete = async (data: any) => {
try {
await message.delConfirm()
await ProductCategoryApi.deleteProductCategory(data.id)
message.success(t('common.delSuccess'))
await fetchGroupTree()
} catch {}
}
const handleCategorySuccess = () => {
fetchGroupTree()
}
/** 查询列表 */
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await ProductApi.getProductPage(queryParams) const data = await ProductApi.getProductPage(queryParams)
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
if (currentDetailId.value && !list.value.some((item) => item.id === currentDetailId.value)) {
closeDetail()
}
} finally { } finally {
loading.value = false loading.value = false
} }
} }
/** 搜索按钮操作 */
const handleQuery = () => { const handleQuery = () => {
queryParams.pageNo = 1 queryParams.pageNo = 1
closeDetail()
getList() getList()
} }
/** 重置按钮操作 */
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
closeDetail()
handleQuery() handleQuery()
} }
/** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
formVisible.value = true
formType.value = 'product'
nextTick(() => {
formRef.value.open(type, id) formRef.value.open(type, id)
})
} }
/** 删除按钮操作 */
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
//
await message.delConfirm() await message.delConfirm()
//
await ProductApi.deleteProduct(id) await ProductApi.deleteProduct(id)
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
// if (currentDetailId.value === id) {
closeDetail()
}
await getList() await getList()
} catch {} } catch { }
} }
/** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {
//
await message.exportConfirm() await message.exportConfirm()
//
exportLoading.value = true exportLoading.value = true
const data = await ProductApi.exportProduct(queryParams) const data = await ProductApi.exportProduct(queryParams)
download.excel(data, '产品.xls') download.excel(data, '产品.xls')
@ -214,33 +460,320 @@ const handleExport = async () => {
} }
} }
/** 初始化 **/
onMounted(async () => { onMounted(async () => {
queryParams.categoryId = 2 await fetchGroupTree()
await getList() const typeOptions = getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)
// if (typeOptions.length > 0) {
const categoryData = await ProductCategoryApi.getProductCategorySimpleList() queryParams.categoryType = typeOptions[0].value
categoryList.value = handleTree(categoryData, 'id', 'parentId')
//
for (let i = 0; i < categoryData.length; i++) {
if (categoryData[i].parentId === 0) {
parentList.value.push(categoryData[i]);
}
} }
// await getList()
parentList.value.sort((a, b) => a.sort - b.sort);
}) })
/** tab 切换 */
let activeName = '2'
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.categoryId = tab.paneName
handleQuery()
}
/** 添加/修改操作 */
const bomFormRef = ref() const bomFormRef = ref()
const openBomForm = (type: string, id?: number) => { const openBomForm = (type: string, id?: number) => {
formVisible.value = true
formType.value = 'bom'
nextTick(() => {
bomFormRef.value.open(type, id) bomFormRef.value.open(type, id)
})
}
const handleFormClosed = () => {
formVisible.value = false
formType.value = ''
}
const handleShowDetail = async (id: number) => {
detailVisible.value = true
currentDetailId.value = id
detailLoading.value = true
try {
productDetail.value = await ProductApi.getProduct(id)
} finally {
detailLoading.value = false
}
}
const closeDetail = () => {
detailVisible.value = false
detailLoading.value = false
currentDetailId.value = undefined
productDetail.value = undefined
}
const getRowClassName = ({ row }: { row: ProductVO }) => {
return row.id === currentDetailId.value ? 'current-detail-row' : ''
}
const packagingSchemes = computed(() => productDetail.value?.packagingSchemes || [])
const packagingSchemeCount = computed(() => packagingSchemes.value.length)
const defaultPackagingScheme = computed(() => {
const defaultId = productDetail.value?.defaultPackagingSchemeId
return packagingSchemes.value.find((item: any) => item.defaultStatus === 1)
|| packagingSchemes.value.find((item: any) => item.packagingSchemeId === defaultId)
})
const defaultPackagingSchemeText = computed(() => {
const scheme = defaultPackagingScheme.value
if (!scheme) return '-'
return formatValue(scheme.packagingSchemeName)
})
const detailSummaryTitle = computed(() => {
const categoryType = formatCategoryType(productDetail.value?.categoryType)
return categoryType === '-' ? '物料属性摘要' : `${categoryType}属性摘要`
})
//
const suppliers = computed(() => productDetail.value?.suppliers || [])
const supplierCount = computed(() => suppliers.value.length)
const defaultSupplier = computed(() => {
return suppliers.value.find((item: any) => item.defaultStatus === 1)
})
const defaultSupplierText = computed(() => {
const supplier = defaultSupplier.value
if (!supplier) return '-'
return supplier.supplierName || '-'
})
//
const formatDeviceText = (detail: any) => {
if (!detail?.devices?.length && !detail?.deviceList?.length) return '-'
const list = detail.devices || detail.deviceList || []
return list.map((item: any) => item.name || item.deviceName || `ID:${item.id}`).join('、') || '-'
}
const formatMoldText = (detail: any) => {
if (!detail?.molds?.length && !detail?.moldList?.length) return '-'
const list = detail.molds || detail.moldList || []
return list.map((item: any) => item.name || item.code || `ID:${item.id}`).join('、') || '-'
}
const formatSupplierText = (detail: any) => {
if (!detail?.suppliers?.length) return '-'
return detail.suppliers.map((item: any) => item.supplierName || `ID:${item.supplierId}`).join('、') || '-'
}
const formatSparePartLevel = (value: any) => {
if (value === undefined || value === null || value === '') return '-'
const dictItem = sparePartLevelDict.value.find((item: any) => item.value === Number(value))
return dictItem?.label || formatValue(value)
}
const sparePartLevelDict = computed(() => getIntDictOptions(DICT_TYPE.SPARE_PARTS_LEVEL))
const formatValue = (value: any) => {
return value === undefined || value === null || value === '' ? '-' : value
}
const formatCategoryType = (value: any) => {
const dictItem = typeDict.value.find((item: any) => item.value === Number(value))
return dictItem?.label || formatValue(value)
}
const formatDateTime = (value: any) => {
if (value === undefined || value === null || value === '') return '-'
return formatDate(value)
} }
</script> </script>
<style lang="scss" scoped>
.product-page-layout {
display: flex;
gap: 12px;
}
.product-page-left {
width: 260px;
flex: 0 0 auto;
}
.product-page-right {
flex: 1;
min-width: 0;
}
.product-detail-wrap {
margin-top: 12px;
}
:deep(.current-detail-row) {
--el-table-tr-bg-color: var(--el-color-primary-light-9);
}
.detail-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.detail-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.detail-card {
display: grid;
grid-template-columns: 200px 1fr 300px 1fr;
gap: 16px;
padding: 16px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
}
.detail-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px 28px;
align-content: start;
}
.detail-field {
display: flex;
min-width: 0;
font-size: 14px;
line-height: 24px;
}
.detail-field--full {
grid-column: 1 / -1;
}
.field-label {
flex: 0 0 auto;
color: var(--el-text-color-regular);
}
.field-value {
min-width: 0;
color: var(--el-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.detail-image-box {
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;
}
.detail-image-item {
display: flex;
flex-direction: column;
align-items: left;
gap: 6px;
}
.detail-image-label {
font-size: 13px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.detail-image {
width: 170px;
height: 170px;
}
.detail-summary {
min-width: 0;
}
.section-title {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.summary-item {
display: flex;
gap: 10px;
align-items: center;
min-height: 32px;
font-size: 14px;
color: var(--el-text-color-primary);
}
.summary-icon {
flex: 0 0 auto;
color: var(--el-color-primary);
}
.detail-table-box {
min-width: 0;
}
.detail-table-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
color: var(--el-text-color-primary);
span:last-child {
color: var(--el-text-color-secondary);
}
}
@media (max-width: 1200px) {
.detail-card {
grid-template-columns: 1fr 1fr;
}
.detail-summary,
.detail-table-box {
grid-column: 1 / -1;
}
}
.tree-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid var(--el-border-color-lighter);
margin-bottom: 4px;
.tree-title {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.tree-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
}
.custom-tree-node {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
padding-right: 8px;
.tree-node-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-node-actions {
display: none;
flex-shrink: 0;
margin-left: 8px;
}
&:hover .tree-node-actions {
display: flex;
}
}
</style>

@ -0,0 +1,144 @@
<template>
<Dialog v-model="dialogVisible" :title="t('FactoryModeling.ProductUnit.import')" width="400">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
:action="importUrl + '?updateSupport=' + updateSupport"
:auto-upload="false"
:disabled="formLoading"
:headers="uploadHeaders"
:limit="1"
:on-error="submitFormError"
:on-exceed="handleExceed"
:on-success="submitFormSuccess"
accept=".xlsx, .xls"
drag
>
<Icon icon="ep:upload" />
<div class="el-upload__text">
{{ t('FactoryModeling.ProductUnit.importDragText') }}<em>{{ t('FactoryModeling.ProductUnit.importClickText') }}</em>
</div>
<template #tip>
<div class="el-upload__tip text-center">
<!-- <div class="el-upload__tip">
<el-checkbox v-model="updateSupport" />
{{ t('FactoryModeling.ProductUnit.importUpdateSupport') }}
</div>-->
<span>{{ t('FactoryModeling.ProductUnit.importFormatLimit') }}</span>
<el-link
:underline="false"
style="font-size: 12px; vertical-align: baseline"
type="primary"
@click="importTemplate"
>
{{ t('FactoryModeling.ProductUnit.importDownloadTemplate') }}
</el-link>
</div>
</template>
</el-upload>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">{{ t('common.ok') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as UserApi from '@/api/system/user'
import { getAccessToken, getTenantId } from '@/utils/auth'
import download from '@/utils/download'
import {ProductUnitApi} from "@/api/erp/product/unit";
defineOptions({ name: 'ProductUnitImportForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const uploadRef = ref()
const importUrl =
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/erp/product-unit/import'
const uploadHeaders = ref() // Header
const fileList = ref([]) //
const updateSupport = ref(0) //
/** 打开弹窗 */
const open = () => {
dialogVisible.value = true
updateSupport.value = 0
fileList.value = []
resetForm()
}
defineExpose({ open }) // open
/** 提交表单 */
const submitForm = async () => {
if (fileList.value.length == 0) {
message.error(t('FactoryModeling.ProductUnit.importFileRequired'))
return
}
//
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenantId': getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
}
/** 文件上传成功 */
const emits = defineEmits(['success'])
const submitFormSuccess = (response: any) => {
if (response.code !== 0) {
message.error(response.msg)
formLoading.value = false
return
}
/* // 拼接提示语
const data = response.data
let text = t('FactoryModeling.ProductUnit.importSuccessTip', {
createCount: data.createUsernames.length,
updateCount: data.updateUsernames.length,
failureCount: Object.keys(data.failureUsernames).length
})
for (let username of data.createUsernames) {
text += '< ' + username + ' >'
}
for (const username of data.updateUsernames) {
text += '< ' + username + ' >'
}
for (const username in data.failureUsernames) {
text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
}*/
message.alert(response.data)
formLoading.value = false
dialogVisible.value = false
//
emits('success')
}
/** 上传错误提示 */
const submitFormError = (): void => {
message.error(t('FactoryModeling.ProductUnit.importFailed'))
formLoading.value = false
}
/** 重置表单 */
const resetForm = async (): Promise<void> => {
//
formLoading.value = false
await nextTick()
uploadRef.value?.clearFiles()
}
/** 文件数超出提示 */
const handleExceed = (): void => {
message.error(t('FactoryModeling.ProductUnit.importFileLimit'))
}
/** 下载模板操作 */
const importTemplate = async () => {
const res = await ProductUnitApi.importUserTemplate()
download.excel(res, '单位导入模版.xls')
}
</script>

@ -50,6 +50,14 @@
<Icon icon="ep:plus" class="mr-5px" /> <Icon icon="ep:plus" class="mr-5px" />
{{ t('FactoryModeling.ProductUnit.addButtonText') }} {{ t('FactoryModeling.ProductUnit.addButtonText') }}
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['erp:product-unit:export']"
>
<Icon icon="ep:upload" /> {{ t('FactoryModeling.ProductUnit.importButtonText') }}
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@ -129,6 +137,9 @@
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<ProductUnitForm ref="formRef" @success="getList" /> <ProductUnitForm ref="formRef" @success="getList" />
<!-- 单位导入对话框 -->
<ProductUnitImportForm ref="importFormRef" @success="getList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -137,7 +148,7 @@ import download from '@/utils/download'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit' import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import ProductUnitForm from './ProductUnitForm.vue' import ProductUnitForm from './ProductUnitForm.vue'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import ProductUnitImportForm from './ProductUnitImportForm.vue'
/** ERP 产品单位列表 */ /** ERP 产品单位列表 */
defineOptions({ name: 'ErpProductUnit' }) defineOptions({ name: 'ErpProductUnit' })
@ -199,6 +210,12 @@ const handleDelete = async (id: number) => {
} catch {} } catch {}
} }
/** 导入 */
const importFormRef = ref()
const handleImport = () => {
importFormRef.value.open()
}
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {

@ -1,11 +1,17 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1080"> <div class="dv-repair-panel">
<div class="dv-repair-panel__header">
<div class="dv-repair-panel__title">{{ dialogTitle }}</div>
<el-button text @click="closeForm">
<Icon icon="ep:close" />
</el-button>
</div>
<div class="dv-repair-dialog" v-loading="formLoading">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="auto" label-width="auto"
v-loading="formLoading"
:disabled="disabled" :disabled="disabled"
> >
<el-form-item prop="code"> <el-form-item prop="code">
@ -66,24 +72,6 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- <el-col :span="8">
<el-form-item label="供应商" prop="supplierId">
<el-select
v-model="formData.supplierId"
clearable
filterable
placeholder="请选择供应商"
class="!w-1/1"
>
<el-option
v-for="item in supplierList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col> -->
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="t('ErpStock.In.remark')" prop="remark"> <el-form-item :label="t('ErpStock.In.remark')" prop="remark">
<el-input <el-input
@ -109,13 +97,14 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</ContentWrap> </ContentWrap>
<template #footer> </div>
<div class="dv-repair-footer">
<el-button @click="closeForm">{{ t('common.cancel') }}</el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled"> <el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">
{{ t('common.ok') }} {{ t('common.ok') }}
</el-button> </el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button> </div>
</template> </div>
</Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { StockInApi, StockInVO } from '@/api/erp/stock/in' import { StockInApi, StockInVO } from '@/api/erp/stock/in'
@ -128,7 +117,6 @@ defineOptions({ name: 'StockInForm' })
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - detail - const formType = ref('') // create - update - detail -
@ -159,28 +147,27 @@ const options = [
value: '备件入库', value: '备件入库',
label: t('ErpStock.In.tabPart') label: t('ErpStock.In.tabPart')
}, },
// {
// value: '',
// label: '',
// },
{ {
value: '产品入库', value: '产品入库',
label: t('ErpStock.In.tabProduct') label: t('ErpStock.In.tabProduct')
}, },
// {
// value: '',
// label: '',
// disabled: true,
// },
] ]
/** 子表的表单 */ /** 子表的表单 */
const subTabsName = ref('item') const subTabsName = ref('item')
const itemFormRef = ref() const itemFormRef = ref()
//
const openRequestId = ref(0)
/** 关闭表单 */
const closeForm = () => {
openRequestId.value++
emit('closed')
}
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
dialogVisible.value = true const currentRequestId = ++openRequestId.value
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm() resetForm()
@ -189,6 +176,7 @@ const open = async (type: string, id?: number) => {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await StockInApi.getStockIn(id) formData.value = await StockInApi.getStockIn(id)
if (currentRequestId !== openRequestId.value) return
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -199,7 +187,7 @@ const open = async (type: string, id?: number) => {
defineExpose({ open }) // open defineExpose({ open }) // open
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success', 'closed']) // success
const submitForm = async () => { const submitForm = async () => {
// //
await formRef.value.validate() await formRef.value.validate()
@ -215,9 +203,8 @@ const submitForm = async () => {
await StockInApi.updateStockIn(data) await StockInApi.updateStockIn(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false
//
emit('success') emit('success')
closeForm()
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -237,4 +224,68 @@ const resetForm = () => {
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
onBeforeUnmount(() => {
openRequestId.value++
})
</script> </script>
<style scoped lang="scss">
.dv-repair-panel {
background: #fff;
border-radius: 12px;
box-shadow: var(--el-box-shadow-light);
}
.dv-repair-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
}
.dv-repair-panel__title {
color: #1f2937;
font-size: 18px;
font-weight: 600;
}
.dv-repair-dialog {
max-height: calc(100vh - 220px);
padding: 20px 20px 0;
padding-right: 16px;
overflow-y: auto;
}
.dv-repair-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
width: 100%;
padding: 16px 20px 20px;
border-top: 1px solid #ebeef5;
}
@media (max-width: 1200px) {
.dv-repair-dialog :deep(.el-col) {
max-width: 100%;
flex: 0 100%;
}
}
@media (max-width: 768px) {
.dv-repair-panel__header {
padding: 14px 16px;
}
.dv-repair-dialog {
max-height: none;
padding: 16px 16px 0;
overflow-y: visible;
}
.dv-repair-footer {
padding: 16px;
}
}
</style>

@ -8,7 +8,7 @@
:inline-message="true" :inline-message="true"
:disabled="disabled" :disabled="disabled"
> >
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px"> <el-table :data="formData" size="small" show-summary :summary-method="getSummaries" class="-mt-10px" >
<el-table-column :label="t('common.index')" type="index" align="center" width="60" /> <el-table-column :label="t('common.index')" type="index" align="center" width="60" />
<el-table-column :label="t('ErpStock.Item.warehouse')" min-width="125"> <el-table-column :label="t('ErpStock.Item.warehouse')" min-width="125">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
@ -34,6 +34,31 @@
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('ErpStock.Item.barcode')" min-width="160">
<!-- <template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>-->
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productBarCode`" class="mb-0px!">
<el-select
v-model="row.productBarCode"
clearable
filterable
@change="onChangeProductCode($event, row)"
:placeholder="t('ErpStock.Item.placeholderBarcode')"
>
<el-option
v-for="item in productList"
:key="item.barCode"
:label="item.barCode"
:value="item.barCode"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.product')" min-width="180"> <el-table-column :label="t('ErpStock.Item.product')" min-width="180">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!"> <el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
@ -58,13 +83,7 @@
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('ErpStock.Item.barcode')" min-width="150">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.unit')" min-width="80"> <el-table-column :label="t('ErpStock.Item.unit')" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-form-item class="mb-0px!"> <el-form-item class="mb-0px!">
@ -85,7 +104,7 @@
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120"> <!-- <el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productPrice`" class="mb-0px!"> <el-form-item :prop="`${$index}.productPrice`" class="mb-0px!">
<el-input-number <el-input-number
@ -104,7 +123,7 @@
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" /> <el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>-->
<el-table-column :label="t('ErpStock.Item.remark')" fixed="right" min-width="150"> <el-table-column :label="t('ErpStock.Item.remark')" fixed="right" min-width="150">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<el-form-item :prop="`${$index}.remark`" class="mb-0px!"> <el-form-item :prop="`${$index}.remark`" class="mb-0px!">
@ -250,6 +269,19 @@ const handleDelete = (index) => {
formData.value.splice(index, 1) formData.value.splice(index, 1)
} }
/** 处理产品变更 */
const onChangeProductCode = (productBarCode, row) => {
const product = productList.value.find((item) => item.barCode === productBarCode)
if (product) {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
row.productId = product.id
}
//
setStockCount(row)
}
/** 处理仓库变更 */ /** 处理仓库变更 */
const onChangeWarehouse = (warehouseId, row) => { const onChangeWarehouse = (warehouseId, row) => {
// //

@ -1,4 +1,6 @@
<template> <template>
<div class="dv-repair-page">
<template v-if="!formVisible">
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<el-form <el-form
@ -44,23 +46,7 @@
class="!w-220px" class="!w-220px"
/> />
</el-form-item> </el-form-item>
<!-- <el-form-item label="供应商" prop="supplierId"> <el-form-item :label="t('ErpStock.In.warehouse')" prop="warehouseId" v-show="showAllFilters">
<el-select
v-model="queryParams.supplierId"
clearable
filterable
placeholder="请选择供应商"
class="!w-240px"
>
<el-option
v-for="item in supplierList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item> -->
<el-form-item :label="t('ErpStock.In.warehouse')" prop="warehouseId">
<el-select <el-select
v-model="queryParams.warehouseId" v-model="queryParams.warehouseId"
clearable clearable
@ -76,7 +62,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('ErpStock.In.creator')" prop="creator"> <el-form-item :label="t('ErpStock.In.creator')" prop="creator" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.creator" v-model="queryParams.creator"
clearable clearable
@ -92,7 +78,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('ErpStock.In.status')" prop="status"> <el-form-item :label="t('ErpStock.In.status')" prop="status" v-show="showAllFilters">
<el-select <el-select
v-model="queryParams.status" v-model="queryParams.status"
:placeholder="t('ErpStock.In.placeholderStatus')" :placeholder="t('ErpStock.In.placeholderStatus')"
@ -107,7 +93,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('ErpStock.In.remark')" prop="remark"> <el-form-item :label="t('ErpStock.In.remark')" prop="remark" v-show="showAllFilters">
<el-input <el-input
v-model="queryParams.remark" v-model="queryParams.remark"
:placeholder="t('ErpStock.In.placeholderRemark')" :placeholder="t('ErpStock.In.placeholderRemark')"
@ -116,6 +102,13 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -153,7 +146,6 @@
<ContentWrap> <ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick"> <el-tabs v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane :label="t('ErpStock.In.tabProduct')" name="产品入库" /> <el-tab-pane :label="t('ErpStock.In.tabProduct')" name="产品入库" />
<!-- <el-tab-pane :label="t('ErpStock.In.tabMaterial')" name="原料入库" /> -->
<el-tab-pane :label="t('ErpStock.In.tabPart')" name="备件入库" /> <el-tab-pane :label="t('ErpStock.In.tabPart')" name="备件入库" />
<el-tab-pane :label="t('ErpStock.In.tabOther')" name="其他入库" /> <el-tab-pane :label="t('ErpStock.In.tabOther')" name="其他入库" />
</el-tabs> </el-tabs>
@ -179,7 +171,6 @@
prop="productNames" prop="productNames"
min-width="200" min-width="200"
/> />
<!-- <el-table-column label="供应商" align="center" prop="supplierName" sortable /> -->
<el-table-column <el-table-column
:label="t('ErpStock.In.inTime')" :label="t('ErpStock.In.inTime')"
align="center" align="center"
@ -195,13 +186,6 @@
prop="totalCount" prop="totalCount"
:formatter="erpCountTableColumnFormatter" :formatter="erpCountTableColumnFormatter"
/> />
<!-- <el-table-column
label="金额"
align="right"
sortable
prop="totalPrice"
:formatter="erpPriceTableColumnFormatter"
/> -->
<el-table-column <el-table-column
:label="t('ErpStock.In.status')" :label="t('ErpStock.In.status')"
align="center" align="center"
@ -268,9 +252,11 @@
@pagination="getList" @pagination="getList"
/> />
</ContentWrap> </ContentWrap>
</template>
<!-- 表单弹窗添加/修改 --> <!-- 表单面板添加/修改 -->
<StockInForm ref="formRef" @success="getList" /> <StockInForm v-else ref="formRef" @success="getList" @closed="formVisible = false" />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -308,11 +294,18 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 7
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) // const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) // const warehouseList = ref<WarehouseVO[]>([]) //
const supplierList = ref<SupplierVO[]>([]) // const supplierList = ref<SupplierVO[]>([]) //
const userList = ref<UserVO[]>([]) // const userList = ref<UserVO[]>([]) //
const formVisible = ref(false) //
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
@ -340,7 +333,10 @@ const resetQuery = () => {
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
formVisible.value = true
nextTick(() => {
formRef.value.open(type, id) formRef.value.open(type, id)
})
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
@ -408,8 +404,6 @@ onMounted(async () => {
supplierList.value = await SupplierApi.getSupplierSimpleList() supplierList.value = await SupplierApi.getSupplierSimpleList()
userList.value = await UserApi.getSimpleUserList() userList.value = await UserApi.getSimpleUserList()
}) })
// TODO
// TODO
let activeName = '产品入库' let activeName = '产品入库'
const handleTabClick = (tab: TabsPaneContext) => { const handleTabClick = (tab: TabsPaneContext) => {
queryParams.inType = tab.paneName queryParams.inType = tab.paneName

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

Loading…
Cancel
Save