e03ff28fb1dbaa19571b7ea0414e0161f178cf26..db42d47f5692ef64e5436c5a6d29dcb537b44596
昨天 zouyu
浪潮对接单点登录:mis调整
db42d4 对比 | 目录
昨天 zouyu
Merge remote-tracking branch 'refs/remotes/origin/dev_New' into dev_tide_mi...
1c0863 对比 | 目录
昨天 yaowanxin
Merge remote-tracking branch 'origin/dev_New' into dev_New
60830a 对比 | 目录
昨天 yaowanxin
设备管理-巡检管理-更新巡检管理搜索字段
af671f 对比 | 目录
昨天 zhangwencui
来票台账接口报错修改
8d724f 对比 | 目录
昨天 spring
fix: 修改设备模块bug
9a9764 对比 | 目录
昨天 spring
fix: 计量器具附件功能优化
675f77 对比 | 目录
昨天 spring
fix: 删除接口引入
73904f 对比 | 目录
昨天 gaoluyang
进销存升级 1.销售台账页面代码重构 2.发货台账页面联调
a868d0 对比 | 目录
昨天 gaoluyang
Merge remote-tracking branch 'origin/dev_New' into dev_New
8b531e 对比 | 目录
昨天 gaoluyang
进销存升级 1.财务报表页面样式修改
01975c 对比 | 目录
3 天以前 gongchunyi
feat: BOM的导入导出功能
a3aed6 对比 | 目录
4 天以前 liyong
feat(inventory): 计量器具
b3474a 对比 | 目录
4 天以前 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
8d1df6 对比 | 目录
4 天以前 huminmin
仓储物流:对接入库出库导出
927ee6 对比 | 目录
4 天以前 spring
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
fa4ea8 对比 | 目录
4 天以前 spring
fix: 印章管理样式修改
64e9df 对比 | 目录
4 天以前 gongchunyi
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
2078a3 对比 | 目录
4 天以前 gongchunyi
refactor: 产品结构详情:重构为树结构显示
c7677e 对比 | 目录
4 天以前 yaowanxin
Merge remote-tracking branch 'origin/dev_New' into dev_New
e38472 对比 | 目录
4 天以前 yaowanxin
销售管理-销售台账的附件显示
84c3ef 对比 | 目录
4 天以前 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
209017 对比 | 目录
4 天以前 huminmin
仓储物流:对接库存导出
a3c307 对比 | 目录
4 天以前 yaowanxin
附件上传两个分页修改
b83dd2 对比 | 目录
4 天以前 yaowanxin
Merge remote-tracking branch 'origin/dev_New' into dev_New
ef0aec 对比 | 目录
4 天以前 yaowanxin
添加供应商管理页面标签页切换功能
7168aa 对比 | 目录
4 天以前 huminmin
仓储物流:修改来源api
830f07 对比 | 目录
4 天以前 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
823573 对比 | 目录
4 天以前 huminmin
仓储物流:入库出库列表增加来源
e94be3 对比 | 目录
4 天以前 chenhj
Merge remote-tracking branch 'origin/dev_New' into dev_New
cac302 对比 | 目录
4 天以前 chenhj
器具校准,校准记录
a7e6e4 对比 | 目录
4 天以前 huminmin
仓储物流:修改页面,增加合格\不合格入库;合格\不合格出库;合格\不合格库存
e5493b 对比 | 目录
4 天以前 huminmin
仓储物流-出库台账管理
64d6d0 对比 | 目录
5 天以前 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
070cf8 对比 | 目录
5 天以前 gongchunyi
fix: 产品信息Id传参错误
4a20b2 对比 | 目录
5 天以前 huminmin
仓储物流-入库管理列表;仓储物流-库存管理-列表、新增、领用;
76b797 对比 | 目录
5 天以前 spring
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
a3800b 对比 | 目录
5 天以前 spring
fix: 优化协同审批部门选择初始化校验及通知公告类型维护删除传参
d6a1a3 对比 | 目录
5 天以前 zhangwencui
规章制度阅读状态隐藏
a78fe5 对比 | 目录
5 天以前 gongchunyi
fix: 接口参数传递错误
fcfb6e 对比 | 目录
5 天以前 spring
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
0ed79e 对比 | 目录
5 天以前 spring
fix: 调整大屏样式
0862ec 对比 | 目录
5 天以前 zss
生产报工增加报废数量
0273d9 对比 | 目录
5 天以前 huminmin
注释
9b5a24 对比 | 目录
6 天以前 zhangwencui
报表管理少个tooltip
041b5f 对比 | 目录
6 天以前 zhangwencui
报表管理遗漏修改
083887 对比 | 目录
6 天以前 spring
fix: 处理不合格-处理结果下拉选择
2c9131 对比 | 目录
6 天以前 spring
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
e9e036 对比 | 目录
6 天以前 spring
fix: 不合格处理,返工返修报废,返工需要重新生产
e4e6f6 对比 | 目录
6 天以前 gongchunyi
feat: 质量管理-报表管理后端接口数据对接
224248 对比 | 目录
已复制1个文件
已添加310个文件
已重命名12个文件
已修改148个文件
已删除13个文件
75715 ■■■■ 文件已修改
.env.development 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.staging 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 386 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/AYNMico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/BDSMico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/BHMY.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/CJNYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/CMNYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/DHDCico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/DHHBico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/DZYSico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HCKXico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HCMYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HGJJico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HHKJIco.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HSMYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HSXico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HXGYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HXSJico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HYSNico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JLMYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JLSNico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JMSLico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JSMYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JSYNYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JYHJico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JZYJico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/LQMico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/MKZSico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/MXSCIco.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/NYDLico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/PHMKico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/QLMCico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/RTSWico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/RZNY.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/TJKHico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/TJXM.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/TYMKico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/WDSYico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/XYHBico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/ZDXMico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/ZQHXico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/ZYRQico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/favicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/AYNMLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/BDSMLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/BHMYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/CJNYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/CMNYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/DHDCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/DHHBLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/DZYSLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HCKXLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HCMYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HGJJLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HHKJLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HSMYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HSXLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HXGYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HXSJLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HYSNLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JLMYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JLSNLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JMSLLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JSMYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JSYNYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JYHJLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JZYJLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/LCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/LQMLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/MKZSLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/MXSCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/NYDLLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/PHMKLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/QLMCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/RTSWLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/RZNYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/TJKHLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/TJXMLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/TYMKLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/WDSYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/XYHBLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/ZDXMLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/ZGLTLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/ZQHXLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/ZYRQLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/南通云从工业互联网有限公司.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/新缆(江苏)数字科技有限公司.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/芯导软件(江苏)有限公司.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/AYNMView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/BDSMView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/DHDCView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/DHHBView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/DZYSView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HCKXView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HCMYView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HGJJView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HHKJView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HSMYView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HSXView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HXGYView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HXSJView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/HYSNView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/JLSNView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/JMSLView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/JZYJView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/LQMView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/MKZSView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/MXSCBack.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/NYDLView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/PHMKView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/QLMCView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/RTSWView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/RZNYView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/TJKHView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/TJXMView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/TYMKView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/WDSYView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/XYHBView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/ZDXMView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/ZQHXView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/ZYRQView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/multiple-build.js 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/enum.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productModel.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productProcess.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/supplierManageFile.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/attendanceManagement.js 162 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/enterpriseBook.js 72 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/noticeManagement.js 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/rpaManagement.js 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/shipmentReview.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/customerService/index.js 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/brand.js 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/calibration.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/defectManagement.js 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/deviceInfo.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/measurementEquipment.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/spareParts.js 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/upkeep.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/bookshelf.js 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/borrow.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/document.js 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/return.js 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/statistics.js 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/accounting.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/expenseManagement.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/loanManagement.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inspectionManagement/index.js 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inspectionUpload/index.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockIn.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInRecord.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInventory.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockManage.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockOut.js 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockUninventory.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/oaSystem/projectManagement.js 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/employeeRecord.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/onboarding.js 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/scheduling.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/selfService.js 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffAnalytics.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffContract.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffLeave.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/advancedPriceManagement.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/arrivalManagement.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementInvoiceLedger.js 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementLedger.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementPlan.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementReport.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/returnManagement.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/transferManagement.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRoute.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRouteItem.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productBom.js 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productStructure.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductInput.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductMain.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductOutput.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionReporting.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/workOrder.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/publicApi/index.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/metricMaintenance.js 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/nearExpiryReturn.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/qualityTestStandardBinding.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/reportAnalysis/qualityReport.js 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/deliveryLedger.js 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/indicatorStats.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/paymentShipping.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/receiptPayment.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/salesLedger.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/salespersonManagement.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/strategyControl.js 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/message.js 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/post.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/viewIndex.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/backImage@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/biaoti.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/border@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/caiwufenxiback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/chuchangyijianicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/guochengyijianicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongicon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongjineback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongjineicon1@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongjineicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongtitleback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/icon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/jiantou@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/kehuhetongback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/pieback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shijianmingchengbeijing@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shijianmingxiicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shujutongji@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shujutongjiicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/yuancailiaoyijianicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/zonghetongbingtubiankuang@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/circleBlue@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/circleGreen@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/circleOrange@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/circleRed@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/circleYellow@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/walletBlue@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/walletGreen@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/walletOrange@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/walletRed@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/walletYellow@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard2.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard3.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/video.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/LCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/login-background.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/logo/敦煌鼎诚.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FileListDialog.vue 309 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FormDialog.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/ImportDialog.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DynamicTable/index.vue 402 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Echarts/echarts.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PageHeader/index.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/QRCodeGenerator/index.vue 566 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/AppMain.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/NotificationCenter/index.vue 372 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/index.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 229 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 213 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/util.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFile/index.vue 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/components/BlacklistTab.vue 564 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/components/HomeTab.vue 569 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/filesDia.vue 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/index.vue 549 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/chatHome/chatHomeIndex/MobileChat.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/enterpriseBook/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/noticeManagement/index.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/summary/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue 996 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/sealManagement/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/shipmentReview/fileList.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/shipmentReview/index.vue 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/afterSalesHandling/components/formDia.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/afterSalesHandling/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/expiryAfterSales/components/formDia.vue 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/expiryAfterSales/index.vue 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/components/formDia.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/carbonManagement/index.vue 1553 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/dynamicEnergySaving/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/energyPeriodTime/index.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/energyPower/components/formDia.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/energyPower/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/energyTrends/index.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/gasManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/meterCollection/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/components/formDia.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/components/waterBillForm.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/brand/index.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/calibration/index.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/defectManagement/index.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/deviceInfo/index.vue 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/gasTank/simple.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/formDia.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/qrCodeDia.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/viewQrCodeFiles.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/index.vue 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/iotMonitor/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/iotMonitor/indexWD.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/kplMonitor/index.vue 714 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Form.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Modal.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/formDia.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/rowClickData.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/filesDia.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/index.vue 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/operationManagement/index.vue 370 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/MaintainForm.vue 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/RepairForm.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/MaintainModal.vue 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/RepairModal.vue 190 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/index.vue 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/spareParts/index.vue 401 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanForm.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanModal.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/formDia.vue 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 764 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/example/DynamicTableExample.vue 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/example/SimpleExample.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/bookshelf/detail.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/bookshelf/index.vue 688 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/borrow/index.vue 647 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/document/attachmentManager.vue 426 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/document/index.vue 1416 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/return/index.vue 699 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/statistics/index.vue 539 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/accounting/index.vue 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/Form.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/Modal.vue 192 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/index.vue 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/financialStatements/index.vue 941 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/inventoryAccounting/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/loanManagement/Modal.vue 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/loanManagement/index.vue 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/Form.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/Modal.vue 192 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/index.vue 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 245 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/Record.vue 711 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/index.vue 397 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/issueManagement/index.vue 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/Record.vue 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/index.vue 226 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Qualified.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Subtract.vue 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Unqualified.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 398 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockReport/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/ledger/Form.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/ledger/Modal.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/ledger/filesDia.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/ledger/index.vue 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/statistics/index.vue 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitorManagement/areaControl/index.vue 264 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitorManagement/videoMonitor/index.vue 990 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/oaSystem/projectManagement/components/milestoneList.vue 289 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/oaSystem/projectManagement/components/phaseGoalList.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/oaSystem/projectManagement/components/projectForm.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/oaSystem/projectManagement/components/taskTree.vue 834 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/oaSystem/projectManagement/index.vue 481 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/oaSystem/projectManagement/projectDetail.vue 565 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/analytics/index.vue 702 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/components/formDia.vue 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/filesDia.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/index.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/components/formDia.vue 445 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/index.vue 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue 321 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/RenewContract.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/Show.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/formDia.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/index.vue 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/components/formDia.vue 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/index.vue 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/components/formDia.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/scheduling/index.vue 622 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/selfService/index.vue 800 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/advancedPriceManagement/index.vue 773 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/arrivalManagement/index.vue 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/index.vue 418 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/components/Modal.vue 935 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/index.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/indexOld.vue 730 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentEntry/index.vue 428 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentHistory/index.vue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/priceManagement/index.vue 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 3055 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementPlan/index.vue 772 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementReport/index.vue 380 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseOrder/index.vue 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/qualityInspection/index.vue 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/returnManagement/index.vue 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/transferManagement/index.vue 431 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productManagement/productIdentifier/index.vue 819 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/components/formDia.vue 130 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/index.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Edit.vue 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/ItemsForm.vue 531 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/New.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 876 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 449 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/StructureEdit.vue 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/index.vue 416 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/formDia.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/index.vue 510 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 723 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/Input.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/Output.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/formDia.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 796 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/safetyMonitoring/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 653 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/filesDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 504 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index.vue 1161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index0.vue 415 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nearExpiryReturn/index.vue 395 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/formDia.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/visualization/qualityDashboard.vue 307 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index.vue 2036 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/projectProfit/index.vue 190 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement.vue 733 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 1471 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/taxComparison/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/customerManagement/index.vue 423 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/deliveryLedger/index.vue 661 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/indicatorStats/index.vue 399 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceLedger/index.vue 112 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceRegistration/index.vue 1268 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/orderManagement/index.vue 490 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/paymentShipping/index.vue 497 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 439 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentHistory/index.vue 141 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 2465 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 1035 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salespersonManagement/index.vue 371 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/strategyControl/index.vue 1587 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 452 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 1231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -1,8 +1,8 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = èŠ¯å¯¼äº‘ï¼ˆç®¡ç†ä¿¡æ¯ç³»ç»Ÿï¼‰
VITE_APP_TITLE = ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包
# å¼€å‘环境配置
VITE_APP_ENV = 'development'
# èŠ¯å¯¼äº‘ï¼ˆç®¡ç†ä¿¡æ¯ç³»ç»Ÿï¼‰/开发环境
# ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包/开发环境
VITE_APP_BASE_API = '/dev-api'
.env.production
@@ -1,13 +1,5 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE =芯导-仓储物流系统
#10.136.58.65    èН坼-财务管理系统    cwglxt
#10.136.58.66    èН坼-设备管理系统    sbglxt
#10.136.58.67    èН坼-生产管控系统    scgkxt
#10.136.58.68    èН坼-协同办公系统    xtbgxt
#10.136.58.69    èН坼-采购管理系统    cgglxt
#10.136.58.70    èН坼-仓储物流系统    ccwlxt
#10.136.58.71    èН坼-营销管理系统    yxglxt
#10.136.58.72    èН坼-人力资源系统    rlzyxt
VITE_APP_TITLE = MIS管理系统
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
.env.staging
@@ -1,11 +1,11 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = èŠ¯å¯¼äº‘ï¼ˆç®¡ç†ä¿¡æ¯ç³»ç»Ÿï¼‰
VITE_APP_TITLE = ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'staging'
# èŠ¯å¯¼äº‘ï¼ˆç®¡ç†ä¿¡æ¯ç³»ç»Ÿï¼‰/生产环境
# ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包/生产环境
VITE_APP_BASE_API = '/stage-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
VITE_BUILD_COMPRESS = gzip
VITE_BUILD_COMPRESS = gzip
README.md
@@ -33,6 +33,7 @@
# æž„建测试环境 yarn build:stage
# æž„建生产环境 yarn build:prod
# æž„建生产环境 yarn build:prod -- --company="AAA"
# å‰ç«¯è®¿é—®åœ°å€ http://localhost:80
```
index.html
@@ -1,215 +1,217 @@
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/favicon.ico">
  <title>%VITE_APP_TITLE%</title>
  <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
  <style>
    html,
    body,
    #app {
      height: 100%;
      margin: 0px;
      padding: 0px;
    }
    .chromeframe {
      margin: 0.2em 0;
      background: #ccc;
      color: #000;
      padding: 0.2em 0;
    }
    #loader-wrapper {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 999999;
    }
    #loader {
      display: block;
      position: relative;
      left: 50%;
      top: 50%;
      width: 150px;
      height: 150px;
      margin: -75px 0 0 -75px;
      border-radius: 50%;
      border: 3px solid transparent;
      border-top-color: #FFF;
      -webkit-animation: spin 2s linear infinite;
      -ms-animation: spin 2s linear infinite;
      -moz-animation: spin 2s linear infinite;
      -o-animation: spin 2s linear infinite;
      animation: spin 2s linear infinite;
      z-index: 1001;
    }
    #loader:before {
      content: "";
      position: absolute;
      top: 5px;
      left: 5px;
      right: 5px;
      bottom: 5px;
      border-radius: 50%;
      border: 3px solid transparent;
      border-top-color: #FFF;
      -webkit-animation: spin 3s linear infinite;
      -moz-animation: spin 3s linear infinite;
      -o-animation: spin 3s linear infinite;
      -ms-animation: spin 3s linear infinite;
      animation: spin 3s linear infinite;
    }
    #loader:after {
      content: "";
      position: absolute;
      top: 15px;
      left: 15px;
      right: 15px;
      bottom: 15px;
      border-radius: 50%;
      border: 3px solid transparent;
      border-top-color: #FFF;
      -moz-animation: spin 1.5s linear infinite;
      -o-animation: spin 1.5s linear infinite;
      -ms-animation: spin 1.5s linear infinite;
      -webkit-animation: spin 1.5s linear infinite;
      animation: spin 1.5s linear infinite;
    }
    @-webkit-keyframes spin {
      0% {
        -webkit-transform: rotate(0deg);
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="renderer" content="webkit" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    />
    <link rel="icon" href="/favicon.ico" />
    <title>%VITE_APP_TITLE%</title>
    <!--[if lt IE 11
      ]><script>
        window.location.href = "/html/ie.html";
      </script><!
    [endif]-->
    <style>
      html,
      body,
      #app {
        height: 100%;
        margin: 0px;
        padding: 0px;
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
        transform: rotate(360deg);
      }
    }
    @keyframes spin {
      0% {
        -webkit-transform: rotate(0deg);
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      .chromeframe {
        margin: 0.2em 0;
        background: #ccc;
        color: #000;
        padding: 0.2em 0;
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
        transform: rotate(360deg);
      #loader-wrapper {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 999999;
      }
    }
      #loader {
        display: block;
        position: relative;
        left: 50%;
        top: 50%;
        width: 150px;
        height: 150px;
        margin: -75px 0 0 -75px;
        border-radius: 50%;
        border: 3px solid transparent;
        border-top-color: #fff;
        -webkit-animation: spin 2s linear infinite;
        -ms-animation: spin 2s linear infinite;
        -moz-animation: spin 2s linear infinite;
        -o-animation: spin 2s linear infinite;
        animation: spin 2s linear infinite;
        z-index: 1001;
      }
    #loader-wrapper .loader-section {
      position: fixed;
      top: 0;
      width: 51%;
      height: 100%;
      background: #7171C6;
      z-index: 1000;
      -webkit-transform: translateX(0);
      -ms-transform: translateX(0);
      transform: translateX(0);
    }
      #loader:before {
        content: "";
        position: absolute;
        top: 5px;
        left: 5px;
        right: 5px;
        bottom: 5px;
        border-radius: 50%;
        border: 3px solid transparent;
        border-top-color: #fff;
        -webkit-animation: spin 3s linear infinite;
        -moz-animation: spin 3s linear infinite;
        -o-animation: spin 3s linear infinite;
        -ms-animation: spin 3s linear infinite;
        animation: spin 3s linear infinite;
      }
    #loader-wrapper .loader-section.section-left {
      left: 0;
    }
      #loader:after {
        content: "";
        position: absolute;
        top: 15px;
        left: 15px;
        right: 15px;
        bottom: 15px;
        border-radius: 50%;
        border: 3px solid transparent;
        border-top-color: #fff;
        -moz-animation: spin 1.5s linear infinite;
        -o-animation: spin 1.5s linear infinite;
        -ms-animation: spin 1.5s linear infinite;
        -webkit-animation: spin 1.5s linear infinite;
        animation: spin 1.5s linear infinite;
      }
    #loader-wrapper .loader-section.section-right {
      right: 0;
    }
      @-webkit-keyframes spin {
        0% {
          -webkit-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          transform: rotate(0deg);
        }
        100% {
          -webkit-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }
    .loaded #loader-wrapper .loader-section.section-left {
      -webkit-transform: translateX(-100%);
      -ms-transform: translateX(-100%);
      transform: translateX(-100%);
      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
    }
      @keyframes spin {
        0% {
          -webkit-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          transform: rotate(0deg);
        }
    .loaded #loader-wrapper .loader-section.section-right {
      -webkit-transform: translateX(100%);
      -ms-transform: translateX(100%);
      transform: translateX(100%);
      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
    }
        100% {
          -webkit-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }
    .loaded #loader {
      opacity: 0;
      -webkit-transition: all 0.3s ease-out;
      transition: all 0.3s ease-out;
    }
      #loader-wrapper .loader-section {
        position: fixed;
        top: 0;
        width: 51%;
        height: 100%;
        background: #7171c6;
        z-index: 1000;
        -webkit-transform: translateX(0);
        -ms-transform: translateX(0);
        transform: translateX(0);
      }
    .loaded #loader-wrapper {
      visibility: hidden;
      -webkit-transform: translateY(-100%);
      -ms-transform: translateY(-100%);
      transform: translateY(-100%);
      -webkit-transition: all 0.3s 1s ease-out;
      transition: all 0.3s 1s ease-out;
    }
      #loader-wrapper .loader-section.section-left {
        left: 0;
      }
    .no-js #loader-wrapper {
      display: none;
    }
      #loader-wrapper .loader-section.section-right {
        right: 0;
      }
    .no-js h1 {
      color: #222222;
    }
      .loaded #loader-wrapper .loader-section.section-left {
        -webkit-transform: translateX(-100%);
        -ms-transform: translateX(-100%);
        transform: translateX(-100%);
        -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      }
    #loader-wrapper .load_title {
      font-family: 'Open Sans';
      color: #FFF;
      font-size: 19px;
      width: 100%;
      text-align: center;
      z-index: 9999999999999;
      position: absolute;
      top: 60%;
      opacity: 1;
      line-height: 30px;
    }
      .loaded #loader-wrapper .loader-section.section-right {
        -webkit-transform: translateX(100%);
        -ms-transform: translateX(100%);
        transform: translateX(100%);
        -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      }
    #loader-wrapper .load_title span {
      font-weight: normal;
      font-style: italic;
      font-size: 13px;
      color: #FFF;
      opacity: 0.5;
    }
  </style>
</head>
      .loaded #loader {
        opacity: 0;
        -webkit-transition: all 0.3s ease-out;
        transition: all 0.3s ease-out;
      }
<body>
  <div id="app">
    <div id="loader-wrapper">
      <div id="loader"></div>
      <div class="loader-section section-left"></div>
      <div class="loader-section section-right"></div>
      <div class="load_title">正在加载系统资源,请耐心等待</div>
      .loaded #loader-wrapper {
        visibility: hidden;
        -webkit-transform: translateY(-100%);
        -ms-transform: translateY(-100%);
        transform: translateY(-100%);
        -webkit-transition: all 0.3s 1s ease-out;
        transition: all 0.3s 1s ease-out;
      }
      .no-js #loader-wrapper {
        display: none;
      }
      .no-js h1 {
        color: #222222;
      }
      #loader-wrapper .load_title {
        font-family: "Open Sans";
        color: #fff;
        font-size: 19px;
        width: 100%;
        text-align: center;
        z-index: 9999999999999;
        position: absolute;
        top: 60%;
        opacity: 1;
        line-height: 30px;
      }
      #loader-wrapper .load_title span {
        font-weight: normal;
        font-style: italic;
        font-size: 13px;
        color: #fff;
        opacity: 0.5;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div id="loader-wrapper">
        <div id="loader"></div>
        <div class="loader-section section-left"></div>
        <div class="loader-section section-right"></div>
        <div class="load_title">正在加载系统资源,请耐心等待</div>
      </div>
    </div>
  </div>
  <script type="module" src="/src/main.js"></script>
</body>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
multiple/assets/favicon/AYNMico.ico
multiple/assets/favicon/BDSMico.ico
multiple/assets/favicon/BHMY.ico
multiple/assets/favicon/CJNYico.ico
multiple/assets/favicon/CMNYico.ico
multiple/assets/favicon/DHDCico.ico
multiple/assets/favicon/DHHBico.ico
multiple/assets/favicon/DZYSico.ico
multiple/assets/favicon/HCKXico.ico
multiple/assets/favicon/HCMYico.ico
multiple/assets/favicon/HGJJico.ico
multiple/assets/favicon/HHKJIco.ico
multiple/assets/favicon/HSMYico.ico
multiple/assets/favicon/HSXico.ico
multiple/assets/favicon/HXGYico.ico
multiple/assets/favicon/HXSJico.ico
multiple/assets/favicon/HYSNico.ico

multiple/assets/favicon/JLMYico.ico
multiple/assets/favicon/JLSNico.ico
multiple/assets/favicon/JMSLico.ico
multiple/assets/favicon/JSMYico.ico
multiple/assets/favicon/JSYNYico.ico
multiple/assets/favicon/JYHJico.ico
multiple/assets/favicon/JZYJico.ico

multiple/assets/favicon/LQMico.ico
multiple/assets/favicon/MKZSico.ico
multiple/assets/favicon/MXSCIco.ico
multiple/assets/favicon/NYDLico.ico
multiple/assets/favicon/PHMKico.ico
multiple/assets/favicon/QLMCico.ico
multiple/assets/favicon/RTSWico.ico
multiple/assets/favicon/RZNY.ico
multiple/assets/favicon/TJKHico.ico
multiple/assets/favicon/TJXM.ico
multiple/assets/favicon/TYMKico.ico
multiple/assets/favicon/WDSYico.ico

multiple/assets/favicon/XYHBico.ico
multiple/assets/favicon/ZDXMico.ico
multiple/assets/favicon/ZQHXico.ico

multiple/assets/favicon/ZYRQico.ico
multiple/assets/favicon/favicon.ico
multiple/assets/logo/AYNMLogo.png
multiple/assets/logo/BDSMLogo.png
multiple/assets/logo/BHMYLogo.png
multiple/assets/logo/CJNYLogo.png
multiple/assets/logo/CMNYLogo.png
multiple/assets/logo/DHDCLogo.png
multiple/assets/logo/DHHBLogo.png
multiple/assets/logo/DZYSLogo.png
multiple/assets/logo/HCKXLogo.png
multiple/assets/logo/HCMYLogo.png
multiple/assets/logo/HGJJLogo.png
multiple/assets/logo/HHKJLogo.png
multiple/assets/logo/HSMYLogo.png
multiple/assets/logo/HSXLogo.png
multiple/assets/logo/HXGYLogo.png
multiple/assets/logo/HXSJLogo.png
multiple/assets/logo/HYSNLogo.png

multiple/assets/logo/JLMYLogo.png
multiple/assets/logo/JLSNLogo.png
multiple/assets/logo/JMSLLogo.png
multiple/assets/logo/JSMYLogo.png
multiple/assets/logo/JSYNYLogo.png
multiple/assets/logo/JYHJLogo.png
multiple/assets/logo/JZYJLogo.png

multiple/assets/logo/LCLogo.png
multiple/assets/logo/LQMLogo.png
multiple/assets/logo/MKZSLogo.png
multiple/assets/logo/MXSCLogo.png
multiple/assets/logo/NYDLLogo.png
multiple/assets/logo/PHMKLogo.png
multiple/assets/logo/QLMCLogo.png
multiple/assets/logo/RTSWLogo.png
multiple/assets/logo/RZNYLogo.png
multiple/assets/logo/TJKHLogo.png
multiple/assets/logo/TJXMLogo.png
multiple/assets/logo/TYMKLogo.png
multiple/assets/logo/WDSYLogo.png

multiple/assets/logo/XYHBLogo.png
multiple/assets/logo/ZDXMLogo.png
multiple/assets/logo/ZGLTLogo.png
multiple/assets/logo/ZQHXLogo.png

multiple/assets/logo/ZYRQLogo.png
multiple/assets/logo/logo.png
multiple/assets/logo/ÄÏÍ¨ÔÆ´Ó¹¤Òµ»¥ÁªÍøÓÐÏÞ¹«Ë¾.png
multiple/assets/logo/ÐÂÀ£¨½­ËÕ£©Êý×ֿƼ¼ÓÐÏÞ¹«Ë¾.png
multiple/assets/logo/оµ¼Èí¼þ£¨½­ËÕ£©ÓÐÏÞ¹«Ë¾.png
multiple/assets/screen/AYNMView.png
multiple/assets/screen/BDSMView.png
multiple/assets/screen/DHDCView.png
multiple/assets/screen/DHHBView.png
multiple/assets/screen/DZYSView.png
multiple/assets/screen/HCKXView.png
multiple/assets/screen/HCMYView.png
multiple/assets/screen/HGJJView.png
multiple/assets/screen/HHKJView.png
multiple/assets/screen/HSMYView.png
multiple/assets/screen/HSXView.png
multiple/assets/screen/HXGYView.png
multiple/assets/screen/HXSJView.png
multiple/assets/screen/HYSNView.png

multiple/assets/screen/JLSNView.png
multiple/assets/screen/JMSLView.png
multiple/assets/screen/JZYJView.png

multiple/assets/screen/LQMView.png
multiple/assets/screen/MKZSView.png
multiple/assets/screen/MXSCBack.png
multiple/assets/screen/NYDLView.png
multiple/assets/screen/PHMKView.png
multiple/assets/screen/QLMCView.png
multiple/assets/screen/RTSWView.png
multiple/assets/screen/RZNYView.png
multiple/assets/screen/TJKHView.png
multiple/assets/screen/TJXMView.png
multiple/assets/screen/TYMKView.png
multiple/assets/screen/WDSYView.png

multiple/assets/screen/XYHBView.png
multiple/assets/screen/ZDXMView.png
multiple/assets/screen/ZQHXView.png

multiple/assets/screen/ZYRQView.png
multiple/config.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
{
  "default": {
    "env": {
      "VITE_APP_TITLE": "芯导云(管理信息系统)"
    },
    "screen": "screen/JZYJView.png",
    "logo": "logo/HYSNLogo.png",
    "favicon": "favicon/HYSNico.ico"
  },
  "TEST": {
    "env": {
      "VITE_APP_TITLE": "中小企业数字化转型二级套餐包",
      "VITE_BASE_API": "http://1.15.17.182:9003",
      "VITE_JAVA_API": "http://1.15.17.182:9002"
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/ZGLTLogo.png",
    "favicon": "favicon/favicon.ico"
  },
  "screen": "/src/assets/images/login-background.png",
  "logo": "/src/assets/logo/logo.png",
  "favicon": "/public/favicon.ico"
}
multiple/multiple-build.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { execSync } from "child_process";
// èŽ·å– __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// è¯»å– JSON é…ç½®
const data = await fs.readFile(path.join(__dirname, 'config.json'), 'utf-8');
const config = JSON.parse(data);
// é¡¹ç›®è·¯å¾„
const rootPath = path.resolve(__dirname, '..');
const resourcePath = path.join(rootPath, 'multiple', 'assets');
const replacePath = path.join(rootPath, 'replace');
// èŽ·å–å‘½ä»¤è¡Œå‚æ•°
const params = parseArgs(process.argv);
const company = params["company"] ?? "default";
const companyMap = config[company];
const envFilePath = path.join(process.cwd(), '.env.production.local');
try {
    // 1️⃣ ç”Ÿæˆ .env
    console.log("=======生成.env=======");
    const envContent = Object.entries(companyMap.env)
        .map(([key, value]) => `${key}='${value}'`)
        .join('\n') + '\n';
    await fs.writeFile(envFilePath, envContent, 'utf-8');
    // 2️⃣ å¤‡ä»½åŽŸå§‹èµ„æºå¹¶æ›¿æ¢
    console.log("=======修改资源=======");
    for (const [key, value] of Object.entries(companyMap)) {
        if (key === 'env') continue;
        const originFile = path.join(rootPath, config[key]);
        const backupFile = path.join(replacePath, config[key]);
        const replaceFile = path.join(resourcePath, companyMap[key]);
        await fs.mkdir(path.dirname(backupFile), { recursive: true });
        await fs.copyFile(originFile, backupFile);
        await fs.copyFile(replaceFile, originFile);
    }
    console.log("=====开始打包======");
    execSync("vite build", { stdio: "inherit" });
    console.log("=====打包完成======");
} finally {
    console.log("=====恢复资源======");
    // åˆ é™¤ä¸´æ—¶ .env æ–‡ä»¶
    if (fsSync.existsSync(envFilePath)) {
        await fs.unlink(envFilePath);
        console.log(`🗑️ å·²åˆ é™¤ ${envFilePath}`);
    }
    // æ¢å¤èµ„源文件
    if (fsSync.existsSync(replacePath)) {
        for (const [key, value] of Object.entries(companyMap)) {
            if (key === 'env') continue;
            const originFile = path.join(rootPath, config[key]);
            const backupFile = path.join(replacePath, config[key]);
            await fs.copyFile(backupFile, originFile);
        }
        await fs.rm(replacePath, { recursive: true, force: true });
        console.log(`🗑️ å·²åˆ é™¤ ${replacePath}`);
    }
}
// ç®€å•命令行参数解析
function parseArgs(argv) {
    const params = {};
    for (const arg of argv.slice(2)) {
        if (arg.startsWith('--')) {
            const [key, value] = arg.slice(2).split('=');
            params[key] = value ?? true;
        }
    }
    return params;
}
src/api/basicData/enum.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
import request from "@/utils/request.js";
export function findAllStockRecordTypeOptions() {
    return request({
        url: '/basic/enum/stockRecordType',
        method: 'get'
    })
}
export function findAllQualifiedStockRecordTypeOptions() {
    return request({
        url: '/basic/enum/StockQualifiedRecordTypeEnum',
        method: 'get'
    })
}
export function findAllUnqualifiedStockRecordTypeOptions() {
    return request({
        url: '/basic/enum/StockUnQualifiedRecordTypeEnum',
        method: 'get'
    })
}
src/api/basicData/productModel.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import request from "@/utils/request.js";
export function productModelList(query) {
    return request({
        url: '/basic/product/pageModel',
        method: 'get',
        params: query
    })
}
src/api/basicData/productProcess.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from '@/utils/request'
// å·¥åºåˆ—表分页查询
export function productProcessListPage(query) {
  return request({
    url: '/productProcess/listPage',
    method: 'get',
    params: query
  })
}
src/api/basicData/supplierManageFile.js
@@ -49,4 +49,27 @@
        data: ids
    })
}
// æŸ¥è¯¢é™„件列表
export function fileListPage(query) {
    return request({
        url: "/basic/supplierManageFile/listPage",
        method: "get",
        params: query,
    });
}
// ä¿å­˜é™„件列表
export function fileAdd(query) {
    return request({
        url: "/basic/supplierManageFile/add",
        method: "post",
        data: query,
    });
}
// åˆ é™¤é™„件列表
export function fileDel(query) {
    return request({
        url: "/basic/supplierManageFile/del",
        method: "delete",
        data: query,
    });
}
src/api/collaborativeApproval/attendanceManagement.js
@@ -2,133 +2,135 @@
// æŸ¥è¯¢å‡æœŸè®¾ç½®åˆ—表
export function listHolidaySettings(query) {
    return request({
        url: "/holidaySettings/getList",
        method: "get",
        params: query,
    });
  return request({
    url: "/holidaySettings/getList",
    method: "get",
    params: query,
  });
}
//查询年假规则列表
export function listAnnualLeaveSettingList(query) {
    return request({
        url: "/holidaySettings/getAnnualLeaveSettingList",
        method: "get",
        params: query,
    });
  return request({
    url: "/holidaySettings/getAnnualLeaveSettingList",
    method: "get",
    params: query,
  });
}
//查询加班规则列表
export function listOvertimeSettingList(query) {
    return request({
        url: "/holidaySettings/getOvertimeSettingList",
        method: "get",
        params: query,
    });
  return request({
    url: "/holidaySettings/getOvertimeSettingList",
    method: "get",
    params: query,
  });
}
//查询工作时间规则列表
export function listWorkingHoursSettingList(query) {
    return request({
        url: "/holidaySettings/getWorkingHoursSettingList",
        method: "get",
        params: query,
    });
  return request({
    url: "/holidaySettings/getWorkingHoursSettingList",
    method: "get",
    params: query,
  });
}
// æ–°å¢žå‡æœŸè®¾ç½®
export function addHolidaySettings(data) {
    return request({
        url: "/holidaySettings/add",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/add",
    method: "post",
    data: data,
  });
}
//新增年假规则
export function addAnnualLeaveSetting(data) {
    return request({
        url: "/holidaySettings/addAnnualLeaveSetting",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/addAnnualLeaveSetting",
    method: "post",
    data: data,
  });
}
//新增加班规则
export function addOvertimeSetting(data) {
    return request({
        url: "/holidaySettings/addOvertimeSetting",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/addOvertimeSetting",
    method: "post",
    data: data,
  });
}
//新增工作时间规则
export function addWorkingHoursSetting(data) {
    return request({
        url: "/holidaySettings/addWorkingHoursSetting",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/addWorkingHoursSetting",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹å‡æœŸè®¾ç½®
export function updateHolidaySettings(data) {
    return request({
        url: "/holidaySettings/update",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/update",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹å¹´å‡è§„则
export function updateAnnualLeaveSetting(data) {
    return request({
        url: "/holidaySettings/updateAnnualLeaveSetting",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/updateAnnualLeaveSetting",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹åŠ ç­è§„åˆ™
export function updateOvertimeSetting(data) {
    return request({
        url: "/holidaySettings/updateOvertimeSetting",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/updateOvertimeSetting",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹å·¥ä½œæ—¶é—´è§„则
export function updateWorkingHoursSetting(data) {
    return request({
        url: "/holidaySettings/updateWorkingHoursSetting",
        method: "post",
        data: data,
    });
  return request({
    url: "/holidaySettings/updateWorkingHoursSetting",
    method: "post",
    data: data,
  });
}
// æ‰¹é‡åˆ é™¤å‡æœŸè®¾ç½®
export function delHolidaySettings(query) {
    return request({
        url: "/holidaySettings/delete",
        method: "delete",
        data: query,
    });
  return request({
    url: "/holidaySettings/delete",
    method: "delete",
    data: query,
  });
}
// æ‰¹é‡åˆ é™¤å¹´å‡è§„则
export function delAnnualLeaveSetting(query) {
    return request({
        url: "/holidaySettings/deleteAnnualLeaveSetting",
        method: "delete",
        data: query,
    });
  return request({
    url: "/holidaySettings/deleteAnnualLeaveSetting",
    method: "delete",
    data: query,
  });
}
// æ‰¹é‡åˆ é™¤åŠ ç­è§„åˆ™
export function delOvertimeSetting(query) {
    return request({
        url: "/holidaySettings/deleteOvertimeSetting",
        method: "delete",
        data: query,
    });
  return request({
    url: "/holidaySettings/deleteOvertimeSetting",
    method: "delete",
    data: query,
  });
}
// æ‰¹é‡åˆ é™¤å·¥ä½œæ—¶é—´è§„则
export function delWorkingHoursSetting(query) {
    return request({
        url: "/holidaySettings/deleteWorkingHoursSetting",
        method: "delete",
        data: query,
    });
  return request({
    url: "/holidaySettings/deleteWorkingHoursSetting",
    method: "delete",
    data: query,
  });
}
src/api/collaborativeApproval/enterpriseBook.js
@@ -3,65 +3,65 @@
// æŸ¥è¯¢ä¸ªäººé€šè®¯å½•
// ä¸ªäººé€šè®¯å½•通常是用户收藏或频繁联系的人员
export function getPersonalContacts(page,query) {
    return request({
        url: '/staffContactsPersonal/getList',
        method: 'get',
        params: {
            ...page,
            ...query
        }
    })
  return request({
    url: '/staffContactsPersonal/getList',
    method: 'get',
    params: {
      ...page,
      ...query
    }
  })
}
// æ·»åŠ è”ç³»äººåˆ°ä¸ªäººé€šè®¯å½•
export function addPersonalContact(data) {
    return request({
        url: '/staffContactsPersonal/add',
        method: 'post',
        data: data
    })
  return request({
    url: '/staffContactsPersonal/add',
    method: 'post',
    data: data
  })
}
// ä»Žä¸ªäººé€šè®¯å½•移除联系人
export function removePersonalContact(id) {
    return request({
        url: '/staffContactsPersonal/delete/' + id,
        method: 'delete'
    })
  return request({
    url: '/staffContactsPersonal/delete/' + id,
    method: 'delete'
  })
}
// æŸ¥è¯¢å…¬å…±é€šè®¯å½•
// å…¬å…±é€šè®¯å½•通常是所有员工可见的联系方式
export function getPublicContacts(query) {
    return request({
        url: '/staff/contacts/public/list',
        method: 'get',
        params: query
    })
  return request({
    url: '/staff/contacts/public/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å•位通讯录
// å•位通讯录通常按部门组织的员工联系方式
export function getCompanyContacts(query) {
    return request({
        url: '/staff/contacts/company/list',
        method: 'get',
        params: query
    })
  return request({
    url: '/staff/contacts/company/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢éƒ¨é—¨é€šè®¯å½•树结构
export function getDepartmentTree() {
    return request({
        url: '/staff/contacts/department/tree',
        method: 'get'
    })
  return request({
    url: '/staff/contacts/department/tree',
    method: 'get'
  })
}
// èŽ·å–å‘˜å·¥è¯¦ç»†ä¿¡æ¯
export function getEmployeeDetail(employeeId) {
    return request({
        url: '/staff/staffOnJob/' + employeeId,
        method: 'get'
    })
}
  return request({
    url: '/staff/staffOnJob/' + employeeId,
    method: 'get'
  })
}
src/api/collaborativeApproval/noticeManagement.js
@@ -1,78 +1,78 @@
import request from '@/utils/request'
import request from "@/utils/request";
// æŸ¥è¯¢å…¬å‘Šåˆ—表
export function listNotice(query) {
    return request({
        url: '/collaborativeApproval/notice/page',
        method: 'get',
        params: query
    })
  return request({
    url: "/collaborativeApproval/notice/page",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢å…¬å‘Šè¯¦ç»†
export function getNotice(noticeId) {
    return request({
        url: '/collaborativeApproval/notice/' + noticeId,
        method: 'get'
    })
  return request({
    url: "/collaborativeApproval/notice/" + noticeId,
    method: "get",
  });
}
// æ–°å¢žå…¬å‘Š
export function addNotice(data) {
    return request({
        url: '/collaborativeApproval/notice/add',
        method: 'post',
        data: data
    })
  return request({
    url: "/collaborativeApproval/notice/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹å…¬å‘Š
export function updateNotice(data) {
    return request({
        url: '/collaborativeApproval/notice/update',
        method: 'put',
        data: data
    })
  return request({
    url: "/collaborativeApproval/notice/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å…¬å‘Š
export function delNotice(ids) {
    return request({
        url: '/collaborativeApproval/notice/' + ids,
        method: 'delete',
    })
  return request({
    url: "/collaborativeApproval/notice/" + ids,
    method: "delete",
  });
}
// èŽ·å–å…¬å‘Šæ•°é‡
export function getCount() {
    return request({
        url: '/collaborativeApproval/notice/count',
        method: 'get',
    })
  return request({
    url: "/collaborativeApproval/notice/count",
    method: "get",
  });
}
// æŸ¥è¯¢å…¬å‘Šç±»åž‹åˆ—表
export function listNoticeType() {
    return request({
        url: '/noticeType/list',
        method: 'get'
    })
  return request({
    url: "/noticeType/list",
    method: "get",
  });
}
// æ–°å¢žå…¬å‘Šç±»åž‹
export function addNoticeType(data) {
    return request({
        url: '/noticeType/add',
        method: 'post',
        data: data
    })
  return request({
    url: "/noticeType/add",
    method: "post",
    data: data,
  });
}
// åˆ é™¤å…¬å‘Šç±»åž‹
export function delNoticeType(id) {
    return request({
        url: '/noticeType/del',
        method: 'delete',
        data: { id }
    })
}
  return request({
    url: "/noticeType/del",
    method: "delete",
    data: [id],
  });
}
src/api/collaborativeApproval/rpaManagement.js
@@ -3,7 +3,7 @@
// æŸ¥è¯¢RPA列表
export function listRpa(query) {
  return request({
    url: "/collaborativeApproval/rpa/list",
    url: "/rpaProcessAutomation/getList",
    method: "get",
    params: query,
  });
@@ -20,7 +20,7 @@
// æ–°å¢žRPA
export function addRpa(data) {
  return request({
    url: "/collaborativeApproval/rpa",
    url: "/rpaProcessAutomation/add",
    method: "post",
    data: data,
  });
@@ -29,17 +29,18 @@
// ä¿®æ”¹RPA
export function updateRpa(data) {
  return request({
    url: "/collaborativeApproval/rpa",
    method: "put",
    url: "/rpaProcessAutomation/update",
    method: "post",
    data: data,
  });
}
// åˆ é™¤RPA
export function delRpa(rpaId) {
export function delRpa(query) {
  return request({
    url: "/collaborativeApproval/rpa/" + rpaId,
    url: "/rpaProcessAutomation/delete",
    method: "delete",
    data: query,
  });
}
src/api/collaborativeApproval/shipmentReview.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
// å‘货审批
import request from "@/utils/request";
// èŽ·å–å‘è´§å®¡æ‰¹åˆ—è¡¨
export function getShipmentApprovalList(query) {
    return request({
        url: '/shipmentApproval/listPage',
        method: 'get',
        params: query,
    })
}
// å‘货申请批准
// /shipmentApproval/update
export function approveShipment(query) {
    return request({
        url: '/shipmentApproval/update',
        method: 'post',
        data: query,
    })
}
src/api/customerService/index.js
@@ -40,4 +40,40 @@
    method: 'post',
    data: query,
  })
}
// ä¸´æœŸå”®åŽç®¡ç†-分页查询
export function expiryAfterSalesListPage(query) {
  return request({
    url: '/expiryAfterSales/listPage',
    method: 'get',
    params: query,
  })
}
// ä¸´æœŸå”®åŽç®¡ç†-新增
export function expiryAfterSalesAdd(query) {
  return request({
    url: '/expiryAfterSales/add',
    method: 'post',
    data: query,
  })
}
// ä¸´æœŸå”®åŽç®¡ç†-更新
export function expiryAfterSalesUpdate(query) {
  return request({
    url: '/expiryAfterSales/update',
    method: 'post',
    data: query,
  })
}
// ä¸´æœŸå”®åŽç®¡ç†-删除
export function expiryAfterSalesDelete(query) {
  return request({
    url: '/expiryAfterSales/delete',
    method: 'delete',
    data: query,
  })
}
src/api/equipmentManagement/brand.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
// è®¾å¤‡å“ç‰Œç®¡ç† - æœ¬åœ°å‡æ•°æ® API(使用 localStorage æŒä¹…化)
const STORAGE_KEY = 'EQUIPMENT_BRANDS';
function readStore() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) {
      const parsed = JSON.parse(raw);
      if (Array.isArray(parsed)) return parsed;
    }
  } catch (e) {
    // ignore
  }
  // åˆå§‹åŒ–一些示例数据
  const initial = [
    { id: 1, name: '西门子', country: '德国', description: '工业自动化与电气工程品牌', createdAt: Date.now() - 86400000 * 10 },
    { id: 2, name: '施耐德', country: '法国', description: '能源管理与自动化', createdAt: Date.now() - 86400000 * 7 },
    { id: 3, name: '三菱电机', country: '日本', description: '电气与自动化设备', createdAt: Date.now() - 86400000 * 3 },
  ];
  localStorage.setItem(STORAGE_KEY, JSON.stringify(initial));
  return initial;
}
function writeStore(list) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
}
function nextId(list) {
  const maxId = list.reduce((max, item) => Math.max(max, Number(item.id) || 0), 0);
  return maxId + 1;
}
export function getBrandPage(params = {}) {
  const { current = 1, size = 10, name } = params;
  const list = readStore();
  let filtered = list;
  if (name) {
    const kw = String(name).trim();
    filtered = filtered.filter((b) =>
      (b.name && b.name.includes(kw)) || (b.country && b.country.includes(kw))
    );
  }
  const start = (current - 1) * size;
  const end = start + Number(size);
  const records = filtered.slice(start, end);
  return Promise.resolve({
    code: 200,
    data: {
      total: filtered.length,
      records,
    },
    msg: 'ok',
  });
}
export function getBrandById(id) {
  const list = readStore();
  const item = list.find((i) => String(i.id) === String(id));
  return Promise.resolve({ code: 200, data: item || null, msg: 'ok' });
}
export function addBrand(data) {
  const list = readStore();
  const item = { ...data };
  item.id = nextId(list);
  item.createdAt = Date.now();
  list.unshift(item);
  writeStore(list);
  return Promise.resolve({ code: 200, data: item, msg: '新增成功' });
}
export function editBrand(data) {
  const list = readStore();
  const index = list.findIndex((i) => String(i.id) === String(data.id));
  if (index !== -1) {
    list[index] = { ...list[index], ...data };
    writeStore(list);
    return Promise.resolve({ code: 200, data: list[index], msg: '修改成功' });
  }
  return Promise.resolve({ code: 404, data: null, msg: '未找到该品牌' });
}
export function delBrand(idOrIds) {
  const list = readStore();
  const ids = Array.isArray(idOrIds) ? idOrIds.map(String) : [String(idOrIds)];
  const newList = list.filter((i) => !ids.includes(String(i.id)));
  writeStore(newList);
  return Promise.resolve({ code: 200, data: null, msg: '删除成功' });
}
src/api/equipmentManagement/calibration.js
@@ -24,4 +24,12 @@
    method: "post",
    data: query,
  });
}
// åˆ é™¤è®°å½•
export function ledgerRecordDelete(ids) {
  return request({
    url: "/measuringInstrumentLedgerRecord/delete",
    method: "delete",
    data: ids,
  });
}
src/api/equipmentManagement/defectManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import request from '@/utils/request';
// ç™»è®°ç¼ºé™·
export function registerDefect(data) {
  return request({
    url: '/defect/add',
    method: 'post',
    data
  });
}
// èŽ·å–ç¼ºé™·åˆ—è¡¨
export function getDefectList() {
  return request({
    url: '/defect/page',
    method: 'get'
  });
}
// æ¶ˆé™¤ç¼ºé™·-修改状态
export function eliminateDefect(data) {
  return request({
    url: '/defect/update',
    method: 'post',
    data
  });
}
//删除
export function deleteDefect(id) {
  return request({
    url: '/defect/delete',
    method: 'delete',
    id
  });
}
// èŽ·å–ç¼ºé™·è®¾å¤‡å°è´¦
export function getDefectLedger(deviceLedgerId) {
  return request({
    url: '/defect//find/' + deviceLedgerId,
    method: 'get'
  });
}
src/api/equipmentManagement/deviceInfo.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from "@/utils/request";
// èŽ·å–è®¾å¤‡åŸºæœ¬ä¿¡æ¯
export function getDeviceInfo(params) {
  return request({
    url: "/device/ledger/scanDevice",
    method: "get",
    params,
  });
}
src/api/equipmentManagement/measurementEquipment.js
@@ -32,4 +32,24 @@
    method: "post",
    data: query,
  });
}
// è®¡é‡å™¨å…·å°è´¦-新增
// /measuringInstrumentLedger/add
export function addMeasuringInstrumentLedger(data){
    return request({
        url:"/measuringInstrumentLedger/add",
        method:"post",
        data
    })
}
// è®¡é‡å™¨å…·å°è´¦-编辑
// /measuringInstrumentLedger/update
export function updateMeasuringInstrumentLedger(data){
    return request({
        url:"/measuringInstrumentLedger/update",
        method:"post",
        data
    })
}
src/api/equipmentManagement/spareParts.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
import request from "@/utils/request";
/**
 *  å¤‡ä»¶åˆ†ç±»-树列表
 */
export const getSparePartsTree = (params) => {
  return request({
    url: "/spareParts/getTree",
    method: "get",
    params,
  });
};
/**
 *  å¤‡ä»¶åˆ†ç±»-分页查询列表
 */
export const getSparePartsList = (params) => {
  return request({
    url: "/spareParts/listPage",
    method: "get",
    params,
  });
};
/**
 * @desc æ–°å¢ž
 */
export const addSparePart = (data) => {
  return request({
    url: "/spareParts/add",
    method: "post",
    data,
  });
};
/**
 * @desc ç¼–辑
 */
export const editSparePart = (data) => {
  return request({
    url: "/spareParts/update",
    method: "post",
    data,
  });
};
/**
 * @desc åˆ é™¤æŠ¥ä¿®
 * @param {编号} ids
 * @returns
 */
export const delSparePart = (id) => {
  return request({
    url: '/spareParts/delete/'+id,
    method: "delete",
  });
};
src/api/equipmentManagement/upkeep.js
@@ -70,3 +70,35 @@
    method: "delete",
  });
};
// æ·»åŠ è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡
export const deviceMaintenanceTaskAdd = (params) => {
  return request({
    url: '/deviceMaintenanceTask/add',
    method: "post",
    data: params,
  });
};
// ä¿®æ”¹è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡
export const deviceMaintenanceTaskEdit = (params) => {
  return request({
    url: '/deviceMaintenanceTask/update',
    method: "post",
    data: params,
  });
};
// è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡åˆ—è¡¨
export const deviceMaintenanceTaskList = (params) => {
  return request({
    url: '/deviceMaintenanceTask/listPage',
    method: "get",
    params: params,
  });
};
// è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡åˆ—è¡¨
export const deviceMaintenanceTaskDel = (params) => {
  return request({
    url: '/deviceMaintenanceTask/delete',
    method: "delete",
    data: params,
  });
};
src/api/fileManagement/bookshelf.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
import request from "@/utils/request";
/**
 * ä¹¦æž¶ç®¡ç†ç›¸å…³API接口
 * åŒ…含仓库管理、货架管理、图书管理等功能的接口
 */
/**
 * èŽ·å–ä»“åº“åˆ—è¡¨
 * @description èŽ·å–æ‰€æœ‰ä»“åº“çš„åŸºæœ¬ä¿¡æ¯åˆ—è¡¨
 * @returns {Promise} è¿”回仓库列表数据
 */
export function getWarehouseList() {
  return request({
    url: "/warehouse/tree",
    method: "get",
  });
}
/**
 * æ–°å¢žä»“库
 * @description åˆ›å»ºæ–°çš„仓库记录
 * @param {Object} data ä»“库信息对象,包含仓库名称等字段
 * @returns {Promise} è¿”回新增结果
 */
export function addWarehouse(data) {
  return request({
    url: "/warehouse/add",
    method: "post",
    data,
  });
}
/**
 * æ›´æ–°ä»“库信息
 * @description ä¿®æ”¹çŽ°æœ‰ä»“åº“çš„åŸºæœ¬ä¿¡æ¯
 * @param {Object} data ä»“库信息对象,必须包含仓库ID
 * @returns {Promise} è¿”回更新结果
 */
export function updateWarehouse(data) {
  return request({
    url: "/warehouse/update",
    method: "put",
    data,
  });
}
/**
 * åˆ é™¤ä»“库
 * @description æ ¹æ®ä»“库ID删除指定的仓库记录
 * @param {string|number} id ä»“库ID
 * @returns {Promise} è¿”回删除结果
 */
export function deleteWarehouse(data) {
  return request({
    url: `/warehouse/delete/`,
    method: "delete",
    data,
  });
}
/**
 * èŽ·å–è´§æž¶åˆ—è¡¨
 * @description æ ¹æ®ä»“库ID获取该仓库下的所有货架信息
 * @param {string|number} warehouseId ä»“库ID
 * @returns {Promise} è¿”回货架列表数据
 */
export function getShelfList(warehouseId) {
  return request({
    url: `/shelf/list/${warehouseId}`,
    method: "get",
  });
}
/**
 * æ–°å¢žè´§æž¶
 * @description åœ¨æŒ‡å®šä»“库下创建新的货架记录
 * @param {Object} data è´§æž¶ä¿¡æ¯å¯¹è±¡ï¼ŒåŒ…含货架名称、层数、列数等字段
 * @returns {Promise} è¿”回新增结果
 */
export function addShelf(data) {
  return request({
    url: "/warehouse/goodsShelves/add",
    method: "post",
    data,
  });
}
/**
 * æ›´æ–°è´§æž¶ä¿¡æ¯
 * @description ä¿®æ”¹çŽ°æœ‰è´§æž¶çš„åŸºæœ¬ä¿¡æ¯
 * @param {Object} data è´§æž¶ä¿¡æ¯å¯¹è±¡ï¼Œå¿…须包含货架ID
 * @returns {Promise} è¿”回更新结果
 */
export function updateShelf(data) {
  return request({
    url: "/warehouse/goodsShelves/update",
    method: "put",
    data,
  });
}
/**
 * åˆ é™¤è´§æž¶
 * @description æ ¹æ®è´§æž¶ID删除指定的货架记录
 * @param {string|number} id è´§æž¶ID
 * @returns {Promise} è¿”回删除结果
 */
export function deleteShelf(id) {
  return request({
    url: `/warehouse/goodsShelves/delete/${id}`,
    method: "delete",
  });
}
/**
 * èŽ·å–ä»“åº“ç»“æž„
 * @description èŽ·å–æŒ‡å®šä»“åº“çš„å®Œæ•´ç»“æž„ä¿¡æ¯ï¼ŒåŒ…æ‹¬è´§æž¶ã€å±‚æ•°ã€åˆ—æ•°ç­‰
 * @param {string|number} warehouseId ä»“库ID
 * @returns {Promise} è¿”回仓库的完整结构数据
 */
export function getWarehouseStructure(data) {
  return request({
    url: `/warehouse/goodsShelvesRowcol/list`,
    method: "get",
    params: data,
  });
}
src/api/fileManagement/borrow.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
import request from "@/utils/request";
// æ–‡æ¡£å€Ÿé˜…管理相关接口
// èŽ·å–æ–‡æ¡£åˆ—è¡¨ï¼ˆç”¨äºŽå€Ÿé˜…ä¹¦ç±é€‰æ‹©ï¼‰
export function getDocumentList() {
  return request({
    url: "/documentation/list",
    method: "get",
  });
}
// å€Ÿé˜…分页查询
export function getBorrowList(params) {
  return request({
    url: "/documentationBorrowManagement/listPage",
    method: "get",
    params: params,
  });
}
// æ–°å¢žå€Ÿé˜…
export function addBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/add",
    method: "post",
    data: data,
  });
}
// æ›´æ–°å€Ÿé˜…
export function updateBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å€Ÿé˜…
export function deleteBorrow(ids) {
  return request({
    url: "/documentationBorrowManagement/delete",
    method: "delete",
    data: ids,
  });
}
src/api/fileManagement/document.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
import request from "@/utils/request";
// èŽ·å–åˆ†ç±»æ ‘
export function getCategoryTree() {
  return request({
    url: "/warehouse/documentClassification/getList",
    method: "get",
  });
}
// æ–°å¢žåˆ†ç±»
export function addCategory(data) {
  return request({
    url: "/warehouse/documentClassification/add",
    method: "post",
    data: {
      category: data.category,
      parentId: data.parentId,
    },
  });
}
// ä¿®æ”¹åˆ†ç±»
export function updateCategory(data) {
  return request({
    url: "/warehouse/documentClassification/update",
    method: "put",
    data: {
      id: data.id,
      category: data.category,
    },
  });
}
// åˆ é™¤åˆ†ç±»
export function deleteCategory(ids) {
  return request({
    url: "/warehouse/documentClassification/delete",
    method: "delete",
    data: ids,
  });
}
// èŽ·å–æ–‡æ¡£åˆ—è¡¨ï¼ˆåˆ†é¡µï¼‰
export function getDocumentList(query) {
  return request({
    url: "/documentation/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žæ–‡æ¡£
export function addDocument(data) {
  return request({
    url: "/documentation/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹æ–‡æ¡£
export function updateDocument(data) {
  return request({
    url: "/documentation/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤æ–‡æ¡£
export function deleteDocument(ids) {
  return request({
    url: "/documentation/delete",
    method: "delete",
    data: ids,
  });
}
// èŽ·å–æ–‡æ¡£è¯¦æƒ…
export function getDocumentDetail(id) {
  return request({
    url: "/document/" + id,
    method: "get",
  });
}
// æœç´¢æ–‡æ¡£
export function searchDocument(query) {
  return request({
    url: "/document/search",
    method: "get",
    params: query,
  });
}
// èŽ·å–ä»“åº“ç»“æž„
export function getWarehouseStructure() {
  return request({
    url: "/document/warehouse/structure",
    method: "get",
  });
}
// é™„件管理相关接口
// æ·»åР附件
export function addDocumentationFile(data) {
  return request({
    url: "/documentation/documentationFile/add",
    method: "post",
    data: data,
  });
}
// èŽ·å–é™„ä»¶åˆ—è¡¨
export function getDocumentationFileList(params) {
  return request({
    url: "/documentation/documentationFile/listPage",
    method: "get",
    params: params,
  });
}
// åˆ é™¤é™„ä»¶
export function deleteDocumentationFile(ids) {
  return request({
    url: "/documentation/documentationFile/del",
    method: "delete",
    data: ids,
  });
}
// æ–‡æ¡£å€Ÿé˜…管理相关接口
export function getBorrowList(params) {
  return request({
    url: "/documentationBorrowManagement/listPage",
    method: "get",
    params: params,
  });
}
export function addBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/add",
    method: "post",
    data: data,
  });
}
export function updateBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/update",
    method: "put",
    data: data,
  });
}
export function deleteBorrow(ids) {
  return request({
    url: "/documentationBorrowManagement/delete",
    method: "delete",
    data: ids,
  });
}
// ç»Ÿè®¡ç›¸å…³æŽ¥å£
// èŽ·å–æ€»ä½“ç»Ÿè®¡æ•°æ®
export function getDocumentationOverview() {
  return request({
    url: "/documentation/overview",
    method: "get",
  });
}
// èŽ·å–åˆ†ç±»ç»Ÿè®¡æ•°æ®
export function getDocumentationCategoryStats() {
  return request({
    url: "/documentation/category",
    method: "get",
  });
}
// èŽ·å–çŠ¶æ€ç»Ÿè®¡æ•°æ®
export function getDocumentationStatusStats() {
  return request({
    url: "/documentation/status",
    method: "get",
  });
}
src/api/fileManagement/return.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢å½’还记录
export function getReturnListPage(query) {
  return request({
    url: "/documentationBorrowManagement/listPageReturn",
    method: "get",
    params: query,
  });
}
// å½’还操作
export function returnDocument(data) {
  return request({
    url: "/documentationBorrowManagement/revent",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å½’还记录
export function deleteReturn(ids) {
  return request({
    url: "/documentationBorrowManagement/reventDelete",
    method: "delete",
    data: ids,
  });
}
//根据书籍id查询借阅记录
export function getBorrowListByDocumentationId(id) {
  return request({
    url: "/documentationBorrowManagement/getByDocumentationId/"+id,
    method: "get"
  });
}
// æ›´æ–°å€Ÿé˜…记录
export function updateBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/update",
    method: "put",
    data: data,
  });
}
// å½’还更新
export function reventUpdate(data) {
  return request({
    url: "/documentationBorrowManagement/reventUpdate",
    method: "put",
    data: data,
  });
}
// èŽ·å–æ–‡æ¡£åˆ—è¡¨
export function getDocumentList() {
  return request({
    url: "/documentationBorrowManagement/list",
    method: "get",
  });
}
src/api/fileManagement/statistics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
import request from "@/utils/request";
// èŽ·å–æ¡£æ¡ˆæ€»ä½“ç»Ÿè®¡
export function getDocumentStatistics() {
  return request({
    url: "/fileManagement/statistics/overview",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆåˆ†ç±»ç»Ÿè®¡
export function getCategoryStatistics() {
  return request({
    url: "/fileManagement/statistics/category",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆçŠ¶æ€ç»Ÿè®¡
export function getStatusStatistics() {
  return request({
    url: "/fileManagement/statistics/status",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆå€Ÿé˜…ç»Ÿè®¡
export function getBorrowStatistics() {
  return request({
    url: "/fileManagement/statistics/borrow",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆå¹´åº¦ç»Ÿè®¡
export function getYearStatistics() {
  return request({
    url: "/fileManagement/statistics/year",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆä½ç½®ç»Ÿè®¡
export function getLocationStatistics() {
  return request({
    url: "/fileManagement/statistics/location",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆè¶‹åŠ¿ç»Ÿè®¡
export function getTrendStatistics(params) {
  return request({
    url: "/fileManagement/statistics/trend",
    method: "get",
    params: params,
  });
}
// èŽ·å–æ¡£æ¡ˆå€Ÿé˜…æŽ’è¡Œ
export function getBorrowRanking() {
  return request({
    url: "/fileManagement/statistics/borrowRanking",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆåˆ†ç±»è¯¦æƒ…ç»Ÿè®¡
export function getCategoryDetailStatistics(categoryId) {
  return request({
    url: `/fileManagement/statistics/categoryDetail/${categoryId}`,
    method: "get",
  });
}
src/api/financialManagement/accounting.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// èŽ·å–å›ºå®šèµ„äº§æ±‡æ€»ä¿¡æ¯
export const getAccountingTotal = (params) => {
  return request({
    url: "/accounting/total",
    method: "get",
    params,
  });
};
// èŽ·å–è®¾å¤‡ç±»åž‹åˆ†å¸ƒæ•°æ®ï¼ˆé¥¼å›¾å’ŒæŠ˜çº¿å›¾ï¼‰
export const getDeviceTypeDistribution = (params) => {
  return request({
    url: "/accounting/deviceTypeDistribution",
    method: "get",
    params,
  });
};
// èŽ·å–æŠ˜æ—§è®¡ç®—æ•°æ®ï¼ˆè¡¨æ ¼æ•°æ®ï¼‰
export const getCalculateDepreciation = (params) => {
  return request({
    url: "/accounting/calculateDepreciation",
    method: "get",
    params,
  });
};
src/api/financialManagement/expenseManagement.js
@@ -8,6 +8,13 @@
    params,
  });
};
export const listPageAnalysis = (params) => {
  return request({
    url: "/account/accountExpense/report/analysis",
    method: "get",
    params,
  });
};
// æ–°å¢ž
export function add(data) {
src/api/financialManagement/loanManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
import request from "@/utils/request";
// æŸ¥è¯¢åˆ—表
export const listPage = (params) => {
  return request({
    url: "/borrowInfo/listPage",
    method: "get",
    params,
  });
};
// æ–°å¢ž
export function add(data) {
  return request({
    url: "/borrowInfo/add",
    method: "post",
    data: data,
  });
}
// ç¼–辑
export function update(data) {
  return request({
    url: "/borrowInfo/update",
    method: "post",
    data: data,
  });
}
// åˆ é™¤
export const delAccountLoan = (data) => {
  return request({
    url: "/borrowInfo/delete",
    method: "delete",
    data,
  });
};
src/api/inspectionManagement/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
// å·¡æ£€ç®¡ç†
import request from '@/utils/request'
// å·¡æ£€ä»»åŠ¡è¡¨è¡¨æŸ¥è¯¢
export function inspectionTaskList(query) {
    return request({
        url: '/inspectionTask/list',
        method: 'get',
        params: query
    })
}
// å·¡æ£€ä»»åŠ¡è¡¨æ–°å¢žä¿®æ”¹
export function addOrEditInspectionTask(query) {
    return request({
        url: '/inspectionTask/addOrEditInspectionTask',
        method: 'post',
        data: query
    })
}
// å·¡æ£€ä»»åŠ¡è¡¨åˆ é™¤
export function delInspectionTask(query) {
    return request({
        url: '/inspectionTask/delInspectionTask',
        method: 'delete',
        data: query
    })
}
// å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨åˆ é™¤
export function delTimingTask(query) {
    return request({
        url: '/timingTask/delTimingTask',
        method: 'delete',
        data: query
    })
}
// /inspectionTask/addOrEditInspectionTask
// å·¡æ£€ä¸Šä¼ 
export function uploadInspectionTask(query) {
    return request({
        url: '/inspectionTask/addOrEditInspectionTask',
        method: 'post',
        data: query
    })
}
// å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨æŸ¥è¯¢
export function timingTaskList(query) {
    return request({
        url: '/timingTask/list',
        method: 'get',
        params: query
    })
}
// å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨æ–°å¢žä¿®æ”¹
export function addOrEditTimingTask(query) {
    return request({
        url: '/timingTask/addOrEditTimingTask',
        method: 'post',
        data: query
    })
}
src/api/inspectionUpload/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
// å·¡æ£€ä¸Šä¼ 
import request from '@/utils/request'
// äºŒç»´ç ç®¡ç†è¡¨æŸ¥è¯¢
export function qrCodeList(query) {
    return request({
        url: '/qrCode/list',
        method: 'get',
        params: query
    })
}
// äºŒç»´ç æ‰«ç è®°å½•表查询
export function qrCodeScanRecordList(query) {
    return request({
        url: '/qrCodeScanRecord/list',
        method: 'get',
        params: query
    })
}
// äºŒç»´ç ç®¡ç†è¡¨æ–°å¢žä¿®æ”¹
export function addOrEditQrCode(query) {
    return request({
        url: '/qrCode/addOrEditQrCode',
        method: 'post',
        data: query
    })
}
// äºŒç»´ç æ‰«ç è®°å½•表新增修改
export function addOrEditQrCodeRecord(query) {
    return request({
        url: '/qrCodeScanRecord/addOrEditQrCodeRecord',
        method: 'post',
        data: query
    })
}
// äºŒç»´ç æ‰«ç è®°å½•表新增修改
export function delQrCode(query) {
    return request({
        url: '/qrCode/delQrCode',
        method: 'delete',
        data: query
    })
}
src/api/inventoryManagement/stockIn.js
@@ -18,6 +18,15 @@
    });
};
// æŸ¥è¯¢ç”Ÿäº§å…¥åº“信息列表
export const getStockInPageByProductProduction = (params) => {
    return request({
        url: "/stockin/listPageByProductProduction",
        method: "get",
        params,
    });
};
// å‡ºåº“台账-查询自定义入库信息列表
export const getStockInPageByCustom = (params) => {
    return request({
@@ -142,5 +151,11 @@
}
//
//查询库存图表数据
export function getStockInChartData() {
    return request({
        url: '/stockin/listReport',
        method: 'get'
    })
}
src/api/inventoryManagement/stockInRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from "@/utils/request";
// æŸ¥è¯¢å…¥åº“信息列表
export const getStockInRecordListPage = (params) => {
    return request({
        url: "/stockInRecord/listPage",
        method: "get",
        params,
    });
};
export const updateStockInRecord = (id, data) => {
    return request({
        url: "/stockInRecord/" + id,
        method: "put",
        data: data,
    });
};
export const batchDeleteStockInRecords = (ids) => {
    return request({
        url: "/stockInRecord",
        method: "delete",
        data: ids,
    });
};
src/api/inventoryManagement/stockInventory.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
import request from "@/utils/request.js";
// åˆ†é¡µæŸ¥è¯¢åº“存记录列表
export const getStockInventoryListPage = (params) => {
    return request({
        url: "/stockInventory/pagestockInventory",
        method: "get",
        params,
    });
};
// åˆ›å»ºåº“存记录
export const createStockInventory = (params) => {
    return request({
        url: "/stockInventory/addstockInventory",
        method: "post",
        data: params,
    });
};
// å‡å°‘库存记录
export const subtractStockInventory = (params) => {
    return request({
        url: "/stockInventory/subtractStockInventory",
        method: "post",
        data: params,
    });
};
export const exportStockInventory = (params) => {
    return request({
        url: "/stockInventory/exportStockInventory",
        method: "post",
        data: params,
    });
};
src/api/inventoryManagement/stockManage.js
@@ -17,7 +17,14 @@
        params,
    });
};
// æŸ¥è¯¢æˆå“åº“存信息列表
export const getStockManageProduction = (params) => {
    return request({
        url: "/stockin/listPageProductionStock",
        method: "get",
        params,
    });
};
// æŸ¥è¯¢è‡ªå®šä¹‰å…¥åº“库存信息列表
export const getStockManagePageByCustom = (params) => {
    return request({
src/api/inventoryManagement/stockOut.js
@@ -3,35 +3,17 @@
// å‡ºåº“台账-采购出库查询出库列表
export const getStockOutPage = (params) => {
    return request({
        url: "/stockmanagement/listPage",
        url: "/stockOutRecord/listPage",
        method: "get",
        params,
    });
};
//新增出库信息
export const addStockOut = (data) => {
    return request({
        url: '/stockout/add',
        method: 'post',
        data: data
    })
}
//修改出库信息
export const updateStockOut = (data) => {
    return request({
        url: "/stockout/update",
        method: "put",
        data,
    });
}
//删除出库信息
export const delStockOut = (ids) => {
    return request({
        url: '/stockmanagement/del',
        method: 'post',
        data: ids
    })
        url: "/stockOutRecord",
        method: "delete",
        data: ids,
    });
}
src/api/inventoryManagement/stockUninventory.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from "@/utils/request.js";
// åˆ†é¡µæŸ¥è¯¢åº“存记录列表
export const getStockUninventoryListPage = (params) => {
    return request({
        url: "/stockUninventory/pagestockUninventory",
        method: "get",
        params,
    });
};
// åˆ›å»ºåº“存记录
export const createStockUnInventory = (params) => {
    return request({
        url: "/stockUninventory/addstockUninventory",
        method: "post",
        data: params,
    });
};
// å‡å°‘库存记录
export const subtractStockUnInventory = (params) => {
    return request({
        url: "/stockUninventory/subtractstockUninventory",
        method: "post",
        data: params,
    });
};
src/api/oaSystem/projectManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,154 @@
import request from "@/utils/request";
import { parseStrEmpty } from "@/utils/ruoyi";
// æŸ¥è¯¢é¡¹ç›®åˆ—表
export function listProject(query) {
  return request({
    url: "/oA/project/listPage",
    method: "get",
    params: query
  });
}
// æŸ¥è¯¢é¡¹ç›®åˆ—表详细
export function getProject(query) {
  return request({
    url: "oA/project/getList",
    method: "get"
  });
}
// æ–°å¢žé¡¹ç›®
export function addProject(data) {
  return request({
    url: "/oA/project/add",
    method: "post",
    data: data
  });
}
// ä¿®æ”¹é¡¹ç›®
export function updateProject(data) {
  return request({
    url: "/oA/project/update",
    method: "post",
    data: data
  });
}
// åˆ é™¤é¡¹ç›®
export function delProject(projectId) {
  return request({
    url: "/oA/project/delete/" + projectId,
    method: "delete"
  });
}
// å¯¼å‡ºé¡¹ç›®
export function exportProject(data) {
  return request({
    url: "/oA/project/export",
    method: "post",
    data: data
  });
}
// // æ‰¹é‡åˆ é™¤é¡¹ç›®
// export function delProjectBatch(projectIds) {
//   return request({
//     url: "/oaSystem/project/batch",
//     method: "delete",
//     data: projectIds
//   });
// }
// æ ¹æ®é¡¹ç›®é˜¶æ®µid查询项目阶段任务列表
export function listProjectTask(phaseId) {
  return request({
    url: "/oA/projectPhaseTask/listByPhaseId/"+ phaseId,
    method: "get"
  });
}
// // æŸ¥è¯¢é¡¹ç›®ä»»åŠ¡è¯¦ç»†
// export function getProjectTask(taskId) {
//   return request({
//     url: "/oaSystem/project/task/" + taskId,
//     method: "get"
//   });
// }
// æ–°å¢žé¡¹ç›®é˜¶æ®µä»»åŠ¡
export function addProjectTask(data) {
  return request({
    url: "/oA/projectPhaseTask/add",
    method: "post",
    data: data
  });
}
// ä¿®æ”¹é¡¹ç›®é˜¶æ®µä»»åŠ¡
export function updateProjectTask(data) {
  return request({
    url: "/oA/projectPhaseTask/update",
    method: "post",
    data: data
  });
}
// åˆ é™¤é¡¹ç›®é˜¶æ®µä»»åŠ¡
export function delProjectTask(taskId) {
  return request({
    url: "/oA/projectPhaseTask/delete/" + taskId,
    method: "delete"
  });
}
// é¡¹ç›®id查询项目阶段列表
export function listProjectPhase(projectId) {
  return request({
    url: "/oA/projectPhase/listByProjectId/" + projectId,
    method: "get"
  });
}
// æ–°å¢žé¡¹ç›®é˜¶æ®µ
export function addProjectPhase(data) {
  return request({
    url: "/oA/projectPhase/add",
    method: "post",
    data: data
  });
}
// ä¿®æ”¹é¡¹ç›®é˜¶æ®µ
export function updateProjectPhase(data) {
  return request({
    url: "/oA/projectPhase/update",
    method: "post",
    data: data
  });
}
// åˆ é™¤é¡¹ç›®é˜¶æ®µ
export function delProjectPhase(phaseId) {
  return request({
    url: "/oA/projectPhase/delete/" + phaseId,
    method: "delete"
  })
}
//
// // æŸ¥è¯¢é¡¹ç›®é‡Œç¨‹ç¢‘列表
// export function listProjectMilestone(query) {
//   return request({
//     url: "/oaSystem/project/milestone/list",
//     method: "get",
//     params: query
//   });
// }
// // é¡¹ç›®ç»Ÿè®¡ä¿¡æ¯
// export function getProjectStatistics() {
//   return request({
//     url: "/oaSystem/project/statistics",
//     method: "get"
//   });
// }
src/api/personnelManagement/employeeRecord.js
@@ -15,4 +15,13 @@
        method: 'get',
        params: query,
    })
}
// å¯¼å‡ºåˆåŒå‰¯æœ¬
export function staffOnJobExportCopy(data) {
    return request({
        url: '/staff/staffOnJob/exportCopy',
        method: 'post',
        data: data,
    })
}
src/api/personnelManagement/onboarding.js
ÎļþÒÑɾ³ý
src/api/personnelManagement/scheduling.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
// æŽ’班管理
import request from "@/utils/request";
export function save(data) {
    return request({
        url: "/staff/staffScheduling/save",
        method: "post",
        data: data,
    });
}
export function del(id) {
    return request({
        url: "/staff/staffScheduling/del/"+id,
        method: "delete",
    });
}
export function delByIds(data) {
    return request({
        url: "/staff/staffScheduling/save",
        method: "post",
        data: data,
    });
}
export function listPage(data){
    return request({
        url: "/staff/staffScheduling/listPage",
        method: "post",
        data: data
    })
}
src/api/personnelManagement/selfService.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
// è–ªé…¬ç®¡ç†
import request from "@/utils/request";
// æŸ¥è¯¢è€ƒå‹¤åˆ—表
export function personalAttendanceRecordsListPage(query) {
  return request({
    url: "/staff/personalAttendanceRecords/listPage",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢å‡æœŸç”³è¯·åˆ—表
export function holidayApplicationListPage(query) {
  return request({
    url: "/staff/holidayApplication/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢ž
export function personalAttendanceRecordsAdd(query) {
  return request({
    url: "/staff/personalAttendanceRecords/add",
    method: "post",
    data: query,
  });
}
// æ–°å¢žå‡æœŸç”³è¯·
export function holidayApplicationAdd(query) {
  return request({
    url: "/staff/holidayApplication/add",
    method: "post",
    data: query,
  });
}
// ä¿®æ”¹
export function personalAttendanceRecordsUpdate(query) {
  return request({
    url: "/staff/personalAttendanceRecords/update",
    method: "put",
    data: query,
  });
}
// ä¿®æ”¹å‡æœŸç”³è¯·
export function holidayApplicationUpdate(query) {
  return request({
    url: "/staff/holidayApplication/update",
    method: "post",
    data: query,
  });
}
// åˆ é™¤
export function personalAttendanceRecordsDelete(id) {
  return request({
    url: "/staff/personalAttendanceRecords/delete/"+id,
    method: "delete",
  });
}
// åˆ é™¤å‡æœŸç”³è¯·
export function holidayApplicationDelete(id) {
  return request({
    url: "/staff/holidayApplication/delete/"+id,
    method: "delete",
  });
}
// export function del(id) {
//     return request({
//         url: "/staff/staffScheduling/del/"+id,
//         method: "delete",
//     });
// }
src/api/personnelManagement/staffAnalytics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from "@/utils/request.js";
// ç¦»èŒåŽŸå› åˆ†æž
export function findStaffLeaveReasonAnalysis() {
    return request({
        url: "/staff/analytics/reason",
        method: "get"
    });
}
// 12个月员工流动流失率分析
export function findStaffAnalysisMonthlyTurnoverRateFor12Months() {
    return request({
        url: "/staff/analytics/monthly_turnover_rate",
        method: "get"
    });
}
export function findStaffAnalysisTotalStatistic() {
    return request({
        url: "/staff/analytics/total_statistic",
        method: "get"
    });
}
src/api/personnelManagement/staffContract.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from "@/utils/request.js";
export function findStaffContractListPage(query) {
    return request({
        url: "/staff/staffContract/listPage",
        method: "get",
        params: query,
    });
}
src/api/personnelManagement/staffLeave.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
import request from "@/utils/request.js";
export function findStaffLeaveListPage(query) {
    return request({
        url: "/staff/staffLeave/listPage",
        method: "get",
        params: query,
    });
}
export function createStaffLeave(data) {
    return request({
        url: "/staff/staffLeave",
        method: "post",
        data: data,
    });
}
export function updateStaffLeave(id, data) {
    return request({
        url: "/staff/staffLeave/" + id,
        method: "put",
        data: data,
    });
}
export function batchDeleteStaffLeaves(data) {
    return request({
        url: "/staff/staffLeave/del",
        method: "delete",
        data: data,
    });
}
src/api/procurementManagement/advancedPriceManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// é«˜çº§é‡‡è´­ä»·æ ¼ç®¡ç†API接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢ä»·æ ¼åˆ—表
export function listPage(query) {
  return request({
    url: "/procurementPriceManagement/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žä»·æ ¼
export function add(data) {
  return request({
    url: "/procurementPriceManagement/add",
    method: "post",
    data: data,
  });
}
// æ›´æ–°ä»·æ ¼
export function update(data) {
  return request({
    url: "/procurementPriceManagement/update",
    method: "post",
    data: data,
  });
}
// åˆ é™¤ä»·æ ¼
export function del(data) {
  return request({
    url: `/procurementPriceManagement/del`,
    method: "delete",
    data
  });
}
src/api/procurementManagement/arrivalManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
// é”€å”®å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
    return request({
        url: "/inboundManagement/listPage",
        method: "get",
        params: query,
    });
}
export function listPageCopy(query) {
    return request({
        url: "/inboundManagement/listPage",
        method: "get",
        params: query,
    });
}
// æ–°å¢ž
export function add(data) {
    return request({
        url: "/inboundManagement/add",
        method: "post",
        data
    });
}
// ä¿®æ”¹
export function update(data) {
    return request({
        url: "/inboundManagement/update",
        method: "post",
        data
    });
}
// åˆ é™¤é”€å”®å°è´¦
export function del(data) {
    return request({
        url: "/inboundManagement/del",
        method: "delete",
        data
    });
}
src/api/procurementManagement/procurementInvoiceLedger.js
@@ -61,7 +61,7 @@
// æŸ¥è¯¢åˆ—表
export function invoiceListPage(query) {
  return request({
    url: "/purchase/registration/listPage",
    url: "/sales/product/listPagePurchaseLedger",
    method: "get",
    params: query,
  });
@@ -83,11 +83,18 @@
  });
}
export function getProductRecordById(params) {
// export function getProductRecordById(params) {
//   return request({
//     url: "/purchase/registration/getProductRecordById",
//     method: "get",
//     params: params,
//   });
// }
export function getProductRecordById(data) {
  return request({
    url: "/purchase/registration/getProductRecordById",
    method: "get",
    params: params,
     method: "post",
    data: data,
  });
}
src/api/procurementManagement/procurementLedger.js
@@ -80,16 +80,6 @@
    });
}
// ä¿å­˜é‡‡è´­æ¨¡æ¿
// /purchase/ledger/addPurchaseTemplate
export function addPurchaseTemplate(data) {
    return request({
        url: "/purchase/ledger/addPurchaseTemplate",
        method: "post",
        data: data,
    });
}
// æŸ¥è¯¢é‡‡è´­æ¨¡æ¿
// /purchase/ledger/getPurchaseTemplateList
export function getPurchaseTemplateList(query) {
@@ -99,3 +89,13 @@
        params: query,
    });
}
// ä¿å­˜é‡‡è´­æ¨¡æ¿
// /purchase/ledger/addPurchaseTemplate
export function addPurchaseTemplate(data) {
    return request({
        url: "/purchase/ledger/addPurchaseTemplate",
        method: "post",
        data: data,
    });
}
src/api/procurementManagement/procurementPlan.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢é‡‡è´­è®¡åˆ’列表
export function listPage(query) {
  return request({
    url: "/procurementPlan/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žé‡‡è´­è®¡åˆ’
export function add(data) {
  return request({
    url: "/procurementPlan/add",
    method: "post",
    data,
  });
}
// ä¿®æ”¹é‡‡è´­è®¡åˆ’
export function update(data) {
  return request({
    url: "/procurementPlan/update",
    method: "post",
    data,
  });
}
// åˆ é™¤é‡‡è´­è®¡åˆ’
export function del(data) {
  return request({
    url: "/procurementPlan/del",
    method: "delete",
    data,
  });
}
// åˆ é™¤é‡‡è´­è®¡åˆ’
export function listPageCopy(query) {
  return request({
    url: "/stockin/listPageCopy",
    method: "get",
    params: query,
  });
}
src/api/procurementManagement/procurementReport.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// é‡‡è´­æŠ¥è¡¨é¡µé¢æŽ¥å£
import request from "@/utils/request";
// é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨åˆ†é¡µæŸ¥è¯¢
export function procurementBusinessSummaryListPage(query) {
  return request({
    url: "/procurementBusinessSummary/listPage",
    method: "get",
    params: query,
  });
}
src/api/procurementManagement/returnManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
// é”€å”®å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
    return request({
        url: "/returnManagement/listPage",
        method: "get",
        params: query,
    });
}
// æ–°å¢ž
export function add(data) {
    return request({
        url: "/returnManagement/add",
        method: "post",
        data
    });
}
// ä¿®æ”¹
export function update(data) {
    return request({
        url: "/returnManagement/update",
        method: "post",
        data
    });
}
// åˆ é™¤é”€å”®å°è´¦
export function del(data) {
    return request({
        url: "/returnManagement/del",
        method: "delete",
        data
    });
}
src/api/procurementManagement/transferManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function getPurchaseOrders(query) {
  return request({
    url: "/purchase/ledger/listPage",
    method: "get",
    params: query,
  });
}
//
export function confirmReceipt(query) {
    return request({
      url: "",
      method: "post",
      data: query,
    });
}
// å¢žæ·»é‡‡è´­å¼‚常记录
export function addPurchaseException(query) {
    return request({
      url: "/procurementExceptionRecord/add",
      method: "post",
      data: query,
    });
}
// ä¿®æ”¹é‡‡è´­å¼‚常记录
export function updatePurchaseException(query) {
    return request({
        url: "/procurementExceptionRecord/update",
        method: "post",
        data: query,
    });
}
src/api/productionManagement/processRoute.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
// å·¥è‰ºè·¯çº¿é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/processRoute/page",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/processRoute",
    method: "post",
    data: data,
  });
}
export function del(ids) {
  return request({
    url: '/processRoute/' + ids,
    method: 'delete',
  })
}
export function update(data) {
  return request({
    url: '/processRoute',
    method: 'put',
    data: data,
  })
}
// èŽ·å–è¯¦æƒ…
export function getById(id) {
  return request({
    url: `/processRoute/${id}`,
    method: 'get',
  })
}
src/api/productionManagement/processRouteItem.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// å·¥è‰ºè·¯çº¿é¡¹ç›®é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ—表查询
export function findProcessRouteItemList(query) {
  return request({
    url: "/processRouteItem/list",
    method: "get",
    params: query,
  });
}
export function addOrUpdateProcessRouteItem(data) {
  return request({
    url: "/processRouteItem",
    method: "post",
    data: data,
  });
}
// æŽ’序接口
export function sortProcessRouteItem(data) {
  return request({
    url: "/processRouteItem/sort",
    method: "post",
    data: data,
  });
}
// æ‰¹é‡åˆ é™¤æŽ¥å£
export function batchDeleteProcessRouteItem(ids) {
  // å°†id数组转换为逗号分隔的字符串,拼接到URL后面
  const idsStr = Array.isArray(ids) ? ids.join(",") : ids;
  return request({
    url: `/processRouteItem/batchDelete/${idsStr}`,
    method: "delete",
  });
}
src/api/productionManagement/productBom.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
// äº§å“BOM页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/productBom/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢ž
export function add(data) {
  return request({
    url: "/productBom/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹
export function update(data) {
  return request({
    url: "/productBom/update",
    method: "put",
    data: data,
  });
}
// æ‰¹é‡åˆ é™¤
export function batchDelete(ids) {
  return request({
    url: "/productBom/batchDelete",
    method: "delete",
    data: ids,
  });
}
// æ ¹æ®äº§å“åž‹å·ID查询BOM
export function getByModel(productModelId) {
  return request({
    url: "/productBom/getByModel",
    method: "get",
    params: { productModelId },
  });
}
// å¯¼å‡ºBOM
export function exportBom(bomId) {
  return request({
    url: "/productBom/exportBom",
    method: "post",
    params: { bomId },
    responseType: "blob",
  });
}
src/api/productionManagement/productProcessRoute.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
// å·¥è‰ºè·¯çº¿é¡¹ç›®é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ—表查询
export function findProductProcessRouteItemList(query) {
  return request({
    url: "/productProcessRoute/list",
    method: "get",
    params: query,
  });
}
export function addOrUpdateProductProcessRouteItem(data) {
  return request({
    url: "/productProcessRoute/updateRouteItem",
    method: "post",
    data: data,
  });
}
// ç”Ÿäº§è®¢å•下:新增工艺路线项目
export function addRouteItem(data) {
  return request({
    url: "/productProcessRoute/addRouteItem",
    method: "post",
    data,
  });
}
// èŽ·å–ç”Ÿäº§è®¢å•å…³è”çš„å·¥è‰ºè·¯çº¿ä¸»ä¿¡æ¯
export function listMain(orderId) {
  return request({
    url: "/productProcessRoute/listMain",
    method: "get",
    params: { orderId },
  });
}
// åˆ é™¤å·¥è‰ºè·¯çº¿é¡¹ç›®ï¼ˆè·¯ç”±åŽæ‹¼æŽ¥ id)
export function deleteRouteItem(id) {
  return request({
    url: `/productProcessRoute/deleteRouteItem/${id}`,
    method: "delete",
  });
}
// ç”Ÿäº§è®¢å•下:排序工艺路线项目
export function sortRouteItem(data) {
  return request({
    url: "/productProcessRoute/sortRouteItem",
    method: "post",
    data,
  });
}
src/api/productionManagement/productStructure.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
// äº§å“ç»“构页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function queryList(id) {
  return request({
    url: "/productStructure/listBybomId/" + id,
    method: "get",
  });
}
export function add(data) {
  return request({
    url: "/productStructure",
    method: "post",
    data: data,
  });
}
src/api/productionManagement/productionOrder.js
@@ -4,11 +4,74 @@
// åˆ†é¡µæŸ¥è¯¢
export function schedulingListPage(query) {
  return request({
    url: "/productionOrder/listPage",
    url: "/salesLedger/scheduling/listPage",
    method: "get",
    params: query,
  });
}
export function productOrderListPage(query) {
  return request({
    url: "/productOrder/page",
    method: "get",
    params: query,
  });
}
// ç”Ÿäº§è®¢å•-按产品型号查询可用工艺路线列表
export function listProcessRoute(query) {
  return request({
    url: "/productOrder/listProcessRoute",
    method: "get",
    params: query,
  });
}
// ç”Ÿäº§è®¢å•-绑定工艺路线
export function bindingRoute(data) {
  return request({
    url: "/productOrder/bindingRoute",
    method: "post",
    data,
  });
}
// ç”Ÿäº§è®¢å•-查询产品结构列表
export function listProcessBom(query) {
  return request({
    url: "/productOrder/listProcessBom",
    method: "get",
    params: query,
  });
}
// èŽ·å–ç‚’æœºæ­£åœ¨å·¥ä½œé‡æ•°æ®
export function schedulingList(query) {
  return request({
    url: "/salesLedger/scheduling/list",
    method: "get",
    params: query,
  });
}
// ä¿å­˜ç‚’机设置
export function addSpeculatTrading(data) {
  return request({
    url: "/salesLedger/scheduling/addSpeculatTrading",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹ç‚’机设置
export function updateSpeculatTrading(data) {
  return request({
    url: "/salesLedger/scheduling/updateSpeculatTrading",
    method: "post",
    data: data,
  });
}
// ç”Ÿäº§æ´¾å·¥
export function productionDispatch(query) {
  return request({
@@ -17,28 +80,37 @@
    data: query,
  });
}
// è‡ªåŠ¨æ´¾å·¥
export function productionDispatchList(query) {
  return request({
    url: "/salesLedger/scheduling/productionDispatchList",
    method: "post",
    data: query,
  });
}
// æ–°å¢žç”Ÿäº§è®¢å•
export function addProductionOrder(query) {
// æŸ¥è¯¢æŸè€—率
export function getLossRate() {
  return request({
    url: "/productionOrder/addProductionOrder",
    method: "post",
    data: query,
    url: "/salesLedger/scheduling/loss",
    method: "get",
  });
}
// ä¿®æ”¹ç”Ÿäº§è®¢å•
export function updateProductionOrder(query) {
// æ–°å¢žæŸè€—率
export function addLossRate(data) {
  return request({
    url: "/productionOrder/updateProductionOrder",
    url: "/salesLedger/scheduling/addLoss",
    method: "post",
    data: query,
    data: data,
  });
}
// åˆ é™¤ç”Ÿäº§è®¢å•
export function deleteProductionOrder(query) {
// ä¿®æ”¹æŸè€—率
export function updateLossRate(data) {
  return request({
    url: "/productionOrder/deleteProductionOrder",
    method: "delete",
    data: query,
    url: "/salesLedger/scheduling/updateLoss",
    method: "post",
    data: data,
  });
}
src/api/productionManagement/productionProcess.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
// å·¥åºé¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/productProcess/listPage",
    method: "get",
    params: query,
  });
}
export function processList(query) {
  return request({
    url: "/productProcess/list",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/productProcess",
    method: "post",
    data: data,
  });
}
export function del(data) {
  return request({
    url: '/productProcess/batchDelete',
    method: 'delete',
    data: data,
  })
}
export function update(data) {
  return request({
    url: '/productProcess/update',
    method: 'put',
    data: data,
  })
}
// å·¥åºæŸ¥è¯¢
export function list() {
    return request({
        url: "/productProcess/list",
        method: "get",
    });
}
// å¯¼å…¥æ•°æ®
export function importData(data) {
  return request({
    url: "/productProcess/importData",
    method: "post",
    data: data,
  });
}
// ä¸‹è½½æ¨¡æ¿
export function downloadTemplate() {
  return request({
    url: "/productProcess/downloadTemplate",
    method: "post",
    responseType: "blob",
  });
}
src/api/productionManagement/productionProductInput.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§æŠ•入页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductInputListPage(query) {
    return request({
        url: "/productionProductInput/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionProductMain.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§æŠ¥å·¥é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductMainListPage(query) {
    return request({
        url: "/productionProductMain/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionProductOutput.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§äº§å‡ºé¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductOutputListPage(query) {
    return request({
        url: "/productionProductOutput/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionReporting.js
@@ -32,4 +32,12 @@
    method: "post",
    data: query,
  });
}
}
// ç”Ÿäº§æŠ¥å·¥-删除
export function productionReportDelete(query) {
  return request({
    url: "/productionProductMain/delete",
    method: "delete",
    data: query,
  });
}
src/api/productionManagement/workOrder.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import request from "@/utils/request";
export function productWorkOrderPage(query) {
  return request({
    url: "/productWorkOrder/page",
    method: "get",
    params: query,
  });
}
export function updateProductWorkOrder(data) {
  return request({
    url: "/productWorkOrder/updateProductWorkOrder",
    method: "post",
    data: data,
  });
}
export function addProductMain(data) {
  return request({
    url: "/productionProductMain/addProductMain",
    method: "post",
    data: data,
  });
}
src/api/publicApi/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
// æ–‡æ¡£ç®¡ç†
import request from '@/utils/request'
// /system/user/listAll
// æŸ¥è¯¢æ‰€æœ‰ç”¨æˆ·åˆ—表
export function userListAll() {
  return request({
    url: '/system/user/listAll',
    method: 'get'
  })
}
// /equipmentManagement/equipmentList
// æŸ¥è¯¢è®¾å¤‡åˆ—表
export function getEquipmentList(query) {
  return request({
    url: '/equipmentManagement/equipmentList',
    method: 'get',
    params: query
  })
}
// /coalInfo/coalInfoList
// æŸ¥è¯¢ç…¤ç§åˆ—表
export function getCoalInfoList(query) {
    return request({
        url: '/coalInfo/coalInfoList',
        method: 'get',
        params: query
    })
}
// /coalField/coalFieldList
// æŸ¥è¯¢ç…¤è´¨å­—段列表
export function getCoalFieldList(query) {
    return request({
        url: '/coalField/coalFieldList',
        method: 'get',
        params: query
    })
}
src/api/qualityManagement/metricMaintenance.js
@@ -1,45 +1,110 @@
import request from '@/utils/request'
import request from "@/utils/request";
// æŸ¥è¯¢æŒ‡æ ‡åˆ—表
export function qualityTestStandardListPage(query) {
    return request({
        url: '/quality/qualityTestStandard/listPage',
        method: 'get',
        params: query,
    })
  return request({
    url: "/qualityTestStandard/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žæŒ‡æ ‡åˆ—表
export function qualityTestStandardAdd(query) {
    return request({
        url: '/quality/qualityTestStandard/add',
        method: 'post',
        data: query,
    })
  return request({
    url: "/qualityTestStandard/add",
    method: "post",
    data: query,
  });
}
// ä¿®æ”¹æŒ‡æ ‡åˆ—表
export function qualityTestStandardUpdate(query) {
    return request({
        url: '/quality/qualityTestStandard/update',
        method: 'post',
        data: query,
    })
  return request({
    url: "/qualityTestStandard/update",
    method: "post",
    data: query,
  });
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityTestStandardDel(query) {
    return request({
        url: '/quality/qualityTestStandard/del',
        method: 'delete',
        data: query,
    })
  return request({
    url: "/qualityTestStandard/del",
    method: "delete",
    data: query,
  });
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityInspectDetailByProductId(productId) {
    return request({
        url: '/quality/qualityTestStandard/product/' + productId,
        method: 'get',
    })
}
export function qualityInspectDetailByProductId(params) {
  return request({
    url: "/qualityTestStandard/getQualityTestStandardByProductId",
    method: "get",
    params: params,
  });
}
// å¤åˆ¶æ ‡å‡†å‚æ•°
export function qualityTestStandardCopyParam(id) {
  return request({
    url: "/qualityTestStandard/copyParam",
    method: "post",
    data: { id },
  });
}
// æ‰¹é‡å®¡æ ¸ï¼ˆçŠ¶æ€ï¼š1=通过/批准,2=撤销)
// ä¼ å‚:[{ id, state }]
export function qualityTestStandardAudit(data) {
  return request({
    url: "/qualityTestStandard/qualityTestStandardAudit",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:列表(不分页)
export function qualityTestStandardParamList(query) {
  return request({
    url: "/qualityTestStandardParam/list",
    method: "get",
    params: query,
  });
}
// æ ‡å‡†å‚数:新增
export function qualityTestStandardParamAdd(data) {
  return request({
    url: "/qualityTestStandardParam/add",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:修改
export function qualityTestStandardParamUpdate(data) {
  return request({
    url: "/qualityTestStandardParam/update",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:删除(传 id æ•°ç»„)
export function qualityTestStandardParamDel(ids) {
  return request({
    url: "/qualityTestStandardParam/del",
    method: "delete",
    data: ids,
  });
}
// æ ¹æ®æ ‡å‡†ID获取标准参数
export function getQualityTestStandardParamByTestStandardId(testStandardId) {
  return request({
    url: "/qualityTestStandard/getQualityTestStandardParamByTestStandardId",
    method: "get",
    params: { testStandardId },
  });
}
src/api/qualityManagement/nearExpiryReturn.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
import request from '@/utils/request'
// æŸ¥è¯¢ä¸´æœŸé€€å›žå°è´¦åˆ—表
export function nearExpiryReturnListPage(query) {
    return request({
        url: '/quality/nearExpiryReturn/listPage',
        method: 'get',
        params: query,
    })
}
// æ–°å¢žä¸´æœŸé€€å›žå°è´¦
export function nearExpiryReturnAdd(data) {
    return request({
        url: '/quality/nearExpiryReturn/add',
        method: 'post',
        data: data,
    })
}
// ä¿®æ”¹ä¸´æœŸé€€å›žå°è´¦
export function nearExpiryReturnUpdate(data) {
    return request({
        url: '/quality/nearExpiryReturn/update',
        method: 'post',
        data: data,
    })
}
// åˆ é™¤ä¸´æœŸé€€å›žå°è´¦
export function nearExpiryReturnDel(ids) {
    return request({
        url: '/quality/nearExpiryReturn/del',
        method: 'delete',
        data: ids,
    })
}
// èŽ·å–ä¸´æœŸé€€å›žå°è´¦è¯¦æƒ…
export function nearExpiryReturnDetail(id) {
    return request({
        url: '/quality/nearExpiryReturn/' + id,
        method: 'get',
    })
}
src/api/qualityManagement/qualityTestStandardBinding.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// ç»‘定列表(不分页)
export function qualityTestStandardBindingList(query) {
  return request({
    url: "/qualityTestStandardBinding/list",
    method: "get",
    params: query,
  });
}
// æ–°å¢žç»‘定(支持批量)
export function qualityTestStandardBindingAdd(data) {
  return request({
    url: "/qualityTestStandardBinding/add",
    method: "post",
    data,
  });
}
// åˆ é™¤ç»‘定(传 id æ•°ç»„)
export function qualityTestStandardBindingDel(ids) {
  return request({
    url: "/qualityTestStandardBinding/del",
    method: "delete",
    data: ids,
  });
}
src/api/reportAnalysis/qualityReport.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
import request from '@/utils/request'
// èŽ·å–å„ç±»åž‹å®Œæˆæ•°é‡
export function getInspectStatistics() {
  return request({
    url: '/qualityReport/getInspectStatistics',
    method: 'get'
  })
}
// èŽ·å–è´¨æ£€åˆæ ¼çŽ‡ç»Ÿè®¡
export function getPassRateStatistics() {
  return request({
    url: '/qualityReport/getPassRateStatistics',
    method: 'get'
  })
}
// èŽ·å–æœˆåº¦åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
export function getMonthlyPassRateStatistics(year) {
  return request({
    url: '/qualityReport/getMonthlyPassRateStatistics',
    method: 'get',
    params: { year }
  })
}
// èŽ·å–å¹´åº¦æ€»åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
export function getYearlyPassRateStatistics(year) {
  return request({
    url: '/qualityReport/getYearlyPassRateStatistics',
    method: 'get',
    params: { year }
  })
}
// èŽ·å–æœˆåº¦å®Œæˆæ˜Žç»†æ•°æ®
export function getMonthlyCompletionDetails(year) {
  return request({
    url: '/qualityReport/getMonthlyCompletionDetails',
    method: 'get',
    params: { year }
  })
}
// èŽ·å–çƒ­ç‚¹æ£€æµ‹æŒ‡æ ‡ç»Ÿè®¡
export function getTopParameters(inspectType) {
  return request({
    url: '/qualityReport/getTopParameters',
    method: 'get',
    params: { inspectType }
  })
}
src/api/salesManagement/deliveryLedger.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
// å‘货台账页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function deliveryLedgerListPage(query) {
  return request({
    url: "/shippingInfo/listPage",
    method: "get",
    params: query,
  });
}
// ä¿®æ”¹å‘货台账
export function addOrUpdateDeliveryLedger(query) {
  return request({
    url: "/shippingInfo/update",
    method: "post",
    data: query,
  });
}
// åˆ é™¤å‘货台账
export function delDeliveryLedger(query) {
  return request({
    url: "/shippingInfo/delete",
    method: "delete",
    data: query,
  });
}
// æ–°å¢žå‘货信息
export function addShippingInfo(data) {
  return request({
    url: "/shippingInfo/add",
    method: "post",
    data,
  });
}
src/api/salesManagement/indicatorStats.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
// æŒ‡æ ‡ç»Ÿè®¡é¡µé¢æŽ¥å£
import request from "@/utils/request";
// å¤´éƒ¨ç»Ÿè®¡æŽ¥å£
export function getTotalStatistics(query) {
  return request({
    url: "/metricStatistics/total",
    method: "get",
    params: query,
  });
}
// æŸ±çŠ¶å›¾æ•°æ®æŽ¥å£
export function getStatisticsTable(query) {
  return request({
    url: "/metricStatistics/statisticsTable",
    method: "get",
    params: query,
  });
}
src/api/salesManagement/paymentShipping.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
// é”€å”®å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
    return request({
        url: "/paymentShipping/listPage",
        method: "get",
        params: query,
    });
}
// æ–°å¢ž
export function add(data) {
    return request({
        url: "/paymentShipping/add",
        method: "post",
        data
    });
}
// ä¿®æ”¹
export function update(data) {
    return request({
        url: "/paymentShipping/update",
        method: "post",
        data
    });
}
// åˆ é™¤é”€å”®å°è´¦
export function deletePaymentShipping(data) {
    return request({
        url: "/paymentShipping/delete",
        method: "delete",
        data
    });
}
src/api/salesManagement/receiptPayment.js
@@ -40,7 +40,7 @@
// æŸ¥è¯¢å·²ç»ç»‘定发票的开票台账
export function bindInvoiceNoRegPage(query) {
    return request({
        url: '/receiptPayment/bindInvoiceNoRegPage',
        url: '/sales/product/listPageSalesLedger',
        method: 'get',
        params: query
    })
src/api/salesManagement/salesLedger.js
@@ -109,3 +109,11 @@
    params: query,
  });
}
// é”€å”®å°è´¦é¡µé¢å‘货,查询库存是否充足
export function getProductInventory(query) {
    return request({
        url: "/sales/ledger/getProductInventory",
        method: "get",
        params: query,
    });
}
src/api/salesManagement/salespersonManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
// é”€å”®å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
    return request({
        url: "/salespersonManagement/listPage",
        method: "get",
        params: query,
    });
}
// æ–°å¢ž
export function add(data) {
    return request({
        url: "/salespersonManagement/add",
        method: "post",
        data
    });
}
// ä¿®æ”¹
export function update(data) {
    return request({
        url: "/salespersonManagement/update",
        method: "post",
        data
    });
}
// åˆ é™¤é”€å”®å°è´¦
export function deleteSalespersonManagement(data) {
    return request({
        url: "/salespersonManagement/delete",
        method: "delete",
        data
    });
}
src/api/salesManagement/strategyControl.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,202 @@
// ç­–略管控页面接口
import request from "@/utils/request";
// ========== ä»·æ ¼ç­–略配置 ==========
// åˆ†é¡µæŸ¥è¯¢ä»·æ ¼ç­–略列表
export function getPriceStrategyList(query) {
  return request({
    url: "/sales/priceStrategy/list",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢ä»·æ ¼ç­–略详情
export function getPriceStrategyDetail(id) {
  return request({
    url: "/sales/priceStrategy/detail",
    method: "get",
    params: { id },
  });
}
// æ–°å¢žä»·æ ¼ç­–ç•¥
export function addPriceStrategy(data) {
  return request({
    url: "/sales/priceStrategy/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹ä»·æ ¼ç­–ç•¥
export function updatePriceStrategy(data) {
  return request({
    url: "/sales/priceStrategy/update",
    method: "post",
    data: data,
  });
}
// åˆ é™¤ä»·æ ¼ç­–ç•¥
export function deletePriceStrategy(id) {
  return request({
    url: "/sales/priceStrategy/delete",
    method: "delete",
    params: { id },
  });
}
// å¯ç”¨/禁用价格策略
export function togglePriceStrategy(data) {
  return request({
    url: "/sales/priceStrategy/toggle",
    method: "post",
    data: data,
  });
}
// ========== åˆåŒæ‰§è¡Œç›‘控 ==========
// èŽ·å–åˆåŒæ‰§è¡Œç»Ÿè®¡æ•°æ®
export function getContractStats(query) {
  return request({
    url: "/sales/contract/stats",
    method: "get",
    params: query,
  });
}
// åˆ†é¡µæŸ¥è¯¢åˆåŒæ‰§è¡Œåˆ—表
export function getContractExecutionList(query) {
  return request({
    url: "/sales/contract/executionList",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢åˆåŒæ‰§è¡Œè¯¦æƒ…
export function getContractExecutionDetail(contractNo) {
  return request({
    url: "/sales/contract/executionDetail",
    method: "get",
    params: { contractNo },
  });
}
// æ›´æ–°åˆåŒæ‰§è¡Œè¿›åº¦
export function updateContractProgress(data) {
  return request({
    url: "/sales/contract/updateProgress",
    method: "post",
    data: data,
  });
}
// ========== åŽ†å²æ¯”ä»·åˆ†æž ==========
// æŸ¥è¯¢åŽ†å²ä»·æ ¼å¯¹æ¯”æ•°æ®
export function getPriceComparisonList(query) {
  return request({
    url: "/sales/priceComparison/list",
    method: "get",
    params: query,
  });
}
// èŽ·å–ä»·æ ¼è¶‹åŠ¿å›¾è¡¨æ•°æ®
export function getPriceTrendChart(query) {
  return request({
    url: "/sales/priceComparison/trendChart",
    method: "get",
    params: query,
  });
}
// å¯¼å‡ºåŽ†å²æ¯”ä»·æ•°æ®
export function exportPriceComparison(query) {
  return request({
    url: "/sales/priceComparison/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
// ========== åˆ©æ¶¦åˆ†æž ==========
// èŽ·å–åˆ©æ¶¦ç»Ÿè®¡æ•°æ®
export function getProfitStats(query) {
  return request({
    url: "/sales/profit/stats",
    method: "get",
    params: query,
  });
}
// åˆ†é¡µæŸ¥è¯¢åˆ©æ¶¦åˆ†æžåˆ—表
export function getProfitAnalysisList(query) {
  return request({
    url: "/sales/profit/analysisList",
    method: "get",
    params: query,
  });
}
// èŽ·å–åˆ©æ¶¦è¶‹åŠ¿å›¾è¡¨æ•°æ®
export function getProfitTrendChart(query) {
  return request({
    url: "/sales/profit/trendChart",
    method: "get",
    params: query,
  });
}
// è®¡ç®—毛利率
export function calculateGrossProfit(data) {
  return request({
    url: "/sales/profit/calculate",
    method: "post",
    data: data,
  });
}
// å¯¼å‡ºåˆ©æ¶¦åˆ†æžæŠ¥è¡¨
export function exportProfitAnalysis(query) {
  return request({
    url: "/sales/profit/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
// ========== å…¬å…±æŽ¥å£ ==========
// æŸ¥è¯¢å®¢æˆ·åˆ—表(用于下拉选择)
export function getCustomerOptions() {
  return request({
    url: "/basic/customer/options",
    method: "get",
  });
}
// æŸ¥è¯¢äº§å“åˆ—表(用于下拉选择)
export function getProductOptions(query) {
  return request({
    url: "/basic/product/options",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢é”€å”®åŒºåŸŸåˆ—表
export function getRegionOptions() {
  return request({
    url: "/basic/region/options",
    method: "get",
  });
}
src/api/system/message.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
import request from "@/utils/request";
// æŸ¥è¯¢æ¶ˆæ¯é€šçŸ¥åˆ—表
export function listMessage(query) {
  return request({
    url: "/system/notice/list",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢æœªè¯»æ¶ˆæ¯æ•°é‡
export function getUnreadCount(consigneeId) {
  return request({
    url: "/system/notice/getCount",
    method: "get",
    params: { consigneeId },
  });
}
// æ ‡è®°æ¶ˆæ¯ä¸ºå·²è¯»
export function markAsRead(noticeId, status) {
  return request({
    url: "/system/notice",
    method: "put",
    data: { noticeId, status },
  });
}
// ä¸€é”®æ ‡è®°æ‰€æœ‰æ¶ˆæ¯ä¸ºå·²è¯»
export function markAllAsRead() {
  return request({
    url: "/system/notice/readAll",
    method: "post",
  });
}
// ç¡®è®¤æ¶ˆæ¯
export function confirmMessage(noticeId, status) {
  return request({
    url: "/system/notice",
    method: "put",
    data: { noticeId, status },
  });
}
src/api/system/post.js
@@ -9,6 +9,15 @@
  })
}
export function findPostOptions(query) {
  return request({
    url: '/system/post/optionselect',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å²—位详细
export function getPost(postId) {
  return request({
src/api/viewIndex.js
@@ -44,4 +44,22 @@
        url: '/sales/ledger/getAmountHalfYear',
        method: 'get'
    })
}
// å„生产订单的完成进度统计
// /home/progressStatistics
export const getProgressStatistics = ()=>{
    return request({
        url: '/home/progressStatistics',
        method: 'get'
    })
}
//在制品周转情况
//home/workInProcessTurnover
export const getWorkInProcessTurnover= ()=>{
    return request({
        url: '/home/workInProcessTurnover',
        method: 'get'
    })
}
src/assets/BI/backImage@2x.png
src/assets/BI/biaoti.png
src/assets/BI/border@2x.png
src/assets/BI/caiwufenxiback@2x.png
src/assets/BI/chuchangyijianicon@2x.png
src/assets/BI/guochengyijianicon@2x.png
src/assets/BI/hetongicon.png
src/assets/BI/hetongjineback@2x.png
src/assets/BI/hetongjineicon1@2x.png
src/assets/BI/hetongjineicon@2x.png
src/assets/BI/hetongtitleback@2x.png
src/assets/BI/icon@2x.png
src/assets/BI/jiantou@2x.png
src/assets/BI/kehuhetongback@2x.png
src/assets/BI/pieback@2x.png
src/assets/BI/shijianmingchengbeijing@2x.png
src/assets/BI/shijianmingxiicon@2x.png
src/assets/BI/shujutongji@2x.png
src/assets/BI/shujutongjiicon@2x.png
src/assets/BI/yuancailiaoyijianicon@2x.png
src/assets/BI/zonghetongbingtubiankuang@2x.png
src/assets/icons/png/circleBlue@2x.png
src/assets/icons/png/circleGreen@2x.png
src/assets/icons/png/circleOrange@2x.png
src/assets/icons/png/circleRed@2x.png
src/assets/icons/png/circleYellow@2x.png
src/assets/icons/png/walletBlue@2x.png
src/assets/icons/png/walletGreen@2x.png
src/assets/icons/png/walletOrange@2x.png
src/assets/icons/png/walletRed@2x.png
src/assets/icons/png/walletYellow@2x.png
src/assets/images/chartCard.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#0092FF" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/assets/images/chartCard2.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#5EB334" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/assets/images/chartCard3.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#8000FF" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/assets/images/video.png
src/assets/indexViews/LCLogo.png
src/assets/indexViews/login-background.png
src/assets/logo/¶Ø»Í¶¦³Ï.png
src/components/Dialog/FileListDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,309 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    :title="title"
    :width="width"
    :before-close="handleClose"
  >
    <div class="file-list-toolbar" v-if="showToolbar">
      <template v-if="useBuiltInUpload">
        <el-upload
          v-model:file-list="uploadFileList"
          class="upload-demo"
          :action="uploadAction"
          :headers="uploadHeaders"
          :show-file-list="false"
          :on-success="handleDefaultUploadSuccess"
          :on-error="handleDefaultUploadError"
        >
          <el-button
            v-if="showUploadButton"
            type="primary"
            size="small"
          >
            ä¸Šä¼ é™„ä»¶
          </el-button>
        </el-upload>
      </template>
      <template v-else>
        <el-button
          v-if="showUploadButton"
          type="primary"
          size="small"
          @click="handleUpload"
        >
          æ–°å¢žé™„ä»¶
        </el-button>
      </template>
    </div>
    <el-table :data="tableData" border :height="tableHeight">
      <el-table-column
        :label="nameColumnLabel"
        :prop="nameColumnProp"
        :min-width="nameColumnMinWidth"
        show-overflow-tooltip
      />
      <el-table-column
        v-if="showActions"
        fixed="right"
        label="操作"
        :width="actionColumnWidth"
        align="center"
      >
        <template #default="scope">
          <el-button
            v-if="showDownload"
            link
            type="primary"
            size="small"
            @click="handleDownload(scope.row)"
          >
            ä¸‹è½½
          </el-button>
          <el-button
            v-if="showPreview"
            link
            type="primary"
            size="small"
            @click="handlePreview(scope.row)"
          >
            é¢„览
          </el-button>
          <el-button
            v-if="showDeleteButton"
            link
            type="danger"
            size="small"
            @click="handleDelete(scope.row, scope.$index)"
          >
            åˆ é™¤
          </el-button>
          <slot name="actions" :row="scope.row"></slot>
        </template>
      </el-table-column>
      <slot name="columns"></slot>
    </el-table>
  </el-dialog>
  <filePreview v-if="showPreview" ref="filePreviewRef" />
</template>
<script setup>
import { ref, computed, getCurrentInstance } from 'vue'
import { ElMessage } from 'element-plus'
import filePreview from '@/components/filePreview/index.vue'
import { getToken } from '@/utils/auth'
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  title: {
    type: String,
    default: '附件'
  },
  width: {
    type: String,
    default: '40%'
  },
  tableHeight: {
    type: String,
    default: '40vh'
  },
  nameColumnLabel: {
    type: String,
    default: '附件名称'
  },
  nameColumnProp: {
    type: String,
    default: 'name'
  },
  nameColumnMinWidth: {
    type: [String, Number],
    default: 400
  },
  actionColumnWidth: {
    type: [String, Number],
    default: 160
  },
  showActions: {
    type: Boolean,
    default: true
  },
  showDownload: {
    type: Boolean,
    default: true
  },
  showPreview: {
    type: Boolean,
    default: true
  },
  showUploadButton: {
    type: Boolean,
    default: false
  },
  showDeleteButton: {
    type: Boolean,
    default: false
  },
  urlField: {
    type: String,
    default: 'url'
  },
  downloadMethod: {
    type: Function,
    default: null
  },
  previewMethod: {
    type: Function,
    default: null
  },
  uploadMethod: {
    type: Function,
    default: null
  },
  deleteMethod: {
    type: Function,
    default: null
  },
  rulesRegulationsManagementId: {
    type: [String, Number],
    default: ''
  },
  uploadUrl: {
    type: String,
    default: `${import.meta.env.VITE_APP_BASE_API}/file/upload`
  }
})
const emit = defineEmits(['update:modelValue', 'close', 'download', 'preview', 'upload', 'delete'])
const { proxy } = getCurrentInstance()
const filePreviewRef = ref(null)
const uploadFileList = ref([])
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
const tableData = ref([])
const showToolbar = computed(() => props.showUploadButton)
const useBuiltInUpload = computed(() => !props.uploadMethod)
const uploadAction = computed(() => props.uploadUrl)
const uploadHeaders = computed(() => ({
  Authorization: `Bearer ${getToken()}`
}))
const handleClose = () => {
  emit('close')
  dialogVisible.value = false
}
const handleDownload = (row) => {
  if (props.downloadMethod) {
    props.downloadMethod(row)
  } else {
    // é»˜è®¤ä¸‹è½½æ–¹æ³•
    proxy.$download.name(row[props.urlField])
  }
  emit('download', row)
}
const handlePreview = (row) => {
  if (props.previewMethod) {
    props.previewMethod(row)
  } else {
    // é»˜è®¤é¢„览方法
    if (filePreviewRef.value) {
      filePreviewRef.value.open(row[props.urlField])
    }
  }
  emit('preview', row)
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list || []
}
const handleUpload = async () => {
  if (props.uploadMethod) {
    // å¦‚果提供了自定义上传方法,由父组件负责更新列表(通过 setList)
    // è¿™é‡Œä¸å†è‡ªåŠ¨æ·»åŠ ï¼Œé¿å…ä¸Žçˆ¶ç»„ä»¶çš„ setList é‡å¤
    await props.uploadMethod()
  }
  emit('upload')
}
const handleDelete = async (row, index) => {
  if (props.deleteMethod) {
    const result = await props.deleteMethod(row, index)
    if (result === false) {
      return
    }
    // å¦‚果提供了 deleteMethod,由父组件负责刷新列表,不在这里删除
  } else {
    // å¦‚果没有提供 deleteMethod,才在组件内部删除
    removeAttachment(index)
  }
  emit('delete', row)
}
const addAttachment = (item) => {
  tableData.value = [...tableData.value, item]
}
const handleDefaultUploadSuccess = async (res, file) => {
  if (res?.code !== 200) {
    ElMessage.error(res?.msg || '文件上传失败')
    return
  }
  if (!props.rulesRegulationsManagementId) {
    ElMessage.error('缺少规章制度ID,无法保存附件')
    return
  }
  const fileName = res?.data?.originalName || file?.name
  const fileUrl = res?.data?.tempPath || res?.data?.url
  const payload = {
    fileName,
    fileUrl,
    rulesRegulationsManagementId: props.rulesRegulationsManagementId,
    raw: res?.data || {}
  }
  emit('upload', payload)
}
const handleDefaultUploadError = () => {
  ElMessage.error('文件上传失败')
}
const removeAttachment = (index) => {
  if (index > -1 && index < tableData.value.length) {
    const newList = [...tableData.value]
    newList.splice(index, 1)
    tableData.value = newList
  }
}
const setList = (list) => {
  tableData.value = list || []
}
defineExpose({
  open,
  addAttachment,
  removeAttachment,
  setList,
  handleUpload,
  handleDelete
})
</script>
<style scoped>
.file-list-toolbar {
  margin-bottom: 8px;
  text-align: right;
}
</style>
src/components/Dialog/FormDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    :title="computedTitle"
    :width="width"
    @close="handleClose"
  >
    <slot></slot>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="handleConfirm">确认</el-button>
        <el-button @click="handleCancel">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  title: {
    type: [String, Function],
    default: ''
  },
  operationType: {
    type: String,
    default: ''
  },
  width: {
    type: String,
    default: '70%'
  }
})
const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel'])
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
const computedTitle = computed(() => {
  if (typeof props.title === 'function') {
    return props.title(props.operationType)
  }
  return props.title
})
const handleClose = () => {
  emit('close')
}
const handleConfirm = () => {
  emit('confirm')
}
const handleCancel = () => {
  emit('cancel')
  dialogVisible.value = false
}
</script>
<style scoped>
.dialog-footer {
  text-align: center;
}
</style>
src/components/Dialog/ImportDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,172 @@
<template>
  <el-dialog
    :title="title"
    v-model="dialogVisible"
    :width="width"
    :append-to-body="appendToBody"
    @close="handleClose"
  >
    <el-upload
      ref="uploadRef"
      :limit="limit"
      :accept="accept"
      :headers="headers"
      :action="action"
      :disabled="disabled"
      :before-upload="beforeUpload"
      :on-progress="onProgress"
      :on-success="onSuccess"
      :on-error="onError"
      :on-change="onChange"
      :auto-upload="autoUpload"
      drag
    >
      <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <template #tip>
        <div class="el-upload__tip text-center">
          <span>{{ tipText }}</span>
          <el-link
            v-if="showDownloadTemplate"
            type="primary"
            :underline="false"
            style="font-size: 12px; vertical-align: baseline; margin-left: 5px;"
            @click="handleDownloadTemplate"
            >下载模板</el-link
          >
        </div>
      </template>
    </el-upload>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="handleConfirm">ç¡® å®š</el-button>
        <el-button @click="handleCancel">取 æ¶ˆ</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { computed, ref } from 'vue'
import { UploadFilled } from '@element-plus/icons-vue'
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  title: {
    type: String,
    default: '导入'
  },
  width: {
    type: String,
    default: '400px'
  },
  appendToBody: {
    type: Boolean,
    default: true
  },
  limit: {
    type: Number,
    default: 1
  },
  accept: {
    type: String,
    default: '.xlsx, .xls'
  },
  headers: {
    type: Object,
    default: () => ({})
  },
  action: {
    type: String,
    required: true
  },
  disabled: {
    type: Boolean,
    default: false
  },
  autoUpload: {
    type: Boolean,
    default: false
  },
  tipText: {
    type: String,
    default: '仅允许导入xls、xlsx格式文件。'
  },
  showDownloadTemplate: {
    type: Boolean,
    default: true
  },
  beforeUpload: {
    type: Function,
    default: null
  },
  onProgress: {
    type: Function,
    default: null
  },
  onSuccess: {
    type: Function,
    default: null
  },
  onError: {
    type: Function,
    default: null
  },
  onChange: {
    type: Function,
    default: null
  }
})
const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel', 'download-template'])
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
const uploadRef = ref(null)
const handleClose = () => {
  emit('close')
}
const handleConfirm = () => {
  emit('confirm')
}
const submit = () => {
  if (uploadRef.value) {
    uploadRef.value.submit()
  }
}
const handleCancel = () => {
  emit('cancel')
  dialogVisible.value = false
}
const handleDownloadTemplate = () => {
  emit('download-template')
}
defineExpose({
  uploadRef,
  submit,
  clearFiles: () => {
    if (uploadRef.value) {
      uploadRef.value.clearFiles()
    }
  }
})
</script>
<style scoped>
.dialog-footer {
  text-align: center;
}
</style>
src/components/DynamicTable/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,402 @@
<template>
  <div class="dynamic-table-container">
    <el-table
      ref="tableRef"
      v-loading="loading"
      :data="tableData"
      :border="border"
      :height="height"
      :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
      style="width: 100%"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
    >
      <!-- é€‰æ‹©åˆ— -->
      <el-table-column
        v-if="showSelection"
        align="center"
        type="selection"
        width="55"
      />
      <!-- åºå·åˆ— -->
      <el-table-column
        v-if="showIndex"
        align="center"
        label="序号"
        type="index"
        width="60"
      />
      <!-- å›ºå®šåˆ—:部门 -->
      <el-table-column
        label="部门"
        prop="department"
        width="120"
        show-overflow-tooltip
        align="center"
      />
      <!-- å›ºå®šåˆ—:姓名 -->
      <el-table-column
        label="姓名"
        prop="name"
        width="100"
        show-overflow-tooltip
        align="center"
      />
      <!-- å›ºå®šåˆ—:工号 -->
      <el-table-column
        label="工号"
        prop="employeeId"
        width="100"
        show-overflow-tooltip
        align="center"
      />
      <!-- åŠ¨æ€åˆ—ï¼šæ ¹æ®å­—å…¸æ¸²æŸ“ -->
      <el-table-column
        v-for="(dictItem, index) in dynamicColumns"
        :key="dictItem.value"
        :label="dictItem.label"
        :prop="dictItem.value"
        :width="dictItem.width || 120"
        show-overflow-tooltip
        align="center"
      >
        <template #default="scope">
          <!-- æ ¹æ®å­—典类型渲染不同的显示方式 -->
          <template v-if="dictItem.renderType === 'tag'">
            <el-tag
              :type="getTagType(scope.row[dictItem.value])"
              size="small"
            >
              {{ getDictValueLabel(dictItem.dictType, scope.row[dictItem.value]) }}
            </el-tag>
          </template>
          <template v-else-if="dictItem.renderType === 'select'">
            <el-select
              v-model="scope.row[dictItem.value]"
              placeholder="请选择"
              size="small"
              @change="handleSelectChange(scope.row, dictItem.value, $event)"
            >
              <el-option
                v-for="option in dictItem.options"
                :key="option.value"
                :label="option.label"
                :value="option.value"
              />
            </el-select>
          </template>
          <template v-else-if="dictItem.renderType === 'input'">
            <el-input
              v-model="scope.row[dictItem.value]"
              size="small"
              placeholder="请输入"
              @blur="handleInputChange(scope.row, dictItem.value, $event)"
            />
          </template>
          <template v-else>
            <span>{{ getDictValueLabel(dictItem.dictType, scope.row[dictItem.value]) }}</span>
          </template>
        </template>
      </el-table-column>
      <!-- æ“ä½œåˆ— -->
      <el-table-column
        v-if="showActions"
        label="操作"
        width="150"
        align="center"
        fixed="right"
      >
        <template #default="scope">
          <el-button
            type="primary"
            link
            size="small"
            @click="handleEdit(scope.row, scope.$index)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="danger"
            link
            size="small"
            @click="handleDelete(scope.row, scope.$index)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µç»„ä»¶ -->
    <div v-if="showPagination" class="pagination-container">
      <el-pagination
        v-model:current-page="pagination.current"
        v-model:page-size="pagination.size"
        :page-sizes="[10, 20, 50, 100]"
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useDict } from '@/utils/dict'
// å®šä¹‰ç»„件属性
const props = defineProps({
  // è¡¨æ ¼æ•°æ®
  data: {
    type: Array,
    default: () => []
  },
  // å­—典类型数组,用于动态生成列
  dictTypes: {
    type: Array,
    default: () => []
  },
  // æ˜¯å¦æ˜¾ç¤ºé€‰æ‹©åˆ—
  showSelection: {
    type: Boolean,
    default: false
  },
  // æ˜¯å¦æ˜¾ç¤ºåºå·åˆ—
  showIndex: {
    type: Boolean,
    default: true
  },
  // æ˜¯å¦æ˜¾ç¤ºæ“ä½œåˆ—
  showActions: {
    type: Boolean,
    default: false
  },
  // æ˜¯å¦æ˜¾ç¤ºåˆ†é¡µ
  showPagination: {
    type: Boolean,
    default: false
  },
  // è¡¨æ ¼é«˜åº¦
  height: {
    type: [String, Number],
    default: 'auto'
  },
  // æ˜¯å¦æ˜¾ç¤ºè¾¹æ¡†
  border: {
    type: Boolean,
    default: true
  },
  // åŠ è½½çŠ¶æ€
  loading: {
    type: Boolean,
    default: false
  },
  // åˆ†é¡µé…ç½®
  pagination: {
    type: Object,
    default: () => ({
      current: 1,
      size: 10,
      total: 0
    })
  }
})
// å®šä¹‰äº‹ä»¶
const emit = defineEmits([
  'selection-change',
  'row-click',
  'edit',
  'delete',
  'select-change',
  'input-change',
  'size-change',
  'current-change'
])
// å“åº”式数据
const tableRef = ref(null)
const tableData = ref([])
// èŽ·å–å­—å…¸æ•°æ®
const dictData = ref({})
// åŠ¨æ€åˆ—é…ç½®
const dynamicColumns = computed(() => {
  const columns = []
  props.dictTypes.forEach(dictType => {
    const dictItems = dictData.value[dictType] || []
    // ä¸ºæ¯ä¸ªå­—典类型创建一个列,而不是为每个字典项创建列
    if (dictItems.length > 0) {
      columns.push({
        label: getDictLabel(dictType), // èŽ·å–å­—å…¸ç±»åž‹çš„æ˜¾ç¤ºåç§°
        value: dictType, // ä½¿ç”¨å­—典类型作为字段名
        width: 120,
        renderType: 'tag', // é»˜è®¤ä½¿ç”¨æ ‡ç­¾æ˜¾ç¤º
        options: dictItems, // æä¾›é€‰é¡¹
        dictType: dictType
      })
    }
  })
  return columns
})
// èŽ·å–å­—å…¸ç±»åž‹çš„æ˜¾ç¤ºåç§°
const getDictLabel = (dictType) => {
  const labelMap = {
    'sys_normal_disable': '状态',
    'sys_user_level': '级别',
    'sys_user_position': '职位',
    'sys_yes_no': '是否',
    'sys_user_sex': '性别',
    'sys_lavor_issue': '劳务问题'  // æ·»åŠ åŠ³åŠ¡é—®é¢˜å­—å…¸
  }
  return labelMap[dictType] || dictType
}
// èŽ·å–å­—å…¸æ•°æ®
const loadDictData = async () => {
  try {
    const dictPromises = props.dictTypes.map(async (dictType) => {
      const { getDicts } = await import('@/api/system/dict/data')
      const response = await getDicts(dictType)
      return {
        type: dictType,
        data: response.data.map(item => ({
          label: item.dictLabel,
          value: item.dictValue,
          elTagType: item.listClass,
          elTagClass: item.cssClass
        }))
      }
    })
    const results = await Promise.all(dictPromises)
    results.forEach(result => {
      dictData.value[result.type] = result.data
    })
  } catch (error) {
    console.error('加载字典数据失败:', error)
    // å¦‚果字典加载失败,使用默认数据
    props.dictTypes.forEach(dictType => {
      if (!dictData.value[dictType]) {
        dictData.value[dictType] = []
      }
    })
  }
}
// èŽ·å–æ ‡ç­¾ç±»åž‹
const getTagType = (value) => {
  // æ ¹æ®å€¼è¿”回不同的标签类型
  if (value === '1' || value === 'true' || value === '是') return 'success'
  if (value === '0' || value === 'false' || value === '否') return 'danger'
  if (value === '2' || value === 'warning') return 'warning'
  return 'info'
}
// èŽ·å–å­—å…¸å€¼çš„æ ‡ç­¾
const getDictValueLabel = (dictType, value) => {
  if (!value) return '-'
  const dictItems = dictData.value[dictType] || []
  const item = dictItems.find(item => item.value === value)
  return item ? item.label : value
}
// äº‹ä»¶å¤„理函数
const handleSelectionChange = (selection) => {
  emit('selection-change', selection)
}
const handleRowClick = (row, column, event) => {
  emit('row-click', row, column, event)
}
const handleEdit = (row, index) => {
  emit('edit', row, index)
}
const handleDelete = (row, index) => {
  emit('delete', row, index)
}
const handleSelectChange = (row, prop, value) => {
  emit('select-change', row, prop, value)
}
const handleInputChange = (row, prop, event) => {
  emit('input-change', row, prop, event.target.value)
}
const handleSizeChange = (size) => {
  emit('size-change', size)
}
const handleCurrentChange = (current) => {
  emit('current-change', current)
}
// ç›‘听数据变化
watch(() => props.data, (newData) => {
  tableData.value = newData
}, { immediate: true })
// ç›‘听字典类型变化
watch(() => props.dictTypes, () => {
  loadDictData()
}, { immediate: true })
// ç»„件挂载时加载字典数据
onMounted(() => {
  loadDictData()
})
// æš´éœ²æ–¹æ³•给父组件
defineExpose({
  tableRef,
  getSelection: () => tableRef.value?.getSelectionRows() || [],
  clearSelection: () => tableRef.value?.clearSelection(),
  toggleRowSelection: (row, selected) => tableRef.value?.toggleRowSelection(row, selected),
  setCurrentRow: (row) => tableRef.value?.setCurrentRow(row)
})
</script>
<style scoped>
.dynamic-table-container {
  width: 100%;
}
.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
:deep(.el-table .el-table__header-wrapper th) {
  background-color: #F0F1F5 !important;
  color: #333333;
  font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
  padding: 8px 0;
}
:deep(.el-select) {
  width: 100%;
}
:deep(.el-input) {
  width: 100%;
}
</style>
src/components/Echarts/echarts.vue
@@ -76,6 +76,10 @@
    type: Array,
    default: () => []
  },
  visualMap: {
    type: Object,
    default: () => ({})
  },
    option: {
        type: Object,
        default: () => ({})
@@ -113,6 +117,7 @@
  const option = {
    color: props.color.length ? props.color : undefined,
    backgroundColor: props.options.backgroundColor || '#fff',
    textStyle: props.options.textStyle || { color: '#333' },
    xAxis: props.xAxis,
    yAxis: props.yAxis,
    dataset: props.dataset,
@@ -120,6 +125,7 @@
    grid: props.grid,
    legend: props.legend,
    tooltip: props.tooltip,
    visualMap: Object.keys(props.visualMap).length ? props.visualMap : undefined,
  }
  
  chartInstance.clear()
@@ -148,7 +154,7 @@
// Watch all reactive props that affect the chart
watch(
    () => [props.xAxis, props.series, props.legend, props.tooltip],
    () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap],
    () => {
      if (chartInstance) {
        renderChart()
src/components/PIMTable/PIMTable.vue
@@ -10,7 +10,7 @@
    :row-class-name="rowClassName"
    :row-style="rowStyle"
    :row-key="rowKey"
    style="width: 100%"
    :style="tableStyle"
    tooltip-effect="dark"
    :expand-row-keys="expandRowKeys"
    :show-summary="isShowSummary"
@@ -41,12 +41,22 @@
      :fixed="item.fixed"
      :label="item.label"
      :prop="item.prop"
      show-overflow-tooltip
      :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
      :align="item.align"
      :sortable="!!item.sortable"
      :type="item.type"
      :width="item.width"
    >
      <template #header="scope">
        <div class="pim-table-header-cell">
          <div class="pim-table-header-title">
            {{ item.label }}
          </div>
          <div v-if="item.headerSlot" class="pim-table-header-extra">
            <slot :name="item.headerSlot" :column="scope.column" />
          </div>
        </div>
      </template>
      <template
        v-if="item.hasOwnProperty('colunmTemplate')"
        #[item.colunmTemplate]="scope"
@@ -121,11 +131,10 @@
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'">
        <div v-else-if="item.dataType == 'action'" @click.stop>
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button
              v-show="o.type != 'upload'"
              size="small"
              v-if="o.showHide ? o.showHide(scope.row) : true"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :plain="o.plain"
@@ -137,7 +146,7 @@
                    : o.color,
              }"
              link
              @click="o.clickFun(scope.row)"
              @click.stop="o.clickFun(scope.row)"
              :key="key"
            >
              {{ o.name }}
@@ -150,7 +159,6 @@
                (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
              "
              ref="uploadRef"
              size="small"
              :multiple="o.multiple ? o.multiple : false"
              :limit="1"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
@@ -179,7 +187,6 @@
              :show-file-list="false"
            >
              <el-button
                :size="o.size ? o.size : 'small'"
                link
                type="primary"
                :disabled="o.disabled ? o.disabled(scope.row) : false"
@@ -208,7 +215,7 @@
    </el-table-column>
  </el-table>
  <pagination
    v-if="page.total > 0"
        v-if="isShowPagination"
    :total="page.total"
    :layout="page.layout"
    :page="page.current"
@@ -272,6 +279,10 @@
    type: Boolean,
    default: false,
  },
    isShowPagination: {
    type: Boolean,
    default: true,
  },
  isShowSummary: {
    type: Boolean,
    default: false,
@@ -316,6 +327,10 @@
  total: {
    type: Number,
    default: 0,
  },
  tableStyle: {
    type: [String, Object],
    default: () => ({ width: "100%" }),
  },
});
@@ -430,4 +445,9 @@
  padding-right: 0 !important;
  padding-left: 0 !important;
}
.pim-table-header-extra :deep(.el-input),
.pim-table-header-extra :deep(.el-select) {
  width: 100%;
}
</style>
src/components/PageHeader/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
<template>
  <div class="page-header-wrapper">
    <el-page-header @back="handleBack" :content="content">
      <template #icon v-if="$slots.icon">
        <slot name="icon"></slot>
      </template>
      <template #title v-if="$slots.title">
        <slot name="title"></slot>
      </template>
      <template #content v-if="$slots.content">
        <slot name="content"></slot>
      </template>
      <template #extra>
        <slot name="extra">
          <slot name="right-button"></slot>
        </slot>
      </template>
    </el-page-header>
  </div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const props = defineProps({
  content: {
    type: String,
    default: ''
  }
})
const emit = defineEmits(['back'])
const router = useRouter()
const handleBack = () => {
  emit('back')
  // é»˜è®¤è¿”回到上一级
  router.back()
}
</script>
<style scoped>
.page-header-wrapper {
  margin-bottom: 16px;
}
.page-header-wrapper :deep(.el-page-header__extra) {
  display: flex;
  align-items: center;
  gap: 8px;
}
</style>
src/components/QRCodeGenerator/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,566 @@
<template>
  <div class="qr-code-generator">
    <!-- äºŒç»´ç ç”Ÿæˆè¡¨å• -->
    <el-form :model="form"
             :rules="rules"
             ref="formRef"
             label-width="120px"
             class="qr-form">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="标识类型"
                        prop="type">
            <el-select v-model="form.type"
                       placeholder="请选择标识类型"
                       style="width: 100%">
              <el-option label="二维码"
                         value="qrcode"></el-option>
              <el-option label="防伪码"
                         value="security"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="内容"
                        prop="content">
            <el-input v-model="form.content"
                      placeholder="请输入要编码的内容"
                      :type="form.type === 'security' ? 'textarea' : 'text'"
                      :rows="form.type === 'security' ? 3 : 1"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="尺寸"
                        prop="size">
            <el-input-number v-model="form.size"
                             :min="100"
                             :max="500"
                             :step="50"
                             style="width: 100%"></el-input-number>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="边距"
                        prop="margin">
            <el-input-number v-model="form.margin"
                             :min="0"
                             :max="10"
                             :step="1"
                             style="width: 100%"></el-input-number>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="前景色"
                        prop="foregroundColor">
            <el-color-picker v-model="form.foregroundColor"
                             style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="背景色"
                        prop="backgroundColor">
            <el-color-picker v-model="form.backgroundColor"
                             style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="24">
          <el-form-item>
            <el-button type="primary"
                       @click="generateCode"
                       :loading="generating">
              ç”Ÿæˆ{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}
            </el-button>
            <el-button @click="resetForm">重置</el-button>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <!-- ç”Ÿæˆçš„码显示区域 -->
    <div v-if="generatedCodeUrl"
         class="code-display">
      <el-divider content-position="center">
        {{ form.type === 'qrcode' ? '生成的二维码' : '生成的防伪码' }}
      </el-divider>
      <div class="code-container">
        <div class="code-image">
          <img :src="generatedCodeUrl"
               :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" />
        </div>
        <div class="code-info">
          <p><strong>内容:</strong>{{ form.content }}</p>
          <p><strong>类型:</strong>{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}</p>
          <p><strong>尺寸:</strong>{{ form.size }}x{{ form.size }}px</p>
          <p><strong>生成时间:</strong>{{ generateTime }}</p>
        </div>
      </div>
      <div class="code-actions">
        <el-button type="success"
                   @click="downloadCode"
                   icon="Download">
          ä¸‹è½½å›¾ç‰‡
        </el-button>
        <el-button type="primary"
                   @click="copyToClipboard"
                   icon="CopyDocument">
          å¤åˆ¶å†…容
        </el-button>
        <el-button @click="printCode"
                   icon="Printer">
          æ‰“印
        </el-button>
      </div>
    </div>
    <!-- æ‰¹é‡ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="batchDialogVisible"
               title="批量生成"
               width="600px">
      <el-form :model="batchForm"
               label-width="120px">
        <el-form-item label="生成数量">
          <el-input-number v-model="batchForm.quantity"
                           :min="1"
                           :max="100"
                           style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="前缀">
          <el-input v-model="batchForm.prefix"
                    placeholder="请输入前缀,如:PROD_"></el-input>
        </el-form-item>
        <el-form-item label="起始编号">
          <el-input-number v-model="batchForm.startNumber"
                           :min="1"
                           style="width: 100%"></el-input-number>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="generateBatchCodes">开始生成</el-button>
          <el-button @click="batchDialogVisible = false">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰¹é‡ç”Ÿæˆç»“æžœ -->
    <div v-if="batchCodes.length > 0"
         class="batch-results">
      <el-divider content-position="center">批量生成结果</el-divider>
      <div class="batch-grid">
        <div v-for="(code, index) in batchCodes"
             :key="index"
             class="batch-item">
          <img :src="code.url"
               :alt="code.content" />
          <p class="batch-content">{{ code.content }}</p>
          <el-button size="small"
                     @click="downloadSingleCode(code)">下载</el-button>
        </div>
      </div>
      <div class="batch-actions">
        <el-button type="success"
                   @click="downloadAllCodes">下载全部</el-button>
        <el-button @click="clearBatchCodes">清空结果</el-button>
      </div>
    </div>
  </div>
</template>
<script setup>
  import { ref, reactive, computed, onMounted } from "vue";
  import QRCode from "qrcode";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Download, CopyDocument, Printer } from "@element-plus/icons-vue";
  // å®šä¹‰ç»„件名称
  defineOptions({
    name: "QRCodeGenerator",
  });
  // è¡¨å•数据
  const form = reactive({
    type: "qrcode",
    content: "",
    size: 200,
    margin: 2,
    foregroundColor: "#000000",
    backgroundColor: "#FFFFFF",
  });
  // è¡¨å•验证规则
  const rules = {
    type: [{ required: true, message: "请选择标识类型", trigger: "change" }],
    content: [{ required: true, message: "请输入内容", trigger: "blur" }],
  };
  // å“åº”式数据
  const formRef = ref();
  const generating = ref(false);
  const generatedCodeUrl = ref("");
  const generateTime = ref("");
  const batchDialogVisible = ref(false);
  const batchForm = reactive({
    quantity: 10,
    prefix: "",
    startNumber: 1,
  });
  const batchCodes = ref([]);
  // ç”ŸæˆäºŒç»´ç æˆ–防伪码
  const generateCode = async () => {
    try {
      await formRef.value.validate();
      if (!form.content.trim()) {
        ElMessage.warning("请输入要编码的内容");
        return;
      }
      generating.value = true;
      if (form.type === "qrcode") {
        // ç”ŸæˆäºŒç»´ç 
        generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor,
          },
          errorCorrectionLevel: "M",
        });
      } else {
        // ç”Ÿæˆé˜²ä¼ªç ï¼ˆä½¿ç”¨äºŒç»´ç æŠ€æœ¯ï¼Œä½†å†…容格式不同)
        const securityContent = generateSecurityCode(form.content);
        generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor,
          },
          errorCorrectionLevel: "H", // é˜²ä¼ªç ä½¿ç”¨æœ€é«˜çº é”™çº§åˆ«
        });
      }
      generateTime.value = new Date().toLocaleString();
      ElMessage.success("生成成功!");
    } catch (error) {
      console.error("生成失败:", error);
      ElMessage.error("生成失败:" + error.message);
    } finally {
      generating.value = false;
    }
  };
  // ç”Ÿæˆé˜²ä¼ªç å†…容
  const generateSecurityCode = content => {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substr(2, 8);
    return `SEC_${content}_${timestamp}_${random}`;
  };
  // ä¸‹è½½ç”Ÿæˆçš„码
  const downloadCode = () => {
    if (!generatedCodeUrl.value) {
      ElMessage.warning("请先生成码");
      return;
    }
    const a = document.createElement("a");
    a.href = generatedCodeUrl.value;
    a.download = `${
      form.type === "qrcode" ? "二维码" : "防伪码"
    }_${new Date().getTime()}.png`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    ElMessage.success("下载成功!");
  };
  // å¤åˆ¶å†…容到剪贴板
  const copyToClipboard = async () => {
    try {
      await navigator.clipboard.writeText(form.content);
      ElMessage.success("内容已复制到剪贴板");
    } catch (error) {
      // é™çº§æ–¹æ¡ˆ
      const textArea = document.createElement("textarea");
      textArea.value = form.content;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
      ElMessage.success("内容已复制到剪贴板");
    }
  };
  // æ‰“印码
  const printCode = () => {
    if (!generatedCodeUrl.value) {
      ElMessage.warning("请先生成码");
      return;
    }
    const printWindow = window.open("", "_blank");
    printWindow.document.write(`
      <html>
        <head>
          <title>打印${form.type === "qrcode" ? "二维码" : "防伪码"}</title>
          <style>
            body { text-align: center; padding: 20px; }
            img { max-width: 100%; height: auto; }
            .info { margin: 20px 0; }
          </style>
        </head>
        <body>
          <h2>${form.type === "qrcode" ? "二维码" : "防伪码"}</h2>
          <img src="${generatedCodeUrl.value}" alt="${
      form.type === "qrcode" ? "二维码" : "防伪码"
    }" />
          <div class="info">
            <p><strong>内容:</strong>${form.content}</p>
            <p><strong>生成时间:</strong>${generateTime.value}</p>
          </div>
        </body>
      </html>
    `);
    printWindow.document.close();
    printWindow.print();
  };
  // é‡ç½®è¡¨å•
  const resetForm = () => {
    formRef.value.resetFields();
    generatedCodeUrl.value = "";
    generateTime.value = "";
    batchCodes.value = [];
  };
  // æ‰¹é‡ç”Ÿæˆ
  const generateBatchCodes = async () => {
    if (!batchForm.prefix.trim()) {
      ElMessage.warning("请输入前缀");
      return;
    }
    batchCodes.value = [];
    generating.value = true;
    try {
      for (let i = 0; i < batchForm.quantity; i++) {
        const number = batchForm.startNumber + i;
        const content = `${batchForm.prefix}${number
          .toString()
          .padStart(6, "0")}`;
        let codeUrl;
        if (form.type === "qrcode") {
          codeUrl = await QRCode.toDataURL(content, {
            width: form.size,
            margin: form.margin,
            color: {
              dark: form.foregroundColor,
              light: form.backgroundColor,
            },
          });
        } else {
          const securityContent = generateSecurityCode(content);
          codeUrl = await QRCode.toDataURL(securityContent, {
            width: form.size,
            margin: form.margin,
            color: {
              dark: form.foregroundColor,
              light: form.backgroundColor,
            },
          });
        }
        batchCodes.value.push({
          content,
          url: codeUrl,
        });
      }
      ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} ä¸ªç `);
      batchDialogVisible.value = false;
    } catch (error) {
      console.error("批量生成失败:", error);
      ElMessage.error("批量生成失败:" + error.message);
    } finally {
      generating.value = false;
    }
  };
  // ä¸‹è½½å•个批量生成的码
  const downloadSingleCode = code => {
    const a = document.createElement("a");
    a.href = code.url;
    a.download = `${code.content}.png`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  };
  // ä¸‹è½½æ‰€æœ‰æ‰¹é‡ç”Ÿæˆçš„码
  const downloadAllCodes = async () => {
    if (batchCodes.value.length === 0) {
      ElMessage.warning("没有可下载的码");
      return;
    }
    try {
      // ä½¿ç”¨JSZip打包下载
      const JSZip = await import("jszip");
      const zip = new JSZip.default();
      batchCodes.value.forEach((code, index) => {
        // å°†base64转换为blob
        const base64Data = code.url.split(",")[1];
        const byteCharacters = atob(base64Data);
        const byteNumbers = new Array(byteCharacters.length);
        for (let i = 0; i < byteCharacters.length; i++) {
          byteNumbers[i] = byteCharacters.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        zip.file(`${code.content}.png`, byteArray);
      });
      const content = await zip.generateAsync({ type: "blob" });
      const a = document.createElement("a");
      a.href = URL.createObjectURL(content);
      a.download = `批量${
        form.type === "qrcode" ? "二维码" : "防伪码"
      }_${new Date().getTime()}.zip`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(a.href);
      ElMessage.success("批量下载完成!");
    } catch (error) {
      console.error("批量下载失败:", error);
      ElMessage.error("批量下载失败,请逐个下载");
    }
  };
  // æ¸…空批量生成结果
  const clearBatchCodes = () => {
    batchCodes.value = [];
  };
  // æš´éœ²æ–¹æ³•给父组件
  defineExpose({
    generateCode,
    downloadCode,
    resetForm,
    form,
  });
</script>
<style scoped>
  .qr-code-generator {
    padding: 20px;
  }
  .qr-form {
    background: #f8f9fa;
    padding: 20px;
    border-radius: 8px;
    margin-bottom: 20px;
  }
  .code-display {
    margin-top: 30px;
  }
  .code-container {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: 40px;
    margin: 20px 0;
  }
  .code-image img {
    border: 2px solid #e0e0e0;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
  .code-info {
    text-align: left;
    min-width: 200px;
  }
  .code-info p {
    margin: 8px 0;
    color: #666;
  }
  .code-actions {
    text-align: center;
    margin: 20px 0;
  }
  .code-actions .el-button {
    margin: 0 10px;
  }
  .batch-results {
    margin-top: 30px;
  }
  .batch-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 20px;
    margin: 20px 0;
  }
  .batch-item {
    text-align: center;
    padding: 15px;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    background: #fff;
  }
  .batch-item img {
    width: 100px;
    height: 100px;
    margin-bottom: 10px;
  }
  .batch-content {
    font-size: 12px;
    color: #666;
    margin: 10px 0;
    word-break: break-all;
  }
  .batch-actions {
    text-align: center;
    margin: 20px 0;
  }
  .batch-actions .el-button {
    margin: 0 10px;
  }
  @media (max-width: 768px) {
    .code-container {
      flex-direction: column;
      align-items: center;
    }
    .batch-grid {
      grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    }
  }
</style>
src/layout/components/AppMain.vue
@@ -2,9 +2,12 @@
  <section class="app-main">
    <router-view v-slot="{ Component, route }">
      <transition name="fade-transform" mode="out-in">
        <keep-alive :include="tagsViewStore.cachedViews">
          <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
        </keep-alive>
        <div v-if="!route.meta.link" class="route-view-wrapper">
          <keep-alive :include="tagsViewStore.cachedViews">
            <component :is="Component" :key="route.path"/>
          </keep-alive>
        </div>
        <div v-else class="route-view-wrapper"></div>
      </transition>
    </router-view>
    <iframe-toggle />
@@ -43,6 +46,11 @@
  background: #F5F7FB;
}
.route-view-wrapper {
  width: 100%;
  height: 100%;
}
.fixed-header + .app-main {
  padding-top: 50px;
}
src/layout/components/Navbar.vue
@@ -6,24 +6,30 @@
      <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
    </div>
    <!--    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
    <div class="center-menu">
      <span class="label">{{ userStore.currentFactoryName }}</span>
      <el-dropdown @command="handleFactoryChange" class="right-menu-item hover-effect" trigger="click">
        <div>
          <el-icon size="20">
            <Switch />
          </el-icon>
        </div>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item v-for="item in factoryList" :key="item.deptId" :command="item">
              {{ item.deptName }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div class="right-menu">
      <!-- æ¶ˆæ¯é€šçŸ¥ -->
      <el-popover
        v-model:visible="notificationVisible"
        :width="500"
        placement="bottom-end"
        trigger="click"
        :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [0, 10] } }] }"
        popper-class="notification-popover"
      >
        <template #reference>
          <div class="notification-container right-menu-item hover-effect">
            <el-badge :value="unreadCount" :hidden="unreadCount === 0" class="notification-badge">
              <el-icon :size="20" style="cursor: pointer;">
                <Bell />
              </el-icon>
            </el-badge>
          </div>
        </template>
        <NotificationCenter
          @unreadCountChange="handleUnreadCountChange"
          ref="notificationCenterRef"
        />
      </el-popover>
      <div class="avatar-container">
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
          <div class="avatar-wrapper">
@@ -51,6 +57,7 @@
<script setup>
import { ElMessageBox } from 'element-plus'
import { Bell } from '@element-plus/icons-vue'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
@@ -59,17 +66,17 @@
import HeaderSearch from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
import NotificationCenter from './NotificationCenter/index.vue'
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
import { userLoginFacotryList } from "@/api/system/user.js"
import Cookies from "js-cookie";
import { decrypt } from "@/utils/jsencrypt"
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const factoryList = ref([])
const notificationVisible = ref(false)
const notificationCenterRef = ref(null)
const unreadCount = ref(0)
function toggleSideBar() {
  appStore.toggleSideBar()
}
@@ -112,41 +119,42 @@
  settingsStore.toggleTheme()
}
function getUserLoginFacotryList() {
  if (userStore.id) {
    userLoginFacotryList({ userId: userStore.id }).then(res => {
      console.log('res', res)
      factoryList.value = res.data
    })
  } else {
    factoryList.value = []
  }
// æ¶ˆæ¯é€šçŸ¥ç›¸å…³
function handleUnreadCountChange(count) {
  unreadCount.value = count
}
function handleFactoryChange(command) {
  console.log('command', command)
  handleLogin(command.deptId);
}
function handleLogin(currentFatoryId) {
  const loginForm = {
    username: Cookies.get("username"),
    password: Cookies.get("password") === undefined ? null : decrypt(Cookies.get("password")),
    currentFatoryId: currentFatoryId
  }
  userStore.loginCheckFactory(loginForm).then(res => {
    forceReload();
  }).catch((err) => {
    console.log(err)
// ç»„件挂载时加载未读数量和定时刷新
let unreadCountTimer = null
onMounted(() => {
  // å»¶è¿ŸåŠ è½½ï¼Œç¡®ä¿ç»„ä»¶å·²æ¸²æŸ“
  nextTick(() => {
    if (notificationCenterRef.value) {
      notificationCenterRef.value.loadUnreadCount()
    }
  })
}
function forceReload() {
  const currentUrl = window.location.origin + window.location.pathname;
  const timestamp = new Date().getTime();
  window.location.href = `${currentUrl}?reload=${timestamp}`;
}
  // å®šæ—¶åˆ·æ–°æœªè¯»æ•°é‡ï¼ˆæ¯30秒)
  unreadCountTimer = setInterval(() => {
    if (notificationCenterRef.value) {
      notificationCenterRef.value.loadUnreadCount()
    }
  }, 30000)
})
getUserLoginFacotryList();
// ç›‘听 popover æ˜¾ç¤ºçŠ¶æ€ï¼Œæ‰“å¼€æ—¶åŠ è½½æ¶ˆæ¯åˆ—è¡¨
watch(notificationVisible, (val) => {
  if (val && notificationCenterRef.value) {
    nextTick(() => {
      notificationCenterRef.value.loadMessages()
    })
  }
})
onUnmounted(() => {
  if (unreadCountTimer) {
    clearInterval(unreadCountTimer)
  }
})
</script>
<style lang='scss' scoped>
@@ -156,22 +164,6 @@
  position: relative;
  background: var(--navbar-bg);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .center-menu {
    line-height: 50px;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    .label {
      font-weight: bold;
      font-size: 18px;
      color: #333333;
      margin-right: 10px;
    }
  }
  .hamburger-container {
    line-height: 46px;
@@ -241,6 +233,19 @@
      }
    }
    .notification-container {
      margin-right: 20px;
      display: flex;
      align-items: center;
      cursor: pointer;
      .notification-badge {
        :deep(.el-badge__content) {
          border: none;
        }
      }
    }
    .avatar-container {
      margin-right: 40px;
@@ -266,4 +271,22 @@
    }
  }
}
</style>
<style lang="scss">
.notification-popover {
  padding: 0 !important;
  .el-popover__title {
    display: none;
  }
  .el-popover__body {
    padding: 0 !important;
  }
}
.el-badge__content.is-fixed{
  top: 12px;
}
</style>
src/layout/components/NotificationCenter/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,372 @@
<template>
  <div class="notification-popover-content">
    <div class="popover-header">
      <span class="popover-title">消息通知</span>
      <el-button type="primary" size="small" @click="handleMarkAllAsRead" :disabled="unreadCount === 0">
        ä¸€é”®å·²è¯»
      </el-button>
    </div>
    <div class="notification-content">
      <el-tabs v-model="activeTab" @tab-change="handleTabChange">
        <el-tab-pane :label="`未读(${unreadCount})`" name="unread">
          <div v-if="unreadList.length === 0" class="empty-state">
            <el-empty description="暂无未读消息" />
          </div>
          <div v-else class="notification-list">
            <div
              v-for="item in unreadList"
              :key="item.id"
              class="notification-item"
            >
              <div class="notification-icon">
                <el-icon :size="24" color="#67C23A">
                  <Bell />
                </el-icon>
              </div>
              <div class="notification-content-wrapper">
                <div class="notification-title">{{ item.noticeTitle }}</div>
                <div class="notification-detail">{{ item.noticeContent }}</div>
                <div class="notification-time">{{ item.createTime }}</div>
              </div>
              <div class="notification-action">
                <el-button type="primary" size="small" @click="handleConfirm(item)">
                  ç¡®è®¤
                </el-button>
              </div>
            </div>
          </div>
        </el-tab-pane>
        <el-tab-pane label="已读" name="read">
          <div v-if="readList.length === 0" class="empty-state">
            <el-empty description="暂无已读消息" />
          </div>
          <div v-else class="notification-list">
            <div
              v-for="item in readList"
              :key="item.id"
              class="notification-item read"
            >
              <div class="notification-icon">
                <el-icon :size="24" color="#909399">
                  <Bell />
                </el-icon>
              </div>
              <div class="notification-content-wrapper">
                <div class="notification-title">{{ item.noticeTitle }}</div>
                <div class="notification-detail">{{ item.noticeContent }}</div>
                <div class="notification-time">{{ item.createTime }}</div>
              </div>
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
      <!-- åˆ†é¡µ -->
      <div class="pagination-wrapper" v-if="total > 0">
        <el-pagination
          v-model:current-page="pageNum"
          v-model:page-size="pageSize"
          :page-sizes="[10, 20, 50, 100]"
          :total="total"
          layout="prev, pager, next, sizes"
          @size-change="handleSizeChange"
          @current-change="handlePageChange"
        />
      </div>
    </div>
  </div>
</template>
<script setup>
import { Bell } from '@element-plus/icons-vue'
import { listMessage, markAsRead, markAllAsRead, confirmMessage, getUnreadCount } from '@/api/system/message'
import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()
const emit = defineEmits(['unreadCountChange'])
const activeTab = ref('unread')
const unreadList = ref([])
const readList = ref([])
const unreadCount = ref(0)
const total = ref(0)
const pageNum = ref(1)
const pageSize = ref(10)
// åŠ è½½æ¶ˆæ¯åˆ—è¡¨
const loadMessages = async () => {
  try {
    const consigneeId = userStore.id
    if (!consigneeId) {
      console.warn('未获取到当前登录用户ID')
      return
    }
    const params = {
      consigneeId: consigneeId,
      current: pageNum.value,
      size: pageSize.value,
      status: activeTab.value === 'read' ? 1 : 0
    }
    const res = await listMessage(params)
    if (res.code === 200) {
      if (activeTab.value === 'unread') {
        unreadList.value = res.data.records || []
      } else {
        readList.value = res.data.records || []
      }
      total.value = res.data.total || 0
    }
  } catch (error) {
    console.error('加载消息列表失败:', error)
  }
}
// åŠ è½½æœªè¯»æ•°é‡
const loadUnreadCount = async () => {
  try {
    const consigneeId = userStore.id
    if (!consigneeId) {
      console.warn('未获取到当前登录用户ID')
      return
    }
    const res = await getUnreadCount(consigneeId)
    if (res.code === 200) {
      unreadCount.value = res.data || 0
      emit('unreadCountChange', unreadCount.value)
    }
  } catch (error) {
    console.error('加载未读数量失败:', error)
  }
}
// æ ‡ç­¾é¡µåˆ‡æ¢
const handleTabChange = (tab) => {
  pageNum.value = 1
  loadMessages()
}
// ç¡®è®¤æ¶ˆæ¯
const handleConfirm = async (item) => {
  try {
    console.log('item', item)
    const res = await confirmMessage(item.noticeId, 1)
    if (res.code === 200) {
      ElMessage.success('确认成功')
      // é‡æ–°åŠ è½½æ•°æ®
      loadMessages()
      loadUnreadCount()
      // æ ¹æ® jumpPath è¿›è¡Œé¡µé¢è·³è½¬
      if (item.jumpPath) {
        try {
          // è§£æž jumpPath,分离路径和查询参数
          const [path, queryString] = item.jumpPath.split('?')
          let query = {}
          if (queryString) {
            // è§£æžæŸ¥è¯¢å‚æ•°
            queryString.split('&').forEach(param => {
              const [key, value] = param.split('=')
              if (key && value) {
                query[key] = decodeURIComponent(value)
              }
            })
          }
          // è·³è½¬åˆ°æŒ‡å®šé¡µé¢
          router.push({
            path: path,
            query: query
          })
        } catch (error) {
          console.error('页面跳转失败:', error)
        }
      }
    }
  } catch (error) {
    console.error('确认消息失败:', error)
    ElMessage.error('确认失败')
  }
}
// ä¸€é”®å·²è¯»
const handleMarkAllAsRead = async () => {
  try {
    const res = await markAllAsRead()
    if (res.code === 200) {
      ElMessage.success('已全部标记为已读')
      loadMessages()
      loadUnreadCount()
    }
  } catch (error) {
    console.error('一键已读失败:', error)
    ElMessage.error('操作失败')
  }
}
// åˆ†é¡µå¤§å°æ”¹å˜
const handleSizeChange = (size) => {
  pageSize.value = size
  pageNum.value = 1
  loadMessages()
}
// é¡µç æ”¹å˜
const handlePageChange = (page) => {
  pageNum.value = page
  loadMessages()
}
// ç»„件挂载时加载未读数量
onMounted(() => {
  loadUnreadCount()
})
// ç›‘听父组件传递的 visible çŠ¶æ€ï¼ˆé€šè¿‡ watch åœ¨ Navbar ä¸­å¤„理)
// è¿™é‡Œåªè´Ÿè´£æ•°æ®åŠ è½½ï¼Œä¸æŽ§åˆ¶æ˜¾ç¤º
// æš´éœ²æ–¹æ³•供外部调用
defineExpose({
  loadUnreadCount,
  loadMessages
})
</script>
<style lang="scss" scoped>
.notification-popover-content {
  display: flex;
  flex-direction: column;
  width: 500px;
  padding: 16px;
}
.popover-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #f0f0f0;
  .popover-title {
    font-size: 18px;
    font-weight: 500;
    color: #303133;
  }
}
.notification-content {
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  :deep(.el-tabs) {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    .el-tabs__header {
      margin-bottom: 0;
      flex-shrink: 0;
      padding: 0;
    }
    .el-tabs__content {
      flex: 1;
      overflow-y: auto;
      min-height: 0;
      padding-top: 16px;
    }
    .el-tab-pane {
      height: 100%;
    }
  }
}
.empty-state {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 300px;
  padding: 40px 0;
}
.notification-list {
  .notification-item {
    display: flex;
    padding: 12px 0;
    border-bottom: 1px solid #f0f0f0;
    transition: background-color 0.3s;
    &:hover {
      background-color: #f5f7fa;
    }
    &.read {
      opacity: 0.7;
    }
    .notification-icon {
      flex-shrink: 0;
      width: 40px;
      height: 40px;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #f0f9ff;
      border-radius: 50%;
      margin-right: 12px;
    }
    .notification-content-wrapper {
      flex: 1;
      min-width: 0;
      .notification-title {
        font-size: 14px;
        font-weight: 500;
        color: #303133;
        margin-bottom: 8px;
      }
      .notification-detail {
        font-size: 13px;
        color: #606266;
        line-height: 1.5;
        margin-bottom: 8px;
        word-break: break-all;
      }
      .notification-time {
        font-size: 12px;
        color: #909399;
      }
    }
    .notification-action {
      flex-shrink: 0;
      margin-left: 12px;
      display: flex;
      align-items: center;
    }
  }
}
.pagination-wrapper {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid #f0f0f0;
  display: flex;
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}
</style>
src/layout/components/Sidebar/Logo.vue
@@ -3,11 +3,11 @@
    <transition name="sidebarLogoFade">
      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
        <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>
@@ -126,4 +126,4 @@
    }
  }
}
</style>
</style>
src/layout/components/Sidebar/index.vue
@@ -30,7 +30,6 @@
const sideTheme = computed(() => settingsStore.sideTheme)
const theme = computed(() => settingsStore.theme)
const isCollapse = computed(() => !appStore.sidebar.opened)
console.log(44444, settingsStore.isDark, sideTheme.value)
// èŽ·å–èœå•èƒŒæ™¯è‰²
const getMenuBackground = computed(() => {
src/layout/components/index.js
@@ -2,3 +2,4 @@
export { default as Navbar } from './Navbar'
export { default as Settings } from './Settings'
export { default as TagsView } from './TagsView/index.vue'
export { default as NotificationCenter } from './NotificationCenter/index.vue'
src/main.js
@@ -1,113 +1,116 @@
import { createApp } from "vue";
import Cookies from "js-cookie";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "element-plus/theme-chalk/dark/css-vars.css";
import locale from "element-plus/es/locale/lang/zh-cn";
import "@/assets/styles/index.scss"; // global css
import App from "./App";
import store from "./store";
import router from "./router";
import directive from "./directive"; // directive
// æ³¨å†ŒæŒ‡ä»¤
import plugins from "./plugins"; // plugins
import { download } from "@/utils/request";
// svg图标
import "virtual:svg-icons-register";
import SvgIcon from "@/components/SvgIcon";
import elementIcons from "@/components/SvgIcon/svgicon";
import "./assets/fonts/font.css";
import "./permission"; // permission control
import { useDict } from "@/utils/dict";
import {
  parseTime,
  resetForm,
  addDateRange,
  handleTree,
  selectDictLabel,
  selectDictLabels,
} from "@/utils/ruoyi";
// åˆ†é¡µç»„ä»¶
import Pagination from "@/components/Pagination";
// è‡ªå®šä¹‰è¡¨æ ¼å·¥å…·ç»„ä»¶
import RightToolbar from "@/components/RightToolbar";
// å¯Œæ–‡æœ¬ç»„ä»¶
import Editor from "@/components/Editor";
// æ–‡ä»¶ä¸Šä¼ ç»„ä»¶
import FileUpload from "@/components/FileUpload";
// å›¾ç‰‡ä¸Šä¼ ç»„ä»¶
import ImageUpload from "@/components/ImageUpload";
// å›¾ç‰‡é¢„览组件
import ImagePreview from "@/components/ImagePreview";
// å­—典标签组件
import DictTag from "@/components/DictTag";
// è¡¨æ ¼ç»„ä»¶
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { getToken } from "@/utils/auth";
import {
  calculateTaxExclusiveTotalPrice,
  summarizeTable,
  calculateTaxIncludeTotalPrice,
} from "@/utils/summarizeTable.js";
const app = createApp(App);
// å…¨å±€æ–¹æ³•挂载
app.config.globalProperties.useDict = useDict;
app.config.globalProperties.download = download;
app.config.globalProperties.parseTime = parseTime;
app.config.globalProperties.resetForm = resetForm;
app.config.globalProperties.summarizeTable = summarizeTable;
app.config.globalProperties.calculateTaxExclusiveTotalPrice =
  calculateTaxExclusiveTotalPrice;
app.config.globalProperties.calculateTaxIncludeTotalPrice =
  calculateTaxIncludeTotalPrice;
app.config.globalProperties.handleTree = handleTree;
app.config.globalProperties.addDateRange = addDateRange;
app.config.globalProperties.selectDictLabel = selectDictLabel;
app.config.globalProperties.selectDictLabels = selectDictLabels;
app.config.globalProperties.javaApi = "http://114.132.189.42:7004";
app.config.globalProperties.HaveJson = (val) => {
  return JSON.parse(JSON.stringify(val));
};
app.config.globalProperties.uploadHeader = {
  Authorization: "Bearer " + getToken(),
};
// å…¨å±€ç»„件挂载
app.component("DictTag", DictTag);
app.component("Pagination", Pagination);
app.component("FileUpload", FileUpload);
app.component("ImageUpload", ImageUpload);
app.component("ImagePreview", ImagePreview);
app.component("RightToolbar", RightToolbar);
app.component("Editor", Editor);
app.component("PIMTable", PIMTable);
app.use(router);
app.use(store);
app.use(plugins);
app.use(elementIcons);
app.component("svg-icon", SvgIcon);
directive(app);
// ä½¿ç”¨element-plus å¹¶ä¸”设置全局的大小
app.use(ElementPlus, {
  locale: locale,
  // æ”¯æŒ large、default、small
  size: Cookies.get("size") || "default",
});
app._context.components.ElDialog.props.closeOnClickModal.default = false;
app.mount("#app");
import { createApp } from "vue";
import Cookies from "js-cookie";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "element-plus/theme-chalk/dark/css-vars.css";
import locale from "element-plus/es/locale/lang/zh-cn";
import "@/assets/styles/index.scss"; // global css
import App from "./App";
import store from "./store";
import router from "./router";
import directive from "./directive"; // directive
// æ³¨å†ŒæŒ‡ä»¤
import plugins from "./plugins"; // plugins
import { download } from "@/utils/request";
// svg图标
import "virtual:svg-icons-register";
import SvgIcon from "@/components/SvgIcon";
import elementIcons from "@/components/SvgIcon/svgicon";
import "./assets/fonts/font.css";
import "./permission"; // permission control
import { useDict } from "@/utils/dict";
import {
  parseTime,
  resetForm,
  addDateRange,
  handleTree,
  selectDictLabel,
  selectDictLabels,
} from "@/utils/ruoyi";
// åˆ†é¡µç»„ä»¶
import Pagination from "@/components/Pagination";
// è‡ªå®šä¹‰è¡¨æ ¼å·¥å…·ç»„ä»¶
import RightToolbar from "@/components/RightToolbar";
// å¯Œæ–‡æœ¬ç»„ä»¶
import Editor from "@/components/Editor";
// æ–‡ä»¶ä¸Šä¼ ç»„ä»¶
import FileUpload from "@/components/FileUpload";
// å›¾ç‰‡ä¸Šä¼ ç»„ä»¶
import ImageUpload from "@/components/ImageUpload";
// å›¾ç‰‡é¢„览组件
import ImagePreview from "@/components/ImagePreview";
// å­—典标签组件
import DictTag from "@/components/DictTag";
// è¡¨æ ¼ç»„ä»¶
import PIMTable from "@/components/PIMTable/PIMTable.vue";
// é¡µé¢å¤´éƒ¨ç»„ä»¶
import PageHeader from "@/components/PageHeader/index.vue";
import { getToken } from "@/utils/auth";
import {
  calculateTaxExclusiveTotalPrice,
  summarizeTable,
  calculateTaxIncludeTotalPrice,
} from "@/utils/summarizeTable.js";
const app = createApp(App);
// å…¨å±€æ–¹æ³•挂载
app.config.globalProperties.useDict = useDict;
app.config.globalProperties.download = download;
app.config.globalProperties.parseTime = parseTime;
app.config.globalProperties.resetForm = resetForm;
app.config.globalProperties.summarizeTable = summarizeTable;
app.config.globalProperties.calculateTaxExclusiveTotalPrice =
  calculateTaxExclusiveTotalPrice;
app.config.globalProperties.calculateTaxIncludeTotalPrice =
  calculateTaxIncludeTotalPrice;
app.config.globalProperties.handleTree = handleTree;
app.config.globalProperties.addDateRange = addDateRange;
app.config.globalProperties.selectDictLabel = selectDictLabel;
app.config.globalProperties.selectDictLabels = selectDictLabels;
app.config.globalProperties.javaApi = "http://127.0.0.1:7003";
app.config.globalProperties.HaveJson = (val) => {
  return JSON.parse(JSON.stringify(val));
};
app.config.globalProperties.uploadHeader = {
  Authorization: "Bearer " + getToken(),
};
// å…¨å±€ç»„件挂载
app.component("DictTag", DictTag);
app.component("Pagination", Pagination);
app.component("FileUpload", FileUpload);
app.component("ImageUpload", ImageUpload);
app.component("ImagePreview", ImagePreview);
app.component("RightToolbar", RightToolbar);
app.component("Editor", Editor);
app.component("PIMTable", PIMTable);
app.component("PageHeader", PageHeader);
app.use(router);
app.use(store);
app.use(plugins);
app.use(elementIcons);
app.component("svg-icon", SvgIcon);
directive(app);
// ä½¿ç”¨element-plus å¹¶ä¸”设置全局的大小
app.use(ElementPlus, {
  locale: locale,
  // æ”¯æŒ large、default、small
  size: Cookies.get("size") || "default",
});
app._context.components.ElDialog.props.closeOnClickModal.default = false;
app.mount("#app");
src/permission.js
@@ -11,7 +11,7 @@
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register', '/callbacklccpn']
const whiteList = ['/login', '/register', '/callbacklccpn','/device-info']
const isWhiteList = (path) => {
  return whiteList.some(pattern => isPathMatch(pattern, path))
src/router/index.js
@@ -1,6 +1,6 @@
import { createWebHistory, createRouter } from 'vue-router'
import { createWebHistory, createRouter } from "vue-router";
/* Layout */
import Layout from '@/layout'
import Layout from "@/layout";
/**
 * Note: è·¯ç”±é…ç½®é¡¹
@@ -16,180 +16,181 @@
 * roles: ['admin', 'common']       // è®¿é—®è·¯ç”±çš„角色权限
 * permissions: ['a:a:a', 'b:b:b']  // è®¿é—®è·¯ç”±çš„菜单权限
 * meta : {
    noCache: true                   // å¦‚果设置为true,则不会被 <keep-alive> ç¼“å­˜(默认 false)
    title: 'title'                  // è®¾ç½®è¯¥è·¯ç”±åœ¨ä¾§è¾¹æ å’Œé¢åŒ…屑中展示的名字
    icon: 'svg-name'                // è®¾ç½®è¯¥è·¯ç”±çš„图标,对应路径src/assets/icons/svg
    breadcrumb: false               // å¦‚果设置为false,则不会在breadcrumb面包屑中显示
    activeMenu: '/system/user'      // å½“路由设置了该属性,则会高亮相对应的侧边栏。
  }
 noCache: true                   // å¦‚果设置为true,则不会被 <keep-alive> ç¼“å­˜(默认 false)
 title: 'title'                  // è®¾ç½®è¯¥è·¯ç”±åœ¨ä¾§è¾¹æ å’Œé¢åŒ…屑中展示的名字
 icon: 'svg-name'                // è®¾ç½®è¯¥è·¯ç”±çš„图标,对应路径src/assets/icons/svg
 breadcrumb: false               // å¦‚果设置为false,则不会在breadcrumb面包屑中显示
 activeMenu: '/system/user'      // å½“路由设置了该属性,则会高亮相对应的侧边栏。
 }
 */
// å…¬å…±è·¯ç”±
export const constantRoutes = [
  {
    path: '/redirect',
    path: "/redirect",
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index.vue')
      }
    ]
        path: "/redirect/:path(.*)",
        component: () => import("@/views/redirect/index.vue"),
      },
    ],
  },
  {
    path: '/login',
    component: () => import('@/views/login'),
    hidden: true
  },
  {
    path: "/callbacklccpn",
    component: () => import("@/views/tideLogin.vue"),
    path: "/login",
    component: () => import("@/views/login"),
    hidden: true,
  },
  {
    path: '/register',
    component: () => import('@/views/register'),
    hidden: true
    path: "/register",
    component: () => import("@/views/register"),
    hidden: true,
  },
  {
    path: "/:pathMatch(.*)*",
    component: () => import('@/views/error/404'),
    hidden: true
    component: () => import("@/views/error/404"),
    hidden: true,
  },
  {
    path: '/401',
    component: () => import('@/views/error/401'),
    hidden: true
    path: "/401",
    component: () => import("@/views/error/401"),
    hidden: true,
  },
  {
    path: '',
    path: "",
    component: Layout,
    redirect: '/index',
    redirect: "/index",
    children: [
      {
        path: '/index',
        component: () => import('@/views/index'),
        name: 'Index',
        meta: { title: '首页', icon: 'dashboard', affix: true }
      }
    ]
        path: "/index",
        component: () => import("@/views/index"),
        name: "Index",
        meta: { title: "首页", icon: "dashboard", affix: true },
      },
    ],
  },
  {
    path: '/user',
    path: "/user",
    component: Layout,
    hidden: true,
    redirect: 'noredirect',
    redirect: "noredirect",
    children: [
      {
        path: 'profile',
        component: () => import('@/views/system/user/profile/index'),
        name: 'Profile',
        meta: { title: '个人中心', icon: 'user' }
      }
    ]
  }
]
        path: "profile",
        component: () => import("@/views/system/user/profile/index"),
        name: "Profile",
        meta: { title: "个人中心", icon: "user" },
      },
    ],
  },
  {
    path: "/device-info",
    component: () => import("@/views/equipmentManagement/deviceInfo/index.vue"),
    hidden: true,
    name: "DeviceInfo",
    meta: { title: "设备信息", icon: "monitor" },
  },
  // æ·»åŠ é¡¹ç›®è¯¦æƒ…é¡µé¢è·¯ç”±é…ç½®
  {
    path: "/oaSystem/projectManagement/projectDetail",
    component: Layout,
    hidden: true,
    children: [
      {
        path: ":projectId",
        component: () => import("@/views/oaSystem/projectManagement/projectDetail.vue"),
        name: "ProjectDetail",
        meta: { title: "项目详情", activeMenu: "/oaSystem/projectManagement" },
      },
    ],
  },
];
// åŠ¨æ€è·¯ç”±ï¼ŒåŸºäºŽç”¨æˆ·æƒé™åŠ¨æ€åŽ»åŠ è½½
export const dynamicRoutes = [
  {
    path: '/system/user-auth',
    path: "/system/user-auth",
    component: Layout,
    hidden: true,
    permissions: ['system:user:edit'],
    permissions: ["system:user:edit"],
    children: [
      {
        path: 'role/:userId(\\d+)',
        component: () => import('@/views/system/user/authRole'),
        name: 'AuthRole',
        meta: { title: '分配角色', activeMenu: '/system/user' }
      }
    ]
        path: "role/:userId(\\d+)",
        component: () => import("@/views/system/user/authRole"),
        name: "AuthRole",
        meta: { title: "分配角色", activeMenu: "/system/user" },
      },
    ],
  },
  {
    path: '/main/MobileChat',
    path: "/system/role-auth",
    component: Layout,
    redirect: '',
    hidden: true,
    permissions: ['MobileChat:edit'],
    permissions: ["system:role:edit"],
    children: [
      {
        path: '',
        component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
        name: 'MobileChat',
        meta: { title: 'AI对话', activeMenu: '/chatHome/chatHomeIndex'}
      }
    ]
        path: "user/:roleId(\\d+)",
        component: () => import("@/views/system/role/authUser"),
        name: "AuthUser",
        meta: { title: "分配用户", activeMenu: "/system/role" },
      },
    ],
  },
  {
    path: '/system/role-auth',
    path: "/system/dict-data",
    component: Layout,
    hidden: true,
    permissions: ['system:role:edit'],
    permissions: ["system:dict:list"],
    children: [
      {
        path: 'user/:roleId(\\d+)',
        component: () => import('@/views/system/role/authUser'),
        name: 'AuthUser',
        meta: { title: '分配用户', activeMenu: '/system/role' }
      }
    ]
        path: "index/:dictId(\\d+)",
        component: () => import("@/views/system/dict/data"),
        name: "Data",
        meta: { title: "字典数据", activeMenu: "/system/dict" },
      },
    ],
  },
  {
    path: '/system/dict-data',
    path: "/monitor/job-log",
    component: Layout,
    hidden: true,
    permissions: ['system:dict:list'],
    permissions: ["monitor:job:list"],
    children: [
      {
        path: 'index/:dictId(\\d+)',
        component: () => import('@/views/system/dict/data'),
        name: 'Data',
        meta: { title: '字典数据', activeMenu: '/system/dict' }
      }
    ]
        path: "index/:jobId(\\d+)",
        component: () => import("@/views/monitor/job/log"),
        name: "JobLog",
        meta: { title: "调度日志", activeMenu: "/monitor/job" },
      },
    ],
  },
  {
    path: '/monitor/job-log',
    path: "/tool/gen-edit",
    component: Layout,
    hidden: true,
    permissions: ['monitor:job:list'],
    permissions: ["tool:gen:edit"],
    children: [
      {
        path: 'index/:jobId(\\d+)',
        component: () => import('@/views/monitor/job/log'),
        name: 'JobLog',
        meta: { title: '调度日志', activeMenu: '/monitor/job' }
      }
    ]
        path: "index/:tableId(\\d+)",
        component: () => import("@/views/tool/gen/editTable"),
        name: "GenEdit",
        meta: { title: "修改生成配置", activeMenu: "/tool/gen" },
      },
    ],
  },
  {
    path: '/tool/gen-edit',
    component: Layout,
    hidden: true,
    permissions: ['tool:gen:edit'],
    children: [
      {
        path: 'index/:tableId(\\d+)',
        component: () => import('@/views/tool/gen/editTable'),
        name: 'GenEdit',
        meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
      }
    ]
  }
]
];
const router = createRouter({
  history: createWebHistory(),
  routes: constantRoutes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
      return savedPosition;
    }
    return { top: 0 }
    return { top: 0 };
  },
})
});
export default router
export default router;
src/utils/request.js
@@ -17,7 +17,7 @@
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: import.meta.env.VITE_APP_BASE_API,
  // è¶…æ—¶
  timeout: 60000
  timeout: 160000
})
// request拦截器
src/utils/util.js
@@ -1,4 +1,6 @@
//防抖
import dayjs from "dayjs";
export  function debounce(fn) {
    console.log(1)
    let t = null //只会执行一次
@@ -86,7 +88,34 @@
      'aplication/zip': 'zpi',
    }
  }
 export const deepCopySameProperties = (source, target) =>{
    for (const key in source) {
        if (target.hasOwnProperty(key)) {
            if (typeof source[key] === 'object' && source[key] !== null &&
                typeof target[key] === 'object' && target[key] !== null) {
                // é€’归处理对象
                deepCopySameProperties(source[key], target[key]);
            } else {
                // åŸºæœ¬ç±»åž‹ç›´æŽ¥èµ‹å€¼
                target[key] = source[key];
            }
        }
    }
    return target;
}
  export function filterArr(arr) {
      return arr.filter(item => item.flag !== false);
  }
  }
 export function getCurrentMonth () {
    let month = dayjs().month() + 1
    if (month <= 3) {
        return '1';
    } else if (month <= 6) {
        return '2';
    } else if (month <= 9) {
        return '3';
    } else if (month <= 12) {
        return '4';
    }
}
src/views/basicData/customerFile/index.vue
@@ -5,12 +5,23 @@
        <span class="search_title">客户名称:</span>
        <el-input
          v-model="searchForm.customerName"
          style="width: 240px"
          style="width: 240px;margin-right: 10px"
          placeholder="请输入"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
         <span class="search_title">客户分类:</span>
         <el-select
          v-model="searchForm.customerType"
          placeholder="请选择"
          style="width: 240px"
          clearable
           @change="handleQuery"
        >
           <el-option  label="零售客户" value="零售客户" />
           <el-option  label="进销商客户" value="进销商客户" />
        </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
          >搜索</el-button
        >
@@ -122,6 +133,14 @@
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户分类:" prop="customerType">
               <el-select  v-model="form.customerType" placeholder="请选择" clearable>
                 <el-option  label="零售客户" value="零售客户" />
                 <el-option  label="进销商客户" value="进销商客户" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
                <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index">
                    <el-col :span="12">
@@ -169,7 +188,6 @@
                type="date"
                placeholder="请选择"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
@@ -209,13 +227,13 @@
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入xls、xlsx格式文件。</span>
<!--            <el-link-->
<!--              type="primary"-->
<!--              :underline="false"-->
<!--              style="font-size: 12px; vertical-align: baseline"-->
<!--              @click="importTemplate"-->
<!--              >下载模板</el-link-->
<!--            >-->
            <el-link
              type="primary"
              :underline="false"
              style="font-size: 12px; vertical-align: baseline"
              @click="importTemplate"
              >下载模板</el-link
            >
          </div>
        </template>
      </el-upload>
@@ -247,6 +265,11 @@
const userStore = useUserStore();
const tableColumn = ref([
  {
    label: "客户分类",
    prop: "customerType",
    width: 120,
  },
  {
    label: "客户名称",
    prop: "customerName",
@@ -307,9 +330,6 @@
        clickFun: (row) => {
          openForm("edit", row);
        },
                disabled: (row) => {
                    return row.maintainer !== userStore.nickName
                }
      },
    ],
  },
@@ -339,6 +359,7 @@
const data = reactive({
  searchForm: {
    customerName: "",
    customerType: "",
  },
  form: {
    customerName: "",
@@ -352,6 +373,7 @@
    basicBankAccount: "",
    bankAccount: "",
    bankCode: "",
    customerType: "",
  },
  rules: {
    customerName: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -369,6 +391,7 @@
    basicBankAccount: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccount: [{ required: true, message: "请输入", trigger: "blur" }],
    bankCode: [{ required: true, message: "请输入", trigger: "blur" }],
    customerType: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const upload = reactive({
@@ -399,8 +422,12 @@
  // æ–‡ä»¶ä¸Šä¼ æˆåŠŸæ—¶çš„å›žè°ƒ
  onSuccess: (response, file, fileList) => {
    console.log('上传成功', response, file, fileList);
    upload.isUploading = false;
    if(response.code === 200){
      proxy.$modal.msgSuccess("文件上传成功");
      upload.open = false;
      proxy.$refs["uploadRef"].clearFiles();
      getList();
    }else if(response.code === 500){
      proxy.$modal.msgError(response.msg);
    }else{
@@ -410,6 +437,7 @@
  // æ–‡ä»¶ä¸Šä¼ å¤±è´¥æ—¶çš„回调
  onError: (error, file, fileList) => {
    console.error('上传失败', error, file, fileList);
    upload.isUploading = false;
    proxy.$modal.msgError("文件上传失败");
  },
  // æ–‡ä»¶ä¸Šä¼ è¿›åº¦å›žè°ƒ
@@ -455,6 +483,7 @@
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
  upload.isUploading = true;
  proxy.$refs["uploadRef"].submit();
}
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
@@ -462,6 +491,10 @@
  upload.title = "客户导入";
  upload.open = true;
}
/** ä¸‹è½½æ¨¡æ¿ */
function importTemplate() {
  proxy.download("/basic/customer/downloadTemplate", {}, "客户导入模板.xlsx");
}
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
src/views/basicData/product/ProductSelectDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,180 @@
<template>
  <el-dialog v-model="visible" title="选择产品" width="900px" destroy-on-close :close-on-click-modal="false">
    <el-form :inline="true" :model="query" class="mb-2">
      <el-form-item label="产品大类">
        <el-input v-model="query.productName" placeholder="输入产品大类" clearable @keyup.enter="onSearch" />
      </el-form-item>
      <el-form-item label="型号名称">
        <el-input v-model="query.model" placeholder="输入型号名称" clearable @keyup.enter="onSearch" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button @click="onReset">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- åˆ—表 -->
    <el-table ref="tableRef" v-loading="loading" :data="tableData" height="420" highlight-current-row row-key="id"
      @selection-change="handleSelectionChange" @select="handleSelect">
      <el-table-column type="selection" width="55" />
      <el-table-column type="index" label="序号" width="60" />
      <el-table-column prop="productName" label="产品大类" min-width="160" />
      <el-table-column prop="model" label="型号名称" min-width="200" />
      <el-table-column prop="unit" label="单位" min-width="160" />
    </el-table>
    <div class="mt-3 flex justify-end">
      <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
        v-model:page-size="page.pageSize" v-model:current-page="page.pageNum" :page-sizes="[10, 20, 50, 100]"
        @size-change="onPageChange" @current-change="onPageChange" />
    </div>
    <template #footer>
      <el-button @click="close()">取消</el-button>
      <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm">
        ç¡®å®š
      </el-button>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { productModelList } from '@/api/basicData/productModel'
export type ProductRow = {
  id: number;
  productName: string;
  model: string;
  unit?: string;
};
const props = defineProps<{
  modelValue: boolean;
  single?: boolean; // æ˜¯å¦åªèƒ½é€‰æ‹©ä¸€ä¸ªï¼Œé»˜è®¤false(可选择多个)
}>();
const emit = defineEmits(['update:modelValue', 'confirm']);
const visible = computed({
  get: () => props.modelValue,
  set: (v) => emit("update:modelValue", v),
});
const query = reactive({
  productName: "",
  model: "",
});
const page = reactive({
  pageNum: 1,
  pageSize: 10,
});
const loading = ref(false);
const tableData = ref<ProductRow[]>([]);
const total = ref(0);
const multipleSelection = ref<ProductRow[]>([]);
const tableRef = ref();
function close() {
  visible.value = false;
}
const handleSelectionChange = (val: ProductRow[]) => {
  if (props.single && val.length > 1) {
    // å¦‚果限制为单个选择,只保留最后一个选中的
    const lastSelected = val[val.length - 1];
    multipleSelection.value = [lastSelected];
    // æ¸…空表格选中状态,然后重新选中最后一个
    nextTick(() => {
      if (tableRef.value) {
        tableRef.value.clearSelection();
        tableRef.value.toggleRowSelection(lastSelected, true);
      }
    });
  } else {
    multipleSelection.value = val;
  }
}
// å¤„理单个选择
const handleSelect = (selection: ProductRow[], row: ProductRow) => {
  if (props.single) {
    // å¦‚果限制为单个,清空其他选择,只保留当前行
    if (selection.includes(row)) {
      // é€‰ä¸­å½“前行时,清空其他选中
      multipleSelection.value = [row];
      nextTick(() => {
        if (tableRef.value) {
          tableData.value.forEach((item) => {
            if (item.id !== row.id) {
              tableRef.value.toggleRowSelection(item, false);
            }
          });
        }
      });
    }
  }
}
function onSearch() {
  page.pageNum = 1;
  loadData();
}
function onReset() {
  query.productName = "";
  query.model = "";
  page.pageNum = 1;
  loadData();
}
function onPageChange() {
  loadData();
}
function onConfirm() {
  if (multipleSelection.value.length === 0) {
    ElMessage.warning("请选择一条产品");
    return;
  }
  if (props.single && multipleSelection.value.length > 1) {
    ElMessage.warning("只能选择一个产品");
    return;
  }
  emit("confirm", props.single ? [multipleSelection.value[0]] : multipleSelection.value);
  close();
}
async function loadData() {
  loading.value = true;
  try {
    multipleSelection.value = []; // ç¿»é¡µ/搜索后清空选择更符合预期
    const res: any = await productModelList({
      productName: query.productName.trim(),
      model: query.model.trim(),
      current: page.pageNum,
      size: page.pageSize,
    });
    tableData.value = res.records;
    total.value = res.total;
  } finally {
    loading.value = false;
  }
}
// ç›‘听弹窗打开,重置选择
watch(() => props.modelValue, (visible) => {
  if (visible) {
    multipleSelection.value = [];
  }
});
onMounted(() => {
  loadData()
})
</script>
src/views/basicData/product/index.vue
@@ -25,9 +25,7 @@
          :data="list"
          @node-click="handleNodeClick"
          :expand-on-click-node="false"
          default-expand-all
          :default-expanded-keys="expandedKeys"
          :draggable="true"
          :filter-node-method="filterNode"
          :props="{ children: 'children', label: 'label' }"
          highlight-current
src/views/basicData/supplierManage/components/BlacklistTab.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,564 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">供应商档案:</span>
        <el-input
            v-model="searchForm.supplierName"
            style="width: 240px"
            placeholder="输入供应商名称搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
      </div>
      <div>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
      ></PIMTable>
    </div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增供应商信息' : '编辑供应商信息'"
        width="70%"
        @close="closeDia"
    >
      <el-form
          :model="form"
          label-width="140px"
          label-position="top"
          :rules="rules"
          ref="formRef"
      >
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                  v-model="form.supplierName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
                label="纳税人识别号:"
                prop="taxpayerIdentificationNum"
            >
              <el-input
                  v-model="form.taxpayerIdentificationNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公司地址:" prop="companyAddress">
              <el-input
                  v-model="form.companyAddress"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公司电话:" prop="companyPhone">
              <el-input
                  v-model="form.companyPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="开户行:" prop="bankAccountName">
              <el-input
                  v-model="form.bankAccountName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="账号:" prop="bankAccountNum">
              <el-input
                  v-model="form.bankAccountNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="联系人:" prop="contactUserName">
              <el-input
                  v-model="form.contactUserName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话:" prop="contactUserPhone">
              <el-input
                  v-model="form.contactUserPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintainUserId">
              <el-select
                  v-model="form.maintainUserId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                    v-for="item in userList"
                    :key="item.nickName"
                    :label="item.nickName"
                    :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护时间:" prop="maintainTime">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.maintainTime"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="是否白名单:" prop="isWhite">
              <el-select v-model="form.isWhite" placeholder="请选择" clearable>
                <el-option label="是" :value="0" />
                <el-option label="否" :value="1" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ä¾›åº”商导入对话框 -->
    <el-dialog
        :title="upload.title"
        v-model="upload.open"
        width="400px"
        append-to-body
    >
      <el-upload
          ref="uploadRef"
          :limit="1"
          accept=".xlsx, .xls"
          :headers="upload.headers"
          :action="upload.url + '?updateSupport=' + upload.updateSupport"
          :disabled="upload.isUploading"
          :on-progress="handleFileUploadProgress"
          :on-success="handleFileSuccess"
          :on-error="handleFileError"
          :auto-upload="false"
          drag
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入xls、xlsx格式文件。</span>
            <el-link
                type="primary"
                :underline="false"
                style="font-size: 12px; vertical-align: baseline"
                @click="importTemplate"
            >下载模板</el-link
            >
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <files-dia ref="filesDia"></files-dia>
  </div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { delSupplier } from "@/api/basicData/supplierManageFile.js";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import {
  addSupplier,
  getSupplier,
  listSupplier,
  updateSupplier,
} from "@/api/basicData/supplierManageFile.js";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth.js";
import FilesDia from "../filesDia.vue";
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const tableColumn = ref([
  {
    label: "供应商名称",
    prop: "supplierName",
    width: 250,
  },
  {
    label: "纳税人识别号",
    prop: "taxpayerIdentificationNum",
    width: 230,
  },
  {
    label: "公司地址",
    prop: "companyAddress",
    width: 220,
  },
  {
    label: "联系方式",
    prop: "companyPhone",
    width:150
  },
  {
    label: "开户行",
    prop: "bankAccountName",
    width: 220,
  },
  {
    label: "账号",
    prop: "bankAccountNum",
    width: 220,
  },
  {
    label: "联系人",
    prop: "contactUserName",
  },
  {
    label: "联系电话",
    prop: "contactUserPhone",
    width: 150,
  },
  {
    label: "维护人",
    prop: "maintainUserName",
  },
  {
    label: "维护时间",
    prop: "maintainTime",
    width:100
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
      },
      {
        //资质附件
        name: "资质文件",
        type: "text",
        clickFun: (row) => {
          openFilesFormDia(row)
        }
      }
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const userList = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const filesDia = ref()
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "",
  },
  form: {
    supplierName: "",
    taxpayerIdentificationNum: "",
    companyAddress: "",
    companyPhone: "",
    bankAccountName: "",
    bankAccountNum: "",
    contactUserName: "",
    contactUserPhone: "",
    maintainUserId: "",
    maintainTime: "",
    isWhite: "",
  },
  rules: {
    supplierName: [{ required: true, message: "请输入", trigger: "blur" }],
    taxpayerIdentificationNum: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountName: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountNum: [{ required: true, message: "请输入", trigger: "blur" }],
    contactUserName: [{ required: false, message: "请输入", trigger: "blur" }],
    contactUserPhone: [{ required: false, message: "请输入", trigger: "blur" }],
    maintainUserId: [{ required: false, message: "请选择", trigger: "change" }],
    maintainTime: [{ required: false, message: "请选择", trigger: "change" }],
  },
});
const { searchForm, form, rules } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
  upload.isUploading = true;
  proxy.$refs["uploadRef"].submit();
}
const getList = () => {
  tableLoading.value = true;
  listSupplier({ ...searchForm.value, ...page, isWhite: 1 }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    page.total = res.data.total;
  });
};
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(供应商导入)
  open: false,
  // å¼¹å‡ºå±‚标题(供应商导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
  updateSupport: 1,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
});
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
  upload.title = "供应商导入";
  upload.open = true;
}
/** ä¸‹è½½æ¨¡æ¿ */
function importTemplate() {
  proxy.download("/system/supplier/downloadTemplate", {}, "供应商导入模板.xlsx");
}
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (response, file, fileList) => {
  upload.isUploading = false;
  if(response.code === 200){
    proxy.$modal.msgSuccess("文件上传成功");
    upload.open = false;
    proxy.$refs["uploadRef"].clearFiles();
    getList();
  }else if(response.code === 500){
    proxy.$modal.msgError(response.msg);
  }else{
    proxy.$modal.msgWarning(response.msg);
  }
};
/** æ–‡ä»¶ä¸Šä¼ å¤±è´¥å¤„理 */
const handleFileError = (error, file, fileList) => {
  upload.isUploading = false;
  proxy.$modal.msgError("文件上传失败");
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
  form.value = {};
  form.value.maintainUserId = userStore.id;
  form.value.maintainTime = getCurrentDate();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  if (type === "edit") {
    getSupplier(row.id).then((res) => {
      form.value = { ...res.data };
    });
  }
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
      } else {
        submitAdd();
      }
    }
  });
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        proxy.download("/system/supplier/export", {}, "供应商档案.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
    const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
    if (unauthorizedData.length > 0) {
      proxy.$modal.msgWarning("不可删除他人维护的数据");
      return;
    }
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        tableLoading.value = true;
        delSupplier(ids)
            .then((res) => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .finally(() => {
              tableLoading.value = false;
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  nextTick(() => {
    filesDia.value?.openDialog(row)
  })
};
onMounted(() => {
  getList();
});
</script>
src/views/basicData/supplierManage/components/HomeTab.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,569 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">供应商档案:</span>
        <el-input
            v-model="searchForm.supplierName"
            style="width: 240px"
            placeholder="输入供应商名称搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')"
        >新增供应商</el-button
        >
        <el-button @click="handleOut">导出</el-button>
        <el-button type="info" plain icon="Upload" @click="handleImport"
        >导入</el-button
        >
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
      ></PIMTable>
    </div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增供应商信息' : '编辑供应商信息'"
        width="70%"
        @close="closeDia"
    >
      <el-form
          :model="form"
          label-width="140px"
          label-position="top"
          :rules="rules"
          ref="formRef"
      >
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                  v-model="form.supplierName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
                label="纳税人识别号:"
                prop="taxpayerIdentificationNum"
            >
              <el-input
                  v-model="form.taxpayerIdentificationNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公司地址:" prop="companyAddress">
              <el-input
                  v-model="form.companyAddress"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公司电话:" prop="companyPhone">
              <el-input
                  v-model="form.companyPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="开户行:" prop="bankAccountName">
              <el-input
                  v-model="form.bankAccountName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="账号:" prop="bankAccountNum">
              <el-input
                  v-model="form.bankAccountNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="联系人:" prop="contactUserName">
              <el-input
                  v-model="form.contactUserName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话:" prop="contactUserPhone">
              <el-input
                  v-model="form.contactUserPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintainUserId">
              <el-select
                  v-model="form.maintainUserId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                    v-for="item in userList"
                    :key="item.nickName"
                    :label="item.nickName"
                    :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护时间:" prop="maintainTime">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.maintainTime"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="是否白名单:" prop="isWhite">
              <el-select v-model="form.isWhite" placeholder="请选择" clearable>
                <el-option label="是" :value="0" />
                <el-option label="否" :value="1" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ä¾›åº”商导入对话框 -->
    <el-dialog
        :title="upload.title"
        v-model="upload.open"
        width="400px"
        append-to-body
    >
      <el-upload
          ref="uploadRef"
          :limit="1"
          accept=".xlsx, .xls"
          :headers="upload.headers"
          :action="upload.url + '?updateSupport=' + upload.updateSupport"
          :disabled="upload.isUploading"
          :on-progress="handleFileUploadProgress"
          :on-success="handleFileSuccess"
          :on-error="handleFileError"
          :auto-upload="false"
          drag
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入xls、xlsx格式文件。</span>
            <el-link
                type="primary"
                :underline="false"
                style="font-size: 12px; vertical-align: baseline"
                @click="importTemplate"
            >下载模板</el-link
            >
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <files-dia ref="filesDia"></files-dia>
  </div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { delSupplier } from "@/api/basicData/supplierManageFile.js";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import {
  addSupplier,
  getSupplier,
  listSupplier,
  updateSupplier,
} from "@/api/basicData/supplierManageFile.js";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth.js";
import FilesDia from "../filesDia.vue";
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const tableColumn = ref([
  {
    label: "供应商名称",
    prop: "supplierName",
    width: 250,
  },
  {
    label: "纳税人识别号",
    prop: "taxpayerIdentificationNum",
    width: 230,
  },
  {
    label: "公司地址",
    prop: "companyAddress",
    width: 220,
  },
  {
    label: "联系方式",
    prop: "companyPhone",
    width:150
  },
  {
    label: "开户行",
    prop: "bankAccountName",
    width: 220,
  },
  {
    label: "账号",
    prop: "bankAccountNum",
    width: 220,
  },
  {
    label: "联系人",
    prop: "contactUserName",
  },
  {
    label: "联系电话",
    prop: "contactUserPhone",
    width: 150,
  },
  {
    label: "维护人",
    prop: "maintainUserName",
  },
  {
    label: "维护时间",
    prop: "maintainTime",
    width:100
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
      },
      {
        //资质附件
        name: "资质文件",
        type: "text",
        clickFun: (row) => {
          openFilesFormDia(row)
        }
      }
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const userList = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const filesDia = ref()
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "",
  },
  form: {
    supplierName: "",
    taxpayerIdentificationNum: "",
    companyAddress: "",
    companyPhone: "",
    bankAccountName: "",
    bankAccountNum: "",
    contactUserName: "",
    contactUserPhone: "",
    maintainUserId: "",
    maintainTime: "",
    isWhite: "",
  },
  rules: {
    supplierName: [{ required: true, message: "请输入", trigger: "blur" }],
    taxpayerIdentificationNum: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountName: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountNum: [{ required: true, message: "请输入", trigger: "blur" }],
    contactUserName: [{ required: false, message: "请输入", trigger: "blur" }],
    contactUserPhone: [{ required: false, message: "请输入", trigger: "blur" }],
    maintainUserId: [{ required: false, message: "请选择", trigger: "change" }],
    maintainTime: [{ required: false, message: "请选择", trigger: "change" }],
  },
});
const { searchForm, form, rules } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
  upload.isUploading = true;
  proxy.$refs["uploadRef"].submit();
}
const getList = () => {
  tableLoading.value = true;
  listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    page.total = res.data.total;
  });
};
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(供应商导入)
  open: false,
  // å¼¹å‡ºå±‚标题(供应商导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
  updateSupport: 1,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
});
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
  upload.title = "供应商导入";
  upload.open = true;
}
/** ä¸‹è½½æ¨¡æ¿ */
function importTemplate() {
  proxy.download("/system/supplier/downloadTemplate", {}, "供应商导入模板.xlsx");
}
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (response, file, fileList) => {
  upload.isUploading = false;
  if(response.code === 200){
    proxy.$modal.msgSuccess("文件上传成功");
    upload.open = false;
    proxy.$refs["uploadRef"].clearFiles();
    getList();
  }else if(response.code === 500){
    proxy.$modal.msgError(response.msg);
  }else{
    proxy.$modal.msgWarning(response.msg);
  }
};
/** æ–‡ä»¶ä¸Šä¼ å¤±è´¥å¤„理 */
const handleFileError = (error, file, fileList) => {
  upload.isUploading = false;
  proxy.$modal.msgError("文件上传失败");
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
  form.value = {};
  form.value.maintainUserId = userStore.id;
  form.value.maintainTime = getCurrentDate();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  if (type === "edit") {
    getSupplier(row.id).then((res) => {
      form.value = { ...res.data };
    });
  }
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
      } else {
        submitAdd();
      }
    }
  });
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        proxy.download("/system/supplier/export", {}, "供应商档案.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
    const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
    if (unauthorizedData.length > 0) {
      proxy.$modal.msgWarning("不可删除他人维护的数据");
      return;
    }
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        tableLoading.value = true;
        delSupplier(ids)
            .then((res) => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .finally(() => {
              tableLoading.value = false;
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  nextTick(() => {
    filesDia.value?.openDialog(row)
  })
};
onMounted(() => {
  getList();
});
</script>
src/views/basicData/supplierManage/filesDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,203 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="上传附件"
        width="50%"
        @close="closeDia"
    >
      <div style="margin-bottom: 10px;text-align: right">
        <el-upload
            v-model:file-list="fileList"
            class="upload-demo"
            :action="uploadUrl"
            :on-success="handleUploadSuccess"
            :on-error="handleUploadError"
            name="file"
            :show-file-list="false"
            :headers="headers"
            style="display: inline;margin-right: 10px"
        >
          <el-button type="primary">上传附件</el-button>
        </el-upload>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :tableLoading="tableLoading"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          height="500"
          @pagination-change="paginationSearch"
          :total="total"
          :page="page.current"
          :limit="page.size"
      >
      </PIMTable>
            <pagination
                style="margin: 10px 0"
                v-show="total > 0"
                @pagination="paginationSearch"
                :total="total"
                :page="page.current"
                :limit="page.size"
            />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
<script setup>
import {ref} from "vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import filePreview from '@/components/filePreview/index.vue'
import {
  fileAdd,
  fileDel,
  fileListPage
} from "@/api/basicData/supplierManageFile.js";
import Pagination from "@/components/PIMTable/Pagination.vue";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const currentId = ref('')
const selectedRows = ref([]);
const filePreviewRef = ref()
const tableColumn = ref([
  {
    label: "文件名称",
    prop: "name",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    operation: [
      {
        name: "下载",
        type: "text",
        clickFun: (row) => {
          downLoadFile(row);
        },
      },
      {
        name: "预览",
        type: "text",
        clickFun: (row) => {
          lookFile(row);
        },
      }
    ],
  },
]);
const page = reactive({
    current: 1,
    size: 100,
});
const total = ref(0);
const tableData = ref([]);
const fileList = ref([]);
const tableLoading = ref(false);
const headers = ref({
  Authorization: "Bearer " + getToken(),
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // ä¸Šä¼ çš„图片服务器地址
// æ‰“开弹框
const openDialog = (row) => {
  dialogFormVisible.value = true;
  currentId.value = row.id;
  getList()
}
const paginationSearch = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
  fileListPage({supplierId: currentId.value, ...page}).then(res => {
    tableData.value = res.data.records;
        total.value = res.data.total;
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
  emit('close')
};
// ä¸Šä¼ æˆåŠŸå¤„ç†
function handleUploadSuccess(res, file) {
  // å¦‚果上传成功
  if (res.code == 200) {
    const fileRow = {}
    fileRow.name = res.data.originalName
    fileRow.url = res.data.tempPath
    uploadFile(fileRow)
  } else {
    proxy.$modal.msgError("文件上传失败");
  }
}
function uploadFile(file) {
  file.supplierId = currentId.value;
  fileAdd(file).then(res => {
    proxy.$modal.msgSuccess("文件上传成功");
    getList()
  })
}
// ä¸Šä¼ å¤±è´¥å¤„理
function handleUploadError() {
  proxy.$modal.msgError("文件上传失败");
}
// ä¸‹è½½é™„ä»¶
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    fileDel(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
      getList();
    });
  }).catch(() => {
    proxy.$modal.msg("已取消");
  });
};
// é¢„览附件
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/basicData/supplierManage/index.vue
@@ -1,528 +1,47 @@
<!-- åœ¨ä½ çš„主页面中 -->
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">供应商档案:</span>
        <el-input
          v-model="searchForm.supplierName"
          style="width: 240px"
          placeholder="输入供应商名称搜索"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
          >搜索</el-button
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')"
          >新增供应商</el-button
        >
        <el-button @click="handleOut">导出</el-button>
        <el-button type="info" plain icon="Upload" @click="handleImport"
          >导入</el-button
        >
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
                @pagination="pagination"
      ></PIMTable>
    </div>
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增供应商信息' : '编辑供应商信息'"
      width="70%"
      @close="closeDia"
    >
      <el-form
        :model="form"
        label-width="140px"
        label-position="top"
        :rules="rules"
        ref="formRef"
      >
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                v-model="form.supplierName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
              label="纳税人识别号:"
              prop="taxpayerIdentificationNum"
            >
              <el-input
                v-model="form.taxpayerIdentificationNum"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公司地址:" prop="companyAddress">
              <el-input
                v-model="form.companyAddress"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公司电话:" prop="companyPhone">
              <el-input
                v-model="form.companyPhone"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="开户行:" prop="bankAccountName">
              <el-input
                v-model="form.bankAccountName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="账号:" prop="bankAccountNum">
              <el-input
                v-model="form.bankAccountNum"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="联系人:" prop="contactUserName">
              <el-input
                v-model="form.contactUserName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话:" prop="contactUserPhone">
              <el-input
                v-model="form.contactUserPhone"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintainUserId">
              <el-select
                v-model="form.maintainUserId"
                placeholder="请选择"
                clearable
                disabled
              >
                <el-option
                  v-for="item in userList"
                  :key="item.nickName"
                  :label="item.nickName"
                  :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护时间:" prop="maintainTime">
              <el-date-picker
                style="width: 100%"
                v-model="form.maintainTime"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ä¾›åº”商导入对话框 -->
    <el-dialog
      :title="upload.title"
      v-model="upload.open"
      width="400px"
      append-to-body
    >
      <el-upload
        ref="uploadRef"
        :limit="1"
        accept=".xlsx, .xls"
        :headers="upload.headers"
        :action="upload.url + '?updateSupport=' + upload.updateSupport"
        :disabled="upload.isUploading"
        :on-progress="handleFileUploadProgress"
        :on-success="handleFileSuccess"
        :auto-upload="false"
        drag
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入xls、xlsx格式文件。</span>
            <!-- <el-link
              type="primary"
              :underline="false"
              style="font-size: 12px; vertical-align: baseline"
              @click="importTemplate"
              >下载模板</el-link
            > -->
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <el-tabs v-model="activeTab" type="card">
      <el-tab-pane label="正常供应商" name="home">
        <HomeTab />
      </el-tab-pane>
      <el-tab-pane label="黑名单" name="blacklist">
        <BlacklistTab />
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { delSupplier } from "@/api/basicData/supplierManageFile.js";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import {
  addSupplier,
  getSupplier,
  listSupplier,
  updateSupplier,
} from "@/api/basicData/supplierManageFile.js";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth.js";
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
<script>
import HomeTab from './components/HomeTab.vue'
import BlacklistTab from './components/BlacklistTab.vue'
const tableColumn = ref([
  {
    label: "供应商名称",
    prop: "supplierName",
    width: 250,
export default {
  name: 'MainPage',
  components: {
    HomeTab,
    BlacklistTab
  },
  {
    label: "纳税人识别号",
    prop: "taxpayerIdentificationNum",
    width: 230,
  data() {
    return {
      activeTab: 'home'
    }
  },
  {
    label: "公司地址",
    prop: "companyAddress",
    width: 220,
  },
  {
    label: "联系方式",
    prop: "companyPhone",
    width:150
  },
  {
    label: "开户行",
    prop: "bankAccountName",
    width: 220,
  },
  {
    label: "账号",
    prop: "bankAccountNum",
    width: 220,
  },
  {
    label: "联系人",
    prop: "contactUserName",
  },
  {
    label: "联系电话",
    prop: "contactUserPhone",
    width: 150,
  },
  {
    label: "维护人",
    prop: "maintainUserName",
  },
  {
    label: "维护时间",
    prop: "maintainTime",
    width:100
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
        fixed: 'right',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
                disabled: (row) => {
                    return row.maintainUserName !== userStore.nickName
                }
      },
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const userList = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "",
  },
  form: {
    supplierName: "",
    taxpayerIdentificationNum: "",
    companyAddress: "",
    companyPhone: "",
    bankAccountName: "",
    bankAccountNum: "",
    contactUserName: "",
    contactUserPhone: "",
    maintainUserId: "",
    maintainTime: "",
  },
  rules: {
    supplierName: [{ required: true, message: "请输入", trigger: "blur" }],
    taxpayerIdentificationNum: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountName: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountNum: [{ required: true, message: "请输入", trigger: "blur" }],
    contactUserName: [{ required: false, message: "请输入", trigger: "blur" }],
    contactUserPhone: [{ required: false, message: "请输入", trigger: "blur" }],
    maintainUserId: [{ required: false, message: "请选择", trigger: "change" }],
    maintainTime: [{ required: false, message: "请选择", trigger: "change" }],
  },
});
const { searchForm, form, rules } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
  console.log(upload.url + '?updateSupport=' + upload.updateSupport)
  proxy.$refs["uploadRef"].submit();
}
const getList = () => {
  tableLoading.value = true;
  listSupplier({ ...searchForm.value, ...page }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    page.total = res.data.total;
  });
};
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(供应商导入)
  open: false,
  // å¼¹å‡ºå±‚标题(供应商导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
  updateSupport: 1,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
});
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
  upload.title = "供应商导入";
  upload.open = true;
}
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (response, file, fileList) => {
  upload.open = false;
  upload.isUploading = false;
  proxy.$refs["uploadRef"].handleRemove(file);
  getList();
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
  form.value = {};
  form.value.maintainUserId = userStore.id;
  form.value.maintainTime = getCurrentDate();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  if (type === "edit") {
    getSupplier(row.id).then((res) => {
      form.value = { ...res.data };
    });
  }
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
      } else {
        submitAdd();
  watch: {
    activeTab(newVal) {
      if (newVal === 'home') {
        this.$refs.homeTab && this.$refs.homeTab.getList()
      } else if (newVal === 'blacklist') {
        this.$refs.blacklistTab && this.$refs.blacklistTab.getList()
      }
    }
  });
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/system/supplier/export", {}, "供应商档案.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
        if (unauthorizedData.length > 0) {
            proxy.$modal.msgWarning("不可删除他人维护的数据");
            return;
        }
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      tableLoading.value = true;
      delSupplier(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
</script>
<style>
.main-container :deep(.el-tabs__item.is-active) {
  color: #1883f6 !important;
  border-bottom: 2px solid #409EFF;
}
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss"></style>
</style>
src/views/chatHome/chatHomeIndex/MobileChat.vue
@@ -14,7 +14,7 @@
                <span class="flash_cursor"></span>
              </template>
              <template v-else>
                <pre>{{ item.msg }}</pre>
                <pre style="font-family: none;">{{ item.msg }}</pre>
              </template>
            </div>
            <div class="chat-img" v-if="item.chatType == 1">
@@ -140,7 +140,7 @@
    }
    chatList.value.push(chatMsg)
    let chatGPT = {
      headImg: headPortrait,
      headImg: chatGPTHeadImg,
      name: '小智',
      time: new Date().toLocaleTimeString(),
      msg: "",
@@ -185,7 +185,7 @@
    uid: '1002'
  })
  chatList.value.push({
    headImg: chatGPTHeadImg,
    headImg: headPortrait,
    name: '卧龙',
    time: new Date().toLocaleTimeString(),
    msg: route.query.keyWord,
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -178,7 +178,7 @@
</template>
<script setup>
import { computed, getCurrentInstance, reactive, ref, toRefs } from "vue";
import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue";
import {
    approveProcessDetails,
    getDept,
@@ -255,7 +255,31 @@
        userList.value = res.data;
    });
    form.value = {...row}
    getProductOptions()
    // ç«‹å³æ¸…除表单验证状态(因为字段是disabled的,不需要验证)
    nextTick(() => {
        if (formRef.value) {
            formRef.value.clearValidate();
        }
    });
    // ç¡®ä¿é€‰é¡¹åŠ è½½å®ŒæˆåŽå†åŒ¹é…å€¼ç±»åž‹
    getProductOptions().then(() => {
        // ç¡®ä¿å€¼ç±»åž‹åŒ¹é…ï¼ˆå¦‚果选项已加载)
        if (productOptions.value.length > 0 && form.value.approveDeptId) {
            const matchedOption = productOptions.value.find(opt =>
                opt.deptId == form.value.approveDeptId ||
                String(opt.deptId) === String(form.value.approveDeptId)
            );
            if (matchedOption) {
                form.value.approveDeptId = matchedOption.deptId;
            }
        }
        // å†æ¬¡æ¸…除验证,确保选项加载后值匹配正确
        nextTick(() => {
            if (formRef.value) {
                formRef.value.clearValidate();
            }
        });
    });
  // æŠ¥ä»·å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的“报价单号”去查报价列表
  if (isQuotationApproval.value) {
@@ -291,17 +315,26 @@
  })
}
const getProductOptions = () => {
    getDept().then((res) => {
    return getDept().then((res) => {
        productOptions.value = res.data;
    });
};
// æäº¤å®¡æ‰¹
const submitForm = (status) => {
  const filteredActivities = activities.value.filter(activity => activity.isShen);
  filteredActivities[0].approveNodeStatus = status;
  if (!filteredActivities || filteredActivities.length === 0) {
    proxy.$modal.msgError("未找到待审批的节点");
    return;
  }
  const currentActivity = filteredActivities[0];
  if (!currentActivity) {
    proxy.$modal.msgError("未找到待审批的节点");
    return;
  }
  currentActivity.approveNodeStatus = status;
  // åˆ¤æ–­æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
  const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1;
  updateApproveNode({ ...filteredActivities[0], isLast }).then(() => {
  updateApproveNode({ ...currentActivity, isLast }).then(() => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
  });
@@ -350,4 +383,4 @@
    width: 200px;
    height: 60px;
}
</style>
</style>
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -16,8 +16,7 @@
        </el-row>
        <el-row>
          <el-col :span="24">
            <!-- ç”³è¯·éƒ¨é—¨ï¼šæ ¡éªŒä½¿ç”¨éƒ¨é—¨ID,便于下拉选择后立即通过校验 -->
            <el-form-item label="申请部门:" prop="approveDeptId">
            <el-form-item label="申请部门:" prop="approveDeptName">
<!--              <el-input v-model="form.approveDeptName" placeholder="请输入" clearable/>-->
                            <el-select
                                v-model="form.approveDeptId"
@@ -89,10 +88,10 @@
        <!-- å‡ºå·®åœ°ç‚¹ï¼ˆä»…当 approveType ä¸º 3 æ—¶æ˜¾ç¤ºï¼‰ -->
        <el-row v-if="props.approveType == 3">
          <el-col :span="24">
            <el-form-item label="备注:" prop="location">
            <el-form-item label="出差地点:" prop="location">
              <el-input
                  v-model="form.location"
                  placeholder="请输入备注"
                  placeholder="请输入出差地点"
                  clearable
              />
            </el-form-item>
@@ -235,7 +234,7 @@
    approveTime: "",
    approveId: "",
    approveUser: "",
    approveDeptId: "",
        approveDeptId: "",
    approveDeptName: "",
    approveReason: "",
    checkResult: "",
@@ -247,11 +246,10 @@
    location: "" // å‡ºå·®åœ°ç‚¹
  },
  rules: {
    approveTime: [{ required: false, message: "请输入", trigger: "change" }],
    approveTime: [{ required: false, message: "请输入", trigger: "change" },],
    approveId: [{ required: false, message: "请输入", trigger: "blur" }],
    approveUser: [{ required: false, message: "请输入", trigger: "blur" }],
    // ä½¿ç”¨éƒ¨é—¨ID做必填校验,避免名称未同步导致误报
    approveDeptId: [{ required: true, message: "请选择申请部门", trigger: "change" }],
    approveDeptName: [{ required: true, message: "请输入", trigger: "blur" }],
    approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
    checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
    startDate: [{ required: true, message: "请选择请假开始时间", trigger: "change" }],
@@ -300,7 +298,6 @@
    userListNoPageByTenantId().then((res) => {
    userList.value = res.data;
  });
  getProductOptions();
    form.value = {}
    approverNodes.value = [
        { id: 1, userId: null }
@@ -310,6 +307,9 @@
  
  // èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯å¹¶è®¾ç½®éƒ¨é—¨ID
  form.value.approveDeptId = userStore.currentDeptId
  // åŠ è½½éƒ¨é—¨é€‰é¡¹ï¼Œå¹¶åœ¨åŠ è½½å®ŒæˆåŽè®¾ç½®éƒ¨é—¨åç§°
  getProductOptions();
  if (operationType.value === 'edit') {
    fileList.value = row.commonFileList
    form.value.tempFileIds = fileList.value.map(file => file.id)
@@ -332,8 +332,18 @@
  }
}
const getProductOptions = () => {
  getDept().then((res) => {
  return getDept().then((res) => {
    productOptions.value = res.data;
    // å¦‚果已有部门ID,自动设置部门名称(用于验证)
    if (form.value.approveDeptId && productOptions.value.length > 0) {
      const matchedDept = productOptions.value.find(dept =>
        dept.deptId == form.value.approveDeptId ||
        String(dept.deptId) === String(form.value.approveDeptId)
      );
      if (matchedDept) {
        form.value.approveDeptName = matchedDept.deptName;
      }
    }
  });
};
function convertIdToValue(data) {
src/views/collaborativeApproval/approvalProcess/index.vue
@@ -2,13 +2,13 @@
  <div class="app-container">
    <!-- æ ‡ç­¾é¡µåˆ‡æ¢ä¸åŒçš„审批类型 -->
    <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs">
      <el-tab-pane label="协同审批" name="1"></el-tab-pane>
      <el-tab-pane label="公出管理" name="1"></el-tab-pane>
      <el-tab-pane label="请假管理" name="2"></el-tab-pane>
      <el-tab-pane label="销售审批" name="3"></el-tab-pane>
      <el-tab-pane label="报销审批" name="4"></el-tab-pane>
      <el-tab-pane label="出差管理" name="3"></el-tab-pane>
      <el-tab-pane label="报销管理" name="4"></el-tab-pane>
      <el-tab-pane label="采购审批" name="5"></el-tab-pane>
      <el-tab-pane label="报价审批" name="6"></el-tab-pane>
      <el-tab-pane label="出库审批" name="7"></el-tab-pane>
      <el-tab-pane label="发货审批" name="7"></el-tab-pane>
    </el-tabs>
    
    <div class="search_form">
@@ -35,9 +35,18 @@
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 6">新增</el-button>
        <el-button
          type="primary"
          @click="openForm('add')"
          v-if="currentApproveType !== 6 && currentApproveType !== 7"
        >新增</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
        <el-button
          type="danger"
          plain
          @click="handleDelete"
          v-if="currentApproveType !== 7"
        >删除</el-button>
      </div>
    </div>
    <div class="table_list">
@@ -205,7 +214,12 @@
        clickFun: (row) => {
          openForm("edit", row);
        },
        disabled: (row) => currentApproveType.value === 6 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
        disabled: (row) =>
          currentApproveType.value === 6 ||
          currentApproveType.value === 7 ||
          row.approveStatus == 2 ||
          row.approveStatus == 1 ||
          row.approveStatus == 4
      },
      {
        name: "审核",
@@ -294,7 +308,7 @@
    4: "报销管理审批表",
    5: "采购申请审批表",
    6: "报价审批表",
    7: "出库审批表",
    7: "发货审批表",
  }
  const fileName = nameMap[type] || nameMap[0]
  proxy.download(url, {}, `${fileName}.xlsx`)
src/views/collaborativeApproval/enterpriseBook/index.vue
@@ -295,7 +295,6 @@
  getEmployeeDetail
} from '@/api/collaborativeApproval/enterpriseBook.js'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {
  changeUserStatus,
  listUser,
@@ -306,6 +305,7 @@
  addUser,
  deptTreeSelect,
} from "@/api/system/user";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ ‡ç­¾é¡µçŠ¶æ€
const activeTab = ref('personal')
@@ -395,7 +395,7 @@
}
  //获取员工列表
const getEmployeeList = async () => {
  staffJoinListPage(publicSearch.value).then(res => {
  staffOnJobListPage(Object.assign({current: -1, size: -1},publicSearch.value)).then(res => {
    console.log(res.data.records)
      EmployeeList.value = res.data.records
    }).catch(err => {})
@@ -403,7 +403,7 @@
// èŽ·å–å•ä½é€šè®¯å½•åˆ—è¡¨
const getCompanyContactsList = async () => {
  loading.value = true
    staffJoinListPage(companySearch.value).then(res => {
  staffOnJobListPage(Object.assign({current: -1, size: -1},companySearch.value)).then(res => {
    // console.log(res.data.records)
      companyContacts.value = res.data.records
    }).catch(err => {})
src/views/collaborativeApproval/noticeManagement/index.vue
@@ -617,34 +617,23 @@
  };
  
  if (row.id) {
    // ç¼–辑模式 - å…ˆåˆ é™¤å†æ·»åŠ ï¼ˆå› ä¸ºåªæœ‰ add å’Œ del æŽ¥å£ï¼‰
    delNoticeType(row.id).then(res => {
      if (res.code === 200) {
        addNoticeType(data).then(addRes => {
          if (addRes.code === 200) {
            ElMessage.success('编辑成功');
            row.editing = false;
            delete row.originalNoticeType;
            fetchNoticeTypeList().then(() => {
              // å¦‚果当前选中的类型被编辑,需要重新获取数据
              if (activeNoticeTypeTab.value === String(row.id)) {
                fetchNoticesByType(addRes.data?.id || row.id);
              }
            });
          }
        });
      }
    });
  } else {
    // æ–°å¢žæ¨¡å¼
    addNoticeType(data).then(res => {
      if (res.code === 200) {
        ElMessage.success('新增成功');
        row.editing = false;
        fetchNoticeTypeList();
      }
    });
    // ç¼–辑模式 - ä¼ å…¥id
    data.id = row.id;
  }
  addNoticeType(data).then(res => {
    if (res.code === 200) {
      ElMessage.success(row.id ? '编辑成功' : '新增成功');
      row.editing = false;
      delete row.originalNoticeType;
      fetchNoticeTypeList().then(() => {
        // å¦‚果当前选中的类型被编辑,需要重新获取数据
        if (row.id && activeNoticeTypeTab.value === String(row.id)) {
          fetchNoticesByType(res.data?.id || row.id);
        }
      });
    }
  });
};
const handleDeleteNoticeType = (row) => {
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
@@ -127,7 +127,7 @@
            <el-option
                v-for="person in employees"
                :key="person.id"
                :label="`${person.staffName} (${person.postJob})`"
                :label="`${person.staffName} (${person.postName})`"
                :value="person.id"
            />
          </el-select>
@@ -156,7 +156,7 @@
import {ElMessage} from 'element-plus'
import {Plus, Document, Promotion, Bell} from '@element-plus/icons-vue'
import {getRoomEnum, saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// å½“前申请类型
const currentType = ref('department') // approval: å®¡æ‰¹æµç¨‹, department: éƒ¨é—¨çº§, notification: é€šçŸ¥å‘布
@@ -302,8 +302,12 @@
  getRoomEnum().then(res => {
    meetingRooms.value = res.data
  })
  getStaffOnJob().then(res => {
    employees.value = res.data.sort((a, b) => a.postJob.localeCompare(b.postJob))
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    employees.value = res.data.records.sort((a, b) => a.postName.localeCompare(b.postName))
  })
})
</script>
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
@@ -188,8 +188,8 @@
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getExamineList,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
@@ -240,7 +240,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -342,9 +342,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
@@ -186,8 +186,8 @@
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getMeetingPublish,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
@@ -239,7 +239,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -340,9 +340,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -160,8 +160,8 @@
import Pagination from '@/components/Pagination/index.vue'
import Editor from '@/components/Editor/index.vue'
import { getRoomEnum, getMeetingPublish ,getMeetingMinutesByMeetingId,saveMeetingMinutes} from '@/api/collaborativeApproval/meeting.js'
import { getStaffOnJob } from "@/api/personnelManagement/onboarding.js"
import dayjs from "dayjs"
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
@@ -214,7 +214,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -337,9 +337,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2] = await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2] = await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -1,100 +1,166 @@
<template>
  <div class="app-container">
        <!-- è§„章制度管理-->
          <el-card class="box-card">
            <template #header>
              <div class="card-header">
                <span>规章制度发布</span>
              </div>
    <!-- è§„章制度管理-->
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>规章制度发布</span>
        </div>
      </template>
      <div class="tab-content">
        <el-row :gutter="20"
                class="mb-20">
          <span class="ml-10">制度标题:</span>
          <el-col :span="6">
            <el-input v-model="regulationSearchForm.title"
                      placeholder="请输入制度标题"
                      clearable />
          </el-col>
          <span class="search_title">制度分类:</span>
          <el-col :span="4">
            <el-select v-model="regulationSearchForm.category"
                       placeholder="制度分类"
                       clearable>
              <el-option label="人事制度"
                         value="hr" />
              <el-option label="财务制度"
                         value="finance" />
              <el-option label="安全制度"
                         value="safety" />
              <el-option label="技术制度"
                         value="tech" />
            </el-select>
          </el-col>
          <el-col :span="8">
            <el-button type="primary"
                       @click="searchRegulations">搜索</el-button>
            <el-button @click="resetRegulationSearch">重置</el-button>
            <el-button @click="handleExport">导出</el-button>
            <el-button type="success"
                       @click="handleAdd">
              å‘布制度
            </el-button>
          </el-col>
        </el-row>
        <el-table :data="regulations"
                  border
                  v-loading="tableLoading"
                  style="width: 100%">
          <el-table-column prop="regulationNum"
                           label="制度编号"
                           width="120" />
          <el-table-column prop="title"
                           label="制度标题"
                           min-width="150" />
          <el-table-column prop="category"
                           label="分类"
                           width="120">
            <template #default="scope">
              <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
            </template>
            <div class="tab-content">
              <el-row :gutter="20" class="mb-20">
                <span class="ml-10">制度标题:</span>
                <el-col :span="6">
                  <el-input v-model="regulationSearchForm.title" placeholder="请输入制度标题" clearable />
                </el-col>
                <span class="search_title">制度分类:</span>
                <el-col :span="4">
                  <el-select v-model="regulationSearchForm.category" placeholder="制度分类" clearable>
                    <el-option label="人事制度" value="hr" />
                    <el-option label="财务制度" value="finance" />
                    <el-option label="安全制度" value="safety" />
                    <el-option label="技术制度" value="tech" />
                  </el-select>
                </el-col>
                <el-col :span="8">
                  <el-button type="primary" @click="searchRegulations">搜索</el-button>
                  <el-button @click="resetRegulationSearch">重置</el-button>
                  <el-button @click="handleExport">导出</el-button>
                  <el-button type="success" @click="handleAdd">
                    å‘布制度
                  </el-button>
                </el-col>
              </el-row>
              <el-table :data="regulations" border v-loading="tableLoading"  style="width: 100%">
                <el-table-column prop="regulationNum" label="制度编号" width="120" />
                <el-table-column prop="title" label="制度标题" min-width="150" />
                <el-table-column prop="category" label="分类" width="120">
                  <template #default="scope">
                    <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="version" label="版本" width="120" />
                <el-table-column prop="createUserName" label="发布人" width="120" />
                <el-table-column prop="createTime" label="发布时间" width="180" />
                <el-table-column prop="status" label="状态" width="100">
                  <template #default="scope">
                    <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                      {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="readCount" label="已读人数" width="100" />
                <el-table-column label="操作" width="320" fixed="right">
                  <template #default="scope">
                    <el-button link @click="viewRegulation(scope.row)">查看</el-button>
                    <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
                    <el-button link type="danger" @click="repealEdit(scope.row)">废弃</el-button>
                    <el-button link type="success" @click="viewVersionHistory(scope.row)">版本历史</el-button>
                    <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-card>
          </el-table-column>
          <el-table-column prop="version"
                           label="版本"
                           width="120" />
          <el-table-column prop="createUserName"
                           label="发布人"
                           width="120" />
          <el-table-column prop="createTime"
                           label="发布时间"
                           width="180" />
          <el-table-column prop="status"
                           label="状态"
                           width="100">
            <template #default="scope">
              <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="readCount"
                           label="已读人数"
                           width="100" />
          <el-table-column label="操作"
                           width="320"
                           fixed="right">
            <template #default="scope">
              <el-button link
                         @click="viewRegulation(scope.row)">查看</el-button>
              <el-button link
                         type="primary"
                         @click="handleEdit(scope.row)">编辑</el-button>
              <el-button link
                         type="danger"
                         @click="repealEdit(scope.row)">废弃</el-button>
              <el-button link
                         type="success"
                         @click="viewVersionHistory(scope.row)">版本历史</el-button>
              <!-- <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button> -->
              <el-button link
                         type="primary"
                         @click="openFileDialog(scope.row)">附件</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-card>
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡†ï¼ˆå·²ç§»é™¤ï¼‰ -->
    <!-- è§„章制度发布对话框 -->
    <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px">
      <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px">
        <el-form-item label="制度编号" prop="regulationNum">
          <el-input v-model="regulationForm.regulationNum" placeholder="请输入制度编号" />
    <el-dialog v-model="showRegulationDialog"
               :title="operationType === 'add' ? '发布制度' : '编辑制度'"
               width="800px">
      <el-form :model="regulationForm"
               :rules="regulationRules"
               ref="regulationFormRef"
               label-width="100px">
        <el-form-item label="制度编号"
                      prop="regulationNum">
          <el-input v-model="regulationForm.regulationNum"
                    placeholder="请输入制度编号" />
        </el-form-item>
        <el-form-item label="制度标题" prop="title">
          <el-input v-model="regulationForm.title" placeholder="请输入制度标题" />
        <el-form-item label="制度标题"
                      prop="title">
          <el-input v-model="regulationForm.title"
                    placeholder="请输入制度标题" />
        </el-form-item>
        <el-form-item label="制度分类" prop="category">
          <el-select v-model="regulationForm.category" placeholder="请选择制度分类" style="width: 100%">
            <el-option label="人事制度" value="hr" />
            <el-option label="财务制度" value="finance" />
            <el-option label="安全制度" value="safety" />
            <el-option label="技术制度" value="tech" />
        <el-form-item label="制度分类"
                      prop="category">
          <el-select v-model="regulationForm.category"
                     placeholder="请选择制度分类"
                     style="width: 100%">
            <el-option label="人事制度"
                       value="hr" />
            <el-option label="财务制度"
                       value="finance" />
            <el-option label="安全制度"
                       value="safety" />
            <el-option label="技术制度"
                       value="tech" />
          </el-select>
        </el-form-item>
        <el-form-item label="制度内容" prop="content">
          <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="请输入制度详细内容" />
        <el-form-item label="制度内容"
                      prop="content">
          <el-input v-model="regulationForm.content"
                    type="textarea"
                    :rows="10"
                    placeholder="请输入制度详细内容" />
        </el-form-item>
        <el-form-item label="制度版本" prop="version">
          <el-input v-model="regulationForm.version" placeholder="请输入制度版本" />
        <el-form-item label="制度版本"
                      prop="version">
          <el-input v-model="regulationForm.version"
                    placeholder="请输入制度版本" />
        </el-form-item>
        <el-form-item label="生效时间" prop="effectiveTime">
          <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" format="YYYY-MM-DD HH:mm:ss"
             value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择生效时间" style="width: 100%" />
        <el-form-item label="生效时间"
                      prop="effectiveTime">
          <el-date-picker v-model="regulationForm.effectiveTime"
                          type="datetime"
                          format="YYYY-MM-DD HH:mm:ss"
                          value-format="YYYY-MM-DD HH:mm:ss"
                          placeholder="选择生效时间"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="适用范围" prop="scope">
        <el-form-item label="适用范围"
                      prop="scope">
          <el-checkbox-group v-model="regulationForm.scope">
            <el-checkbox label="all">全体员工</el-checkbox>
            <el-checkbox label="manager">管理层</el-checkbox>
@@ -103,7 +169,8 @@
            <el-checkbox label="tech">技术部门</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
        <el-form-item label="是否需要确认" prop="requireConfirm">
        <el-form-item label="是否需要确认"
                      prop="requireConfirm">
          <el-switch v-model="regulationForm.requireConfirm" />
          <span class="ml-10">开启后员工需要阅读确认</span>
        </el-form-item>
@@ -111,17 +178,19 @@
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showRegulationDialog = false">取消</el-button>
          <el-button type="primary" @click="submitRegulation">发布制度</el-button>
          <el-button type="primary"
                     @click="submitRegulation">发布制度</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç”¨å°è¯¦æƒ…对话框(已移除) -->
    <!-- è§„章制度详情对话框 -->
    <el-dialog v-model="showRegulationDetailDialog" title="规章制度详情" width="800px">
    <el-dialog v-model="showRegulationDetailDialog"
               title="规章制度详情"
               width="800px">
      <div v-if="currentRegulationDetail">
        <el-descriptions :column="2" border>
        <el-descriptions :column="2"
                         border>
          <el-descriptions-item label="制度编号">{{ currentRegulationDetail.id }}</el-descriptions-item>
          <el-descriptions-item label="制度标题">{{ currentRegulationDetail.title }}</el-descriptions-item>
          <el-descriptions-item label="分类">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item>
@@ -134,19 +203,30 @@
          <div class="regulation-content">{{ currentRegulationDetail.content }}</div>
        </div>
        <!-- å¦‚æžœtableData>0 æ˜¾ç¤º -->
        <div style="margin: 10px 0;" v-if="tableData && tableData.length > 0" >
          <el-button type="success" @click="resetForm(currentRegulationDetail)">确认查看</el-button>
        <div style="margin: 10px 0;"
             v-if="tableData && tableData.length > 0">
          <el-button type="success"
                     @click="resetForm(currentRegulationDetail)">确认查看</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- ç‰ˆæœ¬åŽ†å²å¯¹è¯æ¡† -->
    <el-dialog v-model="showVersionHistoryDialog" title="版本历史" width="800px">
      <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="version" label="版本号" width="100" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column prop="createUserName" label="更新人" width="120" />
        <el-table-column prop="changeLog" label="变更说明">
    <el-dialog v-model="showVersionHistoryDialog"
               title="版本历史"
               width="800px">
      <el-table :data="versionHistory"
                style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="version"
                         label="版本号"
                         width="100" />
        <el-table-column prop="updateTime"
                         label="更新时间"
                         width="180" />
        <el-table-column prop="createUserName"
                         label="更新人"
                         width="120" />
        <el-table-column prop="changeLog"
                         label="变更说明">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
              {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
@@ -155,15 +235,27 @@
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- é˜…读状态对话框 -->
    <el-dialog v-model="showReadStatusDialog" title="阅读状态" width="800px">
      <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="employee" label="员工姓名" width="120" />
        <el-table-column prop="department" label="所属部门" width="150" />
        <el-table-column prop="createTime" label="阅读时间" width="180" />
        <el-table-column prop="confirmTime" label="确认时间" width="180" />
        <el-table-column prop="status" label="状态" width="100">
    <el-dialog v-model="showReadStatusDialog"
               title="阅读状态"
               width="800px">
      <el-table :data="readStatusList"
                style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="employee"
                         label="员工姓名"
                         width="120" />
        <el-table-column prop="department"
                         label="所属部门"
                         width="150" />
        <el-table-column prop="createTime"
                         label="阅读时间"
                         width="180" />
        <el-table-column prop="confirmTime"
                         label="确认时间"
                         width="180" />
        <el-table-column prop="status"
                         label="状态"
                         width="100">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'">
              {{ scope.row.status === 'confirmed' ? '已确认' : '未确认' }}
@@ -172,333 +264,437 @@
        </el-table-column>
      </el-table>
    </el-dialog>
    <FileListDialog ref="fileListDialogRef"
                    v-model="fileDialogVisible"
                    :show-upload-button="true"
                    :show-delete-button="true"
                    :delete-method="handleAttachmentDelete"
                    :rules-regulations-management-id="currentFileRuleId"
                    :name-column-label="'附件名称'"
                    @upload="handleAttachmentUpload" />
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import {
    listRuleManagement,
    addRuleManagement,
    updateRuleManagement,
    delRuleManagement,
    getReadingStatusByRuleId,
    addReadingStatus,
    updateReadingStatus,
  } from "@/api/collaborativeApproval/sealManagement.js";
  import FileListDialog from "@/components/Dialog/FileListDialog.vue";
  import {
    listRuleFiles,
    delRuleFile,
    addRuleFile,
  } from "@/api/collaborativeApproval/rulesRegulationsManagementFile.js";
// å“åº”式数据
const operationType = ref('add')
const tableData = ref([])
const tableLoading = ref(false)
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// è§„章制度相关
const showRegulationDialog = ref(false)
const showRegulationDetailDialog = ref(false)
const showVersionHistoryDialog = ref(false)
const showReadStatusDialog = ref(false)
const currentRegulationDetail = ref(null)
const regulationFormRef = ref()
const regulationForm = reactive({
  id: '',
  regulationNum: '',
  title: '',
  category: '',
  content: '',
  version: '',
  status: 'active',
  readCount: 0,
  effectiveTime: '',
  scope: [],
  requireConfirm: false
})
  // å“åº”式数据
  const operationType = ref("add");
  const tableData = ref([]);
  const tableLoading = ref(false);
  // åˆ†é¡µå‚æ•°
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // é™„件弹窗
  const fileDialogVisible = ref(false);
  const fileListDialogRef = ref(null);
  const currentFileRuleId = ref(null);
  const filePage = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // è§„章制度相关
  const showRegulationDialog = ref(false);
  const showRegulationDetailDialog = ref(false);
  const showVersionHistoryDialog = ref(false);
  const showReadStatusDialog = ref(false);
  const currentRegulationDetail = ref(null);
  const regulationFormRef = ref();
  const regulationForm = reactive({
    id: "",
    regulationNum: "",
    title: "",
    category: "",
    content: "",
    version: "",
    status: "active",
    readCount: 0,
    effectiveTime: "",
    scope: [],
    requireConfirm: false,
  });
const readStatus = ref({
  id: '',
  ruleId: '',
  employee: '',
  department: '',
  createTime: '',
  confirmTime: '',
  status: 'unconfirmed'
})
  const readStatus = ref({
    id: "",
    ruleId: "",
    employee: "",
    department: "",
    createTime: "",
    confirmTime: "",
    status: "unconfirmed",
  });
const regulationRules = {
  title: [{ required: true, message: '请输入制度标题', trigger: 'blur' }],
  category: [{ required: true, message: '请选择制度分类', trigger: 'change' }],
  content: [{ required: true, message: '请输入制度内容', trigger: 'blur' }],
  effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'change' }],
  scope: [{ required: true, message: '请选择适用范围', trigger: 'change' }]
}
  const regulationRules = {
    title: [{ required: true, message: "请输入制度标题", trigger: "blur" }],
    category: [{ required: true, message: "请选择制度分类", trigger: "change" }],
    content: [{ required: true, message: "请输入制度内容", trigger: "blur" }],
    effectiveTime: [
      { required: true, message: "请选择生效时间", trigger: "change" },
    ],
    scope: [{ required: true, message: "请选择适用范围", trigger: "change" }],
  };
const regulationSearchForm = reactive({
  title: '',
  category: ''
})
  const regulationSearchForm = reactive({
    title: "",
    category: "",
  });
const regulations = ref([])
  const regulations = ref([]);
const versionHistory = ref([])
  const versionHistory = ref([]);
const readStatusList = ref([])
  const readStatusList = ref([]);
  // { employee: '陈志强', department: '销售部', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' },
  // { employee: '刘雅婷', department: '技术部', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' },
  // { employee: '王建国', department: '财务部', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' }
// åˆ¶åº¦åˆ†ç±»
const getCategoryText = (category) => {
  const categoryMap = {
    hr: '人事制度',
    finance: '财务制度',
    safety: '安全制度',
    tech: '技术制度'
  }
  return categoryMap[category] || '未知'
}
// æœç´¢åˆ¶åº¦
const searchRegulations = () => {
  page.current=1
  getRegulationList()
}
// é‡ç½®åˆ¶åº¦æœç´¢
const resetRegulationSearch = () => {
  regulationSearchForm.title = ''
  regulationSearchForm.category = ''
  searchRegulations()
}
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add'
  resetRegulationForm()
  showRegulationDialog.value = true
}
  // åˆ¶åº¦åˆ†ç±»
  const getCategoryText = category => {
    const categoryMap = {
      hr: "人事制度",
      finance: "财务制度",
      safety: "安全制度",
      tech: "技术制度",
    };
    return categoryMap[category] || "未知";
  };
  // æœç´¢åˆ¶åº¦
  const searchRegulations = () => {
    page.current = 1;
    getRegulationList();
  };
  // é‡ç½®åˆ¶åº¦æœç´¢
  const resetRegulationSearch = () => {
    regulationSearchForm.title = "";
    regulationSearchForm.category = "";
    searchRegulations();
  };
  // æ–°å¢ž
  const handleAdd = () => {
    operationType.value = "add";
    resetRegulationForm();
    showRegulationDialog.value = true;
  };
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(regulationForm, row)
  showRegulationDialog.value = true
}
// åºŸå¼ƒ
const repealEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(regulationForm, row)
  regulationForm.status = 'repealed'
  ElMessageBox.confirm('确认废弃该制度?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    updateRuleManagement(regulationForm).then(res => {
      if(res.code == 200){
        ElMessage.success('制度废弃成功')
        // showRegulationDialog.value = false
        getRegulationList()
        resetRegulationForm()
      }
  // ç¼–辑
  const handleEdit = row => {
    operationType.value = "edit";
    Object.assign(regulationForm, row);
    showRegulationDialog.value = true;
  };
  // åºŸå¼ƒ
  const repealEdit = row => {
    operationType.value = "edit";
    Object.assign(regulationForm, row);
    regulationForm.status = "repealed";
    ElMessageBox.confirm("确认废弃该制度?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
  }).catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消废弃'
    })
  })
}
// å‘布制度
const submitRegulation = async () => {
  try {
    await regulationFormRef.value.validate()
    if(operationType.value == 'add'){
      addRuleManagement(regulationForm).then(res => {
        if(res.code == 200){
          ElMessage.success('制度发布成功')
          showRegulationDialog.value = false
          getRegulationList()
          resetRegulationForm()
        }
      .then(() => {
        updateRuleManagement(regulationForm).then(res => {
          if (res.code == 200) {
            ElMessage.success("制度废弃成功");
            // showRegulationDialog.value = false
            getRegulationList();
            resetRegulationForm();
          }
        });
      })
    }else{
      updateRuleManagement(regulationForm).then(res => {
        if(res.code == 200){
          ElMessage.success('制度编辑成功')
          showRegulationDialog.value = false
          resetRegulationForm()
          getRegulationList()
      }})}
  }catch(err){
    ElMessage.error(err.msg)
  }
}
//重置制度表单
const resetRegulationForm = () => {
  Object.assign(regulationForm, {
    id: '',
    regulationNum: '',
    title: '',
    category: '',
    content: '',
    version: '',
    status: 'active',
    readCount: 0,
    effectiveTime: '',
    scope: [],
    requireConfirm: false
})
}
// æŸ¥çœ‹åˆ¶åº¦ç‰ˆæœ¬åŽ†å²
const viewVersionHistory = (row) => {
  showVersionHistoryDialog.value = true
  const params = {
    category: row.category
  }
  listRuleManagement(page,params).then(res => {
    if(res.code == 200){
      versionHistory.value = res.data.records
      .catch(() => {
        ElMessage({
          type: "info",
          message: "已取消废弃",
        });
      });
  };
  // å‘布制度
  const submitRegulation = async () => {
    try {
      await regulationFormRef.value.validate();
      if (operationType.value == "add") {
        addRuleManagement(regulationForm).then(res => {
          if (res.code == 200) {
            ElMessage.success("制度发布成功");
            showRegulationDialog.value = false;
            getRegulationList();
            resetRegulationForm();
          }
        });
      } else {
        updateRuleManagement(regulationForm).then(res => {
          if (res.code == 200) {
            ElMessage.success("制度编辑成功");
            showRegulationDialog.value = false;
            resetRegulationForm();
            getRegulationList();
          }
        });
      }
    } catch (err) {
      ElMessage.error(err.msg);
    }
  })
}
// æŸ¥çœ‹åˆ¶åº¦è¯¦æƒ…
const viewRegulation = (row) => {
  currentRegulationDetail.value = row
  showRegulationDetailDialog.value = true
  getReadingStatusByRuleId(row.id).then(res => {
    if(res.code == 200){
      readStatusList.value = res.data
      if(readStatusList.value.length==0 && tableData.value.length>0){
  };
  //重置制度表单
  const resetRegulationForm = () => {
    Object.assign(regulationForm, {
      id: "",
      regulationNum: "",
      title: "",
      category: "",
      content: "",
      version: "",
      status: "active",
      readCount: 0,
      effectiveTime: "",
      scope: [],
      requireConfirm: false,
    });
  };
  // æŸ¥çœ‹åˆ¶åº¦ç‰ˆæœ¬åŽ†å²
  const viewVersionHistory = row => {
    showVersionHistoryDialog.value = true;
    const params = {
      category: row.category,
    };
    listRuleManagement(page, params).then(res => {
      if (res.code == 200) {
        versionHistory.value = res.data.records;
      }
    });
  };
  // æŸ¥çœ‹åˆ¶åº¦è¯¦æƒ…
  const viewRegulation = row => {
    currentRegulationDetail.value = row;
    showRegulationDetailDialog.value = true;
    getReadingStatusByRuleId(row.id).then(res => {
      if (res.code == 200) {
        readStatusList.value = res.data;
        if (readStatusList.value.length == 0 && tableData.value.length > 0) {
          const params = {
          ruleId: row.id,
          employee: tableData.value[0].staffName,
          department: tableData.value[0].postJob,
          status: 'unconfirmed'
            ruleId: row.id,
            employee: tableData.value[0].staffName,
            department: tableData.value[0].postJob,
            status: "unconfirmed",
          };
          addReadingStatus(params).then(res => {
            if (res.code == 200) {
              ElMessage.success("制度阅读成功");
            }
          });
        }
        addReadingStatus(params).then(res => {
          if(res.code == 200){
            ElMessage.success('制度阅读成功')
          }
        })
      }
    }
  })
}
// æŸ¥çœ‹åˆ¶åº¦é˜…读状态
const viewReadStatus = (row) => {
  showReadStatusDialog.value = true
  //查看阅读状态列表
  getReadingStatusByRuleId(row.id).then(res => {
    if(res.code == 200){
      readStatusList.value = res.data
    }
  })
}
//确认查看
const resetForm = (row) => {
  console.log("row",row)
  row.readCount = row.readCount + 1
  updateRuleManagement(row).then(res => {
    if(res.code == 200){
      ElMessage.success('查看数量修改成功')
      //修改阅读状态
      //根据制度id和当前登录的员工得到阅读状态
      // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName )
      // if(item.length>0){
      //   item[0].status = 'confirmed',
      //   item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
      // }
      // ç­›é€‰å½“前员工对应该制度的阅读状态记录
      let statusItem = readStatusList.value.find(item => item.employee === tableData.value[0].staffName && item.ruleId === row.id);
      if (statusItem) {
        // å¦‚果找到记录,更新状态和确认时间
        statusItem.status = 'confirmed';
        // æ ¼å¼åŒ–时间为"YYYY-MM-DD HH:mm:ss"格式
        const now = new Date();
        statusItem.confirmTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
        // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
        updateReadingStatus(statusItem).then(res => {
          if(res.code == 200){
            ElMessage.success('制度阅读状态修改成功')
          }
        })
    });
  };
  // æŸ¥çœ‹åˆ¶åº¦é˜…读状态
  const viewReadStatus = row => {
    showReadStatusDialog.value = true;
    //查看阅读状态列表
    getReadingStatusByRuleId(row.id).then(res => {
      if (res.code == 200) {
        readStatusList.value = res.data;
      }
    });
  };
  //确认查看
  const resetForm = row => {
    console.log("row", row);
    row.readCount = row.readCount + 1;
    updateRuleManagement(row).then(res => {
      if (res.code == 200) {
        ElMessage.success("查看数量修改成功");
        //修改阅读状态
        //根据制度id和当前登录的员工得到阅读状态
        // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName )
        // if(item.length>0){
        //   item[0].status = 'confirmed',
        //   item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
        // }
        // ç­›é€‰å½“前员工对应该制度的阅读状态记录
        let statusItem = readStatusList.value.find(
          item =>
            item.employee === tableData.value[0].staffName &&
            item.ruleId === row.id
        );
        if (statusItem) {
          // å¦‚果找到记录,更新状态和确认时间
          statusItem.status = "confirmed";
          // æ ¼å¼åŒ–时间为"YYYY-MM-DD HH:mm:ss"格式
          const now = new Date();
          statusItem.confirmTime = `${now.getFullYear()}-${String(
            now.getMonth() + 1
          ).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(
            now.getHours()
          ).padStart(2, "0")}:${String(now.getMinutes()).padStart(
            2,
            "0"
          )}:${String(now.getSeconds()).padStart(2, "0")}`;
          // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
          updateReadingStatus(statusItem).then(res => {
            if (res.code == 200) {
              ElMessage.success("制度阅读状态修改成功");
            }
          });
        }
      }
    });
  };
  // å¯¼å‡ºè§„章制度
  const { proxy } = getCurrentInstance();
  const handleExport = () => {
    proxy.download(
      "/rulesRegulationsManagement/export",
      { ...regulationSearchForm },
      "规章制度.xlsx"
    );
  };
  // é™„件:查询
  const fetchRuleFiles = async rulesRegulationsManagementId => {
    const params = {
      current: filePage.current,
      size: filePage.size,
      rulesRegulationsManagementId,
    };
    const res = await listRuleFiles(params);
    const records = res?.data?.records || [];
    filePage.total = res?.data?.total || records.length;
    const mapped = records.map(item => ({
      id: item.id,
      name: item.fileName || item.name,
      url: item.fileUrl || item.url,
      raw: item,
    }));
    fileListDialogRef.value?.setList(mapped);
  };
  // æ‰“开附件弹窗
  const openFileDialog = async row => {
    currentFileRuleId.value = row.id;
    fileDialogVisible.value = true;
    await fetchRuleFiles(row.id);
  };
  // åˆ·æ–°é™„件列表
  const refreshFileList = async () => {
    if (!currentFileRuleId.value) return;
    await fetchRuleFiles(currentFileRuleId.value);
  };
  // ä¸Šä¼ é™„件(由子组件触发)
  const handleAttachmentUpload = async filePayload => {
    if (!currentFileRuleId.value) return;
    const payload = {
      name: filePayload?.fileName || filePayload?.name,
      url: filePayload?.fileUrl || filePayload?.url,
      rulesRegulationsManagementId: currentFileRuleId.value,
    };
    await addRuleFile(payload);
    ElMessage.success("文件上传成功");
    await refreshFileList();
  };
  // åˆ é™¤é™„ä»¶
  const handleAttachmentDelete = async row => {
    if (!row?.id) return false;
    try {
      await ElMessageBox.confirm("确认删除该附件?", "提示", { type: "warning" });
    } catch {
      return false;
    }
  })
}
    await delRuleFile([row.id]);
    ElMessage.success("删除成功");
    await refreshFileList();
  };
// å¯¼å‡ºè§„章制度
const { proxy } = getCurrentInstance()
const handleExport = () => {
  proxy.download('/rulesRegulationsManagement/export', { ...regulationSearchForm }, '规章制度.xlsx')
}
  // èŽ·å–è§„ç« åˆ¶åº¦åˆ—è¡¨æ•°æ®
  const getRegulationList = async () => {
    tableLoading.value = true;
    listRuleManagement(page, regulationSearchForm)
      .then(res => {
        regulations.value = res.data.records;
        // è¿‡æ»¤æŽ‰å·²åºŸå¼ƒçš„制度
        // regulations.value = res.data.records.filter(item => item.status !== 'repealed')
        page.value.total = res.data.total;
        tableLoading.value = false;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
// èŽ·å–è§„ç« åˆ¶åº¦åˆ—è¡¨æ•°æ®
const getRegulationList = async () => {
  tableLoading.value = true
  listRuleManagement(page,regulationSearchForm)
  .then(res => {
    regulations.value = res.data.records
    // è¿‡æ»¤æŽ‰å·²åºŸå¼ƒçš„制度
    // regulations.value = res.data.records.filter(item => item.status !== 'repealed')
    page.value.total = res.data.total;
    tableLoading.value = false;
  }).catch(err => {
    tableLoading.value = false;
  })
}
onMounted(() => {
  // åˆå§‹åŒ–
  getRegulationList()
})
  onMounted(() => {
    // åˆå§‹åŒ–
    getRegulationList();
  });
</script>
<style scoped>
.app-container {
  padding: 20px;
}
  .app-container {
    padding: 20px;
  }
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
.tab-content {
  padding: 20px 0;
}
  .tab-content {
    padding: 20px 0;
  }
.mb-20 {
  margin-bottom: 20px;
}
  .mb-20 {
    margin-bottom: 20px;
  }
.mt-20 {
  margin-top: 20px;
}
  .mt-20 {
    margin-top: 20px;
  }
.ml-10 {
  margin-left: 10px;
}
  .ml-10 {
    margin-left: 10px;
  }
.regulation-content {
  background-color: #f5f5f5;
  padding: 15px;
  border-radius: 4px;
  line-height: 1.6;
  white-space: pre-wrap;
  height: 200px;
}
  .regulation-content {
    background-color: #f5f5f5;
    padding: 15px;
    border-radius: 4px;
    line-height: 1.6;
    white-space: pre-wrap;
    height: 200px;
  }
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
  .dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
</style>
src/views/collaborativeApproval/sealManagement/index.vue
@@ -27,7 +27,7 @@
                  <el-option label="已拒绝" value="rejected" />
                </el-select>
              </el-col>
              <el-col :span="8">
              <el-col :span="6">
                <el-button type="primary" @click="searchSealApplications">搜索</el-button>
                <el-button @click="resetSealSearch">重置</el-button>
                <el-button @click="handleExport">导出</el-button>
@@ -261,9 +261,9 @@
import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile, userListNoPageByTenantId } from '@/api/system/user.js'
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// å“åº”式数据
const currentUser = ref(null)
@@ -583,7 +583,7 @@
      currentUser.value = res.data.userName
    }
  })
  staffJoinListPage({staffState: 1, ...page}).then(res => {
  staffOnJobListPage({staffState: 1, ...page}).then(res => {
    tableLoading.value = false;
    // tableData.value = res.data.records
    // //筛选出和currentUser同名的人员
src/views/collaborativeApproval/shipmentReview/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <el-table :data="tableData" border height="40vh">
      <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="100" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">下载</el-button>
          <el-button link type="primary" size="small" @click="lookFile(scope.row)">预览</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import filePreview from '@/components/filePreview/index.vue'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  open
})
</script>
<style></style>
src/views/collaborativeApproval/shipmentReview/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,340 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">销售合同号:</span>
        <el-input
            v-model="searchForm.salesContractNo"
            style="width: 240px"
            placeholder="请输入销售合同号搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <span class="search_title ml10">审批状态:</span>
        <el-select v-model="searchForm.approveStatus" clearable @change="handleQuery" style="width: 240px">
          <el-option label="待审核" :value="2" />
          <el-option label="审核成功" :value="3" />
          <el-option label="审核失败" :value="4" />
        </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
      </div>
      <div>
<!--        <el-button type="primary" @click="openForm('add')">新增</el-button>-->
        <el-button @click="handleOut">导出</el-button>
<!--        <el-button type="danger" plain @click="handleDelete">删除</el-button>-->
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      ></PIMTable>
    </div>
    <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="approveType"></info-form-dia>
    <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia>
    <FileList ref="fileListRef" />
  </div>
</template>
<script setup>
import FileList from "./fileList.vue";
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {ElMessageBox} from "element-plus";
import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
import {getShipmentApprovalList, approveShipment} from "@/api/collaborativeApproval/shipmentReview.js";
// import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
// å®šä¹‰ç»„件接收的props
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 6
  }
});
const userList = ref([]);
const userStore = useUserStore();
const data = reactive({
  searchForm: {
    approveId: "",
    approveStatus: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  {
    label: "审批状态",
    prop: "approveStatus",
    dataType: "tag",
    width: 100,
    formatData: (params) => {
      if (params === 2) {
        return "待审核";
      } else if (params === 3) {
        return "审核完成";
      } else if (params === 4) {
        return "审核驳回";
      } else {
        return '未知状态';
      }
    },
    formatType: (params) => {
      if (params === 0) {
        return "warning";
      } else if (params === 2) {
        return "info";
      } else if (params === 3) {
        return "success";
      } else if (params === 4) {
        return "danger";
      } else {
        return 'danger';
      }
    },
  },
  {
    label: "销售合同号",
    prop: "salesContractNo",
    width: 170
  },
  {
    label: "客户名称",
    prop: "customerName",
    width: 200
  },
  {
    label: "产品大类",
    prop: "productCategory",
    width: 200
  },
  {
    label: "规格型号",
    prop: "specificationModel",
    width: 220
  },
  {
    label: "申请人",
    prop: "approveUserId",
    width: 120,
    align: "center",
    formatData:(params)=>{
      const user = userList.value.find(item => item.userId === params)
      return user ? user.nickName : '--'
    }
  },
  {
    label: "车牌号",
    prop: "shippingCarNumber",
    width: 120,
  },
  {
    label: "申请人",
    prop: "approveUserId",
    width: 120,
  },
  {
    label: "申请日期",
    prop: "executionDate",
    width: 200
  },
  {
    label: "当前审批人",
    prop: "salesman",
    width: 120
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 120,
    operation: [
      {
        name: "通过",
        type: "text",
        clickFun: (row) => {
          handleApproval("通过", row);
        },
        disabled: (row) => row.approveStatus !== 2
      },
      {
        name: "驳回",
        type: "text",
        clickFun: (row) => {
          handleApproval("驳回", row);
        },
        disabled: (row) => row.approveStatus !== 2
      },
      // {
      //   name: "编辑",
      //   type: "text",
      //   clickFun: (row) => {
      //     openForm("edit", row);
      //   },
      //   disabled: (row) => row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
      // },
      // {
      //   name: "审核",
      //   type: "text",
      //   clickFun: (row) => {
      //     openApprovalDia("approval", row);
      //   },
      //   disabled: (row) => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id
      // },
      // {
      //   name: "详情",
      //   type: "text",
      //   clickFun: (row) => {
      //     openApprovalDia('view', row);
      //   },
      // },
      // {
      //   name: "附件",
      //   type: "text",
      //   clickFun: (row) => {
      //     downLoadFile(row);
      //   },
      // },
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0
});
const infoFormDia = ref()
const approvalDia = ref()
const { proxy } = getCurrentInstance()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const fileListRef = ref(null)
const downLoadFile = (row) => {
  fileListRef.value.open(row.commonFileList)
}
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList =async () => {
  let userLists = await userListNoPage();
  userList.value = userLists.data;
  tableLoading.value = true;
  getShipmentApprovalList({...page, ...searchForm.value,approveType:props.approveType}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// å¯¼å‡º
const handleOut = () => {
  const type = Number(props.approveType || 6)
  const urlMap = {
    0: "/shipmentApproval/export",
  }
  const url = urlMap[type] || urlMap[0]
  const nameMap = {
    0: "发货审核表",
  }
  const fileName = nameMap[type] || nameMap[0]
  proxy.download(url, {}, `${fileName}.xlsx`)
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开新增、编辑弹框
const openForm = (type, row) => {
  nextTick(() => {
    infoFormDia.value?.openDialog(type, row)
  })
};
// æ‰“开新增检验弹框
const openApprovalDia = (type, row) => {
  nextTick(() => {
    approvalDia.value?.openDialog(type, row)
  })
};
// å®¡æ ¸é€šè¿‡/驳回
const handleApproval = (name = "审核",row) => {
  ElMessageBox.confirm(`选中的内容将被${name},是否确认${name}?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async()=>{
    let res = await approveShipment({
      id: row.id,
      approveStatus: name === "通过" ? 3 : 4
    });
    if(res.code === 200){
      proxy.$modal.msgSuccess(`${name}成功`);
    }else{
      proxy.$modal.msgError(`${name}失败`);
    }
    await getList()
  }).catch(err=>{
    proxy.$modal.msgError(`未知错误,请联系管理员`);
  })
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.approveId);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        approveProcessDelete(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList();
});
</script>
<style scoped></style>
src/views/customerService/afterSalesHandling/components/formDia.vue
@@ -131,6 +131,7 @@
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceDispose, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -194,14 +195,6 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
src/views/customerService/afterSalesHandling/index.vue
@@ -30,6 +30,7 @@
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
                <el-button @click="handleOut" style="margin-left: 10px">导出</el-button>
            </div>
        </div>
        <div class="table_list">
@@ -50,7 +51,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/customerService/afterSalesHandling/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {afterSalesServiceDelete, afterSalesServiceListPage} from "@/api/customerService/index.js";
@@ -225,6 +226,22 @@
            proxy.$modal.msg("已取消");
        });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/afterSalesService/exportTwo", {}, "售后处理.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
src/views/customerService/expiryAfterSales/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,283 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="dialogTitle"
        width="70%"
        @close="closeDia"
    >
            <el-form
                :model="form"
                label-width="140px"
                label-position="top"
                :rules="rules"
                ref="formRef"
            >
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="临期产品名称:" prop="productName">
                            <el-input
                                v-model="form.productName"
                                placeholder="请输入产品名称"
                                clearable
                                :disabled="operationType === 'view'"
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="产品批号:" prop="batchNumber">
                            <el-input
                                v-model="form.batchNumber"
                                placeholder="请输入产品批号"
                                clearable
                                :disabled="operationType === 'view'"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="临期日期:" prop="expiryDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.expiryDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择临期日期"
                                clearable
                                :disabled="operationType === 'view'"
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="库存数量:" prop="stockQuantity">
                            <el-input-number
                                v-model="form.stockQuantity"
                                :min="0"
                                placeholder="请输入库存数量"
                                style="width: 100%"
                                :disabled="operationType === 'view'"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="客户名称:" prop="customerName">
                            <el-input
                                v-model="form.customerName"
                                placeholder="请输入客户名称"
                                clearable
                                :disabled="operationType === 'view'"
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="联系电话:" prop="contactPhone">
                            <el-input
                                v-model="form.contactPhone"
                                placeholder="请输入联系电话"
                                clearable
                                :disabled="operationType === 'view'"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="24">
                        <el-form-item label="问题描述:" prop="problemDesc">
                            <el-input
                                v-model="form.problemDesc"
                                placeholder="请输入问题描述"
                                clearable
                                :disabled="operationType === 'view'"
                                type="textarea"
                                :rows="3"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30" v-if="operationType !== 'add'">
                    <el-col :span="12">
                        <el-form-item label="处理人:" prop="handlerId">
                            <el-select
                                v-model="form.handlerId"
                                placeholder="请选择处理人"
                                clearable
                                :disabled="operationType === 'view'"
                                style="width: 100%"
                            >
                                <el-option
                                    v-for="item in userList"
                                    :key="item.userId"
                                    :label="item.nickName"
                                    :value="item.userId"
                                ></el-option>
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="处理日期:" prop="handleDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.handleDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择处理日期"
                                clearable
                                :disabled="operationType === 'view'"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30" v-if="operationType !== 'add'">
                    <el-col :span="24">
                        <el-form-item label="处理结果:" prop="handleResult">
                            <el-input
                                v-model="form.handleResult"
                                placeholder="请输入处理结果"
                                clearable
                                :disabled="operationType === 'view'"
                                type="textarea"
                                :rows="3"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">确认</el-button>
                    <el-button @click="closeDia">{{ operationType === 'view' ? '关闭' : '取消' }}</el-button>
                </div>
            </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, computed} from "vue";
import useUserStore from "@/store/modules/user.js";
import { getCurrentDate } from "@/utils/index.js";
// import {userListNoPageByTenantId} from "@/api/system/user.js"; // æš‚时注释掉,使用假数据
// import {expiryAfterSalesAdd, expiryAfterSalesUpdate} from "@/api/customerService/index.js"; // æš‚时注释掉,使用假数据
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const userStore = useUserStore();
const dialogTitle = computed(() => {
    switch (operationType.value) {
        case 'add':
            return '新增临期售后';
        case 'edit':
            return '编辑临期售后';
        case 'view':
            return '查看临期售后';
        default:
            return '临期售后管理';
    }
});
const data = reactive({
    form: {
        id: "",
        productName: "",
        batchNumber: "",
        expiryDate: "",
        stockQuantity: 0,
        customerName: "",
        contactPhone: "",
        problemDesc: "",
        handlerId: "",
        handleDate: "",
        handleResult: "",
        status: 1
    },
    rules: {
        productName: [{required: true, message: "请输入产品名称", trigger: "blur"}],
        batchNumber: [{required: true, message: "请输入产品批号", trigger: "blur"}],
        expiryDate: [{required: true, message: "请选择临期日期", trigger: "change"}],
        stockQuantity: [{required: true, message: "请输入库存数量", trigger: "blur"}],
        customerName: [{required: true, message: "请输入客户名称", trigger: "blur"}],
        contactPhone: [
            {required: true, message: "请输入联系电话", trigger: "blur"},
            {pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur"}
        ],
        problemDesc: [{required: true, message: "请输入问题描述", trigger: "blur"}],
    }
})
const { form, rules } = toRefs(data);
const userList = ref([])
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    // æ¨¡æ‹ŸèŽ·å–ç”¨æˆ·åˆ—è¡¨
    userList.value = [
        { userId: 1, nickName: "张三" },
        { userId: 2, nickName: "李四" },
        { userId: 3, nickName: "王五" },
        { userId: 4, nickName: "赵六" },
        { userId: 5, nickName: "孙八" }
    ];
    if (type === 'add') {
        // æ–°å¢žæ—¶é‡ç½®è¡¨å•
        form.value = {
            id: "",
            productName: "",
            batchNumber: "",
            expiryDate: "",
            stockQuantity: 0,
            customerName: "",
            contactPhone: "",
            problemDesc: "",
            handlerId: "",
            handleDate: "",
            handleResult: "",
            status: 1
        };
    } else {
        // ç¼–辑或查看时填充数据
        form.value = { ...row };
        if (type === 'edit' && !form.value.handlerId) {
            form.value.handlerId = userStore.id;
            form.value.handleDate = getCurrentDate();
        }
    }
}
const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            // æ¨¡æ‹Ÿæäº¤æ“ä½œ
            setTimeout(() => {
                console.log("模拟提交的数据:", form.value);
                proxy.$modal.msgSuccess(operationType.value === 'add' ? "新增成功" : "更新成功");
                closeDia();
            }, 300);
        }
    });
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
    proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/customerService/expiryAfterSales/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,258 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">临期日期:</span>
                <el-date-picker
                    v-model="searchForm.expiryDate"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    type="date"
                    placeholder="请选择"
                    clearable
                    @change="handleQuery"
                />
                <span class="search_title ml10">处理日期:</span>
                <el-date-picker
                    v-model="searchForm.handleDate"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    type="date"
                    placeholder="请选择"
                    clearable
                    @change="handleQuery"
                />
        <span style = "margin-left: 10px;" class="search_title">处理状态:</span>
        <el-select v-model="searchForm.status" placeholder="请选择状态" @change="handleQuery" style="width: 140px" clearable>
          <el-option label="待处理" :value="1"></el-option>
          <el-option label="已处理" :value="2"></el-option>
        </el-select>
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
                <el-button @click="resetQuery" style="margin-left: 10px"
                >重置</el-button
                >
            </div>
        </div>
        <div class="table_actions" style="margin-bottom: 10px;">
            <el-button type="primary" @click="openForm('add')">新增</el-button>
            <el-button type="danger" @click="handleDelete">删除</el-button>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
            >
                <!-- è¡¨æ ¼æ’æ§½ -->
                <template #status="{ row }">
                    <el-tag :type="row.status === 1 ? 'warning' : 'success'">
                        {{ row.status === 1 ? '待处理' : '已处理' }}
                    </el-tag>
                </template>
                <template #operation="{ row }">
                    <el-button type="primary" link @click="openForm('view', row)">查看</el-button>
                    <el-button type="primary" link @click="openForm('edit', row)" v-if="row.status === 1">编辑</el-button>
                </template>
            </PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    </div>
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import FormDia from "@/views/customerService/expiryAfterSales/components/formDia.vue";
import {ElMessageBox} from "element-plus";
// import {expiryAfterSalesDelete, expiryAfterSalesListPage} from "@/api/customerService/index.js"; // æš‚时注释掉,使用假数据
import useUserStore from "@/store/modules/user.js";
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
const data = reactive({
    searchForm: {
        expiryDate: "",
        handleDate: "",
        status: ""
    },
    tableData: [],
    page: {
        current: 1,
        size: 10,
        total: 0,
    },
    selectedRows: [],
    tableLoading: false,
    formDia: null,
    tableColumn: [
        {
            label: "临期产品名称",
            prop: "productName",
            width: "",
        },
        {
            label: "产品批号",
            prop: "batchNumber",
            width: "",
        },
        {
            label: "临期日期",
            prop: "expiryDate",
            width: "",
        },
        {
            label: "库存数量",
            prop: "stockQuantity",
            width: "",
        },
        {
            label: "客户名称",
            prop: "customerName",
            width: "",
        },
        {
            label: "问题描述",
            prop: "problemDesc",
            width: "",
        },
        {
            label: "处理状态",
            prop: "status",
            width: "",
            slot: true,
        },
        {
            label: "处理人",
            prop: "handlerName",
            width: "",
        },
        {
            label: "处理日期",
            prop: "handleDate",
            width: "",
        },
        {
            label: "操作",
            prop: "operation",
            slot: true,
            width: "200",
        },
    ],
});
const {
    searchForm,
    tableData,
    page,
    selectedRows,
    tableLoading,
    formDia,
    tableColumn,
} = toRefs(data);
// æŸ¥è¯¢
const handleQuery = () => {
    page.value.current = 1;
    getList();
};
// é€‰æ‹©
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
// é‡ç½®
const resetQuery = () => {
    proxy.resetForm("queryRef");
    searchForm.value = {
        expiryDate: "",
        handleDate: "",
        status: ""
    };
    handleQuery();
};
// åˆ†é¡µ
const pagination = (obj) => {
    page.value.current = obj.page;
    page.value.size = obj.limit;
    getList();
};
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
    tableLoading.value = true;
    // å–消注释并使用真实API
    // expiryAfterSalesListPage({
    //     ...searchForm.value,
    //     current: page.value.current,
    //     size: page.value.size
    // }).then(res => {
    //     tableData.value = res.data.records;
    //     page.value.total = res.data.total;
    //     tableLoading.value = false;
    // });
    // æš‚时返回空数据
    tableData.value = [];
    page.value.total = 0;
    tableLoading.value = false;
};
// æ‰“开弹框
const openForm = (type, row) => {
    nextTick(() => {
        formDia.value?.openDialog(type, row)
    })
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            tableLoading.value = true;
            // å–消注释并使用真实API
            // expiryAfterSalesDelete(ids).then(() => {
            //     proxy.$modal.msgSuccess("删除成功");
            //     getList();
            // }).finally(() => {
            //     tableLoading.value = false;
            // });
            // æš‚时模拟删除成功
            tableLoading.value = false;
            proxy.$modal.msgSuccess("删除成功");
            getList();
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped>
</style>
src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -81,6 +81,7 @@
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -149,14 +150,6 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
src/views/customerService/feedbackRegistration/index.vue
@@ -23,6 +23,7 @@
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button @click="handleOut">导出</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
@@ -44,7 +45,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/customerService/feedbackRegistration/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {afterSalesServiceDelete, afterSalesServiceListPage} from "@/api/customerService/index.js";
@@ -120,7 +121,7 @@
                    openForm("edit", row);
                },
                disabled: (row) => {
                    return row.checkUserId !== userStore.id || row.status !== 1
                    return row.status !== 1
                }
            },
        ],
@@ -202,6 +203,22 @@
            proxy.$modal.msg("已取消");
        });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/afterSalesService/export", {}, "反馈登记.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
src/views/energyManagement/carbonManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1553 @@
<template>
  <div class="carbon-management">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <div class="page-header">
      <div class="header-content">
        <h1 class="page-title">碳排放管理系统</h1>
        <p class="page-subtitle">基于ISO 14064标准 Â· GHG Protocol核算标准</p>
      </div>
      <div class="header-stats">
        <div class="stat-item">
          <span class="stat-label">总碳排放量</span>
          <span class="stat-value">{{totalEmissions}} tCO₂e</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">本月减排</span>
          <span class="stat-value reduction">-{{monthlyReduction}}%</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">碳中和进度</span>
          <span class="stat-value">{{neutralProgress}}%</span>
        </div>
      </div>
    </div>
    <!-- ä¸»è¦å†…容区域 -->
    <div class="dashboard-content">
      <!-- é¡¶éƒ¨æ•°æ®é¢æ¿ -->
      <div class="top-panels">
        <div class="data-panel top-left">
          <div class="panel-title">当前碳排放</div>
          <div class="panel-value">{{carbonData.scope1}} <span class="unit">tCO₂e</span></div>
          <div class="panel-subtitle">范围1直接排放</div>
        </div>
        <div class="data-panel top-center">
          <div class="panel-title">能耗监测</div>
          <div class="panel-value">{{carbonData.scope2}} <span class="unit">tCO₂e</span></div>
          <div class="panel-subtitle">范围2间接排放</div>
        </div>
        <div class="data-panel top-right">
          <div class="panel-title">供应链排放</div>
          <div class="panel-value">{{carbonData.scope3}} <span class="unit">tCO₂e</span></div>
          <div class="panel-subtitle">范围3供应链排放</div>
        </div>
        <div class="data-panel top-far-right">
          <div class="panel-title">减排进度</div>
          <div class="panel-value">{{neutralProgress}} <span class="unit">%</span></div>
          <div class="panel-subtitle">碳中和目标</div>
        </div>
      </div>
      <!-- ä¸­å¿ƒä¸»è§†å›¾åŒºåŸŸ -->
      <div class="center-main-view">
        <!-- å·¦ä¾§æŽ§åˆ¶é¢æ¿ -->
        <div class="left-control-panel">
          <div class="control-section">
            <div class="section-title">碳排放范围</div>
            <el-radio-group v-model="selectedScope" @change="updateScopeData" class="vertical-radio">
              <el-radio-button :value="'all'">全部范围</el-radio-button>
              <el-radio-button :value="'scope1'">范围1</el-radio-button>
              <el-radio-button :value="'scope2'">范围2</el-radio-button>
              <el-radio-button :value="'scope3'">范围3</el-radio-button>
            </el-radio-group>
          </div>
          <div class="control-section">
            <div class="section-title">监测层级</div>
            <el-radio-group v-model="heatmapLevel" @change="updateHeatmapLevel" class="vertical-radio">
              <el-radio-button :value="'device'">设备级</el-radio-button>
              <el-radio-button :value="'line'">产线级</el-radio-button>
              <el-radio-button :value="'enterprise'">企业级</el-radio-button>
            </el-radio-group>
          </div>
        </div>
        <!-- ä¸­å¿ƒçƒ­åЛ图 -->
        <div class="main-heatmap">
          <div class="heatmap-header">
            <h2 class="main-title">碳足迹热力图分析</h2>
            <div class="date-selector">
              <el-date-picker
                v-model="selectedDate"
                type="date"
                placeholder="选择日期"
                size="small"
                @change="updateHeatmapData"
              />
            </div>
          </div>
          <div class="heatmap-view">
            <Echarts ref="heatmapChart"
                     :series="heatmapSeries"
                     :xAxis="heatmapXAxis"
                     :yAxis="heatmapYAxis"
                     :tooltip="heatmapTooltip"
                     :visualMap="heatmapVisualMap"
                     :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                     style="height: 450px"></Echarts>
          </div>
        </div>
        <!-- å³ä¾§æ•°æ®é¢æ¿ -->
        <div class="right-data-panel">
          <div class="data-section">
            <div class="section-title">实时监控</div>
            <div class="mini-chart">
              <Echarts ref="realtimeChart"
                       :series="realtimeSeries"
                       :xAxis="realtimeXAxis"
                                             :chartStyle="chartStyle"
                       :yAxis="realtimeYAxis"
                       :tooltip="realtimeTooltip"
                       :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                       style="height: 300px"></Echarts>
            </div>
          </div>
          <div class="data-section">
            <div class="section-title">趋势分析</div>
            <div class="trend-controls">
              <el-radio-group v-model="trendPeriod" size="small" @change="updateTrendData">
                <el-radio-button :value="'week'">周</el-radio-button>
                <el-radio-button :value="'month'">月</el-radio-button>
                <el-radio-button :value="'year'">å¹´</el-radio-button>
              </el-radio-group>
            </div>
            <div class="mini-chart">
              <Echarts ref="trendChart"
                       :series="trendSeries"
                       :xAxis="trendXAxis"
                       :yAxis="trendYAxis"
                       :tooltip="trendTooltip"
                                             :chartStyle="chartStyle"
                       :legend="trendLegend"
                       :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                       style="height: 200px"></Echarts>
            </div>
          </div>
        </div>
      </div>
      <!-- åº•部进度面板 -->
      <div class="bottom-progress-panel">
        <div class="progress-section">
          <div class="progress-title">2024年减排目标</div>
          <div class="progress-data">
            <span class="current">{{reductionTarget.current}}</span>
            <span class="separator">/</span>
            <span class="target">{{reductionTarget.target}} tCO₂e</span>
          </div>
          <el-progress :percentage="reductionTarget.percentage" :stroke-width="6" color="#00E676"/>
        </div>
        <div class="progress-section">
          <div class="progress-title">碳中和进度</div>
          <div class="progress-data">
            <span class="current">{{neutralTarget.current}}</span>
            <span class="separator">/</span>
            <span class="target">{{neutralTarget.target}} tCO₂e</span>
          </div>
          <el-progress :percentage="neutralTarget.percentage" :stroke-width="6" color="#00D4FF"/>
        </div>
      </div>
      <!-- åº•部数据表格 -->
      <div class="bottom-data-table">
        <div class="table-panel">
          <div class="table-header">
            <h3 class="table-title">碳排放详细数据</h3>
            <div class="table-controls">
              <el-input
                v-model="searchKeyword"
                placeholder="搜索设备或产线"
                size="small"
                style="width: 200px; margin-right: 10px;"
              />
              <el-button type="primary" size="small" @click="exportData">导出数据</el-button>
            </div>
          </div>
          <el-table :data="filteredTableData" style="width: 100%" height="180">
            <el-table-column prop="name" label="设备/产线" width="150"/>
            <el-table-column prop="type" label="类型" width="100"/>
            <el-table-column prop="scope1" label="范围1排放" width="120"/>
            <el-table-column prop="scope2" label="范围2排放" width="120"/>
            <el-table-column prop="scope3" label="范围3排放" width="120"/>
            <el-table-column prop="total" label="总排放量" width="120"/>
            <el-table-column prop="efficiency" label="碳效率" width="100"/>
            <el-table-column prop="status" label="状态" width="100">
              <template #default="scope">
                <el-tag :type="getStatusType(scope.row.status)">{{scope.row.status}}</el-tag>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/Echarts/echarts.vue'
// å“åº”式数据
const selectedScope = ref('all')
const heatmapLevel = ref('device')
const selectedDate = ref(new Date())
const trendPeriod = ref('week')
const searchKeyword = ref('')
// ç¢³æŽ’放数据
const carbonData = ref({
  scope1: 125.6,
  scope2: 89.3,
  scope3: 234.7
})
const chartStyle = {
    width: '96%',
    height: '110%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
// è®¡ç®—属性
const totalEmissions = computed(() => {
  return (carbonData.value.scope1 + carbonData.value.scope2 + carbonData.value.scope3).toFixed(1)
})
const monthlyReduction = ref(8.5)
// è®¡ç®—碳中和进度百分比
const neutralProgress = computed(() => {
  return Math.round(neutralTarget.value.percentage)
})
// å‡æŽ’目标数据
const reductionTarget = ref({
  current: 320.5,
  target: 500,
  percentage: 64.1
})
const neutralTarget = ref({
  current: 1250,
  target: 3800,
  percentage: 32.9
})
// å®žæ—¶ç›‘控图表配置
const realtimeSeries = ref([
  {
    name: '实时碳排放',
    type: 'line',
    smooth: true,
    data: generateRealtimeData(),
    itemStyle: {
      color: '#FF6B6B'
    },
    areaStyle: {
      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
        { offset: 0, color: 'rgba(255, 107, 107, 0.3)' },
        { offset: 1, color: 'rgba(255, 107, 107, 0.1)' }
      ])
    }
  }
])
const realtimeXAxis = [{
  type: 'category',
  data: Array.from({length: 24}, (_, i) => `${i.toString().padStart(2, '0')}:00`),
  axisLabel: { color: '#B8C8E0' }
}]
const realtimeYAxis = [{
  type: 'value',
  name: 'tCO₂e/h',
  axisLabel: { color: '#B8C8E0' },
  nameTextStyle: { color: '#B8C8E0' }
}]
const realtimeTooltip = {
  trigger: 'axis',
  formatter: '{b}: {c} tCO₂e/h'
}
// çƒ­åŠ›å›¾é…ç½®
const heatmapSeries = ref([
  {
    name: '碳排放量',
    type: 'heatmap',
    data: generateHeatmapData(),
    label: {
      show: false
    },
    emphasis: {
      itemStyle: {
        shadowBlur: 10,
        shadowColor: 'rgba(0, 0, 0, 0.5)'
      }
    }
  }
])
const heatmapXAxis = [{
  type: 'category',
  data: Array.from({length: 24}, (_, i) => `${i}:00`),
  splitArea: { show: true },
  axisLabel: { color: '#B8C8E0' }
}]
const heatmapYAxis = [{
  type: 'category',
  data: ['设备A', '设备B', '设备C', '设备D', '设备E', '设备F', '设备G'],
  splitArea: { show: true },
  axisLabel: { color: '#B8C8E0' }
}]
const heatmapTooltip = {
  trigger: 'item',
  formatter: function (params) {
    const [hour, device] = params.data
    const value = params.value[2]
    return `设备: ${heatmapYAxis[0].data[device]}<br/>时间: ${hour}:00<br/>碳排放量: ${value} tCO₂e`
  }
}
const heatmapVisualMap = ref({
  min: 0,
  max: 50,
  calculable: true,
  orient: 'horizontal',
  left: 'center',
  bottom: '5%',
  inRange: {
    color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
  },
  textStyle: { color: '#B8C8E0' }
})
// è¶‹åŠ¿åˆ†æžå›¾è¡¨é…ç½®
const trendSeries = ref([
  {
    name: '范围1',
    type: 'line',
    data: [120, 132, 101, 134, 90, 230, 210],
    itemStyle: { color: '#FF6B6B' }
  },
  {
    name: '范围2',
    type: 'line',
    data: [220, 182, 191, 234, 290, 330, 310],
    itemStyle: { color: '#4ECDC4' }
  },
  {
    name: '范围3',
    type: 'line',
    data: [150, 232, 201, 154, 190, 330, 410],
    itemStyle: { color: '#45B7D1' }
  }
])
const trendXAxis = [{
  type: 'category',
  data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
  axisLabel: { color: '#B8C8E0' }
}]
const trendYAxis = [{
  type: 'value',
  name: 'tCO₂e',
  axisLabel: { color: '#B8C8E0' },
  nameTextStyle: { color: '#B8C8E0' }
}]
const trendTooltip = {
  trigger: 'axis'
}
const trendLegend = {
  data: ['范围1', '范围2', '范围3'],
  textStyle: { color: '#B8C8E0' }
}
// è¡¨æ ¼æ•°æ®
const carbonTableData = ref([
  { name: '生产线A', type: '产线', scope1: 45.2, scope2: 32.1, scope3: 18.7, total: 96.0, efficiency: '良好', status: '正常' },
  { name: '设备B-01', type: '设备', scope1: 12.5, scope2: 8.3, scope3: 5.2, total: 26.0, efficiency: '优秀', status: '正常' },
  { name: '生产线C', type: '产线', scope1: 38.7, scope2: 28.9, scope3: 15.4, total: 83.0, efficiency: '良好', status: '告警' },
  { name: '设备D-02', type: '设备', scope1: 15.8, scope2: 11.2, scope3: 7.1, total: 34.1, efficiency: '一般', status: '正常' },
  { name: '生产线E', type: '产线', scope1: 52.3, scope2: 39.6, scope3: 22.8, total: 114.7, efficiency: '待优化', status: '告警' }
])
// ç”Ÿæˆå®žæ—¶æ•°æ®
function generateRealtimeData() {
  return Array.from({length: 24}, () => (Math.random() * 20 + 10).toFixed(1))
}
// ç”Ÿæˆçƒ­åŠ›å›¾æ•°æ®
function generateHeatmapData() {
  const data = []
  let yAxisLength = 7 // é»˜è®¤è®¾å¤‡çº§
  let baseMultiplier = 1 // åŸºç¡€å€æ•°
  // æ ¹æ®å±‚级确定Y轴长度和数据范围
  if (heatmapLevel.value === 'line') {
    yAxisLength = 5
    baseMultiplier = 2 // äº§çº¿çº§æ•°æ®æ›´å¤§
  } else if (heatmapLevel.value === 'enterprise') {
    yAxisLength = 3
    baseMultiplier = 4 // ä¼ä¸šçº§æ•°æ®æœ€å¤§
  }
  for (let i = 0; i < yAxisLength; i++) {
    for (let j = 0; j < 24; j++) {
      let value
      // ç®€åŒ–的时间段逻辑
      if (j >= 8 && j <= 18) {
        // å·¥ä½œæ—¶é—´æŽ’放量较高
        value = Math.random() * 30 + 20
      } else if (j >= 19 && j <= 22) {
        // æ™šé—´æŽ’放量中等
        value = Math.random() * 20 + 10
      } else {
        // æ·±å¤œå’Œå‡Œæ™¨æŽ’放量较低
        value = Math.random() * 10 + 2
      }
      // æ·»åŠ è®¾å¤‡å·®å¼‚å’Œå±‚çº§å€æ•°
      value *= (0.8 + i * 0.1) * baseMultiplier
      data.push([j, i, Math.round(value * 10) / 10])
    }
  }
  return data
}
// æ›´æ–°èŒƒå›´æ•°æ®
function updateScopeData() {
  // æ ¹æ®é€‰æ‹©çš„范围更新所有相关图表数据
  const scopeMultiplier = {
    'all': 1,
    'scope1': 0.3,
    'scope2': 0.4,
    'scope3': 0.3
  }
  const multiplier = scopeMultiplier[selectedScope.value] || 1
  // æ›´æ–°ç¢³æŽ’放数据显示
  if (selectedScope.value === 'all') {
    carbonData.value = {
      scope1: 125.6,
      scope2: 89.3,
      scope3: 234.7
    }
  } else {
    const baseTotal = 125.6 + 89.3 + 234.7
    carbonData.value = {
      scope1: selectedScope.value === 'scope1' ? 125.6 : 0,
      scope2: selectedScope.value === 'scope2' ? 89.3 : 0,
      scope3: selectedScope.value === 'scope3' ? 234.7 : 0
    }
  }
  // æ›´æ–°çƒ­åŠ›å›¾æ•°æ®
  heatmapSeries.value[0].data = generateHeatmapData().map(item => [
    item[0], item[1], Math.round(item[2] * multiplier * 10) / 10
  ])
  // æ›´æ–°å®žæ—¶ç›‘控数据
  realtimeSeries.value[0].data = generateRealtimeData().map(val =>
    Math.round(parseFloat(val) * multiplier * 10) / 10
  )
}
// æ›´æ–°çƒ­åŠ›å›¾å±‚çº§
function updateHeatmapLevel() {
  // æ ¹æ®å±‚级更新Y轴数据和visualMap范围
  if (heatmapLevel.value === 'device') {
    heatmapYAxis[0].data = ['锅炉A', '压缩机B', '冷却塔C', '风机D', 'æ³µE', '变压器F', '电机G']
    heatmapVisualMap.value.max = 50
  } else if (heatmapLevel.value === 'line') {
    heatmapYAxis[0].data = ['生产线1', '生产线2', '生产线3', '生产线4', '生产线5']
    heatmapVisualMap.value.max = 100
  } else {
    heatmapYAxis[0].data = ['厂区A', '厂区B', '厂区C']
    heatmapVisualMap.value.max = 200
  }
  // æ›´æ–°çƒ­åŠ›å›¾æ•°æ®
  heatmapSeries.value[0].data = generateHeatmapData()
  // æ›´æ–°è¡¨æ ¼æ•°æ®ä»¥åŒ¹é…å½“前层级
  updateTableDataForLevel()
}
// æ ¹æ®å±‚级更新表格数据
function updateTableDataForLevel() {
  const levelConfigs = {
    device: [
      { name: '锅炉A', type: '设备', scope1: 45.2, scope2: 32.1, scope3: 18.7, total: 96.0, efficiency: '良好', status: '正常' },
      { name: '压缩机B', type: '设备', scope1: 38.5, scope2: 28.3, scope3: 15.2, total: 82.0, efficiency: '优秀', status: '正常' },
      { name: '冷却塔C', type: '设备', scope1: 22.8, scope2: 18.9, scope3: 12.3, total: 54.0, efficiency: '良好', status: '告警' },
      { name: '风机D', type: '设备', scope1: 15.6, scope2: 12.4, scope3: 8.1, total: 36.1, efficiency: '一般', status: '正常' },
      { name: 'æ³µE', type: '设备', scope1: 12.3, scope2: 9.8, scope3: 6.4, total: 28.5, efficiency: '优秀', status: '正常' }
    ],
    line: [
      { name: '生产线1', type: '产线', scope1: 125.6, scope2: 89.3, scope3: 56.8, total: 271.7, efficiency: '良好', status: '正常' },
      { name: '生产线2', type: '产线', scope1: 98.4, scope2: 72.1, scope3: 45.2, total: 215.7, efficiency: '优秀', status: '正常' },
      { name: '生产线3', type: '产线', scope1: 87.2, scope2: 65.8, scope3: 41.6, total: 194.6, efficiency: '良好', status: '告警' },
      { name: '生产线4', type: '产线', scope1: 76.9, scope2: 58.3, scope3: 37.1, total: 172.3, efficiency: '一般', status: '正常' },
      { name: '生产线5', type: '产线', scope1: 65.7, scope2: 49.2, scope3: 31.8, total: 146.7, efficiency: '待优化', status: '告警' }
    ],
    enterprise: [
      { name: '厂区A', type: '厂区', scope1: 456.8, scope2: 334.7, scope3: 212.5, total: 1004.0, efficiency: '良好', status: '正常' },
      { name: '厂区B', type: '厂区', scope1: 387.2, scope2: 289.6, scope3: 184.3, total: 861.1, efficiency: '优秀', status: '正常' },
      { name: '厂区C', type: '厂区', scope1: 298.5, scope2: 223.8, scope3: 142.7, total: 665.0, efficiency: '良好', status: '告警' }
    ]
  }
  carbonTableData.value = levelConfigs[heatmapLevel.value] || levelConfigs.device
}
// æ›´æ–°çƒ­åŠ›å›¾æ•°æ®ï¼ˆæ—¥æœŸå˜åŒ–æ—¶ï¼‰
function updateHeatmapData() {
  heatmapSeries.value[0].data = generateHeatmapData()
  // åŒæ—¶æ›´æ–°å…¶ä»–相关数据
  updateScopeData()
}
// æ›´æ–°è¶‹åŠ¿æ•°æ®
function updateTrendData() {
  const trendDataConfigs = {
    week: {
      xAxisData: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
      scope1Data: [120, 132, 101, 134, 90, 80, 75],
      scope2Data: [220, 182, 191, 234, 190, 150, 140],
      scope3Data: [150, 232, 201, 154, 190, 120, 110]
    },
    month: {
      xAxisData: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
      scope1Data: [1200, 1150, 1300, 1250, 1180, 1320, 1280, 1350, 1220, 1290, 1160, 1100],
      scope2Data: [2200, 2100, 2350, 2280, 2150, 2400, 2320, 2450, 2180, 2380, 2120, 2050],
      scope3Data: [1800, 1750, 1950, 1880, 1820, 2000, 1920, 2100, 1850, 1980, 1780, 1720]
    },
    year: {
      xAxisData: ['2019', '2020', '2021', '2022', '2023', '2024'],
      scope1Data: [14500, 14200, 13800, 13500, 13100, 12800],
      scope2Data: [26800, 26200, 25600, 25000, 24400, 23800],
      scope3Data: [22400, 21800, 21200, 20600, 20000, 19400]
    }
  }
  const config = trendDataConfigs[trendPeriod.value] || trendDataConfigs.week
  // æ›´æ–°X轴数据
  trendXAxis[0].data = config.xAxisData
  // æ›´æ–°ç³»åˆ—数据
  trendSeries.value = [
    {
      name: '范围1',
      type: 'line',
      data: config.scope1Data,
      itemStyle: { color: '#FF6B6B' },
      smooth: true
    },
    {
      name: '范围2',
      type: 'line',
      data: config.scope2Data,
      itemStyle: { color: '#4ECDC4' },
      smooth: true
    },
    {
      name: '范围3',
      type: 'line',
      data: config.scope3Data,
      itemStyle: { color: '#45B7D1' },
      smooth: true
    }
  ]
}
// èŽ·å–çŠ¶æ€ç±»åž‹
function getStatusType(status) {
  switch (status) {
    case '正常': return 'success'
    case '告警': return 'warning'
    case '异常': return 'danger'
    default: return 'info'
  }
}
// å¯¼å‡ºæ•°æ®
function exportData() {
  // å‡†å¤‡å¯¼å‡ºæ•°æ®
  const exportDataSet = {
    åŸºæœ¬ä¿¡æ¯: {
      å¯¼å‡ºæ—¶é—´: new Date().toLocaleString('zh-CN'),
      æ•°æ®å±‚级: heatmapLevel.value === 'device' ? '设备级' : heatmapLevel.value === 'line' ? '产线级' : '企业级',
      é€‰æ‹©èŒƒå›´: selectedScope.value === 'all' ? '全部范围' : `范围${selectedScope.value.slice(-1)}`,
      é€‰æ‹©æ—¥æœŸ: selectedDate.value ? selectedDate.value.toLocaleDateString('zh-CN') : '今日'
    },
    ç¢³æŽ’放统计: {
      èŒƒå›´1直接排放: carbonData.value.scope1 + ' tCO₂e',
      èŒƒå›´2间接排放: carbonData.value.scope2 + ' tCO₂e',
      èŒƒå›´3供应链排放: carbonData.value.scope3 + ' tCO₂e',
      æ€»æŽ’放量: totalEmissions.value + ' tCO₂e'
    },
    è¯¦ç»†æ•°æ®: carbonTableData.value,
    çƒ­åŠ›å›¾æ•°æ®: heatmapSeries.value[0].data.map(item => ({
      æ—¶é—´: `${item[0]}:00`,
      è®¾å¤‡åºå·: item[1],
      è®¾å¤‡åç§°: heatmapYAxis.data[item[1]],
      ç¢³æŽ’放量: item[2] + ' tCO₂e'
    }))
  }
  // åˆ›å»ºCSV内容
  let csvContent = '\uFEFF' // BOM for UTF-8
  // åŸºæœ¬ä¿¡æ¯
  csvContent += '基本信息\n'
  Object.entries(exportDataSet.基本信息).forEach(([key, value]) => {
    csvContent += `${key},${value}\n`
  })
  csvContent += '\n'
  // ç¢³æŽ’放统计
  csvContent += '碳排放统计\n'
  Object.entries(exportDataSet.碳排放统计).forEach(([key, value]) => {
    csvContent += `${key},${value}\n`
  })
  csvContent += '\n'
  // è¯¦ç»†æ•°æ®è¡¨æ ¼
  csvContent += '详细数据\n'
  csvContent += '名称,类型,范围1排放,范围2排放,范围3排放,总排放量,碳效率,状态\n'
  exportDataSet.详细数据.forEach(row => {
    csvContent += `${row.name},${row.type},${row.scope1},${row.scope2},${row.scope3},${row.total},${row.efficiency},${row.status}\n`
  })
  csvContent += '\n'
  // çƒ­åŠ›å›¾æ•°æ®ï¼ˆå‰50条)
  csvContent += '热力图数据(前50条)\n'
  csvContent += '时间,设备名称,碳排放量\n'
  exportDataSet.热力图数据.slice(0, 50).forEach(row => {
    csvContent += `${row.时间},${row.设备名称},${row.碳排放量}\n`
  })
  // åˆ›å»ºä¸‹è½½é“¾æŽ¥
  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
  const link = document.createElement('a')
  const url = URL.createObjectURL(blob)
  link.setAttribute('href', url)
  link.setAttribute('download', `碳排放数据_${new Date().toISOString().slice(0, 10)}.csv`)
  link.style.visibility = 'hidden'
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  // æ˜¾ç¤ºæˆåŠŸæ¶ˆæ¯
  console.log('碳排放数据导出成功')
}
// æœç´¢è¿‡æ»¤åŠŸèƒ½
const filteredTableData = computed(() => {
  if (!searchKeyword.value) {
    return carbonTableData.value
  }
  return carbonTableData.value.filter(item =>
    item.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
    item.type.toLowerCase().includes(searchKeyword.value.toLowerCase())
  )
})
// çƒ­åŠ›å›¾ç‚¹å‡»äº‹ä»¶å¤„ç†
function handleHeatmapClick(params) {
  if (params.componentType === 'series') {
    const [hour, deviceIndex, value] = params.data
    const deviceName = heatmapYAxis.data[deviceIndex]
    console.log(`点击了设备: ${deviceName}, æ—¶é—´: ${hour}:00, æŽ’放量: ${value} tCO₂e`)
    // å¯ä»¥åœ¨è¿™é‡Œæ·»åŠ è¯¦ç»†ä¿¡æ¯å¼¹çª—æˆ–è·³è½¬åˆ°è¯¦ç»†é¡µé¢
  }
}
onMounted(() => {
  // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–æ“ä½œ
  console.log('碳管理页面已加载')
  // åˆå§‹åŒ–热力图数据
  updateHeatmapLevel()
  // åˆå§‹åŒ–趋势数据
  updateTrendData()
  // åˆå§‹åŒ–范围数据
  updateScopeData()
  // è®¾ç½®å®šæ—¶å™¨ï¼Œæ¯30秒更新一次实时数据
  const timer = setInterval(() => {
    realtimeSeries.value[0].data = generateRealtimeData()
  }, 30000)
  // æ¸…理定时器
  onBeforeUnmount(() => {
    clearInterval(timer)
  })
})
// æ·»åŠ çƒ­åŠ›å›¾ç‚¹å‡»äº‹ä»¶ç»‘å®š
function bindHeatmapEvents() {
  // è¿™ä¸ªå‡½æ•°å¯ä»¥ç”¨æ¥ç»‘定热力图的点击事件
  // åœ¨å®žé™…使用中,可以通过ECharts的事件系统来实现
}
</script>
<style scoped>
.carbon-management {
  min-height: 100vh;
  background:
    radial-gradient(ellipse at top, rgba(29, 78, 216, 0.15), transparent 50%),
    radial-gradient(ellipse at bottom, rgba(139, 92, 246, 0.15), transparent 50%),
    linear-gradient(135deg, #0a0f1c 0%, #1e293b 25%, #0f172a 50%, #1e293b 75%, #0a0f1c 100%);
  padding: 20px;
  font-family: 'Inter', 'Microsoft YaHei', sans-serif;
  overflow: hidden;
  position: relative;
}
.carbon-management::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background:
    radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
    radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.1) 0%, transparent 50%),
    radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.05) 0%, transparent 50%);
  pointer-events: none;
}
.page-header {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.95) 0%, rgba(30, 41, 59, 0.9) 100%),
    radial-gradient(circle at top right, rgba(59, 130, 246, 0.1), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.2);
  border-radius: 20px;
  padding: 40px;
  margin-bottom: 30px;
  box-shadow:
    0 25px 50px -12px rgba(0, 0, 0, 0.4),
    0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
  overflow: hidden;
  backdrop-filter: blur(20px);
}
.page-header:hover {
  transform: translateY(-2px);
  box-shadow:
    0 32px 64px -12px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.1),
    inset 0 1px 0 rgba(255, 255, 255, 0.15);
}
.page-header::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background:
    linear-gradient(45deg, rgba(59, 130, 246, 0.08) 0%, rgba(147, 51, 234, 0.08) 50%, rgba(236, 72, 153, 0.08) 100%);
  pointer-events: none;
}
.header-content {
  flex: 1;
  position: relative;
  z-index: 1;
}
.page-title {
  font-size: 28px;
  font-weight: bold;
  color: #ffffff;
  margin: 0 0 8px 0;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.page-subtitle {
  font-size: 14px;
  color: #B8C8E0;
  margin: 0;
}
.header-stats {
  display: flex;
  gap: 40px;
  position: relative;
  z-index: 1;
}
.stat-item {
  text-align: center;
  padding: 20px;
  background:
    linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(147, 51, 234, 0.15) 100%),
    radial-gradient(circle at center, rgba(255, 255, 255, 0.05), transparent 70%);
  border-radius: 12px;
  border: 1px solid rgba(148, 163, 184, 0.2);
  position: relative;
  overflow: hidden;
  backdrop-filter: blur(10px);
}
.stat-item:hover {
  transform: translateY(-2px) scale(1.05);
  box-shadow:
    0 20px 25px -5px rgba(59, 130, 246, 0.3),
    0 10px 10px -5px rgba(59, 130, 246, 0.2);
}
.stat-item::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
}
.stat-item:hover::before {
  left: 100%;
}
.stat-label {
  display: block;
  font-size: 12px;
  color: #94A3B8;
  margin-bottom: 8px;
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.stat-value {
  display: block;
  font-size: 28px;
  font-weight: 700;
  color: #00D4FF;
  text-shadow:
    0 0 20px rgba(0, 212, 255, 0.6),
    0 0 40px rgba(0, 212, 255, 0.3);
  position: relative;
  z-index: 1;
}
.stat-value.reduction {
  color: #00E676;
  text-shadow:
    0 0 20px rgba(0, 230, 118, 0.6),
    0 0 40px rgba(0, 230, 118, 0.3);
}
.dashboard-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-height: calc(100vh - 200px);
}
.top-panels {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  gap: 20px;
  height: 120px;
}
.data-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
    radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 10px 10px -5px rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}
.panel-title {
  font-size: 12px;
  color: #94A3B8;
  margin-bottom: 8px;
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.panel-value {
  font-size: 24px;
  font-weight: 700;
  color: #00D4FF;
  text-shadow:
    0 0 20px rgba(0, 212, 255, 0.6),
    0 0 40px rgba(0, 212, 255, 0.3);
  margin-bottom: 4px;
}
.panel-subtitle {
  font-size: 11px;
  color: #B8C8E0;
  font-weight: 400;
}
.unit {
  font-size: 16px;
  color: #94A3B8;
}
.center-main-view {
  display: grid;
  grid-template-columns: 200px 1fr 300px;
  gap: 20px;
  flex: 1;
}
.left-control-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.control-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.section-title {
  font-size: 14px;
  font-weight: 600;
  color: #ffffff;
  margin-bottom: 8px;
}
.vertical-radio {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.main-heatmap {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
}
.heatmap-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.2);
}
.main-title {
  font-size: 18px;
  font-weight: bold;
  color: #ffffff;
  margin: 0;
}
.date-selector {
  display: flex;
  align-items: center;
}
.heatmap-view {
  flex: 1;
}
.right-data-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.data-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.mini-chart {
  width: 100%;
}
.trend-controls {
  margin-bottom: 10px;
}
.bottom-progress-panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  height: 100px;
}
.progress-section {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.progress-title {
  font-size: 14px;
  font-weight: 600;
  color: #ffffff;
  margin-bottom: 8px;
}
.progress-data {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin-bottom: 12px;
}
.progress-data .current {
  font-size: 20px;
  font-weight: 700;
  color: #00D4FF;
}
.progress-data .separator {
  font-size: 16px;
  color: #94A3B8;
}
.progress-data .target {
  font-size: 14px;
  color: #B8C8E0;
}
.bottom-data-table {
  margin-top: 20px;
}
.table-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
    radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 10px 10px -5px rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(16px);
}
.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.2);
}
.table-title {
  font-size: 16px;
  font-weight: 600;
  color: #ffffff;
  margin: 0;
}
.table-controls {
  display: flex;
  align-items: center;
  gap: 10px;
}
.panel-card {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
    radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 24px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 10px 10px -5px rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  position: relative;
  overflow: hidden;
  backdrop-filter: blur(16px);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.heatmap-card {
  height: 500px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 1px solid rgba(81, 129, 219, 0.3);
  position: relative;
  z-index: 1;
}
.card-title {
  font-size: 18px;
  font-weight: bold;
  color: #ffffff;
  margin: 0;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.heatmap-controls {
  display: flex;
  align-items: center;
}
.scope-stats {
  display: flex;
  flex-direction: column;
  gap: 15px;
  position: relative;
  z-index: 1;
}
.scope-item {
  display: flex;
  align-items: center;
  padding: 20px;
  border-radius: 12px;
  background:
    linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(147, 51, 234, 0.12) 100%),
    radial-gradient(circle at top, rgba(255, 255, 255, 0.05), transparent 60%);
  border-left: 4px solid #00D4FF;
  border: 1px solid rgba(148, 163, 184, 0.15);
  position: relative;
  overflow: hidden;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  backdrop-filter: blur(8px);
}
.scope-item:hover {
  transform: translateY(-3px);
  box-shadow:
    0 15px 30px -5px rgba(59, 130, 246, 0.25),
    0 0 0 1px rgba(255, 255, 255, 0.1);
}
.scope-item::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, #3B82F6, #8B5CF6, #EC4899);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.scope-item:hover::after {
  opacity: 1;
}
/* ç¢³æŽ’放统计样式 */
.carbon-stats {
  display: flex;
  justify-content: space-between;
  margin-bottom: 20px;
  gap: 15px;
}
.carbon-stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 20px;
  background:
    linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(147, 51, 234, 0.12) 100%),
    radial-gradient(circle at top, rgba(255, 255, 255, 0.05), transparent 60%);
  border-radius: 12px;
  border: 1px solid rgba(148, 163, 184, 0.15);
  flex: 1;
  position: relative;
  overflow: hidden;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  backdrop-filter: blur(8px);
}
.carbon-stat-item:hover {
  transform: translateY(-3px);
  box-shadow:
    0 15px 30px -5px rgba(59, 130, 246, 0.25),
    0 0 0 1px rgba(255, 255, 255, 0.1);
}
.carbon-stat-item::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, #3B82F6, #8B5CF6, #EC4899);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.carbon-stat-item:hover::after {
  opacity: 1;
}
.carbon-label {
  color: #94A3B8;
  font-size: 11px;
  text-align: center;
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.carbon-value {
  color: #00D4FF;
  font-size: 18px;
  font-weight: 700;
  text-shadow:
    0 0 15px rgba(0, 212, 255, 0.6),
    0 0 30px rgba(0, 212, 255, 0.3);
  position: relative;
}
.scope-item.scope1 {
  border-left-color: #FF6B6B;
}
.scope-item.scope2 {
  border-left-color: #FFD93D;
}
.scope-item.scope3 {
  border-left-color: #6BCF7F;
}
.scope-icon {
  font-size: 24px;
  margin-right: 15px;
}
.scope-info {
  flex: 1;
}
.scope-name {
  display: block;
  font-weight: bold;
  color: #ffffff;
  margin-bottom: 5px;
}
.scope-value {
  display: block;
  font-size: 20px;
  font-weight: bold;
  color: #00D4FF;
  margin-bottom: 3px;
  text-shadow: 0 0 8px rgba(0, 212, 255, 0.5);
}
.scope-desc {
  display: block;
  font-size: 12px;
  color: #B8C8E0;
}
.target-progress {
  display: flex;
  flex-direction: column;
  gap: 20px;
  position: relative;
  z-index: 1;
}
.progress-item {
  padding: 15px;
  background: rgba(81, 129, 219, 0.1);
  border-radius: 8px;
  border: 1px solid rgba(81, 129, 219, 0.2);
}
.progress-info {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
.progress-label {
  font-weight: bold;
  color: #ffffff;
}
.progress-value {
  color: #B8C8E0;
  font-size: 14px;
}
.bottom-panel {
  margin-top: 20px;
}
.table-controls {
  display: flex;
  align-items: center;
}
/* Element Plus ç»„件深色主题样式 */
:deep(.el-table) {
  background: transparent !important;
  color: #ffffff !important;
}
:deep(.el-table th) {
  background: rgba(81, 129, 219, 0.2) !important;
  color: #ffffff !important;
  border-bottom: 1px solid rgba(81, 129, 219, 0.3) !important;
}
:deep(.el-table td) {
  background: transparent !important;
  color: #B8C8E0 !important;
  border-bottom: 1px solid rgba(81, 129, 219, 0.1) !important;
}
:deep(.el-table tr:hover > td) {
  background: rgba(81, 129, 219, 0.1) !important;
}
:deep(.el-input__wrapper) {
  background: rgba(15, 27, 46, 0.8) !important;
  border: 1px solid rgba(81, 129, 219, 0.3) !important;
  color: #ffffff !important;
}
:deep(.el-input__inner) {
  color: #ffffff !important;
}
:deep(.el-button--primary) {
  background: linear-gradient(135deg, #5181DB, #D369E0) !important;
  border: none !important;
  box-shadow: 0 0 10px rgba(81, 129, 219, 0.5) !important;
}
/* åž‚直单选按钮组样式 */
:deep(.vertical-radio) {
  display: flex !important;
  flex-direction: column !important;
  gap: 6px !important;
}
:deep(.vertical-radio .el-radio-button) {
  margin: 0 !important;
  width: 100% !important;
}
:deep(.vertical-radio .el-radio-button__inner) {
  background: rgba(59, 130, 246, 0.1) !important;
  border: 1px solid rgba(148, 163, 184, 0.2) !important;
  color: #B8C8E0 !important;
  border-radius: 8px !important;
  padding: 10px 16px !important;
  width: 100% !important;
  text-align: center !important;
  font-size: 12px !important;
  font-weight: 500 !important;
}
:deep(.vertical-radio .el-radio-button__inner:hover) {
  background: rgba(59, 130, 246, 0.2) !important;
  border-color: rgba(59, 130, 246, 0.4) !important;
  color: #ffffff !important;
}
:deep(.vertical-radio .el-radio-button.is-active .el-radio-button__inner) {
  background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important;
  border-color: #3B82F6 !important;
  color: #ffffff !important;
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
}
:deep(.vertical-radio .el-radio-button:first-child .el-radio-button__inner) {
  border-left: 1px solid rgba(148, 163, 184, 0.2) !important;
}
:deep(.el-radio-group .el-radio-button__inner) {
  background: rgba(59, 130, 246, 0.1) !important;
  border: 1px solid rgba(148, 163, 184, 0.2) !important;
  color: #B8C8E0 !important;
  border-radius: 6px !important;
  padding: 6px 12px !important;
  margin: 0 2px !important;
  font-size: 12px !important;
}
:deep(.el-radio-group .el-radio-button__inner:hover) {
  background: rgba(59, 130, 246, 0.2) !important;
  border-color: rgba(59, 130, 246, 0.4) !important;
  color: #ffffff !important;
}
:deep(.el-radio-group .el-radio-button.is-active .el-radio-button__inner) {
  background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important;
  border-color: #3B82F6 !important;
  color: #ffffff !important;
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
}
:deep(.el-date-editor .el-input__wrapper) {
  background: rgba(15, 27, 46, 0.8) !important;
  border: 1px solid rgba(81, 129, 219, 0.3) !important;
}
:deep(.el-progress-bar__outer) {
  background: rgba(81, 129, 219, 0.2) !important;
}
:deep(.el-tag) {
  background: rgba(81, 129, 219, 0.2) !important;
  border: 1px solid rgba(81, 129, 219, 0.3) !important;
  color: #ffffff !important;
}
:deep(.el-tag.el-tag--success) {
  background: rgba(0, 230, 118, 0.2) !important;
  border-color: rgba(0, 230, 118, 0.3) !important;
  color: #00E676 !important;
}
:deep(.el-tag.el-tag--warning) {
  background: rgba(255, 193, 7, 0.2) !important;
  border-color: rgba(255, 193, 7, 0.3) !important;
  color: #FFC107 !important;
}
:deep(.el-tag.el-tag--danger) {
  background: rgba(244, 67, 54, 0.2) !important;
  border-color: rgba(244, 67, 54, 0.3) !important;
  color: #F44336 !important;
}
/* å“åº”式设计 */
@media (max-width: 1200px) {
  .main-content {
    flex-direction: column;
  }
  .header-stats {
    gap: 20px;
  }
}
@media (max-width: 768px) {
  .page-header {
    flex-direction: column;
    text-align: center;
    gap: 20px;
  }
  .header-stats {
    justify-content: center;
  }
  .carbon-management {
    padding: 10px;
  }
}
</style>
src/views/energyManagement/dynamicEnergySaving/index.vue
@@ -237,21 +237,21 @@
    version: 'v2.1.0',
    status: 'active',
    accuracy: '94.2%',
    lastUpdate: '2024-01-15 14:30:00'
    lastUpdate: '2025-01-15 14:30:00'
  },
  {
    modelName: '地层压力预测模型',
    version: 'v1.8.5',
    status: 'active',
    accuracy: '91.7%',
    lastUpdate: '2024-01-14 09:15:00'
    lastUpdate: '2025-01-14 09:15:00'
  },
  {
    modelName: '能耗分析模型',
    version: 'v2.0.3',
    status: 'standby',
    accuracy: '89.3%',
    lastUpdate: '2024-01-13 16:45:00'
    lastUpdate: '2025-01-13 16:45:00'
  }
])
src/views/energyManagement/energyPeriodTime/index.vue
@@ -16,7 +16,8 @@
            v-model="searchForm.date"
            type="date"
            placeholder="请选择日期"
            :size="size"
            value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
        />
        <!-- <el-time-picker
            v-model="searchForm.timeRange"
@@ -41,6 +42,7 @@
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
@@ -160,7 +162,7 @@
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import {periodListPage,periodDelete,periodAdd,periodUpdate} from "@/api/energyManagement/index.js";
@@ -173,7 +175,7 @@
    },
    form: {
        date: "",
        price: "",
        price: "",
        peak: "",
        valley: "",
        flat: "",
@@ -183,7 +185,7 @@
const { searchForm,form } = toRefs(data);
const page = ref({
  current: 1,
  size: 10,
  size: 100,
  total: 0
});
const dialogFormVisible = ref(false);
@@ -316,14 +318,14 @@
};
const getList = () => {
    tableLoading.value = true;
    periodListPage({ ...searchForm, ...page.value }).then((res) => {
    periodListPage({ ...searchForm.value, ...page.value }).then((res) => {
            tableLoading.value = false;
            if (res && res.data) {
                tableData.value = res.data.records || [];
                page.total = res.data.total || 0;
                page.value.total = res.data.total || 0;
            } else {
                tableData.value = [];
                page.total = 0;
                page.value.total = 0;
                ElMessageBox.warning('未获取到数据');
            }
        })
@@ -434,6 +436,22 @@
        proxy.$modal.msg("已取消");
    });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/energyPeriod/export", {}, "用电时段管理.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
src/views/energyManagement/energyPower/components/formDia.vue
@@ -122,6 +122,7 @@
import {ref} from "vue";
import useUserStore from "@/store/modules/user.js";
import {deviceList, equipmentEnergyAdd, equipmentEnergyUpdate, areaListTree} from "@/api/energyManagement/index.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -217,14 +218,6 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
src/views/energyManagement/energyPower/index.vue
@@ -18,6 +18,7 @@
            <div>
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button type="info" plain icon="Upload" @click="handleImport">导入</el-button>
                <el-button @click="handleOut">导出</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
@@ -83,7 +84,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, getCurrentInstance} from "vue";
import FormDia from "@/views/energyManagement/energyPower/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
@@ -295,6 +296,22 @@
            proxy.$modal.msg("已取消");
        });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/equipmentEnergyConsumption/export", {}, "能源功率.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
src/views/energyManagement/energyTrends/index.vue
@@ -14,6 +14,7 @@
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
                <el-button @click="handleOut" style="margin-left: 10px">导出</el-button>
            </div>
        </div>
        <div class="table_list">
@@ -34,8 +35,11 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, getCurrentInstance} from "vue";
import {listPageByTrend} from "@/api/energyManagement/index.js";
import { ElMessageBox } from "element-plus";
const { proxy } = getCurrentInstance();
const data = reactive({
    searchForm: {
@@ -76,6 +80,7 @@
    },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
@@ -106,6 +111,22 @@
        page.total = res.data.total;
    });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/equipmentEnergyConsumption/exportTwo", {}, "能源趋势.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
src/views/energyManagement/gasManagement/index.vue
@@ -295,7 +295,7 @@
// æ¨¡æ‹Ÿæ•°æ®ç”Ÿæˆ
const generateMockData = () => {
  // æ›´æ–°ç»Ÿè®¡æ•°æ®
  totalDevices.value = Math.floor(Math.random() * 10) + 15 // 15-25台设备
  totalDevices.value = Math.floor(Math.random() * 8) // 0-7台设备
  dailyConsumption.value = Math.floor(Math.random() * 100) + 200 // 200-300 m³
  monthlyConsumption.value = Math.floor(Math.random() * 2000) + 5000 // 5000-7000 m³
  gasUnitPrice.value = (Math.random() * 2 + 3).toFixed(2) // 3-5元/m³
src/views/energyManagement/meterCollection/index.vue
@@ -227,7 +227,7 @@
        power: '75.5',
        powerFactor: '0.85',
        status: '正常',
        lastUpdateTime: '2024-01-15 10:30:00'
        lastUpdateTime: '2025-01-15 10:30:00'
      },
      {
        id: 2,
@@ -241,7 +241,7 @@
        power: '45.2',
        powerFactor: '0.92',
        status: '正常',
        lastUpdateTime: '2024-01-15 10:25:00'
        lastUpdateTime: '2025-01-15 10:25:00'
      }
    ]
    this.pagination.total = this.meterList.length
@@ -408,7 +408,7 @@
        power: '50.0',
        powerFactor: '0.85',
        status: '正常',
        lastUpdateTime: '2024-01-15 12:00:00'
        lastUpdateTime: '2025-01-15 12:00:00'
      }
      this.detailDialogVisible = true
    },
src/views/energyManagement/waterManagement/components/formDia.vue
@@ -128,6 +128,7 @@
import {ref, reactive, nextTick} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterEquipmentAdd, waterEquipmentUpdate} from "@/api/energyManagement/waterManagement.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -203,14 +204,6 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
src/views/energyManagement/waterManagement/components/waterBillForm.vue
@@ -109,6 +109,7 @@
import {ref, reactive, nextTick, watch} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterBillAdd, waterBillUpdate} from "@/api/energyManagement/waterManagement.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -192,14 +193,6 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
src/views/energyManagement/waterManagement/index.vue
@@ -18,6 +18,7 @@
            <div>
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button type="info" plain icon="Upload" @click="handleImport">导入</el-button>
                <el-button @click="handleOut">导出</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
@@ -83,7 +84,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, nextTick} from "vue";
import {onMounted, ref, reactive, nextTick, getCurrentInstance} from "vue";
import FormDia from "@/views/energyManagement/waterManagement/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
@@ -302,6 +303,22 @@
            proxy.$modal.msg("已取消");
        });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/waterRecord/export", {}, "用水管理.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
src/views/equipmentManagement/brand/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,217 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="品牌名称/国家">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入关键词"
          clearable
          prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="openAdd" icon="Plus"> æ–°å¢ž </el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleSelection.length <= 0"
            @click="handleBatchDelete"
          >批量删除</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close>
      <el-form :model="form" ref="formRef" :rules="rules" label-width="90px">
        <el-form-item label="品牌名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入品牌名称" />
        </el-form-item>
        <el-form-item label="所属国家" prop="country">
          <el-input v-model="form.country" placeholder="请输入国家/地区" />
        </el-form-item>
        <el-form-item label="描述" prop="description">
          <el-input v-model="form.description" type="textarea" :rows="3" placeholder="可填写品牌简介" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="visible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, getCurrentInstance, onMounted } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { usePaginationApi } from '@/hooks/usePaginationApi'
import { getBrandPage, addBrand, editBrand, delBrand } from '@/api/equipmentManagement/brand'
defineOptions({ name: '设备品牌管理' })
const { proxy } = getCurrentInstance()
const multipleSelection = ref([])
const formRef = ref()
const visible = ref(false)
const dialogTitle = ref('新增品牌')
const form = ref({ id: undefined, name: '', country: '', description: '' })
const rules = {
  name: [{ required: true, message: '请输入品牌名称', trigger: 'blur' }],
  country: [{ required: true, message: '请输入所属国家', trigger: 'blur' }]
}
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  getBrandPage,
  { name: undefined },
  [
    { label: '品牌名称', align: 'center', prop: 'name' },
    { label: '所属国家', align: 'center', prop: 'country' },
    { label: '描述', align: 'center', prop: 'description' },
    { label: '创建时间', align: 'center', prop: 'createdAt' },
    {
      dataType: 'action',
      label: '操作',
      align: 'center',
      fixed: 'right',
      width: 140,
      operation: [
        {
          name: '编辑',
          type: 'text',
          clickFun: (row) => openEdit(row),
        },
        {
          name: '删除',
          type: 'text',
          clickFun: (row) => handleDelete(row.id),
        }
      ]
    }
  ]
)
const handleSelectionChange = (list) => {
  multipleSelection.value = list
}
const changePage = ({ page, limit }) => {
  pagination.currentPage = page
  pagination.pageSize = limit
  onCurrentChange(page)
}
function resetForm() {
  form.value = { id: undefined, name: '', country: '', description: '' }
}
function openAdd() {
  resetForm()
  dialogTitle.value = '新增品牌'
  visible.value = true
}
function openEdit(row) {
  form.value = { id: row.id, name: row.name, country: row.country, description: row.description }
  dialogTitle.value = '编辑品牌'
  visible.value = true
}
function handleSubmit() {
  formRef.value.validate(async (valid) => {
    if (!valid) return
    const isEdit = Boolean(form.value.id)
    const api = isEdit ? editBrand : addBrand
    const { code, msg } = await api({ ...form.value })
    if (code === 200) {
      ElMessage.success(isEdit ? '修改成功' : '新增成功')
      visible.value = false
      getTableData()
    } else {
      ElMessage.error(msg || '操作失败')
    }
  })
}
function handleDelete(id) {
  ElMessageBox.confirm('此操作将永久删除该品牌, æ˜¯å¦ç»§ç»­?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    const { code } = await delBrand(id)
    if (code === 200) {
      ElMessage.success('删除成功')
      getTableData()
    }
  })
}
function handleBatchDelete() {
  if (multipleSelection.value.length === 0) return
  ElMessageBox.confirm('将删除选中的品牌,是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    const ids = multipleSelection.value.map((i) => i.id)
    const { code } = await delBrand(ids)
    if (code === 200) {
      ElMessage.success('删除成功')
      getTableData()
    }
  })
}
onMounted(() => {
  getTableData()
})
</script>
<style scoped lang="scss">
.table_list { margin-top: unset; }
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/equipmentManagement/calibration/index.vue
@@ -34,6 +34,7 @@
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
                <el-button @click="handleReset" style="margin-left: 10px">重置</el-button>
            </div>
            <div>
                <el-button @click="handleOut">导出</el-button>
@@ -56,11 +57,11 @@
</template>
<script setup>
import {onMounted, ref} from "vue";
import {ElMessageBox} from "element-plus";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
import useUserStore from "@/store/modules/user.js";
import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue";
import {ledgerRecordListPage} from "@/api/equipmentManagement/calibration.js";
import {ledgerRecordListPage, ledgerRecordDelete} from "@/api/equipmentManagement/calibration.js";
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -134,6 +135,7 @@
    {
        dataType: "action",
        label: "操作",
        width: 140,
        align: "center",
        fixed: 'right',
        operation: [
@@ -143,9 +145,16 @@
                clickFun: (row) => {
                    openCalibrationDia("edit", row);
                },
                disabled: (row) => {
                    return row.userId !== userStore.id
                }
            },
            {
                name: "删除",
                type: "text",
                style: {
                    color: "#F56C6C"
                },
                clickFun: (row) => {
                    handleDelete(row);
                },
            },
        ],
    },
@@ -171,6 +180,15 @@
    page.current = 1;
    getList();
};
// é‡ç½®æœç´¢æ¡ä»¶
const handleReset = () => {
    searchForm.value.recordDate = "";
    searchForm.value.entryDate = "";
    searchForm.value.code = "";
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
@@ -194,6 +212,26 @@
    })
}
// åˆ é™¤è®°å½•
const handleDelete = (row) => {
    ElMessageBox.confirm(`确认删除计量器具编号为"${row.code}"的检定记录吗?`, "删除确认", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            ledgerRecordDelete([row.id]).then(() => {
                ElMessage.success("删除成功");
                getList();
            }).catch(() => {
                ElMessage.error("删除失败");
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消删除");
        });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
src/views/equipmentManagement/defectManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,221 @@
<template>
  <div class="defect-management">
    <!-- æ“ä½œæŒ‰é’® -->
    <div class="actions">
      <el-button type="primary" @click="showRegisterDialog = true">登记缺陷</el-button>
    </div>
    <!-- ç¼ºé™·åˆ—表 -->
    <el-table :data="defectList" style="width: 100%; margin-top: 10px;" border>
      <el-table-column prop="deviceName" label="设备名称" width="180"></el-table-column>
      <el-table-column prop="defectDescription" label="缺陷描述" win-width="300"></el-table-column>
      <el-table-column prop="status" label="状态" width="220">
        <template #default="{ row }">
          <el-tag :type="row.status === '严重缺陷' ? 'danger' : 'success'">
            {{ row.status }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="220">
        <template #default="{ row }">
          <el-button
            v-if="row.status === '严重缺陷' || row.status === '一般缺陷'"
            type="text"
            @click="eliminateDefect(row)"
          >
            æ¶ˆé™¤ç¼ºé™·
          </el-button>
          <!-- <el-button
            v-if="row.status === '严重缺陷'"
            type="text"
            @click="transferToRepairOrder(row.id)"
          >
            è½¬ç»´ä¿®å•
          </el-button> -->
          <el-button type="text" @click="getLedger(row.deviceLedgerId)">
            æŸ¥çœ‹å°è´¦
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- ç¼ºé™·ç™»è®°å¯¹è¯æ¡† -->
    <el-dialog title="登记设备缺陷" v-model="showRegisterDialog" width="50%">
      <el-form :model="defectForm" :rules="defectRules" ref="defectFormRef" label-width="100px">
        <el-form-item label="设备名称" prop="deviceName">
          <el-select v-model="defectForm.deviceLedgerId" @change="setDeviceModel">
            <el-option
              v-for="(item, index) in deviceOptions"
              :key="index"
              :label="item.deviceName"
              :value="item.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="缺陷描述" prop="defectDescription">
          <el-input type="textarea" v-model="defectForm.defectDescription"></el-input>
        </el-form-item>
        <el-form-item label="设备状态" prop="status">
          <el-radio-group v-model="defectForm.status">
            <el-radio label="正常">正常</el-radio>
            <el-radio label="一般缺陷">一般缺陷</el-radio>
            <el-radio label="严重缺陷">严重缺陷</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showRegisterDialog = false">取消</el-button>
          <el-button type="primary" @click="submitDefectForm">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç¼ºé™·è®¾å¤‡å°è´¦å¯¹è¯æ¡† -->
    <el-dialog title="缺陷设备台账" v-model="showLedgerDialog" width="80%">
      <el-table :data="ledgerList" style="width: 100%; margin-top: 10px;" border>
        <el-table-column prop="deviceName" label="设备名称"></el-table-column>
        <el-table-column prop="defectDescription" label="缺陷描述"></el-table-column>
        <el-table-column prop="status" label="状态"></el-table-column>
        <el-table-column prop="eliminateTime" label="消缺时间"></el-table-column>
      </el-table>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
// å‡è®¾ä»¥ä¸‹æ˜¯åŽç«¯æŽ¥å£
import {
  registerDefect,
  getDefectList,
  eliminateDefect as apiEliminateDefect,
  getDefectLedger,
  deleteDefect
} from '@/api/equipmentManagement/defectManagement';
// ç¼ºé™·åˆ—表
const defectList = ref([]);
// ç™»è®°å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€
const showRegisterDialog = ref(false);
// å°è´¦å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€
const showLedgerDialog = ref(false);
// ç¼ºé™·è¡¨å•
const defectForm = reactive({
  deviceLedgerId: '',
  defectDescription: '',
  status: '',
});
const deviceOptions = ref([]);
// è¡¨å•验证规则
const defectRules = reactive({
  deviceLedgerId: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
  defectDescription: [{ required: true, message: '请输入缺陷描述', trigger: 'blur' }]
});
// è¡¨å•引用
const defectFormRef = ref(null);
// å°è´¦åˆ—表
const ledgerList = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  // console.log(data);
  deviceOptions.value = data;
};
// èŽ·å–ç¼ºé™·åˆ—è¡¨
const fetchDefectList = async () => {
  try {
    const res = await getDefectList();
    if (res.code === 200) {
      defectList.value = res.data.records;
    } else {
      ElMessage.error(res.message || '获取缺陷列表失败');
    }
  } catch (error) {
    ElMessage.error('获取缺陷列表失败');
  }
};
// æäº¤ç¼ºé™·ç™»è®°è¡¨å•
const submitDefectForm = async () => {
  if (!defectFormRef.value) return;
  try {
    await defectFormRef.value.validate();
    const res = await registerDefect(defectForm);
    if (res.code === 200) {
      ElMessage.success('缺陷登记成功');
      showRegisterDialog.value = false;
      fetchDefectList();
    } else {
      ElMessage.error(res.message || '缺陷登记失败');
    }
  } catch (error) {
    ElMessage.error('请填写完整表单信息');
  }
};
// æ¶ˆé™¤ç¼ºé™·
const eliminateDefect = async (row) => {
  try {
    const res = await apiEliminateDefect(row);
    if (res.code === 200) {
      ElMessage.success('缺陷消除成功');
      fetchDefectList();
    } else {
      ElMessage.error(res.message || '缺陷消除失败');
    }
  } catch (error) {
    ElMessage.error('缺陷消除失败');
  }
};
// // è½¬ç»´ä¿®å·¥å•
// const transferToRepairOrder = async (id) => {
//   try {
//     const res = await transferToRepair(id);
//     if (res.code === 200) {
//       ElMessage.success('转维修工单成功');
//     } else {
//       ElMessage.error(res.message || '转维修工单失败');
//     }
//   } catch (error) {
//     ElMessage.error('转维修工单失败');
//   }
// };
// èŽ·å–ç¼ºé™·è®¾å¤‡å°è´¦
const getLedger = async (deviceLedgerId) => {
  try {
    const res = await getDefectLedger(deviceLedgerId);
    if (res.code === 200) {
      ledgerList.value = res.data.records;
      showLedgerDialog.value = true;
    } else {
      ElMessage.error(res.message || '获取缺陷设备台账失败');
    }
  } catch (error) {
    ElMessage.error('获取缺陷设备台账失败');
  }
};
// ç»„件挂载时获取缺陷列表
const onMounted = () => {
  fetchDefectList();
  loadDeviceName();
};
onMounted();
</script>
<style scoped>
.defect-management {
  padding: 20px;
}
.actions {
  margin-bottom: 10px;
}
</style>
src/views/equipmentManagement/deviceInfo/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,190 @@
<template>
  <div class="device-info-container">
    <div class="page-header">
      <h1>设备信息</h1>
      <div class="device-status" :class="deviceStatusClass">
        {{ deviceStatusText }}
      </div>
    </div>
    <div class="info-card">
      <div class="card-header">基本信息</div>
      <div class="card-content">
        <div class="info-row">
          <span class="label">设备名称:</span>
          <span class="value">{{ deviceInfo.deviceName }}</span>
        </div>
        <div class="info-row">
          <span class="label">规格型号:</span>
          <span class="value">{{ deviceInfo.deviceModel }}</span>
        </div>
        <div class="info-row">
          <span class="label">生产厂家:</span>
          <span class="value">{{ deviceInfo.supplierName }}</span>
        </div>
        <div class="info-row">
          <span class="label">单位:</span>
          <span class="value">{{ deviceInfo.unit }}</span>
        </div>
      </div>
    </div>
         <div class="info-card">
       <div class="card-header">维护信息</div>
       <div class="card-content">
         <div class="maintenance-info">
           <div class="maintenance-item">
             <span class="label">最后维护:</span>
             <span class="value">{{ deviceInfo.updateTime }}</span>
           </div>
           <div class="maintenance-item">
             <span class="label">下次维护:</span>
             <span class="value">{{ deviceInfo.createTime }}</span>
           </div>
           <div class="maintenance-item">
             <span class="label">维护状态:</span>
             <span class="value status-normal">{{ deviceInfo.statusText }}</span>
           </div>
         </div>
       </div>
     </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
  getDeviceInfo,
} from '@/api/equipmentManagement/deviceInfo'
const route = useRoute()
const deviceInfo = reactive({
  deviceName: '',
  deviceModel: '',
  supplierName: '',
  unit: '',
  statusText:'正常',
  updateTime:'',
  createTime:''
})
const deviceStatusClass = computed(() => {
  return 'status-normal'
})
const deviceStatusText = computed(() => {
  return '正常'
})
const fetchDeviceInfo = async (deviceId) => {
  try {
    // èŽ·å–è®¾å¤‡ä¿¡æ¯
    const deviceResponse = await getDeviceInfo({id:deviceId})
    if (deviceResponse.code === 200) {
      Object.assign(deviceInfo, deviceResponse.data)
    }
  } catch (error) {
    ElMessage.warning('使用模拟数据,实际API调用失败')
  }
}
onMounted(() => {
  const deviceId = route.query.deviceId || route.params.deviceId || ''
  fetchDeviceInfo(deviceId)
})
</script>
<style scoped>
.device-info-container {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 20px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.page-header {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
  padding: 20px;
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.page-header h1 {
  margin: 0;
  color: #2c3e50;
  font-size: 24px;
}
.device-status {
  padding: 8px 16px;
  border-radius: 20px;
  font-size: 14px;
  color: white;
  background: #52c41a;
}
.info-card {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
  margin-bottom: 20px;
  overflow: hidden;
}
.card-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 16px 20px;
  font-weight: 500;
}
.card-content {
  padding: 20px;
}
.info-row, .maintenance-item {
  display: flex;
  margin-bottom: 12px;
  align-items: center;
}
.label {
  width: 100px;
  color: #666;
  font-size: 14px;
}
.value {
  flex: 1;
  color: #2c3e50;
  font-weight: 500;
}
.status-normal {
  color: #52c41a;
}
@media (max-width: 768px) {
  .device-info-container {
    padding: 16px;
  }
  .page-header h1 {
    font-size: 20px;
  }
  .label {
    width: 80px;
  }
}
</style>
src/views/equipmentManagement/gasTank/simple.vue
@@ -233,7 +233,7 @@
const maintenanceRecords = ref([
  {
    id: 1,
    date: '2024-01-15',
    date: '2025-01-15',
    type: 'inspection',
    title: '年度检验',
    description: '按照TSG 21-2016标准进行年度检验,设备状态良好',
@@ -241,7 +241,7 @@
  },
  {
    id: 2,
    date: '2024-02-20',
    date: '2025-02-20',
    type: 'maintenance',
    title: '安全阀维护',
    description: '更换安全阀密封圈,校准压力设定值',
@@ -249,7 +249,7 @@
  },
  {
    id: 3,
    date: '2024-03-10',
    date: '2025-03-10',
    type: 'inspection',
    title: '压力测试',
    description: '进行压力容器水压试验,符合设计要求',
src/views/equipmentManagement/inspectionManagement/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
<template>
    <div>
        <el-dialog :title="operationType === 'add' ? '新增巡检任务' : '编辑巡检任务'"
                             v-model="dialogVisitable" width="800px" @close="cancel">
            <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
                <el-row>
                    <el-col :span="12">
                        <el-form-item label="设备名称" prop="taskId">
                            <el-select v-model="form.taskId" @change="setDeviceModel" filterable>
                                <el-option
                                    v-for="(item, index) in deviceOptions"
                                    :key="index"
                                    :label="item.deviceName"
                                    :value="item.id"
                                ></el-option>
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="巡检人" prop="inspector">
                            <el-select v-model="form.inspector"                 filterable
                                                 default-first-option
                                                 :reserve-keyword="false" placeholder="请选择" multiple clearable>
                                <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row>
                    <el-col :span="12">
                        <el-form-item label="备注" prop="remarks">
                            <el-input v-model="form.remarks" placeholder="请输入备注" type="textarea" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="登记时间" prop="dateStr">
                            <el-date-picker
                                v-model="form.dateStr"
                                type="date"
                                placeholder="选择登记日期"
                                format="YYYY-MM-DD"
                                value-format="YYYY-MM-DD"
                                style="width: 100%"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row>
                    <el-col :span="12">
                        <el-form-item label="任务频率" prop="frequencyType">
                            <el-select v-model="form.frequencyType" placeholder="请选择" clearable>
                                <el-option label="每日" value="DAILY"/>
                                <el-option label="每周" value="WEEKLY"/>
                                <el-option label="每月" value="MONTHLY"/>
                                <!-- <el-option label="季度" value="QUARTERLY"/> -->
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
                        <el-form-item label="日期" prop="frequencyDetail">
                            <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm"
                                                            value-format="HH:mm" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
                        <el-form-item label="日期" prop="frequencyDetail">
                            <el-select v-model="form.week" placeholder="请选择" clearable style="width: 50%">
                                <el-option label="周一" value="MON"/>
                                <el-option label="周二" value="TUE"/>
                                <el-option label="周三" value="WED"/>
                                <el-option label="周四" value="THU"/>
                                <el-option label="周五" value="FRI"/>
                                <el-option label="周六" value="SAT"/>
                                <el-option label="周日" value="SUN"/>
                            </el-select>
                            <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm"
                                                            value-format="HH:mm"  style="width: 50%"/>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
                        <el-form-item label="日期" prop="frequencyDetail">
                            <el-date-picker
                                v-model="form.frequencyDetail"
                                type="datetime"
                                clearable
                                placeholder="选择开始日期"
                                format="DD,HH:mm"
                                value-format="DD,HH:mm"
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
                        <el-form-item label="日期" prop="frequencyDetail">
                            <el-date-picker
                                v-model="form.frequencyDetail"
                                type="datetime"
                                clearable
                                placeholder="选择开始日期"
                                format="MM,DD,HH:mm"
                                value-format="MM,DD,HH:mm"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button @click="cancel">取消</el-button>
                    <el-button type="primary" @click="submitForm">保存</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
<script setup>
import {reactive, ref, getCurrentInstance, toRefs} from "vue";
import useUserStore from '@/store/modules/user'
import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const userStore = useUserStore()
const dialogVisitable = ref(false);
const operationType = ref('add');
const deviceOptions = ref([]);
const data = reactive({
    form: {
        taskId: undefined,
        taskName: undefined,
        inspector: '',
        inspectorIds: '',
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        dateStr: ''
    },
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
        inspector: [{ required: true, message: "请输入巡检人", trigger: "blur" },],
        dateStr: [{ required: true, message: "请选择登记时间", trigger: "change" }],
        frequencyType: [{ required: true, message: "请选择任务频率", trigger: "change" }],
        frequencyDetail: [
            {
                required: true,
                message: "请选择日期",
                trigger: "change",
                validator: (rule, value, callback) => {
                    if (!form.value.frequencyType) {
                        callback()
                        return
                    }
                    if (form.value.frequencyType === 'WEEKLY') {
                        if (!form.value.week || !form.value.time) {
                            callback(new Error("请选择日期和时间"))
                        } else {
                            callback()
                        }
                    } else {
                        if (!value) {
                            callback(new Error("请选择日期"))
                        } else {
                            callback()
                        }
                    }
                }
            }
        ],
        week: [
            {
                required: true,
                message: "请选择星期",
                trigger: "change",
                validator: (rule, value, callback) => {
                    if (form.value.frequencyType === 'WEEKLY' && !value) {
                        callback(new Error("请选择星期"))
                    } else {
                        callback()
                    }
                }
            }
        ],
        time: [
            {
                required: true,
                message: "请选择时间",
                trigger: "change",
                validator: (rule, value, callback) => {
                    if (form.value.frequencyType === 'WEEKLY' && !value) {
                        callback(new Error("请选择时间"))
                    } else {
                        callback()
                    }
                }
            }
        ]
    }
})
const { form, rules } = toRefs(data)
const userList = ref([])
const loadDeviceName = async () => {
    const { data } = await getDeviceLedger();
    deviceOptions.value = data;
};
const setDeviceModel = (id) => {
    const option = deviceOptions.value.find((item) => item.id === id);
    if (option) {
        form.value.taskName = option.deviceName;
    }
}
// æ‰“开弹框
const openDialog = async (type, row) => {
    dialogVisitable.value = true
    operationType.value = type
    // é‡ç½®è¡¨å•
    resetForm();
    // åŠ è½½ç”¨æˆ·åˆ—è¡¨
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    // åŠ è½½è®¾å¤‡åˆ—è¡¨
    await loadDeviceName();
    if (type === 'edit' && row) {
        form.value = {...row}
        form.value.inspector = form.value.inspectorIds.split(',').map(Number)
        // å¦‚果有设备ID,自动设置设备信息
        if (form.value.taskId) {
            setDeviceModel(form.value.taskId);
        }
    }
}
// å…³é—­å¯¹è¯æ¡†
const cancel = () => {
    resetForm()
    dialogVisitable.value = false
    emit('closeDia')
}
// é‡ç½®è¡¨å•函数
const resetForm = () => {
    if (proxy.$refs.formRef) {
        proxy.$refs.formRef.resetFields()
    }
    // é‡ç½®è¡¨å•数据确保设备信息正确重置
    form.value = {
        taskId: undefined,
        taskName: undefined,
        inspector: '',
        inspectorIds: '',
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: ''
    }
}
// æäº¤è¡¨å•
const submitForm = () => {
    proxy.$refs["formRef"].validate(async valid => {
        if (valid) {
            try {
                form.value.inspectorIds = form.value.inspector.join(',')
                delete form.value.inspector
                if (form.value.frequencyType === 'WEEKLY') {
                    let frequencyDetail = ''
                    frequencyDetail = form.value.week + ',' + form.value.time
                    form.value.frequencyDetail = frequencyDetail
                }
                let res = await userStore.getInfo()
                form.value.registrantId = res.user.userId
                await addOrEditTimingTask(form.value)
                cancel()
                proxy.$modal.msgSuccess('提交成功')
            } catch (error) {
                proxy.$modal.msgError('提交失败,请重试')
            }
        }
    })
}
defineExpose({ openDialog })
</script>
<style scoped>
</style>
src/views/equipmentManagement/inspectionManagement/components/qrCodeDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
<template>
  <div>
    <el-dialog :title="operationType === 'add' ? '新增二维码' : '编辑二维码'"
               v-model="dialogVisitable" width="500px" @close="cancel">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
        <el-row>
          <el-col :span="24">
            <el-form-item label="设备名称" prop="deviceName">
              <el-input v-model="form.deviceName" placeholder="请输入设备名称" maxlength="30" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="所在位置描述" prop="location">
              <el-input v-model="form.location" placeholder="请输入所在位置描述" maxlength="30"/>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div>
        <el-button type="primary" @click="submitForm">生成并打印二维码</el-button>
      </div>
      <div v-if="isShowQrCode" class="print-section" ref="qrCodeContainer" id="qrCodeContainer">
        <vue-qrcode :value="qrCodeValue" :width="qrCodeSize"></vue-qrcode>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import useUserStore from "@/store/modules/user.js";
import {reactive, ref} from "vue";
import printJS from 'print-js';
import {addOrEditQrCode} from "@/api/inspectionUpload/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const userStore = useUserStore()
const dialogVisitable = ref(false);
const isShowQrCode = ref(false);
const operationType = ref('add');
const qrCodeValue = ref('');
const qrCodeSize = ref(100);
const data = reactive({
  form: {
    deviceName: '',
    location: '',
    qrCodeId: '',
    id: ''
  },
  rules: {
    deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
    location: [{ required: true, message: '请输入地点', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// æ‰“开弹框
const openDialog = async (type, row) => {
  dialogVisitable.value = true
  qrCodeValue.value = ''
  isShowQrCode.value = false;
  if (type === 'edit') {
    form.value.id = row.id
    form.value.qrCodeId = row.id
    form.value.deviceName = row.deviceName
    form.value.location = row.location
    // å°†è¡¨å•数据转为 JSON å­—符串作为二维码内容
    qrCodeValue.value = JSON.stringify(form.value);
    isShowQrCode.value = true;
  }
}
// æäº¤åˆå¹¶è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      addOrEditQrCode(form.value).then((res) => {
        form.value.qrCodeId = res.data
      })
      // å°†è¡¨å•数据转为 JSON å­—符串作为二维码内容
      qrCodeValue.value = JSON.stringify(form.value);
      isShowQrCode.value = true;
      showQrCode()
    }
  })
}
const showQrCode = () => {
  // å»¶è¿Ÿæ‰§è¡Œæ‰“印,避免 DOM æ›´æ–°å‰å°±è°ƒç”¨æ‰“印
  setTimeout(() => {
    printJS({
      printable: 'qrCodeContainer',//页面
      type: "html",//文档类型
      maxWidth: 360,
      style: `@page {
                margin:0;
                size: 400px 75px collapse;
                margin-top:3px;
                &:first-of-type{
                  margin-top:0 !important;
                }
              }
              html{
                zoom:100%;
              }
              @media print{
                width: 400px;
                height: 75px;
                margin:0;
              }`,
      targetStyles: ["*"], // ä½¿ç”¨dom的所有样式,很重要
      font_size: '0.20cm',
    });
  }, 300);
}
// å…³é—­åˆå¹¶è¡¨å•
const cancel = () => {
  proxy.resetForm("formRef")
  dialogVisitable.value = false
  emit('closeDia')
}
defineExpose({ openDialog })
</script>
<style scoped>
.print-section {
  text-align: center;
  margin-top: 30px;
}
</style>
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,246 @@
<template>
  <div>
    <el-dialog title="查看附件"
               v-model="dialogVisitable" width="800px" @close="cancel">
      <div class="upload-container">
        <!-- ç”Ÿäº§å‰ -->
        <div class="form-container">
          <div class="title">生产前</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in beforeProductionImgs" :key="index"
                 @click="showMedia(beforeProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in beforeProductionVideos"
                :key="index"
                @click="showMedia(beforeProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
        <!-- ç”Ÿäº§åŽ -->
        <div class="form-container">
          <div class="title">生产中</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in afterProductionImgs" :key="index"
                 @click="showMedia(afterProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in afterProductionVideos"
                :key="index"
                @click="showMedia(afterProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
        <!-- ç”Ÿäº§é—®é¢˜ -->
        <div class="form-container">
          <div class="title">生产后</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in productionIssuesImgs" :key="index"
                 @click="showMedia(productionIssuesImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in productionIssuesVideos"
                :key="index"
                @click="showMedia(productionIssuesVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
    <!-- ç»Ÿä¸€åª’体查看器 -->
    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
      <div class="media-viewer-content" @click.stop>
        <!-- å›¾ç‰‡ -->
        <vue-easy-lightbox
            v-if="mediaType === 'image'"
            :visible="isMediaViewerVisible"
            :imgs="mediaList"
            :index="currentMediaIndex"
            @hide="closeMediaViewer"
        ></vue-easy-lightbox>
        <!-- è§†é¢‘ -->
        <div v-else-if="mediaType === 'video'" style="position: relative;">
          <Video
              :src="mediaList[currentMediaIndex]"
              autoplay
              controls
              style="max-width: 90vw; max-height: 80vh;"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import VueEasyLightbox from 'vue-easy-lightbox';
// æŽ§åˆ¶å¼¹çª—显示
const dialogVisitable = ref(false);
// å›¾ç‰‡æ•°ç»„
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
// è§†é¢‘数组
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = ref([]);
// åª’体查看器状态
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
const mediaList = ref([]); // å­˜å‚¨å½“前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
// å¤„理每一类数据:分离图片和视频
function processItems(items) {
  const images = [];
  const videos = [];
  items.forEach(item => {
    if (item.contentType?.startsWith('image/')) {
      images.push(item.url);
    } else if (item.contentType?.startsWith('video/')) {
      videos.push(item.url);
    }
  });
  return { images, videos };
}
// æ‰“开弹窗并加载数据
const openDialog = async (row) => {
  const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
  const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues);
  beforeProductionImgs.value = beforeImgs;
  beforeProductionVideos.value = beforeVids;
  afterProductionImgs.value = afterImgs;
  afterProductionVideos.value = afterVids;
  productionIssuesImgs.value = issueImgs;
  productionIssuesVideos.value = issueVids;
  dialogVisitable.value = true;
};
// æ˜¾ç¤ºåª’体(图片 or è§†é¢‘)
function showMedia(mediaArray, index, type) {
  mediaList.value = mediaArray;
  currentMediaIndex.value = index;
  mediaType.value = type;
  isMediaViewerVisible.value = true;
}
// å…³é—­åª’体查看器
function closeMediaViewer() {
  isMediaViewerVisible.value = false;
  mediaList.value = [];
  mediaType.value = 'image';
}
// è¡¨å•关闭方法
const cancel = () => {
  dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  .form-container {
    flex: 1;
    width: 100%;
    margin-bottom: 20px;
  }
}
.title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 3px;
    width: 4px;
    height: 14px;
    background-color: #165dff;
  }
}
.media-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
}
</style>
src/views/equipmentManagement/inspectionManagement/components/viewQrCodeFiles.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,169 @@
<template>
  <div>
    <el-dialog title="查看附件"
               v-model="dialogVisitable" width="800px" @close="cancel">
      <div class="upload-container">
        <div class="form-container">
          <div class="title">巡检附件</div>
          <!-- å›¾ç‰‡åˆ—表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in beforeProductionImgs" :key="index"
                 @click="showMedia(beforeProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- è§†é¢‘列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in beforeProductionVideos"
                :key="index"
                @click="showMedia(beforeProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
    <!-- ç»Ÿä¸€åª’体查看器 -->
    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
      <div class="media-viewer-content" @click.stop>
        <!-- å›¾ç‰‡ -->
        <vue-easy-lightbox
            v-if="mediaType === 'image'"
            :visible="isMediaViewerVisible"
            :imgs="mediaList"
            :index="currentMediaIndex"
            @hide="closeMediaViewer"
        ></vue-easy-lightbox>
        <!-- è§†é¢‘ -->
        <div v-else-if="mediaType === 'video'" style="position: relative;">
          <Video
              :src="mediaList[currentMediaIndex]"
              autoplay
              controls
              style="max-width: 90vw; max-height: 80vh;"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
// æŽ§åˆ¶å¼¹çª—显示
import VueEasyLightbox from "vue-easy-lightbox";
const dialogVisitable = ref(false);
// å›¾ç‰‡æ•°ç»„
const beforeProductionImgs = ref([]);
// è§†é¢‘数组
const beforeProductionVideos = ref([]);
// åª’体查看器状态
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
const mediaList = ref([]); // å­˜å‚¨å½“前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
// æ‰“开弹窗并加载数据
const openDialog = async (row) => {
  const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO);
  beforeProductionImgs.value = beforeImgs;
  beforeProductionVideos.value = beforeVids;
  dialogVisitable.value = true;
};
// æ˜¾ç¤ºåª’体(图片 or è§†é¢‘)
function showMedia(mediaArray, index, type) {
  mediaList.value = mediaArray;
  currentMediaIndex.value = index;
  mediaType.value = type;
  isMediaViewerVisible.value = true;
}
// å…³é—­åª’体查看器
function closeMediaViewer() {
  isMediaViewerVisible.value = false;
  mediaList.value = [];
  mediaType.value = 'image';
}
// è¡¨å•关闭方法
const cancel = () => {
  dialogVisitable.value = false;
};
// å¤„理每一类数据:分离图片和视频
function processItems(items) {
  const images = [];
  const videos = [];
  items.forEach(item => {
    if (item.contentType?.startsWith('image/')) {
      images.push(item.url);
    } else if (item.contentType?.startsWith('video/')) {
      videos.push(item.url);
    }
  });
  return { images, videos };
}
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  .form-container {
    flex: 1;
    width: 100%;
    margin-bottom: 20px;
  }
}
.title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 3px;
    width: 4px;
    height: 14px;
    background-color: #165dff;
  }
}
.media-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
}
</style>
src/views/equipmentManagement/inspectionManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,354 @@
<template>
    <div class="app-container">
        <el-form :inline="true" :model="queryParams" class="search-form">
            <el-form-item label="巡检任务名称">
                <el-input
                    v-model="queryParams.taskName"
                    placeholder="请输入巡检任务名称"
                    clearable
                    :style="{ width: '100%' }"
                />
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="handleQuery">查询</el-button>
                <el-button @click="resetQuery">重置</el-button>
            </el-form-item>
        </el-form>
        <el-card>
            <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;">
                <el-radio-group v-model="activeRadio" @change="radioChange">
                    <el-radio-button v-for="tab in radios"
                                                     :key="tab.name"
                                                     :label="tab.label"
                                                     :value="tab.name"/>
                </el-radio-group>
                <!-- æ“ä½œæŒ‰é’®åŒº -->
                <el-space v-if="activeRadio !== 'task'">
                    <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">新建</el-button>
                    <el-button type="danger" :icon="Delete" @click="handleDelete">删除</el-button>
                    <el-button @click="handleOut">导出</el-button>
                </el-space>
                <el-space v-else>
                    <el-button @click="handleOut">导出</el-button>
                </el-space>
            </div>
            <div>
                <div>
                    <PIMTable :table-loading="tableLoading"
                                        :table-data="tableData"
                                        :column="tableColumns"
                                        @selection-change="handleSelectionChange"
                                        :is-selection="true"
                                        :border="true"
                                        :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }"
                                        :page="{
          current: pageNum,
          size: pageSize,
          total: total,
        }"
                                        @pagination="pagination"
                    >
                        <template #inspector="{ row }">
                            <div class="person-tags">
                                <!-- è°ƒè¯•信息,上线时删除 -->
                                <!-- {{ console.log('inspector data:', row.inspector) }} -->
                                <template v-if="row.inspector && row.inspector.length > 0">
                                    <el-tag
                                        v-for="(person, index) in row.inspector"
                                        :key="index"
                                        size="small"
                                        type="primary"
                                        class="person-tag"
                                    >
                                        {{ person }}
                                    </el-tag>
                                </template>
                                <span v-else class="no-data">--</span>
                            </div>
                        </template>
                    </PIMTable>
                </div>
            </div>
        </el-card>
        <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
        <view-files ref="viewFiles"></view-files>
    </div>
</template>
<script setup>
import { Delete, Plus } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue";
import { ElMessageBox } from "element-plus";
// ç»„件引入
import Pagination from "@/components/Pagination/index.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
// æŽ¥å£å¼•å…¥
import {
    delTimingTask,
    inspectionTaskList,
    timingTaskList
} from "@/api/inspectionManagement/index.js";
// å…¨å±€å˜é‡
const { proxy } = getCurrentInstance();
const formDia = ref();
const viewFiles = ref();
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({
    taskName: "",
});
// å•选框配置
const activeRadio = ref("taskManage");
const radios = reactive([
    { name: "taskManage", label: "定时任务管理" },
    { name: "task", label: "定时任务记录" },
]);
// è¡¨æ ¼æ•°æ®
const selectedRows = ref([]);
const tableData = ref([]);
const operationsArr = ref([]);
const tableColumns = ref([]);
const tableLoading = ref(false);
const total = ref(0);
const pageNum = ref(1);
const pageSize = ref(10);
// åˆ—配置
const columns = ref([
    { prop: "taskName", label: "巡检任务名称", minWidth: 160 },
    { prop: "remarks", label: "备注", minWidth: 150 },
    { prop: "inspector", label: "执行巡检人", minWidth: 150, slot: "inspector" },
    {
        prop: "frequencyType",
        label: "频次",
        minWidth: 150,
        formatData: (cell) => ({
            DAILY: "每日",
            WEEKLY: "每周",
            MONTHLY: "每月",
            QUARTERLY: "季度"
        }[cell] || "")
    },
    {
        prop: "frequencyDetail",
        label: "开始日期与时间",
        minWidth: 150,
        formatter: (row, column, cellValue) => {
            // å…ˆåˆ¤æ–­æ˜¯å¦æ˜¯å­—符串
            if (typeof cellValue !== 'string') return '';
            let val = cellValue;
            const replacements = {
                MON: '周一',
                TUE: '周二',
                WED: '周三',
                THU: '周四',
                FRI: '周五',
                SAT: '周六',
                SUN: '周日'
            };
            // ä½¿ç”¨æ­£åˆ™ä¸€æ¬¡æ€§æ›¿æ¢æ‰€æœ‰åŒ¹é…é¡¹
            return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
        }
    },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    { prop: "dateStr", label: "登记日期", minWidth: 100 },
]);
// æ“ä½œåˆ—配置
const getOperationColumn = (operations) => {
    if (!operations || operations.length === 0) return null;
    const operationConfig = {
        label: "操作",
        width: 130,
        fixed: "right",
        dataType: "action",
        operation: operations.map(op => {
            switch (op) {
                case 'edit':
                    return {
                        name: "编辑",
                        clickFun: handleAdd,
                        color: "#409EFF"
                    };
                case 'viewFile':
                    return {
                        name: "查看附件",
                        clickFun: viewFile,
                        color: "#67C23A"
                    };
                default:
                    return null;
            }
        }).filter(Boolean)
    };
    return operationConfig;
};
onMounted(() => {
    radioChange('taskManage');
});
// å•选变化
const radioChange = (value) => {
    if (value === "taskManage") {
        const operationColumn = getOperationColumn(['edit']);
        tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
        operationsArr.value = ['edit'];
    } else if (value === "task") {
        const operationColumn = getOperationColumn(['viewFile']);
        tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
        operationsArr.value = ['viewFile'];
    }
    pageNum.value = 1;
    pageSize.value = 10;
    getList();
};
// æŸ¥è¯¢æ“ä½œ
const handleQuery = () => {
    pageNum.value = 1;
    pageSize.value = 10;
    getList();
};
const pagination = (obj) => {
    pageNum.value = obj.page;
    pageSize.value = obj.limit;
    getList();
};
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
    tableLoading.value = true;
    const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
    let apiCall;
    if (activeRadio.value === "task") {
        apiCall = inspectionTaskList(params);
    } else {
        apiCall = timingTaskList(params);
    }
    apiCall.then(res => {
        const rawData = res.data.records || [];
        // å¤„理 inspector å­—段,将字符串转换为数组(适用于所有情况)
        tableData.value = rawData.map(item => {
            const processedItem = { ...item };
            // å¤„理 inspector å­—段
            if (processedItem.inspector) {
                if (typeof processedItem.inspector === 'string') {
                    // å­—符串按逗号分割
                    processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s);
                } else if (!Array.isArray(processedItem.inspector)) {
                    // éžæ•°ç»„转为数组
                    processedItem.inspector = [processedItem.inspector];
                }
            } else {
                // ç©ºå€¼è®¾ä¸ºç©ºæ•°ç»„
                processedItem.inspector = [];
            }
            return processedItem;
        });
        total.value = res.data.total || 0;
    }).finally(() => {
        tableLoading.value = false;
    });
};
// é‡ç½®æŸ¥è¯¢
const resetQuery = () => {
    for (const key in queryParams) {
        if (!["pageNum", "pageSize"].includes(key)) {
            queryParams[key] = "";
        }
    }
    handleQuery();
};
// æ–°å¢ž / ç¼–辑
const handleAdd = (row) => {
    const type = row ? 'edit' : 'add';
    nextTick(() => {
        formDia.value?.openDialog(type, row);
    });
};
// æŸ¥çœ‹é™„ä»¶
const viewFile = (row) => {
    nextTick(() => {
        viewFiles.value?.openDialog(row);
    });
};
// åˆ é™¤æ“ä½œ
const handleDelete = () => {
    if (!selectedRows.value.length) {
        proxy.$modal.msgWarning("请选择要删除的数据");
        return;
    }
    const deleteIds = selectedRows.value.map(item => item.id);
    proxy.$modal.confirm('是否确认删除所选数据项?').then(() => {
        return delTimingTask(deleteIds);
    }).then(() => {
        proxy.$modal.msgSuccess("删除成功");
        handleQuery();
    }).catch(() => {});
};
// å¤šé€‰å˜æ›´
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            // æ ¹æ®å½“前选中的标签页调用不同的导出接口
            if (activeRadio.value === "taskManage") {
                // å®šæ—¶ä»»åŠ¡ç®¡ç†
                proxy.download("/timingTask/export", {}, "定时任务管理.xlsx");
            } else if (activeRadio.value === "task") {
                // å®šæ—¶ä»»åŠ¡è®°å½•
                proxy.download("/inspectionTask/export", {}, "定时任务记录.xlsx");
            }
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
</script>
<style scoped>
.person-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.person-tag {
    margin-right: 4px;
    margin-bottom: 2px;
}
.no-data {
    color: #909399;
    font-size: 14px;
}
</style>
src/views/equipmentManagement/iotMonitor/index.vue
@@ -110,7 +110,7 @@
const devices = reactive([
  {
    id: 'water-pump',
    name: '注水泵1',
    name: '设备1',
    type: '移动装备',
    baseline: { vibration: 9 },
    initial: { temperature: 40, pressure: 0.70 },
@@ -124,7 +124,7 @@
  },
  {
    id: 'fluid-supply-truck',
    name: '注水泵2',
    name: '设备2',
    type: '移动装备',
    baseline: { vibration: 7 },
    initial: { temperature: 30, pressure: 0.60 },
@@ -138,7 +138,7 @@
  },
  {
    id: 'fracturing-truck',
    name: '注水泵3',
    name: '设备3',
    type: '移动装备',
    baseline: { vibration: 12 },
    initial: { temperature: 65, pressure: 1.40 },
@@ -152,7 +152,7 @@
  },
  {
    id: 'oil-tank-truck',
    name: '注水泵4',
    name: '设备4',
    type: '移动装备',
    baseline: { vibration: 6 },
    initial: { temperature: 28, pressure: 0.50 },
src/views/equipmentManagement/iotMonitor/indexWD.vue
@@ -110,7 +110,7 @@
const devices = reactive([
  {
    id: 'hydrocyclone-desander',
    name: '旋流除砂器',
    name: '设备1',
    type: '分离设备',
    baseline: { vibration: 8 },
    initial: { temperature: 35, pressure: 0.85 },
@@ -124,7 +124,7 @@
  },
  {
    id: 'high-pressure-separator',
    name: '高压分离器撬',
    name: '设备2',
    type: '分离设备',
    baseline: { vibration: 6 },
    initial: { temperature: 45, pressure: 1.20 },
@@ -138,7 +138,7 @@
  },
  {
    id: 'heating-throttle-pressure',
    name: '组合式加热节流调压',
    name: '设备3',
    type: '调压设备',
    baseline: { vibration: 10 },
    initial: { temperature: 75, pressure: 1.80 },
@@ -152,7 +152,7 @@
  },
  {
    id: 'three-phase-separator',
    name: '三相分离器',
    name: '设备4',
    type: '分离设备',
    baseline: { vibration: 7 },
    initial: { temperature: 38, pressure: 0.95 },
src/views/equipmentManagement/kplMonitor/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,714 @@
<template>
  <div class="kpl-monitor-container">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <div class="page-header">
      <div class="header-content">
        <h1>KPL监控分析</h1>
        <p>设备关键性能指标监控与维保策略优化</p>
      </div>
      <div class="time-range">
        <el-date-picker
          v-model="timeRange"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          value-format="YYYY-MM-DD"
          @change="fetchKPLData"
        />
      </div>
    </div>
    <!-- å…³é”®æŒ‡æ ‡æ¦‚览 -->
    <div class="metrics-overview">
      <div class="metric-card mtbf-card">
        <div class="metric-icon">⏱️</div>
        <div class="metric-content">
          <div class="metric-title">MTBF</div>
          <div class="metric-subtitle">平均无故障时间</div>
          <div class="metric-value">{{ currentMTBF }}<span class="unit">小时</span></div>
          <div class="metric-trend" :class="mtbfTrendClass">
            <span class="trend-icon">{{ mtbfTrendText }}</span>
            <span class="trend-text">{{ Math.abs(mtbfChange) }}%</span>
          </div>
        </div>
      </div>
      <div class="metric-card mttr-card">
        <div class="metric-icon">🔧</div>
        <div class="metric-content">
          <div class="metric-title">MTTR</div>
          <div class="metric-subtitle">平均修复时间</div>
          <div class="metric-value">{{ currentMTTR }}<span class="unit">小时</span></div>
          <div class="metric-trend" :class="mttrTrendClass">
            <span class="trend-icon">{{ mttrTrendText }}</span>
            <span class="trend-text">{{ Math.abs(mttrChange) }}%</span>
          </div>
        </div>
      </div>
      <div class="metric-card availability-card">
        <div class="metric-icon">📊</div>
        <div class="metric-content">
          <div class="metric-title">设备可用率</div>
          <div class="metric-subtitle">运行效率指标</div>
          <div class="metric-value">{{ currentAvailability }}<span class="unit">%</span></div>
          <div class="metric-trend" :class="availabilityTrendClass">
            <span class="trend-icon">{{ availabilityTrendText }}</span>
            <span class="trend-text">{{ Math.abs(availabilityChange) }}%</span>
          </div>
        </div>
      </div>
    </div>
    <!-- è¶‹åŠ¿åˆ†æžå›¾è¡¨ -->
    <div class="charts-section">
      <div class="chart-container">
        <div class="chart-header">
          <h3>MTBF & MTTR è¶‹åŠ¿åˆ†æž</h3>
          <div class="chart-legend">
            <span class="legend-item mtbf-legend">
              <span class="legend-color"></span>
              MTBF (平均无故障时间)
            </span>
            <span class="legend-item mttr-legend">
              <span class="legend-color"></span>
              MTTR (平均修复时间)
            </span>
          </div>
        </div>
        <div class="chart-wrapper">
          <div ref="trendChart" class="chart"></div>
        </div>
      </div>
    </div>
    <!-- ç»´ä¿ç­–略建议 -->
    <div class="recommendations-section">
      <div class="section-header">
        <h3>维保策略优化建议</h3>
        <p>基于当前指标分析,为您提供针对性的优化建议</p>
      </div>
      <div class="recommendations-grid">
        <div
          v-for="(recommendation, index) in recommendations"
          :key="index"
          class="recommendation-card"
        >
          <div class="recommendation-icon">{{ recommendation.icon }}</div>
          <div class="recommendation-content">
            <h4>{{ recommendation.title }}</h4>
            <p>{{ recommendation.description }}</p>
            <div class="recommendation-priority" :class="recommendation.priority">
              {{ recommendation.priorityText }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
import * as echarts from 'echarts'
// ç”Ÿæˆæ¨¡æ‹Ÿæ•°æ®
const generateMockData = () => {
  const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
  const mtbfData = months.map(() => Math.floor(Math.random() * 200 + 300)) // 300-500小时
  const mttrData = months.map(() => Math.floor(Math.random() * 8 + 4)) // 4-12小时
  return {
    months,
    mtbfData,
    mttrData
  }
}
const timeRange = ref([
  new Date(new Date().getFullYear(), 0, 1).toISOString().split('T')[0],
  new Date().toISOString().split('T')[0]
])
const mockData = reactive(generateMockData())
// è®¡ç®—当前指标值
const currentMTBF = computed(() => mockData.mtbfData[mockData.mtbfData.length - 1])
const currentMTTR = computed(() => mockData.mttrData[mockData.mttrData.length - 1])
const currentAvailability = computed(() =>
  Math.round((currentMTBF.value / (currentMTBF.value + currentMTTR.value)) * 100)
)
// è®¡ç®—变化趋势
const mtbfChange = computed(() => {
  const current = currentMTBF.value
  const previous = mockData.mtbfData[mockData.mtbfData.length - 2] || current
  return Math.round(((current - previous) / previous) * 100)
})
const mttrChange = computed(() => {
  const current = currentMTTR.value
  const previous = mockData.mttrData[mockData.mttrData.length - 2] || current
  return Math.round(((current - previous) / previous) * 100)
})
const availabilityChange = computed(() => {
  const current = currentAvailability.value
  const previous = Math.round(
    (mockData.mtbfData[mockData.mtbfData.length - 2] /
    (mockData.mtbfData[mockData.mtbfData.length - 2] + mockData.mttrData[mockData.mttrData.length - 2])) * 100
  ) || current
  return Math.round(((current - previous) / previous) * 100)
})
// è¶‹åŠ¿æ ·å¼å’Œæ–‡æœ¬
const mtbfTrendClass = computed(() => mtbfChange.value >= 0 ? 'trend-up' : 'trend-down')
const mttrTrendClass = computed(() => mttrChange.value <= 0 ? 'trend-up' : 'trend-down')
const availabilityTrendClass = computed(() => availabilityChange.value >= 0 ? 'trend-up' : 'trend-down')
const mtbfTrendText = computed(() => mtbfChange.value >= 0 ? '↗' : '↘')
const mttrTrendText = computed(() => mttrChange.value <= 0 ? '↗' : '↘')
const availabilityTrendText = computed(() => availabilityChange.value >= 0 ? '↗' : '↘')
// æ™ºèƒ½ç»´ä¿å»ºè®®
const recommendations = computed(() => {
  const suggestions = []
  // MTBF相关建议
  if (currentMTBF.value < 400) {
    suggestions.push({
      icon: '🔧',
      title: '提升MTBF',
      description: '当前MTBF较低,建议加强预防性维护,定期检查关键部件,延长设备无故障运行时间',
      priority: 'high',
      priorityText: '高优先级'
    })
  }
  // MTTR相关建议
  if (currentMTTR.value > 8) {
    suggestions.push({
      icon: '⚡',
      title: '优化MTTR',
      description: '当前MTTR较高,建议优化维修流程,提高维修人员技能,缩短故障修复时间',
      priority: 'high',
      priorityText: '高优先级'
    })
  }
  // å¯ç”¨çŽ‡ç›¸å…³å»ºè®®
  if (currentAvailability.value < 95) {
    suggestions.push({
      icon: '📈',
      title: '提升可用率',
      description: '设备可用率有待提升,建议优化维保计划安排,减少计划外停机时间',
      priority: 'medium',
      priorityText: '中优先级'
    })
  }
  // ç»¼åˆå»ºè®®
  if (currentMTBF.value >= 400 && currentMTTR.value <= 8 && currentAvailability.value >= 95) {
    suggestions.push({
      icon: '✅',
      title: '运行状况良好',
      description: '当前设备运行状况良好,各项指标均达到预期,建议继续保持现有维保策略',
      priority: 'low',
      priorityText: '低优先级'
    })
  }
  // é¢„防性建议
  suggestions.push({
    icon: '📋',
    title: '预防性维护',
    description: '建议建立设备健康档案,定期分析设备运行数据,提前识别潜在故障风险',
    priority: 'medium',
    priorityText: '中优先级'
  })
  return suggestions
})
// å›¾è¡¨å®žä¾‹
let trendChart = null
const initChart = () => {
  nextTick(() => {
    trendChart = echarts.init(document.querySelector('.chart'))
    trendChart.setOption({
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'cross'
        }
      },
      legend: {
        data: ['MTBF', 'MTTR'],
        top: 10
      },
      grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
      },
      xAxis: {
        type: 'category',
        data: mockData.months,
        axisLine: {
          lineStyle: {
            color: '#e0e0e0'
          }
        }
      },
      yAxis: [
        {
          type: 'value',
          name: 'MTBF (小时)',
          position: 'left',
          axisLine: {
            lineStyle: {
              color: '#1890ff'
            }
          },
          axisLabel: {
            formatter: '{value}h'
          }
        },
        {
          type: 'value',
          name: 'MTTR (小时)',
          position: 'right',
          axisLine: {
            lineStyle: {
              color: '#52c41a'
            }
          },
          axisLabel: {
            formatter: '{value}h'
          }
        }
      ],
      series: [
        {
          name: 'MTBF',
          type: 'line',
          yAxisIndex: 0,
          data: mockData.mtbfData,
          smooth: true,
          lineStyle: {
            color: '#1890ff',
            width: 3
          },
          itemStyle: {
            color: '#1890ff'
          },
          areaStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
              { offset: 1, color: 'rgba(24, 144, 255, 0.1)' }
            ])
          }
        },
        {
          name: 'MTTR',
          type: 'line',
          yAxisIndex: 1,
          data: mockData.mttrData,
          smooth: true,
          lineStyle: {
            color: '#52c41a',
            width: 3
          },
          itemStyle: {
            color: '#52c41a'
          },
          areaStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: 'rgba(82, 196, 26, 0.3)' },
              { offset: 1, color: 'rgba(82, 196, 26, 0.1)' }
            ])
          }
        }
      ]
    })
  })
}
const fetchKPLData = () => {
  // æ¨¡æ‹Ÿæ•°æ®åˆ·æ–°
  Object.assign(mockData, generateMockData())
  // é‡æ–°æ¸²æŸ“图表
  if (trendChart) {
    trendChart.setOption({
      xAxis: {
        data: mockData.months
      },
      series: [
        {
          data: mockData.mtbfData
        },
        {
          data: mockData.mttrData
        }
      ]
    })
  }
}
onMounted(() => {
  initChart()
  // ç›‘听窗口大小变化,重新调整图表大小
  window.addEventListener('resize', () => {
    if (trendChart) trendChart.resize()
  })
})
</script>
<style scoped>
.kpl-monitor-container {
  min-height: 100vh;
  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  padding: 24px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* é¡µé¢å¤´éƒ¨ */
.page-header {
  background: white;
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.header-content h1 {
  margin: 0 0 8px 0;
  color: #1f2937;
  font-size: 28px;
  font-weight: 700;
}
.header-content p {
  margin: 0;
  color: #6b7280;
  font-size: 16px;
}
.time-range {
  display: flex;
  align-items: center;
  gap: 12px;
}
/* æŒ‡æ ‡æ¦‚览 */
.metrics-overview {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  margin-bottom: 24px;
}
.metric-card {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  gap: 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.metric-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.metric-icon {
  font-size: 32px;
  width: 60px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.mtbf-card .metric-icon {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.mttr-card .metric-icon {
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.availability-card .metric-icon {
  background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.metric-content {
  flex: 1;
}
.metric-title {
  font-size: 18px;
  font-weight: 600;
  color: #1f2937;
  margin-bottom: 4px;
}
.metric-subtitle {
  font-size: 14px;
  color: #6b7280;
  margin-bottom: 12px;
}
.metric-value {
  font-size: 32px;
  font-weight: 700;
  color: #1f2937;
  margin-bottom: 8px;
}
.unit {
  font-size: 16px;
  font-weight: 500;
  color: #6b7280;
  margin-left: 4px;
}
.metric-trend {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 14px;
  font-weight: 600;
}
.trend-up {
  color: #10b981;
}
.trend-down {
  color: #ef4444;
}
.trend-icon {
  font-size: 16px;
}
/* å›¾è¡¨åŒºåŸŸ */
.charts-section {
  margin-bottom: 24px;
}
.chart-container {
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}
.chart-header {
  padding: 20px 24px;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.chart-header h3 {
  margin: 0;
  color: #1f2937;
  font-size: 18px;
  font-weight: 600;
}
.chart-legend {
  display: flex;
  gap: 20px;
}
.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: #6b7280;
}
.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 2px;
}
.mtbf-legend .legend-color {
  background: #1890ff;
}
.mttr-legend .legend-color {
  background: #52c41a;
}
.chart-wrapper {
  padding: 20px;
  height: 400px;
}
.chart {
  width: 100%;
  height: 100%;
}
/* å»ºè®®åŒºåŸŸ */
.recommendations-section {
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}
.section-header {
  padding: 24px;
  border-bottom: 1px solid #e5e7eb;
}
.section-header h3 {
  margin: 0 0 8px 0;
  color: #1f2937;
  font-size: 20px;
  font-weight: 600;
}
.section-header p {
  margin: 0;
  color: #6b7280;
  font-size: 14px;
}
.recommendations-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
  gap: 20px;
  padding: 24px;
}
.recommendation-card {
  background: #f8fafc;
  border-radius: 8px;
  padding: 20px;
  display: flex;
  gap: 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.recommendation-card:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.recommendation-icon {
  font-size: 24px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: white;
  flex-shrink: 0;
}
.recommendation-content {
  flex: 1;
}
.recommendation-content h4 {
  margin: 0 0 8px 0;
  color: #1f2937;
  font-size: 16px;
  font-weight: 600;
}
.recommendation-content p {
  margin: 0 0 12px 0;
  color: #6b7280;
  font-size: 14px;
  line-height: 1.5;
}
.recommendation-priority {
  display: inline-block;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
}
.priority.high {
  background: #fef2f2;
  color: #dc2626;
}
.priority.medium {
  background: #fffbeb;
  color: #d97706;
}
.priority.low {
  background: #f0fdf4;
  color: #16a34a;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .kpl-monitor-container {
    padding: 16px;
  }
  .page-header {
    flex-direction: column;
    gap: 16px;
    align-items: stretch;
  }
  .metrics-overview {
    grid-template-columns: 1fr;
  }
  .recommendations-grid {
    grid-template-columns: 1fr;
  }
  .chart-wrapper {
    height: 300px;
  }
  .chart-legend {
    flex-direction: column;
    gap: 8px;
  }
}
@media (max-width: 480px) {
  .metric-card {
    flex-direction: column;
    text-align: center;
  }
  .recommendation-card {
    flex-direction: column;
    text-align: center;
  }
}
</style>
src/views/equipmentManagement/ledger/Form.vue
@@ -1,5 +1,5 @@
<template>
  <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
  <el-form :model="form" label-width="120px" :rules="formRules" ref="formRef">
    <el-row :gutter="20">
      <el-col :span="12">
        <el-form-item label="设备名称" prop="deviceName">
@@ -8,7 +8,33 @@
      </el-col>
      <el-col :span="12">
        <el-form-item label="规格型号" prop="deviceModel">
          <el-input v-model="form.deviceModel" :disabled="(form.deviceModel != null && operationType === 'edit')" placeholder="请输入规格型号" />
          <el-input v-model="form.deviceModel" placeholder="请输入规格型号" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="设备品牌" prop="deviceBrand">
          <el-input v-model="form.deviceBrand" placeholder="请输入设备品牌" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="设备类型" prop="type">
          <el-select
            v-model="form.type"
            placeholder="请选择或输入设备类型"
            clearable
            filterable
            allow-create
            default-first-option
            style="width: 100%"
            @change="handleDeviceTypeChange"
          >
            <el-option
              v-for="item in deviceTypeOptions"
              :key="item"
              :label="item"
              :value="item"
            />
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -17,14 +43,36 @@
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="存放位置" prop="storageLocation">
          <el-input v-model="form.storageLocation" placeholder="请输入存放位置" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="单位" prop="unit">
          <el-input v-model="form.unit" placeholder="请输入单位" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="启用折旧" prop="isDepr">
          <el-switch v-model="form.isDepr" :active-value="1" :inactive-value="2" />
        </el-form-item>
      </el-col>
      <el-col :span="12" v-if="form.isDepr === 1">
        <el-form-item label="每年折旧金额" prop="annualDepreciationAmount">
          <el-input-number
            :step="0.01"
            :min="0"
            style="width: 100%"
            v-model="form.annualDepreciationAmount"
            placeholder="请输入每年折旧金额"
          />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="数量" prop="number">
          <el-input-number :step="0.01" :min="0" style="width: 100%"
          <el-input-number :min="1" style="width: 100%"
            v-model="form.number"
                                                     disabled
            placeholder="请输入数量"
            @change="mathNum"
          />
@@ -99,6 +147,19 @@
          />
        </el-form-item>
      </el-col>
            <el-col :span="12">
                <el-form-item label="预计运行时间" prop="planRuntimeTime">
                    <el-date-picker
                        style="width: 100%"
                        v-model="form.planRuntimeTime"
                        format="YYYY-MM-DD"
                        value-format="YYYY-MM-DD"
                        type="date"
                        placeholder="请选择录入日期"
                        clearable
                    />
                </el-form-item>
            </el-col>
    </el-row>
  </el-form>
</template>
@@ -120,28 +181,56 @@
});
const formRef = ref(null);
const operationType = ref('');
// è®¾å¤‡ç±»åž‹å›ºå®šé€‰é¡¹
const deviceTypeOptions = ref([
  '生产设备',
  '办公设备',
  '检测设备',
  '运输设备',
  '其他设备'
]);
const formRules = {
    deviceName: [{ required: true, trigger: "blur", message: "请输入" }],
    deviceModel: [{ required: true, trigger: "blur", message: "请输入" }],
    type: [{ required: true, trigger: "change", message: "请选择或输入设备类型" }],
    supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
    unit: [{ required: true, trigger: "blur", message: "请输入" }],
    number: [{ required: true, trigger: "blur", message: "请输入" }],
    taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "请输入" }],
    taxRate: [{ required: true, trigger: "change", message: "请输入" }],
    planRuntimeTime: [{ required: true, trigger: "change", message: "请选择" }],
    annualDepreciationAmount: [
        {
            validator: (rule, value, callback) => {
                if (form.isDepr === 1 && (value === undefined || value === null || value === '')) {
                    callback(new Error('启用折旧时,请输入每年折旧金额'));
                } else {
                    callback();
                }
            },
            trigger: "blur"
        }
    ],
}
const { form, resetForm } = useFormData({
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  deviceBrand: undefined, // è®¾å¤‡å“ç‰Œ
  type: undefined, // è®¾å¤‡ç±»åž‹
  supplierName: undefined, // ä¾›åº”商
  storageLocation: undefined, // å­˜æ”¾ä½ç½®
  isDepr: 2, // æ˜¯å¦å¯ç”¨æŠ˜æ—§ 1-是 2-否
  annualDepreciationAmount: undefined, // æ¯å¹´æŠ˜æ—§é‡‘额
  unit: undefined, // å•位
  number: undefined, // æ•°é‡
  number: 1, // æ•°é‡
  taxIncludingPriceUnit: undefined, // å«ç¨Žå•ä»·
  taxIncludingPriceTotal: undefined, // å«ç¨Žæ€»ä»·
  taxRate: undefined, // ç¨Žçއ
  unTaxIncludingPriceTotal: undefined, // ä¸å«ç¨Žæ€»ä»·
  // createUser: useUserStore().nickName, // å½•入人
  createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // å½•入日期
    planRuntimeTime: dayjs().format("YYYY-MM-DD"), // å½•入日期
});
const loadForm = async (id) => {
@@ -152,9 +241,14 @@
  if (code == 200) {
    form.deviceName = data.deviceName;
    form.deviceModel = data.deviceModel;
    form.deviceBrand = data.deviceBrand;
    form.type = data.type;
    form.supplierName = data.supplierName;
    form.storageLocation = data.storageLocation;
    form.isDepr = data.isDepr;
    form.annualDepreciationAmount = data.annualDepreciationAmount;
    form.unit = data.unit;
    form.number = data.number;
    form.number = 1;
    form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
    form.taxIncludingPriceTotal = data.taxIncludingPriceTotal;
    form.taxRate = data.taxRate;
@@ -163,13 +257,16 @@
  }
};
const handleDeviceTypeChange = (value) => {
  // å¦‚果输入的新值不在固定选项中,则添加到选项列表
  if (value && !deviceTypeOptions.value.includes(value)) {
    deviceTypeOptions.value.push(value);
  }
};
const mathNum = () => {
  if (!form.taxIncludingPriceUnit) {
    ElMessage.error("请输入单价");
    return;
  }
  if (!form.number) {
    ElMessage.error("请输入数量");
    return;
  }
  form.taxIncludingPriceTotal = calculateTaxIncludeTotalPrice(
src/views/equipmentManagement/ledger/Modal.vue
@@ -1,5 +1,5 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close">
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close" draggable>
    <Form ref="formRef"></Form>
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
src/views/equipmentManagement/ledger/index.vue
@@ -7,7 +7,6 @@
          style="width: 240px"
          placeholder="请输入设备名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
@@ -17,7 +16,6 @@
            style="width: 240px"
            placeholder="请输入规格型号"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
@@ -27,17 +25,6 @@
            style="width: 240px"
            placeholder="请输入供应商"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="单位">
        <el-input
            v-model="filters.unit"
            style="width: 240px"
            placeholder="请输入单位"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
@@ -84,7 +71,7 @@
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <el-dialog v-model="qrDialogVisible" title="二维码" width="300px">
    <el-dialog v-model="qrDialogVisible" title="二维码" width="300px" draggable>
      <div style="text-align:center;">
        <img :src="qrCodeUrl" alt="二维码" style="width:200px;height:200px;" />
        <div style="margin:10px 0;">
@@ -134,83 +121,69 @@
    deviceName: undefined,
    deviceModel: undefined,
    supplierName: undefined,
    unit: undefined,
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
  [
    {
      label: "设备名称",
      align: "center",
      prop: "deviceName",
    },
    {
      label: "规格型号",
      align: "center",
      prop: "deviceModel",
    },
    {
      label: "设备品牌",
      prop: "deviceBrand",
    },
    {
      label: "设备类型",
      prop: "type",
    },
    {
      label: "供应商",
      align: "center",
      prop: "supplierName",
    },
    {
      label: "单位",
      align: "center",
      prop: "unit",
      label: "存放位置",
      prop: "storageLocation",
    },
    {
      label: "数量",
      align: "center",
      prop: "number",
    },
    {
      label: "含税单价",
      align: "center",
      prop: "taxIncludingPriceUnit",
    },
    {
      label: "含税总价",
      align: "center",
      prop: "taxIncludingPriceTotal",
    },
    {
      label: "税率",
      align: "center",
      prop: "taxRate",
    },
    {
      label: "不含税总价",
      align: "center",
      prop: "unTaxIncludingPriceTotal",
    },
    {
      label: "录入人",
      align: "center",
      prop: "createUser",
    },
    {
      label: "录入日期",
      align: "center",
      prop: "createTime",
      formatData: (v) => {
        if (!v) return '';
        // å¦‚果包含时分秒,只取日期部分
        if (v.includes(' ')) {
          return v.split(' ')[0];
        }
        return v;
      },
    },
        {
            dataType: "action",
            label: "操作",
            align: "center",
            fixed: 'right',
            width: 140,
            width: 150,
            operation: [
                {
                    name: "编辑",
                    type: "text",
                    clickFun: (row) => {
                        edit(row.id)
                    },
                },
                {
                    name: "生成二维码",
                    type: "text",
                    clickFun: (row) => {
                        showQRCode(row)
                    },
@@ -293,8 +266,8 @@
};
const showQRCode = async (row) => {
  // ä½ å¯ä»¥è‡ªå®šä¹‰äºŒç»´ç å†…容,比如 row.id æˆ– row.deviceName
  const qrContent = JSON.stringify(row); // æˆ– `${row.id}`
  // ç›´æŽ¥ä½¿ç”¨URL,不要用JSON.stringify包装
  const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
  qrCodeUrl.value = await QRCode.toDataURL(qrContent);
  qrRowData.value = row;
  qrDialogVisible.value = true;
@@ -308,12 +281,6 @@
};
onMounted(() => {
  filters.entryDate = [
    dayjs().format("YYYY-MM-DD"),
    dayjs().add(1, "day").format("YYYY-MM-DD"),
  ]
  filters.entryDateStart = dayjs().format("YYYY-MM-DD")
  filters.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
  getTableData();
});
</script>
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
@@ -4,6 +4,7 @@
            v-model="dialogFormVisible"
            title="计量器具"
            width="50%"
            draggable
            @close="closeDia"
        >
            <el-form
@@ -67,7 +68,9 @@
                            <el-select
                                v-model="form.userId"
                                placeholder="请选择"
                                disabled
                filterable
                default-first-option
                :reserve-keyword="false"
                                clearable
                            >
                                <el-option
@@ -88,7 +91,6 @@
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                disabled
                                clearable
                            />
                        </el-form-item>
@@ -123,13 +125,14 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, toRefs, getCurrentInstance} from "vue";
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import {getToken} from "@/utils/auth.js";
import {ledgerRecordUpdate, ledgerRecordVerifying} from "@/api/equipmentManagement/calibration.js";
import {delLedgerFile} from "@/api/salesManagement/salesLedger.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -185,6 +188,10 @@
    if(type === "add"){
    fileList.value = row.commonFiles;
  }
    if(type === "verifying"){
        form.value.valid = row.valid;
        form.value.recordDate = row.mostDate;
    }
    form.value.id = row.id;
    form.value.code = row.code;
@@ -249,14 +256,6 @@
    dialogFormVisible.value = false;
    emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
    openDialog,
});
src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
<template>
</template>
<script setup>
</script>
src/views/equipmentManagement/measurementEquipment/components/formDia.vue
@@ -4,6 +4,7 @@
        v-model="dialogFormVisible"
        title="计量器具"
        width="50%"
                draggable
        @close="closeDia"
    >
            <el-form
@@ -14,8 +15,8 @@
                ref="formRef"
            >
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="计量器具编号:" prop="code">
                    <el-col :span="24">
                        <el-form-item label="出厂编号:" prop="code">
                            <el-input
                                v-model="form.code"
                                placeholder="请输入"
@@ -23,40 +24,73 @@
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="计量器具名称:" prop="name">
                            <el-input
                                v-model="form.name"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="规格型号:" prop="model">
                        <el-form-item label="安装位置:" prop="instationLocation">
                            <el-input
                                v-model="form.model"
                                v-model="form.installationLocation"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="预计下次检定日期:" prop="nextDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.nextDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                clearable
                            />
                        <el-form-item label="检定单位:" prop="unit">
              <el-input
                  v-model="form.unit"
                  placeholder="请输入检定单位"
                  clearable
              />
                        </el-form-item>
                    </el-col>
                </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="证书编号:" prop="model">
              <el-input
                  v-model="form.model"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="最新鉴定日期:" prop="mostDate">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.mostDate"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="有效日期(天):" prop="valid">
              <el-input
                  v-model="form.valid"
                  placeholder="请输入有效期天数"
                  clearable
              >
              <template #append>日</template>
              </el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检定周期:" prop="cycle">
              <el-input
                  v-model="form.cycle"
                  placeholder="请输入检定周期"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="录入人:" prop="userId">
@@ -64,7 +98,9 @@
                                v-model="form.userId"
                                placeholder="请选择"
                                clearable
                                disabled
                filterable
                default-first-option
                :reserve-keyword="false"
                            >
                                <el-option
                                    v-for="item in userList"
@@ -81,9 +117,9 @@
                                style="width: 100%"
                                v-model="form.recordDate"
                                value-format="YYYY-MM-DD"
                disabled
                                format="YYYY-MM-DD"
                                type="date"
                                disabled
                                placeholder="请选择"
                                clearable
                            />
@@ -124,7 +160,8 @@
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import {getToken} from "@/utils/auth.js";
import {measuringInstrumentAdd, measuringInstrumentUpdate} from "@/api/equipmentManagement/measurementEquipment.js";
import {addMeasuringInstrumentLedger, updateMeasuringInstrumentLedger} from "@/api/equipmentManagement/measurementEquipment.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -134,8 +171,10 @@
const data = reactive({
    form: {
        code: "",
        name: "",
    installationLocation: "",
    mostDate:"",
        model: "",
    cycle:"",
        validDate: "",
        nextDate: "",
        userId: "",
@@ -144,12 +183,16 @@
    },
    rules: {
        code: [{required: true, message: "请输入", trigger: "blur"}],
        name: [{required: true, message: "请输入", trigger: "blur"}],
        model: [{required: true, message: "请输入", trigger: "blur"}],
        validDate: [{required: true, message: "请输入", trigger: "blur"}],
        nextDate: [{required: true, message: "请选择", trigger: "change"}],
        userId: [{required: true, message: "请选择", trigger: "change"}],
        recordDate: [{required: true, message: "请选择", trigger: "change"}],
    installationLocation: [{required: true, message: "请输入", trigger: "blur"}],
    mostDate: [{required: true, message: "请选择", trigger: "change"}],
    cycle: [{required: true, message: "请选择", trigger: "blur"}],
    valid: [{required: true, message: "请输入", trigger: "blur"}],
    unit: [{required: true, message: "请输入", trigger: "blur"}],
    }
})
const { form, rules } = toRefs(data);
@@ -214,13 +257,13 @@
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            if (operationType.value === "add") {
                measuringInstrumentAdd(form.value).then(response => {
        addMeasuringInstrumentLedger(form.value).then(response => {
                    proxy.$modal.msgSuccess("新增成功")
          form.value.tempFileIds = []
                    closeDia()
                })
            } else {
                measuringInstrumentUpdate(form.value).then(response => {
        updateMeasuringInstrumentLedger(form.value).then(response => {
                    proxy.$modal.msgSuccess("修改成功")
          form.value.tempFileIds = []
                    closeDia()
@@ -235,14 +278,6 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
src/views/equipmentManagement/measurementEquipment/components/rowClickData.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="检定校准记录"
        width="50%"
        @close="closeDia"
    >
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :tableLoading="tableLoading"
          @selection-change="handleSelectionChange"
          height="500"
          :isPagination="false"
      >
      </PIMTable>
      <pagination
          style="margin: 10px 0"
          v-show="total > 0"
          @pagination="paginationSearch"
          :total="total"
          :page="page.current"
          :limit="page.size"
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
<script setup>
import {ref} from "vue";
import filePreview from '@/components/filePreview/index.vue'
import {ledgerRecordListPage} from "@/api/equipmentManagement/calibration.js";
import Pagination from "@/components/PIMTable/Pagination.vue";
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const currentId = ref('')
const selectedRows = ref([]);
const filePreviewRef = ref()
const tableColumn = ref([
  {
    label: "检定日期",
    prop: "recordDate",
    width: 130,
  },
  {
    label: "计量器具编号",
    prop: "code",
    width: 150,
  },
  {
    label: "计量器具名称",
    prop: "name",
    width: 200,
  },
  {
    label: "规格型号",
    prop: "model",
    width:200
  },
  {
    label: "有效期",
    prop: "valid",
    width: 100,
  },
  {
    label: "录入人",
    prop: "userName",
  },
  {
    label: "录入日期",
    prop: "entryDate",
    width: 130,
  },
]);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const tableData = ref([]);
const tableLoading = ref(false);
// æ‰“开弹框
const openDialog = (row,type) => {
  dialogFormVisible.value = true;
  currentId.value = row.id;
  getList()
}
const paginationSearch = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  let query = {
    measuringInstrumentLedgerId:currentId.value,
    current : page.current,
    size : page.size
  }
  ledgerRecordListPage(query).then(res => {
    tableData.value = res?.data?.records || [];
    total.value = res?.data?.total;
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
src/views/equipmentManagement/measurementEquipment/filesDia.vue
@@ -26,20 +26,14 @@
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :tableLoading="tableLoading"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          @pagination="paginationSearch"
          height="500"
      >
      </PIMTable>
            <pagination
                style="margin: 10px 0"
                v-show="total > 0"
                @pagination="paginationSearch"
                :total="total"
                :page="page.current"
                :limit="page.size"
            />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
@@ -51,16 +45,16 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import filePreview from '@/components/filePreview/index.vue'
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import {
  fileAdd,
  fileDel,
  fileListPage
} from "@/api/financialManagement/revenueManagement.js";
import Pagination from "@/components/PIMTable/Pagination.vue";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -98,8 +92,8 @@
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const total = ref(0);
const tableData = ref([]);
const fileList = ref([]);
const tableLoading = ref(false);
@@ -124,7 +118,7 @@
const getList = () => {
  fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => {
    tableData.value = res.data.records;
        total.value = res.data.total;
        page.total = res.data.total;
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
src/views/equipmentManagement/measurementEquipment/index.vue
@@ -23,6 +23,7 @@
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
                <el-button @click="handleReset" style="margin-left: 10px">重置</el-button>
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">新增计量器具</el-button>
@@ -40,25 +41,28 @@
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
        :dbRowClick="dbRowClick"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <calibration-dia ref="calibrationDia" @close="handleQuery"></calibration-dia>
    <files-dia ref="filesDia"></files-dia>
    <rowClickDataForm ref="rowClickData"></rowClickDataForm>
    </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/equipmentManagement/measurementEquipment/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import useUserStore from "@/store/modules/user.js";
import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue";
import {
    measuringInstrumentDelete,
    measuringInstrumentListPage
  measuringInstrumentDelete,
  measuringInstrumentListPage,
} from "@/api/equipmentManagement/measurementEquipment.js";
import FilesDia from "./filesDia.vue";
import rowClickDataForm from "./components/rowClickData.vue"
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -73,67 +77,74 @@
const tableColumn = ref([
    {
        label: "状态",
        prop: "status",
        dataType: "tag",
        formatData: (params) => {
            if (params == 1) {
                return "有效";
            } else if (params == 2) {
                return "逾期";
            } else {
                return null;
            }
        },
        formatType: (params) => {
            if (params == 1) {
                return "success";
            } else if (params == 2) {
                return "danger";
            } else {
                return null;
            }
        },
        label: "出厂编号",
        prop: "code",
    minWidth:150,
    align:"center"
    },
    {
        label: "最近一次检定日期",
        label: "安装位置",
        prop: "instationLocation",
        width: 150,
    align:"center"
    },
    {
        label: "检定单位",
        prop: "unit",
        width: 200,
    align:"center"
    },
    {
        label: "证书编号",
        prop: "model",
        width:200,
    align:"center"
    },
    {
        label: "最新鉴定日期",
        prop: "mostDate",
        width: 130,
    },
    {
        label: "计量器具编号",
        prop: "code",
        width: 150,
    },
    {
        label: "计量器具名称",
        prop: "name",
        width: 200,
    },
    {
        label: "规格型号",
        prop: "model",
        width:200
    },
    {
        label: "有效期",
        prop: "valid",
        width: 130,
    },
    {
        label: "预计下次检定日期",
        prop: "nextDate",
        width: 130,
    align:"center"
    },
    {
        label: "录入人",
        prop: "userName",
        width: 130,
    align:"center"
    },
    {
        label: "录入日期",
        prop: "recordDate",
        width: 130,
    align:"center",
    minWidth: 130
    },
    {
        label: "有效日期",
        prop: "valid",
        width: 130,
    align:"center"
    },
  {
    label: "检定周期(天)",
    prop: "cycle",
    width: 130,
    align:"center"
  },
  {
    label: "状态",
    prop: "status",
    width: 130,
    align: "center",
    formatData: (params) => {
      if (params === 1) {
        return "有效";
      } else if (params === 2) {
        return "逾期";
      } else {
        return null;
      }
    }
  },
    {
        dataType: "action",
        label: "操作",
@@ -141,18 +152,18 @@
        width: '130',
        fixed: 'right',
        operation: [
      {
          name: "附件",
          type: "text",
          clickFun: (row) => {
          openFilesFormDia(row);
          },
      },
            {
                name: "检定校准",
                name: "查看",
                type: "text",
                clickFun: (row) => {
                    openCalibrationDia("verifying", row);
                },
            },
            {
                name: "附件",
                type: "text",
                clickFun: (row) => {
          openFilesFormDia(row);
                },
            },
        ],
@@ -160,6 +171,7 @@
]);
const tableData = ref([]);
const tableLoading = ref(false);
const rowClickData = ref([])
const filesDia = ref()
const page = reactive({
    current: 1,
@@ -170,11 +182,12 @@
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  console.log(row)
  nextTick(() => {
    filesDia.value?.openDialog( row,'计量器具台账')
  })
    filesDia.value?.openDialog(row,'计量器具台账')
};
const dbRowClick = (row)=>{
  rowClickData.value?.openDialog(row)
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
@@ -186,6 +199,15 @@
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
// é‡ç½®æœç´¢æ¡ä»¶
const handleReset = () => {
    searchForm.value.recordDate = "";
    searchForm.value.code = "";
    searchForm.value.status = "";
    page.current = 1;
    getList();
};
@@ -221,12 +243,6 @@
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.userId !== userStore.id);
        if (unauthorizedData.length > 0) {
            proxy.$modal.msgWarning("不可删除他人维护的数据");
            return;
        }
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
src/views/equipmentManagement/operationManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,370 @@
<template>
  <div class="app-container">
    <!-- ç­›é€‰æ¡ä»¶ -->
    <div class="filter-section">
      <el-select v-model="deviceFilter" placeholder="设备状态筛选" clearable style="width: 200px; margin-right: 10px;">
        <el-option label="全部" value="all" />
        <el-option label="运行中" value="start" />
        <el-option label="停止运行" value="stop" />
      </el-select>
    </div>
    <!-- è®¾å¤‡å¯åœè®°å½•表格 -->
    <el-card class="table-card">
      <template #header>
        <span>设备运行记录</span>
      </template>
      <el-table
        :data="filteredDeviceRecords"
        style="width: 100%"
        :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
        :row-class-name="getRowClassName"
        v-loading="loading"
      >
        <el-table-column
          align="center"
          label="序号"
          type="index"
          width="60"
        />
        <el-table-column
          label="设备名称"
          prop="deviceName"
          show-overflow-tooltip
        />
        <el-table-column
          label="规格型号"
          prop="deviceModel"
          show-overflow-tooltip
        />
        <el-table-column
          label="设备状态"
          prop="status"
          width="150"
          align="center"
        >
          <template #default="scope">
            <!-- è¶…时未启动时显示警告 -->
            <el-tag
              v-if="isOverdue(scope.row)"
              type="warning"
              size="small"
              effect="dark"
            >
              <el-icon><Warning /></el-icon>
              è¶…时未启动
            </el-tag>
            <!-- æ­£å¸¸çŠ¶æ€æ—¶æ˜¾ç¤ºè®¾å¤‡çŠ¶æ€ -->
            <el-tag
              v-else
              :type="getDeviceStatusType(scope.row.status)"
              size="small"
            >
              <el-icon v-if="scope.row.status === '运行中'"><VideoPlay /></el-icon>
              <el-icon v-else><VideoPause /></el-icon>
              {{ scope.row.status || '未知' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="计划运行时间"
          prop="planRuntimeTime"
          width="150"
          align="center"
        >
          <template #default="scope">
            {{ scope.row.planRuntimeTime || '-' }}
          </template>
        </el-table-column>
        <el-table-column
          label="开始运行时间"
          prop="startRuntimeTime"
          width="180"
          align="center"
        >
          <template #default="scope">
            {{ scope.row.startRuntimeTime || '-' }}
          </template>
        </el-table-column>
        <el-table-column
          label="结束运行时间"
          prop="endRuntimeTime"
          width="180"
          align="center"
        >
          <template #default="scope">
            {{ scope.row.endRuntimeTime || '-' }}
          </template>
        </el-table-column>
        <el-table-column
          label="运行时长"
          prop="runtimeDuration"
          width="120"
          align="center"
        >
          <template #default="scope">
            {{ scope.row.runtimeDuration || '-' }}
          </template>
        </el-table-column>
        <el-table-column
          label="操作"
          width="120"
          align="center"
        >
          <template #default="scope">
            <!-- è¶…时未启动时显示启动按钮 -->
            <el-button
              v-if="isOverdue(scope.row)"
              type="warning"
              size="small"
              @click="changeDeviceStatus(scope.row, '启动运行')"
            >
              <el-icon><VideoPlay /></el-icon>
              ç«‹å³å¯åЍ
            </el-button>
            <!-- æ­£å¸¸çŠ¶æ€æ—¶æ˜¾ç¤ºå¯¹åº”çš„æ“ä½œæŒ‰é’® -->
            <template v-else>
              <el-button
                v-if="scope.row.status === '运行中'"
                type="danger"
                size="small"
                @click="changeDeviceStatus(scope.row, '停止运行')"
              >
                <el-icon><VideoPause /></el-icon>
                åœæ­¢è¿è¡Œ
              </el-button>
              <el-button
                v-else
                type="success"
                size="small"
                @click="changeDeviceStatus(scope.row, '启动运行')"
              >
                <el-icon><VideoPlay /></el-icon>
                å¯åŠ¨è¿è¡Œ
              </el-button>
            </template>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import {
  VideoPlay,
  VideoPause,
  Warning
} from '@element-plus/icons-vue'
import {editLedger, getLedgerPage} from "@/api/equipmentManagement/ledger.js";
// å“åº”式数据
const deviceFilter = ref('all')
const loading = ref(false)
const total = ref(0)
const queryParams = ref({
  current: -1,
  size: -1
})
// ç§»é™¤æ¦‚览数据,因为现在使用表格展示
// è®¾å¤‡å¯åœè®°å½•数据
const deviceRecords = ref([])
const allDeviceRecords = ref([]) // å­˜å‚¨æ‰€æœ‰åŽŸå§‹æ•°æ®
// æ ¹æ®ç­›é€‰æ¡ä»¶è¿‡æ»¤æ•°æ®
const filteredDeviceRecords = computed(() => {
  let filtered = allDeviceRecords.value
  // æ ¹æ®è®¾å¤‡çŠ¶æ€ç­›é€‰
  if (deviceFilter.value !== 'all') {
    if (deviceFilter.value === 'start') {
      filtered = filtered.filter(device => device.status === '运行中')
    } else if (deviceFilter.value === 'stop') {
      filtered = filtered.filter(device => device.status === '停止运行')
    }
  }
  return filtered
})
// æ£€æŸ¥è®¾å¤‡æ˜¯å¦è¶…时未启动
const isOverdue = (device) => {
  if (!device.planRuntimeTime || device.status === '运行中' || device.startRuntimeTime) {
    return false
  }
  const planTime = new Date(device.planRuntimeTime)
  const currentTime = new Date()
  return currentTime > planTime
}
// æ–¹æ³•
const getList = async () => {
  loading.value = true
  try {
    const response = await getLedgerPage(queryParams.value)
    if (response.code === 200) {
      allDeviceRecords.value = response.data.records || []
      total.value = response.data.total || 0
    }
  } catch (error) {
    console.error('获取设备列表失败:', error)
    ElMessage.error('获取设备列表失败')
  } finally {
    loading.value = false
  }
}
const changeDeviceStatus = async (device, status) => {
  try {
    const currentTime = new Date().toLocaleString('zh-CN', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false
    }).replace(/\//g, '-')
    // æ›´æ–°è®¾å¤‡çŠ¶æ€å’Œç›¸å…³æ—¶é—´å­—æ®µ
    if (status === '启动运行') {
      device.status = '运行中'
      device.startRuntimeTime = currentTime
      device.endRuntimeTime = null // æ¸…空结束时间
      device.runtimeDuration = null // æ¸…空运行时长
    } else {
      device.status = '停止运行'
      device.endRuntimeTime = currentTime
      // è®¡ç®—运行时长
      if (device.startRuntimeTime) {
        const startTime = new Date(device.startRuntimeTime)
        const endTime = new Date(currentTime)
        const duration = endTime - startTime
        const hours = Math.floor(duration / (1000 * 60 * 60))
        const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60))
        device.runtimeDuration = `${hours}小时${minutes}分钟`
      }
    }
    const params = {
            id: device.id,
            status: device.status,
            planRuntimeTime: device.planRuntimeTime,
            startRuntimeTime: device.startRuntimeTime,
            endRuntimeTime: device.endRuntimeTime,
            runtimeDuration: device.runtimeDuration,
        }
    // è°ƒç”¨API更新设备状态
    const response = await editLedger(params)
    if (response.code === 200) {
      ElMessage.success(`${device.deviceName} ${status}成功`)
      // åˆ·æ–°åˆ—表
      await getList()
    } else {
      ElMessage.error(response.msg || '操作失败')
    }
  } catch (error) {
    console.error('更新设备状态失败:', error)
    ElMessage.error('操作失败')
  }
}
const getDeviceStatusType = (status) => {
  if (status === '运行中') {
    return 'success'
  } else if (status === '停止运行') {
    return 'danger'
  } else {
    return 'info'
  }
}
// èŽ·å–è¡¨æ ¼è¡Œçš„ç±»å
const getRowClassName = ({ row }) => {
  if (isOverdue(row)) {
    return 'overdue-row'
  }
  return ''
}
// ç»„件挂载时初始化数据
onMounted(() => {
  getList()
})
</script>
<style scoped>
.app-container {
  padding: 20px;
  background: #f5f7fa;
  min-height: 100vh;
}
.filter-section {
  margin-bottom: 20px;
  padding: 15px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  display: flex;
  justify-content: flex-start;
}
.table-card {
  margin-bottom: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
:deep(.el-card__header) {
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
  font-weight: 500;
  font-size: 16px;
}
:deep(.el-table .el-table__header-wrapper th) {
  background-color: #F0F1F5 !important;
  color: #333333;
  font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
  padding: 12px 0;
}
:deep(.el-select) {
  width: 100%;
}
:deep(.el-tag) {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* è¶…时未启动行的样式 */
:deep(.overdue-row) {
  background-color: #fef0f0 !important;
  border-left: 4px solid #f56c6c;
}
:deep(.overdue-row:hover) {
  background-color: #fde2e2 !important;
}
:deep(.overdue-row td) {
  background-color: transparent !important;
}
</style>
src/views/equipmentManagement/repair/Form/MaintainForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/repair/Form/RepairForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -1,53 +1,108 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr">
    <MaintainForm ref="maintainFormRef" />
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="visible"
    :title="'设备维修'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="80px">
      <el-form-item label="维修人">
        <el-input v-model="form.maintenanceName" placeholder="请输入维修人" />
      </el-form-item>
      <el-form-item label="维修结果">
        <el-input v-model="form.maintenanceResult" placeholder="请输入维修结果" />
      </el-form-item>
      <el-form-item label="维修状态">
        <el-select v-model="form.status">
          <el-option label="待报修" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="维修日期">
        <el-date-picker
          v-model="form.maintenanceTime"
          placeholder="请选择维修日期"
          format="YYYY-MM-DD HH:mm:ss"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="datetime"
          clearable
          style="width: 100%"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import MaintainForm from "../Form/MaintainForm.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { addMaintain } from "@/api/equipmentManagement/repair";
import useFormData from "@/hooks/useFormData";
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
import { ElMessage } from "element-plus";
defineOptions({
  name: "维修模态框",
});
const maintainFormRef = ref();
const emits = defineEmits(["ok"]);
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "设备维修" });
// ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
const repairId = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const { form, resetForm } = useFormData({
  maintenanceName: undefined, // ç»´ä¿®åç§°
  maintenanceResult: undefined, // ç»´ä¿®ç»“æžœ
  maintenanceTime: undefined, // ç»´ä¿®æ—¥æœŸ
  status: 0,
});
const setForm = (data) => {
  form.maintenanceName = data.maintenanceName ?? userStore.nickName;
  form.maintenanceResult = data.maintenanceResult;
  form.maintenanceTime =
    data.maintenanceTime
      ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss")
      : dayjs().format("YYYY-MM-DD HH:mm:ss");
  form.status = 1; // é»˜è®¤çŠ¶æ€ä¸ºå®Œç»“
};
const sendForm = async () => {
  loading.value = true;
  const form = await maintainFormRef.value.getForm();
  const { code } = await addMaintain({ id: id.value, ...form });
  if (code == 200) {
    emits("ok");
    maintainFormRef.value.resetForm();
    closeModal();
  try {
    const { code } = await addMaintain({ id: repairId.value, ...form });
    if (code == 200) {
      ElMessage.success("维修成功");
      emits("ok");
      resetForm();
      visible.value = false;
    }
  } finally {
    loading.value = false;
  }
  loading.value = false;
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const open = async (id, row) => {
  openModal(id);
  repairId.value = id; // ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
  visible.value = true;
  await nextTick();
  maintainFormRef.value.setForm(row);
  setForm(row);
};
defineExpose({
src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,93 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" @close="close">
    <RepairForm ref="repairFormRef" />
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备报修' : '新增设备报修'"
    width="800px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-row>
        <el-col :span="12">
          <el-form-item label="设备名称">
            <el-select v-model="form.deviceLedgerId" @change="setDeviceModel" filterable>
              <el-option
                v-for="(item, index) in deviceOptions"
                :key="index"
                :label="item.deviceName"
                :value="item.id"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号">
            <el-input
              v-model="form.deviceModel"
              placeholder="请输入规格型号"
              disabled
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修日期">
            <el-date-picker
              v-model="form.repairTime"
              placeholder="请选择报修日期"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              type="date"
              clearable
              style="width: 100%"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修人">
            <el-input v-model="form.repairName" placeholder="请输入报修人" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row v-if="id">
        <el-col :span="12">
          <el-form-item label="报修状态">
            <el-select v-model="form.status">
              <el-option label="待维修" :value="0"></el-option>
              <el-option label="完结" :value="1"></el-option>
              <el-option label="失败" :value="2"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="故障现象">
            <el-input
              v-model="form.remark"
              :rows="2"
              type="textarea"
              placeholder="请输入故障现象"
            />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import RepairForm from "../Form/RepairForm.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addRepair,
  editRepair,
  getRepairById,
} from "@/api/equipmentManagement/repair";
import { ElMessage } from "element-plus";
import dayjs from "dayjs";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import useUserStore from "@/store/modules/user";
defineOptions({
  name: "设备报修弹窗",
@@ -26,48 +95,83 @@
const emits = defineEmits(["ok"]);
const repairFormRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "设备报修" });
const id = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const deviceOptions = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined, // è®¾å¤‡Id
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  repairTime: dayjs().format("YYYY-MM-DD"), // æŠ¥ä¿®æ—¥æœŸï¼Œé»˜è®¤å½“天
  repairName: userStore.nickName, // æŠ¥ä¿®äºº
  remark: undefined, // æ•…障现象
  status: 0, // æŠ¥ä¿®çŠ¶æ€
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
};
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.repairTime = data.repairTime;
  form.repairName = data.repairName;
  form.remark = data.remark;
  form.status = data.status;
};
const sendForm = async () => {
  loading.value = true;
  const form = await repairFormRef.value.getForm();
  const { code } = id.value
    ? await editRepair({ id: unref(id), ...form })
    : await addRepair(form);
  if (code == 200) {
    ElMessage.success(`${id ? "编辑" : "新增"}报修成功`);
    closeModal();
    emits("ok");
  try {
    const { code } = id.value
      ? await editRepair({ id: unref(id), ...form })
      : await addRepair(form);
    if (code == 200) {
      ElMessage.success(`${id.value ? "编辑" : "新增"}报修成功`);
      visible.value = false;
      emits("ok");
    }
  } finally {
    loading.value = false;
  }
  loading.value = false;
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const openAdd = async () => {
  openModal();
  id.value = undefined;
  visible.value = true;
  await nextTick();
  await repairFormRef.value.loadDeviceName();
  await loadDeviceName();
};
const openEdit = async (id) => {
  const { data } = await getRepairById(id);
  openModal(id);
const openEdit = async (editId) => {
  const { data } = await getRepairById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  await repairFormRef.value.loadDeviceName();
  await repairFormRef.value.setForm(data);
};
const close = () => {
  repairFormRef.value.resetForm();
  closeModal();
  await loadDeviceName();
  setForm(data);
};
defineExpose({
@@ -75,3 +179,5 @@
  openEdit,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/repair/index.vue
@@ -68,21 +68,16 @@
      <div class="actions">
        <el-text class="mx-1" size="large">设备报修</el-text>
        <div>
          <el-button
            type="primary"
            icon="Plus"
            :disabled="multipleList.length !== 1"
            @click="addMaintain"
          >
            æ–°å¢žç»´ä¿®
          </el-button>
          <el-button type="success" icon="Van" @click="addRepair">
            æ–°å¢žæŠ¥ä¿®
          </el-button>
          <el-button @click="handleOut">
            å¯¼å‡º
          </el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            :disabled="multipleList.length <= 0 || hasFinishedStatus"
            @click="delRepairByIds(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
@@ -90,35 +85,44 @@
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          rowKey="id"
          isSelection
          :column="columns"
          :tableData="dataList"
          :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
          @selection-change="handleSelectionChange"
          @pagination="changePage"
      >
        <template #statusRef="{ row }">
          <el-tag v-if="row.status === 2" type="danger">失败</el-tag>
          <el-tag v-if="row.status === 1" type="success">完结</el-tag>
          <el-tag v-if="row.status === 0" type="danger">待维修</el-tag>
          <el-tag v-if="row.status === 0" type="warning">待维修</el-tag>
        </template>
        <template #operation="{ row }">
          <el-button
            type="primary"
            text
            icon="editPen"
            link
            :disabled="row.status === 1"
            @click="editRepair(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="success"
            link
            :disabled="row.status === 1"
            @click="addMaintain(row)"
          >
            ç»´ä¿®
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            link
            :disabled="row.status === 1"
            @click="delRepairByIds(row.id)"
          >
            åˆ é™¤
@@ -126,17 +130,17 @@
        </template>
      </PIMTable>
    </div>
    <RepairModal ref="repairModalRef" @ok="getTableData" />
    <MaintainModal ref="maintainModalRef" @ok="getTableData" />
    <RepairModal ref="repairModalRef" @ok="getTableData"/>
    <MaintainModal ref="maintainModalRef" @ok="getTableData"/>
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair";
import { onMounted } from "vue";
import { onMounted, getCurrentInstance, computed } from "vue";
import {usePaginationApi} from "@/hooks/usePaginationApi";
import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
import RepairModal from "./Modal/RepairModal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import {ElMessageBox, ElMessage} from "element-plus";
import dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
import {Search} from "@element-plus/icons-vue";
@@ -144,6 +148,8 @@
defineOptions({
  name: "设备报修",
});
const {proxy} = getCurrentInstance();
// æ¨¡æ€æ¡†å®žä¾‹
const repairModalRef = ref();
@@ -162,85 +168,85 @@
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  getRepairPage,
  {
    deviceName: undefined,
    deviceModel: undefined,
    remark: undefined,
    maintenanceName: undefined,
    repairTimeStr: undefined,
    maintenanceTimeStr: undefined,
  },
  [
    getRepairPage,
    {
      label: "设备名称",
      align: "center",
      prop: "deviceName",
      deviceName: undefined,
      deviceModel: undefined,
      remark: undefined,
      maintenanceName: undefined,
      repairTimeStr: undefined,
      maintenanceTimeStr: undefined,
    },
    {
      label: "规格型号",
      align: "center",
      prop: "deviceModel",
    },
    {
      label: "报修日期",
      align: "center",
      prop: "repairTime",
      formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
    },
    {
      label: "报修人",
      align: "center",
      prop: "repairName",
    },
    {
      label: "故障现象",
      align: "center",
      prop: "remark",
    },
    {
      label: "维修人",
      align: "center",
      prop: "maintenanceName",
    },
    {
      label: "维修结果",
      align: "center",
      prop: "maintenanceResult",
    },
    {
      label: "维修日期",
      align: "center",
      prop: "maintenanceTime",
      formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
    },
    {
      label: "状态",
      align: "center",
      prop: "status",
      dataType: "slot",
      slot: "statusRef",
    },
    {
      fixed: "right",
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
    },
  ]
    [
      {
        label: "设备名称",
        align: "center",
        prop: "deviceName",
      },
      {
        label: "规格型号",
        align: "center",
        prop: "deviceModel",
      },
      {
        label: "报修日期",
        align: "center",
        prop: "repairTime",
        formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
      },
      {
        label: "报修人",
        align: "center",
        prop: "repairName",
      },
      {
        label: "故障现象",
        align: "center",
        prop: "remark",
      },
      {
        label: "维修人",
        align: "center",
        prop: "maintenanceName",
      },
      {
        label: "维修结果",
        align: "center",
        prop: "maintenanceResult",
      },
      {
        label: "维修日期",
        align: "center",
        prop: "maintenanceTime",
        formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
      },
      {
        label: "状态",
        align: "center",
        prop: "status",
        dataType: "slot",
        slot: "statusRef",
      },
      {
        fixed: "right",
        label: "操作",
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "300px",
      },
    ]
);
// type === 1 ç»´ä¿® 2报修间
const handleDateChange = (value,type) => {
const handleDateChange = (value, type) => {
  filters.maintenanceTimeStr = null
  filters.c = null
  if(type === 1){
  if (type === 1) {
    if (value) {
      filters.maintenanceTimeStr = dayjs(value).format("YYYY-MM-DD");
    }
  }else{
  } else {
    if (value) {
      filters.repairTimeStr = dayjs(value).format("YYYY-MM-DD");
    }
@@ -253,6 +259,11 @@
  multipleList.value = selectionList;
};
// æ£€æŸ¥é€‰ä¸­çš„记录中是否有完结状态的
const hasFinishedStatus = computed(() => {
  return multipleList.value.some(item => item.status === 1)
})
// æ–°å¢žæŠ¥ä¿®
const addRepair = () => {
  repairModalRef.value.openAdd();
@@ -264,30 +275,56 @@
};
// æ–°å¢žç»´ä¿®
const addMaintain = () => {
  const row = multipleList.value[0];
const addMaintain = (row) => {
  maintainModalRef.value.open(row.id, row);
};
const changePage = ({ page, limit }) => {
    pagination.currentPage = page;
    pagination.pageSize = limit;
    onCurrentChange(page);
const changePage = ({page, limit}) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  onCurrentChange(page);
};
// å•行删除
const delRepairByIds = async (ids) => {
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  const idsArray = Array.isArray(ids) ? ids : [ids];
  const hasFinished = idsArray.some(id => {
    const record = dataList.value.find(item => item.id === id);
    return record && record.status === 1;
  });
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录');
    return;
  }
  ElMessageBox.confirm("确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?", "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    const { code } = await delRepair(ids);
    const {code} = await delRepair(ids);
    if (code === 200) {
      ElMessage.success("删除成功");
      getTableData();
    }
  });
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        proxy.download("/device/repair/export", {}, "设备报修.xlsx");
      })
      .catch(() => {
        ElMessage.info("已取消");
      });
};
onMounted(() => {
@@ -299,6 +336,7 @@
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
src/views/equipmentManagement/spareParts/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,401 @@
<template>
  <div class="spare-part-category">
        <div class="search_form">
            <el-form :inline="true" :model="queryParams" class="search-form">
                <el-form-item label="备件名称">
                    <el-input
                        v-model="queryParams.name"
                        placeholder="请输入备件名称"
                        clearable
                        style="width: 240px"
                    />
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleQuery">查询</el-button>
                    <el-button @click="resetQuery">重置</el-button>
                </el-form-item>
            </el-form>
            <div>
                <el-button type="primary" @click="addCategory" >新增</el-button>
            </div>
        </div>
    <div class="table_list">
      <el-table
        v-loading="loading"
        :data="renderTableData"
        style="width: 100%; margin-top: 10px;"
        border
        row-key="id"
      >
      <el-table-column prop="deviceNameStr" label="设备名称"  width="300"></el-table-column>
        <el-table-column prop="name" label="备件名称" width="200"></el-table-column>
        <el-table-column prop="sparePartsNo" label="备件编号" width="200"></el-table-column>
        <el-table-column prop="status" label="状态" width="100">
          <template #default="{ row }">
            <el-tag type="success" size="small">{{ row.status }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="price" label="ä»·æ ¼" width="140"></el-table-column>
        <el-table-column prop="quantity" label="数量" width="140"></el-table-column>
        <el-table-column prop="description" label="描述" width="150"></el-table-column>
        <el-table-column label="操作" width="150" fixed="right" align="center">
          <template #default="{ row }">
            <el-button
              link
                            type="primary"
              @click="() => editCategory(row)"
              :disabled="loading"
            >
              ç¼–辑
            </el-button>
            <el-button
                            link
              @click="() => deleteCategory(row.id)"
              style="color: #f56c6c;"
              :disabled="loading"
            >
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <el-dialog title="分类管理" v-model="dialogVisible" width="60%">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-form-item label="设备" prop="deviceLedgerIds">
          <el-select
            v-model="form.deviceLedgerIds"
            placeholder="请选择设备"
            filterable
            default-first-option
            :reserve-keyword="false"
            multiple
            style="width: 100%"
          >
            <el-option
              v-for="(item, index) in deviceOptions"
              :key="index"
              :label="item.deviceName"
              :value="item.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="备件名称" prop="name">
          <el-input v-model="form.name"></el-input>
        </el-form-item>
        <el-form-item label="备件编号" prop="sparePartsNo">
          <el-input v-model="form.sparePartsNo"></el-input>
        </el-form-item>
        <el-form-item label="数量" prop="quantity">
          <el-input type="number" v-model="form.quantity"></el-input>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-select v-model="form.status" placeholder="请选择状态">
            <el-option label="正常" value="正常"></el-option>
            <el-option label="禁用" value="禁用"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="描述" prop="description">
          <el-input v-model="form.description"></el-input>
        </el-form-item>
        <el-form-item label="ä»·æ ¼" prop="price">
          <el-input-number
            v-model="form.price"
            placeholder="请输入价格"
            :min="0"
            :step="0.01"
            :precision="2"
            style="width: 100%"
          ></el-input-number>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false" :disabled="formLoading">取消</el-button>
          <el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, computed, onMounted, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
// åŠ è½½çŠ¶æ€
const loading = ref(false);
const formLoading = ref(false);
// å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€
const dialogVisible = ref(false);
// ç¼–辑 ID
const editId = ref(null);
// è¡¨æ ¼æ•°æ®
const categories = ref([]);
// æ¸²æŸ“用的表格数据
// const renderTableData = computed(() => buildTree(categories.value));
const renderTableData = ref([]);
const operationType = ref('add')
// è®¾å¤‡é€‰é¡¹
const deviceOptions = ref([]);
// è¡¨å•引用
const formRef = ref(null);
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({
  name: ''
});
// è¡¨å•数据
const form = reactive({
  id:'',
  name: '',
  sparePartsNo: '',
  status: '',
  description: '',
  deviceLedgerIds: [],
  price: null
});
// è¡¨å•验证规则
const rules = reactive({
  name: [
    { required: true, message: '请输入备件名称', trigger: 'blur' }
  ],
  sparePartsNo: [
    { required: true, message: '请输入备件编号', trigger: 'blur' }
  ],
  quantity:[
    { required: true, message: '请输入数量', trigger: 'blur' }
  ],
  status: [
    { required: true, message: '请选择状态', trigger: 'change' }
  ],
  deviceLedgerIds: [
    {
      required: true,
      message: '请选择设备',
      trigger: 'change',
      validator: (rule, value, callback) => {
        if (operationType.value === 'add' && (!value || value.length === 0)) {
          callback(new Error('请选择设备'));
        } else {
          callback();
        }
      }
    }
  ]
});
// èŽ·å–ç¼©è¿›é‡
const getIndentation = (row) => {
  // è¿™é‡Œç®€å•返回 20,可根据实际需求实现层级缩进逻辑
  return 20;
};
// å®šä¹‰ buildTree å‡½æ•°
const buildTree = (flatData) => {
  const map = {};
  const result = [];
  if(flatData){
    return result;
  }
  flatData.forEach(item => {
    map[item.id] = { ...item, children: [] };
  });
  flatData.forEach(item => {
    if (item.parentId === null || !map[item.parentId]) {
      result.push(map[item.id]);
    } else {
      map[item.parentId].children.push(map[item.id]);
    }
  });
  return result;
};
// èŽ·å–åˆ—è¡¨æ•°æ®
const fetchListData = async () => {
  loading.value = true;
  try {
    const params = {};
    if (queryParams.name) {
      params.name = queryParams.name;
    }
    const res = await getSparePartsList(params);
    if (res.code === 200) {
      renderTableData.value = res.data.records || [];
      categories.value = res.data.records || [];
    }
  } catch (error) {
        loading.value = false;
  } finally {
    loading.value = false;
  }
}
// æŸ¥è¯¢
const handleQuery = () => {
  fetchListData();
}
// é‡ç½®æŸ¥è¯¢
const resetQuery = () => {
  queryParams.name = '';
  fetchListData();
}
// åŠ è½½è®¾å¤‡åˆ—è¡¨ï¼ˆåœ¨æ‰“å¼€å¼¹æ¡†æ—¶è°ƒç”¨ï¼‰
const loadDeviceName = async () => {
  try {
    const { data } = await getDeviceLedger();
    deviceOptions.value = data || [];
  } catch (error) {
    ElMessage.error('获取设备列表失败');
  }
};
// æ–°å¢žåˆ†ç±»
const addCategory = async () => {
  await loadDeviceName();
  form.id = '';
  form.name = '';
  form.sparePartsNo = '';
  form.status = '';
  form.description = '';
  form.deviceLedgerIds = [];
  form.price = null;
  operationType.value = 'add'
  dialogVisible.value = true;
};
// ç¼–辑分类
const editCategory = async (row) => {
  await loadDeviceName();
  Object.assign(form, row);
  // å¦‚果后端返回的是 deviceIds å­—符串,需要转换为数组
  if (row.deviceIds && typeof row.deviceIds === 'string') {
    // ç¡®ä¿ID类型与设备选项中的ID类型一致
    const deviceIdsArray = row.deviceIds.split(',').map(id => id.trim()).filter(id => id);
    // å¦‚果设备选项中的ID是数字类型,则转换为数字
    if (deviceOptions.value.length > 0 && typeof deviceOptions.value[0].id === 'number') {
      form.deviceLedgerIds = deviceIdsArray.map(id => Number(id)).filter(id => !isNaN(id));
    } else {
      form.deviceLedgerIds = deviceIdsArray;
    }
  } else if (row.deviceIds && Array.isArray(row.deviceIds)) {
    form.deviceLedgerIds = row.deviceIds;
  } else {
    form.deviceLedgerIds = [];
  }
  operationType.value = 'edit'
  dialogVisible.value = true;
};
// åˆ é™¤åˆ†ç±»
const deleteCategory = async (id) => {
  try {
    await ElMessageBox.confirm('此操作将永久删除该分类,是否继续?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    });
    loading.value = true;
    const res = await delSparePart(id);
    if (res.code === 200) {
      ElMessage.success('删除成功');
      fetchListData();
    } else {
      ElMessage.error(res.message || '删除失败');
    }
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败');
    }
  } finally {
    loading.value = false;
  }
};
// æäº¤è¡¨å•
const submitForm = async () => {
  if (!formRef.value) return;
  try {
    await formRef.value.validate();
    formLoading.value = true;
    // æž„建提交数据
    const submitData = {
      ...form,
      deviceIds: form.deviceLedgerIds && form.deviceLedgerIds.length > 0
        ? form.deviceLedgerIds.join(',')
        : ''
    };
    // åˆ é™¤ä¸éœ€è¦çš„字段
    delete submitData.deviceLedgerIds;
    if (operationType.value === 'edit') {
      let res = await editSparePart(submitData);
      if (res.code === 200) {
        ElMessage.success('编辑成功');
        dialogVisible.value = false;
        fetchListData();
      }
    } else {
      let res = await addSparePart(submitData);
      if (res.code === 200) {
        ElMessage.success('新增成功');
        dialogVisible.value = false;
        fetchListData();
      }
    }
  } catch (error) {
    ElMessage.error('请填写完整表单信息');
  } finally {
    formLoading.value = false;
  }
};
// ç»„件挂载时获取列表数据
onMounted(() => {
  fetchListData();
});
</script>
<style scoped>
.spare-part-category {
  padding: 20px;
}
.search_form {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
}
.table_list {
  margin-top: unset;
}
.el-table__header-wrapper th {
  background-color: #f5f7fa;
  font-weight: 600;
}
.el-table__row:hover > td {
  background-color: #fafafa;
}
/* æŒ‰é’®ç»„样式 */
.actions > div {
  display: flex;
  gap: 10px;
}
/* ç¡®ä¿è¡¨æ ¼ä¸­çš„æ“ä½œæŒ‰é’®ä¸ä¼šè¢«æˆªæ–­ */
.el-table-column--fixed-right .el-button {
  margin: 0 2px;
}
/* æ ‘形节点内容样式 */
.nested-tree .el-tree-node__expand-icon {
  font-size: 12px;
  margin-right: 4px;
}
</style>
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
<template>
  <FormDialog
    v-model="visible"
    :title="'设备保养'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-form-item label="实际保养人">
        <el-input
          v-model="form.maintenanceActuallyName"
          placeholder="请输入实际保养人"
        ></el-input>
      </el-form-item>
      <el-form-item label="实际保养日期">
        <el-date-picker
          v-model="form.maintenanceActuallyTime"
          placeholder="请选择实际保养日期"
          format="YYYY-MM-DD HH:mm:ss"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="datetime"
          clearable
          style="width: 100%"
        />
      </el-form-item>
      <el-form-item label="保养状态">
        <el-select v-model="form.status">
          <el-option label="待保养" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="保养结果">
        <el-input
          v-model="form.maintenanceResult"
          placeholder="请输入保养结果"
          type="text" />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { addMaintenance } from "@/api/equipmentManagement/upkeep";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
import useUserStore from "@/store/modules/user";
import { ElMessage } from "element-plus";
defineOptions({
  name: "保养模态框",
});
const emits = defineEmits(["ok"]);
// ä¿å­˜è®¡åˆ’保养记录的id
const planId = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const { form, resetForm } = useFormData({
  maintenanceActuallyName: undefined, // å®žé™…保养人
  maintenanceActuallyTime: undefined, // å®žé™…保养日期
  maintenanceResult: undefined, // ä¿å…»ç»“æžœ
  status: 0, // ä¿å…»çŠ¶æ€
});
const setForm = (data) => {
  form.maintenanceActuallyName =
    data.maintenanceActuallyName ?? userStore.nickName;
  form.maintenanceActuallyTime =
    data.maintenanceActuallyTime
      ? dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss")
      : dayjs().format("YYYY-MM-DD HH:mm:ss");
  form.maintenanceResult = data.maintenanceResult;
  form.status = 1; // é»˜è®¤çŠ¶æ€ä¸ºå®Œç»“
};
/**
 * @desc ä¿å­˜ä¿å…»
 */
const sendForm = async () => {
  loading.value = true;
  try {
    const { code } = await addMaintenance({ id: planId.value, ...form });
    if (code == 200) {
      ElMessage.success("保养成功");
      emits("ok");
      resetForm();
      visible.value = false;
    }
  } finally {
    loading.value = false;
  }
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const open = async (id, row) => {
  planId.value = id; // ä¿å­˜è®¡åˆ’保养记录的id
  visible.value = true;
  await nextTick();
  setForm(row);
};
defineExpose({
  open,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/upkeep/Form/PlanForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Form/PlanModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,188 @@
<template>
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备保养计划' : '新增设备保养计划'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-form-item label="设备名称">
        <el-select
          v-model="form.deviceLedgerId"
          @change="setDeviceModel"
          placeholder="请选择设备"
          filterable
          default-first-option
          :reserve-keyword="false"
        >
          <el-option
            v-for="(item, index) in deviceOptions"
            :key="index"
            :label="item.deviceName"
            :value="item.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="规格型号">
        <el-input
          v-model="form.deviceModel"
          placeholder="请输入规格型号"
          disabled
        />
      </el-form-item>
      <el-form-item label="录入人">
        <el-select
          v-model="form.createUser"
          placeholder="请选择"
          filterable
          default-first-option
          :reserve-keyword="false"
          clearable
        >
          <el-option
            v-for="item in userList"
            :key="item.userId"
            :label="item.nickName"
            :value="item.userId"
          />
        </el-select>
      </el-form-item>
      <el-form-item v-if="id" label="保修状态">
        <el-select v-model="form.status">
          <el-option label="待保修" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="计划保养日期">
        <el-date-picker
          style="width: 100%"
          v-model="form.maintenancePlanTime"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="date"
          placeholder="请选择计划保养日期日期"
          clearable
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addUpkeep,
  editUpkeep,
  getUpkeepById,
} from "@/api/equipmentManagement/upkeep";
import { ElMessage } from "element-plus";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { onMounted } from "vue";
import dayjs from "dayjs";
import { userListNoPage } from "@/api/system/user.js";
defineOptions({
  name: "设备保养新增计划",
});
const emits = defineEmits(["ok"]);
const id = ref();
const visible = ref(false);
const loading = ref(false);
const deviceOptions = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined, // è®¾å¤‡Id
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  maintenancePlanTime: undefined, // è®¡åˆ’保养日期
  createUser: undefined, // å½•入人
  status: 0, //保修状态
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
};
/**
 * @desc è®¾ç½®è¡¨å•内容
 * @param data è®¾å¤‡ä¿¡æ¯
 */
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.createUser = Number(data.createUser);
  form.status = data.status;
  form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
    "YYYY-MM-DD HH:mm:ss"
  );
};
// ç”¨æˆ·åˆ—表
const userList = ref([]);
onMounted(() => {
  loadDeviceName();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
});
const openEdit = async (editId) => {
  const { data } = await getUpkeepById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  setForm(data);
};
const sendForm = async () => {
  loading.value = true;
  try {
    const { code } = id.value
      ? await editUpkeep({ id: unref(id), ...form })
      : await addUpkeep(form);
    if (code == 200) {
      ElMessage.success(`${id.value ? "编辑" : "新增"}计划成功`);
      visible.value = false;
      emits("ok");
    }
  } finally {
    loading.value = false;
  }
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const openModal = () => {
  id.value = undefined;
  visible.value = true;
};
defineExpose({
  openModal,
  openEdit,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/upkeep/Form/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,304 @@
<template>
    <FormDialog
        v-model="dialogVisitable"
        :title="operationType === 'add' ? '新增保养任务' : '编辑保养任务'"
        width="800px"
        :operation-type="operationType"
        @confirm="submitForm"
        @cancel="cancel"
        @close="cancel"
    >
        <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
            <el-row>
                <el-col :span="12">
                    <el-form-item label="设备名称" prop="taskId">
                        <el-select v-model="form.taskId" @change="setDeviceModel" filterable>
                            <el-option
                                v-for="(item, index) in deviceOptions"
                                :key="index"
                                :label="item.deviceName"
                                :value="item.id"
                            ></el-option>
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="规格型号">
                        <el-input
                            v-model="form.deviceModel"
                            placeholder="请输入规格型号"
                            disabled
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="录入人" prop="inspector">
                        <el-select
                            v-model="form.inspector"
                            filterable
                            default-first-option
                            :reserve-keyword="false"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in userList"
                                :label="item.nickName"
                                :value="item.userId"
                                :key="item.userId"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="登记时间" prop="registrationDate">
                        <el-date-picker
                            v-model="form.registrationDate"
                            type="date"
                            placeholder="选择登记日期"
                            format="YYYY-MM-DD"
                            value-format="YYYY-MM-DD"
                            style="width: 100%"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="任务频率" prop="frequencyType">
                        <el-select v-model="form.frequencyType" placeholder="请选择" clearable>
                            <el-option label="每日" value="DAILY"/>
                            <el-option label="每周" value="WEEKLY"/>
                            <el-option label="每月" value="MONTHLY"/>
                            <el-option label="季度" value="QUARTERLY"/>
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm" />
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-select v-model="form.week" placeholder="请选择" clearable style="width: 50%">
                            <el-option label="周一" value="MON"/>
                            <el-option label="周二" value="TUE"/>
                            <el-option label="周三" value="WED"/>
                            <el-option label="周四" value="THU"/>
                            <el-option label="周五" value="FRI"/>
                            <el-option label="周六" value="SAT"/>
                            <el-option label="周日" value="SUN"/>
                        </el-select>
                        <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm"  style="width: 50%"/>
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-date-picker
                            v-model="form.frequencyDetail"
                            type="datetime"
                            clearable
                            placeholder="选择开始日期"
                            format="DD,HH:mm"
                            value-format="DD,HH:mm"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-date-picker
                            v-model="form.frequencyDetail"
                            type="datetime"
                            clearable
                            placeholder="选择开始日期"
                            format="MM,DD,HH:mm"
                            value-format="MM,DD,HH:mm"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="备注" prop="remarks">
                        <el-input v-model="form.remarks" placeholder="请输入备注" type="textarea" />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
    </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { reactive, ref, getCurrentInstance, toRefs } from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { deviceMaintenanceTaskAdd, deviceMaintenanceTaskEdit } from "@/api/equipmentManagement/upkeep";
import { getCurrentDate } from "@/utils/index.js";
import useUserStore from "@/store/modules/user.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const dialogVisitable = ref(false);
const operationType = ref('add');
const deviceOptions = ref([]);
const userStore = useUserStore();
const data = reactive({
    form: {
        taskId: undefined,
        taskName: undefined,
        // å½•入人:单选一个用户 id
        inspector: undefined,
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        deviceModel: undefined, // è§„格型号
        registrationDate: ''
    },
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
        inspector: [{ required: true, message: "请选择录入人", trigger: "blur" },],
        registrationDate: [{ required: true, message: "请选择登记时间", trigger: "change" }]
    }
})
const { form, rules } = toRefs(data)
const userList = ref([])
const loadDeviceName = async () => {
    const { data } = await getDeviceLedger();
    deviceOptions.value = data;
};
// é€‰æ‹©è®¾å¤‡æ—¶ï¼Œå›žå¡«è®¾å¤‡åç§°(taskName)和规格型号(deviceModel)
const setDeviceModel = (id) => {
    const option = deviceOptions.value.find((item) => item.id === id);
    if (option) {
        form.value.taskId = option.id;
        form.value.taskName = option.deviceName;
        form.value.deviceModel = option.deviceModel;
    }
}
// æ‰“开弹框
const openDialog = async (type, row) => {
    dialogVisitable.value = true
    operationType.value = type
    // é‡ç½®è¡¨å•
    resetForm();
    // åŠ è½½ç”¨æˆ·åˆ—è¡¨
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    // åŠ è½½è®¾å¤‡åˆ—è¡¨
    await loadDeviceName();
    if (type === 'edit' && row) {
        form.value = { ...row }
        // ç¼–辑时用接口返回的 registrantId å›žæ˜¾å½•入人
        if (row.registrantId) {
            form.value.inspector = row.registrantId
        }
        // å¦‚果有设备ID,自动设置设备信息
        if (form.value.taskId) {
            setDeviceModel(form.value.taskId);
        }
    } else if (type === 'add') {
        // æ–°å¢žæ—¶è®¾ç½®ç™»è®°æ—¥æœŸä¸ºå½“天
        form.value.registrationDate = getCurrentDate();
        // æ–°å¢žæ—¶è®¾ç½®å½•入人为当前登录账户
        form.value.inspector = userStore.id;
    }
}
// å…³é—­å¯¹è¯æ¡†
const cancel = () => {
    resetForm()
    dialogVisitable.value = false
    emit('closeDia')
}
// é‡ç½®è¡¨å•函数
const resetForm = () => {
    if (proxy.$refs.formRef) {
        proxy.$refs.formRef.resetFields()
    }
    // é‡ç½®è¡¨å•数据确保设备信息正确重置
    form.value = {
        taskId: undefined,
        taskName: undefined,
        inspector: undefined,
        inspector: undefined,
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        deviceModel: undefined,
        registrationDate: ''
    }
}
// æäº¤è¡¨å•
const submitForm = () => {
    proxy.$refs["formRef"].validate(async valid => {
        if (valid) {
            try {
                const payload = { ...form.value }
                // ä¸å†å‘后端传保养人字段,仅使用接口要求的 registrant / registrantId
                // æ ¹æ®é€‰æ‹©çš„"录入人"设置 registrant / registrantId
                if (payload.inspector) {
                    const selectedUser = userList.value.find(
                        (u) => String(u.userId) === String(payload.inspector)
                    )
                    if (selectedUser) {
                        payload.registrantId = selectedUser.userId
                        payload.registrant = selectedUser.nickName
                    }
                }
                delete payload.inspector
                delete payload.inspectorIds
                if (payload.frequencyType === 'WEEKLY') {
                    let frequencyDetail = ''
                    frequencyDetail = payload.week + ',' + payload.time
                    payload.frequencyDetail = frequencyDetail
                }
                // å½•入日期:直接使用表单里的 registrationDate å­—段
                // ä¸€äº›é»˜è®¤çŠ¶æ€å­—æ®µ
                if (payload.status === undefined || payload.status === null || payload.status === '') {
                    payload.status = '0' // é»˜è®¤çŠ¶æ€ï¼Œå¯æŒ‰å®žé™…æžšä¸¾è°ƒæ•´
                }
                payload.active = true
                payload.deleted = 0
                if (operationType.value === 'edit') {
                    await deviceMaintenanceTaskEdit(payload)
                } else {
                    await deviceMaintenanceTaskAdd(payload)
                }
                cancel()
                proxy.$modal.msgSuccess('提交成功')
            } catch (error) {
                proxy.$modal.msgError('提交失败,请重试')
            }
        }
    })
}
defineExpose({ openDialog })
</script>
<style scoped>
</style>
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/index.vue
@@ -1,75 +1,155 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="设备名称">
        <el-input
            v-model="filters.deviceName"
            style="width: 240px"
            placeholder="请输入设备名称"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="计划保养日期">
        <el-date-picker
            v-model="filters.maintenancePlanTime"
            type="date"
            placeholder="请选择计划保养日期"
            size="default"
            @change="(date) => handleDateChange(date,2)"
        />
      </el-form-item>
      <el-form-item label="实际保养日期">
        <el-date-picker
            v-model="filters.maintenanceActuallyTime"
            type="date"
            placeholder="请选择实际保养日期"
            size="default"
            @change="(date) => handleDateChange(date,1)"
        />
      </el-form-item>
      <el-form-item label="实际保养人">
        <el-input
            v-model="filters.maintenanceActuallyName"
            style="width: 240px"
            placeholder="请输入实际保养人"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <el-text class="mx-1" size="large">设备保养</el-text>
        <div>
          <el-button
            type="primary"
            icon="Plus"
            :disabled="multipleList.length !== 1"
            @click="addMaintain"
          >
            æ–°å¢žä¿å…»
          </el-button>
          <el-button type="success" icon="Van" @click="addPlan">
            æ–°å¢žè®¡åˆ’
          </el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="delRepairByIds(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
          </el-button>
    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
      <!-- å®šæ—¶ä»»åŠ¡ç®¡ç†tab -->
      <el-tab-pane label="定时任务管理" name="scheduled">
        <div class="search_form">
          <el-form :model="scheduledFilters" :inline="true">
            <el-form-item label="任务名称">
              <el-input
                  v-model="scheduledFilters.taskName"
                  style="width: 240px"
                  placeholder="请输入任务名称"
                  clearable
                  :prefix-icon="Search"
                  @change="getScheduledTableData"
              />
            </el-form-item>
            <el-form-item label="任务状态">
              <el-select v-model="scheduledFilters.status" placeholder="请选择任务状态" clearable style="width: 200px">
                <el-option label="启用" value="1" />
                <el-option label="停用" value="0" />
              </el-select>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="getScheduledTableData">搜索</el-button>
              <el-button @click="resetScheduledFilters">重置</el-button>
            </el-form-item>
          </el-form>
        </div>
      </div>
      <PIMTable
        <div class="table_list">
          <div class="actions">
            <el-text class="mx-1" size="large">定时任务管理</el-text>
            <div>
              <el-button type="primary" icon="Plus" @click="addScheduledTask">
                æ–°å¢žä»»åŠ¡
              </el-button>
              <el-button
                type="danger"
                icon="Delete"
                :disabled="scheduledMultipleList.length <= 0"
                @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))"
              >
                æ‰¹é‡åˆ é™¤
              </el-button>
            </div>
          </div>
          <PIMTable
            rowKey="id"
            isSelection
            :column="scheduledColumns"
            :tableData="scheduledDataList"
            :page="{
              current: scheduledPagination.currentPage,
              size: scheduledPagination.pageSize,
              total: scheduledPagination.total,
            }"
            @selection-change="handleScheduledSelectionChange"
            @pagination="changeScheduledPage"
          >
            <template #statusRef="{ row }">
              <el-tag v-if="row.status === 1" type="success">启用</el-tag>
              <el-tag v-if="row.status === 0" type="danger">停用</el-tag>
            </template>
            <template #operation="{ row }">
              <el-button
                type="primary"
                link
                @click="editScheduledTask(row)"
              >
                ç¼–辑
              </el-button>
              <el-button
                type="danger"
                link
                @click="delScheduledTaskByIds(row.id)"
              >
                åˆ é™¤
              </el-button>
            </template>
          </PIMTable>
        </div>
      </el-tab-pane>
      <!-- ä»»åŠ¡è®°å½•tab(原设备保养页面) -->
      <el-tab-pane label="任务记录" name="record">
        <div class="search_form">
          <el-form :model="filters" :inline="true">
            <el-form-item label="设备名称">
              <el-input
                  v-model="filters.deviceName"
                  style="width: 240px"
                  placeholder="请输入设备名称"
                  clearable
                  :prefix-icon="Search"
                  @change="getTableData"
              />
            </el-form-item>
            <el-form-item label="计划保养日期">
              <el-date-picker
                  v-model="filters.maintenancePlanTime"
                  type="date"
                  placeholder="请选择计划保养日期"
                  size="default"
                  @change="(date) => handleDateChange(date,2)"
              />
            </el-form-item>
            <el-form-item label="实际保养日期">
              <el-date-picker
                  v-model="filters.maintenanceActuallyTime"
                  type="date"
                  placeholder="请选择实际保养日期"
                  size="default"
                  @change="(date) => handleDateChange(date,1)"
              />
            </el-form-item>
            <el-form-item label="实际保养人">
              <el-input
                  v-model="filters.maintenanceActuallyName"
                  style="width: 240px"
                  placeholder="请输入实际保养人"
                  clearable
                  :prefix-icon="Search"
                  @change="getTableData"
              />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="getTableData">搜索</el-button>
              <el-button @click="resetFilters">重置</el-button>
            </el-form-item>
          </el-form>
        </div>
        <div class="table_list">
          <div class="actions">
            <el-text class="mx-1" size="large">任务记录</el-text>
            <div>
              <el-button type="success" icon="Van" @click="addPlan">
                æ–°å¢žè®¡åˆ’
              </el-button>
              <el-button @click="handleOut">
                å¯¼å‡º
              </el-button>
              <el-button
                type="danger"
                icon="Delete"
                :disabled="multipleList.length <= 0 || hasFinishedStatus"
                @click="delRepairByIds(multipleList.map((item) => item.id))"
              >
                æ‰¹é‡åˆ é™¤
              </el-button>
            </div>
          </div>
         <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
@@ -83,203 +163,422 @@
        @pagination="changePage"
      >
        <template #maintenanceResultRef="{ row }">
          <el-tag v-if="row.maintenanceResult === 1" type="success">
            å®Œå¥½
          </el-tag>
          <el-tag v-if="row.maintenanceResult === 0" type="danger">
            ç»´ä¿®
          </el-tag>
          <div>{{ row.maintenanceResult || '-' }}</div>
        </template>
        <template #statusRef="{ row }">
          <el-tag v-if="row.status === 2" type="danger">失败</el-tag>
          <el-tag v-if="row.status === 1" type="success">完结</el-tag>
          <el-tag v-if="row.status === 0" type="danger">待保养</el-tag>
          <el-tag v-if="row.status === 0" type="warning">待保养</el-tag>
        </template>
        <template #operation="{ row }">
          <!-- è¿™ä¸ªåŠŸèƒ½è·Ÿæ–°å¢žä¿å…»åŠŸèƒ½ä¸€æ¨¡ä¸€æ ·ï¼Œæœ‰å•¥æ„ä¹‰ï¼Ÿ -->
          <!-- <el-button
              type="primary"
              text
              @click="addMaintain(row)"
          >
            æ–°å¢žä¿å…»
          </el-button> -->
          <el-button
            type="primary"
            text
            icon="editPen"
            link
            :disabled="row.status === 1"
            @click="editPlan(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="success"
            link
            :disabled="row.status === 1"
            @click="addMaintain(row)"
          >
            ä¿å…»
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            link
            :disabled="row.status === 1"
            @click="delRepairByIds(row.id)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </PIMTable>
    </div>
        </div>
      </el-tab-pane>
    </el-tabs>
    <PlanModal ref="planModalRef" @ok="getTableData" />
    <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
        <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
        <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" />
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { getUpkeepPage, delUpkeep } from "@/api/equipmentManagement/upkeep";
import { onMounted } from "vue";
import PlanModal from "./Modal/PlanModal.vue";
import MaintenanceModal from "./Modal/MaintenanceModal.vue";
import dayjs from "dayjs";
import { ElMessageBox, ElMessage } from "element-plus";
import {Search} from "@element-plus/icons-vue";
import { ref, onMounted, reactive, getCurrentInstance, nextTick, computed } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import PlanModal from './Form/PlanModal.vue'
import MaintenanceModal from './Form/MaintenanceModal.vue'
import FormDia from './Form/formDia.vue'
import {
  getUpkeepPage,
  delUpkeep,
  deviceMaintenanceTaskList,
  deviceMaintenanceTaskDel,
} from '@/api/equipmentManagement/upkeep'
import dayjs from 'dayjs'
defineOptions({
  name: "设备保养",
});
const { proxy } = getCurrentInstance()
// Tab相关
const activeTab = ref('scheduled')
// è®¡åˆ’弹窗控制器
const planModalRef = ref();
const planModalRef = ref()
// ä¿å…»å¼¹çª—控制器
const maintainModalRef = ref();
const maintainModalRef = ref()
// å®šæ—¶ä»»åŠ¡å¼¹çª—æŽ§åˆ¶å™¨
const formDiaRef = ref()
// è¡¨æ ¼å¤šé€‰æ¡†é€‰ä¸­é¡¹
const multipleList = ref([]);
// ä»»åŠ¡è®°å½•tab(原设备保养页面)相关变量
const filters = reactive({
  deviceName: '',
  maintenancePlanTime: '',
  maintenanceActuallyTime: '',
  maintenanceActuallyName: '',
})
// å¤šé€‰åŽåšä»€ä¹ˆ
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
const dataList = ref([])
const pagination = ref({
  currentPage: 1,
  pageSize: 10,
  total: 0,
})
const multipleList = ref([])
// è¡¨æ ¼é’©å­
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(getUpkeepPage, {
  deviceName: undefined,
  maintenancePlanTime: undefined,
  maintenanceActuallyTime: undefined,
  maintenanceActuallyName: undefined,
}, [
  {
    label: "设备名称",
    align: "center",
    prop: "deviceName",
  },
  {
    label: "规格型号",
    align: "center",
    prop: "deviceModel",
  },
  {
    label: "计划保养日期",
    align: "center",
    prop: "maintenancePlanTime",
    formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
  },
  {
    label: "录入人",
    align: "center",
    prop: "createUserName",
  },
  {
    label: "录入日期",
    align: "center",
    prop: "createTime",
    formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
    width: 200,
  },
  {
    label: "实际保养人",
    align: "center",
    prop: "maintenanceActuallyName",
  },
  {
    label: "实际保养日期",
    align: "center",
    prop: "maintenanceActuallyTime",
    formatData: (cell) =>
      cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
  },
  {
    label: "保养结果",
    align: "center",
    prop: "maintenanceResult",
    dataType: "slot",
    slot: "maintenanceResultRef",
  },
  {
    label: "状态",
    align: "center",
    prop: "status",
    dataType: "slot",
    slot: "statusRef",
  },
  {
    fixed: "right",
    label: "操作",
    dataType: "slot",
    slot: "operation",
    align: "center",
    width: "200px",
  },
]);
// type == 1实际保养时间 2计划保养时间
const handleDateChange = (value,type) => {
  filters.maintenanceActuallyTimeReq = null
  filters.maintenancePlanTimeReq = null
  if(type === 1){
    if (value) {
      filters.maintenanceActuallyTimeReq = dayjs(value).format("YYYY-MM-DD");
    }
  }else{
    if (value) {
      filters.maintenancePlanTimeReq = dayjs(value).format("YYYY-MM-DD");
    }
// å®šæ—¶ä»»åŠ¡ç®¡ç†tab相关变量
const scheduledFilters = reactive({
  taskName: '',
  status: '',
})
const scheduledDataList = ref([])
const scheduledPagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
})
const scheduledMultipleList = ref([])
// å®šæ—¶ä»»åŠ¡ç®¡ç†è¡¨æ ¼åˆ—é…ç½®
const scheduledColumns = ref([
    { prop: "taskName", label: "设备名称"},
    {
        label: "规格型号",
        prop: "deviceModel",
    },
    {
        prop: "frequencyType",
        label: "频次",
        minWidth: 150,
        // PIMTable ä½¿ç”¨çš„æ˜¯ formatData,而不是 Element-Plus çš„ formatter
        formatData: (cell) => ({
            DAILY: "每日",
            WEEKLY: "每周",
            MONTHLY: "每月",
            QUARTERLY: "季度"
        }[cell] || "")
    },
    {
        prop: "frequencyDetail",
        label: "开始日期与时间",
        minWidth: 150,
        // åŒæ ·æ”¹ç”¨ formatData,PIMTable å†…部会把单元格值传进来
        formatData: (cell) => {
            if (typeof cell !== 'string') return '';
            let val = cell;
            const replacements = {
                MON: '周一',
                TUE: '周二',
                WED: '周三',
                THU: '周四',
                FRI: '周五',
                SAT: '周六',
                SUN: '周日'
            };
            // ä½¿ç”¨æ­£åˆ™ä¸€æ¬¡æ€§æ›¿æ¢æ‰€æœ‰åŒ¹é…é¡¹
            return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
        }
    },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    { prop: "registrationDate", label: "登记日期", minWidth: 100 },
    {
        fixed: "right",
        label: "操作",
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "200px",
    },
])
// ä»»åŠ¡è®°å½•è¡¨æ ¼åˆ—é…ç½®ï¼ˆåŽŸè®¾å¤‡ä¿å…»è¡¨æ ¼åˆ—ï¼‰
const columns = ref([
    {
        label: "设备名称",
        align: "center",
        prop: "deviceName",
    },
    {
        label: "规格型号",
        align: "center",
        prop: "deviceModel",
    },
    {
        label: "计划保养日期",
        align: "center",
        prop: "maintenancePlanTime",
        formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
    },
    {
        label: "录入人",
        align: "center",
        prop: "createUserName",
    },
    // {
    //   label: "录入日期",
    //   align: "center",
    //   prop: "createTime",
    //   formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
    //   width: 200,
    // },
    {
        label: "实际保养人",
        align: "center",
        prop: "maintenanceActuallyName",
    },
    {
        label: "实际保养日期",
        align: "center",
        prop: "maintenanceActuallyTime",
        formatData: (cell) =>
            cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
    },
    {
        label: "保养结果",
        align: "center",
        prop: "maintenanceResult",
        dataType: "slot",
        slot: "maintenanceResultRef",
    },
    {
        label: "状态",
        align: "center",
        prop: "status",
        dataType: "slot",
        slot: "statusRef",
    },
    {
        fixed: "right",
        label: "操作",
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "300px",
    },
])
// Tab切换处理
const handleTabChange = (tabName) => {
  if (tabName === 'record') {
    getTableData()
  } else if (tabName === 'scheduled') {
    getScheduledTableData()
  }
  getTableData();
};
}
// æ–°å¢žä¿å…»
const addMaintain = () => {
  const row = multipleList.value[0];
  maintainModalRef.value.open(row.id, row);
};
// æ–°å¢žè®¡åˆ’
const addPlan = () => {
  planModalRef.value.openModal();
};
// ç¼–辑计划
const editPlan = (id) => {
  planModalRef.value.openEdit(id);
};
const changePage = ({ page, limit }) => {
    pagination.currentPage = page;
    pagination.pageSize = limit;
    onCurrentChange(page);
};
// å•行删除
const delRepairByIds = async (ids) => {
  ElMessageBox.confirm("确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?", "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    const { code } = await delUpkeep(ids);
    if (code === 200) {
      ElMessage.success("删除成功");
      getTableData();
// å®šæ—¶ä»»åŠ¡ç®¡ç†ç›¸å…³æ–¹æ³•
const getScheduledTableData = async () => {
  try {
    const params = {
      current: scheduledPagination.currentPage,
      size: scheduledPagination.pageSize,
      taskName: scheduledFilters.taskName || undefined,
      status: scheduledFilters.status || undefined,
    }
  });
};
    const { code, data } = await deviceMaintenanceTaskList(params)
    if (code === 200) {
      scheduledDataList.value = data?.records || []
      scheduledPagination.total = data?.total || 0
    }
  } catch (error) {
    ElMessage.error('获取定时任务列表失败')
  }
}
const resetScheduledFilters = () => {
  scheduledFilters.taskName = ''
  scheduledFilters.status = ''
  getScheduledTableData()
}
const handleScheduledSelectionChange = (selection) => {
  scheduledMultipleList.value = selection
}
const changeScheduledPage = (page) => {
  scheduledPagination.currentPage = page.page
  scheduledPagination.pageSize = page.limit
  getScheduledTableData()
}
const addScheduledTask = () => {
  nextTick(() => {
        formDiaRef.value?.openDialog('add');
    });
}
const editScheduledTask = (row) => {
  if (row) {
        nextTick(() => {
            formDiaRef.value?.openDialog('edit', row);
        });
  }
}
const delScheduledTaskByIds = async (ids) => {
  try {
    await ElMessageBox.confirm('确定删除选中的定时任务吗?', '提示', {
      type: 'warning',
    })
    const payload = Array.isArray(ids) ? ids : [ids]
    await deviceMaintenanceTaskDel(payload)
    ElMessage.success('删除定时任务成功')
    getScheduledTableData()
  } catch (error) {
    // ç”¨æˆ·å–消删除
  }
}
const handleScheduledOut = () => {
  ElMessage.info('导出定时任务功能待实现')
}
// ä»»åŠ¡è®°å½•ç›¸å…³æ–¹æ³•ï¼ˆåŽŸè®¾å¤‡ä¿å…»é¡µé¢æ–¹æ³•ï¼‰
const getTableData = async () => {
  try {
    const params = {
      current: pagination.value.currentPage,
      size: pagination.value.pageSize,
      deviceName: filters.deviceName || undefined,
      maintenancePlanTime: filters.maintenancePlanTime ? dayjs(filters.maintenancePlanTime).format('YYYY-MM-DD') : undefined,
      maintenanceActuallyTime: filters.maintenanceActuallyTime ? dayjs(filters.maintenanceActuallyTime).format('YYYY-MM-DD') : undefined,
      maintenanceActuallyName: filters.maintenanceActuallyName || undefined,
    }
    const { code, data } = await getUpkeepPage(params)
    if (code === 200) {
      dataList.value = data.records
      pagination.value.total = data.total
    }
  } catch (error) {
    console.log(error);
  }
}
const resetFilters = () => {
  filters.deviceName = ''
  filters.maintenancePlanTime = ''
  filters.maintenanceActuallyTime = ''
  filters.maintenanceActuallyName = ''
  getTableData()
}
const handleSelectionChange = (selection) => {
  multipleList.value = selection
}
// æ£€æŸ¥é€‰ä¸­çš„记录中是否有完结状态的
const hasFinishedStatus = computed(() => {
  return multipleList.value.some(item => item.status === 1)
})
const changePage = (page) => {
  pagination.value.currentPage = page.page
  pagination.value.pageSize = page.limit
  getTableData()
}
const addMaintain = (row) => {
  maintainModalRef.value.open(row.id, row)
}
const addPlan = () => {
  planModalRef.value.openModal()
}
const editPlan = (id) => {
  planModalRef.value.openEdit(id)
}
const delRepairByIds = async (ids) => {
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  const hasFinished = multipleList.value.some(item => item.status === 1)
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?', '警告', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    })
    const { code } = await delUpkeep(ids)
    if (code === 200) {
      ElMessage.success('删除成功')
      getTableData()
    }
  } catch (error) {
    // ç”¨æˆ·å–消删除
  }
}
const handleOut = () => {
  ElMessageBox.confirm('选中的内容将被导出,是否确认导出?', '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => {
      proxy.download('/device/maintenance/export', {}, '设备保养.xlsx')
    })
    .catch(() => {
      ElMessage.info('已取消')
    })
}
const handleDateChange = (date, type) => {
  if (type === 1) {
    filters.maintenanceActuallyTime = date ? dayjs(date).format('YYYY-MM-DD') : ''
  } else {
    filters.maintenancePlanTime = date ? dayjs(date).format('YYYY-MM-DD') : ''
  }
  getTableData()
}
onMounted(() => {
  getTableData();
});
  // æ ¹æ®é»˜è®¤æ¿€æ´»çš„ Tab è°ƒç”¨å¯¹åº”的查询接口
  if (activeTab.value === 'scheduled') {
    getScheduledTableData()
  } else {
    getTableData()
  }
})
</script>
<style lang="scss" scoped>
@@ -292,3 +591,8 @@
  margin-bottom: 10px;
}
</style>
在上述文件截断后对比
src/views/example/DynamicTableExample.vue src/views/example/SimpleExample.vue src/views/fileManagement/bookshelf/detail.vue src/views/fileManagement/bookshelf/index.vue src/views/fileManagement/borrow/index.vue src/views/fileManagement/document/attachmentManager.vue src/views/fileManagement/document/index.vue src/views/fileManagement/return/index.vue src/views/fileManagement/statistics/index.vue src/views/financialManagement/accounting/index.vue src/views/financialManagement/expenseManagement/Form.vue (已删除) src/views/financialManagement/expenseManagement/Modal.vue src/views/financialManagement/expenseManagement/index.vue src/views/financialManagement/financialStatements/index.vue src/views/financialManagement/inventoryAccounting/index.vue src/views/financialManagement/loanManagement/Modal.vue src/views/financialManagement/loanManagement/index.vue src/views/financialManagement/revenueManagement/Form.vue (已删除) src/views/financialManagement/revenueManagement/Modal.vue src/views/financialManagement/revenueManagement/index.vue src/views/index.vue src/views/inventoryManagement/dispatchLog/Record.vue src/views/inventoryManagement/dispatchLog/index.vue src/views/inventoryManagement/issueManagement/index.vue src/views/inventoryManagement/receiptManagement/Record.vue src/views/inventoryManagement/receiptManagement/index.vue src/views/inventoryManagement/stockManagement/New.vue src/views/inventoryManagement/stockManagement/Qualified.vue src/views/inventoryManagement/stockManagement/Subtract.vue src/views/inventoryManagement/stockManagement/Unqualified.vue src/views/inventoryManagement/stockManagement/index.vue src/views/inventoryManagement/stockReport/index.vue src/views/lavorissue/ledger/Form.vue src/views/lavorissue/ledger/Modal.vue src/views/lavorissue/ledger/filesDia.vue src/views/lavorissue/ledger/index.vue src/views/lavorissue/statistics/index.vue src/views/login.vue src/views/monitorManagement/areaControl/index.vue src/views/monitorManagement/videoMonitor/index.vue src/views/oaSystem/projectManagement/components/milestoneList.vue src/views/oaSystem/projectManagement/components/phaseGoalList.vue src/views/oaSystem/projectManagement/components/projectForm.vue src/views/oaSystem/projectManagement/components/taskTree.vue src/views/oaSystem/projectManagement/index.vue src/views/oaSystem/projectManagement/projectDetail.vue src/views/personnelManagement/analytics/index.vue src/views/personnelManagement/contractManagement/components/formDia.vue src/views/personnelManagement/contractManagement/filesDia.vue src/views/personnelManagement/contractManagement/index.vue src/views/personnelManagement/dimission/components/formDia.vue src/views/personnelManagement/dimission/index.vue src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue src/views/personnelManagement/employeeRecord/components/RenewContract.vue src/views/personnelManagement/employeeRecord/components/Show.vue src/views/personnelManagement/employeeRecord/components/formDia.vue (已删除) src/views/personnelManagement/employeeRecord/index.vue src/views/personnelManagement/onboarding/components/formDia.vue (已删除) src/views/personnelManagement/onboarding/index.vue (已删除) src/views/personnelManagement/payrollManagement/components/formDia.vue src/views/personnelManagement/payrollManagement/index.vue src/views/personnelManagement/scheduling/index.vue src/views/personnelManagement/selfService/index.vue src/views/procurementManagement/advancedPriceManagement/index.vue src/views/procurementManagement/arrivalManagement/index.vue src/views/procurementManagement/index.vue src/views/procurementManagement/invoiceEntry/components/Modal.vue src/views/procurementManagement/invoiceEntry/index.vue src/views/procurementManagement/invoiceEntry/indexOld.vue (已删除) src/views/procurementManagement/paymentEntry/index.vue src/views/procurementManagement/paymentHistory/index.vue src/views/procurementManagement/paymentLedger/index.vue src/views/procurementManagement/priceManagement/index.vue src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue src/views/procurementManagement/procurementInvoiceLedger/index.vue src/views/procurementManagement/procurementLedger/index.vue src/views/procurementManagement/procurementPlan/index.vue src/views/procurementManagement/procurementReport/index.vue src/views/procurementManagement/purchaseOrder/index.vue src/views/procurementManagement/qualityInspection/index.vue src/views/procurementManagement/returnManagement/index.vue src/views/procurementManagement/transferManagement/index.vue src/views/productManagement/productIdentifier/index.vue src/views/productionManagement/operationScheduling/components/formDia.vue src/views/productionManagement/operationScheduling/index.vue src/views/productionManagement/processRoute/Edit.vue src/views/productionManagement/processRoute/ItemsForm.vue src/views/productionManagement/processRoute/New.vue src/views/productionManagement/processRoute/index.vue src/views/productionManagement/processRoute/processRouteItem/index.vue src/views/productionManagement/productStructure/Detail/index.vue src/views/productionManagement/productStructure/StructureEdit.vue src/views/productionManagement/productStructure/index.vue src/views/productionManagement/productionCosting/index.vue src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue src/views/productionManagement/productionDispatching/components/formDia.vue src/views/productionManagement/productionDispatching/index.vue src/views/productionManagement/productionOrder/index.vue src/views/productionManagement/productionProcess/Edit.vue src/views/productionManagement/productionProcess/New.vue src/views/productionManagement/productionProcess/index.vue src/views/productionManagement/productionReporting/Input.vue src/views/productionManagement/productionReporting/Output.vue src/views/productionManagement/productionReporting/components/formDia.vue src/views/productionManagement/productionReporting/index.vue src/views/productionManagement/safetyMonitoring/index.vue src/views/productionManagement/workOrder/index.vue src/views/qualityManagement/finalInspection/components/filesDia.vue src/views/qualityManagement/finalInspection/components/formDia.vue src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue src/views/qualityManagement/finalInspection/index.vue src/views/qualityManagement/metricBinding/index.vue src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue src/views/qualityManagement/metricMaintenance/index.vue src/views/qualityManagement/metricMaintenance/index0.vue src/views/qualityManagement/nearExpiryReturn/index.vue src/views/qualityManagement/nonconformingManagement/components/formDia.vue src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue src/views/qualityManagement/nonconformingManagement/index.vue src/views/qualityManagement/processInspection/components/formDia.vue src/views/qualityManagement/processInspection/components/inspectionFormDia.vue src/views/qualityManagement/processInspection/index.vue src/views/qualityManagement/rawMaterialInspection/components/formDia.vue src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue src/views/qualityManagement/rawMaterialInspection/index.vue src/views/qualityManagement/visualization/qualityDashboard.vue src/views/reportAnalysis/dataDashboard/index.vue src/views/reportAnalysis/projectProfit/index.vue src/views/reportAnalysis/reportManagement.vue src/views/reportAnalysis/reportManagement/index.vue src/views/reportAnalysis/taxComparison/index.vue src/views/salesManagement/customerManagement/index.vue src/views/salesManagement/deliveryLedger/index.vue src/views/salesManagement/indicatorStats/index.vue src/views/salesManagement/invoiceLedger/index.vue src/views/salesManagement/invoiceRegistration/index.vue src/views/salesManagement/orderManagement/index.vue src/views/salesManagement/paymentShipping/index.vue src/views/salesManagement/receiptPayment/index.vue src/views/salesManagement/receiptPaymentHistory/index.vue src/views/salesManagement/receiptPaymentLedger/index.vue src/views/salesManagement/salesLedger/index.vue src/views/salesManagement/salesQuotation/index.vue src/views/salesManagement/salespersonManagement/index.vue src/views/salesManagement/strategyControl/index.vue src/views/system/dept/index.vue src/views/system/user/index.vue