yuan
5 天以前 9ea5ff67655241271bdf04bb50ca3a8d60c4e22a
fix: 用河南鹤壁代码覆盖山西长治
已修改165个文件
已添加12个文件
16008 ■■■■■ 文件已修改
.env.development 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/DDGWfavicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/customer.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/enum.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/knowledgeBase.js 159 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/customerService/index.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/invoiceApply.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockOut.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffSalaryMain.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/paymentLedger.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementLedger.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/salesLedger.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/viewIndex.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/aiIndustrialBrain/reference-cards.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/aiIndustrialBrain/reference-chat.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/HYSNLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AIChatSidebar/assistants/financeAssistant.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AIChatSidebar/assistants/productionAssistant.js 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AIChatSidebar/assistants/purchaseAssistant.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AIChatSidebar/assistants/salesAssistant.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Echarts/echarts.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/Pagination.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ProcessParamListDialog.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/documentPrint.js 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/numberFormat.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/summarizeTable.js 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFile/index.vue 117 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFileOpenSea/index.vue 414 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/parameterMaintenance/index.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/components/BlacklistTab.vue 323 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/components/HomeTab.vue 336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/knowledgeBase/index.vue 741 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/summary/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/purchaseApproval/index.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/sealManagement/index.vue 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/afterSalesHandling/components/formDia.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/afterSalesHandling/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/components/viewDia.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/components/formDia.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/index.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/formDia.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue 326 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/index.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/RepairModal.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/spareParts/index.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanModal.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/RecordDetailDia.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/detailDia.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/formDia.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/assets/fixedAssets.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/assets/intangibleAssets.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/input-invoice.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/payment.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/paymentApply.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/purchaseIn.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/purchaseReturn.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/reconciliation.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/receivable/invoiceApply.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/receivable/outputInvoice.vue 140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/receivable/receipt.vue 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/receivable/reconciliation.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/receivable/salesOut.vue 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/receivable/salesReturn.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/voucher/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/Record.vue 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/Record.vue 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Record.vue 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/classsSheduling/index.vue 268 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/components/formDia.vue 59 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/index.vue 144 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/components/formDia.vue 167 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/index.vue 81 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/auditDia.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/formDia.vue 626 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/socialSecuritySet/components/formDia.vue 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/detail.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 996 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementPlan/index.vue 630 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementReport/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseOrder/index.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseReturnOrder/New.vue 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseReturnOrder/ProductList.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseReturnOrder/index.vue 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/qualityInspection/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productManagement/productIdentifier/index.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/StructureEdit.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/index.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 135 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionTraceability/index.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderEdit/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderManagement/index.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/filesDia.vue 87 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 359 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/index.vue 360 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 617 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nearExpiryReturn/index.vue 262 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/formDia.vue 240 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/index.vue 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 431 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/index.vue 325 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/ProductTypeSwitch.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/psiNavigation.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/PanelHeader.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/center-center.vue 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/center-top.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/left-top.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/right-top.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/productionNavigation.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/safeProduction/dangerInvestigation/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/safeProduction/safetyTrainingAssessment/index.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/deliveryLedger/index.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/indicatorStats/index.vue 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/components/detailDia.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/components/formDia.vue 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/index.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 1698 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/systemArchitecture/index.vue 270 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -6,3 +6,5 @@
# ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包/开发环境
VITE_APP_BASE_API = '/dev-api'
VITE_BASE_API = 'http://localhost:9003'
.gitignore
@@ -6,8 +6,6 @@
yarn-error.log*
**/*.log
.claude/
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
@@ -20,9 +18,6 @@
*.njsproj
*.sln
*.local
# Claude Code
.claude/
package-lock.json
yarn.lock
package.json
@@ -29,6 +29,7 @@
    "element-plus": "2.7.7",
    "file-saver": "2.0.5",
    "fuse.js": "6.6.2",
    "html2canvas": "^1.4.1",
    "js-beautify": "1.14.11",
    "js-cookie": "3.0.5",
    "jsencrypt": "3.3.2",
public/DDGWfavicon.ico
src/api/basicData/customer.js
@@ -26,7 +26,7 @@
    })
}
// æµå…¥å…¬æµ·
// å›žé€€å®¢æˆ·åˆ°é”€å”®æ¡£æ¡ˆ
export function backCustomer(id) {
    return request({
        url: '/basic/customer/back/' + id,
src/api/basicData/enum.js
@@ -1,13 +1,5 @@
import request from "@/utils/request.js";
/** å®¡æ‰¹æ¨¡æ¿ç±»åž‹ç­‰é€šç”¨æžšä¸¾ï¼ˆTypeEnums) */
export function getTypeEnums() {
    return request({
        url: '/basic/enum/TypeEnums',
        method: 'get'
    })
}
export function findAllStockRecordTypeOptions() {
    return request({
        url: '/basic/enum/stockRecordType',
src/api/collaborativeApproval/knowledgeBase.js
@@ -1,172 +1,55 @@
import request from "@/utils/request";
import { getToken } from '@/utils/auth';
/**
 * çŸ¥è¯†åº“管理接口
 * ä¼ å‚规范:
 * - GET请求: ä½¿ç”¨ params
 * - POST请求: ä½¿ç”¨ data
 * - DELETE请求: ä½¿ç”¨ data
 * - æµå¼è¯·æ±‚: ä½¿ç”¨ Fetch API
 */
// ==================== çŸ¥è¯†åº“CRUD接口 ====================
/**
 * æŸ¥è¯¢çŸ¥è¯†åº“列表
 * @param {Object} query - æŸ¥è¯¢å‚æ•°
 * @param {number} query.current - å½“前页码(必填)
 * @param {number} query.size - æ¯é¡µæ¡æ•°(必填)
 * @param {string} [query.title] - çŸ¥è¯†æ ‡é¢˜(可选,模糊搜索)
 * @param {string} [query.type] - çŸ¥è¯†ç±»åž‹(可选,精确匹配)
 * @returns {Promise}
 */
// æŸ¥è¯¢çŸ¥è¯†åº“列表
export function listKnowledgeBase(query) {
  return request({
    url: "/knowledgeBase/getList",
    method: "get",
    params: query,  // GET请求使用params
    params: query,
  });
}
/**
 * æ–°å¢žçŸ¥è¯†åº“
 * @param {Object} data - çŸ¥è¯†åº“数据
 * @param {string} data.title - çŸ¥è¯†æ ‡é¢˜(必填)
 * @param {string} data.type - çŸ¥è¯†ç±»åž‹(必填)
 * @param {string} [data.scenario] - é€‚用场景(可选)
 * @param {string} [data.efficiency] - è§£å†³æ•ˆçއ(可选)
 * @param {string} data.problem - é—®é¢˜æè¿°(必填)
 * @param {string} data.solution - è§£å†³æ–¹æ¡ˆ(必填)
 * @param {string} [data.keyPoints] - å…³é”®è¦ç‚¹(可选)
 * @param {string} [data.creator] - åˆ›å»ºäºº(可选)
 * @param {number} [data.usageCount=0] - ä½¿ç”¨æ¬¡æ•°(可选)
 * @returns {Promise}
 */
// æŸ¥è¯¢çŸ¥è¯†åº“详细
// export function getKnowledgeBase(knowledgeBaseId) {
//   return request({
//     url: "/collaborativeApproval/knowledgeBase/" + knowledgeBaseId,
//     method: "get",
//   });
// }
// æ–°å¢žçŸ¥è¯†åº“
export function addKnowledgeBase(data) {
  return request({
    url: "/knowledgeBase/add",
    method: "post",
    data: data,  // POST请求使用data
    data: data,
  });
}
/**
 * ä¿®æ”¹çŸ¥è¯†åº“
 * @param {Object} data - çŸ¥è¯†åº“数据
 * @param {number} data.id - çŸ¥è¯†åº“ID(必填)
 * @param {string} data.title - çŸ¥è¯†æ ‡é¢˜(必填)
 * @param {string} data.type - çŸ¥è¯†ç±»åž‹(必填)
 * @returns {Promise}
 */
// ä¿®æ”¹çŸ¥è¯†åº“
export function updateKnowledgeBase(data) {
  return request({
    url: "/knowledgeBase/update",
    method: "post",
    data: data,  // POST请求使用data
    data: data,
  });
}
/**
 * åˆ é™¤çŸ¥è¯†åº“(支持批量删除)
 * @param {number[]} ids - çŸ¥è¯†åº“ID数组
 * @returns {Promise}
 */
export function delKnowledgeBase(ids) {
// åˆ é™¤çŸ¥è¯†åº“
export function delKnowledgeBase(query) {
  return request({
    url: "/knowledgeBase/delete",
    method: "delete",
    data: ids,  // DELETE请求使用data传递数组
    data: query,
  });
}
// ==================== æ–‡ä»¶ç®¡ç†æŽ¥å£ ====================
/**
 * æŸ¥è¯¢çŸ¥è¯†åº“文件向量化状态
 * @param {number} knowledgeBaseId - çŸ¥è¯†åº“ID
 * @returns {Promise} è¿”回文件列表及向量化状态
 */
export function getVectorStatus(knowledgeBaseId) {
// æ‰¹é‡åˆ é™¤çŸ¥è¯†åº“
export function delKnowledgeBaseBatch(knowledgeBaseIds) {
  return request({
    url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
    method: "get",
  });
}
/**
 * ä¿å­˜çŸ¥è¯†åº“文件关联(触发向量化)
 * @param {Object} data - æ–‡ä»¶å…³è”数据
 * @param {number} data.knowledgeBaseId - çŸ¥è¯†åº“ID(必填)
 * @param {number[]} data.storageBlobIds - æ–‡ä»¶blob ID数组(必填)
 * @returns {Promise}
 */
export function saveKnowledgeBaseFiles(data) {
  return request({
    url: "/knowledgeBase/file/save",
    method: "post",
    data: data,  // POST请求使用data
  });
}
/**
 * åˆ é™¤çŸ¥è¯†åº“文件
 * @param {number[]} ids - å‘量记录ID数组
 * @returns {Promise}
 */
export function deleteKnowledgeBaseFile(ids) {
  return request({
    url: "/knowledgeBase/file/delete",
    url: "/knowledgeBase/batch",
    method: "delete",
    data: ids,  // DELETE请求使用data传递数组
    data: knowledgeBaseIds,
  });
}
/**
 * é‡æ–°å‘量化文件
 * @param {number} vectorId - å‘量记录ID
 * @returns {Promise}
 */
export function reprocessVector(vectorId) {
  return request({
    url: `/knowledgeBase/vector/reprocess/${vectorId}`,
    method: "post",
  });
}
// ==================== çŸ¥è¯†é—®ç­”接口 ====================
/**
 * çŸ¥è¯†åº“问答(流式)
 * åŽç«¯æŽ¥å£: POST /ai/knowledge/chat
 * å“åº”类型: text/stream;charset=utf-8 (Spring Flux<String>)
 *
 * @param {Object} data - é—®ç­”参数
 * @param {number} data.knowledgeBaseId - çŸ¥è¯†åº“ID(必填)
 * @param {string} data.memoryId - ä¼šè¯ID(必填,用于保持上下文)
 * @param {string} data.question - ç”¨æˆ·é—®é¢˜(必填)
 * @returns {Promise<Response>} è¿”回Fetch Response对象
 */
export function knowledgeChat(data) {
  const token = getToken();
  return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token
    },
    body: JSON.stringify(data)
  });
}
/**
 * æŸ¥è¯¢çŸ¥è¯†åº“问答历史
 * @param {string} memoryId - ä¼šè¯ID
 * @returns {Promise}
 */
export function getKnowledgeHistory(memoryId) {
  return request({
    url: `/ai/knowledge/history/${memoryId}`,
    method: "get",
  });
}
src/api/customerService/index.js
@@ -130,3 +130,12 @@
        params: query,
    })
}
// å”®åŽå•详情
export function getAfterSalesServiceById(id) {
    return request({
        url: '/afterSalesService/getById',
        method: 'get',
        params: { id }
    })
}
src/api/financialManagement/invoiceApply.js
@@ -57,12 +57,3 @@
    method: "delete",
  });
}
/** æ ¹æ®å®¢æˆ·id查询开票申请台账 */
export function getAccountInvoiceApplicationList(query) {
  return request({
    url: '/accountInvoiceApplication/getAccountInvoiceApplicationList',
    method: "get",
    params: query,
  });
}
src/api/inventoryManagement/stockOut.js
@@ -35,12 +35,3 @@
        data,
    });
}
// æ‰¹é‡åå®¡å‡ºåº“记录
export const batchUnapproveStockOutRecords = (data) => {
    return request({
        url: "/stockOutRecord/reAudit",
        method: "post",
        data,
    });
}
src/api/personnelManagement/staffSalaryMain.js
@@ -17,14 +17,6 @@
  });
}
export function staffSalaryMainCalculateByEmployeeId(data) {
  return request({
    url: "/staffSalaryMain/calculateByEmployeeId",
    method: "post",
    data,
  });
}
export function staffSalaryMainAdd(data) {
  return request({
    url: "/staffSalaryMain/add",
@@ -49,11 +41,3 @@
  });
}
// èŽ·å–å¯ç”¨éƒ¨é—¨åˆ—è¡¨ï¼ˆæ ¹æ®æœˆä»½æŸ¥è¯¢ï¼ŒæŽ’é™¤å·²å‘æ”¾å·¥èµ„è¡¨çš„éƒ¨é—¨ï¼‰
export function staffSalaryMainAvailableDepts(salaryMonth) {
  return request({
    url: "/staffSalaryMain/availableDepts",
    method: "get",
    params: { salaryMonth },
  });
}
src/api/procurementManagement/paymentLedger.js
@@ -18,12 +18,3 @@
    params,
  });
}
/** ä»˜æ¬¾å°è´¦ - ä»˜æ¬¾ç™»è®°åˆ—表 */
export function registrationList(params) {
  return request({
    url: "/purchase/report/registration",
    method: "get",
    params,
  });
}
src/api/procurementManagement/procurementLedger.js
@@ -9,14 +9,6 @@
    params: query,
  });
}
// é‡‡è´­é€€è´§å¯é€‰é‡‡è´­å°è´¦
export function purchaseReturnableList(query) {
  return request({
    url: "/purchase/ledger/returnableList",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢åˆåŒå·
export function getSalesNo(query) {
  return request({
@@ -74,11 +66,10 @@
  });
}
export function createPurchaseNo(entryDate) {
export function createPurchaseNo() {
  return request({
    url: "/purchase/ledger/createPurchaseNo",
    method: "get",
    params: { entryDate },
  });
}
export function updateApprovalStatus(query) {
@@ -132,11 +123,10 @@
    params: id,
  });
}
export function batchGeneratePurchaseInboundSteps(query) {
// èŽ·å–é‡‡è´­å•æ‰“å°æ•°æ®
export function getPrintData(id) {
  return request({
    url: "/purchase/ledger/batchInsertPurchaseSteps",
    method: "post",
    data: query,
        url: '/purchase/ledger/printData/' + id,
        method: 'get',
  });
}
src/api/salesManagement/salesLedger.js
@@ -117,3 +117,10 @@
        params: query,
    });
}
// èŽ·å–é”€å”®å•æ‰“å°æ•°æ®
export function getPrintData(id) {
    return request({
        url: '/sales/ledger/printData/' + id,
        method: 'get',
    });
}
src/api/viewIndex.js
@@ -349,7 +349,8 @@
export const productionOrderProgress = (params = {}) => {
  const safePageNum = Math.max(1, Number(params.pageNum || 1));
  const safeTab = ["all", "inProgress", "completed", "end"].includes(params.tab)
  const safePageSize = Math.min(50, Math.max(1, Number(params.pageSize || 10)));
  const safeTab = ["all", "inProgress", "completed", "paused"].includes(params.tab)
    ? params.tab
    : "all";
  return request({
@@ -359,7 +360,7 @@
      ...params,
      tab: safeTab,
      pageNum: safePageNum,
      pageSize: params.pageSize,
      pageSize: safePageSize,
    },
    headers: {
      handleAuthError: false,
src/assets/aiIndustrialBrain/reference-cards.png
src/assets/aiIndustrialBrain/reference-chat.png
src/assets/indexViews/HYSNLogo.png
src/assets/styles/sidebar.scss
@@ -216,37 +216,32 @@
    & .nest-menu .el-sub-menu > .el-sub-menu__title,
    & .el-sub-menu .el-menu-item {
      min-width: 0 !important;
      width: calc(100% - 32px) !important;
      margin: 0 16px 6px !important;
      height: 40px;
      line-height: 40px;
      padding-left: 12px !important;
      padding-right: 12px !important;
      border-radius: 8px;
      width: calc(100% - 24px) !important;
      margin: 0 12px 8px !important;
      height: 46px;
      line-height: 46px;
      padding-left: 14px !important;
      padding-right: 14px !important;
      border-radius: 12px;
      transition: all 0.24s ease;
      color: var(--sidebar-text);
      border: none !important;
      background: transparent;
      font-size: 13px;
      border: 1px solid rgba(255, 255, 255, 0.06) !important;
      background: linear-gradient(128deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
      &:hover {
        background: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.12) !important;
        transform: translateX(4px);
        background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.07)) !important;
        border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3) !important;
        transform: translateX(2px);
      }
      &.is-active {
        background: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.85) !important;
        background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important;
        background-size: 180% 180%;
        color: #fff !important;
        font-weight: 500;
        box-shadow: 0 4px 12px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.25);
        box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34));
        animation: sidebarActiveFlow 4.6s ease infinite;
      }
    }
    // å­èœå•容器样式 - å¢žåŠ ç¼©è¿›å’Œè§†è§‰å±‚æ¬¡
    & .el-sub-menu .el-menu {
      padding: 4px 0 8px;
      margin-left: 8px;
      border-left: 2px solid rgba(255, 255, 255, 0.08);
    }
  }
src/components/AIChatSidebar/assistants/financeAssistant.js
@@ -14,15 +14,13 @@
  allowFileUpload: false,
  emptySessionText: '暂无财务会话',
  quickPrompts: [
    '查看经营驾驶舱',
    '查看本月经营驾驶舱',
    '生成本周经营周报(利润与现金流)',
    '分析本月利润下降原因',
    '查询近30天亏损订单',
    '近30天哪个客户利润贡献最高',
    '查看本月经营驾驶舱',
    '查询近30天亏损订单',
    '分析近30天库存资金占用',
    '预测未来3个月现金流',
    '查询应收回款风险',
    '哪个工序成本最高'
  ]
}
src/components/AIChatSidebar/assistants/productionAssistant.js
@@ -14,15 +14,14 @@
  allowFileUpload: false,
  emptySessionText: '暂无生产会话',
  quickPrompts: [
    '查询生产现场数据',
    '查询生产计划执行情况',
    '查询工单执行情况',
    '查询设备运行情况',
    '查询质量异常数据',
    '查询物料相关数据',
    '查看制造预警看板',
    '分析本月制造经营情况',
    '分析近7天生产异常',
    '生成制造办理建议'
    '查询本月生产计划',
    '查看最近10条工单',
    '查设备A-01的维修情况',
    '查质量不合格记录',
    '查低库存物料',
    '查近7天异常处理',
    '生成制造预警看板',
    '分析本月生产完成率和异常率',
    '给出工单逾期和设备待修的办理建议'
  ]
}
src/components/AIChatSidebar/assistants/purchaseAssistant.js
@@ -16,15 +16,10 @@
  fileAnalyzeUrl: '/purchase-ai/analyze-files',
  emptySessionText: '暂无采购会话',
  quickPrompts: [
    '查询采购台账列表',
    '查询最近10条采购台账',
    '统计本月采购数据',
    '查询本月采购物料金额排行',
    '查询未入库采购订单',
    '查询采购到货异常',
    '查询待付款采购单',
    '查询本月采购退货情况',
    '查询某个采购台账详情',
    '分析供应商采购金额排名'
    '本月采购金额排名前十的物料有哪些?',
    '哪些采购订单还未入库?',
    '最近7天供应商到货异常有哪些?',
    '帮我统计待付款采购单!',
    '列出本月采购退货情况'
  ]
}
src/components/AIChatSidebar/assistants/salesAssistant.js
@@ -14,8 +14,8 @@
  allowFileUpload: false,
  emptySessionText: '暂无销售会话',
  quickPrompts: [
    '查询私海客户档案前10条',
    '查询公海客户档案',
    '查询销售档案前10条',
    '查询客户档案',
    '查询本月销售报价',
    '查询本月销售台账',
    '查询近30天销售退货',
src/components/Echarts/echarts.vue
@@ -203,6 +203,10 @@
  clearInitTimer()
})
defineExpose({
  resize: windowResizeListener,
})
// Watch all reactive props that affect the chart
watch(
  () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap],
src/components/PIMTable/PIMTable.vue
@@ -8,10 +8,11 @@
            :highlight-current-row="highlightCurrentRow"
            :row-class-name="rowClassName"
            :row-style="rowStyle"
            :cell-style="cellStyle"
            :row-key="rowKey"
            :style="tableStyle"
            tooltip-effect="dark"
            :tooltip-options="{ popperOptions: { strategy: 'absolute' } }"
            :tooltip-options="{ appendTo: 'body' }"
            :expand-row-keys="expandRowKeys"
            :show-summary="isShowSummary"
            :summary-method="summaryMethod"
@@ -120,6 +121,7 @@
            <el-button v-show="o.type != 'upload'"
                       v-if="o.showHide ? o.showHide(scope.row) : true"
                       :disabled="isOperationDisabled(o, scope.row)"
                       :loading="isOperationLoading(o, scope.row)"
                       :plain="o.plain"
                       type="primary"
                       :style="{
@@ -292,6 +294,10 @@
      type: [Object, Function],
      default: () => ({}),
    },
    cellStyle: {
      type: [Object, Function],
      default: () => ({}),
    },
    tableData: {
      type: Array,
      default: () => [],
@@ -373,6 +379,13 @@
      : !!operation.disabled;
  };
  const isOperationLoading = (operation, row) => {
    if (!operation?.loading) return false;
    return typeof operation.loading === "function"
      ? !!operation.loading(row)
      : !!operation.loading;
  };
  const parseHexToRgb = hex => {
    const normalized = String(hex || "")
      .trim()
src/components/PIMTable/Pagination.vue
@@ -34,7 +34,7 @@
  },
  pageSizes: {
    type: Array,
    default: () => [10, 20, 30, 50, 100]
    default: () => [10, 20, 30, 50, 100, 200, 500]
  },
  pagerCount: {
    type: Number,
src/components/ProcessParamListDialog.vue
@@ -372,14 +372,7 @@
      size: paramPage.value.size,
    }).then(res => {
      if (res.code === 200) {
        const existingParamIds = new Set(
          (props.paramList || [])
            .map(item => Number(item.paramId || item.technologyOperationParamId))
            .filter(id => Number.isFinite(id) && id > 0)
        );
        filteredParamList.value = (res.data?.records || []).filter(
          item => !existingParamIds.has(Number(item.id))
        );
        filteredParamList.value = res.data?.records || [];
        paramPage.value.total = res.data.total || 0;
      } else {
        ElMessage.error(res.msg || "查询失败");
@@ -409,6 +402,7 @@
      addProcessRouteItemParamOrder({
        productionOrderId: Number(props.orderId),
        productionOrderRoutingOperationId: props.process.id,
        technologyRoutingOperationParamId: props.process.id,
        paramId: selectedParam.value.id,
        standardValue: selectedParam.value.standardValue || "",
        isRequired: selectedParam.value.isRequired || 0,
src/layout/components/Sidebar/SidebarItem.vue
@@ -147,25 +147,12 @@
  
  :deep(.nest-menu) {
    .menu-icon {
      width: 18px;
      height: 18px;
      margin-right: 8px;
      opacity: 0.7;
      width: 22px;
      height: 22px;
      margin-right: 10px;
    }
    
    .menu-title {
      font-size: 13px;
      opacity: 0.85;
    }
    .el-menu-item.is-active {
      .menu-icon {
        opacity: 1;
      }
      .menu-title {
        opacity: 1;
      }
    }
    .menu-title { font-size: 13px; }
  }
}
</style>
src/main.js
@@ -115,5 +115,22 @@
  size: Cookies.get("size") || "default",
});
app._context.components.ElDialog.props.closeOnClickModal.default = false;
const ElTable = app._context.components.ElTable;
if (ElTable?.props?.tooltipOptions) {
  if (ElTable.props.tooltipOptions === Object) {
    ElTable.props.tooltipOptions = {
      type: Object,
      default: () => ({
        appendTo: "body",
      }),
    };
  } else if (typeof ElTable.props.tooltipOptions === "object") {
    ElTable.props.tooltipOptions.default =
      ElTable.props.tooltipOptions.default ||
      (() => ({
        appendTo: "body",
      }));
  }
}
app.mount("#app");
src/router/index.js
@@ -97,44 +97,6 @@
      },
    ],
  },
  // æ•™å­¦æ¼”示模块
  {
    path: "/productionManagement/teachingDemo",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "",
        component: () => import("@/views/productionManagement/teachingDemo/index.vue"),
        name: "TeachingDemo",
        meta: { title: "工艺路线与BOM教学演示", icon: "education" },
      },
      {
        path: "bom",
        component: () => import("@/views/productionManagement/teachingDemo/BomDemo.vue"),
        name: "BomDemo",
        meta: { title: "BOM结构演示", activeMenu: "/productionManagement/teachingDemo" },
      },
      {
        path: "processRoute",
        component: () => import("@/views/productionManagement/teachingDemo/ProcessRouteDemo.vue"),
        name: "ProcessRouteDemo",
        meta: { title: "工艺路线演示", activeMenu: "/productionManagement/teachingDemo" },
      },
      {
        path: "combined",
        component: () => import("@/views/productionManagement/teachingDemo/CombinedDemo.vue"),
        name: "CombinedDemo",
        meta: { title: "联动演示", activeMenu: "/productionManagement/teachingDemo" },
      },
      {
        path: "factory",
        component: () => import("@/views/productionManagement/teachingDemo/FactoryDemo.vue"),
        name: "FactoryDemo",
        meta: { title: "动态工厂演示", activeMenu: "/productionManagement/teachingDemo" },
      },
    ],
  },
  {
    path: "/user",
    component: Layout,
src/utils/documentPrint.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,331 @@
import html2canvas from 'html2canvas'
function esc(s) {
  if (!s) return ''
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}
function fmtDate(v) {
  if (!v) return ''
  const d = new Date(v)
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  return y + '-' + m + '-' + day
}
function fmtMoney(v) {
  if (v == null) return '0.00'
  return Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
function digitUppercase(n) {
  const digits = ['零', '壹', 'è´°', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
  const radices = ['', '拾', 'ä½°', '仟', '万', '拾', 'ä½°', '仟', '亿']
  let num = Math.abs(n)
  let result = ''
  const intPart = Math.floor(num)
  const intStr = String(intPart)
  const len = intStr.length
  if (intPart === 0) {
    result = '零'
  } else {
    let zeroFlag = false
    for (let i = 0; i < len; i++) {
      const d = parseInt(intStr[i])
      const pos = len - 1 - i
      if (d === 0) {
        zeroFlag = true
        if (pos % 4 === 0) {
          result += radices[pos]
          zeroFlag = false
        }
      } else {
        if (zeroFlag) {
          result += '零'
          zeroFlag = false
        }
        result += digits[d] + radices[pos]
      }
    }
    if (result.endsWith('零')) result = result.slice(0, -1)
  }
  result += '元'
  const decPart = Math.round((num - intPart) * 100)
  if (decPart === 0) {
    result += '整'
  } else {
    const jiao = Math.floor(decPart / 10)
    const fen = decPart % 10
    if (jiao > 0) result += digits[jiao] + '角'
    if (fen > 0) result += digits[fen] + '分'
  }
  return result
}
function coreCss() {
  return `*{margin:0;padding:0;box-sizing:border-box}body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:30px 36px;color:#333;background:#fff}.title{text-align:center;font-size:18px;font-weight:bold;letter-spacing:6px;margin-bottom:4px}.sub{text-align:center;font-size:12px;color:#999;margin-bottom:18px}.info-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px dashed #ccc}.info-top .left,.info-top .right{line-height:2}.info-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 36px;font-size:13px;margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid #ccc}.info-grid .l{color:#666}.info-grid .v{font-weight:500}table{width:100%;border-collapse:collapse;font-size:13px;margin-bottom:14px}th,td{border:1px solid #333;padding:5px 6px;text-align:center}th{background:#eee;font-weight:500}td.r{text-align:right}.summary{text-align:right;font-size:13px;margin-bottom:16px;line-height:2.2}.summary .cn{font-weight:bold;color:#cf1322}`
}
function productRows(products) {
  if (!products || !products.length) return ''
  let html = ''
  products.forEach((p, i) => {
    const name = p.productCategory || ''
    const spec = p.specificationModel || ''
    html += '<tr>'
    html += '<td>' + (i + 1) + '</td>'
    html += '<td style="text-align:left">' + esc(name)
    if (spec) html += '<br><span style="color:#888;font-size:12px">' + esc(spec) + '</span>'
    html += '</td>'
    html += '<td>' + esc(p.unit || '') + '</td>'
    html += '<td class="r">' + (p.quantity != null ? Number(p.quantity).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '') + '</td>'
    html += '<td class="r">' + fmtMoney(p.taxInclusiveUnitPrice) + '</td>'
    html += '<td class="r">' + fmtMoney(p.taxInclusiveTotalPrice) + '</td>'
    html += '<td>' + (p.taxRate != null ? p.taxRate + '%' : '') + '</td>'
    html += '<td>' + esc(p.invoiceType || '') + '</td>'
    html += '</tr>'
  })
  return html
}
function calcTotals(products) {
  let total = 0, totalNoTax = 0
  if (products) {
    products.forEach(p => {
      if (p.taxInclusiveTotalPrice != null) total += Number(p.taxInclusiveTotalPrice)
      if (p.taxExclusiveTotalPrice != null) totalNoTax += Number(p.taxExclusiveTotalPrice)
    })
  }
  return { total, totalNoTax }
}
function buildSalesHtml(companyName, ledger, forPrint) {
  const products = ledger.productData || []
  const { total, totalNoTax } = calcTotals(products)
  const toolbar = forPrint ? '<div class="toolbar"><button onclick="window.print()">打印 / å¦å­˜ä¸º PDF</button></div>' : ''
  const printCss = forPrint ? '@media print{body{padding:15px 24px}.toolbar{display:none}}' : ''
  const printScript = forPrint ? '<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>' : ''
  return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<title>销售单 - ${esc(ledger.salesContractNo)} - ${esc(companyName)}</title>
<style>.toolbar{text-align:center;margin-bottom:16px}.toolbar button{padding:6px 20px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}.toolbar button:hover{background:#40a9ff}${coreCss()}${printCss}</style></head><body>
${toolbar}
<div class="title">${esc(companyName)}</div>
<div class="sub">销售合同 / é”€å”®å•</div>
<div class="info-top">
  <div class="left"><div><strong>销方名称:</strong>${esc(companyName)}</div></div>
  <div class="right"><div><strong>合同编号:</strong>${esc(ledger.salesContractNo)}</div><div><strong>签订日期:</strong>${fmtDate(ledger.executionDate)}</div></div>
</div>
<div class="info-grid">
  <div><span class="l">客户名称:</span><span class="v">${esc(ledger.customerName)}</span></div>
  <div><span class="l">项目名称:</span><span class="v">${esc(ledger.projectName)}</span></div>
  <div><span class="l">业务员:</span><span class="v">${esc(ledger.salesman)}</span></div>
  <div><span class="l">付款方式:</span><span class="v">${esc(ledger.paymentMethod)}</span></div>
  <div><span class="l">录入日期:</span><span class="v">${fmtDate(ledger.entryDate)}</span></div>
  <div><span class="l">交货日期:</span><span class="v">${fmtDate(ledger.deliveryDate)}</span></div>
  ${ledger.remarks ? `<div style="grid-column:1/-1"><span class="l">备注:</span><span class="v">${esc(ledger.remarks)}</span></div>` : ''}
</div>
<table><thead><tr><th>序号</th><th>产品名称 / è§„格型号</th><th>单位</th><th>数量</th><th>含税单价</th><th>含税总价</th><th>税率</th><th>发票类型</th></tr></thead><tbody>
${productRows(products)}
</tbody></table>
<div class="summary">
  <div>不含税合计:<strong>${fmtMoney(totalNoTax)}</strong></div>
  <div>含税合计(大写):<span class="cn">${digitUppercase(total)}</span></div>
  <div>含税合计(小写):<strong>Â¥${fmtMoney(total)}</strong></div>
</div>
${printScript}
</body></html>`
}
function buildPurchaseHtml(companyName, ledger, products, forPrint) {
  const { total, totalNoTax } = calcTotals(products)
  const contractAmount = ledger.contractAmount != null ? Number(ledger.contractAmount) : total
  const toolbar = forPrint ? '<div class="toolbar"><button onclick="window.print()">打印 / å¦å­˜ä¸º PDF</button></div>' : ''
  const printCss = forPrint ? '@media print{body{padding:15px 24px}.toolbar{display:none}}' : ''
  const printScript = forPrint ? '<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>' : ''
  return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<title>采购单 - ${esc(ledger.purchaseContractNumber)} - ${esc(companyName)}</title>
<style>.toolbar{text-align:center;margin-bottom:16px}.toolbar button{padding:6px 20px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}.toolbar button:hover{background:#40a9ff}${coreCss()}${printCss}</style></head><body>
${toolbar}
<div class="title">${esc(companyName)}</div>
<div class="info-top">
  <div class="left"><div><strong>购方名称:</strong>${esc(companyName)}</div></div>
  <div class="right"><div><strong>合同编号:</strong>${esc(ledger.purchaseContractNumber)}</div><div><strong>签订日期:</strong>${fmtDate(ledger.executionDate)}</div></div>
</div>
<div class="info-grid">
  <div><span class="l">供应商名称:</span><span class="v">${esc(ledger.supplierName)}</span></div>
  <div><span class="l">项目名称:</span><span class="v">${esc(ledger.projectName)}</span></div>
  <div><span class="l">录入人:</span><span class="v">${esc(ledger.recorderName)}</span></div>
  <div><span class="l">付款方式:</span><span class="v">${esc(ledger.paymentMethod)}</span></div>
  <div><span class="l">录入日期:</span><span class="v">${fmtDate(ledger.entryDate)}</span></div>
  ${ledger.salesContractNo ? `<div><span class="l">关联销售合同号:</span><span class="v">${esc(ledger.salesContractNo)}</span></div>` : ''}
  ${ledger.remarks ? `<div style="grid-column:1/-1"><span class="l">备注:</span><span class="v">${esc(ledger.remarks)}</span></div>` : ''}
</div>
<table><thead><tr><th>序号</th><th>产品名称 / è§„格型号</th><th>单位</th><th>数量</th><th>含税单价</th><th>含税总价</th><th>税率</th><th>发票类型</th></tr></thead><tbody>
${productRows(products)}
</tbody></table>
<div class="summary">
  <div>不含税合计:<strong>${fmtMoney(totalNoTax)}</strong></div>
  <div>含税合计(大写):<span class="cn">${digitUppercase(total)}</span></div>
  <div>含税合计(小写):<strong>Â¥${fmtMoney(total)}</strong></div>
  <div>合同金额(大写):<span class="cn">${digitUppercase(contractAmount)}</span></div>
  <div>合同金额(小写):<strong>Â¥${fmtMoney(contractAmount)}</strong></div>
</div>
${printScript}
</body></html>`
}
export function buildSalesDocument(companyName, ledger) {
  return buildSalesHtml(companyName, ledger, false)
}
export function buildPurchaseDocument(companyName, ledger, products) {
  return buildPurchaseHtml(companyName, ledger, products, false)
}
function buildSalesPrintHtml(companyName, ledger) {
  return buildSalesHtml(companyName, ledger, true)
}
function buildPurchasePrintHtml(companyName, ledger, products) {
  return buildPurchaseHtml(companyName, ledger, products, true)
}
export function openPrintWindow(html) {
  const w = window.open('', '_blank', 'width=900,height=700')
  if (!w) return
  w.document.write(html)
  w.document.close()
}
export async function downloadAsJpg(html, filename) {
  const iframe = document.createElement('iframe')
  iframe.style.position = 'fixed'
  iframe.style.left = '0'
  iframe.style.top = '0'
  iframe.style.width = '900px'
  iframe.style.height = '600px'
  iframe.style.opacity = '0'
  iframe.style.pointerEvents = 'none'
  iframe.style.zIndex = '99999'
  document.body.appendChild(iframe)
  return new Promise((resolve, reject) => {
    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document
    iframeDoc.open()
    iframeDoc.write(html)
    iframeDoc.close()
    const doCapture = async () => {
      try {
        const body = iframeDoc.body
        iframe.style.height = Math.max(body.scrollHeight, 600) + 'px'
        await new Promise(r => setTimeout(r, 100))
        const canvas = await html2canvas(body, {
          scale: 2,
          useCORS: true,
          backgroundColor: '#ffffff',
          windowWidth: 900
        })
        document.body.removeChild(iframe)
        canvas.toBlob(blob => {
          const url = URL.createObjectURL(blob)
          const a = document.createElement('a')
          a.href = url
          a.download = filename
          a.click()
          URL.revokeObjectURL(url)
          resolve()
        }, 'image/jpeg', 0.95)
      } catch (e) {
        document.body.removeChild(iframe)
        reject(e)
      }
    }
    iframe.onload = () => setTimeout(doCapture, 300)
    setTimeout(doCapture, 1000)
  })
}
export function downloadSalesJpg(companyName, ledger, filename) {
  const html = buildSalesHtml(companyName, ledger, false)
  return downloadAsJpg(html, filename)
}
export function downloadPurchaseJpg(companyName, ledger, products, filename) {
  const html = buildPurchaseHtml(companyName, ledger, products, false)
  return downloadAsJpg(html, filename)
}
export function printSales(companyName, ledger) {
  const html = buildSalesPrintHtml(companyName, ledger)
  openPrintWindow(html)
}
export function printPurchase(companyName, ledger, products) {
  const html = buildPurchasePrintHtml(companyName, ledger, products)
  openPrintWindow(html)
}
export function printSalesMulti(companyName, orders) {
  let bodies = ''
  orders.forEach((ledger, i) => {
    const html = buildSalesHtml(companyName, ledger, true)
    const bodyMatch = html.match(/<body>([\s\S]*)<\/body>/)
    if (bodyMatch) {
      const body = bodyMatch[1].replace(/<script>[\s\S]*?<\/script>/g, '')
      bodies += '<div class="print-page' + (i < orders.length - 1 ? ' page-break' : '') + '">' + body + '</div>'
    }
  })
  const combined = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<title>销售单 - ${esc(companyName)}</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:20px 36px;color:#333;background:#fff}
.toolbar{text-align:center;margin-bottom:20px}
.toolbar button{padding:6px 24px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}
.toolbar button:hover{background:#40a9ff}
${coreCss()}
.print-page{page-break-inside:avoid}
.page-break{page-break-after:always}
@media print{body{padding:15px 24px}.toolbar{display:none}}
</style></head><body>
<div class="toolbar"><button onclick="window.print()">打印全部 (共${orders.length}份)</button></div>
${bodies}
<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>
</body></html>`
  openPrintWindow(combined)
}
export function printPurchaseMulti(companyName, orders) {
  let bodies = ''
  orders.forEach((item, i) => {
    const html = buildPurchaseHtml(companyName, item.ledger, item.products, true)
    const bodyMatch = html.match(/<body>([\s\S]*)<\/body>/)
    if (bodyMatch) {
      const body = bodyMatch[1].replace(/<script>[\s\S]*?<\/script>/g, '')
      bodies += '<div class="print-page' + (i < orders.length - 1 ? ' page-break' : '') + '">' + body + '</div>'
    }
  })
  const combined = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<title>采购单 - ${esc(companyName)}</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:20px 36px;color:#333;background:#fff}
.toolbar{text-align:center;margin-bottom:20px}
.toolbar button{padding:6px 24px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}
.toolbar button:hover{background:#40a9ff}
${coreCss()}
.print-page{page-break-inside:avoid}
.page-break{page-break-after:always}
@media print{body{padding:15px 24px}.toolbar{display:none}}
</style></head><body>
<div class="toolbar"><button onclick="window.print()">打印全部 (共${orders.length}份)</button></div>
${bodies}
<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>
</body></html>`
  openPrintWindow(combined)
}
src/utils/numberFormat.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
/** é‡‘额类字段默认最大小数位数 */
export const AMOUNT_DECIMAL_SCALE = 6
/**
 * æ ¼å¼åŒ–数字:最多保留 scale ä½å°æ•°ï¼Œå¹¶åŽ»é™¤æœ«å°¾å¤šä½™çš„ 0
 */
export function formatDecimal(value, scale = AMOUNT_DECIMAL_SCALE, fallback = '0') {
  if (value === null || value === undefined || value === '') {
    return fallback
  }
  const num = Number(value)
  if (Number.isNaN(num)) {
    return fallback
  }
  return parseFloat(num.toFixed(scale)).toString()
}
/** å¸¦è´§å¸ç¬¦å·çš„金额展示 */
export function formatCurrency(value, scale = AMOUNT_DECIMAL_SCALE) {
  return `Â¥${formatDecimal(value, scale)}`
}
/** el-table-column formatter */
export function tableAmountFormatter(row, column, cellValue) {
  return formatDecimal(cellValue)
}
/** è®¡ç®—后保留指定位小数 */
export function roundAmount(value, scale = AMOUNT_DECIMAL_SCALE) {
  if (value === null || value === undefined || value === '') {
    return 0
  }
  const num = Number(value)
  if (Number.isNaN(num)) {
    return 0
  }
  return parseFloat(num.toFixed(scale))
}
/** æž„建 summarizeTable é‡‘额列格式配置 */
export function buildAmountSummaryFormat(fields, extraFormat = {}) {
  const format = { ...extraFormat }
  fields.forEach(field => {
    format[field] = { decimalPlaces: AMOUNT_DECIMAL_SCALE }
  })
  return format
}
src/utils/summarizeTable.js
@@ -1,3 +1,5 @@
import { formatDecimal, AMOUNT_DECIMAL_SCALE } from './numberFormat'
/**
 * é€šç”¨çš„表格合计方法
 * @param {Object} param - åŒ…含表格列配置和数据源的对象
@@ -26,28 +28,35 @@
          // å¦‚果指定了不需要保留小数,则直接转换为整数
          sums[index] = Math.round(sum).toString();
        } else {
          // é»˜è®¤ä¿ç•™ä¸¤ä½å°æ•°
          sums[index] = parseFloat(sum).toFixed(
          sums[index] = formatDecimal(
            sum,
            specialFormat[prop]?.decimalPlaces ?? 2
          );
        }
      } else {
        sums[index] = "";
        sums[index] = '';
      }
    } else {
      sums[index] = "";
      sums[index] = '';
    }
  });
  return sums;
};
// ä¸å«ç¨Žæ€»ä»·è®¡ç®—
const calculateTaxExclusiveTotalPrice = (taxInclusiveTotalPrice, taxRate) => {
  const taxRateDecimal = taxRate / 100;
  return (taxInclusiveTotalPrice / (1 + taxRateDecimal)).toFixed(2);
  return formatDecimal(
    Number(taxInclusiveTotalPrice) / (1 + taxRateDecimal),
    AMOUNT_DECIMAL_SCALE
  );
};
// å«ç¨Žæ€»ä»·è®¡ç®—
const calculateTaxIncludeTotalPrice = (taxInclusiveUnitPrice, quantity) => {
  return (taxInclusiveUnitPrice * quantity).toFixed(2);
  return formatDecimal(
    Number(taxInclusiveUnitPrice) * Number(quantity),
    AMOUNT_DECIMAL_SCALE
  );
};
// å¯¼å‡ºå‡½æ•°ä¾›å…¶ä»–文件使用
export {
src/views/basicData/customerFile/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <div class="search_form" style="margin-bottom: 20px;">
    <div class="search_form"
         style="margin-bottom: 20px;">
      <div>
        <span class="search_title">客户名称:</span>
        <el-input v-model="searchForm.customerName"
@@ -17,8 +18,8 @@
                   @change="handleQuery">
          <el-option label="零售客户"
                     value="零售客户" />
          <el-option label="进销商客户"
                     value="进销商客户" />
          <el-option label="经销商客户"
                     value="经销商客户" />
        </el-select>
        <el-button type="primary"
                   @click="handleQuery"
@@ -130,8 +131,8 @@
                         clearable>
                <el-option label="零售客户"
                           value="零售客户" />
                <el-option label="进销商客户"
                           value="进销商客户" />
                <el-option label="经销商客户"
                           value="经销商客户" />
              </el-select>
            </el-form-item>
          </el-col>
@@ -280,7 +281,8 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitReminderForm">确认</el-button>
          <el-button type="primary"
                     @click="submitReminderForm">确认</el-button>
          <el-button @click="closeReminderDialog">取消</el-button>
        </div>
      </template>
@@ -363,7 +365,8 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitNegotiationForm">确认</el-button>
          <el-button type="primary"
                     @click="submitNegotiationForm">确认</el-button>
          <el-button @click="closeNegotiationDialog">取消</el-button>
        </div>
      </template>
@@ -412,14 +415,14 @@
                <span class="info-value">{{ detailForm.companyAddress }}</span>
              </div>
            </el-col>
            <el-col :span="12">
            <!-- <el-col :span="12">
              <div class="info-item">
                <span class="info-label">银行基本户:</span>
                <span class="info-value">{{ detailForm.basicBankAccount }}</span>
              </div>
            </el-col>
            </el-col> -->
          </el-row>
          <el-row :gutter="20">
          <!-- <el-row :gutter="20">
            <el-col :span="12">
              <div class="info-item">
                <span class="info-label">银行账号:</span>
@@ -432,7 +435,7 @@
                <span class="info-value">{{ detailForm.bankCode }}</span>
              </div>
            </el-col>
          </el-row>
          </el-row> -->
          <el-row :gutter="20">
            <el-col :span="12">
              <div class="info-item">
@@ -464,14 +467,9 @@
        </div>
      </div>
      <!-- æ´½è°ˆè¿›åº¦è®°å½• -->
      <div class="detail-section">
      <!-- <div class="detail-section">
        <div class="section-header">
          <h3 class="section-title">洽谈进度记录</h3>
          <el-button type="primary"
                     size="small"
                     @click="openNegotiationDialog(detailForm)">
            æ·»åŠ è¿›åº¦
          </el-button>
        </div>
        <el-table :data="negotiationRecords"
                  border
@@ -501,7 +499,6 @@
                  <Paperclip />
                </el-icon>
                é™„ä»¶
                <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : '上传' }} -->
              </el-button>
            </template>
          </el-table-column>
@@ -526,7 +523,7 @@
             class="no-records">
          æš‚无洽谈进度记录
        </div>
      </div>
      </div> -->
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDetailDialog">关闭</el-button>
@@ -622,7 +619,14 @@
    addReturnVisit,
    getReturnVisit,
  } from "@/api/basicData/customerFile.js";
  import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, backCustomer} from "@/api/basicData/customer.js";
  import {
    listCustomer,
    getCustomer,
    addCustomer,
    updateCustomer,
    delCustomer,
    backCustomer,
  } from "@/api/basicData/customer.js";
  import { ElMessageBox } from "element-plus";
  import { userListNoPage } from "@/api/system/user.js";
  import useUserStore from "@/store/modules/user";
@@ -738,16 +742,16 @@
      prop: "contactPhone",
      width: 150,
    },
    {
      label: "跟进进度",
      prop: "followUpLevel",
      width: 120,
    },
    {
      label: "跟进时间",
      prop: "followUpTime",
      width: 120,
    },
    // {
    //   label: "跟进进度",
    //   prop: "followUpLevel",
    //   width: 120,
    // },
    // {
    //   label: "跟进时间",
    //   prop: "followUpTime",
    //   width: 120,
    // },
    {
      label: "银行基本户",
      prop: "basicBankAccount",
@@ -767,7 +771,7 @@
      label: "维护人",
      prop: "maintainer",
    },
    {
    /*{
      label: "客户来源",
      prop: "type",
      dataType: "tag",
@@ -784,7 +788,7 @@
        }
        return "success";
      },
    },
    },*/
    {
      label: "维护时间",
      prop: "maintenanceTime",
@@ -800,29 +804,24 @@
        {
          name: "编辑",
          type: "text",
          showHide: row => !isCustomerEditLocked(row),
          clickFun: row => {
            if (isCustomerEditLocked(row)) {
              proxy.$modal.msgWarning("已被销售模块引用的私海客户不能编辑");
              return;
            }
            openForm("edit", row);
          },
        },
                {
                    name: "添加洽谈进度",
                    type: "text",
                    clickFun: row => {
                        openNegotiationDialog(row);
                    },
                },
        {
          name: "回访提醒",
          type: "text",
          clickFun: row => {
            openReminderDialog(row);
          },
        },
        // {
        //   name: "添加洽谈进度",
        //   type: "text",
        //   clickFun: row => {
        //     openNegotiationDialog(row);
        //   },
        // },
        // {
        //   name: "回访提醒",
        //   type: "text",
        //   clickFun: row => {
        //     openReminderDialog(row);
        //   },
        // },
                {
                    name: "详情",
                    type: "text",
@@ -860,7 +859,7 @@
    searchForm: {
      customerName: "",
      customerType: "",
      type: 0
      type: 0,
    },
    form: {
      customerName: "",
@@ -875,7 +874,7 @@
      bankAccount: "",
      bankCode: "",
      customerType: "",
      type: 0
      type: 0,
    },
    rules: {
      customerName: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -908,7 +907,7 @@
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData",
    data: {
      type: 0
      type: 0,
    },
    // æ–‡ä»¶ä¸Šä¼ å‰çš„回调
    beforeUpload: file => {
@@ -1006,10 +1005,6 @@
  }
  // æ‰“开弹框
  const openForm = (type, row) => {
    if (type === "edit" && isCustomerEditLocked(row)) {
      proxy.$modal.msgWarning("已被销售模块引用的私海客户不能编辑");
      return;
    }
    operationType.value = type;
    form.value = {};
    form.value.maintainer = userStore.nickName;
@@ -1038,12 +1033,6 @@
      });
    }
    dialogFormVisible.value = true;
  };
  const isCustomerEditLocked = row => {
    const salesReferenced = Number(row?.salesReferenceFlag || 0) === 1;
    const customerType = Number(row?.type);
    const usageStatus = Number(row?.usageStatus || 0);
    return salesReferenced && (customerType === 0 || (customerType === 1 && usageStatus === 1));
  };
  // æäº¤è¡¨å•
  const submitForm = () => {
src/views/basicData/customerFileOpenSea/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <div class="search_form" style="margin-bottom: 20px;">
    <div class="search_form"
         style="margin-bottom: 20px;">
      <div>
        <span class="search_title">客户名称:</span>
        <el-input v-model="searchForm.customerName"
@@ -17,8 +18,8 @@
                   @change="handleQuery">
          <el-option label="零售客户"
                     value="零售客户" />
          <el-option label="进销商客户"
                     value="进销商客户" />
          <el-option label="经销商客户"
                     value="经销商客户" />
        </el-select>
        <el-button type="primary"
                   @click="handleQuery"
@@ -92,7 +93,7 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
        <!-- <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="银行基本户:"
                          prop="basicBankAccount">
@@ -109,16 +110,16 @@
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        </el-row> -->
        <el-row :gutter="30">
          <el-col :span="12">
          <!-- <el-col :span="12">
            <el-form-item label="开户行号:"
                          prop="bankCode">
              <el-input v-model="form.bankCode"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          </el-col> -->
          <el-col :span="12">
            <el-form-item label="客户分类:"
                          prop="customerType">
@@ -127,8 +128,8 @@
                         clearable>
                <el-option label="零售客户"
                           value="零售客户" />
                <el-option label="进销商客户"
                           value="进销商客户" />
                <el-option label="经销商客户"
                           value="经销商客户" />
              </el-select>
            </el-form-item>
          </el-col>
@@ -199,75 +200,6 @@
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog v-model="assignDialogVisible"
               title="分配客户"
               width="500px"
               @close="closeAssignDialog">
      <el-form :model="assignForm"
               :rules="assignRules"
               ref="assignFormRef"
               label-width="100px">
        <el-form-item label="客户名称">
          <el-input v-model="assignForm.customerName"
                    disabled />
        </el-form-item>
        <el-form-item label="分配人员"
                      prop="boundId">
          <el-select v-model="assignForm.boundId"
                     placeholder="请选择分配人员"
                     style="width: 100%"
                     filterable>
            <el-option v-for="item in userList"
                       :key="item.userId || item.nickName"
                       :label="item.nickName"
                       :value="item.userId" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitAssignForm">确认</el-button>
          <el-button @click="closeAssignDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog v-model="shareDialogVisible"
               title="共享客户"
               width="500px"
               @close="closeShareDialog">
      <el-form :model="shareForm"
               :rules="shareRules"
               ref="shareFormRef"
               label-width="100px">
        <el-form-item label="客户名称">
          <el-input v-model="shareForm.customerName"
                    disabled />
        </el-form-item>
        <el-form-item label="共享人员"
                      prop="boundIds">
          <el-select v-model="shareForm.boundIds"
                     placeholder="请选择共享人员"
                     style="width: 100%"
                     filterable
                     multiple
                     collapse-tags
                     collapse-tags-tooltip>
            <el-option v-for="item in userList"
                       :key="item.userId || item.nickName"
                       :label="item.nickName"
                       :value="item.userId" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitShareForm">确认</el-button>
          <el-button @click="closeShareDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
@@ -346,7 +278,8 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitReminderForm">确认</el-button>
          <el-button type="primary"
                     @click="submitReminderForm">确认</el-button>
          <el-button @click="closeReminderDialog">取消</el-button>
        </div>
      </template>
@@ -429,7 +362,8 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitNegotiationForm">确认</el-button>
          <el-button type="primary"
                     @click="submitNegotiationForm">确认</el-button>
          <el-button @click="closeNegotiationDialog">取消</el-button>
        </div>
      </template>
@@ -478,14 +412,14 @@
                <span class="info-value">{{ detailForm.companyAddress }}</span>
              </div>
            </el-col>
            <el-col :span="12">
            <!-- <el-col :span="12">
              <div class="info-item">
                <span class="info-label">银行基本户:</span>
                <span class="info-value">{{ detailForm.basicBankAccount }}</span>
              </div>
            </el-col>
            </el-col> -->
          </el-row>
          <el-row :gutter="20">
          <!-- <el-row :gutter="20">
            <el-col :span="12">
              <div class="info-item">
                <span class="info-label">银行账号:</span>
@@ -498,7 +432,7 @@
                <span class="info-value">{{ detailForm.bankCode }}</span>
              </div>
            </el-col>
          </el-row>
          </el-row> -->
          <el-row :gutter="20">
            <el-col :span="12">
              <div class="info-item">
@@ -530,14 +464,9 @@
        </div>
      </div>
      <!-- æ´½è°ˆè¿›åº¦è®°å½• -->
      <div class="detail-section">
      <!-- <div class="detail-section">
        <div class="section-header">
          <h3 class="section-title">洽谈进度记录</h3>
          <el-button type="primary"
                     size="small"
                     @click="openNegotiationDialog(detailForm)">
            æ·»åŠ è¿›åº¦
          </el-button>
        </div>
        <el-table :data="negotiationRecords"
                  border
@@ -567,7 +496,6 @@
                  <Paperclip />
                </el-icon>
                é™„ä»¶
                <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : '上传' }} -->
              </el-button>
            </template>
          </el-table-column>
@@ -592,7 +520,7 @@
             class="no-records">
          æš‚无洽谈进度记录
        </div>
      </div>
      </div> -->
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDetailDialog">关闭</el-button>
@@ -690,41 +618,17 @@
  } from "@/api/basicData/customerFile.js";
  import {
    listCustomer,
    addCustomer,
    delCustomer,
    updateCustomer,
    getCustomer,
    assignCustomer,
    recycleCustomer,
    shareCustomer,
    addCustomer,
    updateCustomer,
    delCustomer,
  } from "@/api/basicData/customer.js";
  import { ElMessageBox } from "element-plus";
  import { userListNoPage } from "@/api/system/user.js";
  import useUserStore from "@/store/modules/user";
  import { getToken } from "@/utils/auth.js";
  const { proxy } = getCurrentInstance();
  const userStore = useUserStore();
  const assignDialogVisible = ref(false);
  const assignFormRef = ref();
  const assignForm = reactive({
    id: undefined,
    customerName: "",
    boundId: undefined,
  });
  const assignRules = {
    boundId: [{ required: true, message: "请选择分配人员", trigger: "change" }],
  };
  const shareDialogVisible = ref(false);
  const shareFormRef = ref();
  const shareForm = reactive({
    id: undefined,
    customerName: "",
    boundIds: [],
  });
  const shareRules = {
    boundIds: [{ required: true, message: "请选择共享人员", trigger: "change" }],
  };
  // å›žè®¿æé†’相关
  const reminderDialogVisible = ref(false);
@@ -822,7 +726,7 @@
    },
    {
      label: "地址及联系方式",
      prop: "addressPhone",
      prop: "companyAddress",
      width: 250,
    },
    {
@@ -844,21 +748,21 @@
    //   prop: "followUpTime",
    //   width: 120,
    // },
    {
      label: "银行基本户",
      prop: "basicBankAccount",
      width: 220,
    },
    {
      label: "银行账号",
      prop: "bankAccount",
      width: 220,
    },
    {
      label: "开户行号",
      prop: "bankCode",
      width: 220,
    },
    // {
    //   label: "银行基本户",
    //   prop: "basicBankAccount",
    //   width: 220,
    // },
    // {
    //   label: "银行账号",
    //   prop: "bankAccount",
    //   width: 220,
    // },
    // {
    //   label: "开户行号",
    //   prop: "bankCode",
    //   width: 220,
    // },
    {
      label: "维护人",
      prop: "maintainer",
@@ -869,91 +773,40 @@
            width: 100,
        },
    {
      label: "领用人",
      prop: "usageUserName",
      width: 120,
            fixed: "right",
    },
    {
      label: "领用状态",
      prop: "usageStatus",
      dataType: "tag",
      width: 100,
            fixed: "right",
      formatData: value => {
        if (value === 1 || value === "1") {
          return "已领用";
        }
        return "未领用";
      },
      formatType: value => {
        if (value === 1 || value === "1") {
          return "success";
        }
        return "info";
      },
    },
        {
            label: "共享人",
            prop: "togetherUserNames",
            width: 120,
            fixed: "right",
        },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      width: 140,
      operation: [
        {
          name: "分配",
          type: "text",
          showHide: row => row.usageStatus != 1,
          clickFun: row => {
            openAssignDialog(row);
          },
        },
        {
          name: "回收",
          type: "text",
          disabled: row => isCustomerRecoveryLocked(row),
          showHide: row => row.usageStatus == 1,
          clickFun: row => {
            if (isCustomerRecoveryLocked(row)) {
              proxy.$modal.msgWarning("已领用并且被销售模块引用的公海客户不能回收");
              return;
            }
            recycle(row);
          },
        },
                {
                    name: "共享",
                    type: "text",
                    showHide: row => row.usageStatus == 1,
                    clickFun: row => {
                        openShareDialog(row);
                    },
                },
                {
                    name: "编辑",
                    type: "text",
          disabled: row => isCustomerEditLocked(row),
          clickFun: row => {
            if (isCustomerEditLocked(row)) {
              proxy.$modal.msgWarning("已领用并且被销售模块引用的公海客户不能编辑");
              return;
            }
                        openForm("edit", row);
                    },
                },
        // {
        //   name: "详情",
        //   name: "添加洽谈进度",
        //   type: "text",
        //   clickFun: row => {
        //     openDetailDialog(row);
        //     openNegotiationDialog(row);
        //   },
        // },
        // {
        //   name: "回访提醒",
        //   type: "text",
        //   clickFun: row => {
        //     openReminderDialog(row);
        //   },
        // },
        {
          name: "详情",
          type: "text",
          clickFun: row => {
            openDetailDialog(row);
          },
        },
      ],
    },
  ]);
@@ -984,7 +837,6 @@
    searchForm: {
      customerName: "",
      customerType: "",
      type: 1
    },
    form: {
      customerName: "",
@@ -999,15 +851,14 @@
      bankAccount: "",
      bankCode: "",
      customerType: "",
      type: 1
    },
    rules: {
      customerName: [{ required: true, message: "请输入", trigger: "blur" }],
      taxpayerIdentificationNumber: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
      companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
      // taxpayerIdentificationNumber: [
      //   { required: true, message: "请输入", trigger: "blur" },
      // ],
      // companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
      // companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
      // contactPerson: [{ required: true, message: "请输入", trigger: "blur" }],
      // contactPhone: [{ required: true, message: "请输入", trigger: "blur" }],
      maintainer: [{ required: false, message: "请选择", trigger: "change" }],
@@ -1031,9 +882,7 @@
    headers: { Authorization: "Bearer " + getToken() },
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData",
    data: {
      type: 1
    },
    data: {},
    // æ–‡ä»¶ä¸Šä¼ å‰çš„回调
    beforeUpload: file => {
      console.log("文件即将上传", file);
@@ -1104,8 +953,7 @@
  };
  const getList = () => {
    tableLoading.value = true;
    const { total, ...queryPage } = page;
    listCustomer({ ...searchForm.value, ...queryPage }).then(res => {
    listCustomer({ ...searchForm.value, ...page }).then(res => {
      tableLoading.value = false;
      tableData.value = res.data.records;
      page.total = res.data.total;
@@ -1131,10 +979,6 @@
  }
  // æ‰“开弹框
  const openForm = (type, row) => {
    if (type === "edit" && isCustomerEditLocked(row)) {
      proxy.$modal.msgWarning("已领用并且被销售模块引用的公海客户不能编辑");
      return;
    }
    operationType.value = type;
    form.value = {};
    form.value.maintainer = userStore.nickName;
@@ -1145,7 +989,6 @@
      },
    ];
    form.value.maintenanceTime = getCurrentDate();
    form.value.type = 1;
    userListNoPage().then(res => {
      userList.value = res.data;
    });
@@ -1164,11 +1007,6 @@
    }
    dialogFormVisible.value = true;
  };
  const isCustomerSalesReferenced = row => Number(row?.salesReferenceFlag || 0) === 1;
  const isCustomerRecoveryLocked = row => Number(row?.usageStatus || 0) === 1 && isCustomerSalesReferenced(row);
  const isCustomerEditLocked = row => isCustomerSalesReferenced(row) && (
    Number(row?.type) === 0 || (Number(row?.type) === 1 && Number(row?.usageStatus || 0) === 1)
  );
  // æäº¤è¡¨å•
  const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
@@ -1217,92 +1055,6 @@
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
  };
  const ensureUserList = () => {
    if (userList.value.length) {
      return Promise.resolve();
    }
    return userListNoPage().then(res => {
      userList.value = res.data || [];
    });
  };
  const openAssignDialog = row => {
    assignForm.id = row.id;
    assignForm.customerName = row.customerName;
    assignForm.boundId = undefined;
    ensureUserList().then(() => {
      assignDialogVisible.value = true;
    });
  };
  const closeAssignDialog = () => {
    proxy.resetForm("assignFormRef");
    assignForm.id = undefined;
    assignForm.customerName = "";
    assignForm.boundId = undefined;
    assignDialogVisible.value = false;
  };
  const openShareDialog = row => {
    shareForm.id = row.id;
    shareForm.customerName = row.customerName;
    shareForm.boundIds = row.userIds || [];
    ensureUserList().then(() => {
      shareDialogVisible.value = true;
    });
  };
  const closeShareDialog = () => {
    proxy.resetForm("shareFormRef");
    shareForm.id = undefined;
    shareForm.customerName = "";
    shareForm.boundIds = [];
    shareDialogVisible.value = false;
  };
  const submitAssignForm = () => {
    proxy.$refs.assignFormRef.validate(valid => {
      if (!valid) {
        return;
      }
      assignCustomer({
        id: assignForm.id,
        usageUser: assignForm.boundId,
      }).then(() => {
        proxy.$modal.msgSuccess("分配成功");
        closeAssignDialog();
        getList();
      });
    });
  };
  const submitShareForm = () => {
    proxy.$refs.shareFormRef.validate(valid => {
      if (!valid) {
        return;
      }
      shareCustomer({
        id: shareForm.id,
        userIds: shareForm.boundIds,
      }).then(() => {
        proxy.$modal.msgSuccess("共享成功");
        closeShareDialog();
        getList();
      });
    });
  };
  const recycle = row => {
    ElMessageBox.confirm("确认回收客户“" + row.customerName + "”吗?", "回收提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        return recycleCustomer({id: row.id}).then(() => {
          proxy.$modal.msgSuccess("回收成功");
          getList();
        })
      })
      .catch(error => {
        if (error === "cancel" || error === "close") {
          proxy.$modal.msg("已取消");
        }
      });
  };
  // å¯¼å‡º
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
@@ -1311,7 +1063,7 @@
      type: "warning",
    })
      .then(() => {
        proxy.download("/basic/customer/export", {type: 1}, "客户档案.xlsx");
        proxy.download("/basic/customer/export", {}, "客户档案.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
@@ -1321,14 +1073,6 @@
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
      const unauthorizedData = selectedRows.value.filter(
        item => item.maintainer !== userStore.nickName
      );
      if (unauthorizedData.length > 0) {
        proxy.$modal.msgWarning("不可删除他人维护的数据");
        return;
      }
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
@@ -1342,7 +1086,7 @@
      .then(() => {
        tableLoading.value = true;
        delCustomer(ids)
          .then(res => {
          .then(() => {
            proxy.$modal.msgSuccess("删除成功");
            getList();
          })
@@ -1411,8 +1155,6 @@
          };
        }
        console.log("提交回访提醒数据:", submitvalue.value);
        // è°ƒç”¨æŽ¥å£
        addReturnVisit(submitvalue.value)
          .then(res => {
@@ -1440,14 +1182,6 @@
    negotiationForm.followUpTime = "";
    negotiationForm.followerUserName = userStore.nickName; // é»˜è®¤å½“前登录人
    negotiationForm.content = "";
    // {
    //     "customerId": 152,
    //     "followUpMethod": "电话沟通",
    //     "followUpLevel": "没有意向",
    //     "followUpTime": "2026-03-04T15:30:00",
    //     "followerUserName": "管理员账号",
    //     "content": "111"
    // }
    negotiationDialogVisible.value = true;
  };
@@ -1469,23 +1203,6 @@
        if (isEdit) {
          // ä¿®æ”¹æ“ä½œ
          console.log("修改洽谈进度数据:", negotiationForm);
          // è¿™é‡Œå¯ä»¥è°ƒç”¨æ›´æ–°æŽ¥å£
          // å®žé™…项目中需要根据后端接口进行调整
          // ç¤ºä¾‹ï¼šupdateCustomerFollow(negotiationForm).then(res => {
          //   // æ›´æ–°æœ¬åœ°æ•°æ®
          //   const index = negotiationForm.editIndex;
          //   negotiationRecords.value[index] = {
          //     followUpTime: negotiationForm.followUpTime,
          //     followUpMethod: negotiationForm.followUpMethod,
          //     followUpLevel: negotiationForm.followUpLevel,
          //     followerUserName: negotiationForm.followerUserName,
          //     content: negotiationForm.content,
          //     id: negotiationForm.id,
          //   };
          //   proxy.$modal.msgSuccess("修改成功");
          //   closeNegotiationDialog();
          // });
          updateCustomerFollow(negotiationForm).then(res => {
            // æ›´æ–°æœ¬åœ°æ•°æ®
            getCustomer(negotiationForm.customerId).then(res => {
@@ -1520,7 +1237,6 @@
  // æ‰“开详情弹窗
  const openDetailDialog = row => {
    // è°ƒç”¨getCustomer接口获取客户详情
    getCustomer(row.id).then(res => {
      // å¡«å……客户基本信息
      Object.assign(detailForm, res.data);
src/views/basicData/parameterMaintenance/index.vue
@@ -50,7 +50,7 @@
                      prop="paramCode">
          <el-input v-model="formData.paramCode"
                    disabled
                    placeholder="保存后自动生成" />
                    placeholder="自动生成" />
        </el-form-item>
        <el-form-item label="参数名称"
                      prop="paramName">
@@ -82,14 +82,6 @@
                       value="2" />
          </el-select>
        </el-form-item> -->
        <el-form-item label="创建时间"
                      prop="createTime">
          <el-date-picker v-model="formCreateTimeDate"
                          type="date"
                          placeholder="选择日期"
                          value-format="YYYY-MM-DD"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formData.unit"
@@ -215,8 +207,7 @@
</template>
<script setup>
  import { onMounted, ref, reactive, computed } from "vue";
  import dayjs from "dayjs";
  import { onMounted, ref, reactive } from "vue";
  import {
    parameterListPage,
    addParameter,
@@ -232,6 +223,7 @@
  import { deptTreeSelect } from "@/api/system/user.js";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { parseTime } from "@/utils/ruoyi";
  const tableColumn = ref([
    {
@@ -302,6 +294,10 @@
    {
      label: "创建时间",
      prop: "createTime",
      formatData: val => {
        if (!val) return "";
        return parseTime(val, "{y}-{m}-{d} {h}:{i}:{s}");
      },
    },
    {
      label: "操作",
@@ -350,7 +346,6 @@
    remark: "",
    isRequired: 0,
    paramFormat: "",
    createTime: "",
  });
  const rules = reactive({
    paramName: [{ required: true, message: "请输入参数名称", trigger: "blur" }],
@@ -370,12 +365,6 @@
        },
      },
    ],
  });
  const formCreateTimeDate = computed({
    get: () => (formData.createTime ? String(formData.createTime).split(" ")[0] : ""),
    set: (value) => {
      formData.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
    },
  });
  // const productTypes = ref([]);
  const isEdit = ref(false);
@@ -535,7 +524,6 @@
    formData.unit = "";
    formData.remark = "";
    formData.isRequired = 0;
    formData.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    dialogVisible.value = true;
  };
@@ -554,7 +542,6 @@
    formData.remark = row.remark || "";
    formData.paramFormat = row.paramFormat || "";
    formData.isRequired = row.isRequired || 0;
    formData.createTime = row.createTime || "";
    dialogVisible.value = true;
  };
src/views/basicData/product/ProductSelectDialog.vue
@@ -2,11 +2,11 @@
  <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-input v-model="query.productName" placeholder="输入产品名称" clearable @keyup.enter="onSearch" style="width: 200px" />
      </el-form-item>
      <el-form-item label="产品型号">
        <el-input v-model="query.model" placeholder="输入产品型号" clearable @keyup.enter="onSearch" />
        <el-input v-model="query.model" placeholder="输入产品型号" clearable @keyup.enter="onSearch" style="width: 200px" />
      </el-form-item>
      <el-form-item>
@@ -20,9 +20,10 @@
      @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-column prop="topProductName" label="产品大类" min-width="120" />
      <el-table-column prop="productName" label="产品名称" min-width="140" />
      <el-table-column prop="model" label="产品型号" min-width="180" />
      <el-table-column prop="unit" label="单位" min-width="100" />
    </el-table>
    <div class="mt-3 flex justify-end">
src/views/basicData/product/index.vue
@@ -104,8 +104,10 @@
            <el-form-item label="产品名称:"
                          prop="productName">
              <el-input v-model="form.productName"
                        type="textarea"
                        resize="vertical"
                        placeholder="请输入产品名称"
                        maxlength="20"
                        maxlength="50"
                        show-word-limit
                        clearable
                        @keydown.enter.prevent />
@@ -132,7 +134,6 @@
               :rules="modelRules"
               ref="modelFormRef">
        <el-row>
          <el-row>
            <el-col :span="24">
              <el-form-item label="产品编号:"
                            prop="productCode">
@@ -142,7 +143,6 @@
                          @keydown.enter.prevent />
              </el-form-item>
            </el-col>
          </el-row>
          <el-col :span="24">
            <el-form-item label="规格型号:"
                          prop="model">
@@ -335,7 +335,7 @@
    rules: {
      productName: [
        { required: true, message: "请输入", trigger: "blur" },
        { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
        { max: 50, message: "产品名称不能超过50个字符", trigger: "blur" },
      ],
    },
    modelForm: {
src/views/basicData/supplierManage/components/BlacklistTab.vue
@@ -3,179 +3,126 @@
    <div class="search_form">
      <div>
        <span class="search_title">供应商档案:</span>
        <el-input
            v-model="searchForm.supplierName"
        <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
        >
                  :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>
        <el-button type="danger"
                   plain
                   @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
      <PIMTable rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
      ></PIMTable>
                @pagination="pagination"></PIMTable>
    </div>
    <el-dialog
        v-model="dialogFormVisible"
    <el-dialog v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增供应商信息' : '编辑供应商信息'"
        width="70%"
        @close="closeDia"
    >
      <el-form
          :model="form"
               @close="closeDia">
      <el-form :model="form"
          label-width="140px"
          label-position="top"
          :rules="rules"
          ref="formRef"
      >
               ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                  v-model="form.supplierName"
            <el-form-item label="供应商名称:"
                          prop="supplierName">
              <el-input v-model="form.supplierName"
                  placeholder="请输入"
                  clearable
              />
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
                label="纳税人识别号:"
                prop="taxpayerIdentificationNum"
            >
              <el-input
                  v-model="form.taxpayerIdentificationNum"
            <el-form-item label="纳税人识别号:"
                          prop="taxpayerIdentificationNum">
              <el-input v-model="form.taxpayerIdentificationNum"
                  placeholder="请输入"
                  clearable
              />
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="公司地址:"
                          prop="companyAddress">
              <el-input v-model="form.companyAddress"
                        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"
            <el-form-item label="维护人:"
                          prop="maintainUserId">
              <el-select v-model="form.maintainUserId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                    v-for="item in userList"
                         disabled>
                <el-option v-for="item in userList"
                    :key="item.nickName"
                    :label="item.nickName"
                    :value="item.userId"
                />
                           :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%"
            <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
              />
                              clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商类型:" prop="supplierType">
              <el-select v-model="form.supplierType" placeholder="请选择" clearable>
                <el-option label="甲" value="甲" />
                <el-option label="乙" value="乙" />
                <el-option label="丙" value="丙" />
                <el-option label="丁" value="丁" />
          <!-- <el-col :span="12">
            <el-form-item label="供应商类型:"
                          prop="supplierType">
              <el-select v-model="form.supplierType"
                         placeholder="请选择"
                         clearable>
                <el-option label="甲"
                           value="甲" />
                <el-option label="乙"
                           value="乙" />
                <el-option label="丙"
                           value="丙" />
                <el-option label="丁"
                           value="丁" />
              </el-select>
            </el-form-item>
          </el-col>
          </el-col> -->
          <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-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>
@@ -183,21 +130,18 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ä¾›åº”商导入对话框 -->
    <el-dialog
        :title="upload.title"
    <el-dialog :title="upload.title"
        v-model="upload.open"
        width="400px"
        append-to-body
    >
      <el-upload
          ref="uploadRef"
               append-to-body>
      <el-upload ref="uploadRef"
          :limit="1"
          accept=".xlsx, .xls"
          :headers="upload.headers"
@@ -207,26 +151,23 @@
          :on-success="handleFileSuccess"
          :on-error="handleFileError"
          :auto-upload="false"
          drag
      >
                 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"
            <el-link type="primary"
                :underline="false"
                style="font-size: 12px; vertical-align: baseline"
                @click="importTemplate"
            >下载模板</el-link
            >
                     @click="importTemplate">下载模板</el-link>
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button type="primary"
                     @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
@@ -262,46 +203,22 @@
  {
    label: "供应商名称",
    prop: "supplierName",
    width: 250,
      // width: 250,
  },
  {
    label: "供应商类型",
    prop: "supplierType",
    width: 120,
  },
    // {
    //   label: "供应商类型",
    //   prop: "supplierType",
    //   width: 120,
    // },
  {
    label: "纳税人识别号",
    prop: "taxpayerIdentificationNum",
    width: 230,
      // 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,
      // width: 300,
  },
  {
    label: "维护人",
@@ -311,30 +228,30 @@
  {
    label: "维护时间",
    prop: "maintainTime",
    width:100
      // width: 100,
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
      fixed: "right",
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          clickFun: row => {
          openForm("edit", row);
        },
      },
      {
        //资质附件
        name: "资质文件",
        type: "text",
        clickFun: (row) => {
          openFileDialog(row)
        }
      }
        // {
        //   //资质附件
        //   name: "资质文件",
        //   type: "text",
        //   clickFun: row => {
        //     openFileDialog(row);
        //   },
        // },
    ],
  },
]);
@@ -360,11 +277,6 @@
    supplierName: "",
    taxpayerIdentificationNum: "",
    companyAddress: "",
    companyPhone: "",
    bankAccountName: "",
    bankAccountNum: "",
    contactUserName: "",
    contactUserPhone: "",
    maintainUserId: "",
    maintainTime: "",
    supplierType: "",
@@ -376,14 +288,11 @@
      { 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" }],
    supplierType: [{ required: true, message: "请选择供应商类型", trigger: "change" }],
      // supplierType: [
      //   { required: true, message: "请选择供应商类型", trigger: "change" },
      // ],
  },
});
const { searchForm, form, rules } = toRefs(data);
@@ -394,7 +303,7 @@
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  const pagination = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
@@ -406,7 +315,7 @@
}
const getList = () => {
  tableLoading.value = true;
  listSupplier({ ...searchForm.value, ...page, isWhite: 1 }).then((res) => {
    listSupplier({ ...searchForm.value, ...page, isWhite: 1 }).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    page.total = res.data.total;
@@ -433,7 +342,11 @@
}
/** ä¸‹è½½æ¨¡æ¿ */
function importTemplate() {
  proxy.download("/system/supplier/downloadTemplate", {}, "供应商导入模板.xlsx");
    proxy.download(
      "/system/supplier/downloadTemplate",
      {},
      "供应商导入模板.xlsx"
    );
}
/**文件上传中处理 */
@@ -462,7 +375,7 @@
  proxy.$modal.msgError("文件上传失败");
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  const handleSelectionChange = selection => {
  selectedRows.value = selection;
};
// æ‰“开弹框
@@ -471,11 +384,11 @@
  form.value = {};
  form.value.maintainUserId = userStore.id;
  form.value.maintainTime = getCurrentDate();
  userListNoPage().then((res) => {
    userListNoPage().then(res => {
    userList.value = res.data;
  });
  if (type === "edit") {
    getSupplier(row.id).then((res) => {
      getSupplier(row.id).then(res => {
      form.value = { ...res.data };
    });
  }
@@ -483,7 +396,7 @@
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
@@ -495,7 +408,7 @@
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addSupplier(form.value).then((res) => {
    addSupplier(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
@@ -503,7 +416,7 @@
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateSupplier(form.value).then((res) => {
    updateSupplier(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
@@ -522,7 +435,11 @@
    type: "warning",
  })
      .then(() => {
        proxy.download("/system/supplier/export", { isWhite: 1 }, "供应商档案.xlsx");
        proxy.download(
          "/system/supplier/export",
          { isWhite: 1 },
          "供应商档案.xlsx"
        );
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
@@ -533,12 +450,14 @@
  let ids = [];
  if (selectedRows.value.length > 0) {
    // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
    const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
      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);
      ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
@@ -551,7 +470,7 @@
      .then(() => {
        tableLoading.value = true;
        delSupplier(ids)
            .then((res) => {
          .then(res => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
src/views/basicData/supplierManage/components/HomeTab.vue
@@ -3,185 +3,132 @@
    <div class="search_form">
      <div style="margin-bottom: 10px;">
        <span class="search_title">供应商档案:</span>
        <el-input
            v-model="searchForm.supplierName"
        <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
        >
                  :prefix-icon="Search" />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
      </div>
      <div style="margin-bottom: 10px;">
        <el-button type="primary" @click="openForm('add')"
        >新增供应商</el-button
        >
        <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>
        <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"
      <PIMTable rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
      ></PIMTable>
                @pagination="pagination"></PIMTable>
    </div>
    <el-dialog
        v-model="dialogFormVisible"
    <el-dialog v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增供应商信息' : '编辑供应商信息'"
        width="70%"
        @close="closeDia"
    >
      <el-form
          :model="form"
               @close="closeDia">
      <el-form :model="form"
          label-width="140px"
          label-position="top"
          :rules="rules"
          ref="formRef"
      >
               ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                  v-model="form.supplierName"
            <el-form-item label="供应商名称:"
                          prop="supplierName">
              <el-input v-model="form.supplierName"
                  placeholder="请输入"
                  clearable
              />
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
                label="纳税人识别号:"
                prop="taxpayerIdentificationNum"
            >
              <el-input
                  v-model="form.taxpayerIdentificationNum"
            <el-form-item label="纳税人识别号:"
                          prop="taxpayerIdentificationNum">
              <el-input v-model="form.taxpayerIdentificationNum"
                  placeholder="请输入"
                  clearable
              />
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="公司地址:"
                          prop="companyAddress">
              <el-input v-model="form.companyAddress"
                        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"
            <el-form-item label="维护人:"
                          prop="maintainUserId">
              <el-select v-model="form.maintainUserId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                    v-for="item in userList"
                         disabled>
                <el-option v-for="item in userList"
                    :key="item.nickName"
                    :label="item.nickName"
                    :value="item.userId"
                />
                           :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%"
            <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
              />
                              clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商类型:" prop="supplierType">
              <el-select v-model="form.supplierType" placeholder="请选择" clearable>
                <el-option label="甲" value="甲" />
                <el-option label="乙" value="乙" />
                <el-option label="丙" value="丙" />
                <el-option label="丁" value="丁" />
          <!-- <el-col :span="12">
            <el-form-item label="供应商类型:"
                          prop="supplierType">
              <el-select v-model="form.supplierType"
                         placeholder="请选择"
                         clearable>
                <el-option label="甲"
                           value="甲" />
                <el-option label="乙"
                           value="乙" />
                <el-option label="丙"
                           value="丙" />
                <el-option label="丁"
                           value="丁" />
              </el-select>
            </el-form-item>
          </el-col>
          </el-col> -->
          <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-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>
@@ -189,21 +136,18 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ä¾›åº”商导入对话框 -->
    <el-dialog
        :title="upload.title"
    <el-dialog :title="upload.title"
        v-model="upload.open"
        width="400px"
        append-to-body
    >
      <el-upload
          ref="uploadRef"
               append-to-body>
      <el-upload ref="uploadRef"
          :limit="1"
          accept=".xlsx, .xls"
          :headers="upload.headers"
@@ -213,26 +157,23 @@
          :on-success="handleFileSuccess"
          :on-error="handleFileError"
          :auto-upload="false"
          drag
      >
                 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"
            <el-link type="primary"
                :underline="false"
                style="font-size: 12px; vertical-align: baseline"
                @click="importTemplate"
            >下载模板</el-link
            >
                     @click="importTemplate">下载模板</el-link>
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button type="primary"
                     @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
@@ -268,79 +209,56 @@
  {
    label: "供应商名称",
    prop: "supplierName",
    width: 250,
      // width: 250,
  },
  {
    label: "供应商类型",
    prop: "supplierType",
    width: 120,
  },
    // {
    //   label: "供应商类型",
    //   prop: "supplierType",
    //   width: 120,
    // },
  {
    label: "纳税人识别号",
    prop: "taxpayerIdentificationNum",
    width: 230,
      // 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,
      // width: 220,
  },
  {
    label: "维护人",
    prop: "maintainUserName",
      // width: 220,
  },
  {
    label: "维护时间",
    prop: "maintainTime",
    width:100
      width: 100,
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
      fixed: "right",
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          clickFun: row => {
          openForm("edit", row);
        },
      },
      {
        //资质附件
        name: "资质文件",
        type: "text",
        clickFun: (row) => {
          openFileDialog(row)
        }
      }
        // {
        //   //资质附件
        //   name: "资质文件",
        //   type: "text",
        //   clickFun: row => {
        //     openFileDialog(row);
        //   },
        // },
    ],
  },
]);
@@ -366,11 +284,6 @@
    supplierName: "",
    taxpayerIdentificationNum: "",
    companyAddress: "",
    companyPhone: "",
    bankAccountName: "",
    bankAccountNum: "",
    contactUserName: "",
    contactUserPhone: "",
    maintainUserId: "",
    maintainTime: "",
    supplierType: "",
@@ -382,14 +295,11 @@
      { 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" }],
    supplierType: [{ required: true, message: "请选择供应商类型", trigger: "change" }],
      // supplierType: [
      //   { required: true, message: "请选择供应商类型", trigger: "change" },
      // ],
  },
});
const { searchForm, form, rules } = toRefs(data);
@@ -400,7 +310,7 @@
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  const pagination = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
@@ -412,7 +322,7 @@
}
const getList = () => {
  tableLoading.value = true;
  listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then((res) => {
    listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    page.total = res.data.total;
@@ -439,7 +349,11 @@
}
/** ä¸‹è½½æ¨¡æ¿ */
function importTemplate() {
  proxy.download("/system/supplier/downloadTemplate", {}, "供应商导入模板.xlsx");
    proxy.download(
      "/system/supplier/downloadTemplate",
      {},
      "供应商导入模板.xlsx"
    );
}
/**文件上传中处理 */
@@ -468,7 +382,7 @@
  proxy.$modal.msgError("文件上传失败");
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  const handleSelectionChange = selection => {
  selectedRows.value = selection;
};
// æ‰“开弹框
@@ -477,11 +391,11 @@
  form.value = {};
  form.value.maintainUserId = userStore.id;
  form.value.maintainTime = getCurrentDate();
  userListNoPage().then((res) => {
    userListNoPage().then(res => {
    userList.value = res.data;
  });
  if (type === "edit") {
    getSupplier(row.id).then((res) => {
      getSupplier(row.id).then(res => {
      form.value = { ...res.data };
    });
  }
@@ -489,7 +403,7 @@
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
@@ -501,7 +415,7 @@
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addSupplier(form.value).then((res) => {
    addSupplier(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
@@ -509,7 +423,7 @@
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateSupplier(form.value).then((res) => {
    updateSupplier(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
@@ -528,7 +442,11 @@
    type: "warning",
  })
      .then(() => {
        proxy.download("/system/supplier/export", { isWhite: 0 }, "供应商档案.xlsx");
        proxy.download(
          "/system/supplier/export",
          { isWhite: 0 },
          "供应商档案.xlsx"
        );
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
@@ -539,12 +457,14 @@
  let ids = [];
  if (selectedRows.value.length > 0) {
    // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
    const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
      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);
      ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
@@ -557,7 +477,7 @@
      .then(() => {
        tableLoading.value = true;
        delSupplier(ids)
            .then((res) => {
          .then(res => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -22,14 +22,10 @@
        <el-row>
          <el-col :span="24">
            <el-form-item label="申请部门:">
              <el-select disabled
                         v-model="form.approveDeptId"
                         placeholder="选择部门">
                <el-option v-for="user in productOptions"
                           :key="user.deptId"
                           :label="user.deptName"
                           :value="user.deptId" />
              </el-select>
              <el-input v-model="form.approveDeptName"
                        placeholder=""
                        clearable
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
@@ -42,6 +38,18 @@
                        clearable
                        type="textarea"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row v-if="Number(props.approveType) === 4">
          <el-col :span="24">
            <el-form-item label="报销金额:">
              <el-input-number v-model="form.price"
                               disabled
                               :min="0"
                               :precision="2"
                               style="width: 100%"
                               placeholder="" />
            </el-form-item>
          </el-col>
        </el-row>
@@ -75,7 +83,7 @@
                <el-descriptions-item label="报价总额"
                                      :span="2">
                  <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
                    Â¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }}
                    {{ formatCurrency(currentQuotation.totalAmount) }}
                  </span>
                </el-descriptions-item>
              </el-descriptions>
@@ -92,7 +100,7 @@
                                   label="单位" />
                  <el-table-column prop="unitPrice"
                                   label="单价">
                    <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template>
                    <template #default="scope">{{ formatCurrency(scope.row.unitPrice) }}</template>
                  </el-table-column>
                </el-table>
              </div>
@@ -135,7 +143,7 @@
                <el-descriptions-item label="合同金额"
                                      :span="2">
                  <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
                    Â¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }}
                    {{ formatCurrency(currentPurchase.contractAmount) }}
                  </span>
                </el-descriptions-item>
              </el-descriptions>
@@ -154,11 +162,11 @@
                                   label="数量" />
                  <el-table-column prop="taxInclusiveUnitPrice"
                                   label="含税单价">
                    <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template>
                    <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveUnitPrice) }}</template>
                  </el-table-column>
                  <el-table-column prop="taxInclusiveTotalPrice"
                                   label="含税总价">
                    <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template>
                    <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveTotalPrice) }}</template>
                  </el-table-column>
                </el-table>
              </div>
@@ -295,8 +303,10 @@
                v-if="operationType === 'approval'">
        <div class="dialog-footer">
          <el-button type="primary"
                     :loading="submitLoading"
                     @click="submitForm(2)">不通过</el-button>
          <el-button type="primary"
                     :loading="submitLoading"
                     @click="submitForm(1)">通过</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
@@ -330,6 +340,7 @@
  import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js";
  import { getDeliveryDetailByShippingNo } from "@/api/salesManagement/deliveryLedger.js";
  import ImagePreview from "@/components/AttachmentPreview/image/index.vue";
  import { formatCurrency } from "@/utils/numberFormat";
  const emit = defineEmits(["close"]);
  const { proxy } = getCurrentInstance();
@@ -353,6 +364,7 @@
  const deliveryLoading = ref(false);
  const currentDelivery = ref({});
  const deliveryProductList = ref([]);
  const submitLoading = ref(false);
  const isQuotationApproval = computed(() => Number(props.approveType) === 6);
  const isPurchaseApproval = computed(() => Number(props.approveType) === 5);
  const isDeliveryApproval = computed(() => Number(props.approveType) === 7);
@@ -581,9 +593,14 @@
    // åˆ¤æ–­æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
    const isLast =
      activities.value.findIndex(a => a.isShen) === activities.value.length - 1;
    updateApproveNode({ ...currentActivity, isLast }).then(() => {
    submitLoading.value = true;
    updateApproveNode({ ...currentActivity, isLast })
      .then(() => {
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
      })
      .finally(() => {
        submitLoading.value = false;
    });
  };
  // å…³é—­å¼¹æ¡†
@@ -594,6 +611,7 @@
    currentQuotation.value = {};
    purchaseLoading.value = false;
    currentPurchase.value = {};
    submitLoading.value = false;
    emit("close");
  };
  defineExpose({
src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -225,167 +225,18 @@
        </div>
      </div>
    </FormDialog>
    <!-- æ–‡ä»¶ç®¡ç†å¼¹çª— -->
    <FormDialog
      v-model="filesDialogVisible"
      title="文件管理"
      :width="'900px'"
      @close="closeFilesDialog"
      @confirm="closeFilesDialog"
      @cancel="closeFilesDialog"
    >
      <div class="file-manager">
        <!-- æ–‡ä»¶ä¸Šä¼  -->
        <div class="upload-section">
          <el-upload
            :action="uploadUrl"
            :headers="uploadHeaders"
            :on-success="handleUploadSuccess"
            :on-error="handleUploadError"
            :before-upload="beforeUpload"
            name="files"
            multiple
            :show-file-list="false"
            accept=".txt,.md,.docx,.xlsx,.xls,.pdf"
          >
            <el-button type="primary">上传文件</el-button>
          </el-upload>
          <el-button
            type="success"
            @click="saveFiles"
            :disabled="uploadedBlobIds.length === 0"
            :loading="savingFiles"
            style="margin-left: 10px"
          >
            ä¿å­˜æ–‡ä»¶å…³è”
          </el-button>
          <el-button
            v-if="uploadedBlobIds.length > 0"
            type="text"
            @click="clearUploadedFiles"
            style="margin-left: 10px"
          >
            æ¸…空待保存列表
          </el-button>
        </div>
        <!-- å¾…保存的文件列表 -->
        <div v-if="uploadedBlobIds.length > 0" class="uploaded-list">
          <div class="uploaded-tip">
            <el-icon style="color: #409eff"><InfoFilled /></el-icon>
            <span>已上传 {{ uploadedBlobIds.length }} ä¸ªæ–‡ä»¶,请点击"保存文件关联"按钮触发向量化处理</span>
          </div>
        </div>
        <!-- æ–‡ä»¶åˆ—表与向量化状态 -->
        <el-table :data="fileList" style="margin-top: 20px" border>
          <el-table-column prop="fileName" label="文件名" show-overflow-tooltip />
          <el-table-column prop="fileType" label="文件类型" width="100" />
          <el-table-column label="向量化状态" width="120">
            <template #default="{ row }">
              <el-tag :type="getStatusType(row.vectorStatus)">
                {{ getStatusText(row.vectorStatus) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="chunkCount" label="切片数" width="100" align="center" />
          <el-table-column label="错误信息" width="200" show-overflow-tooltip>
            <template #default="{ row }">
              <span v-if="row.vectorError" style="color: #f56c6c">{{ row.vectorError }}</span>
              <span v-else style="color: #909399">-</span>
            </template>
          </el-table-column>
          <el-table-column prop="createTime" label="上传时间" width="180" />
          <el-table-column label="操作" width="150" align="center">
            <template #default="{ row }">
              <el-button
                v-if="row.vectorStatus === 3"
                type="text"
                @click="reprocessFile(row)"
              >
                é‡æ–°å¤„理
              </el-button>
              <el-button type="text" @click="deleteFile(row)" style="color: #f56c6c">
                åˆ é™¤
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </FormDialog>
    <!-- çŸ¥è¯†åº“问答弹窗 -->
    <FormDialog
      v-model="chatDialogVisible"
      title="知识库问答"
      :width="'800px'"
      @close="closeChatDialog"
      @confirm="closeChatDialog"
      @cancel="closeChatDialog"
    >
      <div class="knowledge-chat">
        <div class="chat-header">
          <el-tag type="success">当前知识库: {{ currentKnowledgeBase?.title }}</el-tag>
        </div>
        <!-- å¯¹è¯åŒºåŸŸ -->
        <div class="chat-messages" ref="chatMessagesRef">
          <div
            v-for="(msg, index) in messages"
            :key="index"
            :class="['message', msg.role]"
          >
            <div class="message-role">{{ msg.role === 'user' ? '我' : 'AI助手' }}</div>
            <div class="message-content">{{ msg.content }}</div>
          </div>
          <div v-if="chatLoading" class="message assistant">
            <div class="message-role">AI助手</div>
            <div class="message-content typing">正在思考中...</div>
          </div>
        </div>
        <!-- è¾“入框 -->
        <div class="chat-input">
          <el-input
            v-model="inputQuestion"
            placeholder="请输入问题,按回车发送(Ctrl+Enter快捷发送)"
            @keyup.enter="sendMessage"
            :disabled="chatLoading"
          >
            <template #append>
              <el-button @click="sendMessage" :loading="chatLoading">发送</el-button>
            </template>
          </el-input>
          <div class="chat-actions">
            <el-button type="text" size="small" @click="clearMessages">清空对话</el-button>
          </div>
        </div>
      </div>
    </FormDialog>
  </div>
</template>
<script setup>
import { Search, InfoFilled } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch, nextTick } from "vue";
import { Search } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import FormDialog from '@/components/Dialog/FormDialog.vue';
import {
  listKnowledgeBase,
  delKnowledgeBase,
  addKnowledgeBase,
  updateKnowledgeBase,
  getVectorStatus,
  reprocessVector,
  saveKnowledgeBaseFiles,
  deleteKnowledgeBaseFile,
  knowledgeChat
} from "@/api/collaborativeApproval/knowledgeBase.js";
import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js";
import useUserStore from '@/store/modules/user';
import { userListNoPageByTenantId } from '@/api/system/user.js';
import { getToken } from "@/utils/auth";
// è¡¨å•验证规则
const rules = {
@@ -432,18 +283,7 @@
  dialogTitle: "",
  dialogType: "add",
  viewDialogVisible: false,
  currentKnowledge: {},
  filesDialogVisible: false,
  currentKnowledgeBase: null,
  fileList: [],
  uploadedBlobIds: [],
  savingFiles: false,
  vectorStatusTimer: null, // å‘量化状态轮询定时器
  chatDialogVisible: false,
  messages: [],
  inputQuestion: "",
  chatLoading: false,
  memoryId: ""
  currentKnowledge: {}
});
const {
@@ -457,18 +297,7 @@
  dialogTitle,
  dialogType,
  viewDialogVisible,
  currentKnowledge,
  filesDialogVisible,
  currentKnowledgeBase,
  fileList,
  uploadedBlobIds,
  savingFiles,
  vectorStatusTimer,
  chatDialogVisible,
  messages,
  inputQuestion,
  chatLoading,
  memoryId
  currentKnowledge
} = toRefs(data);
// è¡¨å•引用
@@ -476,12 +305,6 @@
// ç”¨æˆ·ç›¸å…³
const userStore = useUserStore();
const userList = ref([]);
// èŠå¤©æ¶ˆæ¯å®¹å™¨å¼•用
const chatMessagesRef = ref();
// æ–‡ä»¶ä¸Šä¼ ç›¸å…³
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
const uploadHeaders = { Authorization: "Bearer " + getToken() };
// è¡¨æ ¼åˆ—配置
const tableColumn = ref([
@@ -529,18 +352,6 @@
    }
  },
  {
    label: "文件数量",
    prop: "fileCount",
    width: 100,
    align: "center"
  },
  {
    label: "切片数量",
    prop: "totalChunkCount",
    width: 100,
    align: "center"
  },
  {
    label: "使用次数",
    prop: "usageCount",
    width: 100,
@@ -568,20 +379,6 @@
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        }
      },
      {
        name: "文件",
        type: "text",
        clickFun: (row) => {
          openFilesDialog(row);
        }
      },
      {
        name: "问答",
        type: "text",
        clickFun: (row) => {
          openChatDialog(row);
        }
      },
      {
@@ -625,31 +422,21 @@
const getList = () => {
  tableLoading.value = true;
  // âœ… GET请求使用params传参
  listKnowledgeBase({
    current: page.value.current,
    size: page.value.size,
    title: searchForm.value.title,
    type: searchForm.value.type
  })
  listKnowledgeBase({...page.value, ...searchForm.value})
  .then(res => {
    tableLoading.value = false;
    page.value.total = res.data.total;
    // å¦‚果当前页数超过总页数,重置到第1页并重新查询
    // å¦‚果当前页数超过总页数,重置到第1页并重新查询
    const maxPage = Math.ceil(res.data.total / page.value.size) || 1;
    if (page.value.current > maxPage && maxPage > 0) {
      page.value.current = 1;
      // é‡æ–°æŸ¥è¯¢ç¬¬1页数据
      return getList();
    }
    tableData.value = res.data.records;
  })
  .catch(err => {
  }).catch(err => {
    tableLoading.value = false;
    console.error("查询知识库列表失败:", err);
  });
  })
};
// åˆ†é¡µå¤„理
@@ -824,47 +611,27 @@
const submitForm = async () => {
  try {
    await formRef.value.validate();
    // âœ… POST请求使用data传参,明确参数结构
    const formData = {
      title: form.value.title,
      type: form.value.type,
      scenario: form.value.scenario || "",
      efficiency: form.value.efficiency || "",
      problem: form.value.problem,
      solution: form.value.solution,
      keyPoints: form.value.keyPoints || "",
      creator: form.value.creator || "",
      usageCount: form.value.usageCount || 0
    };
    if (dialogType.value === "add") {
      // æ–°å¢žçŸ¥è¯†
      addKnowledgeBase(formData).then(res => {
      addKnowledgeBase({...form.value}).then(res => {
        if(res.code == 200){
          ElMessage.success("添加成功");
          closeKnowledgeDialog();
          getList();
        }
      }).catch(err => {
        console.error("添加知识库失败:", err);
        ElMessage.error(err.msg || "添加失败");
      });
        ElMessage.error(err.msg);
      })
    } else {
      // æ›´æ–°çŸ¥è¯† - æ·»åŠ id参数
      updateKnowledgeBase({
        id: form.value.id,
        ...formData
      }).then(res => {
      updateKnowledgeBase({...form.value}).then(res => {
        if(res.code == 200){
          ElMessage.success("更新成功");
          closeKnowledgeDialog();
          getList();
        }
      }).catch(err => {
        console.error("更新知识库失败:", err);
        ElMessage.error(err.msg || "更新失败");
      });
        ElMessage.error(err.msg);
      })
    }
  } catch (error) {
    console.error("表单验证失败:", error);
@@ -878,22 +645,19 @@
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    // âœ… DELETE请求使用data传递ID数组
    // console.log(selectedIds.value);
    delKnowledgeBase(selectedIds.value).then(res => {
      if(res.code == 200){
        ElMessage.success("删除成功");
        selectedIds.value = [];
        getList();
      }
    }).catch(err => {
      console.error("删除知识库失败:", err);
      ElMessage.error(err.msg || "删除失败");
    });
    })
  }).catch(() => {
    // ç”¨æˆ·å–消
  });
@@ -916,364 +680,6 @@
const handleExport = () => {
  proxy.download('/knowledgeBase/export', { ...searchForm.value }, '知识库.xlsx')
}
// ============ æ–‡ä»¶ç®¡ç†ç›¸å…³ ============
// æ‰“开文件管理弹窗
const openFilesDialog = (row) => {
  currentKnowledgeBase.value = row;
  filesDialogVisible.value = true;
  loadFileList();
};
// åŠ è½½æ–‡ä»¶åˆ—è¡¨
const loadFileList = async () => {
  if (!currentKnowledgeBase.value?.id) return;
  try {
    const res = await getVectorStatus(currentKnowledgeBase.value.id);
    fileList.value = res.data || [];
    // æ£€æŸ¥æ˜¯å¦æœ‰å¤„理中的文件,如果有则启动轮询
    const hasProcessing = res.data.some(item => item.vectorStatus === 1);
    if (hasProcessing && !vectorStatusTimer.value) {
      startVectorStatusPolling();
    } else if (!hasProcessing && vectorStatusTimer.value) {
      stopVectorStatusPolling();
    }
  } catch (error) {
    console.error("加载文件列表失败:", error);
    ElMessage.error("加载文件列表失败");
  }
};
// å¼€å§‹è½®è¯¢å‘量化状态
const startVectorStatusPolling = () => {
  vectorStatusTimer.value = setInterval(async () => {
    try {
      const res = await getVectorStatus(currentKnowledgeBase.value.id);
      fileList.value = res.data || [];
      // æ£€æŸ¥æ˜¯å¦è¿˜æœ‰å¤„理中的文件
      const hasProcessing = res.data.some(item => item.vectorStatus === 1);
      if (!hasProcessing) {
        stopVectorStatusPolling();
        ElMessage.success("所有文件向量化处理完成");
      }
    } catch (error) {
      console.error("轮询向量化状态失败:", error);
      stopVectorStatusPolling();
    }
  }, 3000); // æ¯3秒轮询一次
};
// åœæ­¢è½®è¯¢å‘量化状态
const stopVectorStatusPolling = () => {
  if (vectorStatusTimer.value) {
    clearInterval(vectorStatusTimer.value);
    vectorStatusTimer.value = null;
  }
};
// ä¸Šä¼ å‰æ ¡éªŒ
const beforeUpload = (file) => {
  const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf'];
  const fileName = file.name.toLowerCase();
  const isAllowed = allowedTypes.some(type => fileName.endsWith(type));
  if (!isAllowed) {
    ElMessage.error('只支持 txt、md、docx、xlsx、xls、pdf æ ¼å¼çš„æ–‡ä»¶');
    return false;
  }
  const isLt50M = file.size / 1024 / 1024 < 50;
  if (!isLt50M) {
    ElMessage.error('文件大小不能超过 50MB');
    return false;
  }
  return true;
};
// ä¸Šä¼ æˆåŠŸ
const handleUploadSuccess = (response, file) => {
  console.log("上传响应:", response);  // è°ƒè¯•日志
  if (response.code === 200) {
    // âœ… åŽç«¯è¿”回的是 List<StorageBlobVO>,所以data是数组
    if (Array.isArray(response.data) && response.data.length > 0) {
      // å–数组第一个元素的id
      const blobId = response.data[0].id;
      if (blobId) {
        uploadedBlobIds.value.push(blobId);
        ElMessage.success(`文件 ${file.name} ä¸Šä¼ æˆåŠŸ`);
      } else {
        console.error("上传响应中未找到id:", response.data[0]);
        ElMessage.error("上传失败: æœªèŽ·å–åˆ°æ–‡ä»¶ID");
      }
    } else {
      console.error("上传响应格式错误:", response);
      ElMessage.error("上传失败: å“åº”格式错误");
    }
  } else {
    ElMessage.error(response.msg || "上传失败");
  }
};
// ä¸Šä¼ å¤±è´¥
const handleUploadError = (error, file) => {
  ElMessage.error(`文件 ${file.name} ä¸Šä¼ å¤±è´¥`);
};
// ä¿å­˜æ–‡ä»¶å…³è”
const saveFiles = async () => {
  // å‚数校验
  if (!currentKnowledgeBase.value?.id) {
    ElMessage.error("知识库信息异常");
    return;
  }
  if (uploadedBlobIds.value.length === 0) {
    ElMessage.warning("请先上传文件");
    return;
  }
  savingFiles.value = true;
  try {
    // âœ… POST请求使用data传参,明确参数结构
    await saveKnowledgeBaseFiles({
      knowledgeBaseId: currentKnowledgeBase.value.id,  // çŸ¥è¯†åº“ID
      storageBlobIds: uploadedBlobIds.value             // æ–‡ä»¶blob ID数组
    });
    ElMessage.success("文件关联保存成功,正在后台处理向量化");
    uploadedBlobIds.value = [];
    // å»¶è¿Ÿåˆ·æ–°æ–‡ä»¶åˆ—表,给后台处理时间
    setTimeout(() => {
      loadFileList();
    }, 1000);
  } catch (error) {
    console.error("保存文件关联失败:", error);
    ElMessage.error("保存文件关联失败");
  } finally {
    savingFiles.value = false;
  }
};
// é‡æ–°å¤„理向量化的文件
const reprocessFile = async (row) => {
  try {
    await reprocessVector(row.id);
    ElMessage.success("已重新提交向量化任务");
    // å»¶è¿Ÿåˆ·æ–°
    setTimeout(() => {
      loadFileList();
    }, 1000);
  } catch (error) {
    console.error("重新处理失败:", error);
    ElMessage.error("重新处理失败");
  }
};
// æ¸…空待保存的文件列表
const clearUploadedFiles = () => {
  uploadedBlobIds.value = [];
  ElMessage.success("已清空待保存文件列表");
};
// åˆ é™¤æ–‡ä»¶
const deleteFile = async (row) => {
  try {
    await ElMessageBox.confirm(
      "确定要删除该文件吗?删除后将无法恢复向量数据",
      "删除确认",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }
    );
    // âœ… DELETE请求使用data传递ID数组
    await deleteKnowledgeBaseFile([row.id]);  // æ³¨æ„: row.id是向量记录ID,不是storageBlobId
    ElMessage.success("删除成功");
    loadFileList();
  } catch (error) {
    if (error !== 'cancel') {
      console.error("删除文件失败:", error);
      ElMessage.error("删除文件失败");
    }
  }
};
// çŠ¶æ€æ–‡æœ¬æ˜ å°„
const getStatusText = (status) => {
  const map = {
    0: '待处理',
    1: '处理中',
    2: '已完成',
    3: '失败'
  };
  return map[status] || '未知';
};
// çŠ¶æ€æ ‡ç­¾ç±»åž‹æ˜ å°„
const getStatusType = (status) => {
  const map = {
    0: 'info',
    1: 'warning',
    2: 'success',
    3: 'danger'
  };
  return map[status] || 'info';
};
// å…³é—­æ–‡ä»¶ç®¡ç†å¼¹çª—
const closeFilesDialog = () => {
  filesDialogVisible.value = false;
  currentKnowledgeBase.value = null;
  fileList.value = [];
  uploadedBlobIds.value = [];
  stopVectorStatusPolling(); // åœæ­¢è½®è¯¢
  getList(); // åˆ·æ–°ä¸»åˆ—表,更新文件数量
};
// ============ çŸ¥è¯†åº“问答相关 ============
// ç”ŸæˆUUID的fallback方案
const generateUUID = () => {
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID();
  }
  // fallback: å…¼å®¹ä¸æ”¯æŒ crypto.randomUUID çš„环境
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    const r = Math.random() * 16 | 0;
    const v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
};
// æ‰“开问答弹窗
const openChatDialog = (row) => {
  currentKnowledgeBase.value = row;
  chatDialogVisible.value = true;
  memoryId.value = generateUUID();
  messages.value = [];
  inputQuestion.value = "";
};
// å‘送消息
const sendMessage = async () => {
  // å‚数校验
  if (!inputQuestion.value.trim()) {
    ElMessage.warning("请输入问题");
    return;
  }
  if (!currentKnowledgeBase.value?.id) {
    ElMessage.error("知识库信息异常");
    return;
  }
  const question = inputQuestion.value.trim();
  // æ·»åŠ ç”¨æˆ·æ¶ˆæ¯
  messages.value.push({
    role: 'user',
    content: question
  });
  inputQuestion.value = "";
  chatLoading.value = true;
  // æ»šåŠ¨åˆ°åº•éƒ¨
  await nextTick();
  scrollToBottom();
  try {
    // âœ… æµå¼è¯·æ±‚使用Fetch API
    const response = await knowledgeChat({
      knowledgeBaseId: currentKnowledgeBase.value.id,  // çŸ¥è¯†åº“ID
      memoryId: memoryId.value,                         // ä¼šè¯ID
      question: question                                // ç”¨æˆ·é—®é¢˜
    });
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(errorText || '请求失败');
    }
    // âœ… åŽç«¯è¿”回 text/stream;charset=utf-8
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let aiContent = '';
    messages.value.push({ role: 'assistant', content: '' });
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      const text = decoder.decode(value, { stream: true });  // âœ… æ·»åŠ stream选项
      aiContent += text;
      messages.value[messages.value.length - 1].content = aiContent;
      // æ»šåŠ¨åˆ°åº•éƒ¨
      await nextTick();
      scrollToBottom();
    }
    // å¦‚æžœAI返回空内容,显示提示
    if (!aiContent.trim()) {
      messages.value[messages.value.length - 1].content = '抱歉,知识库中未找到相关内容,请尝试其他问题。';
    }
  } catch (error) {
    console.error("问答请求失败:", error);
    ElMessage.error("问答请求失败,请稍后重试");
    messages.value.push({
      role: 'assistant',
      content: '抱歉,发生了错误,请稍后重试'
    });
  } finally {
    chatLoading.value = false;
  }
};
// æ¸…空对话
const clearMessages = () => {
  ElMessageBox.confirm(
    "确定要清空所有对话记录吗?",
    "清空确认",
    {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning"
    }
  ).then(() => {
    messages.value = [];
    memoryId.value = generateUUID(); // é‡æ–°ç”Ÿæˆä¼šè¯ID
    ElMessage.success("对话已清空");
  }).catch(() => {
    // ç”¨æˆ·å–消
  });
};
// æ»šåŠ¨åˆ°åº•éƒ¨
const scrollToBottom = () => {
  if (chatMessagesRef.value) {
    chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
  }
};
// å…³é—­é—®ç­”弹窗
const closeChatDialog = () => {
  chatDialogVisible.value = false;
  currentKnowledgeBase.value = null;
  messages.value = [];
  inputQuestion.value = "";
};
</script>
<style scoped>
@@ -1349,113 +755,4 @@
  font-size: 14px;
  color: #909399;
}
/* æ–‡ä»¶ç®¡ç†æ ·å¼ */
.file-manager {
  padding: 20px 0;
}
.upload-section {
  display: flex;
  align-items: center;
}
.uploaded-list {
  margin-top: 16px;
  padding: 12px;
  background: #f0f9ff;
  border-radius: 6px;
  border: 1px solid #b3d8ff;
}
.uploaded-tip {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #409eff;
  font-size: 14px;
}
/* çŸ¥è¯†åº“问答样式 */
.knowledge-chat {
  display: flex;
  flex-direction: column;
  height: 500px;
}
.chat-header {
  margin-bottom: 16px;
}
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  background: #f5f7fa;
  border-radius: 8px;
  margin-bottom: 16px;
}
.message {
  margin-bottom: 16px;
  max-width: 80%;
}
.message.user {
  margin-left: auto;
  text-align: right;
}
.message.assistant {
  margin-right: auto;
}
.message-role {
  font-size: 12px;
  color: #909399;
  margin-bottom: 4px;
}
.message-content {
  display: inline-block;
  padding: 10px 14px;
  border-radius: 8px;
  line-height: 1.6;
  word-wrap: break-word;
  white-space: pre-wrap;
}
.message.user .message-content {
  background: #409eff;
  color: white;
}
.message.assistant .message-content {
  background: white;
  color: #303133;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.typing {
  animation: typing 1.5s infinite;
}
@keyframes typing {
  0%, 50%, 100% {
    opacity: 1;
  }
  25%, 75% {
    opacity: 0.5;
  }
}
.chat-input {
  margin-top: auto;
}
.chat-actions {
  margin-top: 8px;
  text-align: right;
}
</style>
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
@@ -84,28 +84,34 @@
                <el-row :gutter="20">
                    <el-col :span="12">
                        <el-form-item label="开始时间" prop="startTime">
                            <el-time-picker
              <el-select
                                v-model="meetingForm.startTime"
                                placeholder="请选择开始时间"
                                format="HH:mm"
                                value-format="HH:mm"
                                :disabled-hours="disabledStartHours"
                                :disabled-minutes="disabledStartMinutes"
                                style="width: 100%"
              >
                <el-option
                    v-for="time in startTimeOptions"
                    :key="time.value"
                    :label="time.label"
                    :value="time.value"
                            />
              </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="结束时间" prop="endTime">
                            <el-time-picker
              <el-select
                                v-model="meetingForm.endTime"
                                placeholder="请选择结束时间"
                                format="HH:mm"
                                value-format="HH:mm"
                                :disabled-hours="disabledEndHours"
                                :disabled-minutes="disabledEndMinutes"
                                style="width: 100%"
              >
                <el-option
                    v-for="time in endTimeOptions"
                    :key="time.value"
                    :label="time.label"
                    :value="time.value"
                            />
              </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
@@ -199,6 +205,9 @@
// å‘˜å·¥åˆ—表
const employees = ref([])
// æ—¶é—´é€‰é¡¹ï¼ˆä»¥åŠå°æ—¶ä¸ºé—´éš”)
const timeOptions = ref([])
const getTimeInMinutes = (time) => {
    if (!time) return -1
    const [hour, minute] = time.split(':').map(Number)
@@ -217,6 +226,16 @@
        callback()
        return
    }
  if (isToday(meetingForm.meetingDate)) {
    const now = new Date()
    const currentMinutes = now.getHours() * 60 + now.getMinutes()
    if (getTimeInMinutes(value) > currentMinutes) {
      callback(new Error('当天开始时间不能晚于当前时间'))
      return
    }
  }
    callback()
}
@@ -225,13 +244,16 @@
        callback()
        return
    }
    if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) {
        callback(new Error('结束时间必须大于开始时间'))
        return
    }
    callback()
}
// è¡¨å•校验规则
const rules = {
    title: [{required: true, message: '请输入会议主题', trigger: 'blur'}],
    roomId: [{required: true, message: '请选择会议室', trigger: 'change'}],
@@ -248,70 +270,77 @@
    participants: [{required: true, message: '请选择参会人员', trigger: 'change'}]
}
// æ—¶é—´é€‰æ‹©å™¨ç¦ç”¨é€»è¾‘
const disabledStartHours = () => {
    const hours = []
    for (let h = 0; h < 24; h++) {
        if (h < 8 || h > 18) hours.push(h)
const startTimeOptions = computed(() => {
  if (!isToday(meetingForm.meetingDate)) {
    return timeOptions.value
    }
    if (isToday(meetingForm.meetingDate)) {
        const now = new Date()
        for (let h = 8; h < now.getHours(); h++) {
            if (!hours.includes(h)) hours.push(h)
  const currentMinutes = now.getHours() * 60 + now.getMinutes()
  return timeOptions.value.filter(item => getTimeInMinutes(item.value) <= currentMinutes)
})
const endTimeOptions = computed(() => {
  if (!meetingForm.startTime) {
    return timeOptions.value
  }
  const startMinutes = getTimeInMinutes(meetingForm.startTime)
  return timeOptions.value.filter(item => getTimeInMinutes(item.value) > startMinutes)
})
// åˆå§‹åŒ–时间选项
const initTimeOptions = () => {
  const options = []
  const now = new Date()
  const currentHour = now.getHours()
  const currentMinute = now.getMinutes()
  // meetingDate æ˜¯ "yyyy-MM-dd"
  const meetingDate = new Date(meetingForm.meetingDate)
  const isSameDay =
    now.getFullYear() === meetingDate.getFullYear() &&
    now.getMonth() === meetingDate.getMonth() &&
    now.getDate() === meetingDate.getDate()
  console.log('是否同一天:', isSameDay)
  for (let hour = 8; hour <= 18; hour++) {
    // å¼€å§‹æ—¶é—´å¿…须晚于当前时间
    if (hour < currentHour && isSameDay) {
      continue
    }
    if (hour === currentHour && currentMinute > 30 && isSameDay) {
      continue
    }
    // æ¯ä¸ªå°æ—¶æ·»åŠ ä¸¤ä¸ªé€‰é¡¹ï¼šæ•´ç‚¹å’ŒåŠç‚¹
    options.push({
      value: `${hour.toString().padStart(2, '0')}:00`,
      label: `${hour.toString().padStart(2, '0')}:00`
    })
    if (hour < 18) { // 18:00之后没有半点选项
      options.push({
        value: `${hour.toString().padStart(2, '0')}:30`,
        label: `${hour.toString().padStart(2, '0')}:30`
      })
        }
    }
    return hours
  timeOptions.value = options
}
const disabledStartMinutes = (hour) => {
    const minutes = []
    for (let m = 0; m < 60; m++) {
        if (m !== 0 && m !== 30) minutes.push(m)
watch(() => meetingForm.meetingDate, () => {
  if (meetingForm.startTime && !startTimeOptions.value.some(item => item.value === meetingForm.startTime)) {
    meetingForm.startTime = ''
    }
    if (isToday(meetingForm.meetingDate)) {
        const now = new Date()
        if (hour === now.getHours()) {
            if (now.getMinutes() >= 30) {
                minutes.push(0)
            }
        }
    }
    return minutes
}
const disabledEndHours = () => {
    const hours = []
    for (let h = 0; h < 24; h++) {
        if (h < 8 || h > 18) hours.push(h)
  if (meetingForm.endTime && !endTimeOptions.value.some(item => item.value === meetingForm.endTime)) {
    meetingForm.endTime = ''
    }
    if (meetingForm.startTime) {
        const startHour = parseInt(meetingForm.startTime.split(':')[0])
        for (let h = 8; h < startHour; h++) {
            if (!hours.includes(h)) hours.push(h)
    meetingFormRef.value?.validateField('startTime')
        }
  if (meetingForm.endTime) {
    meetingFormRef.value?.validateField('endTime')
    }
    return hours
}
const disabledEndMinutes = (hour) => {
    const minutes = []
    for (let m = 0; m < 60; m++) {
        if (m !== 0 && m !== 30) minutes.push(m)
    }
    if (meetingForm.startTime) {
        const startHour = parseInt(meetingForm.startTime.split(':')[0])
        const startMinute = parseInt(meetingForm.startTime.split(':')[1])
        if (hour === startHour) {
            if (startMinute >= 0) minutes.push(0)
            if (startMinute >= 30) minutes.push(30)
            // only keep minutes > startMinute
            for (let m = 0; m <= startMinute; m++) {
                if (!minutes.includes(m)) minutes.push(m)
            }
        }
    }
    return minutes
}
  initTimeOptions()
})
watch(() => meetingForm.startTime, () => {
    if (meetingForm.endTime && getTimeInMinutes(meetingForm.endTime) <= getTimeInMinutes(meetingForm.startTime)) {
@@ -360,6 +389,19 @@
                
                // æ¨¡æ‹Ÿæäº¤æ“ä½œ
                ElMessage.success(`${getCurrentTypeName()}提交成功`)
        // æ ¹æ®ä¸åŒç±»åž‹æ‰§è¡Œä¸åŒæ“ä½œ
        switch (currentType.value) {
          case 'approval':
            ElMessage.info('会议已提交审批流程')
            break
          case 'department':
            ElMessage.info('部门级会议申请已提交')
            break
          case 'notification':
            ElMessage.info('会议通知已发布')
            break
        }
                resetForm()
            })
            
@@ -369,6 +411,7 @@
// é¡µé¢åŠ è½½æ—¶åˆå§‹åŒ–
onMounted(() => {
  initTimeOptions()
    getRoomEnum().then(res => {
        meetingRooms.value = res.data
    })
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
@@ -36,7 +36,7 @@
        <el-table-column prop="location" label="会议地点" align="center" width="150"/>
        <el-table-column prop="participants" label="参会人数" align="center" width="100">
          <template #default="scope">
            {{ scope.row.staffCount }}人
            {{ scope.row.participants.length }}人
          </template>
        </el-table-column>
        <el-table-column prop="status" label="审批状态" align="center" width="120">
@@ -233,9 +233,9 @@
  let resp = await getExamineList({...searchForm, ...queryParams})
  approvalList.value = resp.data.records.map(it => {
    let room = roomEnum.value.find(room => it.roomId === room.id)
    it.location = room ? `${room.name}(${room.location})` : ''
    it.location = `${room.name}(${room.location})`
    let staffs = JSON.parse(it.participants)
    it.staffCount = staffs.length
    it.staffCount = staffs.size
    it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
@@ -145,11 +145,10 @@
    if (endIdx === -1) {
      endIdx = timeSlots.value.length
    }
    // å¾€åŽå»¶ä¼¸ä¸€æ ¼ï¼Œè®©ä¼šè®®æ ¼å­è¦†ç›–到结束时间列上
    const displayEndIdx = Math.min(endIdx + 1, timeSlots.value.length)
    if (startIdx !== -1) {
    console.log('endIdx111', endIdx)
    if (startIdx !== -1 && endIdx !== -1) {
      // æ ‡è®°è¢«å ç”¨çš„æ—¶é—´æ®µ
      for (let i = startIdx; i < displayEndIdx; i++) {
      for (let i = startIdx; i < endIdx; i++) {
        occupiedSlots.add(timeSlots.value[i].value)
      }
@@ -157,7 +156,7 @@
      cells.push({
        type: 'meeting',
        meeting: meeting,
        span: displayEndIdx - startIdx,
        span: endIdx - startIdx,
        startTime: meeting.startTime,
        endTime: meeting.endTime
      })
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
@@ -34,7 +34,7 @@
        <el-table-column prop="location" label="会议地点" align="center" width="150"/>
        <el-table-column prop="participants" label="参会人数" align="center" width="100">
          <template #default="scope">
            {{ scope.row.staffCount }}人
            {{ scope.row.participants.length }}人
          </template>
        </el-table-column>
        <el-table-column prop="status" label="发布状态" align="center" width="120">
@@ -231,9 +231,9 @@
  let resp = await getMeetingPublish({...searchForm, ...queryParams})
  approvalList.value = resp.data.records.map(it => {
    let room = roomEnum.value.find(room => it.roomId === room.id)
    it.location = room ? `${room.name}(${room.location})` : ''
    it.location = `${room.name}(${room.location})`
    let staffs = JSON.parse(it.participants)
    it.staffCount = staffs.length
    it.staffCount = staffs.size
    it.status = it.publishStatus
    it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -28,7 +28,7 @@
        <el-table-column prop="location" label="会议地点" align="center" width="150" />
        <el-table-column prop="participants" label="参会人数" align="center" width="100">
          <template #default="scope">
            {{ scope.row.staffCount }}人
            {{ scope.row.participants.length }}人
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="200" fixed="right">
@@ -207,9 +207,9 @@
  let resp = await getMeetingPublish({ ...searchForm, ...queryParams })
  meetingList.value = resp.data.records.map(it => {
    let room = roomEnum.value.find(room => it.roomId === room.id)
    it.location = room ? `${room.name}(${room.location})` : ''
    it.location = `${room.name}(${room.location})`
    let staffs = JSON.parse(it.participants)
    it.staffCount = staffs.length
    it.staffCount = staffs.size
    it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
    it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => {
      return {
@@ -260,7 +260,7 @@
<p><strong>主持人:</strong>${row.host}</p>
<p><strong>参会人员:</strong></p>
<ol>
  ${(row.participants || []).map(p => `<li>${p?.name || ''}</li>`).join('')}
  ${row.participants.map(p => `<li>${p.name}</li>`).join('')}
</ol>
<p><strong>会议内容:</strong></p>
<ol>
src/views/collaborativeApproval/purchaseApproval/index.vue
@@ -278,7 +278,7 @@
    },
    rules: {
      purchaseContractNumber: [
        { required: false, message: "请输入", trigger: "blur" },
        { required: true, message: "请输入", trigger: "blur" },
      ],
      projectName: [{ required: true, message: "请输入", trigger: "blur" }],
      supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -448,7 +448,9 @@
    productData.value = [];
    fileList.value = [];
    if (operationType.value == "add") {
      form.value.purchaseContractNumber = "";
      createPurchaseNo().then(res => {
        form.value.purchaseContractNumber = res.data;
      });
    }
    userListNoPage().then(res => {
      userList.value = res.data;
@@ -519,7 +521,7 @@
  }
  // æäº¤è¡¨å•
  const submitForm = n => {
    proxy.$refs["formRef"].validate(async valid => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        if (productData.value.length > 0) {
          form.value.productData = proxy.HaveJson(productData.value);
@@ -534,21 +536,6 @@
        form.value.tempFileIds = tempFileIds;
        form.value.type = 2;
        form.value.approvalStatus = n;
        // å¦‚果采购合同号为空,则根据录入日期自动生成
        if (!form.value.purchaseContractNumber) {
          try {
            const purchaseNoRes = await createPurchaseNo(form.value.entryDate);
            if (purchaseNoRes?.data) {
              form.value.purchaseContractNumber = purchaseNoRes.data;
            }
          } catch (error) {
            console.error("生成采购合同号失败:", error);
            proxy.$modal.msgWarning("生成采购合同号失败");
            return;
          }
        }
        addOrEditPurchase(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -318,7 +318,7 @@
      formatData: (v) => (v === "active" ? "生效中" : "已废止"),
      formatType: (v) => (v === "active" ? "success" : "info"),
    },
    { label: "已读人数", prop: "readCount", width: 100 },
    // { label: "已读人数", prop: "readCount", width: 100 },
    {
      dataType: "action",
      label: "操作",
src/views/collaborativeApproval/sealManagement/index.vue
@@ -7,6 +7,7 @@
        </div>
      </template>
   <!-- ç”¨å°ç”³è¯·ç®¡ç† -->
        <div class="tab-content">
            <el-row :gutter="20" class="mb-20 ">
@@ -176,7 +177,6 @@
const sealFormRef = ref()
const userList = ref([])
const sealForm = reactive({
  id: null,
  applicationNum: '',
  title: '',
  sealType: '',
@@ -241,9 +241,9 @@
// ç”¨å°ç”³è¯·è¡¨æ ¼åˆ—配置(需在 getStatusText/getSealTypeText ç­‰ä¹‹åŽå®šä¹‰ï¼‰
const sealTableColumn = ref([
  { label: '申请编号', prop: 'applicationNum' },
  { label: '申请编号', prop: 'applicationNum',},
  { label: '申请标题', prop: 'title', showOverflowTooltip: true },
  { label: '申请人', prop: 'createUserName' },
  { label: '申请人', prop: 'createUserName', },
  { label: '所属部门', prop: 'department', width: 150 },
  {
    label: '用印类型',
@@ -261,28 +261,22 @@
    formatData: (v) => getStatusText(v),
    formatType: (v) => getStatusType(v)
  },
  { label: '审批人', prop: 'approveUserName', width: 100 },
  {
    dataType: 'action',
    label: '操作',
    width: 250,
    width: 200,
    fixed: 'right',
    align: 'center',
    operation: [
      {
        name: '审批',
        clickFun: (row) => approveSeal(row),
        showHide: (row) => row.status === 'pending' && Number(userStore.id) === row.approveUserId
        showHide: (row) => row.status === 'pending'
      },
      {
        name: '拒绝',
        clickFun: (row) => rejectSeal(row),
        showHide: (row) => row.status === 'pending' && Number(userStore.id) === row.approveUserId
      },
      {
        name: '重新申请',
        clickFun: (row) => reapplySeal(row),
        showHide: (row) => row.status === 'rejected' && Number(userStore.id) === row.createUser
        showHide: (row) => row.status === 'pending'
      },
      { name: '详情', clickFun: (row) => viewSealDetail(row) }
    ]
@@ -293,6 +287,8 @@
const searchSealApplications = () => {
  page.current = 1
  getSealApplicationList()
  // ElMessage.success('搜索完成')
}
// é‡ç½®å°ç« ç”³è¯·æœç´¢
const resetSealSearch = () => {
@@ -301,36 +297,16 @@
  sealSearchForm.applicationNum = ''
  searchSealApplications()
}
// é‡æ–°ç”³è¯·ç”¨å°
const reapplySeal = (row) => {
  // é¢„填表单数据
  Object.assign(sealForm, {
    id: row.id,
    applicationNum: row.applicationNum,
    title: row.title,
    sealType: row.sealType,
    reason: row.reason,
    approveUserId: row.approveUserId,
    urgency: row.urgency || 'normal',
    status: 'pending',
    storageBlobDTOs: row.storageBlobVOList || []
  })
  showSealApplyDialog.value = true
}
// æäº¤ç”¨å°ç”³è¯·
const submitSealApplication = async () => {
  try {
    await sealFormRef.value.validate()
    const request = sealForm.id ? updateSealApplication : addSealApplication
    request(sealForm).then(res => {
    addSealApplication(sealForm).then(res => {
      if (res.code == 200) {
        ElMessage.success(sealForm.id ? '重新申请成功' : '申请提交成功')
        ElMessage.success('申请提交成功')
        closeSealApplyDialog()
        getSealApplicationList()
        Object.assign(sealForm, {
          id: null,
          applicationNum: '',
          title: '',
          sealType: '',
@@ -344,15 +320,14 @@
    }).catch(err => {
      console.log(err.msg)
    })
  } catch (error) {
  }
}
// å…³é—­ç”¨å°ç”³è¯·å¯¹è¯æ¡†
const closeSealApplyDialog = () => {
  // æ¸…空表单数据
  Object.assign(sealForm, {
    id: null,
    applicationNum: '',
    title: '',
    sealType: '',
@@ -424,7 +399,6 @@
    inputErrorMessage: '拒绝原因不能为空'
  }).then(({ value }) => {
    row.status = 'rejected'
    row.reason = value
    updateSealApplication(row).then(res => {
      if (res.code == 200) {
        ElMessage.success('已拒绝申请')
src/views/customerService/afterSalesHandling/components/formDia.vue
@@ -82,6 +82,33 @@
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="评分:" prop="rating">
                            <el-rate
                                v-model="form.rating"
                                :disabled="operationType === 'view'"
                                :max="5"
                                show-text
                                :texts="['非常不满意', '不满意', '一般', '满意', '非常满意']"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="24">
                        <el-form-item label="评价内容描述:" prop="evaluation">
                            <el-input
                                v-model="form.evaluation"
                                placeholder="请输入评价内容描述"
                                clearable
                                :disabled="operationType === 'view'"
                                type="textarea"
                                :rows="3"
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <el-row :gutter="30">
                <el-col :span="12">
@@ -148,6 +175,8 @@
        disposeUserId: "",
        disDate: "",
        disRes: "",
        rating: null,
        evaluation: "",
    },
    rules: {
        feedbackDate: [{required: true, message: "请选择", trigger: "change"}],
src/views/customerService/afterSalesHandling/index.vue
@@ -102,6 +102,7 @@
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    <view-dia ref="viewDia"></view-dia>
    <FileList v-if="fileDialogVisible"  v-model:visible="fileDialogVisible" record-type="after_sales_service" :record-id="recordId"  />
  </div>
</template>
@@ -109,6 +110,7 @@
<script setup>
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, defineAsyncComponent} from "vue";
import FormDia from "@/views/customerService/afterSalesHandling/components/formDia.vue";
import ViewDia from "@/views/customerService/components/viewDia.vue";
import { ElMessageBox } from "element-plus";
import {
    afterSalesServiceListPage,
@@ -214,6 +216,15 @@
    align: "center"
  },
    {
    label: "数量",
    prop: "productModelQuantities",
    align: "center",
    formatData: params => {
      if (!params) return "0";
      return String(params).split(',').reduce((sum, val) => sum + (Number(val) || 0), 0);
    }
  },
    {
    label: "问题描述",
    prop: "proDesc",
    width:300,
@@ -247,10 +258,10 @@
                }
            },
            {
                name: "查看",
                name: "详情",
                type: "text",
                clickFun: (row) => {
                    openForm("view", row);
                    viewDia.value?.openDialog(row);
                },
            },
            // TODO ä¸ºå†™æŠ¥å‘Šæ·»åŠ çš„
@@ -278,6 +289,7 @@
    selectedRows.value = selection;
};
const formDia = ref()
const viewDia = ref()
const fileListRef = ref(null)
const fileListDialogVisible = ref(false)
const currentFileRow = ref(null)
src/views/customerService/components/viewDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
<template>
  <el-dialog v-model="dialogVisible" title="售后单详情" width="80%" @close="closeDia">
    <div v-loading="loading">
      <span class="descriptions">基础资料</span>
      <el-descriptions :column="4" border style="margin-top: 10px;">
        <el-descriptions-item label="客户名称">{{ detail.customerName || '-' }}</el-descriptions-item>
        <el-descriptions-item label="售后类型">{{ getDictLabel(classificationOptions, detail.serviceType) }}</el-descriptions-item>
        <el-descriptions-item label="关联销售单号">{{ detail.salesContractNo || '-' }}</el-descriptions-item>
        <el-descriptions-item label="紧急程度">{{ getDictLabel(degreeOfUrgencyOptions, detail.urgency) }}</el-descriptions-item>
        <el-descriptions-item label="工单编号">{{ detail.afterSalesServiceNo || '-' }}</el-descriptions-item>
        <el-descriptions-item label="处理状态">
          <el-tag :type="detail.status === 1 ? 'danger' : 'success'" size="small">
            {{ detail.status === 1 ? '待处理' : '已处理' }}
          </el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="登记人">{{ detail.checkNickName || '-' }}</el-descriptions-item>
        <el-descriptions-item label="反馈日期">{{ detail.feedbackDate || '-' }}</el-descriptions-item>
        <el-descriptions-item label="客户诉求" :span="4">{{ detail.proDesc || '-' }}</el-descriptions-item>
      </el-descriptions>
      <div style="margin-top: 20px;">
        <span class="descriptions">处理信息</span>
        <el-descriptions :column="3" border style="margin-top: 10px;">
          <el-descriptions-item label="处理人">{{ detail.disposeNickName || '-' }}</el-descriptions-item>
          <el-descriptions-item label="处理日期">{{ detail.disDate || '-' }}</el-descriptions-item>
          <el-descriptions-item label="处理结果" :span="3">{{ detail.disRes || '-' }}</el-descriptions-item>
        </el-descriptions>
      </div>
      <div style="margin-top: 20px;">
        <span class="descriptions">关联产品</span>
        <el-table :data="tableData" border style="width: 100%; margin-top: 10px;">
          <el-table-column type="index" label="序号" width="60" align="center" />
          <el-table-column prop="productCategory" label="产品大类" align="center" />
          <el-table-column prop="specificationModel" label="规格型号" align="center" />
          <el-table-column prop="unit" label="单位" align="center" />
          <el-table-column prop="expressCompany" label="快递公司" align="center" />
          <el-table-column prop="expressNumber" label="快递单号" align="center" />
          <el-table-column prop="shippingCarNumber" label="发货车牌" align="center" />
          <el-table-column prop="shippingDate" label="发货日期" align="center" />
          <el-table-column prop="quantity" label="售后数量" align="center" />
        </el-table>
      </div>
    </div>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="closeDia">关闭</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref, computed, getCurrentInstance } from 'vue';
import { getAfterSalesServiceById } from '@/api/customerService/index.js';
const { proxy } = getCurrentInstance();
const dialogVisible = ref(false);
const loading = ref(false);
const detail = ref({});
const tableData = ref([]);
const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict(
  "post_sale_waiting_list",
  "degree_of_urgency"
);
const classificationOptions = computed(() => post_sale_waiting_list?.value || []);
const degreeOfUrgencyOptions = computed(() => degree_of_urgency?.value || []);
const getDictLabel = (options, value) => {
  if (!value) return '-';
  const item = options.find(i => String(i.value) === String(value));
  return item ? item.label : value;
};
const openDialog = (row) => {
  dialogVisible.value = true;
  loading.value = true;
  detail.value = {};
  tableData.value = [];
  getAfterSalesServiceById(row.id).then(res => {
    loading.value = false;
    if (res.code === 200) {
      detail.value = res.data || {};
      let productData = res.data?.salesLedgerDto?.productData || [];
      const selectedIds = res.data.productModelIds ? String(res.data.productModelIds).split(",") : [];
      tableData.value = productData.filter(item => selectedIds.includes(String(item.id)));
    }
  }).catch(() => {
    loading.value = false;
  });
};
const closeDia = () => {
  dialogVisible.value = false;
};
defineExpose({
  openDialog
});
</script>
<style scoped>
.descriptions {
  display: inline-block;
  font-size: 1rem;
  font-weight: 600;
  padding-left: 12px;
  position: relative;
}
.descriptions::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 1rem;
  background-color: #002fa7;
  border-radius: 2px;
}
</style>
src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -97,6 +97,9 @@
                {{ getShippingStatusText(row) }}
              </el-tag>
            </template>
            <template #quantity="{ row }">
              <el-input-number v-model="row.quantity" :min="0" size="small" />
            </template>
          </PIMTable>
        </div>
      </div>
@@ -147,6 +150,7 @@
      salesContractNo: "",
      proDesc: "",
      customerName: "",
      productModelQuantities: "",
    },
    rules: {
      customerName: [
@@ -240,25 +244,12 @@
      align: "center",
    },
    { label: "发货日期", prop: "shippingDate", minWidth: 100, align: "center" },
    { label: "数量", prop: "quantity", width: 100 },
    { label: "税率(%)", prop: "taxRate", width: 100 },
    {
      label: "含税单价(元)",
      prop: "taxInclusiveUnitPrice",
      width: 160,
      formatData: formatCurrency,
    },
    {
      label: "含税总价(元)",
      prop: "taxInclusiveTotalPrice",
      width: 160,
      formatData: formatCurrency,
    },
    {
      label: "不含税总价(元)",
      prop: "taxExclusiveTotalPrice",
      width: 160,
      formatData: formatCurrency,
      label: "数量",
      prop: "quantity",
      width: 140,
      dataType: "slot",
      slot: "quantity",
    },
    {
      dataType: "action",
@@ -326,7 +317,7 @@
      form.value.customerId = null;
    }
    getSalesLedger({
      customerName: form.value.customerName,
      customerId: form.value.customerId,
    }).then(res => {
      if (res.code === 200) {
        associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({
@@ -434,7 +425,7 @@
      form.value = { ...row };
      if (form.value.customerName) {
        const res = await getSalesLedger({
          customerName: form.value.customerName,
          customerId: form.value.customerId,
        });
        if (res?.code === 200) {
          console.log(res);
@@ -446,6 +437,21 @@
              id: item.id,
            })
          );
          const opt = associatedSalesOrderNumberOptions.value.find(
            item => item.value === form.value.salesContractNo
          );
          if (opt) {
            let restoredData = (opt.productData || []).map(normalizeProductRow);
            const selectedIds = form.value.productModelIds ? String(form.value.productModelIds).split(",") : [];
            const quantities = form.value.productModelQuantities ? String(form.value.productModelQuantities).split(",") : [];
            tableData.value = restoredData.filter(item => selectedIds.includes(String(item.id))).map(item => {
              let qIndex = selectedIds.indexOf(String(item.id));
              if (qIndex !== -1 && qIndex < quantities.length && quantities[qIndex] !== "") {
                 item.quantity = Number(quantities[qIndex]);
              }
              return item;
            });
          }
        }
      }
      console.log(form.value);
@@ -458,6 +464,9 @@
        form.value.productModelIds = tableData.value
          .map(item => item.id)
          .join(",");
        form.value.productModelQuantities = tableData.value
          .map(item => item.quantity || 0)
          .join(",");
        if (operationType.value === "add") {
          afterSalesServiceAdd(form.value).then(response => {
            proxy.$modal.msgSuccess("新增成功");
src/views/customerService/feedbackRegistration/index.vue
@@ -112,6 +112,7 @@
    </div>
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <view-dia ref="viewDia"></view-dia>
  </div>
</template>
@@ -126,6 +127,7 @@
    nextTick,
  } from "vue";
  import FormDia from "@/views/customerService/feedbackRegistration/components/formDia.vue";
  import ViewDia from "@/views/customerService/components/viewDia.vue";
  import { ElMessageBox } from "element-plus";
  import {
    afterSalesServiceDelete,
@@ -149,14 +151,14 @@
    {
      icon: markRaw(FolderOpened),
      count: 0,
      label: "已处理",
      label: "待处理",
      color: "#ff9a2e",
      bgColor: "#fff5e6",
    },
    {
      icon: markRaw(UserFilled),
      count: 0,
      label: "已完成",
      label: "已处理",
      color: "#00b42a",
      bgColor: "#e6f7ed",
    },
@@ -255,6 +257,15 @@
      align: "center",
    },
    {
      label: "数量",
      prop: "productModelQuantities",
      align: "center",
      formatData: params => {
        if (!params) return "0";
        return String(params).split(',').reduce((sum, val) => sum + (Number(val) || 0), 0);
      }
    },
    {
      label: "客户诉求",
      prop: "proDesc",
      width: 300,
@@ -269,7 +280,16 @@
      dataType: "action",
      label: "操作",
      fixed: "right",
      width: 180,
      align: "center",
      operation: [
        {
          name: "详情",
          type: "text",
          clickFun: row => {
            viewDia.value?.openDialog(row);
          },
        },
        {
          name: "编辑",
          type: "text",
@@ -307,6 +327,7 @@
    selectedRows.value = selection;
  };
  const formDia = ref();
  const viewDia = ref();
  // å­—典获取
  /*
@@ -424,8 +445,8 @@
      if (res.code === 200) {
        const statsData = Array.isArray(res.data) ? res.data : [];
        statsList.value[0].count = getStatsCountByStatus(statsData, 3);
        statsList.value[1].count = getStatsCountByStatus(statsData, 2);
        statsList.value[2].count = getStatsCountByStatus(statsData, 1);
        statsList.value[1].count = getStatsCountByStatus(statsData, 1);
        statsList.value[2].count = getStatsCountByStatus(statsData, 2);
      }
    });
  };
src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -237,6 +237,14 @@
    form.value = {...row}
    form.value.inspector = form.value.inspectorIds.split(',').map(Number)
    
    // å¤„理WEEKLY频率的回显:将frequencyDetail拆分为week和time
    if (form.value.frequencyType === 'WEEKLY' && form.value.frequencyDetail) {
      const parts = form.value.frequencyDetail.split(',')
      form.value.week = parts[0] || ''
      form.value.time = parts[1] || ''
      form.value.frequencyDetail = ''
    }
    // å¦‚果有设备ID,自动设置设备信息
    if (form.value.taskId) {
      setDeviceModel(form.value.taskId);
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
@@ -1,117 +1,131 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    title="上传巡检记录"
  <FormDialog v-model="dialogVisible"
              :title="operationType === 'view' ? '巡检记录详情' : '上传巡检记录'"
    width="980px"
              :operation-type="operationType"
    @close="handleClose"
    @cancel="handleClose"
  >
              @cancel="handleClose">
    <main class="upload-content">
      <el-card v-if="taskInfo" class="section-card">
        <el-descriptions :column="1" border>
      <el-card v-if="taskInfo"
               class="section-card">
        <el-descriptions :column="2"
                         border>
          <el-descriptions-item label="巡检任务名称">
            {{ taskInfo.taskName || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="巡检项目">
            {{ taskInfo.inspectionProject || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="执行巡检人">
            <template v-if="formattedInspector && formattedInspector.length">
              <el-tag v-for="tag in formattedInspector"
                      :key="tag"
                      size="small"
                      style="margin-right: 4px">
                {{ tag }}
              </el-tag>
            </template>
            <span v-else>--</span>
          </el-descriptions-item>
          <el-descriptions-item label="频次">
            {{ formatFrequencyType(taskInfo.frequencyType) || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="开始日期与时间">
            {{ formatFrequencyDetail(taskInfo.frequencyDetail) || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="登记人">
            {{ taskInfo.registrant || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="登记日期">
            {{ formatDateTime(taskInfo.createTime) || "-" }}
          </el-descriptions-item>
          <el-descriptions-item label="备注">
            {{ taskInfo.remarks || "-" }}
          </el-descriptions-item>
        </el-descriptions>
      </el-card>
      <el-card class="section-card">
        <h3>巡检状态</h3>
        <el-radio-group v-model="hasException">
        <el-radio-group v-model="hasException"
                        :disabled="operationType === 'view'">
          <el-radio-button :value="false">正常</el-radio-button>
          <el-radio-button :value="true">存在异常</el-radio-button>
        </el-radio-group>
      </el-card>
      <el-card v-if="hasException === true" class="section-card">
      <el-card v-if="hasException === true"
               class="section-card">
        <h3>异常描述</h3>
        <el-input
          v-model="abnormalDescription"
        <el-input v-model="abnormalDescription"
          type="textarea"
          maxlength="500"
          show-word-limit
          :rows="4"
          placeholder="请描述异常情况..."
        />
                  :disabled="operationType === 'view'"
                  placeholder="请描述异常情况..." />
      </el-card>
      <el-card v-if="hasException === true" class="section-card">
        <el-tabs v-model="currentUploadType">
          <el-tab-pane label="生产前" name="before" />
          <el-tab-pane label="生产中" name="after" />
          <el-tab-pane label="生产后" name="issue" />
        </el-tabs>
        <div class="upload-buttons">
          <el-upload
            :show-file-list="false"
      <el-card v-if="hasException === true"
               class="section-card">
        <div class="upload-buttons"
             v-if="operationType !== 'view'">
          <el-upload :show-file-list="false"
            :http-request="uploadFile"
            :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
            accept="image/*"
          >
            <el-button type="primary" :loading="uploading">
              <el-icon><Camera /></el-icon>
                     :disabled="beforeModelValue.length >= uploadConfig.limit || uploading"
                     accept="image/*">
            <el-button type="primary"
                       :loading="uploading">
              <el-icon>
                <Camera />
              </el-icon>
              é€‰æ‹©å›¾ç‰‡
            </el-button>
          </el-upload>
          <el-upload
            :show-file-list="false"
          <el-upload :show-file-list="false"
            :http-request="uploadFile"
            :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
            accept="video/*"
          >
            <el-button type="success" :loading="uploading">
              <el-icon><VideoCamera /></el-icon>
                     :disabled="beforeModelValue.length >= uploadConfig.limit || uploading"
                     accept="video/*">
            <el-button type="success"
                       :loading="uploading">
              <el-icon>
                <VideoCamera />
              </el-icon>
              é€‰æ‹©è§†é¢‘
            </el-button>
          </el-upload>
        </div>
        <el-progress
          v-if="uploading"
        <el-progress v-if="uploading"
          :percentage="uploadProgress"
          class="upload-progress"
        />
        <div v-if="getCurrentFiles().length" class="file-list">
          <div
            v-for="(file, index) in getCurrentFiles()"
                     class="upload-progress" />
        <div v-if="beforeModelValue.length"
             class="file-list">
          <div v-for="(file, index) in beforeModelValue"
            :key="file.uid || file.id || index"
            class="file-item"
          >
               class="file-item">
            <div class="file-preview-container">
              <el-image
                v-if="file.type === 'image' || !file.type"
              <el-image v-if="file.type === 'image' || !file.type"
                :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
                fit="cover"
                class="file-preview"
                :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]"
                preview-teleported
              />
              <div v-else class="video-preview" @click="previewVideo(file)">
                <el-icon><VideoCamera /></el-icon>
                        preview-teleported />
              <div v-else
                   class="video-preview"
                   @click="previewVideo(file)">
                <el-icon>
                  <VideoCamera />
                </el-icon>
                <span>视频</span>
              </div>
              <el-button
                class="delete-btn"
              <el-button class="delete-btn"
                type="danger"
                circle
                size="small"
                @click="removeFile(index)"
              >
                <el-icon><Close /></el-icon>
                         v-if="operationType !== 'view'"
                         @click="removeFile(index)">
                <el-icon>
                  <Close />
                </el-icon>
              </el-button>
            </div>
            <div class="file-info">
              <div class="file-name">
                {{ file.bucketFilename || file.name || (file.type === "image" ? "图片" : "视频") }}
@@ -120,51 +134,36 @@
            </div>
          </div>
        </div>
        <el-empty
          v-else
          :description="`请选择要上传的${getUploadTypeText()}图片或视频`"
        />
        <el-alert
          class="upload-summary"
          type="info"
          :closable="false"
          :title="`生产前:${beforeModelValue.length}个文件 | ç”Ÿäº§ä¸­ï¼š${afterModelValue.length}个文件 | ç”Ÿäº§åŽï¼š${issueModelValue.length}个文件`"
        />
        <el-empty v-else
                  :description="operationType === 'view' ? '暂无异常照片或视频' : '请选择要上传的巡检图片或视频'" />
      </el-card>
      <el-result
        v-if="hasException === false"
      <el-result v-if="hasException === false"
        icon="success"
        title="设备运行正常"
        sub-title="无需上传照片"
      />
                 :sub-title="operationType === 'view' ? '' : '无需上传照片'" />
    </main>
    <template #footer>
      <footer class="footer-buttons">
        <el-button type="primary" @click="submitUpload">提交</el-button>
        <el-button v-if="hasException === true" type="warning" @click="goToRepair">
        <el-button type="primary"
                   v-if="operationType !== 'view'"
                   @click="submitUpload">提交</el-button>
        <el-button v-if="hasException === true && operationType !== 'view'"
                   type="warning"
                   @click="goToRepair">
          æ–°å¢žæŠ¥ä¿®
        </el-button>
        <el-button @click="handleClose">取消</el-button>
        <el-button @click="handleClose">{{ operationType === 'view' ? '关闭' : '取消' }}</el-button>
      </footer>
    </template>
  </FormDialog>
  <el-dialog
    v-model="showVideoDialog"
  <el-dialog v-model="showVideoDialog"
    :title="currentVideoFile?.originalFilename || currentVideoFile?.name || '视频预览'"
    width="720px"
  >
    <video
      v-if="currentVideoFile"
             width="720px">
    <video v-if="currentVideoFile"
      :src="currentVideoFile.url || currentVideoFile.downloadUrl"
      class="video-player"
      controls
      autoplay
    />
           autoplay />
  </el-dialog>
</template>
@@ -174,6 +173,7 @@
import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
import { Camera, Close, VideoCamera } from "@element-plus/icons-vue";
import axios from "axios";
  import dayjs from "dayjs";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { uploadInspectionTask } from "@/api/inspectionManagement/index.js";
import { getToken } from "@/utils/auth";
@@ -185,12 +185,54 @@
const taskInfo = ref(null);
const uploading = ref(false);
const uploadProgress = ref(0);
  const operationType = ref("add"); // add, view
const beforeModelValue = ref([]);
const afterModelValue = ref([]);
const issueModelValue = ref([]);
const currentUploadType = ref("before");
  const formattedInspector = computed(() => {
    if (!taskInfo.value?.inspector) return [];
    if (Array.isArray(taskInfo.value.inspector)) return taskInfo.value.inspector;
    if (typeof taskInfo.value.inspector === "string") {
      return taskInfo.value.inspector
        .split(",")
        .map(s => s.trim())
        .filter(s => s);
    }
    return [taskInfo.value.inspector];
  });
  const formatFrequencyType = type => {
    const mapping = {
      DAILY: "每日",
      WEEKLY: "每周",
      MONTHLY: "每月",
      QUARTERLY: "季度",
    };
    return mapping[type] || type;
  };
  const formatFrequencyDetail = detail => {
    if (typeof detail !== "string") return detail;
    const replacements = {
      MON: "周一",
      TUE: "周二",
      WED: "周三",
      THU: "周四",
      FRI: "周五",
      SAT: "周六",
      SUN: "周日",
    };
    return detail.replace(
      /MON|TUE|WED|THU|FRI|SAT|SUN/g,
      match => replacements[match]
    );
  };
  const formatDateTime = date => {
    if (!date) return "-";
    return dayjs(date).format("YYYY-MM-DD");
  };
const hasException = ref(null);
const abnormalDescription = ref("");
@@ -245,7 +287,9 @@
    return {
      ...item,
      url: processFileUrl(item.url || item.previewURL || item.downloadUrl || item.path || ""),
        url: processFileUrl(
          item.url || item.previewURL || item.downloadUrl || item.path || ""
        ),
      downloadUrl: processFileUrl(
        item.downloadUrl || item.url || item.previewURL || item.path || ""
      ),
@@ -263,18 +307,17 @@
const resetState = () => {
  taskInfo.value = null;
  beforeModelValue.value = [];
  afterModelValue.value = [];
  issueModelValue.value = [];
  currentUploadType.value = "before";
  hasException.value = null;
  abnormalDescription.value = "";
  uploading.value = false;
  uploadProgress.value = 0;
  showVideoDialog.value = false;
  currentVideoFile.value = null;
    operationType.value = "add";
};
const openDialog = row => {
  const openDialog = (type, row) => {
    operationType.value = type || "add";
  const raw = JSON.parse(JSON.stringify(row?.__raw || row || {}));
  taskInfo.value = raw;
@@ -282,29 +325,21 @@
    raw.commonFileListBeforeVO || raw.commonFileListBefore || [],
    "image"
  );
  afterModelValue.value = normalizeList(
    raw.commonFileListVO || raw.commonFileList || [],
    "image"
  );
  issueModelValue.value = normalizeList(
    raw.commonFileListAfterVO || raw.commonFileListAfter || [],
    "image"
  );
  abnormalDescription.value = raw.abnormalDescription || "";
  if (raw.hasException !== undefined && raw.hasException !== null) {
    hasException.value = raw.hasException;
  } else if (raw.inspectionResult !== undefined && raw.inspectionResult !== null) {
    } else if (
      raw.inspectionResult !== undefined &&
      raw.inspectionResult !== null
    ) {
    hasException.value = String(raw.inspectionResult) === "0";
  } else {
    hasException.value = null;
  }
  if (
    hasException.value !== true &&
    (beforeModelValue.value.length || afterModelValue.value.length || issueModelValue.value.length)
  ) {
    if (hasException.value !== true && beforeModelValue.value.length) {
    hasException.value = true;
  }
@@ -317,27 +352,6 @@
  emit("closeDia");
};
const getCurrentFiles = () => {
  if (currentUploadType.value === "before") return beforeModelValue.value;
  if (currentUploadType.value === "after") return afterModelValue.value;
  if (currentUploadType.value === "issue") return issueModelValue.value;
  return [];
};
const getUploadTypeText = () => {
  if (currentUploadType.value === "before") return "生产前";
  if (currentUploadType.value === "after") return "生产中";
  if (currentUploadType.value === "issue") return "生产后";
  return "";
};
const getTabType = () => {
  if (currentUploadType.value === "before") return 10;
  if (currentUploadType.value === "after") return 11;
  if (currentUploadType.value === "issue") return 12;
  return 10;
};
const previewVideo = file => {
  currentVideoFile.value = file;
  showVideoDialog.value = true;
@@ -346,14 +360,16 @@
const uploadFile = async uploadRequest => {
  const rawFile = uploadRequest.file;
  if (getCurrentFiles().length >= uploadConfig.limit) {
    if (beforeModelValue.value.length >= uploadConfig.limit) {
    ElMessage.warning(`最多只能选择${uploadConfig.limit}个文件`);
    return;
  }
  const ext = rawFile.name.split(".").pop()?.toLowerCase();
  if (!uploadConfig.fileType.includes(ext)) {
    ElMessage.warning(`文件格式不支持,请上传 ${uploadConfig.fileType.join("/")} æ ¼å¼`);
      ElMessage.warning(
        `文件格式不支持,请上传 ${uploadConfig.fileType.join("/")} æ ¼å¼`
      );
    return;
  }
@@ -370,7 +386,7 @@
  const formData = new FormData();
  formData.append("files", rawFile);
  formData.append("type", getTabType());
    formData.append("type", 10); // ç”Ÿäº§å‰å›ºå®šä¸º10
  uploading.value = true;
  uploadProgress.value = 0;
@@ -397,7 +413,10 @@
    const finalUrl = processFileUrl(
      resultData.url || resultData.previewURL || resultData.downloadUrl || ""
    );
    const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename;
      const finalName =
        resultData.name ||
        resultData.originalFilename ||
        resultData.bucketFilename;
    const finalId = resultData.tempId || resultData.id || resultData.tempFileId;
    const uploadedFile = {
@@ -413,7 +432,7 @@
      uid: `${Date.now()}-${Math.random()}`,
    };
    getCurrentFiles().push(uploadedFile);
      beforeModelValue.value.push(uploadedFile);
    ElMessage.success("上传成功");
  } catch (error) {
    ElMessage.error(error?.message || "上传失败");
@@ -444,10 +463,7 @@
  }
  if (hasException.value === true) {
    const totalFiles =
      beforeModelValue.value.length +
      afterModelValue.value.length +
      issueModelValue.value.length;
      const totalFiles = beforeModelValue.value.length;
    if (!totalFiles) {
      ElMessage.warning("请上传异常照片或视频");
@@ -466,11 +482,7 @@
  });
  try {
    const allFiles = [
      ...beforeModelValue.value,
      ...afterModelValue.value,
      ...issueModelValue.value,
    ];
      const allFiles = [...beforeModelValue.value];
    const tempFileIds = allFiles
      .map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
@@ -493,8 +505,8 @@
    const submitData = {
      ...baseTaskInfo,
      commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
      commonFileListDTO: afterModelValue.value.map(buildFileItem),
      commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
        commonFileListDTO: [],
        commonFileListAfterDTO: [],
      hasException: hasException.value,
      inspectionResult: hasException.value ? 0 : 1,
      abnormalDescription: abnormalDescription.value,
@@ -524,7 +536,7 @@
    await ElMessageBox.confirm("确定要删除这个文件吗?", "确认删除", {
      type: "warning",
    });
    getCurrentFiles().splice(index, 1);
      beforeModelValue.value.splice(index, 1);
  } catch {}
};
@@ -537,12 +549,12 @@
    hasException: hasException.value,
    inspectionResult: hasException.value ? 0 : 1,
    commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
    commonFileListDTO: afterModelValue.value.map(buildFileItem),
    commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
      commonFileListDTO: [],
      commonFileListAfterDTO: [],
    uploadedFiles: {
      before: beforeModelValue.value,
      after: afterModelValue.value,
      issue: issueModelValue.value,
        after: [],
        issue: [],
    },
  };
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,37 +1,35 @@
<template>
  <div>
    <el-dialog title="查看附件" v-model="dialogVisitable" width="800px" @close="cancel">
    <el-dialog title="查看附件"
               v-model="dialogVisitable"
               width="800px"
               @close="cancel">
      <div class="upload-container">
        <div class="form-container">
          <div class="title">生产前</div>
          <div class="title">图片列表</div>
          <div class="media-list">
            <img
              v-for="(item, index) in beforeProductionImgs"
            <img v-for="(item, index) in beforeProductionImgs"
              :key="`before-img-${index}`"
              :src="item"
              alt=""
              class="media-image"
              @click="showMedia(beforeProductionImgs, index, 'image')"
            />
                 @click="showMedia(beforeProductionImgs, index, 'image')" />
          </div>
          <div class="media-list">
            <div
              v-for="(videoUrl, index) in beforeProductionVideos"
            <div v-for="(videoUrl, index) in beforeProductionVideos"
              :key="`before-video-${index}`"
              class="video-item"
              @click="showMedia(beforeProductionVideos, index, 'video')"
            >
                 @click="showMedia(beforeProductionVideos, index, 'video')">
              <div class="video-thumb">
                <img src="@/assets/images/video.png" alt="播放" class="video-icon" />
                <img src="@/assets/images/video.png"
                     alt="播放"
                     class="video-icon" />
              </div>
              <div class="video-text">点击播放</div>
            </div>
          </div>
        </div>
        <div class="form-container">
        <!-- <div class="form-container">
          <div class="title">生产中</div>
          <div class="media-list">
@@ -87,22 +85,25 @@
              <div class="video-text">点击播放</div>
            </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'"
    <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"
        />
        <div v-else-if="mediaType === 'video'" class="video-player-wrap">
          <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" />
                           @hide="closeMediaViewer" />
        <div v-else-if="mediaType === 'video'"
             class="video-player-wrap">
          <video :src="mediaList[currentMediaIndex]"
                 autoplay
                 controls
                 class="video-player" />
        </div>
      </div>
    </div>
src/views/equipmentManagement/inspectionManagement/index.vue
@@ -197,7 +197,7 @@
      formatData: cell => {
        if (!cell) return "-";
        try {
          return dayjs(cell).format("YYYY-MM-DD HH:mm:ss");
          return dayjs(cell).format("YYYY-MM-DD");
        } catch {
          return cell;
        }
@@ -231,6 +231,12 @@
      operation: operations
        .map(op => {
          switch (op) {
            case "view":
              return {
                name: "详情",
                clickFun: openViewDialog,
                color: "#409EFF",
              };
            case "edit":
              return {
                name: "编辑",
@@ -273,14 +279,14 @@
      ];
      operationsArr.value = ["edit"];
    } else if (value === "task") {
      const operationColumn = getOperationColumn(["upload", "viewFile"]);
      const operationColumn = getOperationColumn(["view", "upload", "viewFile"]);
      // å·¡æ£€è®°å½•不展示"是否启用"列
      const taskColumns = columns.value.filter(col => col.prop !== "isEnabled");
      tableColumns.value = [
        ...taskColumns,
        ...(operationColumn ? [operationColumn] : []),
      ];
      operationsArr.value = ["upload", "viewFile"];
      operationsArr.value = ["view", "upload", "viewFile"];
    }
    pageNum.value = 1;
    pageSize.value = 10;
@@ -377,7 +383,13 @@
  const openUploadDialog = row => {
    nextTick(() => {
      uploadFiles.value?.openDialog(row);
      uploadFiles.value?.openDialog("add", row);
    });
  };
  const openViewDialog = row => {
    nextTick(() => {
      uploadFiles.value?.openDialog("view", row);
    });
  };
src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -49,9 +49,9 @@
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修报修项目">
          <el-form-item label="报修项目">
            <el-input v-model="form.machineryCategory"
                      placeholder="请输入报修报修项目"
                      placeholder="请输入报修项目"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
src/views/equipmentManagement/spareParts/index.vue
@@ -29,6 +29,7 @@
                        :tableLoading="loading"
                        :page="pagination"
                        :isShowPagination="true"
                        :row-class-name="rowClassName"
                        @pagination="handleSizeChange"
                    >
                        <template #status="{ row }">
@@ -95,7 +96,7 @@
        </el-dialog>
      </el-tab-pane>
      <el-tab-pane label="备件领用记录" name="usage">
      <el-tab-pane label="备件使用记录" name="usage">
        <div class="search_form">
          <el-form :inline="true" :model="usageQuery" class="search-form">
            <el-form-item label="备件名称">
@@ -521,9 +522,17 @@
onMounted(() => {
  fetchListData();
});
// æ ¹æ®æ•°é‡è¿›è¡Œåˆ¤æ–­èƒŒæ™¯é¢œè‰²
const rowClassName = ({ row }) => {
  const num = Number(row.quantity);
  if (num <= 1) return 'row-qty-low';
  if (num >= 8) return 'row-qty-high';
  return 'row-qty-mid';
};
</script>
<style scoped>
<style>
.spare-part-category {
  padding: 20px;
}
@@ -547,8 +556,17 @@
  font-weight: 600;
}
.el-table__row:hover > td {
  background-color: #fafafa;
.el-table .row-qty-low td.el-table__cell,
.el-table .row-qty-low:hover > td.el-table__cell {
  background-color: #fde8e8 !important;
}
.el-table .row-qty-mid td.el-table__cell,
.el-table .row-qty-mid:hover > td.el-table__cell {
  background-color: #f0f4f8 !important;
}
.el-table .row-qty-high td.el-table__cell,
.el-table .row-qty-high:hover > td.el-table__cell {
  background-color: #e8f5e9 !important;
}
/* æŒ‰é’®ç»„样式 */
@@ -567,4 +585,5 @@
  font-size: 12px;
  margin-right: 4px;
}
</style>
src/views/equipmentManagement/upkeep/Form/PlanModal.vue
@@ -35,6 +35,7 @@
      <el-form-item label="保养项目">
        <el-input
            v-model="form.machineryCategory"
            type="textarea"
            placeholder="请输入保养项目"
        />
      </el-form-item>
src/views/equipmentManagement/upkeep/Form/RecordDetailDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<template>
    <FormDialog
        v-model="dialogVisitable"
        title="保养记录详情"
        width="800px"
        operation-type="detail"
        @close="cancel"
    >
        <el-descriptions border :column="2">
            <el-descriptions-item label="设备名称">
                {{ detailData.deviceName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="规格型号">
                {{ detailData.deviceModel || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="计划保养日期">
                {{ formatDate(detailData.maintenancePlanTime) }}
            </el-descriptions-item>
            <el-descriptions-item label="录入人">
                {{ detailData.createUserName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="保养项目" :span="2">
                {{ detailData.machineryCategory || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="实际保养人">
                {{ detailData.maintenanceActuallyName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="实际保养日期">
                {{ formatDate(detailData.maintenanceActuallyTime) }}
            </el-descriptions-item>
            <el-descriptions-item label="保养结果" :span="2">
                {{ detailData.maintenanceResult || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="状态" :span="2">
                <el-tag v-if="detailData.status === 2" type="danger">失败</el-tag>
                <el-tag v-else-if="detailData.status === 1" type="success">完结</el-tag>
                <el-tag v-else type="warning">待保养</el-tag>
            </el-descriptions-item>
        </el-descriptions>
        <div v-if="fileList && fileList.length > 0" class="image-section">
            <div class="image-title">保养附件(图片):</div>
            <AttachmentUploadImage
                v-model:fileList="fileList"
                :disabled="true"
            />
        </div>
    </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { ref } from "vue";
import dayjs from "dayjs";
import { attachmentList } from "@/api/basicData/storageAttachment.js";
import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue';
const dialogVisitable = ref(false);
const detailData = ref({});
const fileList = ref([]);
const formatDate = (date) => {
    return date ? dayjs(date).format("YYYY-MM-DD") : "--";
};
const openDialog = (row) => {
    dialogVisitable.value = true;
    detailData.value = { ...row };
    fileList.value = [];
    if (row.id) {
        attachmentList({
            recordType: 'device_maintenance',
            recordId: row.id,
        }).then(res => {
            if (res && res.data) {
                fileList.value = res.data || [];
            }
        });
    }
};
const cancel = () => {
    dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped>
.image-section {
    margin-top: 20px;
}
.image-title {
    font-weight: bold;
    margin-bottom: 10px;
    color: #606266;
}
</style>
src/views/equipmentManagement/upkeep/Form/detailDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
<template>
    <FormDialog
        v-model="dialogVisitable"
        title="保养任务详情"
        width="800px"
        operation-type="detail"
        @close="cancel"
    >
        <el-descriptions border :column="2">
            <el-descriptions-item label="设备名称">
                {{ detailData.taskName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="规格型号">
                {{ detailData.deviceModel || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="录入人">
                {{ detailData.registrant || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="登记时间">
                {{ detailData.registrationDate || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="保养项目" :span="2">
                {{ detailData.machineryCategory || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="保养人">
                {{ detailData.maintenancePerson || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="任务频率">
                {{ formatFrequencyType(detailData.frequencyType) }}
            </el-descriptions-item>
            <el-descriptions-item label="执行时间">
                {{ formatFrequencyDetail(detailData.frequencyDetail) }}
            </el-descriptions-item>
            <el-descriptions-item label="定时任务">
                <el-tag :type="detailData.isActive === 1 ? 'success' : 'info'">
                    {{ detailData.isActive === 1 ? '开启' : '关闭' }}
                </el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="备注" :span="2">
                {{ detailData.remarks || '--' }}
            </el-descriptions-item>
        </el-descriptions>
        <div v-if="fileList && fileList.length > 0" class="image-section">
            <div class="image-title">设备图片:</div>
            <AttachmentUploadImage
                v-model:fileList="fileList"
                :disabled="true"
            />
        </div>
    </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { ref } from "vue";
import { getLedgerById } from "@/api/equipmentManagement/ledger";
import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue';
const dialogVisitable = ref(false);
const detailData = ref({});
const fileList = ref([]);
const formatFrequencyType = (type) => {
    const map = {
        DAILY: "每日",
        WEEKLY: "每周",
        MONTHLY: "每月",
        QUARTERLY: "季度",
    };
    return map[type] || "--";
};
const formatFrequencyDetail = (detail) => {
    if (!detail) return "--";
    const replacements = {
        MON: "周一",
        TUE: "周二",
        WED: "周三",
        THU: "周四",
        FRI: "周五",
        SAT: "周六",
        SUN: "周日",
    };
    return detail.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
};
const openDialog = (row) => {
    dialogVisitable.value = true;
    detailData.value = { ...row };
    fileList.value = [];
    if (row.taskId) {
        getLedgerById(row.taskId).then(res => {
            if (res.code === 200 && res.data) {
                fileList.value = res.data.storageBlobVOs || [];
            }
        });
    }
};
const cancel = () => {
    dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped>
.image-section {
    margin-top: 20px;
}
.image-title {
    font-weight: bold;
    margin-bottom: 10px;
    color: #606266;
}
</style>
src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -70,8 +70,8 @@
                    <el-form-item label="保养项目" prop="machineryCategory">
                        <el-input
                            v-model.trim="form.machineryCategory"
                            type="textarea"
                            placeholder="请输入保养项目"
                            maxlength="100"
                            clearable
                        />
                    </el-form-item>
@@ -145,6 +145,23 @@
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="24">
                    <el-form-item label="定时任务">
                        <el-switch
                            v-model="form.isActive"
                            :active-value="1"
                            :inactive-value="0"
                            active-text="开启"
                            inactive-text="关闭"
                            inline-prompt
                        />
                        <span style="margin-left: 12px; color: #909399; font-size: 12px;">
                            {{ form.isActive === 1 ? '任务将按计划执行' : '任务暂停执行' }}
                        </span>
                    </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" />
@@ -184,7 +201,8 @@
        time: '',
        deviceModel: undefined, // è§„格型号
        registrationDate: '',
        maintenancePerson: '' // ä¿å…»äºº
        maintenancePerson: '', // ä¿å…»äºº
        isActive: 0 // å®šæ—¶ä»»åŠ¡å¼€å…³ï¼š0=关闭,1=开启
    },
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
@@ -271,7 +289,8 @@
        time: '',
        deviceModel: undefined,
        registrationDate: '',
        maintenancePerson: ''
        maintenancePerson: '',
        isActive: 0
    }
}
@@ -294,6 +313,7 @@
                }
                delete payload.inspector
                delete payload.inspectorIds
                delete payload.active
                
                if (payload.frequencyType === 'WEEKLY') {
                    let frequencyDetail = ''
@@ -306,7 +326,6 @@
                if (payload.status === undefined || payload.status === null || payload.status === '') {
                    payload.status = '0' // é»˜è®¤çŠ¶æ€ï¼Œå¯æŒ‰å®žé™…æžšä¸¾è°ƒæ•´
                }
                payload.active = true
                payload.deleted = 0
                
                if (operationType.value === 'edit') {
src/views/equipmentManagement/upkeep/index.vue
@@ -63,6 +63,12 @@
            }"
                    @selection-change="handleScheduledSelectionChange"
                    @pagination="changeScheduledPage">
            <template #isActiveRef="{ row }">
              <el-tag v-if="row.isActive === 1"
                      type="success">开启</el-tag>
              <el-tag v-else
                      type="info">关闭</el-tag>
            </template>
            <template #statusRef="{ row }">
              <el-tag v-if="row.status === 1"
                      type="success">启用</el-tag>
@@ -70,6 +76,11 @@
                      type="danger">停用</el-tag>
            </template>
            <template #operation="{ row }">
              <el-button type="primary"
                         link
                         @click="handleDetail(row)">
                è¯¦æƒ…
              </el-button>
              <el-button type="primary"
                         link
                         @click="editScheduledTask(row)">
@@ -171,6 +182,11 @@
                      type="warning">待保养</el-tag>
            </template>
            <template #operation="{ row }">
              <el-button type="primary"
                         link
                         @click="handleRecordDetail(row)">
                è¯¦æƒ…
              </el-button>
              <!-- è¿™ä¸ªåŠŸèƒ½è·Ÿæ–°å¢žä¿å…»åŠŸèƒ½ä¸€æ¨¡ä¸€æ ·ï¼Œæœ‰å•¥æ„ä¹‰ï¼Ÿ -->
              <!-- <el-button
              type="primary"
@@ -213,6 +229,8 @@
                      @ok="getTableData" />
    <FormDia ref="formDiaRef"
             @closeDia="getScheduledTableData" />
    <DetailDia ref="detailDiaRef" />
    <RecordDetailDia ref="recordDetailDiaRef" />
    <FileList v-if="fileDialogVisible"
              v-model:visible="fileDialogVisible"
              :record-type="'device_maintenance'"
@@ -235,6 +253,8 @@
  import PlanModal from "./Form/PlanModal.vue";
  import MaintenanceModal from "./Form/MaintenanceModal.vue";
  import FormDia from "./Form/formDia.vue";
  import DetailDia from "./Form/detailDia.vue";
  import RecordDetailDia from "./Form/RecordDetailDia.vue";
  import {
    getUpkeepPage,
    delUpkeep,
@@ -257,6 +277,10 @@
  const maintainModalRef = ref();
  // å®šæ—¶ä»»åŠ¡å¼¹çª—æŽ§åˆ¶å™¨
  const formDiaRef = ref();
  // å®šæ—¶ä»»åŠ¡è¯¦æƒ…å¼¹çª—æŽ§åˆ¶å™¨
  const detailDiaRef = ref();
  // ä¿å…»è®°å½•详情弹窗控制器
  const recordDetailDiaRef = ref();
  // é™„件弹窗
  const fileListDialogRef = ref(null);
  const fileDialogVisible = ref(false);
@@ -308,7 +332,7 @@
    {
      prop: "frequencyType",
      label: "频次",
      minWidth: 150,
      minWidth: 80,
      // PIMTable ä½¿ç”¨çš„æ˜¯ formatData,而不是 Element-Plus çš„ formatter
      formatData: cell =>
        ({
@@ -321,7 +345,7 @@
    {
      prop: "frequencyDetail",
      label: "开始日期与时间",
      minWidth: 150,
      minWidth: 130,
      // åŒæ ·æ”¹ç”¨ formatData,PIMTable å†…部会把单元格值传进来
      formatData: cell => {
        if (typeof cell !== "string") return "";
@@ -342,6 +366,14 @@
        );
      },
    },
    {
      prop: "isActive",
      label: "定时任务",
      dataType: "slot",
      slot: "isActiveRef",
      align: "center",
      minWidth: 100,
    },
    { prop: "maintenancePerson", label: "保养人", minWidth: 100 },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    {
@@ -349,7 +381,7 @@
      label: "登记日期",
      minWidth: 100,
      formatData: cell =>
        cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
        cell ? dayjs(cell).format("YYYY-MM-DD") : "-",
    },
    {
      fixed: "right",
@@ -357,7 +389,7 @@
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
      width: "160px",
    },
  ]);
@@ -409,7 +441,7 @@
      align: "center",
      prop: "maintenanceActuallyTime",
      formatData: cell =>
        cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
        cell ? dayjs(cell).format("YYYY-MM-DD") : "-",
    },
    {
      label: "保养结果",
@@ -431,7 +463,7 @@
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "350px",
      width: "250px",
    },
  ]);
@@ -489,6 +521,14 @@
    if (row) {
      nextTick(() => {
        formDiaRef.value?.openDialog("edit", row);
      });
    }
  };
  const handleDetail = row => {
    if (row) {
      nextTick(() => {
        detailDiaRef.value?.openDialog(row);
      });
    }
  };
@@ -564,6 +604,14 @@
    maintainModalRef.value.open(row.id, row);
  };
  const handleRecordDetail = row => {
    if (row) {
      nextTick(() => {
        recordDetailDiaRef.value?.openDialog(row);
      });
    }
  };
  const addPlan = () => {
    planModalRef.value.openModal();
  };
src/views/financialManagement/assets/fixedAssets.vue
@@ -82,7 +82,7 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="资产编号" prop="assetCode">
              <el-input v-model="form.assetCode" placeholder="保存后自动生成" disabled />
              <el-input v-model="form.assetCode" placeholder="系统自动生成" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -175,13 +175,6 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建时间" prop="createTime">
              <el-date-picker v-model="createTimeDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%;" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
@@ -196,7 +189,6 @@
<script setup>
import { ref, reactive, onMounted, computed } from "vue";
import dayjs from "dayjs";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
@@ -266,17 +258,10 @@
  keeper: "",
  status: "in_use",
  remark: "",
  createTime: "",
});
const form = reactive({
  ...createDefaultForm(),
});
const createTimeDate = computed({
  get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
  set: (value) => {
    form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
  },
});
const rules = {
@@ -371,14 +356,16 @@
  getTableData();
};
const buildAssetCode = () => `GD${Date.now().toString().slice(-10)}`;
const add = () => {
  isEdit.value = false;
  isView.value = false;
  currentId.value = null;
  dialogTitle.value = "新增固定资产";
  Object.assign(form, createDefaultForm(), {
    assetCode: buildAssetCode(),
    purchaseDate: new Date().toISOString().split('T')[0],
    createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
  });
  dialogVisible.value = true;
};
src/views/financialManagement/assets/intangibleAssets.vue
@@ -72,7 +72,7 @@
        </template>
        <template #operation="{ row }">
          <el-button type="primary" link @click="view(row)">查看</el-button>
          <el-button v-if="row.status !== 'amortized'" type="primary" link @click="edit(row)">编辑</el-button>
          <el-button type="primary" link @click="edit(row)">编辑</el-button>
          <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
        </template>
      </PIMTable>
@@ -83,7 +83,7 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="资产编号" prop="assetCode">
              <el-input v-model="form.assetCode" placeholder="保存后自动生成" disabled />
              <el-input v-model="form.assetCode" placeholder="系统自动生成" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -160,15 +160,8 @@
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%;">
                <el-option label="在用" value="in_use" />
                <el-option label="闲置" value="idle" />
                <el-option v-if="isView" label="已摊销完毕" value="amortized" />
                <el-option label="已摊销完毕" value="amortized" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建时间" prop="createTime">
              <el-date-picker v-model="createTimeDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%;" />
            </el-form-item>
          </el-col>
        </el-row>
@@ -189,7 +182,6 @@
<script setup>
import { ref, reactive, onMounted, computed } from "vue";
import dayjs from "dayjs";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
@@ -244,7 +236,6 @@
);
const createDefaultForm = () => ({
  id: null,
  assetCode: "",
  assetName: "",
  category: "",
@@ -259,17 +250,10 @@
  status: "in_use",
  description: "",
  remark: "",
  createTime: "",
});
const form = reactive({
  ...createDefaultForm(),
});
const createTimeDate = computed({
  get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
  set: (value) => {
    form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
  },
});
const rules = {
@@ -370,14 +354,16 @@
  getTableData();
};
const buildAssetCode = () => `WX${Date.now().toString().slice(-10)}`;
const add = () => {
  isEdit.value = false;
  isView.value = false;
  currentId.value = null;
  dialogTitle.value = "新增无形资产";
  Object.assign(form, createDefaultForm(), {
    assetCode: buildAssetCode(),
    acquisitionDate: new Date().toISOString().split('T')[0],
    createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
  });
  dialogVisible.value = true;
};
src/views/financialManagement/payable/input-invoice.vue
@@ -51,8 +51,6 @@
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -375,26 +373,6 @@
  invoiceType: [{ required: true, message: "请选择发票类型", trigger: "change" }],
  taxRate: [{ required: true, message: "请选择税率", trigger: "change" }],
  amount: [{ required: true, message: "请输入金额", trigger: "blur" }],
};
const summaryProps = ["amount", "taxAmount", "totalAmount"];
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (summaryProps.includes(col.property)) {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur[col.property]);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const formatMoney = (value) => {
src/views/financialManagement/payable/payment.vue
@@ -65,8 +65,6 @@
                :column="columns"
                :tableData="dataList"
                :tableLoading="tableLoading"
                isShowSummary
                :summaryMethod="getSummaries"
                :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -150,24 +148,6 @@
  const totalPaymentAmount = computed(() =>
    dataList.value.reduce((sum, item) => sum + Number(item.amount ?? 0), 0)
  );
  const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (col.property === "amount") {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur.amount);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const formatMoney = value => {
    if (value === undefined || value === null) return "0.00";
src/views/financialManagement/payable/paymentApply.vue
@@ -52,8 +52,6 @@
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -99,7 +97,7 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="申请单号" prop="invoiceApplicationNo">
              <el-input v-model="form.invoiceApplicationNo" placeholder="保存后自动生成" disabled />
              <el-input v-model="form.invoiceApplicationNo" placeholder="系统自动生成" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -160,18 +158,6 @@
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建时间" prop="createTime">
              <el-date-picker
                v-model="formCreateTimeDate"
                type="date"
                placeholder="选择日期"
                value-format="YYYY-MM-DD"
                style="width: 100%;"
                :disabled="isView"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="付款金额" prop="paymentAmount">
              <el-input-number
                v-model="form.paymentAmount"
@@ -231,7 +217,7 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款单号" prop="paymentNumber">
              <el-input v-model="paymentForm.paymentNumber" placeholder="保存后自动生成" disabled />
              <el-input v-model="paymentForm.paymentNumber" placeholder="系统自动生成" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -259,17 +245,6 @@
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建时间" prop="createTime">
              <el-date-picker
                v-model="paymentFormCreateTimeDate"
                type="date"
                placeholder="选择日期"
                value-format="YYYY-MM-DD"
                style="width: 100%;"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="付款金额" prop="paymentAmount">
              <el-input-number
@@ -355,7 +330,6 @@
<script setup>
import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue";
import dayjs from "dayjs";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
@@ -433,7 +407,6 @@
  bankAccount: "",
  bankName: "",
  remark: "",
  createTime: "",
});
const paymentRules = {
@@ -456,19 +429,6 @@
  stockInRecordIds: [],
  inboundBatches: "",
  status: 0,
  createTime: "",
});
const formCreateTimeDate = computed({
  get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
  set: (value) => {
    form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
  },
});
const paymentFormCreateTimeDate = computed({
  get: () => (paymentForm.createTime ? String(paymentForm.createTime).split(" ")[0] : ""),
  set: (value) => {
    paymentForm.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
  },
});
const rules = {
@@ -477,24 +437,6 @@
  paymentAmount: [{ required: true, message: "请输入付款金额", trigger: "blur" }],
  paymentMethod: [{ required: true, message: "请选择付款方式", trigger: "change" }],
  applyDate: [{ required: true, message: "请选择申请日期", trigger: "change" }],
};
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (col.property === "amount") {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur.amount);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const formatMoney = (value) => {
@@ -621,7 +563,6 @@
    stockInRecordIds,
    inboundBatches: formatInboundBatches(row.inboundBatches),
    status: normalizeStatus(row.status),
    createTime: row.createTime ?? "",
  });
};
@@ -639,7 +580,6 @@
  remark: row.remark ?? "",
  status: statusOverride !== undefined ? statusOverride : normalizeStatus(row.status),
  paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0),
  createTime: row.createTime,
});
const buildSubmitPayload = (forUpdate = false) => {
@@ -653,7 +593,6 @@
    remark: form.remark || "",
    status: 0,
    paymentAmount: form.paymentAmount,
    createTime: form.createTime,
  };
  if (forUpdate) {
    payload.id = currentId.value;
@@ -764,7 +703,6 @@
    stockInRecordIds: [],
    inboundBatches: "",
    status: 0,
    createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
  });
  inboundBatchList.value = [];
  inboundBatchOptions.value = [];
@@ -843,7 +781,6 @@
    bankAccount: row.bankAccountNum ?? row.bankAccount ?? "",
    bankName: row.bankAccountName ?? row.bankName ?? "",
    remark: "",
    createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
  });
  paymentDialogVisible.value = true;
  nextTick(() => {
@@ -863,7 +800,6 @@
      paymentAmount: paymentForm.paymentAmount,
      paymentNumber: paymentForm.paymentNumber || "",
      remark: paymentForm.remark || "",
      createTime: paymentForm.createTime,
    })
      .then((res) => {
        if (res.code === 200) {
src/views/financialManagement/payable/purchaseIn.vue
@@ -49,8 +49,6 @@
                :column="columns"
                :tableData="dataList"
                :tableLoading="tableLoading"
                isShowSummary
                :summaryMethod="getSummaries"
                :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -100,7 +98,7 @@
      slot: "inboundDate",
    },
    { label: "产品名称", prop: "productName", minWidth: "140" },
    { label: "规格型号", prop: "specificationModel", minWidth: "140" },
    { label: "产品规格", prop: "specificationModel", minWidth: "140" },
    {
      label: "金额",
      prop: "inboundAmount",
@@ -189,24 +187,6 @@
    pagination.currentPage = page;
    pagination.pageSize = limit;
    getTableData();
  };
  const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (col.property === "inboundAmount") {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur.inboundAmount);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const handleOut = () => {
src/views/financialManagement/payable/purchaseReturn.vue
@@ -44,8 +44,6 @@
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -85,30 +83,6 @@
  { label: "退货单号", prop: "returnNo", minWidth: "150" },
  { label: "供应商", prop: "supplierName", minWidth: "180" },
  { label: "关联入库单号", prop: "inboundBatches", minWidth: "150" },
  {
    label: "发货类型",
    prop: "shippingType",
    minWidth: "110",
    formatData: (val) => ({ 1: "货车", 2: "快递" }[String(val)] || "--"),
  },
  {
    label: "发货车牌号",
    prop: "truckPlateNo",
    minWidth: "140",
    formatData: (_val, row) => (String(row?.shippingType) === "1" ? row?.truckPlateNo || "--" : "--"),
  },
  {
    label: "快递公司",
    prop: "expressCompany",
    minWidth: "140",
    formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressCompany || "--" : "--"),
  },
  {
    label: "快递单号",
    prop: "expressNo",
    minWidth: "150",
    formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressNo || "--" : "--"),
  },
  { label: "退货日期", prop: "preparedAt", minWidth: "170" },
  {
    label: "退款总额",
@@ -199,24 +173,6 @@
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (col.property === "totalAmount") {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur.totalAmount);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const handleOut = () => {
src/views/financialManagement/payable/reconciliation.vue
@@ -35,8 +35,6 @@
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -406,26 +404,6 @@
  closingBalance: generateForm.closingBalance,
  accountStatementDetails: selectedPurchases.value.map(buildDetailSubmitItem),
});
const summaryProps = ["openingBalance", "currentPlan", "currentActually", "closingBalance"];
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (summaryProps.includes(col.property)) {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur[col.property]);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const formatMoney = (value) => {
  if (value === undefined || value === null) return "0.00";
src/views/financialManagement/receivable/invoiceApply.vue
@@ -48,8 +48,6 @@
        v-loading="tableLoading"
        :column="columns"
        :tableData="dataList"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -100,7 +98,7 @@
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="申请单号" prop="applyCode">
              <el-input v-model="form.applyCode" placeholder="保存后自动生成" disabled />
              <el-input v-model="form.applyCode" placeholder="系统自动生成" disabled />
            </el-form-item>
          </el-col>
        </el-row>
@@ -191,20 +189,6 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建时间" prop="createTime">
              <el-date-picker
                v-model="formCreateTimeDate"
                type="date"
                placeholder="选择日期"
                value-format="YYYY-MM-DD"
                style="width: 100%;"
                :disabled="isView"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="发票内容" prop="content">
          <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请输入发票内容" :disabled="isView" />
        </el-form-item>
@@ -269,7 +253,6 @@
<script setup>
import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance, defineAsyncComponent } from "vue";
import dayjs from "dayjs";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { listCustomer } from "@/api/basicData/customer.js";
@@ -541,13 +524,6 @@
  applyDate: "",
  content: "",
  remark: "",
  createTime: "",
});
const formCreateTimeDate = computed({
  get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
  set: (value) => {
    form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
  },
});
const rules = {
@@ -650,27 +626,6 @@
  proxy.download("/accountInvoiceApplication/exportAccountInvoiceApplication", params, filename);
};
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (col.property === "amount") {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur.amount);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = total.toLocaleString("zh-CN", {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
      });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const formatMoney = (value) => {
  if (value === undefined || value === null) return "0.00";
  return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -751,7 +706,6 @@
    status: normalizeStatus(row.status ?? row.auditStatus),
    outboundBatchNos,
    outboundBatches: formatOutboundBatches(row.outboundBatches),
    createTime: row.createTime ?? "",
  });
};
@@ -760,7 +714,7 @@
  isView.value = false;
  dialogTitle.value = "新增开票申请";
  Object.assign(form, {
    applyCode: "",
    applyCode: "KP" + Date.now().toString().slice(-8),
    customerId: "",
    outboundBatchNos: [],
    outboundBatches: "",
@@ -770,7 +724,6 @@
    applyDate: new Date().toISOString().split("T")[0],
    content: "",
    remark: "",
    createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
  });
  outboundBatchList.value = [];
  outboundBatchOptions.value = [];
@@ -799,7 +752,6 @@
    invoiceAmount: form.amount,
    taxRate: form.taxRate,
    status: 0,
    createTime: form.createTime,
  };
  if (forUpdate) {
    payload.id = currentId.value;
@@ -814,7 +766,6 @@
  dialogTitle.value = "编辑开票申请";
  fillFormFromRow(row);
  dialogVisible.value = true;
  loadOutboundBatches(form.customerId, true);
};
const view = (row) => {
src/views/financialManagement/receivable/outputInvoice.vue
@@ -37,7 +37,7 @@
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus">录入发票</el-button>
          <!-- <el-button type="primary" @click="add" icon="Plus">录入发票</el-button> -->
          <el-button type="success" @click="handleExport" icon="Download">导出</el-button>
        </div>
      </div>
@@ -46,8 +46,6 @@
        v-loading="tableLoading"
        :column="columns"
        :tableData="dataList"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -93,7 +91,7 @@
      @confirm="submitForm"
      @cancel="closeDialog"
    >
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
        <el-row v-if="isView" :gutter="20">
          <el-col :span="12">
            <el-form-item label="状态">
@@ -111,25 +109,13 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户" prop="customerId">
              <el-select @change="checkCustomer" v-model="form.customerId" placeholder="请选择客户" style="width: 100%;" :disabled="isView">
              <el-select v-model="form.customerId" placeholder="请选择客户" style="width: 100%;" :disabled="isView">
                <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联开票申请" prop="accountInvoiceApplicationId">
              <el-select v-model="form.accountInvoiceApplicationId"
                        placeholder="请先选择客户"
                        readonly
                         @change="selectionApply"
                        :disabled="!form.customerId || isView"
                        class="outbound-batch-select">
                <el-option v-for="(item,index) in applyList" :key="index" :label="item.invoiceApplicationNo" :value="item.id"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="开票日期" prop="invoiceDate">
              <el-date-picker
@@ -142,8 +128,6 @@
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="发票类型" prop="invoiceType">
              <el-select
@@ -151,6 +135,7 @@
                  placeholder="请选择发票类型"
                  style="width: 100%;"
                  :disabled="isView"
                @change="handleInvoiceTypeChange"
              >
                <el-option label="增值税专用发票" value="增值税专用发票" />
                <el-option label="增值税普通发票" value="增值税普通发票" />
@@ -158,6 +143,8 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="税率" prop="taxRate">
              <el-select
@@ -165,7 +152,7 @@
                placeholder="请选择税率"
                style="width: 100%;"
                :disabled="isView"
                @change="handleTaxRateChange"
                @change="calculateTax"
              >
                <el-option
                  v-for="dict in tax_rate"
@@ -178,7 +165,7 @@
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="10">
          <el-col :span="8">
            <el-form-item label="金额(不含税)" prop="amount">
              <el-input-number
                v-model="form.amount"
@@ -186,18 +173,18 @@
                :precision="2"
                style="width: 100%;"
                :disabled="isView"
                @change="handleTaxRateChange"
                @change="calculateTax"
              />
            </el-form-item>
          </el-col>
          <el-col :span="7">
          <el-col :span="8">
            <el-form-item label="税额">
              <el-input v-model="form.taxAmount" style="width: 100%;" disabled />
              <el-input v-model="form.taxAmount" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="7">
          <el-col :span="8">
            <el-form-item label="价税合计">
              <el-input v-model="form.totalAmount" style="width: 100%;" disabled />
              <el-input v-model="form.totalAmount" disabled />
            </el-form-item>
          </el-col>
        </el-row>
@@ -235,7 +222,6 @@
  cancelAccountSalesInvoice,
  deleteAccountSalesInvoice,
} from "@/api/financialManagement/accountSalesInvoice.js";
import {getAccountInvoiceApplicationList} from "@/api/financialManagement/invoiceApply.js";
const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
@@ -283,9 +269,6 @@
const fileDialogVisible = ref(false);
const currentRecordId = ref(0);
const applySelectVisible = ref(false);
const applyList = ref([]);
const openFileDialog = (row) => {
  if (!row.accountInvoiceApplicationId) {
    ElMessage.warning("未关联开票申请,无法查看附件");
@@ -328,14 +311,12 @@
  totalAmount: 0,
  content: "",
  remark: "",
  deptId: undefined,
  accountInvoiceApplicationId: undefined,
  storageAttachmentId: undefined,
});
const rules = {
  invoiceNo: [{ required: true, message: "请输入发票号码", trigger: "blur" }],
  accountInvoiceApplicationId: [{ required: true, message: "请选择开票申请", trigger: "change" }],
  customerId: [{ required: true, message: "请选择客户", trigger: "change" }],
  invoiceDate: [{ required: true, message: "请选择开票日期", trigger: "change" }],
  invoiceType: [{ required: true, message: "请选择发票类型", trigger: "change" }],
@@ -343,99 +324,18 @@
  amount: [{ required: true, message: "请输入金额", trigger: "blur" }],
};
// é€‰æ‹©å®¢æˆ·
const checkCustomer = (val) =>{
  loadApplyList(val);
}
//查询开票申请列表
const loadApplyList = (customerId) => {
  if (!customerId) {
    applyList.value = [];
    return Promise.resolve();
  }
  return getAccountInvoiceApplicationList({ customerId })
      .then(res => {
        if (res.code === 200) {
          const list = res.data?.records ?? res.data ?? [];
          applyList.value = Array.isArray(list) ? list : [];
        } else {
          applyList.value = [];
        }
      })
      .catch(() => {
        applyList.value = [];
      })
const formatMoney = (value) => {
  if (value === undefined || value === null) return "0.00";
  return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
const selectionApply = (val)=>{
  const applyObj = applyList.value.find(item=>item.id === val);
  if(!applyObj){
    ElMessage.warning("请选择关联的开票申请");
    return;
  }
  form.remark = applyObj.remark;
  form.content = applyObj.invoiceContent;
  form.invoiceDate = applyObj.applyDate;
  form.invoiceType = applyObj.invoiceType;
  form.deptId = applyObj.deptId;
  form.taxRate = applyObj.taxRate;
  form.totalAmount = applyObj.invoiceAmount
  handleTaxRateChange();
}
/** ä»·ç¨Žåˆè®¡å˜æ›´ï¼šæŒ‰ç¨ŽçŽ‡åç®—ä¸å«ç¨Žé‡‘é¢ã€ç¨Žé¢ */
const calculateTaxFromInclusive = (inclusiveTotal) => {
  const total = Number(inclusiveTotal ?? form.totalAmount ?? 0);
  if (total <= 0) {
    form.amount = 0;
    form.taxAmount = 0;
    form.totalAmount = 0;
    return;
  }
  const rate = Number(form.taxRate) / 100;
  form.totalAmount = Number(total.toFixed(2));
  form.amount = Number((form.totalAmount / (1 + rate)).toFixed(2));
  form.taxAmount = Number((form.totalAmount - form.amount).toFixed(2));
};
const calculateTaxFromExclusive = () => {
const calculateTax = () => {
  form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2));
  form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2));
};
const handleTaxRateChange = () => {
  if (form.totalAmount > 0) {
    calculateTaxFromInclusive(form.totalAmount);
  } else {
    calculateTaxFromExclusive();
  }
};
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (col.property === "amount") {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur.amount);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = total.toLocaleString("zh-CN", {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
      });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const formatMoney = (value) => {
  if (value === undefined || value === null) return "0.00";
  return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const handleInvoiceTypeChange = () => {
  calculateTax();
};
const normalizeTableRow = (row) => ({
@@ -583,8 +483,6 @@
const closeDialog = () => {
  dialogVisible.value = false;
  applySelectVisible.value = false;
  isView.value = false;
  isView.value = false;
};
src/views/financialManagement/receivable/receipt.vue
@@ -111,7 +111,7 @@
            <el-form-item label="收款单号"
                          prop="receiptCode">
              <el-input v-model="form.receiptCode"
                        placeholder="保存后自动生成"
                        placeholder="系统自动生成"
                        disabled />
            </el-form-item>
          </el-col>
@@ -171,10 +171,10 @@
                          prop="amount">
              <el-input-number v-model="form.amount"
                               :min="0"
                               :max="collectionAmountInputMax"
                               :precision="2"
                               style="width: 100%;"
                               :disabled="isView" />
                               :disabled="isView"
                               placeholder="根据关联单据自动汇总,可修改" />
            </el-form-item>
          </el-col>
        </el-row>
@@ -191,17 +191,6 @@
                           :label="item.label"
                           :value="item.value" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="创建时间"
                          prop="createTime">
              <el-date-picker v-model="formCreateTimeDate"
                              type="date"
                              placeholder="选择日期"
                              value-format="YYYY-MM-DD"
                              style="width: 100%;"
                              :disabled="isView" />
            </el-form-item>
          </el-col>
        </el-row>
@@ -274,12 +263,6 @@
                         align="right">
          <template #default="{ row }">Â¥{{ formatMoney(row.outboundAmount) }}</template>
        </el-table-column>
        <el-table-column prop="amountReceived"
                         label="已收款金额"
                         width="110"
                         align="right">
          <template #default="{ row }">Â¥{{ formatMoney(row.amountReceived) }}</template>
        </el-table-column>
        <el-table-column prop="taxRate"
                         label="税率"
                         width="80"
@@ -305,7 +288,6 @@
    nextTick,
    getCurrentInstance,
  } from "vue";
  import dayjs from "dayjs";
  import { ElMessage, ElMessageBox } from "element-plus";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import { listCustomer } from "@/api/basicData/customer.js";
@@ -368,7 +350,6 @@
  const isEdit = ref(false);
  const isView = ref(false);
  const currentId = ref(null);
  const originalReceiptAmount = ref(0);
  const submitLoading = ref(false);
  const customerList = ref([]);
@@ -398,54 +379,7 @@
    stockOutRecordIds: [],
    outboundBatches: "",
    remark: "",
    createTime: "",
  });
  const formCreateTimeDate = computed({
    get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
    set: (value) => {
      form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
    },
  });
  const maxCollectionAmount = computed(() => {
    const selected = form.stockOutRecordIds || [];
    const editAmount = isEdit.value ? Number(originalReceiptAmount.value) || 0 : 0;
    if (!selected.length) return isEdit.value ? editAmount : undefined;
    const selectedValueSet = new Set(selected.map(id => String(id)));
    const selectedOptions = outboundBatchOptions.value.filter(
      opt => opt.amountLimitAvailable && selectedValueSet.has(String(opt.value))
    );
    if (selectedOptions.length !== selectedValueSet.size) {
      return isEdit.value ? Number(editAmount.toFixed(2)) : undefined;
    }
    const sum = selectedOptions.reduce(
      (acc, opt) => acc + (Number(opt.outboundAmount) || 0),
      0
    );
    return Number((sum + editAmount).toFixed(2));
  });
  const collectionAmountInputMax = computed(
    () => maxCollectionAmount.value ?? Number.MAX_SAFE_INTEGER
  );
  const validateCollectionAmount = (rule, value, callback) => {
    if (value === undefined || value === null || value === "") {
      callback();
      return;
    }
    const amount = Number(value);
    if (Number.isNaN(amount)) {
      callback(new Error("请输入收款金额"));
      return;
    }
    const max = maxCollectionAmount.value;
    if (max !== undefined && amount - max > 0.000001) {
      callback(new Error(`收款金额不能超过${max.toFixed(2)}`));
      return;
    }
    callback();
  };
  const rules = {
    customerId: [{ required: true, message: "请选择客户", trigger: "change" }],
@@ -461,10 +395,7 @@
    receiptDate: [
      { required: true, message: "请选择收款日期", trigger: "change" },
    ],
    amount: [
      { required: true, message: "请输入收款金额", trigger: "blur" },
      { validator: validateCollectionAmount, trigger: ["blur", "change"] },
    ],
    amount: [{ required: true, message: "请输入收款金额", trigger: "blur" }],
    receiptMethod: [
      { required: true, message: "请选择收款方式", trigger: "change" },
    ],
@@ -524,12 +455,7 @@
    return list.map((item, index) => {
      if (typeof item === "string" || typeof item === "number") {
        const text = String(item);
        return {
          label: text,
          value: text,
          outboundAmount: 0,
          amountLimitAvailable: false,
        };
        return { label: text, value: text, outboundAmount: 0 };
      }
      const label =
        item.outboundBatches ??
@@ -539,15 +465,10 @@
        item.label ??
        `出库单${index + 1}`;
      const value = item.id ?? item.stockOutRecordId ?? label;
      const outboundAmount = Number(item.outboundAmount) || 0;
      const amountReceived = Number(item.amountReceived) || 0;
      const availableAmount = outboundAmount - amountReceived;
      return {
        label: String(label),
        value,
        outboundAmount:
          availableAmount > 0 ? Number(availableAmount.toFixed(2)) : 0,
        amountLimitAvailable: true,
        outboundAmount: Number(item.outboundAmount) || 0,
      };
    });
  };
@@ -595,7 +516,6 @@
        label: String(id),
        value: id,
        outboundAmount: 0,
        amountLimitAvailable: false,
      });
    });
  };
@@ -698,7 +618,6 @@
    outboundSelectVisible.value = false;
    syncCollectionAmount();
    formRef.value?.validateField("stockOutRecordIds");
    formRef.value?.validateField("amount");
  };
  const handleOutboundDialogClosed = () => {
@@ -739,7 +658,6 @@
      collectionNumber: form.receiptCode || "",
      remark: form.remark || "",
      stockOutRecordIds: (form.stockOutRecordIds || []).join(","),
      createTime: form.createTime,
    };
    if (forUpdate) {
      payload.id = currentId.value;
@@ -760,7 +678,6 @@
      stockOutRecordIds,
      outboundBatches: formatOutboundBatches(row.outboundBatches),
      remark: row.remark ?? "",
      createTime: row.createTime ?? "",
    });
  };
@@ -769,7 +686,6 @@
    outboundSelectVisible.value = false;
    isView.value = false;
    isEdit.value = false;
    originalReceiptAmount.value = 0;
  };
  const handleExport = () => {
@@ -829,9 +745,8 @@
    isEdit.value = false;
    isView.value = false;
    dialogTitle.value = "新增收款";
    originalReceiptAmount.value = 0;
    Object.assign(form, {
      receiptCode: "",
      receiptCode: "SK" + Date.now().toString().slice(-8),
      customerId: "",
      receiptDate: new Date().toISOString().split("T")[0],
      amount: 0,
@@ -839,7 +754,6 @@
      stockOutRecordIds: [],
      outboundBatches: "",
      remark: "",
      createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    });
    outboundBatchList.value = [];
    outboundBatchOptions.value = [];
@@ -852,16 +766,13 @@
    currentId.value = row.id;
    dialogTitle.value = "编辑收款";
    fillFormFromRow(row);
    originalReceiptAmount.value = Number(form.amount || 0);
    dialogVisible.value = true;
    loadOutboundBatches(form.customerId, true);
  };
  const view = row => {
    isView.value = true;
    isEdit.value = false;
    dialogTitle.value = "查看收款";
    originalReceiptAmount.value = 0;
    fillFormFromRow(row);
    dialogVisible.value = true;
  };
src/views/financialManagement/receivable/reconciliation.vue
@@ -30,8 +30,6 @@
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -389,29 +387,6 @@
  closingBalance: generateForm.closingBalance,
  accountStatementDetails: selectedSales.value.map(buildDetailSubmitItem),
});
const summaryProps = ["openingBalance", "currentPlan", "currentActually", "closingBalance"];
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (summaryProps.includes(col.property)) {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur[col.property]);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
      });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const formatMoney = (value) => {
  if (value === undefined || value === null) return "0.00";
src/views/financialManagement/receivable/salesOut.vue
@@ -1,33 +1,27 @@
<template>
  <!-- é”€å”®å‡ºåº“ -->
  <div class="app-container">
    <el-form :model="filters"
             :inline="true">
    <el-form :model="filters" :inline="true">
      <el-form-item label="出库单号:">
        <el-input v-model="filters.outboundBatches"
                  placeholder="请输入出库单号"
                  clearable
                  style="width: 200px;" />
        <el-input v-model="filters.outboundBatches" placeholder="请输入出库单号" clearable style="width: 200px;" />
      </el-form-item>
      <el-form-item label="客户名称:">
        <el-input v-model="filters.customerName"
                  placeholder="请输入客户名称"
                  clearable
                  style="width: 200px;" />
        <el-input v-model="filters.customerName" placeholder="请输入客户名称" clearable style="width: 200px;" />
      </el-form-item>
      <el-form-item label="出库日期:">
        <el-date-picker v-model="filters.dateRange"
        <el-date-picker
          v-model="filters.dateRange"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        type="daterange"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        clearable />
          clearable
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   @click="onSearch">搜索</el-button>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
@@ -35,22 +29,21 @@
      <div class="actions">
        <div></div>
        <div>
          <el-button @click="handleOut"
                     icon="Download">导出</el-button>
          <el-button @click="handleOut" icon="Download">导出</el-button>
        </div>
      </div>
      <PIMTable rowKey="id"
      <PIMTable
        rowKey="id"
                :column="columns"
                :tableData="dataList"
                :tableLoading="tableLoading"
                isShowSummary
                :summaryMethod="getSummaries"
                :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
                @pagination="changePage" />
        @pagination="changePage"
      />
    </div>
  </div>
</template>
@@ -83,18 +76,13 @@
    { label: "客户名称", prop: "customerName", minWidth: "180" },
    { label: "出库日期", prop: "shippingDate", width: "170" },
    { label: "产品名称", prop: "productName", minWidth: "140" },
    { label: "规格型号", prop: "specificationModel", minWidth: "140" },
  { label: "产品规格", prop: "specificationModel", minWidth: "140" },
    {
      label: "金额",
      prop: "outboundAmount",
      minWidth: "120",
      formatData: val =>
        val === null || val === undefined || val === ""
          ? ""
          : Number(val).toLocaleString("zh-CN", {
              minimumFractionDigits: 2,
              maximumFractionDigits: 2,
            }),
    align: "right",
    formatData: (val) => (val === null || val === undefined || val === "" ? "" : Number(val).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })),
    },
    { label: "发货编号", prop: "shippingNo", minWidth: "140" },
    { label: "销售订单号", prop: "salesContractNo", minWidth: "150" },
@@ -127,7 +115,7 @@
      current: pagination.currentPage,
      size: pagination.pageSize,
    })
      .then(res => {
    .then((res) => {
        const ok = res.code === 200 || res.code === 0;
        if (ok && res.data) {
          pagination.total = res.data.total ?? 0;
@@ -157,27 +145,6 @@
    pagination.currentPage = page;
    pagination.pageSize = limit;
    getTableData();
  };
  const getSummaries = ({ columns, data }) => {
    const sums = [];
    columns.forEach((col, index) => {
      if (index === 0) {
        sums[index] = "合计";
      } else if (col.property === "outboundAmount") {
        const total = data.reduce((prev, cur) => {
          const v = Number(cur.outboundAmount);
          return prev + (isNaN(v) ? 0 : v);
        }, 0);
        sums[index] = total.toLocaleString("zh-CN", {
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        });
      } else {
        sums[index] = "";
      }
    });
    return sums;
  };
  const handleOut = () => {
src/views/financialManagement/receivable/salesReturn.vue
@@ -37,8 +37,6 @@
        :column="columns"
        :tableData="dataList"
        :tableLoading="tableLoading"
        isShowSummary
        :summaryMethod="getSummaries"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
@@ -77,35 +75,12 @@
  { label: "退货单号", prop: "returnNo", minWidth: "150" },
  { label: "客户名称", prop: "customerName", minWidth: "180" },
  { label: "关联发货单号", prop: "shippingNo", minWidth: "150" },
  {
    label: "发货类型",
    prop: "shippingType",
    minWidth: "110",
    formatData: (val) => ({ 1: "货车", 2: "快递" }[String(val)] || "--"),
  },
  {
    label: "发货车牌号",
    prop: "truckPlateNo",
    minWidth: "140",
    formatData: (_val, row) => (String(row?.shippingType) === "1" ? row?.truckPlateNo || "--" : "--"),
  },
  {
    label: "快递公司",
    prop: "expressCompany",
    minWidth: "140",
    formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressCompany || "--" : "--"),
  },
  {
    label: "快递单号",
    prop: "expressNo",
    minWidth: "150",
    formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressNo || "--" : "--"),
  },
  { label: "退货日期", prop: "makeTime", minWidth: "170" },
  {
    label: "退款总额",
    prop: "refundAmount",
    minWidth: "120",
    align: "right",
    formatData: (val) =>
      val === null || val === undefined || val === ""
        ? ""
@@ -172,27 +147,6 @@
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
const getSummaries = ({ columns, data }) => {
  const sums = [];
  columns.forEach((col, index) => {
    if (index === 0) {
      sums[index] = "合计";
    } else if (col.property === "refundAmount") {
      const total = data.reduce((prev, cur) => {
        const v = Number(cur.refundAmount);
        return prev + (isNaN(v) ? 0 : v);
      }, 0);
      sums[index] = total.toLocaleString("zh-CN", {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
      });
    } else {
      sums[index] = "";
    }
  });
  return sums;
};
const handleOut = () => {
src/views/financialManagement/voucher/index.vue
@@ -25,7 +25,7 @@
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
@@ -453,11 +453,6 @@
  return map[key] || "";
};
const onSearch = () => {
  pagination.currentPage = 1;
  getTableData();
};
// è”调约定:分页参数使用 current/size,日期范围拆分为 startDate/endDate
const getTableData = async () => {
  try {
@@ -523,9 +518,9 @@
  getTableData();
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
const changePage = ({ current, size }) => {
  pagination.currentPage = current;
  pagination.pageSize = size;
  getTableData();
};
src/views/index.vue
@@ -117,11 +117,10 @@
              <el-radio-button :value="'waiting'">待开始({{ orderProgressMeta.waitingCount }})</el-radio-button>
              <el-radio-button :value="'inProgress'">进行中({{ orderProgressMeta.inProgressCount }})</el-radio-button>
              <el-radio-button :value="'completed'">已完成({{ orderProgressMeta.completedCount }})</el-radio-button>
              <el-radio-button :value="'end'">已结束({{ orderProgressMeta.endCount }})</el-radio-button>
              <el-radio-button :value="'paused'">已暂停({{ orderProgressMeta.pausedCount }})</el-radio-button>
            </el-radio-group>
          </div>
          <div class="order-table-wrap">
            <el-table :data="filteredOrders" stripe max-height="440">
          <el-table :data="filteredOrders" stripe>
              <el-table-column prop="orderNo" label="订单编号" min-width="150" />
              <el-table-column prop="productName" label="产品名称" min-width="120" />
              <el-table-column prop="planQty" label="计划数量" min-width="90" />
@@ -148,7 +147,6 @@
                </template>
              </el-table-column>
            </el-table>
          </div>
        </div>
        <div v-if="visiblePanels.contract" class="cockpit-panel contract-panel">
@@ -216,7 +214,7 @@
        <div v-if="visiblePanels.todo" class="cockpit-panel todo-panel">
          <div class="panel-title-row">
            <div class="panel-title">待办事项</div>
            <span class="panel-more" @click="openTodoDialog">更多</span>
            <span class="panel-more" @click="goToApprovalProcess">更多</span>
          </div>
          <ul class="todo-list" v-if="todoList.length > 0">
            <li v-for="item in todoList" :key="item.id" class="todo-item">
@@ -234,6 +232,7 @@
        <div v-if="visiblePanels.realtime" class="cockpit-panel realtime-panel">
          <div class="panel-title-row">
            <div class="panel-title">生产实时看板</div>
            <span class="panel-more" @click="goToProductionOrder">更多</span>
          </div>
          <div class="realtime-grid">
            <div class="realtime-item" v-for="item in realtimeBoard" :key="item.key">
@@ -279,7 +278,7 @@
        <div v-if="visiblePanels.plan" class="cockpit-panel plan-panel">
          <div class="panel-title-row">
            <div class="panel-title">今日生产计划</div>
            <span class="panel-more">{{ todayPlanTotal }}项</span>
            <span class="panel-more" @click="goToProductionPlan">{{ todayPlanTotal }}项</span>
          </div>
          <ul class="plan-list">
            <li v-for="item in todayPlanList" :key="item.orderNo" class="plan-item">
@@ -333,18 +332,6 @@
          <el-button type="primary" @click="handleProcessDialogConfirm">确认</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="todoDialogVisible" title="全部待办事项" width="900px" append-to-body destroy-on-close>
      <div v-loading="todoDialogLoading" class="todo-dialog-body">
        <el-table :data="todoDialogList" stripe max-height="520">
          <el-table-column prop="approveId" label="待办编号" min-width="160" />
          <el-table-column prop="approveDeptName" label="部门/模板" min-width="160" />
          <el-table-column prop="approveReason" label="事由" min-width="240" show-overflow-tooltip />
          <el-table-column prop="approveUserName" label="发起人" min-width="120" />
          <el-table-column prop="approveTime" label="时间" min-width="170" />
        </el-table>
      </div>
    </el-dialog>
  </div>
</template>
@@ -462,7 +449,6 @@
});
const realtimeBoardData = ref({
  deviceOee: { value: 0, compareYesterday: 0 },
  orderAchievementRate: { value: 0, compareYesterday: 0 },
  defectRate: { value: 0, compareYesterday: 0 },
});
@@ -477,14 +463,11 @@
  waitingCount: 0,
  inProgressCount: 0,
  completedCount: 0,
  endCount: 0,
  pausedCount: 0,
});
const todayPlanList = ref([]);
const todayPlanTotal = ref(0);
const todoDialogVisible = ref(false);
const todoDialogLoading = ref(false);
const todoDialogList = ref([]);
const sum = ref(0);
const yny = ref(0);
@@ -852,12 +835,12 @@
const productionOrders = ref([]);
const orderFilterOptions = ["all", "waiting", "inProgress", "completed", "end"];
const orderFilterOptions = ["all", "waiting", "inProgress", "completed", "paused"];
const orderFilterAliasMap = {
  1: "waiting",
  2: "inProgress",
  3: "completed",
  5: "end",
  4: "paused",
};
const orderFilter = ref("all");
const filteredOrders = computed(() => productionOrders.value);
@@ -899,22 +882,11 @@
};
const realtimeBoard = computed(() => {
  const oee = Number(realtimeBoardData.value.deviceOee?.value || 0);
  const orderAchievement = Number(realtimeBoardData.value.orderAchievementRate?.value || 0);
  const defectRate = Number(realtimeBoardData.value.defectRate?.value || 0);
  const oeeCompare = Number(realtimeBoardData.value.deviceOee?.compareYesterday || 0);
  const orderCompare = Number(realtimeBoardData.value.orderAchievementRate?.compareYesterday || 0);
  const defectCompare = Number(realtimeBoardData.value.defectRate?.compareYesterday || 0);
  return [
    {
      key: "oee",
      label: "设备 OEE",
      percent: clampPercent(oee),
      display: `${oee.toFixed(2)}%`,
      delta: getCompareText(oeeCompare),
      trend: getCompareTrend(oeeCompare),
      color: "#2d8cff",
    },
    {
      key: "order",
      label: "订单达成率",
@@ -922,7 +894,7 @@
      display: `${orderAchievement.toFixed(2)}%`,
      delta: getCompareText(orderCompare),
      trend: getCompareTrend(orderCompare),
      color: "#31d2ff",
      color: "#2d8cff",
    },
    {
      key: "defect",
@@ -1031,8 +1003,8 @@
    pathPrefixes: ["/inventorymanagement"],
  },
  production: {
    titles: ["生产管控"],
    pathPrefixes: ["/productionManagement", "/productionPlan"],
    titles: ["生产管理", "主生产计划", "生产订单", "生产报工"],
    pathPrefixes: ["/productionmanagement", "/productionplan"],
  },
  quality: {
    titles: ["质量管理"],
@@ -1086,7 +1058,7 @@
  realtime: visibleModules.value.production,
  quick: quickEntries.value.length > 0,
  plan: visibleModules.value.production,
  receipt: visibleModules.value.finance,
  receipt: visibleModules.value.sales || visibleModules.value.finance,
}));
const hasLeftPanels = computed(
@@ -1157,7 +1129,7 @@
    1: "待开始",
    2: "进行中",
    3: "已完成",
    5: "已结束",
    4: "已暂停",
  };
  return mapping[status] || "未知";
};
@@ -1221,10 +1193,6 @@
    const res = await productionRealtimeBoard();
    const data = res?.data || {};
    realtimeBoardData.value = {
      deviceOee: {
        value: Number(data.deviceOee?.value || 0),
        compareYesterday: Number(data.deviceOee?.compareYesterday || 0),
      },
      orderAchievementRate: {
        value: Number(data.orderAchievementRate?.value || 0),
        compareYesterday: Number(data.orderAchievementRate?.compareYesterday || 0),
@@ -1236,7 +1204,6 @@
    };
  } catch {
    realtimeBoardData.value = {
      deviceOee: { value: 0, compareYesterday: 0 },
      orderAchievementRate: { value: 0, compareYesterday: 0 },
      defectRate: { value: 0, compareYesterday: 0 },
    };
@@ -1248,8 +1215,8 @@
    const res = await productionOrderProgress({
      status: orderFilter.value,
      tab: orderFilter.value,
      pageNum: -1,
      pageSize: -1,
      pageNum: 1,
      pageSize: 10,
    });
    const data = res?.data || {};
    const statusValue = normalizeOrderFilter(data.status, orderFilter.value);
@@ -1264,7 +1231,7 @@
      waitingCount: resolveProgressCount(data.waitingCount, statusValue, "waiting", total),
      inProgressCount: resolveProgressCount(data.inProgressCount, statusValue, "inProgress", total),
      completedCount: resolveProgressCount(data.completedCount, statusValue, "completed", total),
      endCount: resolveProgressCount(data.endCount, statusValue, "end", total),
      pausedCount: resolveProgressCount(data.pausedCount, statusValue, "paused", total),
    };
    productionOrders.value = (data.records || []).map(mapOrderProgressRecord);
  } catch {
@@ -1278,7 +1245,7 @@
      waitingCount: 0,
      inProgressCount: 0,
      completedCount: 0,
      endCount: 0,
      pausedCount: 0,
    };
    productionOrders.value = [];
  }
@@ -1287,7 +1254,7 @@
const refreshTodayProductionPlan = async () => {
  try {
    const res = await todayProductionPlan({
      limit: 1000,
      limit: 4,
      planDate: nowDate.value,
    });
    const data = res?.data || {};
@@ -1319,19 +1286,6 @@
const todoInfoS = async () => {
  const res = await homeTodos();
  todoList.value = res.data || [];
};
const openTodoDialog = async () => {
  todoDialogVisible.value = true;
  todoDialogLoading.value = true;
  try {
    const res = await homeTodos();
    todoDialogList.value = res.data || [];
  } catch {
    todoDialogList.value = [];
  } finally {
    todoDialogLoading.value = false;
  }
};
const statisticsReceivable = async () => {
@@ -1425,6 +1379,18 @@
const goToQuick = (path) => {
  if (!path) return;
  router.push(path).catch(() => {});
};
const goToProductionOrder = () => {
  router.push('/productionManagement/productionOrder').catch(() => {});
};
const goToApprovalProcess = () => {
  router.push('/collaborativeApproval/approvalProcess').catch(() => {});
};
const goToProductionPlan = () => {
  router.push('/productionManagement/productionPlan').catch(() => {});
};
watch(orderFilter, () => {
@@ -1584,12 +1550,9 @@
}
.stats-grid {
  display: flex;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}
.stats-grid .stat-card {
  flex: 1;
}
.stat-card {
@@ -1771,34 +1734,6 @@
  font-size: 14px;
  color: #2563eb;
  cursor: pointer;
}
.todo-dialog-body {
  min-height: 220px;
}
.plan-panel {
  overflow: hidden;
}
.plan-list {
  margin: 10px 0 0;
  padding: 0 4px 0 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 300px;
  overflow-y: auto;
}
.plan-list::-webkit-scrollbar {
  width: 6px;
}
.plan-list::-webkit-scrollbar-thumb {
  border-radius: 999px;
  background: rgba(37, 99, 235, 0.28);
}
.todo-list {
@@ -2154,7 +2089,7 @@
.realtime-grid {
  margin-top: 10px;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
@@ -2162,12 +2097,12 @@
  border: 1px solid rgba(148, 163, 184, 0.2);
  border-radius: 12px;
  background: #f8fbff;
  padding: 10px 8px;
  padding: 16px 12px;
  text-align: center;
}
.realtime-value {
  font-size: 26px;
  font-size: 32px;
  color: #0f172a;
  font-weight: 700;
}
@@ -2254,13 +2189,6 @@
.order-panel {
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.order-table-wrap {
  flex: 1;
  min-height: 0;
}
.quick-panel {
@@ -2300,12 +2228,25 @@
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.plan-panel {
  min-height: 236px;
}
.quality-panel {
  min-height: 0;
}
.receipt-panel {
  min-height: 340px;
}
.plan-list {
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.plan-item {
@@ -2460,6 +2401,10 @@
  .main-grid {
    grid-template-columns: minmax(0, 1fr) 340px;
  }
  .stats-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 1200px) {
@@ -2482,7 +2427,7 @@
  }
  .realtime-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .quality-cards {
@@ -2505,6 +2450,10 @@
    justify-content: flex-start;
  }
  .stats-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .right-column {
    grid-template-columns: 1fr;
  }
src/views/inventoryManagement/dispatchLog/Record.vue
@@ -1,14 +1,14 @@
<template>
  <div>
    <div class="search_form"
         style="margin-bottom: 10px">
      <el-form ref="searchFormRef"
    <div class="search_form" style="margin-bottom: 10px">
      <el-form
          ref="searchFormRef"
               :model="searchForm"
               class="demo-form-inline">
          class="demo-form-inline"
      >
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item label="出库日期"
                          prop="timeStr">
            <el-form-item label="出库日期" prop="timeStr">
              <el-date-picker v-model="searchForm.timeStr"
                              type="date"
                              placeholder="请选择日期"
@@ -18,17 +18,16 @@
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="产品大类"
                          prop="productName">
            <el-form-item label="产品大类" prop="productName">
              <el-input v-model="searchForm.productName"
                        style="width: 240px"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="规格型号"
                          prop="model">
            <el-form-item label="产品规格" prop="model">
              <el-input v-model="searchForm.model"
                        style="width: 240px"
                        placeholder="请输入"
@@ -36,8 +35,7 @@
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="批号"
                          prop="batchNo">
            <el-form-item label="批号" prop="batchNo">
              <el-input v-model="searchForm.batchNo"
                        style="width: 240px"
                        placeholder="请输入"
@@ -45,26 +43,29 @@
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="来源"
                          prop="recordType">
              <el-select v-model="searchForm.recordType"
            <el-form-item label="来源" prop="recordType">
              <el-select
                  v-model="searchForm.recordType"
                         style="width: 240px"
                         placeholder="请选择"
                         clearable>
                <el-option v-for="item in stockRecordTypeOptions"
                  clearable
              >
                <el-option
                    v-for="item in stockRecordTypeOptions"
                           :key="item.value"
                           :label="item.label"
                           :value="item.value" />
                    :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <!-- æŒ‰é’® -->
          <el-col :span="4">
            <el-form-item>
              <el-button type="primary"
                         @click="getList">
              <el-button type="primary" @click="getList">
                æœç´¢
              </el-button>
              <el-button @click="resetSearch">
                é‡ç½®
              </el-button>
@@ -74,83 +75,83 @@
      </el-form>
    </div>
    <div class="actions">
      <el-button type="primary"
                 @click="handleBatchApprove">审批</el-button>
      <el-button :disabled="!canReverseApprove"
                 @click="handleReverseApprove">反审</el-button>
      <!-- <el-button type="primary" @click="handleBatchApprove">审批</el-button> -->
      <el-button @click="handleOut">导出</el-button>
      <el-button type="danger"
                 plain
                 @click="handleDelete">删除</el-button>
      <el-button type="primary"
                 plain
                 @click="handlePrint">打印</el-button>
      <el-button type="danger" plain @click="handleDelete">删除</el-button>
      <el-button type="primary" plain @click="handlePrint">打印</el-button>
    </div>
    <div class="table_list">
      <el-table :data="tableData"
      <el-table
        :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="(row) => row.id"
                style="width: 100%"
                height="calc(100vh - 18.5em)">
        <el-table-column align="center"
                         type="selection"
                         width="55" />
        <el-table-column align="center"
                         label="序号"
                         type="index"
                         width="60" />
        <el-table-column label="出库批次"
        height="calc(100vh - 18.5em)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
          label="出库批次"
                         prop="outboundBatches"
                         min-width="100"
                         show-overflow-tooltip />
        <el-table-column label="出库日期"
          show-overflow-tooltip
        />
        <el-table-column
          label="出库日期"
                         prop="createTime"
                         show-overflow-tooltip />
        <el-table-column label="产品大类"
          width="120"
          show-overflow-tooltip
        >
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column
          label="产品大类"
                         prop="productName"
                         show-overflow-tooltip />
        <el-table-column label="规格型号"
                         prop="model"
                         show-overflow-tooltip />
        <el-table-column label="批号"
                         prop="batchNo"
                         show-overflow-tooltip />
        <el-table-column label="单位"
                         prop="unit"
                         show-overflow-tooltip />
        <el-table-column label="出库数量"
          show-overflow-tooltip
        />
        <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
        <el-table-column label="批号" prop="batchNo" width="150" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="80" show-overflow-tooltip />
        <el-table-column
          label="出库数量"
                         prop="stockOutNum"
                         show-overflow-tooltip />
        <el-table-column label="出库人"
                         prop="createBy"
                         show-overflow-tooltip />
        <el-table-column label="来源"
                         prop="recordType"
                         show-overflow-tooltip>
          width="80"
          show-overflow-tooltip
        />
        <el-table-column label="出库人" prop="createBy" width="100" show-overflow-tooltip />
        <el-table-column label="来源" prop="recordType" show-overflow-tooltip>
          <template #default="scope">
            {{ getRecordType(scope.row.recordType) }}
          </template>
        </el-table-column>
        <el-table-column label="审批状态"
        <!-- <el-table-column
          label="审批状态"
                         prop="approvalStatus"
                         show-overflow-tooltip>
          show-overflow-tooltip
        >
          <template #default="scope">
            <el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)"
                    size="small">
            <el-tag
              :type="getApprovalStatusTagType(scope.row.approvalStatus)"
              size="small"
            >
              {{ getApprovalStatusLabel(scope.row.approvalStatus) }}
            </el-tag>
          </template>
        </el-table-column>
        </el-table-column> -->
      </el-table>
      <pagination v-show="total > 0"
      <pagination
        v-show="total > 0"
                  :total="total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current"
                  :limit="page.size"
                  @pagination="paginationChange" />
        @pagination="paginationChange"
      />
    </div>
  </div>
</template>
@@ -165,12 +166,12 @@
    getStockOutPage,
    delPendingStockOut,
    batchApproveStockOutRecords,
    batchUnapproveStockOutRecords,
  } from "@/api/inventoryManagement/stockOut.js";
  import {
    findAllQualifiedStockOutRecordTypeOptions,
    findAllUnQualifiedStockOutRecordTypeOptions,
  } from "@/api/basicData/enum.js";
import { parseTime } from "@/utils/ruoyi";
  const userStore = useUserStore();
  const { proxy } = getCurrentInstance();
@@ -217,9 +218,9 @@
    searchFormRef.value?.resetFields();
    page.current = 1;
    getList();
  };
}
  const paginationChange = obj => {
const paginationChange = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
@@ -231,10 +232,10 @@
      ...page,
      topParentProductId: props.topParentProductId,
    })
      .then(res => {
    .then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        tableData.value.map(item => {
      tableData.value.map((item) => {
          item.children = [];
        });
        total.value = res.data.total;
@@ -244,9 +245,9 @@
      });
  };
  const getRecordType = recordType => {
const getRecordType = (recordType) => {
    return (
      stockRecordTypeOptions.value.find(item => item.value === recordType)
    stockRecordTypeOptions.value.find((item) => item.value === recordType)
        ?.label || ""
    );
  };
@@ -264,7 +265,7 @@
    REJECTED: "驳回",
  };
  const getApprovalStatusLabel = status => {
const getApprovalStatusLabel = (status) => {
    if (status === null || status === undefined || status === "") {
      return "待审批";
    }
@@ -272,7 +273,7 @@
  };
  // é€šè¿‡/驳回固定色;其余(含待审批、空值、未映射但文案为待审批)统一用 warning é¢„警色
  const getApprovalStatusTagType = status => {
const getApprovalStatusTagType = (status) => {
    if (
      status === 1 ||
      status === "1" ||
@@ -293,60 +294,22 @@
  // èŽ·å–æ¥æºç±»åž‹é€‰é¡¹
  const fetchStockRecordTypeOptions = () => {
    if (props.type === "0") {
      findAllQualifiedStockOutRecordTypeOptions().then(res => {
    findAllQualifiedStockOutRecordTypeOptions().then((res) => {
        stockRecordTypeOptions.value = res.data;
      });
      return;
    }
    findAllUnQualifiedStockOutRecordTypeOptions().then(res => {
  findAllUnQualifiedStockOutRecordTypeOptions().then((res) => {
      stockRecordTypeOptions.value = res.data;
    });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
const handleSelectionChange = (selection) => {
    // è¿‡æ»¤æŽ‰å­æ•°æ®
    selectedRows.value = selection.filter(item => item.id);
  selectedRows.value = selection.filter((item) => item.id);
    console.log("selection", selectedRows.value);
  };
  const isRejectedApproval = status => {
    return status === 2 || status === "2" || status === "rejected" || status === "REJECTED";
  };
  const canReverseApprove = computed(() => {
    return (
      selectedRows.value.length > 0 &&
      selectedRows.value.every(row => isRejectedApproval(row.approvalStatus))
    );
  });
  const handleReverseApprove = () => {
    if (!canReverseApprove.value) {
      proxy.$modal.msgWarning("请选择驳回状态的数据");
      return;
    }
    const ids = selectedRows.value.map(item => item.id);
    ElMessageBox.confirm("反审后记录将恢复为待审批状态,是否确认反审?", "反审", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        batchUnapproveStockOutRecords({ ids })
          .then(() => {
            proxy.$modal.msgSuccess("反审成功");
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError("反审失败");
          });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  const expandedRowKeys = ref([]);
  const handleBatchApprove = () => {
@@ -354,7 +317,7 @@
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    const ids = selectedRows.value.map(item => item.id);
  const ids = selectedRows.value.map((item) => item.id);
    ElMessageBox.confirm("请选择审批结果", "审批", {
      confirmButtonText: "通过",
      cancelButtonText: "驳回",
@@ -371,7 +334,7 @@
            proxy.$modal.msgError("审批通过失败");
          });
      })
      .catch(action => {
    .catch((action) => {
        if (action === "cancel") {
          batchApproveStockOutRecords({ ids, approvalStatus: 2 })
            .then(() => {
@@ -410,7 +373,7 @@
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.id);
    ids = selectedRows.value.map((item) => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
@@ -421,7 +384,7 @@
      type: "warning",
    })
      .then(() => {
        delPendingStockOut(ids).then(res => {
      delPendingStockOut(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
@@ -700,7 +663,7 @@
  };
  // æ ¼å¼åŒ–日期
  const formatDate = dateString => {
const formatDate = (dateString) => {
    if (!dateString) return getCurrentDate();
    const date = new Date(dateString);
    const year = date.getFullYear();
@@ -710,7 +673,7 @@
  };
  // æ ¼å¼åŒ–日期时间
  const formatDateTime = date => {
const formatDateTime = (date) => {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
src/views/inventoryManagement/receiptManagement/Record.vue
@@ -1,14 +1,14 @@
<template>
  <div>
    <div class="search_form"
         style="margin-bottom: 10px;">
      <el-form ref="searchFormRef"
    <div class="search_form" style="margin-bottom: 10px;">
      <el-form
          ref="searchFormRef"
               :model="searchForm"
               class="demo-form-inline">
          class="demo-form-inline"
      >
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item label="入库日期"
                          prop="timeStr">
            <el-form-item label="入库日期" prop="timeStr">
              <el-date-picker v-model="searchForm.timeStr"
                              type="date"
                              placeholder="请选择日期"
@@ -18,17 +18,16 @@
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="产品大类"
                          prop="productName">
            <el-form-item label="产品大类" prop="productName">
              <el-input v-model="searchForm.productName"
                        style="width: 240px"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="规格型号"
                          prop="model">
            <el-form-item label="产品规格" prop="model">
              <el-input v-model="searchForm.model"
                        style="width: 240px"
                        placeholder="请输入"
@@ -36,8 +35,7 @@
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="批号"
                          prop="batchNo">
            <el-form-item label="批号" prop="batchNo">
              <el-input v-model="searchForm.batchNo"
                        style="width: 240px"
                        placeholder="请输入"
@@ -45,8 +43,7 @@
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="来源"
                          prop="recordType">
            <el-form-item label="来源" prop="recordType">
              <el-select v-model="searchForm.recordType"
                         style="width: 240px"
                         placeholder="请选择"
@@ -61,10 +58,10 @@
          <!-- æŒ‰é’® -->
          <el-col :span="4">
            <el-form-item>
              <el-button type="primary"
                         @click="getList">
              <el-button type="primary" @click="getList">
                æœç´¢
              </el-button>
              <el-button @click="resetSearch">
                é‡ç½®
              </el-button>
@@ -74,11 +71,11 @@
      </el-form>
    </div>
    <div class="actions">
      <el-button type="primary"
      <!-- <el-button type="primary"
                 :disabled="!canBatchApprove"
                 @click="handleBatchApprove">审批</el-button>
      <el-button :disabled="!canReverseApprove"
                 @click="handleReverseApprove">反审</el-button>
                 @click="handleReverseApprove">反审</el-button> -->
      <el-button @click="handleOut">导出</el-button>
      <el-button type="danger"
                 plain
@@ -105,11 +102,16 @@
                         width="60" />
        <el-table-column label="入库批次"
                         prop="inboundBatches"
                         width="200"
                         width="150"
                         show-overflow-tooltip />
        <el-table-column label="入库时间"
                         prop="createTime"
                         show-overflow-tooltip />
                         width="120"
                         show-overflow-tooltip>
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="产品大类"
                         prop="productName"
                         show-overflow-tooltip />
@@ -118,6 +120,7 @@
                         show-overflow-tooltip />
        <el-table-column label="批号"
                         prop="batchNo"
                         width="180"
                         show-overflow-tooltip />
        <el-table-column label="单位"
                         prop="unit"
@@ -135,7 +138,8 @@
            {{ getRecordType(scope.row.recordType) }}
          </template>
        </el-table-column>
        <el-table-column v-if="showSourceOrderNoColumn"
        <el-table-column
            v-if="showSourceOrderNoColumn"
                         label="源单号"
                         width="150"
                         prop="sourceOrderNo"
@@ -144,16 +148,15 @@
            {{ formatSourceOrderNo(scope.row?.sourceOrderNo) }}
          </template>
        </el-table-column>
        <el-table-column label="审批状态"
        <!-- <el-table-column label="审批状态"
                         prop="approvalStatus"
                         show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)"
                    size="small">
            <el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" size="small">
              {{ getApprovalStatusLabel(scope.row.approvalStatus) }}
            </el-tag>
          </template>
        </el-table-column>
        </el-table-column> -->
      </el-table>
      <pagination v-show="total > 0"
                  :total="total"
@@ -186,6 +189,7 @@
    findAllQualifiedStockInRecordTypeOptions,
    // findAllUnQualifiedStockInRecordTypeOptions,
  } from "@/api/basicData/enum.js";
import { parseTime } from "@/utils/ruoyi";
  const { proxy } = getCurrentInstance();
@@ -193,13 +197,13 @@
    type: {
      type: String,
      required: true,
      default: "0",
    default: '0'
    },
    topParentProductId: {
      type: [String, Number],
      default: undefined,
    },
  });
    default: undefined
  }
})
  const tableData = ref([]);
  const selectedRows = ref([]);
@@ -228,14 +232,11 @@
    searchFormRef.value?.resetFields();
    page.current = 1;
    getList();
  };
}
  const getRecordType = recordType => {
    return (
      stockRecordTypeOptions.value.find(item => item.value === recordType)
        ?.label || ""
    );
  };
const getRecordType = (recordType) => {
  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
}
  const approvalStatusLabelMap = {
    0: "待审批",
@@ -250,7 +251,7 @@
  };
  approvalStatusLabelMap[3] = "待确认";
  const getApprovalStatusLabel = status => {
const getApprovalStatusLabel = (status) => {
    if (status === null || status === undefined || status === "") {
      return "待审批";
    }
@@ -258,73 +259,41 @@
  };
  // é€šè¿‡/驳回固定色;其余(含待审批、空值、未映射但文案为待审批)统一用 warning é¢„警色
  const getApprovalStatusTagType = status => {
    if (
      status === 1 ||
      status === "1" ||
      status === "approved" ||
      status === "APPROVED"
    )
      return "success";
    if (
      status === 2 ||
      status === "2" ||
      status === "rejected" ||
      status === "REJECTED"
    )
      return "danger";
const getApprovalStatusTagType = (status) => {
  if (status === 1 || status === "1" || status === "approved" || status === "APPROVED") return "success";
  if (status === 2 || status === "2" || status === "rejected" || status === "REJECTED") return "danger";
    return "warning";
  };
  const isPendingApproval = status => {
    return (
      status === 0 ||
      status === "0" ||
      status === "pending" ||
      status === "PENDING" ||
      status === null ||
      status === undefined ||
      status === ""
    );
  return status === 0 || status === "0" || status === "pending" || status === "PENDING" || status === null || status === undefined || status === "";
  };
  const isRejectedApproval = status => {
    return (
      status === 2 ||
      status === "2" ||
      status === "rejected" ||
      status === "REJECTED"
    );
  return status === 2 || status === "2" || status === "rejected" || status === "REJECTED";
  };
  const isRowSelectable = row => {
    return (
      isPendingApproval(row?.approvalStatus) ||
      isRejectedApproval(row?.approvalStatus)
    );
  return isPendingApproval(row?.approvalStatus) || isRejectedApproval(row?.approvalStatus);
  };
  const canBatchApprove = computed(() => {
    return (
      selectedRows.value.length > 0 &&
      selectedRows.value.every(row => isPendingApproval(row.approvalStatus))
    );
  return selectedRows.value.length > 0
      && selectedRows.value.every(row => isPendingApproval(row.approvalStatus));
  });
  const canReverseApprove = computed(() => {
    return (
      selectedRows.value.length > 0 &&
      selectedRows.value.every(row => isRejectedApproval(row.approvalStatus))
    );
  return selectedRows.value.length > 0
      && selectedRows.value.every(row => isRejectedApproval(row.approvalStatus));
  });
  const canDelete = computed(() => canBatchApprove.value);
  const showSourceOrderNoColumn = computed(() => {
    const topParentProductId = Number(props.topParentProductId);
    return topParentProductId === 276 || topParentProductId === 278;
  return topParentProductId === 1 || topParentProductId === 3;
  });
  const formatSourceOrderNo = value => {
const formatSourceOrderNo = (value) => {
    const text = String(value ?? "").trim();
    return text || "--";
  };
@@ -337,44 +306,33 @@
  const getList = () => {
    tableLoading.value = true;
    getStockInRecordListPage(
      Object.assign(
        {},
        {
          ...searchForm.value,
          ...page,
          topParentProductId: props.topParentProductId,
        }
      )
    )
  getStockInRecordListPage(Object.assign({}, {...searchForm.value, ...page,  topParentProductId: props.topParentProductId}))
      .then(res => {
        tableData.value = res.data.records;
        total.value = res.data.total || 0;
      })
      .finally(() => {
      }).finally(() => {
        tableLoading.value = false;
      });
  })
  };
  // èŽ·å–æ¥æºç±»åž‹é€‰é¡¹
  const fetchStockRecordTypeOptions = () => {
    if (props.type === "0") {
      findAllQualifiedStockInRecordTypeOptions().then(res => {
  if (props.type === '0') {
    findAllQualifiedStockInRecordTypeOptions()
        .then(res => {
        stockRecordTypeOptions.value = res.data;
      });
      return;
        })
    return
    }
    // findAllUnQualifiedStockInRecordTypeOptions()
    //     .then(res => {
    //       stockRecordTypeOptions.value = res.data;
    //     })
  };
}
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection.filter(
      item => item.id && isRowSelectable(item)
    );
  selectedRows.value = selection.filter(item => item.id && isRowSelectable(item));
  };
  const expandedRowKeys = ref([]);
@@ -427,7 +385,7 @@
            proxy.$modal.msgError("审批通过失败");
          });
      })
      .catch(action => {
      .catch((action) => {
        if (action === "cancel") {
          batchApproveStockInRecords({ ids, approvalStatus: 2 })
            .then(() => {
@@ -452,11 +410,7 @@
    })
      .then(() => {
        // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„导出接口
        proxy.download(
          "/stockInRecord/exportStockInRecord",
          { type: props.type },
          props.type === "0" ? "合格入库.xlsx" : "不合格入库.xlsx"
        );
        proxy.download("/stockInRecord/exportStockInRecord", {type: props.type}, props.type === '0' ? "合格入库.xlsx" : "不合格入库.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue
@@ -29,12 +29,12 @@
            prop="qualifiedQuantity"
            show-overflow-tooltip
          />
          <el-table-column
          <!-- <el-table-column
            label="不合格库存数量"
            prop="unQualifiedQuantity"
            show-overflow-tooltip
          />
          <el-table-column
          /> -->
          <!-- <el-table-column
            label="合格冻结数量"
            prop="qualifiedLockedQuantity"
            show-overflow-tooltip
@@ -43,7 +43,7 @@
            label="不合格冻结数量"
            prop="unQualifiedLockedQuantity"
            show-overflow-tooltip
          />
          /> -->
          <el-table-column
            label="库存预警数量"
            prop="warnNum"
@@ -55,7 +55,7 @@
            prop="updateTime"
            show-overflow-tooltip
          />
          <el-table-column fixed="right" label="操作" min-width="180" align="center">
          <el-table-column fixed="right" label="操作" min-width="100" align="center">
            <template #default="scope">
              <el-button
                link
@@ -71,7 +71,7 @@
                "
                >领用</el-button
              >
              <el-button
              <!-- <el-button
                link
                type="primary"
                v-if="
@@ -80,7 +80,7 @@
                "
                @click="handleFrozen(scope.row)"
                >冻结</el-button
              >
              > -->
              <el-button
                link
                type="primary"
src/views/inventoryManagement/stockManagement/New.vue
@@ -45,16 +45,15 @@
                     placeholder="请选择库存类型">
            <el-option label="合格库存"
                       value="qualified" />
            <el-option label="不合格库存"
                       value="unqualified" />
            <!-- <el-option label="不合格库存"
                       value="unqualified" /> -->
          </el-select>
        </el-form-item>
        <el-form-item label="库存数量"
                      prop="qualitity">
          <el-input-number v-model="formState.qualitity"
                           :step="0.01"
                           :min="0.01"
                           :precision="2"
                           :step="1"
                           :min="1"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="批号"
@@ -66,10 +65,9 @@
                      label="库存预警数量"
                      prop="warnNum">
          <el-input-number v-model="formState.warnNum"
                           :step="0.01"
                           :step="1"
                           :min="0"
                           :max="formState.qualitity"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注"
src/views/inventoryManagement/stockManagement/Record.vue
@@ -1,22 +1,23 @@
<template>
  <div>
    <div class="search_form mb10">
      <el-form ref="searchFormRef"
      <el-form
          ref="searchFormRef"
               :model="searchForm"
               class="demo-form-inline">
          class="demo-form-inline"
      >
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item label="产品大类"
                          prop="productName">
            <el-form-item label="产品大类" prop="productName">
              <el-input v-model="searchForm.productName"
                        style="width: 240px"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="规格型号"
                          prop="model">
            <el-form-item label="产品规格" prop="model">
              <el-input v-model="searchForm.model"
                        style="width: 240px"
                        placeholder="请输入"
@@ -24,42 +25,46 @@
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="批号"
                          prop="batchNo">
            <el-form-item label="批号" prop="batchNo">
              <el-input v-model="searchForm.batchNo"
                        style="width: 240px"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <!-- æŒ‰é’® -->
          <el-col :span="4">
          <el-col :span="24" :sm="24" :md="12" :lg="12">
            <el-form-item>
              <el-button type="primary"
                         @click="getList">
              <div style="display: flex; align-items: center; gap: 4px; width: 100%; flex-wrap: wrap;">
                <el-button type="primary" @click="getList">
                æœç´¢
              </el-button>
              <el-button @click="resetSearch">
                é‡ç½®
              </el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div style="display: flex; gap: 10px; flex-wrap: nowrap;">
        <el-button type="primary"
                   @click="isShowNewModal = true">新增库存</el-button>
        <el-button type="info"
                <div style="display: flex; gap: 4px; margin-left: auto;">
                  <el-button type="primary" @click="isShowNewModal = true">
                    æ–°å¢žåº“å­˜
                  </el-button>
                  <el-button
                    type="info"
                   plain
                   icon="Upload"
                   @click="isShowImportModal = true">
                    @click="isShowImportModal = true"
                  >
          å¯¼å…¥åº“å­˜
        </el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
    </div>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <div class="table_list">
      <el-table :data="tableData"
      <el-table
        :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
@@ -67,92 +72,112 @@
                :row-key="(row, index) => index"
                style="width: 100%"
                :row-class-name="tableRowClassName"
                height="calc(100vh - 18.5em)">
        <el-table-column align="center"
                         type="selection"
                         width="55" />
        <el-table-column align="center"
                         label="序号"
                         type="index"
                         width="60" />
        <el-table-column label="产品名称"
        height="calc(100vh - 18.5em)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
          label="产品名称"
                         prop="productName"
                         show-overflow-tooltip />
        <el-table-column label="规格型号"
                         prop="model"
                         show-overflow-tooltip />
        <el-table-column label="单位"
                         prop="unit"
                         show-overflow-tooltip />
        <el-table-column label="批号"
                         prop="batchNo"
                         show-overflow-tooltip />
        <el-table-column label="合格库存数量"
          show-overflow-tooltip
        />
        <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" show-overflow-tooltip />
        <!-- <el-table-column label="批号" prop="batchNo" show-overflow-tooltip /> -->
        <el-table-column
          label="合格库存数量"
                         prop="qualifiedQuantity"
                         show-overflow-tooltip />
        <el-table-column label="不合格库存数量"
          show-overflow-tooltip
        />
        <!-- <el-table-column
          label="不合格库存数量"
                         prop="unQualifiedQuantity"
                         show-overflow-tooltip />
        <el-table-column label="合格冻结数量"
          show-overflow-tooltip
        /> -->
        <!-- <el-table-column
          label="合格冻结数量"
                         prop="qualifiedLockedQuantity"
                         show-overflow-tooltip />
        <el-table-column label="不合格冻结数量"
          show-overflow-tooltip
        />
        <el-table-column
          label="不合格冻结数量"
                         prop="unQualifiedLockedQuantity"
                         show-overflow-tooltip />
        <el-table-column label="库存预警数量"
          show-overflow-tooltip
        /> -->
       <el-table-column
          label="库存预警数量"
                         prop="warnNum"
                         show-overflow-tooltip />
        <el-table-column label="备注"
                         prop="remark"
                         show-overflow-tooltip />
        <el-table-column label="最近更新时间"
          show-overflow-tooltip
        />
        <el-table-column label="备注" prop="remark" show-overflow-tooltip />
        <el-table-column
          label="最近更新时间"
                         prop="updateTime"
                         show-overflow-tooltip />
        <el-table-column fixed="right"
          show-overflow-tooltip
        />
        <el-table-column
          fixed="right"
                         label="操作"
                         min-width="80"
                         align="center">
          align="center"
        >
          <template #default="scope">
            <el-button link
            <el-button
              link
                       type="primary"
                       @click="showDetailModal(scope.row)">详情</el-button>
              @click="showDetailModal(scope.row)"
              >详情</el-button
            >
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0"
      <pagination
        v-show="total > 0"
                  :total="total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current"
                  :limit="page.size"
                  @pagination="paginationChange" />
        @pagination="paginationChange"
      />
    </div>
    <batch-no-qty-detail v-if="isShowDetailModal"
    <batch-no-qty-detail
      v-if="isShowDetailModal"
                         v-model:visible="isShowDetailModal"
                         :record="record"
                         @subtract="handleDetailSubtract"
                         @frozen="handleDetailFrozen"
                         @thaw="handleDetailThaw" />
    <new-stock-inventory v-if="isShowNewModal"
      @thaw="handleDetailThaw"
    />
    <new-stock-inventory
      v-if="isShowNewModal"
                         v-model:visible="isShowNewModal"
                         :top-product-parent-id="props.productId"
                         @completed="handleQuery" />
    <subtract-stock-inventory v-if="isShowSubtractModal"
      @completed="handleQuery"
    />
    <subtract-stock-inventory
      v-if="isShowSubtractModal"
                              v-model:visible="isShowSubtractModal"
                              :record="record"
                              :type="record.stockType"
                              @completed="handleQuery" />
      @completed="handleQuery"
    />
    <!-- å¯¼å…¥åº“å­˜-->
    <import-stock-inventory v-if="isShowImportModal"
    <import-stock-inventory
      v-if="isShowImportModal"
                            v-model:visible="isShowImportModal"
                            type="qualified"
                            @uploadSuccess="handleQuery" />
      @uploadSuccess="handleQuery"
    />
    <!-- å†»ç»“/解冻库存-->
    <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
    <frozen-and-thaw-stock-inventory
      v-if="isShowFrozenAndThawModal"
                                     v-model:visible="isShowFrozenAndThawModal"
                                     :record="record"
                                     :operation-type="operationType"
                                     :type="record.stockType"
                                     @completed="handleQuery" />
      @completed="handleQuery"
    />
  </div>
</template>
@@ -221,7 +246,7 @@
    searchFormRef.value?.resetFields();
    page.current = 1;
    getList();
  };
}
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -229,7 +254,7 @@
    page.current = 1;
    getList();
  };
  const paginationChange = obj => {
const paginationChange = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
@@ -237,7 +262,7 @@
  const getList = () => {
    tableLoading.value = true;
    getStockInventoryListPageCombined({ ...searchForm.value, ...page })
      .then(res => {
    .then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        total.value = res.data.total;
@@ -249,7 +274,7 @@
      });
  };
  const handleFileSuccess = response => {
const handleFileSuccess = (response) => {
    const { code, msg } = response;
    if (code == 200) {
      ElMessage({ message: "导入成功", type: "success" });
@@ -261,13 +286,13 @@
  };
  // ç‚¹å‡»é¢†ç”¨
  const showSubtractModal = row => {
const showSubtractModal = (row) => {
    record.value = row;
    isShowSubtractModal.value = true;
  };
  // ç‚¹å‡»è¯¦æƒ…
  const showDetailModal = row => {
const showDetailModal = (row) => {
    if (!row?.productId || !row?.productModelId) {
      proxy.$modal.msgError("当前数据缺少产品ID或规格型号ID");
      return;
@@ -276,39 +301,39 @@
    isShowDetailModal.value = true;
  };
  const handleDetailSubtract = row => {
const handleDetailSubtract = (row) => {
    isShowDetailModal.value = false;
    showSubtractModal(row);
  };
  const handleDetailFrozen = row => {
const handleDetailFrozen = (row) => {
    isShowDetailModal.value = false;
    showFrozenModal(row);
  };
  const handleDetailThaw = row => {
const handleDetailThaw = (row) => {
    isShowDetailModal.value = false;
    showThawModal(row);
  };
  // ç‚¹å‡»å†»ç»“
  const showFrozenModal = row => {
const showFrozenModal = (row) => {
    record.value = row;
    isShowFrozenAndThawModal.value = true;
    operationType.value = "frozen";
  };
  // ç‚¹å‡»è§£å†»
  const showThawModal = row => {
const showThawModal = (row) => {
    record.value = row;
    isShowFrozenAndThawModal.value = true;
    operationType.value = "thaw";
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
const handleSelectionChange = (selection) => {
    // è¿‡æ»¤æŽ‰å­æ•°æ®
    selectedRows.value = selection.filter(item => item.id);
  selectedRows.value = selection.filter((item) => item.id);
    console.log("selection", selectedRows.value);
  };
  const expandedRowKeys = ref([]);
src/views/login.vue
@@ -2,16 +2,16 @@
  <div class="login-page">
    <main class="page">
      <section class="factory">
<!--        <div class="brand hero-brand">-->
<!--          <div class="logo hero-logo">-->
<!--            <img-->
<!--              :src="brandLogoUrl"-->
<!--              :alt="`${companyName} logo`"-->
<!--              class="logo-image hero-logo-image"-->
<!--              @error="handleLogoError"-->
<!--            />-->
<!--          </div>-->
<!--        </div>-->
        <div class="brand hero-brand">
          <div class="logo hero-logo">
            <img
              :src="brandLogoUrl"
              :alt="`${companyName} logo`"
              class="logo-image hero-logo-image"
              @error="handleLogoError"
            />
          </div>
        </div>
        <div class="hero">
          <div class="chip">数字工厂 Â· æ™ºèƒ½æŽ’产 Â· è®¾å¤‡äº’联 Â· è´¨é‡è¿½æº¯</div>
src/views/monitor/operlog/index.vue
@@ -125,7 +125,7 @@
         </el-table-column>
         <el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
            <template #default="scope">
               <span>{{ parseTime(scope.row.operTime) }}</span>
               <span>{{ parseTime(scope.row.operTime, '{y}-{m}-{d}') }}</span>
            </template>
         </el-table-column>
         <el-table-column label="消耗时间" align="center" prop="costTime" width="110" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']">
@@ -165,10 +165,10 @@
               <el-col :span="24">
                  <el-form-item label="操作方法:">{{ form.method }}</el-form-item>
               </el-col>
               <el-col :span="24">
               <el-col :span="24" v-if="form.status === 1">
                  <el-form-item label="请求参数:">{{ form.operParam }}</el-form-item>
               </el-col>
               <el-col :span="24">
               <el-col :span="24" v-if="form.status === 1">
                  <el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
               </el-col>
               <el-col :span="8">
@@ -181,7 +181,7 @@
                  <el-form-item label="消耗时间:">{{ form.costTime }}毫秒</el-form-item>
               </el-col>
               <el-col :span="8">
                  <el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
                  <el-form-item label="操作时间:">{{ parseTime(form.operTime, '{y}-{m}-{d}') }}</el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
@@ -37,17 +37,11 @@
          <el-option v-for="item in shifts_list"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value"
                     :disabled="
              operationType === 'add' &&
              (String(item.label || '').includes('假') ||
                String(item.label || '').includes('休'))
            " />
                     :value="item.value" />
        </el-select>
      </el-form-item>
      <el-form-item label="打卡范围(m)"
                    prop="radius"
                    v-show="!hideClockFields">
                    prop="radius">
        <el-input-number v-model="form.radius"
                         :min="10"
                         :max="1000"
@@ -57,8 +51,7 @@
      </el-form-item>
      <!-- é«˜å¾·åœ°å›¾é€‰æ‹© -->
      <el-form-item label="打卡位置"
                    prop="longitude"
                    v-show="!hideClockFields">
                    prop="longitude">
        <div class="map-container">
          <div class="map-header"
               style="margin-bottom: 10px">
@@ -89,16 +82,14 @@
        </div>
      </el-form-item>
      <el-form-item label="地点名称"
                    prop="locationName"
                    v-show="!hideClockFields">
                    prop="locationName">
        <el-input v-model="form.locationName"
                  :disabled="operationType === 'view'"
                  placeholder="请输入地点名称" />
      </el-form-item>
      <!-- ä¸Šä¸‹ç­æ—¶é—´ -->
      <el-form-item label="上班时间"
                    prop="startAt"
                    v-show="!hideClockFields">
                    prop="startAt">
        <el-time-picker v-model="form.startAt"
                        format="HH:mm"
                        value-format="HH:mm"
@@ -106,8 +97,7 @@
                        :disabled="operationType === 'view'" />
      </el-form-item>
      <el-form-item label="下班时间"
                    prop="endAt"
                    v-show="!hideClockFields">
                    prop="endAt">
        <el-time-picker v-model="form.endAt"
                        format="HH:mm"
                        value-format="HH:mm"
@@ -120,9 +110,7 @@
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button type="primary"
                   @click="submitForm"
                   v-if="operationType !== 'view'">确定</el-button>
        <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">确定</el-button>
        <el-button @click="dialogVisible = false">取消</el-button>
      </span>
    </template>
@@ -169,21 +157,6 @@
  // èŽ·å–ç­æ¬¡å­—å…¸å€¼
  const { shifts_list } = useDict("shifts_list");
  const selectedShiftLabel = computed(() => {
    const item = shifts_list.value?.find(
      s => String(s.value) === String(form.shift)
    );
    return item?.label || "";
  });
  const hideClockFields = computed(() => {
    if (props.operationType === "view") return false;
    return (
      selectedShiftLabel.value.includes("假") ||
      selectedShiftLabel.value.includes("休")
    );
  });
  // è¡¨å•数据
  const formRef = ref();
  const form = reactive({
@@ -199,33 +172,20 @@
  });
  // è¡¨å•验证规则
  const rules = computed(() => {
    const base = {
  const rules = {
      sysDeptId: [{ required: true, message: "请选择部门", trigger: "change" }],
      shift: [{ required: true, message: "请选择班次", trigger: "change" }],
    };
    if (hideClockFields.value) {
      return base;
    }
    return {
      ...base,
      locationName: [
        { required: true, message: "请输入地点名称", trigger: "blur" },
      ],
      longitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }],
      latitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }],
    shift: [{ required: true, message: "请选择班次", trigger: "change" }],
      radius: [{ required: true, message: "请输入打卡范围", trigger: "blur" }],
      startAt: [{ required: true, message: "请选择上班时间", trigger: "change" }],
      endAt: [
        { required: true, message: "请选择下班时间", trigger: "change" },
        {
          validator: (rule, value, callback) => {
            if (form.shift && ["夜班", "夜"].includes(form.shift)) {
              callback();
              return;
            }
            if (form.startAt && value) {
              const startParts = form.startAt.split(":");
              const endParts = value.split(":");
@@ -245,7 +205,6 @@
        },
      ],
    };
  });
  // éƒ¨é—¨é€‰é¡¹
  const deptOptions = ref([]);
@@ -429,8 +388,8 @@
  watch(
    () => form.radius,
    newValue => {
      if (circle && newValue != null && newValue !== "") {
        circle.setRadius(Number(newValue));
      if (circle) {
        circle.setRadius(newValue);
      }
    }
  );
@@ -441,29 +400,6 @@
    () => {
      if (formRef.value && form.endAt) {
        formRef.value.validateField("endAt");
      }
    }
  );
  watch(
    () => hideClockFields.value,
    val => {
      if (!val) return;
      form.radius = null;
      form.longitude = "";
      form.latitude = "";
      form.locationName = "";
      form.startAt = "";
      form.endAt = "";
      if (formRef.value) {
        formRef.value.clearValidate([
          "radius",
          "longitude",
          "latitude",
          "locationName",
          "startAt",
          "endAt",
        ]);
      }
    }
  );
@@ -501,9 +437,7 @@
        // åˆå§‹åŒ–地图
        setTimeout(() => {
          if (!hideClockFields.value) {
            initMap();
          }
        }, 100);
      }
    }
src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
@@ -13,37 +13,30 @@
        </el-button>
      </div>
    </div>
    <el-form :model="searchForm"
    <!-- æŸ¥è¯¢æ¡ä»¶ -->
    <!-- <el-form :model="searchForm"
             :inline="true"
             class="search-form mb16">
      <el-form-item label="部门:"
                    prop="sysDeptId">
        <el-tree-select v-model="searchForm.sysDeptId"
                    prop="countId">
        <el-tree-select v-model="searchForm.countId"
                        :data="deptOptions"
                        :props="{ value: 'id', label: 'label', children: 'children' }"
                        value-key="id"
                        placeholder="请选择部门"
                        check-strictly
                        style="width: 200px" />
      </el-form-item>
      <el-form-item label="地点:"
                    prop="locationName">
        <el-input v-model="searchForm.locationName"
                  placeholder="请输入地点名称"
                        clearable
                        style="width: 200px" />
      </el-form-item>
      <el-form-item label="班次:"
                    prop="shift">
        <el-select v-model="searchForm.shift"
                   placeholder="请选择班次"
                   clearable
                   style="width: 200px">
          <el-option
              v-for="item in shifts_list"
              :key="item.value"
              :label="item.label"
              :value="item.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   @click="handleSearch">
                   @click="fetchData">
          <el-icon>
            <Search />
          </el-icon>
@@ -56,7 +49,7 @@
          é‡ç½®
        </el-button>
      </el-form-item>
    </el-form>
    </el-form> -->
    <!-- ç­æ¬¡åˆ—表 -->
    <el-card shadow="never"
             class="mb16">
@@ -81,39 +74,23 @@
          </template>
        </el-table-column>
        <el-table-column prop="locationName"
                         label="地点名称">
          <template #default="scope">
            {{ scope.row.locationName || "-" }}
          </template>
        </el-table-column>
                         label="地点名称" />
        <el-table-column prop="longitude"
                         label="经度">
          <template #default="scope">
            {{ scope.row.longitude ?? "-" }}
          </template>
        </el-table-column>
                         label="经度" />
        <el-table-column prop="latitude"
                         label="纬度">
          <template #default="scope">
            {{ scope.row.latitude ?? "-" }}
          </template>
        </el-table-column>
                         label="纬度" />
        <el-table-column prop="radius"
                         label="打卡范围(m)">
          <template #default="scope">
            {{ scope.row.radius ?? "-" }}
          </template>
        </el-table-column>
                         label="打卡范围(m)" />
        <el-table-column prop="startAt"
                         label="上班时间">
          <template #default="scope">
            {{ scope.row.startAt || "-" }}
            {{ scope.row.startAt }}
          </template>
        </el-table-column>
        <el-table-column prop="endAt"
                         label="下班时间">
          <template #default="scope">
            {{ scope.row.endAt || "-" }}
            {{ scope.row.endAt }}
          </template>
        </el-table-column>
        <el-table-column label="操作"
@@ -123,11 +100,9 @@
          <template #default="scope">
            <el-button type="primary"
                       link
                       :disabled="isRuleLocked(scope.row)"
                       @click="openForm('edit', scope.row)">编辑</el-button>
            <el-button type="danger"
                       link
                       :disabled="isRuleLocked(scope.row)"
                       @click="handleDelete(scope.row.id)">删除</el-button>
          </template>
        </el-table-column>
@@ -149,7 +124,7 @@
</template>
<script setup>
  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
  import { ref, reactive, onMounted } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import {
    Plus,
@@ -183,8 +158,8 @@
  // æŸ¥è¯¢è¡¨å•
  const searchForm = reactive({
    sysDeptId: "",
    shift: "",
    countId: "",
    locationName: "",
  });
  // éƒ¨é—¨é€‰é¡¹
@@ -212,10 +187,6 @@
    if (!value) return "";
    const shift = shifts_list.value.find(item => item.value === value);
    return shift ? shift.label : value;
  };
  const isRuleLocked = row => {
    return Boolean(row?.usageLocked || row?.canEdit === false || row?.canDelete === false);
  };
  // èŽ·å–éƒ¨é—¨åˆ—è¡¨
@@ -261,18 +232,12 @@
    tableLoading.value = true;
    getAttendanceRules({ ...page, ...searchForm })
      .then(res => {
        const records = Array.isArray(res?.data?.records) ? res.data.records : [];
        tableData.value = records.filter(r => String(r?.id) !== "999");
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
  const handleSearch = () => {
    page.current = 1;
    fetchData();
  };
  // åˆ†é¡µå˜æ›´
@@ -284,18 +249,13 @@
  // é‡ç½®æœç´¢
  const resetSearch = () => {
    searchForm.sysDeptId = "";
    searchForm.shift = "";
    page.current = 1;
    searchForm.countId = "";
    searchForm.locationName = "";
    fetchData();
  };
  // æ‰“开表单
  const openForm = (type, row = {}) => {
    if (type === "edit" && isRuleLocked(row)) {
      ElMessage.warning("该班次配置已被排班并产生打卡记录,不能编辑");
      return;
    }
    operationType.value = type;
    currentRow.value = row;
    dialogVisible.value = true;
@@ -303,11 +263,6 @@
  // åˆ é™¤ç­æ¬¡
  const handleDelete = id => {
    const row = tableData.value.find(item => String(item.id) === String(id));
    if (isRuleLocked(row)) {
      ElMessage.warning("存在已排班且已有打卡记录的班次配置,不能删除");
      return;
    }
    ElMessageBox.confirm("确定要删除这条班次吗?", "删除确认", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
src/views/personnelManagement/classsSheduling/index.vue
@@ -142,20 +142,19 @@
                     :key="'d' + i">
                  <el-dropdown trigger="click"
                               placement="bottom"
                               :disabled="isCellBlockedByLeaveDate(item, m)"
                               @command="(e) => handleCommand(e, item, m)"
                               @command="(e) => handleCommand(e, m)"
                               class="shift-dropdown">
                    <div class="shift-box"
                         :class="{
                      'shift-box-early': m.shift === '早班',
                      'shift-box-mid': m.shift === '中班',
                      'shift-box-night': m.shift === '夜班',
                      'shift-box-rest': !m.shift || m.shift === '休息',
                      'shift-box-rest': m.shift === '休息',
                      'shift-box-leave': m.shift === '请假',
                      'shift-box-other': m.shift === '夜11',
                      'shift-box-business': m.shift === '夜12',
                    }">
                      <span class="shift-text">{{ getShiftNameByValue(m.shift) || '休息' }}</span>
                      <span class="shift-text">{{ getShiftNameByValue(m.shift) || '—' }}</span>
                    </div>
                    <template #dropdown>
                      <el-dropdown-menu>
@@ -200,7 +199,7 @@
              </div> -->
              <div class="user-total">
                <span class="total-label">合计出勤:</span>
                <span class="total-value">{{ item.year.totalYearAttendance }}天</span>
                <span class="total-value">{{ item.work_time }}天</span>
              </div>
            </div>
          </div>
@@ -272,23 +271,6 @@
      <div class="search_thing">
        <div class="search_label"
             style="width: 90px">
          <span style="color: red; margin-right: 4px">*</span>部门:
        </div>
        <div class="search_input">
          <el-tree-select v-model="schedulingQuery.deptId"
                          :data="deptOptions"
                          :props="{ value: 'id', label: 'label', children: 'children' }"
                          value-key="id"
                          placeholder="请选择部门"
                          check-strictly
                          clearable
                          @change="onSchedulingDeptChange"
                          style="width: 100%" />
        </div>
      </div>
      <div class="search_thing">
        <div class="search_label"
             style="width: 90px">
          <span style="color: red; margin-right: 4px">*</span>人员名称:
        </div>
        <div class="search_input">
@@ -297,8 +279,7 @@
                     style="width: 100%"
                     multiple
                     clearable
                     collapse-tags
                     :disabled="!schedulingQuery.deptId">
                     collapse-tags>
            <el-option v-for="item in personList"
                       :key="item.id"
                       :label="item.staffName"
@@ -315,9 +296,8 @@
        <div class="search_input">
          <el-select v-model="schedulingQuery.shift"
                     placeholder="请选择"
                     style="width: 100%"
                     :disabled="!schedulingQuery.deptId">
            <el-option v-for="item in schedulingClassType"
                     style="width: 100%">
            <el-option v-for="item in classType"
                       :key="item.id"
                       :label="getShiftNameByValue(item.shift)"
                       :value="item.id">
@@ -338,14 +318,7 @@
</template>
<script setup>
  import {
    ref,
    reactive,
    computed,
    watch,
    onMounted,
    getCurrentInstance,
  } from "vue";
  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
  import { useRouter } from "vue-router";
  import {
    page,
@@ -355,7 +328,6 @@
    update,
    staffOnJobListPage,
  } from "@/api/personnelManagement/class";
  import { findStaffLeaveListPage } from "@/api/personnelManagement/staffLeave.js";
  import { deptTreeSelect } from "@/api/system/user.js";
  import { getAttendanceRules } from "@/api/personnelManagement/attendanceRules.js";
  import { useDict } from "@/utils/dict";
@@ -404,8 +376,6 @@
  // äººå‘˜åˆ—表
  const personList = ref([]);
  const personListAll = ref([]);
  const leaveDateMap = ref({});
  // åŠ è½½çŠ¶æ€
  const loading = ref(false);
@@ -413,18 +383,8 @@
  // æŽ’班查询条件
  const schedulingQuery = reactive({
    week: "",
    deptId: "",
    userId: null,
    shift: "",
  });
  const schedulingClassType = computed(() => {
    if (!schedulingQuery.deptId) return classType.value || [];
    return (classType.value || []).filter(item => {
      if (["998", "999"].includes(String(item?.id))) return true;
      const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id;
      return String(itemDeptId) === String(schedulingQuery.deptId);
    });
  });
  // åˆ—表数据
@@ -633,10 +593,6 @@
      proxy.$modal.msgError("请选择周次");
      return;
    }
    if (!schedulingQuery.deptId) {
      proxy.$modal.msgError("请选择部门");
      return;
    }
    let time = schedulingQuery.week.getTime();
    // æ ¼å¼åŒ–日期为 YYYY-MM-DD æ ¼å¼
@@ -660,15 +616,6 @@
      proxy.$modal.msgError("请选择班次");
      return;
    }
    const blockedEmployees = (personList.value || []).filter(item => {
      const leaveDate = getEmployeeLeaveDate(item?.id);
      const weekEndDate = getSchedulingWeekEndDate();
      return leaveDate && weekEndDate && weekEndDate.getTime() > leaveDate.getTime();
    });
    if (blockedEmployees.length) {
      proxy.$modal.msgError("存在已超过离职日期的人员,不能继续排班");
      return;
    }
    loading.value = true;
    add({
      startWeek,
@@ -681,10 +628,8 @@
        proxy.$modal.msgSuccess("操作成功");
        schedulingVisible.value = false;
        schedulingQuery.week = "";
        schedulingQuery.deptId = "";
        schedulingQuery.userId = null;
        schedulingQuery.shift = "";
        personList.value = [];
        refresh();
      })
      .catch(err => {
@@ -738,20 +683,17 @@
        }
        proxy.$download.saveAs(blob, fileName + ".xlsx");
      })
      .catch(err => {})
      .catch(err => {
      })
      .finally(() => {
        downLoading.value = false;
      });
  };
  // å¤„理命令
  const handleCommand = (e, row, cell) => {
    if (isCellBlockedByLeaveDate(row, cell)) {
      proxy.$modal.msgError("该员工已超出离职日期,不能继续排班");
      return;
    }
  const handleCommand = (e, m) => {
    // if (e != m.shift) {
    update({
      id: cell.id,
      id: m.id,
      personalAttendanceLocationConfigId: e,
    }).then(res => {
      proxy.$modal.msgSuccess("操作成功");
@@ -767,18 +709,7 @@
  // æŸ¥è¯¢è§„则列表
  const fetchData = () => {
    getAttendanceRules({ current: -1, size: -1 }).then(res => {
      const records = Array.isArray(res?.data?.records) ? res.data.records : [];
      const appendList = [
        { id: 998, shift: "休息" },
        { id: 999, shift: "请假" },
      ];
      const exists = new Set(records.map(r => String(r?.id)));
      appendList.forEach(item => {
        if (!exists.has(String(item.id))) {
          records.push(item);
        }
      });
      classType.value = records;
      classType.value = res.data.records;
    });
  };
  // èŽ·å–ç”¨æˆ·
@@ -793,139 +724,8 @@
      staffState: 1,
    }).then(res => {
      let arr = res.data.records;
      personListAll.value = arr;
      updatePersonListByDept();
      personList.value = arr;
    });
  };
  const getLeaveDates = () => {
    findStaffLeaveListPage({
      current: -1,
      size: -1,
    }).then(res => {
      const records = Array.isArray(res?.data?.records) ? res.data.records : [];
      const map = {};
      records.forEach(item => {
        const staffId = item?.staffOnJobId ?? item?.staff_on_job_id;
        const leaveDate = item?.leaveDate ?? item?.leave_date;
        if (!staffId || !leaveDate) return;
        const key = String(staffId);
        const prev = map[key];
        if (!prev || String(leaveDate) > String(prev)) {
          map[key] = leaveDate;
        }
      });
      leaveDateMap.value = map;
      updatePersonListByDept();
      syncPersonListSelection();
    });
  };
  const findDeptNodeById = (deptId, deptList = deptOptions.value) => {
    const list = Array.isArray(deptList) ? deptList : [];
    for (const node of list) {
      if (String(node.id) === String(deptId)) return node;
      if (node.children && node.children.length) {
        const found = findDeptNodeById(deptId, node.children);
        if (found) return found;
      }
    }
    return null;
  };
  const collectDeptIds = node => {
    const ids = [];
    if (!node) return ids;
    ids.push(String(node.id));
    if (node.children && node.children.length) {
      node.children.forEach(child => {
        ids.push(...collectDeptIds(child));
      });
    }
    return ids;
  };
  const updatePersonListByDept = () => {
    if (!schedulingQuery.deptId) {
      personList.value = [];
      return;
    }
    const node = findDeptNodeById(schedulingQuery.deptId);
    const deptIds = new Set(collectDeptIds(node));
    personList.value = (personListAll.value || []).filter(item => {
      const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id;
      return deptIds.has(String(itemDeptId)) && !isEmployeeBlockedByLeaveDate(item);
    });
  };
  const parseDateOnly = value => {
    if (!value) return null;
    if (value instanceof Date) {
      const d = new Date(value);
      d.setHours(0, 0, 0, 0);
      return d;
    }
    const normalized = String(value).trim().slice(0, 10);
    if (!normalized) return null;
    const d = new Date(normalized.replace(/-/g, "/"));
    if (Number.isNaN(d.getTime())) return null;
    d.setHours(0, 0, 0, 0);
    return d;
  };
  const getSchedulingWeekEndDate = () => {
    if (!schedulingQuery.week) return null;
    const start = new Date(schedulingQuery.week);
    start.setHours(0, 0, 0, 0);
    return new Date(start.getTime() + 24 * 60 * 60 * 1000 * 5);
  };
  const getCellDate = cell => parseDateOnly(cell?.time);
  const getEmployeeLeaveDate = staffId => {
    const leaveDate = leaveDateMap.value[String(staffId)];
    return parseDateOnly(leaveDate);
  };
  const isEmployeeBlockedByLeaveDate = item => {
    const leaveDate = getEmployeeLeaveDate(item?.id);
    if (!leaveDate) return false;
    const weekEndDate = getSchedulingWeekEndDate();
    if (!weekEndDate) return false;
    return weekEndDate.getTime() > leaveDate.getTime();
  };
  const isCellBlockedByLeaveDate = (row, cell) => {
    const staffId = row?.userId ?? row?.user_id ?? row?.id;
    const leaveDate = getEmployeeLeaveDate(staffId);
    const cellDate = getCellDate(cell);
    if (!leaveDate || !cellDate) return false;
    return cellDate.getTime() > leaveDate.getTime();
  };
  const syncPersonListSelection = () => {
    if (!Array.isArray(schedulingQuery.userId) || !schedulingQuery.userId.length) return;
    const validIds = new Set((personList.value || []).map(item => String(item.id)));
    const nextIds = schedulingQuery.userId.filter(id => validIds.has(String(id)));
    if (nextIds.length !== schedulingQuery.userId.length) {
      schedulingQuery.userId = nextIds;
      proxy.$modal.msgWarning("已自动剔除超出离职日期的人员");
    }
  };
  const setDefaultShiftByDept = () => {
    if (!schedulingQuery.deptId) {
      schedulingQuery.shift = "";
      return;
    }
    const first = schedulingClassType.value?.[0];
    schedulingQuery.shift = first ? first.id : "";
  };
  const onSchedulingDeptChange = () => {
    schedulingQuery.userId = null;
    updatePersonListByDept();
    setDefaultShiftByDept();
  };
  // æ ¹æ®å­—典获取日期
@@ -949,7 +749,6 @@
  onMounted(() => {
    fetchData();
    getUsers();
    getLeaveDates();
    fetchDeptOptions();
    if (query.month) {
      init();
@@ -962,45 +761,6 @@
    }
    monthList.value.reverse();
  });
  watch(
    () => classType.value,
    () => {
      if (schedulingVisible.value && schedulingQuery.deptId) {
        setDefaultShiftByDept();
      }
    }
  );
  watch(
    () => schedulingQuery.week,
    () => {
      if (!schedulingVisible.value) return;
      updatePersonListByDept();
      syncPersonListSelection();
    }
  );
  watch(
    () => schedulingQuery.deptId,
    () => {
      if (!schedulingVisible.value) return;
      updatePersonListByDept();
      syncPersonListSelection();
    }
  );
  watch(
    () => schedulingVisible.value,
    val => {
      if (!val) return;
      schedulingQuery.week = "";
      schedulingQuery.deptId = "";
      schedulingQuery.userId = null;
      schedulingQuery.shift = "";
      personList.value = [];
    }
  );
</script>
<style scoped>
src/views/personnelManagement/contractManagement/components/formDia.vue
@@ -1,39 +1,37 @@
<template>
  <div>
    <el-dialog v-model="dialogFormVisible"
    <el-dialog
        v-model="dialogFormVisible"
               title="详情"
               width="70%"
               @close="closeDia">
      <PIMTable rowKey="id"
        @close="closeDia"
    >
      <PIMTable
          rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :tableLoading="tableLoading"
                height="600"></PIMTable>
          height="600"
      ></PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <FileList v-if="fileDialogVisible"
              v-model:visible="fileDialogVisible"
              :record-type="'staff_contract'"
              :record-id="recordId" />
    <Files ref="filesDia"></Files>
  </div>
</template>
<script setup>
  import { ref, defineAsyncComponent, getCurrentInstance } from "vue";
import {ref} from "vue";
  import { findStaffContractListPage } from "@/api/personnelManagement/staffContract.js";
  const FileList = defineAsyncComponent(() =>
    import("@/components/Dialog/FileList.vue")
  );
  const { proxy } = getCurrentInstance();
  const emit = defineEmits(["close"]);
  const fileDialogVisible = ref(false);
  const recordId = ref(0);
const Files = defineAsyncComponent(() => import( "@/views/personnelManagement/contractManagement/filesDia.vue"));
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const filesDia = ref()
  const dialogFormVisible = ref(false);
  const operationType = ref("");
const operationType = ref('')
  const tableColumn = ref([
    {
      label: "合同年限",
@@ -51,17 +49,16 @@
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
    fixed: 'right',
      width: 120,
      operation: [
        {
          name: "附件",
        name: "上传附件",
          type: "text",
          clickFun: row => {
            recordId.value = row.id;
            fileDialogVisible.value = true;
        clickFun: (row) => {
          filesDia.value.openDialog( row,'合同')
          },
        },
      }
      ],
    },
  ]);
@@ -72,17 +69,22 @@
  const openDialog = (type, row) => {
    operationType.value = type;
    dialogFormVisible.value = true;
    if (operationType.value === "edit") {
  if (operationType.value === 'edit') {
      findStaffContractListPage({ staffOnJobId: row.id }).then(res => {
        tableData.value = res.data.records;
      });
      tableData.value = res.data.records
    })
    }
  };
}
const openUploadFile = (row) => {
  filesDia.value.open = true
  filesDia.value.row = row
}
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    dialogFormVisible.value = false;
    emit("close");
  emit('close')
  };
  defineExpose({
    openDialog,
@@ -90,4 +92,5 @@
</script>
<style scoped>
</style>
src/views/personnelManagement/contractManagement/index.vue
@@ -3,24 +3,12 @@
    <div class="search_form mb20">
      <div>
        <span class="search_title">姓名:</span>
        <el-input v-model="searchForm.staffName"
                  style="width: 240px"
                  placeholder="请输入姓名搜索"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <span style="margin-left: 10px"
              class="search_title">合同结束日期:</span>
        <el-date-picker v-model="searchForm.entryDate"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        type="daterange"
                        placeholder="请选择"
                        clearable
                        @change="changeDaterange" />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
        <el-input v-model="searchForm.staffName" style="width: 240px" placeholder="请输入姓名搜索" @change="handleQuery"
          clearable :prefix-icon="Search" />
        <span style="margin-left: 10px" class="search_title">合同结束日期:</span>
        <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
          placeholder="请选择" clearable @change="changeDaterange" />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
        <!--        <el-button type="primary" @click="openForm('add')">新增入职</el-button>-->
@@ -30,28 +18,27 @@
      </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 rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true"
        @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination"
                :total="page.total"></PIMTable>
    </div>
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <renew-contract v-if="isShowRenewContractModal"
    <form-dia ref="formDia" @close="handleQuery"></form-dia>
    <renew-contract
        v-if="isShowRenewContractModal"
                    v-model:visible="isShowRenewContractModal"
                    :id="id"
                    @completed="handleQuery" />
        @completed="handleQuery"
    />
    <!-- åˆåŒå¯¼å…¥å¯¹è¯æ¡† -->
    <el-dialog :title="upload.title"
    <el-dialog
      :title="upload.title"
               v-model="upload.open"
               width="400px"
               append-to-body>
      <el-upload ref="uploadRef"
      append-to-body
    >
      <el-upload
        ref="uploadRef"
                 :limit="1"
                 accept=".xlsx, .xls"
                 :headers="upload.headers"
@@ -60,7 +47,8 @@
                 :on-progress="handleFileUploadProgress"
                 :on-success="handleFileSuccess"
                 :auto-upload="false"
                 drag>
        drag
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
@@ -78,8 +66,7 @@
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitFileForm">ç¡® å®š</el-button>
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
@@ -92,11 +79,7 @@
  import { Search } from "@element-plus/icons-vue";
  import { onMounted, ref, defineAsyncComponent } from "vue";
  import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue";
  const RenewContract = defineAsyncComponent(() =>
    import(
      "@/views/personnelManagement/employeeRecord/components/RenewContract.vue"
    )
  );
const RenewContract = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/RenewContract.vue"));
  import { ElMessageBox } from "element-plus";
  import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
  import dayjs from "dayjs";
@@ -116,7 +99,7 @@
      label: "状态",
      prop: "staffState",
      dataType: "tag",
      formatData: params => {
    formatData: (params) => {
        if (params == 0) {
          return "离职";
        } else if (params == 1) {
@@ -125,7 +108,7 @@
          return null;
        }
      },
      formatType: params => {
    formatType: (params) => {
        if (params == 0) {
          return "danger";
        } else if (params == 1) {
@@ -148,8 +131,17 @@
      prop: "sex",
    },
    {
    label: "户籍住址",
    prop: "nativePlace",
  },
  {
      label: "岗位",
      prop: "postName",
    prop: "postJob",
  },
  {
    label: "现住址",
    prop: "adress",
    width: 200
    },
    {
      label: "第一学历",
@@ -158,7 +150,7 @@
    {
      label: "专业",
      prop: "profession",
      width: 100,
    width: 100
    },
    {
      label: "年龄",
@@ -167,17 +159,17 @@
    {
      label: "联系电话",
      prop: "phone",
      width: 150,
    width: 150
    },
    {
      label: "紧急联系人",
      prop: "emergencyContact",
      width: 120,
    width: 120
    },
    {
      label: "紧急联系人电话",
      prop: "emergencyContactPhone",
      width: 150,
    width: 150
    },
    // {
    //   label: "合同年限",
@@ -191,19 +183,19 @@
    {
      label: "合同结束日期",
      prop: "contractExpireTime",
      width: 120,
    width: 120
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
    fixed: 'right',
      width: 160,
      operation: [
        {
          name: "详情",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            openForm("edit", row);
          },
        },
@@ -211,15 +203,15 @@
          name: "续签合同",
          type: "text",
          showHide: row => row.staffState === 1,
          clickFun: row => {
        clickFun: (row) => {
            isShowRenewContractModal.value = true;
            id.value = row.id;
          },
        },
      }
      ],
    },
  ]);
  const filesDia = ref();
const filesDia = ref()
  const isShowRenewContractModal = ref(false);
  const id = ref(0);
  const tableData = ref([]);
@@ -230,10 +222,10 @@
    size: 100,
    total: 0,
  });
  const formDia = ref();
  const { proxy } = getCurrentInstance();
const formDia = ref()
const { proxy } = getCurrentInstance()
  const changeDaterange = value => {
const changeDaterange = (value) => {
    searchForm.value.entryDateStart = undefined;
    searchForm.value.entryDateEnd = undefined;
    if (value) {
@@ -243,11 +235,11 @@
    getList();
  };
  // æ‰“开附件弹框
  const openFilesFormDia = row => {
    console.log(row);
const openFilesFormDia = (row) => {
  console.log(row)
    nextTick(() => {
      filesDia.value?.openDialog(row, "合同");
    });
    filesDia.value?.openDialog( row,'合同')
  })
  };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -255,7 +247,7 @@
    page.current = 1;
    getList();
  };
  const pagination = obj => {
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
@@ -263,28 +255,26 @@
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    params.staffState = 1;
    staffOnJobListPage(params)
      .then(res => {
  params.entryDate = undefined
  params.staffState = 1
  staffOnJobListPage(params).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
    tableData.value = res.data.records
        page.total = res.data.total;
      })
      .catch(err => {
  }).catch(err => {
        tableLoading.value = false;
      });
  })
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    nextTick(() => {
      formDia.value?.openDialog(type, row);
    });
    formDia.value?.openDialog(type, row)
  })
  };
  // å¯¼å‡º
  const handleOut = () => {
@@ -294,11 +284,7 @@
      type: "warning",
    })
      .then(() => {
        proxy.download(
          "/staff/staffOnJob/export",
          { staffState: 1 },
          "合同管理.xlsx"
        );
      proxy.download("/staff/staffOnJob/export", {staffState: 1}, "合同管理.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
@@ -325,7 +311,7 @@
  }
  /** æäº¤ä¸Šä¼ æ–‡ä»¶ */
  function submitFileForm() {
    console.log(upload.url + "?updateSupport=" + upload.updateSupport);
  console.log(upload.url + '?updateSupport=' + upload.updateSupport)
    proxy.$refs["uploadRef"].submit();
  }
  /**文件上传中处理 */
src/views/personnelManagement/dimission/components/formDia.vue
@@ -1,31 +1,29 @@
<template>
  <div>
    <el-dialog v-model="dialogFormVisible"
    <el-dialog
        v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增离职' : '编辑离职'"
               width="70%"
               @close="closeDia">
        @close="closeDia"
    >
      <!-- å‘˜å·¥ä¿¡æ¯å±•示区域 -->
      <div class="info-section">
        <div class="info-title">员工信息</div>
        <el-form :model="form"
                 label-width="200px"
                 label-position="left"
                 :rules="rules"
                 ref="formRef"
                 style="margin-top: 20px">
        <el-form :model="form" label-width="200px" label-position="left" :rules="rules" ref="formRef" style="margin-top: 20px">
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="姓名:"
                            prop="staffOnJobId">
              <el-form-item label="姓名:" prop="staffOnJobId">
                <el-select v-model="form.staffOnJobId"
                           placeholder="请选择人员"
                           style="width: 100%"
                           :disabled="operationType === 'edit'"
                           @change="handleSelect">
                  <el-option v-for="item in personList"
                  <el-option
                      v-for="item in personList"
                             :key="item.id"
                             :label="item.staffName"
                             :value="item.id" />
                      :value="item.id"
                  />
                </el-select>
              </el-form-item>
            </el-col>
@@ -41,11 +39,21 @@
                {{ currentStaffRecord.sex || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="户籍住址:">
                {{ currentStaffRecord.nativePlace || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="岗位:">
                {{ currentStaffRecord.postName || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="现住址:">
                {{ currentStaffRecord.adress || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
@@ -89,48 +97,48 @@
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="离职日期:"
                            prop="leaveDate">
                <el-date-picker v-model="form.leaveDate"
              <el-form-item label="离职日期:" prop="leaveDate">
                <el-date-picker
                    v-model="form.leaveDate"
                                type="date"
                                :disabled="operationType === 'edit'"
                                :disabled-date="disabledFutureDate"
                                placeholder="请选择离职日期"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                style="width: 100%" />
                    style="width: 100%"
                />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="离职原因:"
                            prop="reason">
                <el-select v-model="form.reason"
                           placeholder="请选择离职原因"
                           style="width: 100%"
                           @change="handleSelectDimissionReason">
                  <el-option v-for="(item, index) in dimissionReasonOptions"
              <el-form-item label="离职原因:" prop="reason">
                <el-select v-model="form.reason" placeholder="请选择离职原因" style="width: 100%" @change="handleSelectDimissionReason">
                  <el-option
                      v-for="(item, index) in dimissionReasonOptions"
                             :key="index"
                             :label="item.label"
                             :value="item.value" />
                      :value="item.value"
                  />
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="备注:"
                            prop="remark"
                            v-if="form.reason === 'other'">
                <el-input v-model="form.remark"
              <el-form-item label="备注:" prop="remark" v-if="form.reason === 'other'">
                <el-input
                    v-model="form.remark"
                          type="textarea"
                          :rows="3"
                          placeholder="备注"
                          maxlength="500"
                          show-word-limit />
                    show-word-limit
                />
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
        <!--        <el-row :gutter="30">-->
        <!--          <el-col :span="12">-->
        <!--            <div class="info-item">-->
@@ -153,10 +161,10 @@
        <!--          </el-col>-->
        <!--        </el-row>-->
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
@@ -167,24 +175,21 @@
<script setup>
  import { ref, reactive, toRefs, getCurrentInstance } from "vue";
  import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
  import {
    createStaffLeave,
    updateStaffLeave,
  } from "@/api/personnelManagement/staffLeave.js";
  const { proxy } = getCurrentInstance();
  const emit = defineEmits(["close"]);
import {createStaffLeave, updateStaffLeave} from "@/api/personnelManagement/staffLeave.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
  const dialogFormVisible = ref(false);
  const operationType = ref("");
const operationType = ref('')
  const getTodayDate = () => {
    const now = new Date();
    const year = now.getFullYear();
    const month = `${now.getMonth() + 1}`.padStart(2, "0");
    const day = `${now.getDate()}`.padStart(2, "0");
  const month = `${now.getMonth() + 1}`.padStart(2, '0');
  const day = `${now.getDate()}`.padStart(2, '0');
    return `${year}-${month}-${day}`;
  };
  const disabledFutureDate = time => {
const disabledFutureDate = (time) => {
    const todayEnd = new Date();
    todayEnd.setHours(23, 59, 59, 999);
    return time.getTime() > todayEnd.getTime();
@@ -198,55 +203,52 @@
    },
    rules: {
      staffName: [{ required: true, message: "请选择人员" }],
      leaveDate: [
        { required: true, message: "请选择离职日期", trigger: "change" },
      ],
    leaveDate: [{ required: true, message: "请选择离职日期", trigger: "change" }],
      reason: [{ required: true, message: "请选择离职原因" }],
    },
    dimissionReasonOptions: [
      { label: "薪资待遇", value: "salary" },
      { label: "职业发展", value: "career_development" },
      { label: "工作环境", value: "work_environment" },
      { label: "个人原因", value: "personal_reason" },
      { label: "其他", value: "other" },
      {label: '薪资待遇', value: 'salary'},
      {label: '职业发展', value: 'career_development'},
      {label: '工作环境', value: 'work_environment'},
      {label: '个人原因', value: 'personal_reason'},
      {label: '其他', value: 'other'},
    ],
    currentStaffRecord: {},
  });
  const { form, rules, dimissionReasonOptions, currentStaffRecord } =
    toRefs(data);
const { form, rules, dimissionReasonOptions, currentStaffRecord } = toRefs(data);
  // æ‰“开弹框
  const openDialog = (type, row) => {
    operationType.value = type;
    dialogFormVisible.value = true;
    if (operationType.value === "edit") {
      currentStaffRecord.value = row;
      form.value.staffOnJobId = row.staffOnJobId;
      form.value.leaveDate = row.leaveDate;
      form.value.reason = row.reason;
      form.value.remark = row.remark;
  if (operationType.value === 'edit') {
    currentStaffRecord.value = row
    form.value.staffOnJobId = row.staffOnJobId
    form.value.leaveDate = row.leaveDate
    form.value.reason = row.reason
    form.value.remark = row.remark
      personList.value = [
        {
          staffName: row.staffName,
          id: row.staffOnJobId,
        },
      ];
      }
    ]
    } else {
      form.value.leaveDate = getTodayDate();
      getList();
    form.value.leaveDate = getTodayDate()
    getList()
    }
  };
}
  const handleSelectDimissionReason = val => {
    if (val === "other") {
      form.value.remark = "";
const handleSelectDimissionReason = (val) => {
  if (val === 'other') {
    form.value.remark = ''
    }
  };
}
  // æäº¤äº§å“è¡¨å•
  const submitForm = () => {
    form.value.staffState = 0;
    if (form.value.reason !== "other") {
      form.value.remark = "";
  form.value.staffState = 0
  if (form.value.reason !== 'other') {
    form.value.remark = ''
    }
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
@@ -254,16 +256,17 @@
          createStaffLeave(form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        })
        } else {
          updateStaffLeave(currentStaffRecord.value.id, form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        })
        }
      }
    });
  };
  })
}
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    // è¡¨å•已注释,手动重置表单数据
@@ -274,7 +277,7 @@
      remark: "",
    };
    dialogFormVisible.value = false;
    emit("close");
  emit('close')
  };
  const personList = ref([]);
@@ -286,20 +289,20 @@
    staffOnJobListPage({
      current: -1,
      size: -1,
      staffState: 1,
        staffState: 1
    }).then(res => {
      personList.value = res.data.records || [];
    });
    personList.value = res.data.records || []
  })
  };
  const handleSelect = val => {
    let obj = personList.value.find(item => item.id === val);
    currentStaffRecord.value = {};
const handleSelect = (val) => {
  let obj = personList.value.find(item => item.id === val)
  currentStaffRecord.value = {}
    if (obj) {
      // ä¿ç•™ç¦»èŒæ—¥æœŸå’Œç¦»èŒåŽŸå› ï¼Œåªæ›´æ–°å‘˜å·¥ä¿¡æ¯
      currentStaffRecord.value = obj;
    currentStaffRecord.value = obj
    }
  };
}
  defineExpose({
    openDialog,
  });
src/views/personnelManagement/dimission/index.vue
@@ -3,25 +3,27 @@
    <div class="search_form mb20">
      <div>
        <span class="search_title">姓名:</span>
        <el-input v-model="searchForm.staffName"
        <el-input
            v-model="searchForm.staffName"
                  style="width: 240px"
                  placeholder="请输入姓名搜索"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
            :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 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"
      <PIMTable
          rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
@@ -29,10 +31,10 @@
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
          :total="page.total"
      ></PIMTable>
    </div>
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <form-dia ref="formDia" @close="handleQuery"></form-dia>
  </div>
</template>
@@ -40,10 +42,7 @@
  import { Search } from "@element-plus/icons-vue";
  import { onMounted, ref } from "vue";
  import FormDia from "@/views/personnelManagement/dimission/components/formDia.vue";
  import {
    findStaffLeaveListPage,
    batchDeleteStaffLeaves,
  } from "@/api/personnelManagement/staffLeave.js";
import {findStaffLeaveListPage, batchDeleteStaffLeaves} from "@/api/personnelManagement/staffLeave.js";
  import { ElMessageBox } from "element-plus";
  const data = reactive({
@@ -57,7 +56,7 @@
      label: "状态",
      prop: "staffState",
      dataType: "tag",
      formatData: params => {
    formatData: (params) => {
        if (params == 0) {
          return "离职";
        } else if (params == 1) {
@@ -66,7 +65,7 @@
          return null;
        }
      },
      formatType: params => {
    formatType: (params) => {
        if (params == 0) {
          return "danger";
        } else if (params == 1) {
@@ -93,6 +92,10 @@
      prop: "sex",
    },
    {
    label: "户籍住址",
    prop: "nativePlace",
  },
  {
      label: "部门",
      prop: "deptName",
    },
@@ -101,13 +104,18 @@
      prop: "postName",
    },
    {
    label: "现住址",
    prop: "adress",
    width:200
  },
  {
      label: "第一学历",
      prop: "firstStudy",
    },
    {
      label: "专业",
      prop: "profession",
      width: 100,
    width:100
    },
    {
      label: "年龄",
@@ -116,28 +124,28 @@
    {
      label: "联系电话",
      prop: "phone",
      width: 150,
    width:150
    },
    {
      label: "紧急联系人",
      prop: "emergencyContact",
      width: 120,
    width: 120
    },
    {
      label: "紧急联系人电话",
      prop: "emergencyContactPhone",
      width: 150,
    width:150
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
    fixed: 'right',
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            openForm("edit", row);
          },
        },
@@ -152,8 +160,9 @@
    size: 100,
    total: 0,
  });
  const formDia = ref();
  const { proxy } = getCurrentInstance();
const formDia = ref()
const { proxy } = getCurrentInstance()
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -161,40 +170,38 @@
    page.current = 1;
    getList();
  };
  const pagination = obj => {
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    findStaffLeaveListPage({ ...page, ...searchForm.value })
      .then(res => {
  findStaffLeaveListPage({...page, ...searchForm.value}).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
    tableData.value = res.data.records
        page.total = res.data.total;
      })
      .catch(err => {
  }).catch(err => {
        tableLoading.value = false;
      });
  })
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    nextTick(() => {
      formDia.value?.openDialog(type, row);
    });
    formDia.value?.openDialog(type, row)
  })
  };
  // åˆ é™¤
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.id);
    ids = selectedRows.value.map((item) => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
@@ -205,7 +212,7 @@
      type: "warning",
    })
      .then(() => {
        batchDeleteStaffLeaves(ids).then(res => {
        batchDeleteStaffLeaves(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
src/views/personnelManagement/employeeRecord/index.vue
@@ -145,10 +145,6 @@
    prop: "staffNo",
  },
  {
    label: "部门",
    prop: "deptName",
  },
  {
    label: "姓名",
    prop: "staffName",
  },
src/views/personnelManagement/monthlyStatistics/components/auditDia.vue
@@ -86,7 +86,7 @@
<script setup>
import { ref, computed, reactive, toRefs, getCurrentInstance, watch } from "vue";
import { ElMessage } from "element-plus";
import useUserStore from "@/store/modules/user";
import Cookies from "js-cookie";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { staffSalaryMainUpdate } from "@/api/personnelManagement/staffSalaryMain.js";
@@ -98,7 +98,6 @@
});
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const dialogVisible = computed({
  get: () => props.modelValue,
@@ -177,21 +176,15 @@
      return;
    }
    
    const username = userStore.name || "";
    const currentUserId = Number(userStore.id);
    const assignedAuditUserId = Number(row?.auditUserId);
    // æƒé™éªŒè¯ï¼šåªæœ‰æŒ‡å®šçš„审核人才能进行审核
    if (!currentUserId || currentUserId !== assignedAuditUserId) {
      ElMessage.warning("您不是指定的审核人,无法进行审核操作");
      return;
    }
    const username = Cookies.get("username") || "";
    const userIdRaw = Cookies.get("userId");
    const auditUserId = userIdRaw ? Number(userIdRaw) : undefined;
    
    // æž„建审核数据
    const submitData = {
      id: row.id,
      status: Number(auditResult.value) === 2 ? 2 : 4, // 2=不通过 4=通过(待发放)
      auditUserId: currentUserId,
      auditUserId,
      auditUserName: username,
    };
    loading.value = true;
src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -1,352 +1,342 @@
<template>
  <FormDialog v-model="dialogVisible"
  <FormDialog
    v-model="dialogVisible"
              :title="operationType === 'add' ? '新建工资表' : '编辑工资表'"
              width="90%"
              @close="closeDia">
    @close="closeDia"
  >
    <template #footer>
      <el-button type="info"
                 @click="saveDraft">保存草稿</el-button>
      <el-button type="primary"
                 @click="submitForm">确认提交</el-button>
      <el-button type="info" @click="saveDraft">保存草稿</el-button>
      <el-button type="primary" @click="submitForm">确认提交</el-button>
      <el-button @click="closeDia">取消</el-button>
    </template>
    <div class="form-dia-body">
      <!-- åŸºç¡€èµ„æ–™ -->
      <el-card class="form-card"
               shadow="never">
      <el-card class="form-card" shadow="never">
        <template #header>
          <span class="card-title"><span class="card-title-line">|</span> åŸºç¡€èµ„æ–™</span>
          <el-icon class="card-collapse">
            <ArrowUp />
          </el-icon>
          <el-icon class="card-collapse"><ArrowUp /></el-icon>
        </template>
        <el-form ref="formRef"
                 :model="form"
                 :rules="rules"
                 label-position="top">
        <el-form ref="formRef" :model="form" :rules="rules" label-position="top">
          <el-row :gutter="24">
            <el-col :span="6">
              <el-form-item label="工资主题"
                            prop="salaryTitle">
                <el-input v-model="form.salaryTitle"
              <el-form-item label="工资主题" prop="salaryTitle">
                <el-input
                  v-model="form.salaryTitle"
                          placeholder="请输入"
                          clearable
                          maxlength="20"
                          show-word-limit />
                  show-word-limit
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="选择工资月份"
                            prop="salaryMonth">
                <el-date-picker v-model="form.salaryMonth"
              <el-form-item label="选择部门" prop="deptIds">
                <el-select
                  v-model="form.deptIds"
                  placeholder="请选择"
                  clearable
                  multiple
                  collapse-tags-tooltip
                  style="width: 100%"
                >
                  <el-option
                    v-for="item in deptOptions"
                    :key="item.deptId"
                    :label="item.deptName"
                    :value="item.deptId"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="选择工资月份" prop="salaryMonth">
                <el-date-picker
                  v-model="form.salaryMonth"
                                type="month"
                                value-format="YYYY-MM"
                                format="YYYY-MM"
                                placeholder="请选择工资月份"
                                style="width: 100%"
                                clearable />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="选择部门"
                            prop="deptIds">
                <el-select v-model="form.deptIds"
                           placeholder="请选择"
                           clearable
                           multiple
                           collapse-tags-tooltip
                           style="width: 100%">
                  <el-option v-for="item in deptOptions"
                             :key="item.deptId"
                             :label="item.deptName"
                             :value="item.deptId"
                             :disabled="isDeptDisabled(item.deptId)" />
                </el-select>
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="备注"
                            prop="remark">
                <el-input v-model="form.remark"
              <el-form-item label="备注" prop="remark">
                <el-input
                  v-model="form.remark"
                          placeholder="请输入"
                          clearable />
                  clearable
                />
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="24">
            <el-col :span="6">
              <el-form-item label="支付银行"
                            prop="payBank">
                <el-select v-model="form.payBank"
              <el-form-item label="支付银行" prop="payBank">
                <el-select
                  v-model="form.payBank"
                           placeholder="请选择"
                           clearable
                           filterable
                           style="width: 100%">
                  <el-option v-for="b in bankOptions"
                  style="width: 100%"
                >
                  <el-option
                    v-for="b in bankOptions"
                             :key="b"
                             :label="b"
                             :value="b" />
                    :value="b"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="审核人"
                            prop="auditUserId">
                <el-select v-model="form.auditUserId"
              <el-form-item label="审核人" prop="auditUserId">
                <el-select
                  v-model="form.auditUserId"
                           placeholder="请选择审核人"
                           clearable
                           filterable
                           style="width: 100%">
                  <el-option v-for="item in userList"
                  style="width: 100%"
                >
                  <el-option
                    v-for="item in userList"
                             :key="item.userId"
                             :label="item.nickName"
                             :value="item.userId" />
                    :value="item.userId"
                  />
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </el-card>
      <!-- æ“ä½œæŒ‰é’® -->
      <div class="toolbar">
        <el-button type="primary"
                   @click="handleGenerate">生成工资表</el-button>
        <el-button type="primary" @click="handleGenerate">生成工资表</el-button>
        <el-button @click="handleClear">清空</el-button>
        <el-button @click="handleBatchDelete">删除</el-button>
        <el-button @click="handleTaxForm">个税表</el-button>
      </div>
      <!-- å‘˜å·¥å·¥èµ„详情表格 -->
      <div class="employee-table-wrap">
        <el-table ref="employeeTableRef"
        <el-table
          ref="employeeTableRef"
                  :data="employeeList"
                  border
                  max-height="400"
                  @selection-change="onEmployeeSelectionChange">
          <el-table-column type="selection"
                           width="55"
                           align="center" />
          <el-table-column label="员工姓名"
                           prop="staffName"
                           minWidth="100" />
          <el-table-column label="部门"
                           prop="deptName"
                           minWidth="100" />
          <el-table-column label="基本工资"
                           minWidth="110">
          @selection-change="onEmployeeSelectionChange"
        >
          <el-table-column type="selection" width="55" align="center" />
          <el-table-column label="员工姓名" prop="staffName" minWidth="100" />
          <el-table-column label="部门" prop="deptName" minWidth="100" />
          <el-table-column label="基本工资" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.basicSalary"
              <el-input
                v-model.number="row.basicSalary"
                        type="number"
                        placeholder="0"
                        size="small"
                        @input="handleSalaryInput(row)" />
                @input="row.basicSalary = parseNum(row.basicSalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="计件工资"
                           minWidth="110">
          <el-table-column label="计件工资" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.pieceSalary"
              <el-input
                v-model.number="row.pieceSalary"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.pieceSalary = parseNum(row.pieceSalary)" />
                @input="row.pieceSalary = parseNum(row.pieceSalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="计时工资"
                           minWidth="110">
          <el-table-column label="计时工资" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.hourlySalary"
              <el-input
                v-model.number="row.hourlySalary"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.hourlySalary = parseNum(row.hourlySalary)" />
                @input="row.hourlySalary = parseNum(row.hourlySalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="其他收入"
                           minWidth="110">
          <el-table-column label="其他收入" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.otherIncome"
              <el-input
                v-model.number="row.otherIncome"
                        type="number"
                        placeholder="0"
                        size="small"
                        @input="handleSalaryInput(row)" />
                @input="row.otherIncome = parseNum(row.otherIncome)"
              />
            </template>
          </el-table-column>
          <el-table-column label="社保个人"
                           minWidth="110">
          <el-table-column label="社保个人" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.socialPersonal"
              <el-input
                v-model.number="row.socialPersonal"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.socialPersonal = parseNum(row.socialPersonal)" />
                @input="row.socialPersonal = parseNum(row.socialPersonal)"
              />
            </template>
          </el-table-column>
          <el-table-column label="公积金个人"
                           minWidth="120">
          <el-table-column label="公积金个人" minWidth="120">
            <template #default="{ row }">
              <el-input v-model.number="row.fundPersonal"
              <el-input
                v-model.number="row.fundPersonal"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.fundPersonal = parseNum(row.fundPersonal)" />
                @input="row.fundPersonal = parseNum(row.fundPersonal)"
              />
            </template>
          </el-table-column>
          <el-table-column label="其他支出"
                           minWidth="110">
          <el-table-column label="其他支出" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.otherDeduct"
              <el-input
                v-model.number="row.otherDeduct"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.otherDeduct = parseNum(row.otherDeduct)" />
                @input="row.otherDeduct = parseNum(row.otherDeduct)"
              />
            </template>
          </el-table-column>
          <el-table-column label="社保补缴"
                           minWidth="120">
          <el-table-column label="工资个税" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.socialSupplementAmount"
              <el-input
                v-model.number="row.salaryTax"
                        type="number"
                        placeholder="0"
                        size="small"
                        @input="handleSalaryInput(row)" />
                @input="row.salaryTax = parseNum(row.salaryTax)"
              />
            </template>
          </el-table-column>
          <el-table-column label="工资个税"
                           minWidth="110">
          <el-table-column label="应发工资" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.salaryTax"
              <el-input
                v-model.number="row.grossSalary"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.salaryTax = parseNum(row.salaryTax)" />
                @input="row.grossSalary = parseNum(row.grossSalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="应发工资"
                           minWidth="110">
          <el-table-column label="应扣工资" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.grossSalary"
              <el-input
                v-model.number="row.deductSalary"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.grossSalary = parseNum(row.grossSalary)" />
                @input="row.deductSalary = parseNum(row.deductSalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="应扣工资"
                           minWidth="110">
          <el-table-column label="实发工资" minWidth="110">
            <template #default="{ row }">
              <el-input v-model.number="row.deductSalary"
              <el-input
                v-model.number="row.netSalary"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.deductSalary = parseNum(row.deductSalary)" />
                @input="row.netSalary = parseNum(row.netSalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="实发工资"
                           minWidth="110">
          <el-table-column label="备注" minWidth="120">
            <template #default="{ row }">
              <el-input v-model.number="row.netSalary"
                        type="number"
                        placeholder="0"
                        size="small"
                        disabled
                        @input="row.netSalary = parseNum(row.netSalary)" />
            </template>
          </el-table-column>
          <el-table-column label="备注"
                           minWidth="120">
            <template #default="{ row }">
              <el-input v-model="row.remark"
              <el-input
                v-model="row.remark"
                        placeholder="请输入"
                        size="small" />
                size="small"
              />
            </template>
          </el-table-column>
          <el-table-column label="操作"
                           width="80"
                           align="center"
                           fixed="right">
          <el-table-column label="操作" width="80" align="center" fixed="right">
            <template #default="{ row }">
              <el-button type="primary"
                         link
                         @click="removeEmployee(row)">删除</el-button>
              <el-button type="primary" link @click="removeEmployee(row)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <div v-if="!employeeList.length"
             class="table-empty">暂无数据</div>
        <div v-else
             class="salary-total">
        <div v-if="!employeeList.length" class="table-empty">暂无数据</div>
        <div v-else class="salary-total">
          <span class="total-label">工资总额:</span>
          <span class="total-value">Â¥ {{ totalSalary.toFixed(2) }}</span>
        </div>
      </div>
    </div>
    <!-- æ–°å¢žäººå‘˜å¼¹çª— -->
    <el-dialog v-model="addPersonVisible"
    <el-dialog
      v-model="addPersonVisible"
               title="新增人员"
               width="400px"
               append-to-body
               @close="addPersonClose">
      @close="addPersonClose"
    >
      <div class="add-person-tree">
        <el-tree ref="personTreeRef"
        <el-tree
          ref="personTreeRef"
                 :data="deptStaffTree"
                 show-checkbox
                 node-key="id"
                 :props="{ label: 'label', children: 'children' }"
                 default-expand-all />
          default-expand-all
        />
      </div>
      <template #footer>
        <el-button @click="addPersonVisible = false">取消</el-button>
        <el-button type="primary"
                   @click="confirmAddPerson">确定</el-button>
        <el-button type="primary" @click="confirmAddPerson">确定</el-button>
      </template>
    </el-dialog>
    <!-- ä¸ªç¨Žè¡¨å¼¹çª— -->
    <el-dialog v-model="taxDialogVisible"
    <el-dialog
      v-model="taxDialogVisible"
               title="个税表"
               width="700px"
               append-to-body>
      append-to-body
    >
      <div class="tax-desc">个人所得税免征额:5000元</div>
      <el-table :data="taxTableData"
                border
                style="width: 100%;margin-bottom: 20px;">
        <el-table-column prop="level"
                         label="级数"
                         width="80"
                         align="center" />
        <el-table-column prop="range"
      <el-table :data="taxTableData" border style="width: 100%;margin-bottom: 20px;">
        <el-table-column prop="level" label="级数" width="80" align="center" />
        <el-table-column
          prop="range"
                         label="全年应纳税所得额/元"
                         min-width="220" />
        <el-table-column prop="rate"
          min-width="220"
        />
        <el-table-column
          prop="rate"
                         label="税率(%)"
                         width="100"
                         align="center" />
        <el-table-column prop="quickDeduction"
          align="center"
        />
        <el-table-column
          prop="quickDeduction"
                         label="速算扣除数/元"
                         width="160"
                         align="center" />
          align="center"
        />
      </el-table>
    </el-dialog>
  </FormDialog>
</template>
<script setup>
  import {
    ref,
    reactive,
    toRefs,
    computed,
    getCurrentInstance,
    nextTick,
    watch,
  } from "vue";
import { ref, reactive, toRefs, computed, getCurrentInstance, nextTick } from "vue";
  import { ArrowUp } from "@element-plus/icons-vue";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import { listDept } from "@/api/system/dept.js";
@@ -356,10 +346,9 @@
  staffSalaryMainAdd,
  staffSalaryMainUpdate,
  staffSalaryMainCalculateSalary,
  staffSalaryMainCalculateByEmployeeId,
  staffSalaryMainAvailableDepts,
} from "@/api/personnelManagement/staffSalaryMain.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  const emit = defineEmits(["update:modelValue", "close"]);
  const props = defineProps({
@@ -372,7 +361,7 @@
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  set: (val) => emit("update:modelValue", val),
  });
  const formRef = ref(null);
@@ -381,14 +370,11 @@
  const addPersonVisible = ref(false);
  const taxDialogVisible = ref(false);
  const deptOptions = ref([]);
  const availableDeptIds = ref(new Set());
  const deptStaffTree = ref([]);
  const employeeList = ref([]);
  const selectedEmployees = ref([]);
  const bankOptions = ref([]);
  const userList = ref([]);
  const recalcTimers = new Map();
  const recalcVersions = new Map();
  const taxTableData = ref([
    { level: 1, range: "不超过36000元", rate: 3, quickDeduction: 0 },
    { level: 2, range: "超过36000-144000元", rate: 10, quickDeduction: 2520 },
@@ -405,113 +391,6 @@
    return isNaN(n) ? 0 : n;
  }
  function parseNullableNum(v) {
    if (v === "" || v == null) return null;
    const n = Number(v);
    return isNaN(n) ? null : n;
  }
  const createEmployeeRow = () => ({
    staffOnJobId: undefined,
    id: undefined,
    staffName: "",
    postName: "",
    deptName: "",
    basicSalary: 0,
    pieceSalary: 0,
    hourlySalary: 0,
    otherIncome: 0,
    socialPersonal: 0,
    fundPersonal: 0,
    otherDeduct: 0,
    socialSupplementAmount: null,
    salaryTax: 0,
    grossSalary: 0,
    deductSalary: 0,
    netSalary: 0,
    remark: "",
  });
  const normalizeEmployeeRow = (source, fallback = {}) => ({
    ...createEmployeeRow(),
    ...fallback,
    ...source,
    staffOnJobId: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id,
    id: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id,
    staffName: source?.staffName ?? fallback.staffName ?? "",
    postName: source?.postName ?? fallback.postName ?? "",
    deptName: source?.deptName ?? fallback.deptName ?? "",
    basicSalary: parseNum(source?.basicSalary ?? fallback.basicSalary),
    pieceSalary: parseNum(source?.pieceSalary ?? fallback.pieceSalary),
    hourlySalary: parseNum(source?.hourlySalary ?? fallback.hourlySalary),
    otherIncome: parseNum(source?.otherIncome ?? fallback.otherIncome),
    socialPersonal: parseNum(source?.socialPersonal ?? fallback.socialPersonal),
    fundPersonal: parseNum(source?.fundPersonal ?? fallback.fundPersonal),
    otherDeduct: parseNum(source?.otherDeduct ?? fallback.otherDeduct),
    socialSupplementAmount:
      source?.socialSupplementAmount ?? fallback.socialSupplementAmount ?? null,
    salaryTax: parseNum(source?.salaryTax ?? fallback.salaryTax),
    grossSalary: parseNum(source?.grossSalary ?? fallback.grossSalary),
    deductSalary: parseNum(source?.deductSalary ?? fallback.deductSalary),
    netSalary: parseNum(source?.netSalary ?? fallback.netSalary),
    remark: source?.remark ?? fallback.remark ?? "",
  });
  const applyBackendRow = (targetRow, sourceRow) => {
    Object.assign(targetRow, normalizeEmployeeRow(sourceRow, targetRow));
  };
  const clearRowRecalcState = row => {
    const key = String(row?.staffOnJobId ?? row?.id ?? "");
    if (!key) return;
    clearTimeout(recalcTimers.get(key));
    recalcTimers.delete(key);
    recalcVersions.delete(key);
  };
  const resetRecalcState = () => {
    recalcTimers.forEach(timer => clearTimeout(timer));
    recalcTimers.clear();
    recalcVersions.clear();
  };
  const requestEmployeeRecalculate = row => {
    const key = String(row?.staffOnJobId ?? row?.id ?? "");
    if (!key || !form.value.salaryMonth) return;
    const version = (recalcVersions.get(key) || 0) + 1;
    recalcVersions.set(key, version);
    clearTimeout(recalcTimers.get(key));
    const payload = {
      staffOnJobId: row.staffOnJobId ?? row.id,
      salaryMonth: form.value.salaryMonth,
      staffName: row.staffName ?? "",
      postName: row.postName ?? "",
      deptName: row.deptName ?? "",
      basicSalary: parseNum(row.basicSalary),
      pieceSalary: parseNum(row.pieceSalary),
      hourlySalary: parseNum(row.hourlySalary),
      otherIncome: parseNum(row.otherIncome),
      otherDeduct: parseNum(row.otherDeduct),
      socialSupplementAmount: parseNullableNum(row.socialSupplementAmount),
      remark: row.remark ?? "",
    };
    recalcTimers.set(
      key,
      setTimeout(() => {
        staffSalaryMainCalculateByEmployeeId(payload).then(res => {
          if (recalcVersions.get(key) !== version) return;
          applyBackendRow(row, res?.data || {});
        });
      }, 250)
    );
  };
  const handleSalaryInput = row => {
    row.basicSalary = parseNum(row.basicSalary);
    row.otherIncome = parseNum(row.otherIncome);
    requestEmployeeRecalculate(row);
  };
  // åŸºç¡€èµ„料表单
  const data = reactive({
    form: {
@@ -524,16 +403,10 @@
      auditUserId: undefined,
    },
    rules: {
      salaryTitle: [
        { required: true, message: "请输入工资主题", trigger: "blur" },
      ],
    salaryTitle: [{ required: true, message: "请输入工资主题", trigger: "blur" }],
      deptIds: [{ required: true, message: "请选择部门", trigger: "change" }],
      salaryMonth: [
        { required: true, message: "请选择工资月份", trigger: "change" },
      ],
      auditUserId: [
        { required: true, message: "请选择审核人", trigger: "change" },
      ],
    salaryMonth: [{ required: true, message: "请选择工资月份", trigger: "change" }],
    auditUserId: [{ required: true, message: "请选择审核人", trigger: "change" }],
    },
  });
  const { form, rules } = toRefs(data);
@@ -551,16 +424,16 @@
  });
  const loadBankOptions = () => {
    return bankList().then(res => {
  return bankList().then((res) => {
      const list = Array.isArray(res?.data) ? res.data : [];
      bankOptions.value = list
        .map(b => (b?.bankName == null ? "" : String(b.bankName).trim()))
        .filter(v => v !== "");
      .map((b) => (b?.bankName == null ? "" : String(b.bankName).trim()))
      .filter((v) => v !== "");
    });
  };
  const loadUserList = () => {
    return userListNoPageByTenantId().then(res => {
  return userListNoPageByTenantId().then((res) => {
      userList.value = res.data || [];
    });
  };
@@ -568,39 +441,18 @@
  // æ‰å¹³åŒ–部门树供下拉使用
  function flattenDept(tree, list = []) {
    if (!tree?.length) return list;
    tree.forEach(node => {
  tree.forEach((node) => {
      list.push({ deptId: node.deptId, deptName: node.deptName });
      if (node.children?.length) flattenDept(node.children, list);
    });
    return list;
  }
  // åŠ è½½æ‰€æœ‰éƒ¨é—¨
  const loadAllDeptOptions = () => {
    listDept().then(res => {
const loadDeptOptions = () => {
  listDept().then((res) => {
      const tree = res.data ?? [];
      deptOptions.value = flattenDept(tree);
    });
  };
  // åŠ è½½å¯ç”¨éƒ¨é—¨åˆ—è¡¨ï¼ˆç”¨äºŽåˆ¤æ–­éƒ¨é—¨æ˜¯å¦å¯é€‰ï¼‰
  const loadAvailableDepts = (salaryMonth) => {
    if (!salaryMonth) {
      availableDeptIds.value = new Set();
      return;
    }
    staffSalaryMainAvailableDepts(salaryMonth).then(res => {
      const availableDepts = Array.isArray(res.data) ? res.data : [];
      availableDeptIds.value = new Set(availableDepts.map(d => d.deptId));
    });
  };
  // åˆ¤æ–­éƒ¨é—¨æ˜¯å¦ç¦ç”¨
  const isDeptDisabled = (deptId) => {
    // å¦‚果没有选择月份,所有部门都可选
    if (!form.value.salaryMonth) return false;
    // å¦‚果已选择月份,只有在可用部门列表中的部门才可选
    return !availableDeptIds.value.has(deptId);
  };
  // æž„建 éƒ¨é—¨-人员 æ ‘(用于新增人员弹窗)
@@ -610,7 +462,7 @@
      const staffList = staffRes.data ?? [];
      const deptMap = new Map();
      function walk(nodes) {
        nodes.forEach(node => {
      nodes.forEach((node) => {
          deptMap.set(node.deptId, {
            id: "dept_" + node.deptId,
            deptId: node.deptId,
@@ -622,7 +474,7 @@
        });
      }
      walk(tree);
      staffList.forEach(s => {
    staffList.forEach((s) => {
        const deptId = s.deptId ?? s.dept_id;
        const node = deptMap.get(deptId);
        if (node) {
@@ -636,17 +488,16 @@
        }
      });
      deptStaffTree.value = Array.from(deptMap.values()).filter(
        n => n.children && n.children.length > 0
      (n) => n.children && n.children.length > 0
      );
    });
  };
  const openDialog = (type, row) => {
    nextTick(() => {
      resetRecalcState();
    loadDeptOptions();
      loadBankOptions();
      loadUserList();
      loadAllDeptOptions();
      employeeList.value = [];
      Object.assign(form.value, {
        id: undefined,
@@ -663,10 +514,7 @@
        form.value.salaryTitle = row.salaryTitle ?? "";
        // deptIds åŽç«¯æ˜¯å­—符串(多个用逗号分隔);当前表单仍是单选 deptId
        form.value.deptIds = row.deptIds
          ? String(row.deptIds)
              .split(",")
              .map(id => Number(id.trim()))
              .filter(Boolean)
        ? String(row.deptIds).split(",").map((id) => Number(id.trim())).filter(Boolean)
          : [];
        form.value.salaryMonth = row.salaryMonth ?? "";
        form.value.remark = row.remark ?? "";
@@ -675,24 +523,29 @@
        // å¦‚果有员工明细数据,直接反显
        if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) {
          employeeList.value = row.staffSalaryDetailList.map(e => normalizeEmployeeRow(e));
        employeeList.value = row.staffSalaryDetailList.map((e) => ({
          staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
          id: e.staffOnJobId ?? e.staffId ?? e.id,
          staffName: e.staffName ?? "",
          postName: e.postName ?? "",
          deptName: e.deptName ?? "",
          basicSalary: parseNum(e.basicSalary),
          pieceSalary: parseNum(e.pieceSalary),
          hourlySalary: parseNum(e.hourlySalary),
          otherIncome: parseNum(e.otherIncome),
          socialPersonal: parseNum(e.socialPersonal),
          fundPersonal: parseNum(e.fundPersonal),
          otherDeduct: parseNum(e.otherDeduct),
          salaryTax: parseNum(e.salaryTax),
          grossSalary: parseNum(e.grossSalary),
          deductSalary: parseNum(e.deductSalary),
          netSalary: parseNum(e.netSalary),
          remark: e.remark ?? "",
        }));
        }
      }
      // ç¼–辑状态且有月份时,加载可用部门列表
      if (type === "edit" && form.value.salaryMonth) {
        loadAvailableDepts(form.value.salaryMonth);
      }
    });
  };
  // ç›‘听工资月份变化,清空部门选择并加载可用部门列表
  watch(() => form.value.salaryMonth, (newMonth, oldMonth) => {
    // åˆ‡æ¢æœˆä»½æ—¶æ¸…空已选择的部门
    if (newMonth !== oldMonth) {
      form.value.deptIds = [];
    }
    loadAvailableDepts(newMonth);
  });
  const openAddPerson = () => {
    loadDeptStaffTree();
@@ -711,35 +564,42 @@
      return;
    }
    const checked = tree.getCheckedNodes();
    const staffNodes = checked.filter(n => n.type === "staff");
    const existIds = new Set(employeeList.value.map(e => e.staffId || e.id));
    staffNodes.forEach(node => {
  const staffNodes = checked.filter((n) => n.type === "staff");
  const existIds = new Set(employeeList.value.map((e) => e.staffId || e.id));
  staffNodes.forEach((node) => {
      const id = node.staffId ?? node.id;
      if (existIds.has(id)) return;
      existIds.add(id);
      const newRow = normalizeEmployeeRow({
    employeeList.value.push({
        staffOnJobId: id,
        id,
        staffName: node.label,
        postName: node.postName ?? node.post ?? "",
        deptName: node.deptName ?? "",
      basicSalary: 0,
      pieceSalary: 0,
      hourlySalary: 0,
      otherIncome: 0,
      socialPersonal: 0,
      fundPersonal: 0,
      otherDeduct: 0,
      salaryTax: 0,
      grossSalary: 0,
      deductSalary: 0,
      netSalary: 0,
        remark: "",
        socialSupplementAmount: null,
      });
      employeeList.value.push(newRow);
      requestEmployeeRecalculate(newRow);
    });
    addPersonVisible.value = false;
  };
  const removeEmployee = row => {
    clearRowRecalcState(row);
const removeEmployee = (row) => {
    employeeList.value = employeeList.value.filter(
      e => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id)
    (e) => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id)
    );
  };
  const onEmployeeSelectionChange = selection => {
const onEmployeeSelectionChange = (selection) => {
    selectedEmployees.value = selection;
  };
@@ -748,10 +608,9 @@
      proxy.$modal.msgWarning("请先勾选要删除的员工");
      return;
    }
    const ids = new Set(selectedEmployees.value.map(e => e.staffOnJobId || e.id));
    selectedEmployees.value.forEach(item => clearRowRecalcState(item));
  const ids = new Set(selectedEmployees.value.map((e) => e.staffOnJobId || e.id));
    employeeList.value = employeeList.value.filter(
      e => !ids.has(e.staffOnJobId || e.id)
    (e) => !ids.has(e.staffOnJobId || e.id)
    );
  };
@@ -768,26 +627,39 @@
      ids: form.value.deptIds,
      date: form.value.salaryMonth,
    };
    staffSalaryMainCalculateSalary(payload).then(res => {
  staffSalaryMainCalculateSalary(payload).then((res) => {
      const list = Array.isArray(res?.data) ? res.data : [];
      if (!list.length) {
        proxy.$modal.msgWarning("未计算到工资数据");
        return;
      }
      resetRecalcState();
      employeeList.value = list.map(e => normalizeEmployeeRow(e));
    employeeList.value = list.map((e) => ({
      ...e,
      staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
      staffName: e.staffName,
      postName: e.postName,
      deptName: e.deptName,
      basicSalary: parseNum(e.basicSalary),
      pieceSalary: parseNum(e.pieceSalary),
      hourlySalary: parseNum(e.hourlySalary),
      otherIncome: parseNum(e.otherIncome),
      socialPersonal: parseNum(e.socialPersonal),
      fundPersonal: parseNum(e.fundPersonal),
      otherDeduct: parseNum(e.otherDeduct),
      salaryTax: parseNum(e.salaryTax),
      grossSalary: parseNum(e.grossSalary),
      deductSalary: parseNum(e.deductSalary),
      netSalary: parseNum(e.netSalary),
      remark: e.remark ?? "",
    }));
      proxy.$modal.msgSuccess("生成成功");
    });
  };
  const handleClear = () => {
    proxy.$modal
      .confirm("确定清空当前员工列表吗?")
      .then(() => {
        resetRecalcState();
  proxy.$modal.confirm("确定清空当前员工列表吗?").then(() => {
        employeeList.value = [];
      })
      .catch(() => {});
  }).catch(() => {});
  };
  const handleTaxForm = () => {
@@ -795,20 +667,20 @@
  };
  const submitForm = () => {
    formRef.value?.validate(valid => {
  formRef.value?.validate((valid) => {
      if (!valid) return;
      saveData(3); // ç¡®è®¤æäº¤ï¼ŒçŠ¶æ€ä¸º3(待审核)
    });
  };
  const saveDraft = () => {
    formRef.value?.validate(valid => {
  formRef.value?.validate((valid) => {
      if (!valid) return;
      saveData(1); // ä¿å­˜è‰ç¨¿ï¼ŒçŠ¶æ€ä¸º1(草稿)
    });
  };
  const saveData = status => {
const saveData = (status) => {
    const payload = {
      id: form.value.id,
      salaryTitle: form.value.salaryTitle,
@@ -819,7 +691,7 @@
      auditUserId: form.value.auditUserId,
      auditUserName: auditUserName.value,
      totalSalary: totalSalary.value,
      staffSalaryDetailList: employeeList.value.map(e => ({
    staffSalaryDetailList: employeeList.value.map((e) => ({
        staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
        staffName: e.staffName,
        postName: e.postName ?? "",
@@ -831,7 +703,6 @@
        socialPersonal: parseNum(e.socialPersonal),
        fundPersonal: parseNum(e.fundPersonal),
        otherDeduct: parseNum(e.otherDeduct),
        socialSupplementAmount: parseNum(e.socialSupplementAmount),
        salaryTax: parseNum(e.salaryTax),
        grossSalary: parseNum(e.grossSalary),
        deductSalary: parseNum(e.deductSalary),
@@ -853,7 +724,6 @@
  };
  const closeDia = () => {
    resetRecalcState();
    dialogVisible.value = false;
    emit("close");
  };
src/views/personnelManagement/monthlyStatistics/index.vue
@@ -107,7 +107,7 @@
  nextTick,
} from "vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user";
import Cookies from "js-cookie";
import FormDia from "./components/formDia.vue";
import BankSettingDia from "./components/bankSettingDia.vue";
import AuditDia from "./components/auditDia.vue";
@@ -167,12 +167,7 @@
      {
        name: "审核",
        type: "text",
        disabled: (row) => {
          const currentUserId = Number(userStore.id);
          const auditUserId = Number(row?.auditUserId);
          // çŠ¶æ€ä¸æ˜¯å¾…å®¡æ ¸ æˆ– å½“前用户不是指定的审核人,则禁用审核按钮
          return Number(row?.status) !== 3 || !currentUserId || currentUserId !== auditUserId;
        },
        disabled: (row) => Number(row?.status) !== 3,
        clickFun: (row) => openAudit(row),
      },
      {
@@ -198,7 +193,6 @@
const operationType = ref("add");
const currentRow = ref({});
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const bankSetting = ref({});
const bankDialogVisible = ref(false);
const bankDiaRef = ref(null);
@@ -384,8 +378,8 @@
const handleExport = () => {
  proxy.download(
    "/staffSalaryMain/export",
    { ...searchForm.value },
    "/compensationPerformance/export",
    { ...searchForm.value, current: page.current, size: page.size },
    "工资表.xlsx"
  );
};
src/views/personnelManagement/socialSecuritySet/components/formDia.vue
@@ -1,31 +1,34 @@
<template>
  <div>
    <FormDialog v-model="dialogFormVisible"
    <FormDialog
      v-model="dialogFormVisible"
                :operation-type="operationType"
                :title="dialogTitle"
                width="80%"
                @close="closeDia"
                @confirm="submitForm"
                @cancel="closeDia">
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-position="top">
      @cancel="closeDia"
    >
      <el-form ref="formRef" :model="form" :rules="rules" label-position="top">
        <el-row :gutter="24">
          <!-- å·¦ä¾§ï¼šé€‚用人员 -->
          <el-col :span="8">
            <el-form-item label="适用人员:"
                          prop="deptIds">
            <el-form-item label="适用人员:" prop="deptIds">
              <div class="dept-checkbox-wrap">
                <el-checkbox-group v-model="form.deptIds"
                                   :disabled="isDetail">
                  <div v-for="dept in deptList"
                <el-checkbox-group
                  v-model="form.deptIds"
                  :disabled="isDetail"
                >
                  <div
                    v-for="dept in deptList"
                       :key="dept.deptId"
                       class="dept-checkbox-item">
                    class="dept-checkbox-item"
                  >
                    <el-checkbox :value="dept.deptId">
                      {{ dept.deptName }}
                      <span v-if="dept.personCount != null"
                            class="dept-count">{{ dept.personCount }}人</span>
                      <span v-if="dept.personCount != null" class="dept-count"
                        >{{ dept.personCount }}人</span
                      >
                    </el-checkbox>
                  </div>
                </el-checkbox-group>
@@ -35,117 +38,124 @@
          <!-- å³ä¾§ï¼šåŸºç¡€ä¿¡æ¯ + ä¿é™©ç±»åž‹ -->
          <el-col :span="16">
            <!-- åŸºç¡€ä¿¡æ¯ -->
            <el-card class="form-card"
                     shadow="never">
            <el-card class="form-card" shadow="never">
              <template #header>
                <span class="card-title"><span class="card-title-line">|</span> åŸºç¡€ä¿¡æ¯</span>
                <el-icon class="card-collapse">
                  <ArrowUp />
                </el-icon>
                <el-icon class="card-collapse"><ArrowUp /></el-icon>
              </template>
              <el-form-item label="方案标题:"
                            prop="title">
                <el-input v-model="form.title"
              <el-form-item label="方案标题:" prop="title">
                <el-input
                  v-model="form.title"
                          placeholder="请输入"
                          clearable
                          :disabled="isDetail" />
                  :disabled="isDetail"
                />
              </el-form-item>
              <el-form-item label="备注:"
                            prop="remark">
                <el-input v-model="form.remark"
              <el-form-item label="备注:" prop="remark">
                <el-input
                  v-model="form.remark"
                          type="textarea"
                          :rows="2"
                          placeholder="请输入"
                          clearable
                          :disabled="isDetail" />
                  :disabled="isDetail"
                />
              </el-form-item>
            </el-card>
            <!-- ä¿é™©ç±»åž‹ -->
            <el-card class="form-card"
                     shadow="never">
            <el-card class="form-card" shadow="never">
              <template #header>
                <span class="card-title"><span class="card-title-line">|</span> ä¿é™©ç±»åž‹</span>
                <el-button v-if="!isDetail"
                <el-button
                  v-if="!isDetail"
                           type="primary"
                           size="small"
                           @click="addInsuranceBenefit">
                  @click="addInsuranceBenefit"
                >
                  æ·»åŠ ä¿é™©ç¦åˆ©
                </el-button>
              </template>
              <el-row :gutter="16">
                <el-col v-for="(item, index) in form.insuranceBenefits"
                <el-col
                  v-for="(item, index) in form.insuranceBenefits"
                        :key="item._key"
                        :span="12">
                  :span="12"
                >
                  <div class="insurance-benefit-card">
                    <div class="insurance-benefit-title">
                      ä¿é™©ç¦åˆ©{{ index + 1 }}
                      <el-button v-if="!isDetail && form.insuranceBenefits.length > 1"
                      <el-button
                        v-if="!isDetail && form.insuranceBenefits.length > 1"
                                 type="danger"
                                 link
                                 size="small"
                                 class="card-delete-btn"
                                 @click="removeInsuranceBenefit(index)">
                        @click="removeInsuranceBenefit(index)"
                      >
                        åˆ é™¤
                      </el-button>
                    </div>
                    <el-form-item :prop="'insuranceBenefits.' + index + '.insuranceType'"
                    <el-form-item
                      :prop="'insuranceBenefits.' + index + '.insuranceType'"
                                  label="保险类型:"
                                  label-width="100px"
                                  :rules="[{ required: true, message: '请选择保险类型', trigger: 'blur' }]"
                    >
                      <el-select v-model="item.insuranceType"
                      <el-select
                        v-model="item.insuranceType"
                                 placeholder="请选择"
                                 clearable
                                 style="width: 100%"
                                 :disabled="isDetail">
                        <el-option v-for="opt in insuranceTypeOptions"
                        :disabled="isDetail"
                      >
                        <el-option
                          v-for="opt in insuranceTypeOptions"
                                   :key="opt.value"
                                   :label="opt.label"
                                   :value="opt.value" />
                          :value="opt.value"
                        />
                      </el-select>
                    </el-form-item>
                    <el-form-item label="缴费基数:"
                                  label-width="100px"
                                  :prop="'insuranceBenefits.' + index + '.paymentBase'"
                                  :rules="[{ validator: validatePaymentBase, trigger: 'blur' }]">
                    <el-form-item label="缴费基数:" label-width="100px">
                      <div class="base-salary-wrap">
                        <el-input v-model="item.paymentBase"
                        <el-input
                          v-model="item.paymentBase"
                                  placeholder="根据基本工资缴纳"
                                  clearable
                                  style="width: 120px"
                                  type="number"
                                  :disabled="isDetail || item.useBasicSalary"
                                  @input="handlePaymentBaseInput(item)" />
                        <el-checkbox v-model="item.useBasicSalary"
                                     @change="(val) => handleUseBasicSalaryChange(item, index)"
                                     :disabled="isDetail">
                          @input="handlePaymentBaseInput(item)"
                        />
                        <el-checkbox
                          v-model="item.useBasicSalary"
                          @change="handleUseBasicSalaryChange(item)"
                          :disabled="isDetail"
                        >
                          è°ƒç”¨åŸºæœ¬å·¥èµ„
                        </el-checkbox>
                      </div>
                    </el-form-item>
                    <el-form-item label="个人缴费比例:"
                                  label-width="100px"
                                  :prop="'insuranceBenefits.' + index + '.personalRatio'"
                                  :rules="[{ required: true, message: '请输入个人缴费比例', trigger: 'blur' }]">
                    <el-form-item label="个人缴费比例:" label-width="100px">
                      <div class="personal-ratio-wrap">
                        <el-input v-model="item.personalRatio"
                        <el-input
                          v-model="item.personalRatio"
                                  placeholder="请输入"
                                  clearable
                                  style="width: 100px"
                                  type="number"
                                  :disabled="isDetail"
                                  :min="0"
                                  @input="handlePersonalRatioInput(item)" />
                        />
                        <span class="ratio-unit">(%)</span>
                        <span class="ratio-plus">+</span>
                        <el-input v-model="item.personalFixed"
                        <el-input
                          v-model="item.personalFixed"
                                  placeholder="请输入"
                                  clearable
                                  style="width: 100px"
                                  type="number"
                                  :disabled="isDetail"
                                  :min="0"
                                  @input="handlePersonalFixedInput(item)" />
                        />
                      </div>
                    </el-form-item>
                  </div>
@@ -160,21 +170,11 @@
</template>
<script setup>
  import {
    ref,
    reactive,
    toRefs,
    getCurrentInstance,
    nextTick,
    computed,
  } from "vue";
import { ref, reactive, toRefs, getCurrentInstance, nextTick, computed } from "vue";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import { ArrowUp } from "@element-plus/icons-vue";
  import { listDept } from "@/api/system/dept.js";
  import {
    socialSecurityAdd,
    socialSecurityUpdate,
  } from "@/api/personnelManagement/socialSecuritySet.js";
import { socialSecurityAdd, socialSecurityUpdate } from "@/api/personnelManagement/socialSecuritySet.js";
  const emit = defineEmits(["close"]);
  const { proxy } = getCurrentInstance();
@@ -237,7 +237,7 @@
  function flattenDept(tree, list = []) {
    if (!tree || !tree.length) return list;
    tree.forEach(node => {
  tree.forEach((node) => {
      list.push({
        deptId: node.deptId,
        deptName: node.deptName,
@@ -251,7 +251,7 @@
  }
  const loadDeptList = () => {
    listDept().then(res => {
  listDept().then((res) => {
      const tree = res.data ?? [];
      deptList.value = flattenDept(tree);
    });
@@ -261,55 +261,19 @@
    form.value.insuranceBenefits.push(defaultBenefit());
  };
  const removeInsuranceBenefit = index => {
const removeInsuranceBenefit = (index) => {
    form.value.insuranceBenefits.splice(index, 1);
  };
  const handleUseBasicSalaryChange = (item, index) => {
const handleUseBasicSalaryChange = (item) => {
    if (item.useBasicSalary) {
      item.paymentBase = "";
    }
    nextTick(() => {
      formRef.value?.validateField(`insuranceBenefits.${index}.paymentBase`);
    });
  };
  const handlePaymentBaseInput = item => {
const handlePaymentBaseInput = (item) => {
    if (item.paymentBase !== "" && item.paymentBase != null) {
      item.useBasicSalary = false;
    }
  };
  const handlePersonalRatioInput = item => {
    if (item.personalRatio !== "" && item.personalRatio != null) {
      const value = Number(item.personalRatio);
      if (value < 0) {
        item.personalRatio = "";
      }
    }
  };
  const handlePersonalFixedInput = item => {
    if (item.personalFixed !== "" && item.personalFixed != null) {
      const value = Number(item.personalFixed);
      if (value < 0) {
        item.personalFixed = "";
      }
    }
  };
  const validatePaymentBase = (rule, value, callback) => {
    const match = rule.field.match(/insuranceBenefits\.(\d+)\.paymentBase/);
    if (match) {
      const index = parseInt(match[1], 10);
      const item = form.value.insuranceBenefits[index];
      if (item && !item.useBasicSalary && (!value || value === "")) {
        callback(new Error("请输入缴费基数"));
      } else {
        callback();
      }
    } else {
      callback();
    }
  };
@@ -337,8 +301,8 @@
      if (d.deptIds) {
        form.value.deptIds = String(d.deptIds)
          .split(",")
          .filter(v => v !== "")
          .map(v => {
        .filter((v) => v !== "")
        .map((v) => {
            const num = Number(v);
            return Number.isNaN(num) ? v : num;
          });
@@ -348,7 +312,7 @@
      const detailList = d.schemeInsuranceDetailList || [];
      form.value.insuranceBenefits =
        detailList.length > 0
          ? detailList.map(b => ({
        ? detailList.map((b) => ({
              _key: Math.random().toString(36).slice(2),
              insuranceType: b.insuranceType || "",
              paymentBase: b.paymentBase ?? "",
@@ -366,7 +330,7 @@
      closeDia();
      return;
    }
    formRef.value?.validate(valid => {
  formRef.value?.validate((valid) => {
      if (!valid) return;
      const deptIds =
        Array.isArray(form.value.deptIds) && form.value.deptIds.length
@@ -379,15 +343,17 @@
        })
      );
      const insuranceTypes = schemeInsuranceDetailList
        .map(item => item.insuranceType)
        .filter(v => v)
      .map((item) => item.insuranceType)
      .filter((v) => v)
        .join(",");
      // éƒ¨é—¨åç§°ï¼Œå¤šä¸ªä½¿ç”¨é€—号隔开(根据选中的 deptIds ä¸Ž deptList è®¡ç®—)
      const deptNames = (deptList.value || [])
        .filter(d =>
          (form.value.deptIds || []).some(id => String(id) === String(d.deptId))
      .filter((d) =>
        (form.value.deptIds || []).some(
          (id) => String(id) === String(d.deptId)
        )
        .map(d => d.deptName)
      )
      .map((d) => d.deptName)
        .join(",");
      const submitData = {
        id: form.value.id,
src/views/procurementManagement/paymentLedger/index.vue
@@ -42,20 +42,16 @@
                             prop="contractAmounts"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
            <el-table-column label="付款金额(元)"
                             prop="paymentAmount"
            <el-table-column label="已入库金额(元)"
                             prop="shippedAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
            <el-table-column label="退货金额(元)"
                             prop="returnAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
            <el-table-column label="应付金额(元)"
                             prop="payableAmount"
            <el-table-column label="未入库金额(元)"
                             prop="unshippedAmount"
                             show-overflow-tooltip>
              <template #default="{ row, column }">
                <el-text type="danger">
                  {{ formattedNumber(row, column, row.payableAmount) }}
                  {{ formattedNumber(row, column, row.unshippedAmount) }}
                </el-text>
              </template>
            </el-table-column>
@@ -83,7 +79,7 @@
                    @pagination="sonPaginationSearch">
            <template #payableAmountSlot="{ row }">
              <el-text type="danger">
                {{ parseFloat(row.payableAmount).toFixed(2) }}
                {{ formatDecimal(row.unshippedAmount) }}
              </el-text>
            </template>
          </PIMTable>
@@ -94,13 +90,14 @@
</template>
<script setup>
  import { ref, toRefs } from "vue";
  import { ref, toRefs, reactive, getCurrentInstance } from "vue";
  import { Search } from "@element-plus/icons-vue";
  import {
    paymentLedgerList,
    paymentRecordList,
  } from "@/api/procurementManagement/paymentLedger.js";
  import Pagination from "../../../components/PIMTable/Pagination.vue";
  import { tableAmountFormatter, formatDecimal, buildAmountSummaryFormat } from "@/utils/numberFormat";
  const tableData = ref([]);
  const tableLoading = ref(false);
@@ -144,31 +141,19 @@
      label: "合同金额(元)",
      prop: "contractAmount",
      width: 200,
      formatData: params => {
        return params ? parseFloat(params).toFixed(2) : 0;
      },
      formatData: params => formatDecimal(params),
    },
    {
      label: "付款金额(元)",
      prop: "paymentAmount",
      label: "已入库金额(元)",
      prop: "shippedAmount",
      width: 200,
      formatData: params => {
        return params ? parseFloat(params).toFixed(2) : 0;
      },
      formatData: params => formatDecimal(params),
    },
    {
      label: "退货金额(元)",
      prop: "returnAmount",
      width: 200,
      formatData: params => {
        return params ? parseFloat(params).toFixed(2) : 0;
      },
    },
    {
      label: "应付金额(元)",
      label: "未入库金额(元)",
      dataType: "slot",
      width: 200,
      prop: "payableAmount",
      prop: "unshippedAmount",
      slot: "payableAmountSlot",
    },
  ]);
@@ -182,31 +167,29 @@
  const summarizeMainTable = param => {
    return proxy.summarizeTable(
      param,
      ["contractAmounts", "paymentAmount", "returnAmount", "payableAmount"],
      ["contractAmounts", "shippedAmount", "unshippedAmount"],
      buildAmountSummaryFormat(
        ["contractAmounts", "shippedAmount", "unshippedAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
          ticketsNum: { noDecimal: true },
          futureTickets: { noDecimal: true },
      }
      )
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable1 = param => {
    let summarizeTable = proxy.summarizeTable(
      param,
      ["contractAmount", "invoiceAmount", "paymentAmount", "returnAmount"],
      ["contractAmount", "shippedAmount", "unshippedAmount"],
      buildAmountSummaryFormat(
        ["contractAmount", "shippedAmount", "unshippedAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
          ticketsNum: { noDecimal: true },
          futureTickets: { noDecimal: true },
      }
      )
    );
    if (originalTableDataSon.value.length > 0) {
      summarizeTable[summarizeTable.length - 1] =
        originalTableDataSon.value[
          originalTableDataSon.value.length - 1
        ].payableAmount.toFixed(2);
    } else {
      summarizeTable[summarizeTable.length - 1] = 0.0;
    }
    return summarizeTable;
  };
  /** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -277,13 +260,7 @@
    sonPage.size = pagination.limit;
    getPaymenRecordtList(currentSupplierId.value);
  };
  const formattedNumber = (row, column, cellValue) => {
    if (column.property !== "supplierName") {
      return parseFloat(cellValue).toFixed(2);
    } else {
      return cellValue;
    }
  };
  const formattedNumber = tableAmountFormatter;
  getList();
</script>
src/views/procurementManagement/procurementLedger/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,246 @@
<template>
  <FormDialog v-model="visible" title="采购台账详情" width="70%" operation-type="detail" @close="handleClose">
    <el-form :model="form" label-width="140px" label-position="top">
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="采购合同号:">
            <span class="readonly-text">{{ form.purchaseContractNumber || '--' }}</span>
          </el-form-item>
        </el-col>
        <el-col v-if="showSalesContractBinding" :span="12">
          <el-form-item label="销售合同号:">
            <span class="readonly-text">{{ form.salesContractNo || '--' }}</span>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="供应商名称:">
            <span class="readonly-text">{{ form.supplierName || '--' }}</span>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="项目名称:">
            <span class="readonly-text">{{ form.projectName || '--' }}</span>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="付款方式:">
            <span class="readonly-text">{{ form.paymentMethod || '--' }}</span>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="签订日期:">
            <span class="readonly-text">{{ form.executionDate || '--' }}</span>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="录入人:">
            <span class="readonly-text">{{ form.recorderName || '--' }}</span>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="录入日期:">
            <span class="readonly-text">{{ form.entryDate || '--' }}</span>
          </el-form-item>
        </el-col>
      </el-row>
      <div style="font-weight: bold; margin: 10px 0; color: #f56c6c">
        * äº§å“ä¿¡æ¯ï¼š
      </div>
      <el-table :data="productData" border>
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column label="产品大类" prop="productCategory" />
        <el-table-column label="规格型号" prop="specificationModel" />
        <el-table-column label="单位" prop="unit" />
        <el-table-column label="数量" prop="quantity" />
        <el-table-column label="库存预留数量" prop="stockReservedQuantity" />
        <el-table-column label="税率(%)" prop="taxRate" />
        <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" width="150" />
        <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" width="150" />
        <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" width="150" />
        <template #append>
          <div class="summary-row" v-if="productData.length > 0">
            <div class="summary-label">合计</div>
            <div class="summary-value" style="width: 150px">
              {{ formattedNumber(null, null, sumTaxInclusiveUnitPrice) }}
            </div>
            <div class="summary-value" style="width: 150px">
              {{ formattedNumber(null, null, sumTaxInclusiveTotalPrice) }}
            </div>
            <div class="summary-value" style="width: 150px">
              {{ formattedNumber(null, null, sumTaxExclusiveTotalPrice) }}
            </div>
          </div>
        </template>
      </el-table>
      <el-row :gutter="30" style="margin-top: 20px;">
        <el-col :span="24">
          <el-form-item label="备注:">
            <div class="readonly-text-area">{{ form.remarks || '--' }}</div>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="24">
          <el-form-item label="附件材料:">
            <div class="table-container" style="width: 100%;">
              <el-table :data="fileList" border class="attachment-table" style="width: 100%;"
                :height="fileList.length > 0 ? 'auto' : '120px'">
                <el-table-column label="附件名称" prop="originalFilename" show-overflow-tooltip min-width="200">
                  <template #default="scope">
                    {{ scope.row.name || scope.row.originalFilename || ('附件' + (scope.$index + 1)) }}
                  </template>
                </el-table-column>
                <el-table-column fixed="right" label="操作" :width="150" align="center">
                  <template #default="scope">
                    <el-button link type="primary" size="small" class="download-link"
                      @click="previewFile(scope.row.previewURL || scope.row.url)">
                      é¢„览
                    </el-button>
                    <el-button link type="primary" size="small" class="download-link"
                      @click="downloadFile(scope.row.downloadURL || scope.row.url)">
                      ä¸‹è½½
                    </el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </FormDialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import FormDialog from "@/components/Dialog/FormDialog.vue"
import filePreview from '@/components/filePreview/index.vue'
import { getPurchaseById } from "@/api/procurementManagement/procurementLedger"
import { tableAmountFormatter } from '@/utils/numberFormat'
const visible = ref(false)
// æ˜¯å¦æ˜¾ç¤ºé”€å”®åˆåŒå·ç»‘定
const showSalesContractBinding = false
const form = ref({})
const productData = ref([])
const fileList = ref([])
const filePreviewRef = ref()
const open = (id) => {
  visible.value = true
  getPurchaseById({ id: id, type: 2 }).then(res => {
    form.value = res.data ? res.data : res || {}
    productData.value = form.value.purchaseLedgerProductList || form.value.productData || []
    fileList.value = form.value.storageBlobVOS || []
  })
}
// æš´éœ² open æ–¹æ³•给父组件调用
defineExpose({
  open
})
const handleClose = () => {
  visible.value = false
  form.value = {}
  productData.value = []
  fileList.value = []
}
const formattedNumber = tableAmountFormatter
const sumTaxInclusiveUnitPrice = computed(() => {
  return productData.value.reduce((sum, item) => {
    return sum + (Number(item.taxInclusiveUnitPrice) || 0)
  }, 0)
})
const sumTaxInclusiveTotalPrice = computed(() => {
  return productData.value.reduce((sum, item) => {
    return sum + (Number(item.taxInclusiveTotalPrice) || 0)
  }, 0)
})
const sumTaxExclusiveTotalPrice = computed(() => {
  return productData.value.reduce((sum, item) => {
    return sum + (Number(item.taxExclusiveTotalPrice) || 0)
  }, 0)
})
const previewFile = (url) => {
  if (url) {
    filePreviewRef.value.open(url)
  } else {
    ElMessage.warning('文件地址无效,无法预览')
  }
}
const downloadFile = (url) => {
  window.open(url, "_blank")
}
</script>
<style scoped>
.readonly-text {
  width: 100%;
  padding: 0 12px;
  background-color: #f5f7fa;
  color: #606266;
  border-radius: 4px;
  border: 1px solid #e4e7ed;
  box-sizing: border-box;
  min-height: 32px;
  line-height: 32px;
  display: inline-block;
}
.readonly-text-area {
  width: 100%;
  padding: 8px 12px;
  background-color: #f5f7fa;
  color: #606266;
  border-radius: 4px;
  border: 1px solid #e4e7ed;
  box-sizing: border-box;
  min-height: 64px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-all;
}
.summary-row {
  display: flex;
  background-color: #f5f7fa;
  font-weight: bold;
  border-top: 1px solid #ebeef5;
}
.summary-label {
  flex: 1;
  text-align: right;
  padding: 12px;
  color: #606266;
}
.summary-value {
  padding: 12px;
  color: #606266;
  border-left: 1px solid #ebeef5;
}
.table-container {
  overflow-y: auto;
  box-sizing: border-box;
}
</style>
src/views/procurementManagement/procurementLedger/index.vue
@@ -2,63 +2,37 @@
  <div class="app-container">
    <div class="search_form">
      <div>
        <el-form :model="searchForm"
                 :inline="true">
        <el-form :model="searchForm" :inline="true">
          <el-form-item label="供应商名称:">
            <el-input v-model="searchForm.supplierName"
                      placeholder="请输入"
                      clearable
                      prefix-icon="Search"
            <el-input v-model="searchForm.supplierName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="采购合同号:">
            <el-input v-model="searchForm.purchaseContractNumber"
                      style="width: 240px"
                      placeholder="请输入"
                      @change="handleQuery"
                      clearable
                      :prefix-icon="Search" />
            <el-input v-model="searchForm.purchaseContractNumber" style="width: 240px" placeholder="请输入"
              @change="handleQuery" clearable :prefix-icon="Search" />
          </el-form-item>
          <el-form-item label="销售合同号:">
            <el-input v-model="searchForm.salesContractNo"
                      placeholder="请输入"
                      clearable
                      prefix-icon="Search"
          <el-form-item v-if="showSalesContractBinding" label="销售合同号:">
            <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="项目名称:">
            <el-input v-model="searchForm.projectName"
                      placeholder="请输入"
                      clearable
                      prefix-icon="Search"
            <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="录入日期:">
            <el-date-picker v-model="searchForm.entryDate"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            type="daterange"
                            placeholder="请选择"
                            clearable
                            @change="changeDaterange" />
            <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
              type="daterange" placeholder="请选择" clearable @change="changeDaterange" />
          </el-form-item>
          <el-form-item label="入库状态:">
            <el-select v-model="searchForm.stockInStatus"
                       placeholder="请选择"
                       clearable
                       style="width: 240px"
            <el-select v-model="searchForm.stockInStatus" placeholder="请选择" clearable style="width: 240px"
                       @change="handleQuery">
              <el-option label="待入库"
                         value="待入库" />
              <el-option label="入库中"
                         value="入库中" />
              <el-option label="完全入库"
                         value="完全入库" />
              <el-option label="待入库" value="待入库" />
              <el-option label="入库中" value="入库中" />
              <el-option label="完全入库" value="完全入库" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary"
                       @click="handleQuery"> æœç´¢
            <el-button type="primary" @click="handleQuery"> æœç´¢
            </el-button>
          </el-form-item>
        </el-form>
@@ -66,323 +40,154 @@
    </div>
    <div class="table_list">
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
        <el-button type="success"
                   plain
                   @click="handleBatchGenerate">批量生成数据</el-button>
        <el-button type="primary"
                   @click="openForm('add')">新增台账</el-button>
        <el-button type="primary"
                   plain
                   @click="handleImport">导入</el-button>
        <el-button type="primary" @click="openForm('add')">新增台账</el-button>
        <el-button type="primary" plain @click="handleImport">导入</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger"
                   plain
                   @click="handleDelete">删除
        <el-button type="danger" plain @click="handleDelete">删除
        </el-button>
        <el-button type="warning" plain @click="handlePrint">打印单据</el-button>
      </div>
      <el-table :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="(row) => row.id"
                show-summary
                :summary-method="summarizeMainTable"
                @expand-change="expandChange"
                height="calc(100vh - 21.5em)">
        <el-table-column align="center"
                         type="selection"
                         width="55" />
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary :summary-method="summarizeMainTable"
        @expand-change="expandChange" height="calc(100vh - 21.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column type="expand">
          <template #default="props">
            <el-table :data="props.row.children"
                      border
                      show-summary
                      :summary-method="summarizeChildrenTable">
              <el-table-column align="center"
                               label="序号"
                               type="index"
                               width="60" />
              <el-table-column label="产品大类"
                               prop="productCategory" />
              <el-table-column label="规格型号"
                               prop="specificationModel" />
              <el-table-column label="单位"
                               prop="unit" />
              <el-table-column label="入库审核状态"
                               prop="stockInApprovalStatus"
                               width="120">
            <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable">
              <el-table-column align="center" label="序号" type="index" width="60" />
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="入库审核状态" prop="stockInApprovalStatus" width="120">
                <template #default="scope">
                  <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)"
                          size="small">
                  <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)" size="small">
                    {{ scope.row.stockInApprovalStatus || '--' }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column label="数量"
                               prop="quantity" />
              <el-table-column label="销售发货数量"
                               prop="shippedQuantity" />
              <el-table-column label="可用数量"
                               prop="availableQuality" />
              <el-table-column label="退货数量"
                               prop="returnQuantity" />
              <el-table-column label="税率(%)"
                               prop="taxRate" />
              <el-table-column label="含税单价(元)"
                               prop="taxInclusiveUnitPrice"
                               :formatter="formattedNumber" />
              <el-table-column label="含税总价(元)"
                               prop="taxInclusiveTotalPrice"
                               :formatter="formattedNumber" />
              <el-table-column label="不含税总价(元)"
                               prop="taxExclusiveTotalPrice"
                               :formatter="formattedNumber" />
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="可用数量" prop="availableQuality" />
              <el-table-column label="退货数量" prop="returnQuality" />
              <el-table-column label="税率(%)" prop="taxRate" />
              <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" />
              <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" />
              <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" />
            </el-table>
          </template>
        </el-table-column>
        <el-table-column align="center"
                         label="序号"
                         type="index"
                         width="60" />
        <el-table-column label="采购合同号"
                         prop="purchaseContractNumber"
                         width="160"
                         show-overflow-tooltip />
        <el-table-column label="销售合同号"
                         prop="salesContractNo"
                         width="160"
                         show-overflow-tooltip />
        <el-table-column label="供应商名称"
                         prop="supplierName"
                         width="160"
                         show-overflow-tooltip />
        <el-table-column label="项目名称"
                         prop="projectName"
                         width="320"
                         show-overflow-tooltip />
        <el-table-column label="审批状态"
                         prop="approvalStatus"
                         width="100"
                         show-overflow-tooltip>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="采购合同号" prop="purchaseContractNumber" width="160" show-overflow-tooltip />
        <el-table-column v-if="showSalesContractBinding" label="销售合同号" prop="salesContractNo" width="160" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" width="160" show-overflow-tooltip />
        <el-table-column label="项目名称" prop="projectName" width="320" show-overflow-tooltip />
        <el-table-column label="审批状态" prop="approvalStatus" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)"
                    size="small">
            <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" size="small">
              {{ approvalStatusText[scope.row.approvalStatus] || '未知状态' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="入库状态"
                         prop="stockInStatus"
                         width="100"
                         show-overflow-tooltip>
        <el-table-column label="入库状态" prop="stockInStatus" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getStockInStatusType(scope.row.stockInStatus)"
                    size="small">
            <el-tag :type="getStockInStatusType(scope.row.stockInStatus)" size="small">
              {{ scope.row.stockInStatus || '--' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="签订日期"
                         prop="executionDate"
                         width="100"
                         show-overflow-tooltip />
        <el-table-column label="付款方式"
                         width="100"
                         prop="paymentMethod"
                         show-overflow-tooltip />
        <el-table-column label="合同金额(元)"
                         prop="contractAmount"
                         width="200"
                         show-overflow-tooltip
        <el-table-column label="签订日期" prop="executionDate" width="100" show-overflow-tooltip />
        <el-table-column label="付款方式" width="100" prop="paymentMethod" show-overflow-tooltip />
        <el-table-column label="合同金额(元)" prop="contractAmount" width="200" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="实际合同金额(元)"
                         prop="netContractAmount"
                         width="200"
                         show-overflow-tooltip
                         :formatter="(_, __, cellValue) => formatMoney(cellValue)" />
        <el-table-column label="录入人"
                         prop="recorderName"
                         width="120"
                         show-overflow-tooltip />
        <el-table-column label="录入日期"
                         prop="entryDate"
                         width="100"
                         show-overflow-tooltip />
        <el-table-column label="备注"
                         prop="remarks"
                         width="200"
                         show-overflow-tooltip />
        <el-table-column fixed="right"
                         label="操作"
                         width="120"
                         align="center">
        <el-table-column label="录入人" prop="recorderName" width="120" show-overflow-tooltip />
        <el-table-column label="录入日期" prop="entryDate" width="100" show-overflow-tooltip />
        <el-table-column label="备注" prop="remarks" width="200" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" width="220" align="center">
          <template #default="scope">
            <el-button link
                       type="primary"
                       @click="openForm('edit', scope.row)"
                       :disabled="scope.row.approvalStatus === 3">编辑
            <el-button link type="primary" @click="openForm('detail', scope.row)">详情
            </el-button>
            <el-button link
                       type="primary"
                       @click="openFileDialog(scope.row)">附件</el-button>
            <el-button link type="primary" @click="openForm('edit', scope.row)"
              :disabled="scope.row.stockInStatus === '完全入库'">编辑
            </el-button>
            <el-button link type="primary" @click="openFileDialog(scope.row)">附件</el-button> <!--downloadOrder(scope.row.id) ä¸‹è½½-->
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0"
                  :total="total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current"
                  :limit="page.size"
                  @pagination="paginationChange" />
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
    </div>
    <FormDialog v-model="dialogFormVisible"
                :title="operationType === 'add' ? '新增采购台账页面' : '编辑采购台账页面'"
                :width="'70%'"
                :operation-type="operationType"
                @close="closeDia"
                @confirm="submitForm"
                @cancel="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
      :title="operationType === 'add' ? '新增采购台账页面' : operationType === 'edit' ? '编辑采购台账页面' : '采购台账详情'" :width="'70%'"
      :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="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="purchaseContractNumber">
              <el-input v-model="form.purchaseContractNumber"
                        placeholder="请输入"
                        clearable />
            <el-form-item label="采购合同号:" prop="purchaseContractNumber">
              <el-input v-model="form.purchaseContractNumber" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="销售合同号:"
                          prop="salesLedgerId">
              <el-select v-model="form.salesLedgerId"
                         placeholder="请选择"
                         filterable
                         clearable
                         @change="salesLedgerChange">
                <el-option v-for="item in salesContractList"
                           :key="item.id"
                           :label="item.salesContractNo"
                           :value="item.id" />
          <el-col v-if="showSalesContractBinding" :span="12">
            <el-form-item label="销售合同号:" prop="salesLedgerId">
              <el-select v-model="form.salesLedgerId" placeholder="请选择" filterable clearable @change="salesLedgerChange">
                <el-option v-for="item in salesContractList" :key="item.id" :label="item.salesContractNo" :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:"
                          prop="supplierId">
              <el-select v-model="form.supplierId"
                         placeholder="请选择"
                         filterable
                         clearable>
                <el-option v-for="item in supplierList"
                           :key="item.id"
                           :label="item.supplierName"
                           :value="item.id">{{ item.supplierName + '---' + item.supplierType }}
                </el-option>
            <el-form-item label="供应商名称:" prop="supplierId">
              <el-select v-model="form.supplierId" placeholder="请选择" filterable clearable>
                <el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.id">{{ item.supplierType ? item.supplierName + '---' + item.supplierType : item.supplierName }}</el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="项目名称"
                          prop="projectName">
              <el-input v-model="form.projectName"
                        placeholder="请输入"
                        clearable />
            <el-form-item label="项目名称" prop="projectName">
              <el-input v-model="form.projectName" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="付款方式">
              <el-input v-model="form.paymentMethod"
                        placeholder="请输入"
                        clearable />
              <el-input v-model="form.paymentMethod" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="签订日期:"
                          prop="executionDate">
              <el-date-picker style="width: 100%"
                              v-model="form.executionDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="date"
                              placeholder="请选择"
                              clearable />
            <el-form-item label="签订日期:" prop="executionDate">
              <el-date-picker style="width: 100%" v-model="form.executionDate" 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="recorderId">
              <el-select v-model="form.recorderId"
                         placeholder="请选择"
                         clearable
                         filterable>
                <el-option v-for="item in userList"
                           :key="item.userId"
                           :label="item.nickName"
                           :value="item.userId" />
            <el-form-item label="录入人:" prop="recorderId">
              <el-select v-model="form.recorderId" placeholder="请选择" clearable filterable disabled>
                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入日期:"
                          prop="entryDate">
              <el-date-picker style="width: 100%"
                              v-model="form.entryDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="date"
                              placeholder="请选择"
                              clearable />
            <el-form-item label="录入日期:" prop="entryDate">
              <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-form-item label="产品信息:"
                        prop="entryDate">
            <el-button type="primary"
                       @click="openProductForm('add')">添加
            </el-button>
            <el-button plain
                       type="danger"
                       @click="deleteProduct">删除
            </el-button>
          <el-form-item label="产品信息:" prop="entryDate">
            <el-button type="primary" @click="openProductForm('add')">添加</el-button>
            <el-button plain type="danger" @click="deleteProduct">删除</el-button>
          </el-form-item>
          <div class="select-button-group"
               style="width: 500px; margin: 20px 0;"
               v-if="operationType === 'add'">
            <el-select filterable
                       allow-create
                       :reserve-keyword="true"
                       :default-first-option="false"
                       clearable
                       v-model="templateName"
                       :input-value="filterInputValue"
                       @filter-change="onTemplateFilterChange"
                       @change="onTemplateChange"
                       @focus="getTemplateList"
                       style="width: 500px;"
                       placeholder="请选择模版或者输入新的模版名称后选择"
          <div class="select-button-group" style="width: 500px; margin: 20px 0;" v-if="operationType === 'add'">
            <el-select filterable allow-create :reserve-keyword="true" :default-first-option="false" clearable
              v-model="templateName" :input-value="filterInputValue" @filter-change="onTemplateFilterChange"
              @change="onTemplateChange" @focus="getTemplateList" style="width: 500px;" placeholder="请选择模版或者输入新的模版名称后选择"
                       class="no-arrow-select">
              <el-option v-for="item in templateList"
                         :key="item.id || item.value"
                         :label="item.templateName"
              <el-option v-for="item in templateList" :key="item.id || item.value" :label="item.templateName"
                         :value="item.templateName">
                <div style="display: flex; justify-content: space-between; align-items: center;">
                  <span>{{ item.templateName }}</span>
                  <el-icon v-if="item.id"
                           class="delete-icon"
                           @click.stop="handleDeleteTemplate(item)"
                  <el-icon v-if="item.id" class="delete-icon" @click.stop="handleDeleteTemplate(item)"
                           style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;">
                    <Delete />
                  </el-icon>
@@ -390,81 +195,42 @@
              </el-option>
            </el-select>
            <!-- æŒ‰é’®ï¼šä¸Ž Select é«˜åº¦åŒ¹é…ï¼ŒåŽ»æŽ‰å·¦ä¾§è¾¹æ¡†ï¼Œæ— ç¼è¡”æŽ¥ -->
            <el-button size="small"
                       style="height: 32px;margin-left: 8px;"
                       @click="handleButtonClick"
            <el-button size="small" style="height: 32px;margin-left: 8px;" @click="handleButtonClick"
                       :disabled="!templateName || templateName.trim() === '' || (!currentTemplateId && isTemplateNameDuplicate)">
              ä¿å­˜
            </el-button>
          </div>
        </el-row>
        <el-table :data="productData"
                  border
                  @selection-change="productSelected"
                  show-summary
        <el-table :data="productData" border @selection-change="productSelected" show-summary
                  :summary-method="summarizeProTable">
          <el-table-column align="center"
                           type="selection"
                           width="55" />
          <el-table-column align="center"
                           label="序号"
                           type="index"
                           width="60" />
          <el-table-column label="产品大类"
                           prop="productCategory" />
          <el-table-column label="规格型号"
                           prop="specificationModel" />
          <el-table-column label="单位"
                           prop="unit"
                           width="70" />
          <el-table-column label="数量"
                           prop="quantity"
                           width="70" />
          <el-table-column label="库存预警数量"
                           prop="warnNum"
                           width="120"
                           show-overflow-tooltip />
          <el-table-column label="税率(%)"
                           prop="taxRate"
                           width="80" />
          <el-table-column label="含税单价(元)"
                           prop="taxInclusiveUnitPrice"
                           :formatter="formattedNumber"
                           width="150" />
          <el-table-column label="含税总价(元)"
                           prop="taxInclusiveTotalPrice"
                           :formatter="formattedNumber"
                           width="150" />
          <el-table-column label="不含税总价(元)"
                           prop="taxExclusiveTotalPrice"
                           :formatter="formattedNumber"
                           width="150" />
          <el-table-column label="是否质检"
                           prop="isChecked"
                           width="150">
          <el-table-column align="center" type="selection" width="55" />
          <el-table-column align="center" label="序号" type="index" width="60" />
          <el-table-column label="产品大类" prop="productCategory" />
          <el-table-column label="规格型号" prop="specificationModel" />
          <el-table-column label="单位" prop="unit" width="70" />
          <el-table-column label="数量" prop="quantity" width="70" />
<!--          <el-table-column label="库存预警数量" prop="warnNum" width="120" show-overflow-tooltip />-->
          <el-table-column label="税率(%)" prop="taxRate" width="80" />
          <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" width="150" />
          <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" width="150" />
          <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" width="150" />
          <el-table-column label="是否质检" prop="isChecked" width="150">
            <template #default="scope">
              <el-tag :type="scope.row.isChecked ? 'success' : 'info'">
                {{ scope.row.isChecked ? '是' : '否' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column label="入库审核状态"
                           prop="stockInApprovalStatus"
                           width="120">
          <el-table-column label="入库审核状态" prop="stockInApprovalStatus" width="120">
            <template #default="scope">
              <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)"
                      size="small">
              <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)" size="small">
                {{ scope.row.stockInApprovalStatus || '--' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column fixed="right"
                           label="操作"
                           min-width="60"
                           align="center">
          <el-table-column fixed="right" label="操作" min-width="60" align="center">
            <template #default="scope">
              <el-button link
                         type="primary"
              <el-button link type="primary"
                         @click="openProductForm('edit', scope.row, scope.$index)"
                         :disabled="scope.row.stockInApprovalStatus === '完全入库'">编辑
              </el-button>
@@ -473,20 +239,14 @@
        </el-table>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注·:"
                          prop="remarks">
              <el-input v-model="form.remarks"
                        placeholder="请输入"
                        clearable
                        type="textarea"
                        :rows="2" />
            <el-form-item label="备注·:" prop="remarks">
              <el-input v-model="form.remarks" placeholder="请输入" clearable type="textarea" :rows="2" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:"
                          prop="purchaseLedgerFiles">
            <el-form-item label="附件材料:" prop="purchaseLedgerFiles">
              <FileUpload v-model:file-list="fileList" />
            </el-form-item>
          </el-col>
@@ -494,24 +254,12 @@
      </el-form>
    </FormDialog>
    <!-- å¯¼å…¥å¼¹çª— -->
    <FormDialog v-model="importUpload.open"
                :title="importUpload.title"
                :width="'600px'"
                @close="importUpload.open = false"
                @confirm="submitImportFile"
                @cancel="importUpload.open = false">
      <el-upload ref="importUploadRef"
                 :limit="1"
                 accept=".xlsx,.xls"
                 :action="importUpload.url"
                 :headers="importUpload.headers"
                 :before-upload="importUpload.beforeUpload"
                 :on-success="importUpload.onSuccess"
                 :on-error="importUpload.onError"
                 :on-progress="importUpload.onProgress"
                 :on-change="importUpload.onChange"
                 :auto-upload="false"
                 drag>
    <FormDialog v-model="importUpload.open" :title="importUpload.title" :width="'600px'"
      @close="importUpload.open = false" @confirm="submitImportFile" @cancel="importUpload.open = false">
      <el-upload ref="importUploadRef" :limit="1" accept=".xlsx,.xls" :action="importUpload.url"
        :headers="importUpload.headers" :before-upload="importUpload.beforeUpload" :on-success="importUpload.onSuccess"
        :on-error="importUpload.onError" :on-progress="importUpload.onProgress" :on-change="importUpload.onChange"
        :auto-upload="false" drag>
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">
          å°†æ–‡ä»¶æ‹–到此处,或<em>点击上传</em>
@@ -519,186 +267,126 @@
        <template #tip>
          <div class="el-upload__tip">
            ä»…支持 xls/xlsx,大小不超过 10MB。
            <el-button link
                       type="primary"
                       @click="downloadTemplate">下载导入模板</el-button>
            <el-button link type="primary" @click="downloadTemplate">下载导入模板</el-button>
          </div>
        </template>
      </el-upload>
    </FormDialog>
    <FormDialog v-model="productFormVisible"
                :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
                :width="'40%'"
                :operation-type="productOperationType"
                @close="closeProductDia"
                @confirm="submitProduct"
    <FormDialog v-model="productFormVisible" :title="productOperationType === 'add' ? '新增产品' : '编辑产品'" :width="'40%'"
      :operation-type="productOperationType" @close="closeProductDia" @confirm="submitProduct"
                @cancel="closeProductDia">
      <el-form :model="productForm"
               label-width="140px"
               label-position="top"
               :rules="productRules"
               ref="productFormRef">
      <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品大类:"
                          prop="productId">
              <el-tree-select v-model="productForm.productId"
                              placeholder="请选择"
                              clearable
                              filterable
                              check-strictly
                              @change="getModels"
                              :data="productOptions"
                              :render-after-expand="false"
                              style="width: 100%" />
          <el-col :span="12">
            <el-form-item label="产品大类:" prop="productCategory">
              <el-input v-model="productForm.productCategory"
                        placeholder="点击选择"
                        readonly
                        @click="showProductSelectDialog = true"
                        style="cursor:pointer" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="productModelId">
              <el-input v-model="productForm.specificationModel"
                        placeholder="点击选择"
                        readonly
                        @click="showProductSelectDialog = true"
                        style="cursor:pointer" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="规格型号:"
                          prop="productModelId">
              <el-select v-model="productForm.productModelId"
                         placeholder="请选择"
                         filterable
                         clearable
                         @change="getProductModel">
                <el-option v-for="item in modelOptions"
                           :key="item.id"
                           :label="item.model"
                           :value="item.id" />
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="productForm.unit" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="税率(%):" prop="taxRate">
              <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="mathNum">
                <el-option v-for="dict in tax_rate" :key="dict.value" :label="dict.label" :value="dict.value" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:"
                          prop="unit">
              <el-input v-model="productForm.unit"
                        placeholder="请输入"
                        clearable />
            <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
              <el-input-number v-model="productForm.taxInclusiveUnitPrice" :precision="6" :step="0.000001" :min="0" clearable
                style="width: 100%" @change="mathNum" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="税率(%):"
                          prop="taxRate">
              <el-select v-model="productForm.taxRate"
                         placeholder="请选择"
                         clearable
                         @change="mathNum">
                <el-option v-for="dict in tax_rate"
                           :key="dict.value"
                           :label="dict.label"
                           :value="dict.value" />
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.1" clearable :precision="2" :min="0" style="width: 100%"
                v-model="productForm.quantity" placeholder="请输入" @change="mathNum" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice">
              <el-input-number v-model="productForm.taxInclusiveTotalPrice" :precision="6" :step="0.000001" :min="0"
                clearable style="width: 100%" @change="reverseMathNum('taxInclusiveTotalPrice')" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="不含税总价(元):" prop="taxExclusiveTotalPrice">
              <el-input-number v-model="productForm.taxExclusiveTotalPrice" :precision="6" :step="0.000001" :min="0"
                clearable style="width: 100%" @change="reverseMathNum('taxExclusiveTotalPrice')" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票类型:" prop="invoiceType">
              <el-select v-model="productForm.invoiceType" placeholder="请选择" clearable>
                <el-option label="增普票" value="增普票" />
                <el-option label="增专票" value="增专票" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
<!--          <el-col :span="12">-->
<!--            <el-form-item label="库存预警数量:" prop="warnNum">-->
<!--              <el-input-number v-model="productForm.warnNum" :precision="2" :step="0.1" :min="0" clearable-->
<!--                style="width: 100%" />-->
<!--            </el-form-item>-->
<!--          </el-col>-->
          <el-col :span="12">
            <el-form-item label="含税单价(元):"
                          prop="taxInclusiveUnitPrice">
              <el-input-number v-model="productForm.taxInclusiveUnitPrice"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%"
                               @change="mathNum" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:"
                          prop="quantity">
              <el-input-number :step="0.1"
                               clearable
                               :precision="2"
                               :min="0"
                               style="width: 100%"
                               v-model="productForm.quantity"
                               placeholder="请输入"
                               @change="mathNum" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税总价(元):"
                          prop="taxInclusiveTotalPrice">
              <el-input-number v-model="productForm.taxInclusiveTotalPrice"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%"
                               @change="reverseMathNum('taxInclusiveTotalPrice')" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="不含税总价(元):"
                          prop="taxExclusiveTotalPrice">
              <el-input-number v-model="productForm.taxExclusiveTotalPrice"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%"
                               @change="reverseMathNum('taxExclusiveTotalPrice')" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票类型:"
                          prop="invoiceType">
              <el-select v-model="productForm.invoiceType"
                         placeholder="请选择"
                         clearable>
                <el-option label="增普票"
                           value="增普票" />
                <el-option label="增专票"
                           value="增专票" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="库存预警数量:"
                          prop="warnNum">
              <el-input-number v-model="productForm.warnNum"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="是否质检:"
                          prop="isChecked">
            <el-form-item label="是否质检:" prop="isChecked">
              <el-radio-group v-model="productForm.isChecked">
                <el-radio label="是"
                          :value="true" />
                <el-radio label="否"
                          :value="false" />
                <el-radio label="是" :value="true" />
                <el-radio label="否" :value="false" />
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
<!--        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="是否质检:" prop="isChecked">
              <el-radio-group v-model="productForm.isChecked">
                <el-radio label="是" :value="true" />
                <el-radio label="否" :value="false" />
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>-->
      </el-form>
    </FormDialog>
    <FileList v-if="fileListDialogVisible"
              v-model:visible="fileListDialogVisible"
              record-type="purchase_ledger"
    <ProductSelectDialog v-model="showProductSelectDialog"
                         @confirm="handleProductSelect"
                         single />
    <FileList v-if="fileListDialogVisible" v-model:visible="fileListDialogVisible" record-type="purchase_ledger"
              :record-id="recordId" />
    <Detail ref="detailRef" />
  </div>
</template>
<script setup>
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import Detail from "./detail.vue";
  import { getToken } from "@/utils/auth";
  import pagination from "@/components/PIMTable/Pagination.vue";
  import {
@@ -729,14 +417,16 @@
    productList,
    getPurchaseById,
    getOptions,
  getPrintData,
    getPurchaseTemplateList,
    delPurchaseTemplate,
    batchGeneratePurchaseInboundSteps,
  } from "@/api/procurementManagement/procurementLedger.js";
  import useFormData from "@/hooks/useFormData.js";
  const FileList = defineAsyncComponent(() =>
    import("@/components/Dialog/FileList.vue")
  );
const detailRef = ref(null);
  const { proxy } = getCurrentInstance();
  const { tax_rate } = proxy.useDict("tax_rate");
@@ -760,10 +450,16 @@
  const fileList = ref([]);
  import useUserStore from "@/store/modules/user";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import dayjs from "dayjs";
  import FileUpload from "@/components/AttachmentUpload/file/index.vue";
import { tableAmountFormatter, formatDecimal, buildAmountSummaryFormat } from '@/utils/numberFormat';
import { downloadPurchaseJpg, printPurchaseMulti } from "@/utils/documentPrint.js";
  const userStore = useUserStore();
// æ˜¯å¦æ˜¾ç¤ºé”€å”®åˆåŒå·ç»‘定
const showSalesContractBinding = false;
  // è®¢å•审批状态显示文本
  const approvalStatusText = {
@@ -787,9 +483,9 @@
  // èŽ·å–å…¥åº“çŠ¶æ€æ ‡ç­¾ç±»åž‹
  const getStockInStatusType = status => {
    const typeMap = {
      å¾…入库: "info", // å¾…入库 - ç°è‰²
      å…¥åº“中: "warning", // å…¥åº“中 - æ©™è‰²
      å®Œå…¨å…¥åº“: "success", // å®Œå…¨å…¥åº“ - ç»¿è‰²
    "待入库": "info", // å¾…入库 - ç°è‰²
    "入库中": "warning", // å…¥åº“中 - æ©™è‰²
    "完全入库": "success", // å®Œå…¨å…¥åº“ - ç»¿è‰²
    };
    return typeMap[status] || "";
  };
@@ -797,9 +493,9 @@
  // èŽ·å–å…¥åº“å®¡æ ¸çŠ¶æ€æ ‡ç­¾ç±»åž‹
  const getStockInApprovalStatusType = status => {
    const typeMap = {
      å¾…入库: "info", // å¾…入库 - ç°è‰²
      å…¥åº“中: "warning", // å…¥åº“中 - æ©™è‰²
      å®Œå…¨å…¥åº“: "success", // å®Œå…¨å…¥åº“ - ç»¿è‰²
    "待入库": "info", // å¾…入库 - ç°è‰²
    "入库中": "warning", // å…¥åº“中 - æ©™è‰²
    "完全入库": "success", // å®Œå…¨å…¥åº“ - ç»¿è‰²
    };
    return typeMap[status] || "";
  };
@@ -822,6 +518,13 @@
    );
    isTemplateNameDuplicate.value = isDuplicate;
    return isDuplicate;
};
// åˆ¤æ–­æ–‡ä»¶æ˜¯å¦ä¸ºå›¾ç‰‡
const isImage = (filename) => {
  if (!filename) return false;
  const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
  return ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'].includes(ext);
  };
  // é˜²æŠ–定时器
@@ -934,7 +637,7 @@
    },
    rules: {
      purchaseContractNumber: [
        { required: false, message: "请输入", trigger: "blur" },
      { required: true, message: "请输入", trigger: "blur" },
      ],
      projectName: [
        { required: true, message: "请输入项目名称", trigger: "blur" },
@@ -947,17 +650,11 @@
  const { form, rules } = toRefs(data);
  const { form: searchForm } = useFormData({
    ...data.searchForm,
    // è®¾ç½®å½•入日期范围为当天
    entryDate: [
      dayjs().startOf("day").format("YYYY-MM-DD"),
      dayjs().endOf("day").format("YYYY-MM-DD"),
    ],
    entryDateStart: dayjs().startOf("day").format("YYYY-MM-DD"),
    entryDateEnd: dayjs().endOf("day").format("YYYY-MM-DD"),
  });
  // äº§å“è¡¨å•弹框数据
  const productFormVisible = ref(false);
const showProductSelectDialog = ref(false);
  const productOperationType = ref("");
  const productOperationIndex = ref("");
  const currentId = ref("");
@@ -978,15 +675,15 @@
      isChecked: false,
    },
    productRules: {
      productId: [{ required: true, message: "请选择", trigger: "change" }],
      productModelId: [{ required: true, message: "请选择", trigger: "change" }],
    productCategory: [{ required: true, message: "请选择产品大类", trigger: "change" }],
    productModelId: [{ required: true, message: "请选择规格型号", trigger: "change" }],
      unit: [{ required: true, message: "请输入", trigger: "blur" }],
      quantity: [{ required: true, message: "请输入", trigger: "blur" }],
      taxInclusiveUnitPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      taxRate: [{ required: true, message: "请选择", trigger: "change" }],
      warnNum: [{ required: true, message: "请选择", trigger: "change" }],
    // warnNum: [{ required: true, message: "请选择", trigger: "change" }],
      taxInclusiveTotalPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
@@ -1085,9 +782,7 @@
    handleQuery();
  };
  const formattedNumber = (row, column, cellValue) => {
    return parseFloat(cellValue).toFixed(2);
  };
const formattedNumber = tableAmountFormatter;
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
@@ -1205,10 +900,19 @@
        "futureTickets",
        "futureTicketsAmount",
      ],
    buildAmountSummaryFormat(
      [
        "taxInclusiveUnitPrice",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
        "ticketsAmount",
        "futureTicketsAmount",
      ],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        ticketsNum: { noDecimal: true },
        futureTickets: { noDecimal: true },
      }
    )
    );
  };
  const paginationChange = obj => {
@@ -1271,28 +975,37 @@
  };
  // ä¸»è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["contractAmount", "netContractAmount"]);
  };
  const formatMoney = value => {
    if (value === null || value === undefined || value === "") {
      return "--";
    }
    const num = Number(value);
    return Number.isFinite(num) ? num.toFixed(2) : "--";
  return proxy.summarizeTable(
    param,
    ["contractAmount"],
    buildAmountSummaryFormat(["contractAmount"])
  );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeProTable = param => {
    return proxy.summarizeTable(param, [
  return proxy.summarizeTable(
    param,
    [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ]);
    ],
    buildAmountSummaryFormat([
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ])
  );
  };
  // æ‰“开弹框
  const openForm = async (type, row) => {
  if (type === 'detail') {
    detailRef.value.open(row.id);
    return;
  }
    // ç¼–辑时检查入库状态,完全入库时不能编辑
    if (type === "edit" && row) {
      if (row.stockInStatus === "完全入库") {
    if (row.stockInStatus === '完全入库') {
        proxy.$modal.msgWarning("完全入库状态的记录不能编辑");
        return;
      }
@@ -1323,8 +1036,20 @@
      );
      form.value.entryDate = getCurrentDate();
    form.value.recorderId = userStore.id;
      if (type === "edit" && row?.id) {
    if (type === "add") {
      // æ–°å¢žæ—¶ç”Ÿæˆé‡‡è´­åˆåŒå·
      try {
        const purchaseNoRes = await createPurchaseNo();
        if (purchaseNoRes?.data) {
          form.value.purchaseContractNumber = purchaseNoRes.data;
        }
      } catch (error) {
        console.error("生成采购合同号失败:", error);
        proxy.$modal.msgWarning("生成采购合同号失败");
      }
    } else if (type === "edit" && row?.id) {
        // ç¼–辑时加载数据
        currentId.value = row.id;
        try {
@@ -1332,10 +1057,7 @@
          form.value = { ...purchaseRes, stockInStatus: row.stockInStatus };
          fileList.value = purchaseRes.storageBlobVOS || [];
          // ä½¿ç”¨ productList æŽ¥å£èŽ·å–äº§å“åˆ—è¡¨ï¼Œä»¥èŽ·å–å…¥åº“å®¡æ ¸çŠ¶æ€
          const productRes = await productList({
            salesLedgerId: row.id,
            type: 2,
          });
        const productRes = await productList({ salesLedgerId: row.id, type: 2 });
          productData.value = productRes.data || [];
        } catch (error) {
          console.error("加载采购台账数据失败:", error);
@@ -1347,7 +1069,6 @@
      if (form.value.salesLedgerId == -1) {
        form.value.salesLedgerId = null;
      }
      console.log(form.value, "form.value===========");
      dialogFormVisible.value = true;
    } catch (error) {
      console.error("打开表单失败:", error);
@@ -1408,7 +1129,7 @@
  // æäº¤è¡¨å•
  const submitForm = () => {
    proxy.$refs["formRef"].validate(async valid => {
  proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        if (productData.value.length > 0) {
          // æ–°å¢žæ—¶ï¼Œéœ€è¦ä»Žæ¯ä¸ªäº§å“å¯¹è±¡ä¸­åˆ é™¤ id å­—段
@@ -1438,20 +1159,6 @@
          delete submitData.id;
        }
        // å¦‚果采购合同号为空,则根据录入日期自动生成
        if (!submitData.purchaseContractNumber) {
          try {
            const purchaseNoRes = await createPurchaseNo(submitData.entryDate);
            if (purchaseNoRes?.data) {
              submitData.purchaseContractNumber = purchaseNoRes.data;
            }
          } catch (error) {
            console.error("生成采购合同号失败:", error);
            proxy.$modal.msgWarning("生成采购合同号失败");
            return;
          }
        }
        addOrEditPurchase(submitData).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
@@ -1468,7 +1175,7 @@
  // æ‰“开产品弹框
  const openProductForm = async (type, row, index) => {
    // ç¼–辑时检查产品入库审核状态,完全入库时不能编辑
    if (type === "edit" && row && row.stockInApprovalStatus === "完全入库") {
  if (type === "edit" && row && row.stockInApprovalStatus === '完全入库') {
      proxy.$modal.msgWarning("完全入库状态的产品不能编辑");
      return;
    }
@@ -1557,6 +1264,19 @@
      await nextTick();
    }
  };
// ProductSelectDialog é€‰ä¸­å›žè°ƒ
const handleProductSelect = (products) => {
  if (!products || !products.length) return;
  const p = products[0];
  productForm.value.productId = p.productId;
  productForm.value.productCategory = p.productName || '';
  productForm.value.productModelId = p.id;
  productForm.value.specificationModel = p.model || '';
  productForm.value.unit = p.unit || '';
  showProductSelectDialog.value = false;
};
  const getProductOptions = () => {
    return productTreeList().then(res => {
      productOptions.value = convertIdToValue(res);
@@ -1651,17 +1371,13 @@
  };
  // åˆ é™¤äº§å“
  const deleteProduct = () => {
    if (productData.value.length <= 1) {
      proxy.$modal.msgWarning("采购台账至少需要保留一条产品数据");
      return;
    }
    if (productSelectedRows.value.length === 0) {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    // æ£€æŸ¥é€‰ä¸­çš„产品中是否有完全入库的
    const hasFullyStocked = productSelectedRows.value.some(
      row => row.stockInApprovalStatus === "完全入库"
    row => row.stockInApprovalStatus === '完全入库'
    );
    if (hasFullyStocked) {
      proxy.$modal.msgWarning("选中的产品中包含完全入库的产品,无法删除");
@@ -1743,35 +1459,29 @@
      });
  };
  const handleBatchGenerate = async () => {
// æ‰¹é‡æ‰“印采购单 - å¤šé€‰åˆå¹¶é¢„览
const handlePrint = async () => {
    if (selectedRows.value.length === 0) {
      proxy.$modal.msgWarning("请选择数据");
    proxy.$modal.msgWarning("请选择要打印的数据");
      return;
    }
    const ids = selectedRows.value.map((item) => item.id);
    ElMessageBox.confirm("确认批量生成数据?", "批量生成", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "info",
    })
      .then(() => {
        proxy.$modal.loading("正在批量生成数据,请稍候...");
        batchGeneratePurchaseInboundSteps({ ids })
          .then((res) => {
            proxy.$modal.msgSuccess("批量生成成功");
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError("批量生成失败");
          })
          .finally(() => {
  proxy.$modal.loading("正在加载打印数据...");
  try {
    const orders = [];
    let companyName = '';
    for (const row of selectedRows.value) {
      const res = await getPrintData(row.id);
      const data = res.data || res;
      companyName = data.companyName || companyName;
      orders.push({ ledger: data.ledger, products: data.products });
    }
    printPurchaseMulti(companyName, orders);
  } catch (e) {
    console.error('打印失败:', e);
    proxy.$modal.msgError("打印失败");
  } finally {
            proxy.$modal.closeLoading();
          });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  }
  };
  // èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
@@ -1833,10 +1543,10 @@
    if (field === "taxInclusiveTotalPrice") {
      // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œæ•°é‡ï¼Œåç®—含税单价
      if (productForm.value.quantity) {
        productForm.value.taxInclusiveUnitPrice = (
      productForm.value.taxInclusiveUnitPrice = formatDecimal(
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
      );
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
@@ -1854,30 +1564,30 @@
        }
      }
      // åç®—不含税总价
      productForm.value.taxExclusiveTotalPrice = (
    productForm.value.taxExclusiveTotalPrice = formatDecimal(
        Number(productForm.value.taxInclusiveTotalPrice) /
        (1 + taxRate / 100)
      ).toFixed(2);
    );
      // ç¡®ä¿ç»“果不为负数
      if (Number(productForm.value.taxExclusiveTotalPrice) < 0) {
        productForm.value.taxExclusiveTotalPrice = "0";
      }
    } else if (field === "taxExclusiveTotalPrice") {
      // åç®—含税总价
      productForm.value.taxInclusiveTotalPrice = (
    productForm.value.taxInclusiveTotalPrice = formatDecimal(
        Number(productForm.value.taxExclusiveTotalPrice) *
        (1 + taxRate / 100)
      ).toFixed(2);
    );
      // ç¡®ä¿ç»“果不为负数
      if (Number(productForm.value.taxInclusiveTotalPrice) < 0) {
        productForm.value.taxInclusiveTotalPrice = "0";
      }
      // å·²çŸ¥æ•°é‡ï¼Œåç®—含税单价
      if (productForm.value.quantity) {
        productForm.value.taxInclusiveUnitPrice = (
      productForm.value.taxInclusiveUnitPrice = formatDecimal(
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
      );
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
@@ -1898,9 +1608,7 @@
  };
  // é”€å”®åˆåŒé€‰æ‹©æ”¹å˜æ–¹æ³•
  const salesLedgerChange = async row => {
    console.log("row", row);
    var index = salesContractList.value.findIndex(item => item.id == row);
    console.log("index", index);
    if (index > -1) {
      await querygProductInfoByContractNo();
    }
@@ -1911,7 +1619,7 @@
      contractNo: form.value.salesLedgerId,
    });
    if (code == 200) {
      productData.value = data || [];
    productData.value = data;
    }
  };
@@ -1927,6 +1635,28 @@
  const openFileDialog = async row => {
    recordId.value = row.id;
    fileListDialogVisible.value = true;
};
// ä¸‹è½½é‡‡è´­å•
const downloadOrder = async (id) => {
  try {
    const res = await getPrintData(id);
    const data = res.data || res;
    downloadPurchaseJpg(data.companyName, data.ledger, data.products, '采购单_' + (data.ledger.purchaseContractNumber || id) + '.jpg');
  } catch (e) {
    console.error('下载失败:', e);
  }
};
// æ‰“印采购单
const printOrder = async (id) => {
  try {
    const res = await getPrintData(id);
    const data = res.data || res;
    printPurchase(data.companyName, data.ledger, data.products);
  } catch (e) {
    console.error('打印失败:', e);
  }
  };
  // åˆ é™¤æ¨¡æ¿
@@ -1995,7 +1725,53 @@
  }
  .no-arrow-select {
    --el-select-suffix-icon-color: transparent; /* éšè—é»˜è®¤ä¸‹æ‹‰ç®­å¤´ */
  --el-select-suffix-icon-color: transparent;
  /* éšè—é»˜è®¤ä¸‹æ‹‰ç®­å¤´ */
}
.readonly-text {
  display: inline-block;
  width: 100%;
  padding: 0 15px;
  height: 32px;
  line-height: 32px;
  background-color: var(--el-fill-color-light);
  border-radius: var(--el-border-radius-base);
  color: var(--el-text-color-regular);
  font-size: var(--el-font-size-base);
  box-sizing: border-box;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.readonly-text-area {
  width: 100%;
  min-height: 52px;
  padding: 5px 15px;
  background-color: var(--el-fill-color-light);
  border-radius: var(--el-border-radius-base);
  color: var(--el-text-color-regular);
  font-size: var(--el-font-size-base);
  box-sizing: border-box;
  white-space: pre-wrap;
  word-break: break-all;
}
.detail-file-list {
  width: 100%;
  padding: 10px 15px;
  background-color: var(--el-fill-color-light);
  border-radius: var(--el-border-radius-base);
}
.detail-file-item {
  line-height: 24px;
  margin-bottom: 5px;
}
.detail-file-item:last-child {
  margin-bottom: 0;
  }
  .select-button-group {
src/views/procurementManagement/procurementPlan/index.vue
@@ -1,205 +1,158 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card"
             shadow="never">
      <el-form :model="searchForm"
               :inline="true"
               class="search-form">
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true" class="search-form">
        <el-form-item label="计划名称">
          <el-input v-model="searchForm.planName"
                    placeholder="请输入计划名称"
                    clearable />
          <el-input v-model="searchForm.planName" placeholder="请输入计划名称" clearable />
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="searchForm.status"
                     placeholder="请选择状态"
                     clearable
                     style="width: 150px">
            <el-option label="启用"
                       value="active" />
            <el-option label="禁用"
                       value="disabled" />
          <el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 150px">
            <el-option label="启用" value="active" />
            <el-option label="禁用" value="disabled" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleSearch">
            <el-icon>
              <Search />
            </el-icon>
          <el-button type="primary" @click="handleSearch">
            <el-icon><Search /></el-icon>
            æœç´¢
          </el-button>
          <el-button @click="handleReset">
            <el-icon>
              <Refresh />
            </el-icon>
            <el-icon><Refresh /></el-icon>
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- æ“ä½œæŒ‰é’® -->
    <el-card class="table-card"
             shadow="never">
    <el-card class="table-card" shadow="never">
      <div class="table-header">
        <div class="table-title">采购计划列表</div>
        <div class="table-actions">
          <el-button type="primary"
                     @click="handleAdd">
            <el-icon>
              <Plus />
            </el-icon>
          <el-button type="primary" @click="handleAdd">
            <el-icon><Plus /></el-icon>
            æ–°å¢žè®¡åˆ’
          </el-button>
          <el-button type="info"
                     @click="handleExport">
            <el-icon>
              <Download />
            </el-icon>
          <el-button type="info" @click="handleExport">
            <el-icon><Download /></el-icon>
            å¯¼å‡º
          </el-button>
        </div>
      </div>
      <!-- æ•°æ®è¡¨æ ¼ -->
      <el-table v-loading="loading"
      <el-table
        v-loading="loading"
                :data="tableData"
                stripe
                border
                style="width: 100%">
        <el-table-column prop="planName"
                         label="计划名称"
                         min-width="150" />
        <el-table-column prop="description"
                         label="描述"
                         min-width="200"
                         show-overflow-tooltip />
        <el-table-column prop="formula"
                         label="计算公式"
                         min-width="200"
                         show-overflow-tooltip>
        style="width: 100%"
      >
        <el-table-column prop="planName" label="计划名称" min-width="150" />
        <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
        <el-table-column prop="formula" label="计算公式" min-width="200" show-overflow-tooltip>
          <template #default="{ row }">
            <el-tag type="info"
                    size="small">{{ row.formula }}</el-tag>
            <el-tag type="info" size="small">{{ row.formula }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="status"
                         label="状态"
                         width="80"
                         align="center">
        <el-table-column prop="status" label="状态" width="80" align="center">
          <template #default="{ row }">
            <el-tag :type="row.status === 'active' ? 'success' : 'info'"
                    size="small">
            <el-tag :type="row.status === 'active' ? 'success' : 'info'" size="small">
              {{ row.status === 'active' ? '启用' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="updateTime"
                         label="最后计算时间"
                         width="160" />
        <el-table-column label="操作"
                         width="200"
                         fixed="right"
                         align="center">
        <el-table-column prop="updateTime" label="最后计算时间" width="160" />
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="{ row }">
            <el-button type="primary"
                       link
                       @click="handleEdit(row)">编辑</el-button>
            <el-button type="success"
                       link
                       @click="handleCalculate(row)">计算</el-button>
            <el-button type="danger"
                       link
                       @click="handleDelete(row)">删除</el-button>
            <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
            <el-button type="success" link @click="handleCalculate(row)">计算</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <div class="pagination-container">
        <el-pagination v-model:current-page="pagination.current"
        <el-pagination
          v-model:current-page="pagination.current"
                       v-model:page-size="pagination.size"
                       :page-sizes="[10, 20, 50, 100]"
                       :total="total"
                       layout="total, sizes, prev, pager, next, jumper"
                       @size-change="handleSizeChange"
                       @current-change="handleCurrentChange" />
          @current-change="handleCurrentChange"
        />
      </div>
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <FormDialog v-model="dialogVisible"
    <FormDialog
      v-model="dialogVisible"
                :title="dialogType === 'add' ? '新增采购计划' : '编辑采购计划'"
                :width="'1000px'"
                :operation-type="dialogType"
                :close-on-click-modal="false"
                @close="dialogVisible = false"
                @confirm="handleSubmit"
                @cancel="dialogVisible = false">
      @cancel="dialogVisible = false"
    >
      <div class="form-container">
        <!-- åŸºæœ¬ä¿¡æ¯ -->
        <div class="form-section">
          <div class="section-title">基本信息</div>
          <el-form ref="formRef"
          <el-form
            ref="formRef"
                   :model="formData"
                   :rules="formRules"
                   label-width="120px">
            label-width="120px"
          >
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item label="编码"
                              prop="code">
                  <el-input v-model="formData.code"
                            placeholder="保存后自动生成"
                            disabled />
                <el-form-item label="编码" prop="code">
                  <el-input v-model="formData.code" placeholder="系统自动生成" disabled />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="名称"
                              prop="planName"
                              required>
                  <el-input v-model="formData.planName"
                            placeholder="请输入计划名称" />
                <el-form-item label="名称" prop="planName" required>
                  <el-input v-model="formData.planName" placeholder="请输入计划名称" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-form-item label="描述"
                          prop="description">
              <el-input v-model="formData.description"
            <el-form-item label="描述" prop="description">
              <el-input
                v-model="formData.description"
                        type="textarea"
                        :rows="3"
                        placeholder="请输入计划描述" />
                placeholder="请输入计划描述"
              />
            </el-form-item>
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item label="状态"
                              prop="status">
                  <el-select v-model="formData.status"
                             placeholder="请选择状态"
                             style="width: 100%">
                    <el-option label="启用"
                               value="active" />
                    <el-option label="禁用"
                               value="disabled" />
                <el-form-item label="状态" prop="status">
                  <el-select v-model="formData.status" placeholder="请选择状态" style="width: 100%">
                    <el-option label="启用" value="active" />
                    <el-option label="禁用" value="disabled" />
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="创建时间" prop="createTime">
                  <el-date-picker v-model="formCreateTimeDate"
                                  type="date"
                                  placeholder="选择日期"
                                  value-format="YYYY-MM-DD"
                                  style="width: 100%" />
                <el-form-item label="是否系统预置">
                  <el-checkbox v-model="formData.isSystemPreset">是</el-checkbox>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
        </div>
        <!-- è®¡ç®—参数 -->
        <div class="form-section">
          <div class="section-title">计算参数</div>
          <el-tabs v-model="activeTab"
                   class="param-tabs">
            <el-tab-pane label="需求参数"
                         name="demand">
          <el-tabs v-model="activeTab" class="param-tabs">
            <el-tab-pane label="需求参数" name="demand">
              <div class="checkbox-group">
                <el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
                <el-checkbox v-model="formData.warehouseControl">仓库运行MRP的控制</el-checkbox>
@@ -210,8 +163,7 @@
                <el-checkbox v-model="formData.negativeStockAsDemand">负库存作为需求</el-checkbox>
              </div>
            </el-tab-pane>
            <el-tab-pane label="计算参数"
                         name="calculation">
            <el-tab-pane label="计算参数" name="calculation">
              <div class="checkbox-group">
                <el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
                <el-checkbox v-model="formData.warehouseControl">仓库运行MRP的控制</el-checkbox>
@@ -224,6 +176,7 @@
            </el-tab-pane>
          </el-tabs>
        </div>
        <!-- æ±‡æ€»åˆå¹¶é€‰é¡¹ -->
        <div class="form-section">
          <div class="section-title">汇总合并选项</div>
@@ -233,20 +186,20 @@
            <el-checkbox v-model="formData.summaryDemandDate">需求日期</el-checkbox>
          </div>
        </div>
        <!-- è®¡ç®—公式 -->
        <div class="form-section">
          <div class="section-title">计算公式</div>
          <div class="formula-input-section">
            <el-form-item label="计算公式"
                          prop="formula"
                          required>
              <el-input v-model="formData.formula"
            <el-form-item label="计算公式" prop="formula" required>
              <el-input
                v-model="formData.formula"
                        placeholder="例如: é¢„计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量"
                        @input="validateFormula" />
                @input="validateFormula"
              />
            </el-form-item>
            <div class="formula-help">
              <el-text type="info"
                       size="small">
              <el-text type="info" size="small">
                æ”¯æŒå˜é‡ï¼šé¢„计出库数量、现有库存、安全库存、预计入库数量
              </el-text>
            </div>
@@ -254,117 +207,87 @@
        </div>
      </div>
    </FormDialog>
    <!-- äº§å“é€‰æ‹©å¯¹è¯æ¡† -->
    <FormDialog v-model="productSelectDialogVisible"
    <FormDialog
      v-model="productSelectDialogVisible"
                title="选择产品"
                :width="'800px'"
                :close-on-click-modal="false"
                @close="productSelectDialogVisible = false"
                @confirm="handleConfirmProductSelection"
                @cancel="productSelectDialogVisible = false">
      @cancel="productSelectDialogVisible = false"
    >
      <div class="product-select">
        <el-alert title="请选择要计算的产品"
        <el-alert
          title="请选择要计算的产品"
                  type="info"
                  :closable="false"
                  show-icon>
          show-icon
        >
          <template #default>
            <p>选择产品后,系统将根据当前计算公式和产品库存情况进行计算。</p>
          </template>
        </el-alert>
        <el-table v-loading="productLoading"
        <el-table
          v-loading="productLoading"
                  :data="productList"
                  @selection-change="handleProductSelectionChange"
                  stripe
                  border
                  style="width: 100%; margin-top: 20px;">
          <el-table-column type="selection"
                           width="55" />
          <el-table-column prop="productCategory"
                           label="产品大类"
                           min-width="150" />
          <el-table-column prop="specificationModel"
                           label="规格型号"
                           width="120" />
          <el-table-column prop="inboundNum0"
                           label="现有库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="安全库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="预计出库"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum0"
                           label="预计入库"
                           width="100"
                           align="right" />
          style="width: 100%; margin-top: 20px;"
        >
          <el-table-column type="selection" width="55" />
          <el-table-column prop="productCategory" label="产品大类" min-width="150" />
          <el-table-column prop="specificationModel" label="产品规格" width="120" />
          <el-table-column prop="inboundNum0" label="现有库存" width="100" align="right" />
          <el-table-column prop="inboundNum" label="安全库存" width="100" align="right" />
          <el-table-column prop="inboundNum" label="预计出库" width="100" align="right" />
          <el-table-column prop="inboundNum0" label="预计入库" width="100" align="right" />
        </el-table>
      </div>
    </FormDialog>
    <!-- è®¡ç®—结果对话框 -->
    <FormDialog v-model="calculateDialogVisible"
    <FormDialog
      v-model="calculateDialogVisible"
                title="采购计算结果"
                :width="'1000px'"
                :close-on-click-modal="false"
                @close="calculateDialogVisible = false"
                @confirm="handleCreatePurchaseOrder"
                @cancel="calculateDialogVisible = false">
      @cancel="calculateDialogVisible = false"
    >
      <div class="calculate-result">
        <el-alert title="计算结果"
        <el-alert
          title="计算结果"
                  type="success"
                  :closable="false"
                  show-icon>
          show-icon
        >
          <template #default>
            <p>基于当前配置的计算公式和库存情况,系统已计算出各产品的采购需求。</p>
          </template>
        </el-alert>
        <el-table :data="calculateResult"
                  stripe
                  border
                  style="width: 100%; margin-top: 20px;">
          <el-table-column prop="productCategory"
                           label="产品大类"
                           min-width="150" />
          <el-table-column prop="specificationModel"
                           label="规格型号"
                           width="120" />
          <el-table-column prop="inboundNum0"
                           label="现有库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="安全库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="预计出库数量"
                           width="120"
                           align="right" />
          <el-table-column prop="inboundNum0"
                           label="预计入库数量"
                           width="120"
                           align="right" />
          <el-table-column prop="weeklyNetDemand"
                           label="按周净需求"
                           width="120"
                           align="right">
        <el-table :data="calculateResult" stripe border style="width: 100%; margin-top: 20px;">
          <el-table-column prop="productCategory" label="产品大类" min-width="150" />
          <el-table-column prop="specificationModel" label="产品规格" width="120" />
          <el-table-column prop="inboundNum0" label="现有库存" width="100" align="right" />
          <el-table-column prop="inboundNum" label="安全库存" width="100" align="right" />
          <el-table-column prop="inboundNum" label="预计出库数量" width="120" align="right" />
          <el-table-column prop="inboundNum0" label="预计入库数量" width="120" align="right" />
          <el-table-column prop="weeklyNetDemand" label="按周净需求" width="120" align="right">
            <template #default="{ row }">
              <el-tag :type="row.weeklyNetDemand > 0 ? 'warning' : 'success'"
                      size="small">
              <el-tag :type="row.weeklyNetDemand > 0 ? 'warning' : 'success'" size="small">
                {{ row.weeklyNetDemand }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="suggestedPurchase"
                           label="建议采购"
                           width="100"
                           align="right">
          <el-table-column prop="suggestedPurchase" label="建议采购" width="100" align="right">
            <template #default="{ row }">
              <el-tag :type="row.suggestedPurchase > 0 ? 'danger' : 'success'"
                      size="small">
              <el-tag :type="row.suggestedPurchase > 0 ? 'danger' : 'success'" size="small">
                {{ row.suggestedPurchase }}
              </el-tag>
            </template>
@@ -376,51 +299,43 @@
</template>
<script setup>
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import { ref, reactive, onMounted, getCurrentInstance, computed } from "vue";
  import dayjs from "dayjs";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Search, Refresh, Plus, Download } from "@element-plus/icons-vue";
  import {
    listPage,
    add,
    update,
    del,
    listPageCopy,
  } from "@/api/procurementManagement/procurementPlan.js";
import FormDialog from '@/components/Dialog/FormDialog.vue';
import {ref, reactive, onMounted, getCurrentInstance} from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus, Download } from '@element-plus/icons-vue'
import {listPage,add,update,del,listPageCopy} from "@/api/procurementManagement/procurementPlan.js"
  // å“åº”式数据
  const loading = ref(false);
  const submitLoading = ref(false);
  const dialogVisible = ref(false);
  const productSelectDialogVisible = ref(false);
  const calculateDialogVisible = ref(false);
  const dialogType = ref("add");
  const productLoading = ref(false);
  const selectedProducts = ref([]);
  const currentPlan = ref(null);
const loading = ref(false)
const submitLoading = ref(false)
const dialogVisible = ref(false)
const productSelectDialogVisible = ref(false)
const calculateDialogVisible = ref(false)
const dialogType = ref('add')
const productLoading = ref(false)
const selectedProducts = ref([])
const currentPlan = ref(null)
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    planName: "",
    status: "",
  });
  planName: '',
  status: ''
})
  // åˆ†é¡µæ•°æ®
  const pagination = reactive({
    current: 1,
    size: 20,
  });
  size: 20
})
  // è¡¨å•数据
  const formData = reactive({
    code: "",
    planName: "",
    description: "",
    status: "",
  code: '',
  planName: '',
  description: '',
  status: '',
    isSystemPreset: false,
    formula: "",
    createTime: "",
  formula: '',
    // è®¡ç®—参数
    considerExistingStock: false,
    warehouseControl: false,
@@ -432,163 +347,166 @@
    // æ±‡æ€»åˆå¹¶é€‰é¡¹
    summaryMaterial: false,
    summaryAuxAttributes: false,
    summaryDemandDate: false,
  });
  const formCreateTimeDate = computed({
    get: () => (formData.createTime ? String(formData.createTime).split(" ")[0] : ""),
    set: (value) => {
      formData.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
    },
  });
  summaryDemandDate: false
})
  // å½“前激活的标签页
  const activeTab = ref("demand");
const activeTab = ref('demand')
  // è¡¨å•验证规则
  const formRules = {
    planName: [{ required: true, message: "请输入计划名称", trigger: "blur" }],
    status: [{ required: true, message: "请选择状态", trigger: "change" }],
    formula: [{ required: true, message: "请输入计算公式", trigger: "blur" }],
  };
  planName: [
    { required: true, message: '请输入计划名称', trigger: 'blur' }
  ],
  status: [
    { required: true, message: '请选择状态', trigger: 'change' }
  ],
  formula: [
    { required: true, message: '请输入计算公式', trigger: 'blur' }
  ]
}
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
const tableData = ref([])
  // äº§å“åˆ—表数据
  const productList = ref([
    {
      id: 4,
      productName: "产品D",
      productCode: "PD004",
    productName: '产品D',
    productCode: 'PD004',
      existingStock: 90,
      safetyStock: 40,
      expectedOutbound: 160,
      expectedInbound: 35,
    },
  ]);
    expectedInbound: 35
  }
])
  // è®¡ç®—结果数据
  const calculateResult = ref([
    {
      productName: "产品A",
    productName: '产品A',
      existingStock: 100,
      safetyStock: 50,
      expectedOutbound: 200,
      expectedInbound: 30,
      weeklyNetDemand: 120,
      suggestedPurchase: 150,
    suggestedPurchase: 150
    },
    {
      productName: "产品B",
    productName: '产品B',
      existingStock: 80,
      safetyStock: 30,
      expectedOutbound: 150,
      expectedInbound: 20,
      weeklyNetDemand: 100,
      suggestedPurchase: 120,
    },
  ]);
  const total = ref(0);
    suggestedPurchase: 120
  }
])
const total = ref(0)
  // æ–¹æ³•
  const handleSearch = () => {
    pagination.current = 1;
    loadData();
  };
  pagination.current = 1
  loadData()
}
  const handleReset = () => {
    Object.assign(searchForm, {
      planName: "",
      status: "",
    });
    handleSearch();
  };
    planName: '',
    status: ''
  })
  handleSearch()
}
  const loadData = () => {
    loading.value = true;
  loading.value = true
    listPage({ ...searchForm, ...pagination }).then(res => {
      if (res.code === 200) {
        tableData.value = res.data.records;
        total.value = res.data.total;
        loading.value = false;
      tableData.value = res.data.records
      total.value = res.data.total
      loading.value = false
      }
    });
  };
  })
}
  const handleAdd = () => {
    dialogType.value = "add";
    resetForm();
    formData.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    dialogVisible.value = true;
  };
  dialogType.value = 'add'
  resetForm()
  // è‡ªåŠ¨ç”Ÿæˆç¼–ç 
  formData.code = 'CGJH' + String(Date.now()).slice(-4)
  dialogVisible.value = true
}
  const handleEdit = row => {
    dialogType.value = "edit";
    Object.assign(formData, row);
    dialogVisible.value = true;
  };
const handleEdit = (row) => {
  dialogType.value = 'edit'
  Object.assign(formData, row)
  dialogVisible.value = true
}
  const handleDelete = async row => {
const handleDelete = async (row) => {
    try {
      await ElMessageBox.confirm("确定要删除这个采购计划吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      });
      let ids = [row.id];
    await ElMessageBox.confirm('确定要删除这个采购计划吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    let ids = [row.id]
      del(ids).then(res => {
        if (res.code === 200) {
          ElMessage.success("删除成功");
          loadData();
      ElMessage.success('删除成功')
      loadData()
        }
      });
   })
    } catch {
      // ç”¨æˆ·å–消删除
    }
  };
}
  const handleSubmit = async () => {
    try {
      // è¡¨å•验证
      if (!formData.planName || !formData.formula) {
        ElMessage.error("请填写必填项");
        return;
      ElMessage.error('请填写必填项')
      return
      }
      submitLoading.value = true;
    submitLoading.value = true
      if (dialogType.value === "add") {
    if (dialogType.value === 'add') {
        add(formData).then(res => {
          if (res.code === 200) {
            ElMessage.success("新增成功");
            dialogVisible.value = false;
            loadData();
          ElMessage.success('新增成功')
          dialogVisible.value = false
          loadData()
          }
        });
      })
      } else {
        // ç¼–辑
        update(formData).then(res => {
          if (res.code === 200) {
            ElMessage.success("编辑成功");
            dialogVisible.value = false;
            loadData();
          ElMessage.success('编辑成功')
          dialogVisible.value = false
          loadData()
          }
        });
      })
      }
    } catch (error) {
      ElMessage.error("操作失败");
    ElMessage.error('操作失败')
    } finally {
      submitLoading.value = false;
    submitLoading.value = false
    }
  };
}
  const resetForm = () => {
    Object.assign(formData, {
      code: "",
      planName: "",
      description: "",
      status: "",
    code: '',
    planName: '',
    description: '',
    status: '',
      isSystemPreset: false,
      formula: "预计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量",
    formula: '预计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量',
      // è®¡ç®—参数
      considerExistingStock: false,
      warehouseControl: false,
@@ -600,52 +518,52 @@
      // æ±‡æ€»åˆå¹¶é€‰é¡¹
      summaryMaterial: false,
      summaryAuxAttributes: false,
      summaryDemandDate: false,
    });
    activeTab.value = "demand";
  };
    summaryDemandDate: false
  })
  activeTab.value = 'demand'
}
  const validateFormula = () => {
    // ç®€å•的公式验证
    const formula = formData.formula;
  const formula = formData.formula
    if (formula && !/^[a-zA-Z\u4e00-\u9fa5\s\*\+\-\/\(\)\d\.]+$/.test(formula)) {
      ElMessage.warning("公式格式可能不正确,请检查");
    ElMessage.warning('公式格式可能不正确,请检查')
    }
  };
}
  const handleCalculate = row => {
    currentPlan.value = row;
    productSelectDialogVisible.value = true;
    loadProductList();
  };
const handleCalculate = (row) => {
  currentPlan.value = row
  productSelectDialogVisible.value = true
  loadProductList()
}
  const loadProductList = () => {
    productLoading.value = true;
  productLoading.value = true
    // æ¨¡æ‹ŸåŠ è½½äº§å“æ•°æ®
    listPageCopy({ size: -1 }).then(res => {
      if (res.code === 200) {
        productList.value = res.data.records;
        productLoading.value = false;
      productList.value = res.data.records
      productLoading.value = false
      }
    });
  };
  })
}
  const handleProductSelectionChange = selection => {
    selectedProducts.value = selection;
  };
const handleProductSelectionChange = (selection) => {
  selectedProducts.value = selection
}
  const handleConfirmProductSelection = () => {
    if (selectedProducts.value.length === 0) {
      ElMessage.warning("请选择要计算的产品");
      return;
    ElMessage.warning('请选择要计算的产品')
    return
    }
    ElMessage.success(`正在计算 ${currentPlan.value.planName} çš„采购需求...`);
    productSelectDialogVisible.value = false;
  ElMessage.success(`正在计算 ${currentPlan.value.planName} çš„采购需求...`)
  productSelectDialogVisible.value = false
    // æ ¹æ®é€‰æ‹©çš„产品和计算公式进行计算
    calculateWithSelectedProducts();
  };
  calculateWithSelectedProducts()
}
  const calculateWithSelectedProducts = () => {
    // æ¨¡æ‹Ÿè®¡ç®—过程
@@ -653,12 +571,8 @@
    const result = selectedProducts.value.map(product => {
      // è¿™é‡Œåº”该根据实际的计算公式进行计算
      // ç¤ºä¾‹ï¼šé¢„计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量
      const weeklyNetDemand =
        product.inboundNum -
        product.inboundNum0 +
        product.inboundNum -
        product.inboundNum0;
      const suggestedPurchase = Math.max(0, weeklyNetDemand);
    const weeklyNetDemand = product.inboundNum - product.inboundNum0 + product.inboundNum - product.inboundNum0
    const suggestedPurchase = Math.max(0, weeklyNetDemand)
      return {
        productCategory: product.productCategory,
@@ -666,17 +580,18 @@
        inboundNum0: product.inboundNum0,
        inboundNum: product.inboundNum,
        weeklyNetDemand: weeklyNetDemand,
        suggestedPurchase: suggestedPurchase,
      };
    });
      suggestedPurchase: suggestedPurchase
    }
  })
    calculateResult.value = result;
    calculateDialogVisible.value = true;
  };
  calculateResult.value = result
  calculateDialogVisible.value = true
}
  const handleCreatePurchaseOrder = () => {
    calculateDialogVisible.value = false;
  };
  calculateDialogVisible.value = false
}
  const { proxy } = getCurrentInstance();
  const handleExport = () => {
    ElMessageBox.confirm("内容将被导出,是否确认导出?", "导出", {
@@ -690,22 +605,23 @@
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
}
  const handleSizeChange = size => {
    pagination.size = size;
    loadData();
  };
  const handleCurrentChange = current => {
    pagination.current = current;
    loadData();
  };
const handleSizeChange = (size) => {
  pagination.size = size
  loadData()
}
const handleCurrentChange = (current) => {
  pagination.current = current
  loadData()
}
  // ç”Ÿå‘½å‘¨æœŸ
  onMounted(() => {
    loadData();
  });
  loadData()
})
</script>
<style scoped>
src/views/procurementManagement/procurementReport/index.vue
@@ -83,6 +83,7 @@
import PIMTable from '@/components/PIMTable/PIMTable.vue'
import { procurementBusinessSummaryListPage } from '@/api/procurementManagement/procurementReport'
import { productTreeList } from '@/api/basicData/product'
import { formatDecimal, formatCurrency } from '@/utils/numberFormat'
const { proxy } = getCurrentInstance()
@@ -135,14 +136,14 @@
    prop: 'returnAmount',
    width: 120,
    formatData: (val) => {
      return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00'
      return val ? formatCurrency(val) : 'Â¥0'
    }
  },
  {
    label: '退款储量',
    prop: 'purchaseAmount',
    formatData: (val) => {
      return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00'
      return val ? formatCurrency(val) : 'Â¥0'
    }
  },
  {
@@ -155,7 +156,7 @@
    prop: 'averagePrice',
    width: 120,
    formatData: (val) => {
      return val ? `Â¥${parseFloat(val).toFixed(2)}` : 'Â¥0.00'
      return val ? formatCurrency(val) : 'Â¥0'
    }
  },
  {
src/views/procurementManagement/purchaseOrder/index.vue
@@ -56,13 +56,6 @@
            <el-option label="供应商B" value="供应商B" />
          </el-select>
        </el-form-item>
        <el-form-item label="创建时间">
          <el-date-picker v-model="formCreateTimeDate"
                          type="date"
                          placeholder="选择日期"
                          value-format="YYYY-MM-DD"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
@@ -73,8 +66,7 @@
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref, reactive, computed } from 'vue'
import dayjs from 'dayjs'
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
@@ -89,14 +81,7 @@
const formData = reactive({
  supplierName: '',
  remark: '',
  createTime: ''
})
const formCreateTimeDate = computed({
  get: () => (formData.createTime ? String(formData.createTime).split(' ')[0] : ''),
  set: (value) => {
    formData.createTime = value ? `${value} ${dayjs().format('HH:mm:ss')}` : ''
  }
  remark: ''
})
const mockData = [
@@ -137,9 +122,9 @@
const openDialog = (type, row = {}) => {
  dialogType.value = type
  if (type === 'edit' && row.id) {
    Object.assign(formData, { supplierName: row.supplierName, remark: row.remark, createTime: row.createTime || '' })
    Object.assign(formData, { supplierName: row.supplierName, remark: row.remark })
  } else {
    Object.assign(formData, { supplierName: '', remark: '', createTime: dayjs().format('YYYY-MM-DD HH:mm:ss') })
    Object.assign(formData, { supplierName: '', remark: '' })
  }
  dialogVisible.value = true
}
@@ -148,11 +133,11 @@
  if (dialogType.value === 'add') {
    const newOrder = {
      id: Date.now(),
      orderNo: '',
      orderNo: `PO${Date.now()}`,
      supplierName: formData.supplierName,
      status: 'draft',
      totalAmount: 0,
      createTime: formData.createTime,
      createTime: new Date().toLocaleString(),
      remark: formData.remark
    }
    tableData.value.unshift(newOrder)
src/views/procurementManagement/purchaseReturnOrder/New.vue
@@ -58,63 +58,6 @@
        </el-form-item>
        <el-form-item
            label="发货类型"
            prop="shippingType"
            :rules="[
                {
                  required: true,
                  message: '请选择发货类型',
                  trigger: 'change',
                }
              ]"
        >
          <el-select
              v-model="formState.shippingType"
              placeholder="请选择发货类型"
              style="width: 240px"
              @change="handleShippingTypeChange"
          >
            <el-option label="货车" :value="1" />
            <el-option label="快递" :value="2" />
          </el-select>
        </el-form-item>
        <el-form-item
            v-if="formState.shippingType === 1"
            label="发货车牌号"
            prop="truckPlateNo"
        >
          <el-input
              v-model="formState.truckPlateNo"
              placeholder="请输入发货车牌号"
              style="width: 240px"
          />
        </el-form-item>
        <template v-else-if="formState.shippingType === 2">
          <el-form-item
              label="快递公司"
              prop="expressCompany"
          >
            <el-input
                v-model="formState.expressCompany"
                placeholder="请输入快递公司"
                style="width: 240px"
            />
          </el-form-item>
          <el-form-item
              label="快递单号"
              prop="expressNo"
          >
            <el-input
                v-model="formState.expressNo"
                placeholder="请输入快递单号"
                style="width: 240px"
            />
          </el-form-item>
        </template>
        <el-form-item
            label="供应商名称"
            prop="supplierId"
            :rules="[
@@ -316,16 +259,13 @@
              <el-table-column label="数量"
                               prop="stockInNum"
                               width="100" />
              <el-table-column label="销售发货数量"
                               prop="saleOutQuantity"
                               width="120" />
                               <el-table-column label="可退货数量"
                               prop="unQuantity"
                               width="130" />
              <el-table-column label="已退货数量"
                               width="130">
                <template #default="scope">
                  {{ formattedNumber(scope.row, null, scope.row.totalReturnNum || 0) }}
                  {{ calcAlreadyReturned(scope.row) }}
                </template>
              </el-table-column>
              <el-table-column label="退货数量"
@@ -480,7 +420,7 @@
<script setup>
import {ref, computed, getCurrentInstance, watch, defineAsyncComponent} from "vue";
import {createPurchaseReturnOrder} from "@/api/procurementManagement/purchase_return_order.js";
import {getOptions, purchaseReturnableList} from "@/api/procurementManagement/procurementLedger.js";
import {getOptions, purchaseList} from "@/api/procurementManagement/procurementLedger.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
const ProductList = defineAsyncComponent(() => import("@/views/procurementManagement/purchaseReturnOrder/ProductList.vue"));
const props = defineProps({
@@ -515,10 +455,6 @@
  no: '',
  isDefaultNo: true,
  returnType: 0,
  shippingType: undefined,
  truckPlateNo: '',
  expressCompany: '',
  expressNo: '',
  incomeType: undefined,
  remark: '',
  supplierId: undefined,
@@ -565,19 +501,6 @@
// æ˜¯å¦å±•示产品列表数据
const isShowProductsModal = ref(false)
const handleShippingTypeChange = (val) => {
  if (val === 1) {
    formState.value.expressCompany = '';
    formState.value.expressNo = '';
  } else if (val === 2) {
    formState.value.truckPlateNo = '';
  } else {
    formState.value.truckPlateNo = '';
    formState.value.expressCompany = '';
    formState.value.expressNo = '';
  }
};
const isShow = computed({
  get() {
    return props.visible;
@@ -605,6 +528,14 @@
const toNumber = (val) => {
  const num = Number(val)
  return Number.isNaN(num) ? 0 : num
}
/** å·²é€€è´§æ•°é‡ = å…¥åº“行总数量 âˆ’ å½“前可退货数量(剩余) */
const calcAlreadyReturned = (row) => {
  const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0)
  const un = Number(row?.unQuantity ?? 0)
  if (!Number.isFinite(total) || !Number.isFinite(un)) return 0
  return Math.max(total - un, 0)
}
const getReturnTotal = (row) => {
@@ -650,10 +581,6 @@
}
const closeModal = () => {
  formState.value.shippingType = undefined;
  formState.value.truckPlateNo = '';
  formState.value.expressCompany = '';
  formState.value.expressNo = '';
  isShow.value = false;
};
@@ -747,7 +674,7 @@
const fetchPurchaseLedgerOptions = () => {
  purchaseLedgerOptions.value = []
  if (formState.value.supplierId) {
    purchaseReturnableList({ supplierId: formState.value.supplierId }).then((res) => {
    purchaseList({supplierId: formState.value.supplierId,approvalStatus:3}).then((res) => {
      purchaseLedgerOptions.value = res.rows;
    });
  }
src/views/procurementManagement/purchaseReturnOrder/ProductList.vue
@@ -34,16 +34,13 @@
          <el-table-column label="数量"
                           prop="stockInNum"
                           width="70" />
          <el-table-column label="销售发货数量"
                           prop="saleOutQuantity"
                           width="120" />
          <el-table-column label="可退货数量"
                           prop="unQuantity"
                           width="130" />
          <el-table-column label="已退货数量"
                           width="130">
            <template #default="scope">
              {{ formattedNumber(scope.row, null, scope.row.totalReturnNum || 0) }}
              {{ calcAlreadyReturned(scope.row) }}
            </template>
          </el-table-column>
          <!-- <el-table-column label="库存预警数量"
@@ -122,6 +119,14 @@
  return parseFloat(cellValue).toFixed(2);
};
/** å·²é€€è´§æ•°é‡ = å…¥åº“行总数量 âˆ’ å½“前可退货数量(剩余) */
const calcAlreadyReturned = (row) => {
  const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0)
  const un = Number(row?.unQuantity ?? 0)
  if (!Number.isFinite(total) || !Number.isFinite(un)) return 0
  return Math.max(total - un, 0)
}
const handleChangeSelection = (val) => {
  selectedRows.value = val;
}
src/views/procurementManagement/purchaseReturnOrder/index.vue
@@ -45,13 +45,8 @@
            @click="handleDetail(row)"
            >详情</el-button
          >
          <el-button
            link
            size="small"
            :disabled="row.stockOutApproved"
            :style="{ color: row.stockOutApproved ? '#c0c4cc' : undefined }"
            @click="handleDelete(row)"
            >{{ row.stockOutApproved }}</el-button
          <el-button link size="small" @click="handleDelete(row)"
            >删除</el-button
          >
        </template>
      </PIMTable>
@@ -94,20 +89,6 @@
          <el-descriptions-item label="退料人">{{
            detailData.returnUserName || "--"
          }}</el-descriptions-item>
          <el-descriptions-item label="发货类型">{{
            getShippingTypeLabel(detailData.shippingType)
          }}</el-descriptions-item>
          <el-descriptions-item v-if="String(detailData.shippingType) === '1'" label="发货车牌号">{{
            detailData.truckPlateNo || "--"
          }}</el-descriptions-item>
          <template v-else-if="String(detailData.shippingType) === '2'">
            <el-descriptions-item label="快递公司">{{
              detailData.expressCompany || "--"
            }}</el-descriptions-item>
            <el-descriptions-item label="快递单号">{{
              detailData.expressNo || "--"
            }}</el-descriptions-item>
          </template>
          <el-descriptions-item label="整单折扣额">{{
            formatAmount(detailData.totalDiscountAmount)
          }}</el-descriptions-item>
@@ -161,14 +142,13 @@
          />
          <el-table-column label="单位" prop="unit" width="80" />
          <el-table-column label="数量" prop="stockInNum" width="80" />
          <el-table-column label="销售发货数量" prop="saleOutQuantity" width="110" />
          <el-table-column label="可退货数量"
                           prop="unQuantity"
                           width="100" />
          <el-table-column label="已退货数量"
                           width="100">
            <template #default="scope">
              {{ formatAmount(scope.row.totalReturnNum || 0) }}
              {{ calcAlreadyReturned(scope.row) }}
            </template>
          </el-table-column>
          <!-- <el-table-column label="库存预警数量" prop="warnNum" width="120" /> -->
@@ -309,15 +289,6 @@
    prop: "returnUserName",
    width: 110,
  },
  {
    label: "发货类型",
    prop: "shippingType",
    width: 100,
    formatData: (val) => getShippingTypeLabel(val),
  },
  { label: "发货车牌号", prop: "truckPlateDisplay", width: 140 },
  { label: "快递公司", prop: "expressCompanyDisplay", width: 140 },
  { label: "快递单号", prop: "expressNoDisplay", width: 150 },
  {
    label: "整单折扣额",
@@ -370,7 +341,6 @@
      },
      {
        name: "删除",
        disabled: (row) => !!row.stockOutApproved,
        clickFun: (row) => {
          handleDelete(row);
        },
@@ -452,12 +422,7 @@
  findPurchaseReturnOrderListPage({ ...searchForm.value, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = (res.data.records || []).map((row) => ({
        ...row,
        truckPlateDisplay: String(row?.shippingType) === "1" ? (row?.truckPlateNo || "--") : "--",
        expressCompanyDisplay: String(row?.shippingType) === "2" ? (row?.expressCompany || "--") : "--",
        expressNoDisplay: String(row?.shippingType) === "2" ? (row?.expressNo || "--") : "--",
      }));
      tableData.value = res.data.records;
      page.total = res.data.total;
    })
    .catch(() => {
@@ -485,14 +450,6 @@
  );
};
const getShippingTypeLabel = (value) => {
  const shippingTypeMap = {
    1: "货车",
    2: "快递",
  };
  return shippingTypeMap[String(value)] || shippingTypeMap[value] || "--";
};
const formatAmount = (value) => {
  if (value === null || value === undefined || value === "") {
    return "--";
@@ -504,6 +461,14 @@
  return num.toFixed(2);
};
/** å·²é€€è´§æ•°é‡ = å…¥åº“行总数量 âˆ’ å½“前可退货数量(剩余) */
const calcAlreadyReturned = (row) => {
  const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0);
  const un = Number(row?.unQuantity ?? 0);
  if (!Number.isFinite(total) || !Number.isFinite(un)) return 0;
  return Math.max(total - un, 0);
};
onMounted(() => {
  getList();
});
@@ -513,3 +478,4 @@
  margin-top: unset;
}
</style>
src/views/procurementManagement/qualityInspection/index.vue
@@ -116,18 +116,6 @@
          <el-input v-model="formData.inspector" placeholder="请输入质检员姓名" />
        </el-form-item>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建时间">
              <el-date-picker v-model="formCreateTimeDate"
                              type="date"
                              placeholder="选择日期"
                              value-format="YYYY-MM-DD"
                              style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="备注">
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
@@ -138,8 +126,7 @@
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref, reactive, computed } from 'vue'
import dayjs from 'dayjs'
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
@@ -157,14 +144,7 @@
  supplierName: '',
  products: [],
  inspector: '',
  remark: '',
  createTime: ''
})
const formCreateTimeDate = computed({
  get: () => (formData.createTime ? String(formData.createTime).split(' ')[0] : ''),
  set: (value) => {
    formData.createTime = value ? `${value} ${dayjs().format('HH:mm:ss')}` : ''
  }
  remark: ''
})
const mockData = [
@@ -210,8 +190,7 @@
      arrivalNo: row.arrivalNo, 
      supplierName: row.supplierName, 
      inspector: row.inspector,
      remark: row.remark,
      createTime: row.createTime || ''
      remark: row.remark
    })
  } else {
    Object.assign(formData, {
@@ -219,8 +198,7 @@
      supplierName: '',
      products: [],
      inspector: '',
      remark: '',
      createTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
      remark: ''
    })
  }
  dialogVisible.value = true
@@ -250,13 +228,13 @@
  if (dialogType.value === 'add') {
    const newInspection = {
      id: Date.now(),
      inspectionNo: '',
      inspectionNo: `QI${Date.now()}`,
      arrivalNo: formData.arrivalNo,
      supplierName: formData.supplierName,
      status: 'pending',
      qualifiedQuantity: totalQualified,
      unqualifiedQuantity: totalUnqualified,
      inspectionTime: formData.createTime,
      inspectionTime: new Date().toLocaleString(),
      inspector: formData.inspector,
      remark: formData.remark
    }
src/views/productManagement/productIdentifier/index.vue
@@ -239,13 +239,6 @@
        <el-form-item label="标识类型">
          <span>{{ currentProduct.identifierType }}</span>
        </el-form-item>
        <el-form-item label="创建时间">
          <el-date-picker v-model="createTimeDate"
                          type="date"
                          placeholder="选择日期"
                          value-format="YYYY-MM-DD"
                          style="width: 100%"></el-date-picker>
        </el-form-item>
        <el-form-item label="生成数量"
                      prop="generateQuantity">
          <el-input-number v-model="generateQuantity"
@@ -363,7 +356,6 @@
<script setup>
  import { ref, reactive, computed } from "vue";
  import dayjs from "dayjs";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Plus, Search, Loading, Download } from "@element-plus/icons-vue";
  import Pagination from "@/components/PIMTable/Pagination.vue";
@@ -450,13 +442,6 @@
  const generateQuantity = ref(1);
  const codeRule = ref("");
  const customPrefix = ref("");
  const createTime = ref(dayjs().format("YYYY-MM-DD HH:mm:ss"));
  const createTimeDate = computed({
    get: () => (createTime.value ? String(createTime.value).split(" ")[0] : ""),
    set: (value) => {
      createTime.value = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
    },
  });
  const newBatchNo = ref("");
  const reassignReason = ref("");
  const formRef = ref();
@@ -674,9 +659,9 @@
          currentProduct.value.batchNo
        }_${String(i).padStart(3, "0")}`;
      } else if (codeRule.value === "时间戳+随机数") {
        identifierCode = "";
        identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
      } else if (codeRule.value === "自定义规则") {
        identifierCode = "";
        identifierCode = `${customPrefix.value || "CUSTOM"}_${Date.now()}_${i}`;
      }
      newIdentifiers.push({
@@ -687,7 +672,7 @@
        identifierType: currentProduct.value.identifierType,
        identifierCode: identifierCode,
        status: "已生成",
        generateTime: createTime.value,
        generateTime: new Date().toLocaleString(),
        remark: "批量生成",
      });
    }
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -340,7 +340,6 @@
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
@@ -358,7 +357,6 @@
                                style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
@@ -1203,7 +1201,7 @@
    if (!Number.isFinite(numberValue)) {
      return 0;
    }
    return Number(numberValue.toFixed(2));
    return Number(numberValue.toFixed(4));
  };
  const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => {
src/views/productionManagement/productStructure/Detail/index.vue
@@ -82,7 +82,6 @@
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
@@ -100,7 +99,6 @@
                                style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
@@ -274,7 +272,7 @@
    if (!Number.isFinite(numberValue)) {
      return 0;
    }
    return Number(numberValue.toFixed(2));
    return Number(numberValue.toFixed(4));
  };
  const syncDemandedQuantityTree = (
@@ -384,7 +382,7 @@
  };
  const fetchProcessOptions = async () => {
    const { data } = await list({ size: -1, current: -1 });
    const { data } = await list({});
    console.log(data, "dataValue.dataList");
    dataValue.processOptions = normalizeListData(data);
  };
src/views/productionManagement/productStructure/StructureEdit.vue
@@ -86,7 +86,6 @@
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
@@ -103,7 +102,6 @@
                                style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
src/views/productionManagement/productStructure/index.vue
@@ -1,30 +1,6 @@
<template>
  <div class="app-container">
    <div class="table_list">
      <el-form :inline="true" :model="queryParams" class="search-bar" @submit.prevent>
        <el-form-item label="产品名称">
          <el-input
            v-model="queryParams.productName"
            placeholder="请输入产品名称"
            clearable
            style="width: 220px"
            @keyup.enter="handleQuery"
          />
        </el-form-item>
        <el-form-item label="规格型号">
          <el-input
            v-model="queryParams.productModelName"
            placeholder="请输入规格型号"
            clearable
            style="width: 220px"
            @keyup.enter="handleQuery"
          />
        </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 style="text-align: right; margin-bottom: 10px;">
        <el-button type="primary"
                   @click="handleAdd">新增</el-button>
@@ -157,7 +133,6 @@
      dataType: "slot",
      slot: "detail",
      minWidth: 140,
      align: 'center'
    },
    {
      label: "产品名称",
@@ -222,10 +197,6 @@
  const operationType = ref("add"); // add | edit
  const formRef = ref(null);
  const showProductSelectDialog = ref(false);
  const queryParams = reactive({
    productName: "",
    productModelName: "",
  });
  //  BOM导入参数
  const upload = reactive({
@@ -278,25 +249,12 @@
    getList();
  };
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const resetQuery = () => {
    queryParams.productName = "";
    queryParams.productModelName = "";
    handleQuery();
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    tableLoading.value = true;
    listPage({
      current: page.current,
      size: page.size,
      productName: queryParams.productName || undefined,
      productModelName: queryParams.productModelName || undefined,
    })
      .then(res => {
        const records = res?.data?.records || [];
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -21,28 +21,34 @@
                         prop="batchNo"
                         min-width="150" />
        <el-table-column label="需求数量"
                         prop="demandedQuantity"
                         min-width="110" />
                         min-width="110">
          <template #default="{ row }">
            {{ stripTrailingZeros(row.demandedQuantity) }}
          </template>
        </el-table-column>
        <el-table-column label="计量单位"
                         prop="unit"
                         width="100" />
        <el-table-column label="领用数量"
                         prop="pickQuantity"
                         min-width="110" />
                         min-width="110">
          <template #default="{ row }">
            {{ stripTrailingZeros(row.pickQuantity) }}
          </template>
        </el-table-column>
        <el-table-column label="补料数量"
                         min-width="120">
          <template #default="{ row }">
            <el-button type="primary"
                       link
                       @click="handleViewSupplementRecord(row)">
              {{ row.feedingQty ?? 0 }}
              {{ stripTrailingZeros(row.feedingQty) ?? 0 }}
            </el-button>
          </template>
        </el-table-column>
        <el-table-column label="退料数量"
                         min-width="110">
          <template #default="{ row }">
            {{ row.returnQty ?? 0 }}
            {{ stripTrailingZeros(row.returnQty) ?? 0 }}
          </template>
        </el-table-column>
        <el-table-column label="实际数量"
@@ -50,10 +56,11 @@
          <template #default="{ row }">
            <el-input-number v-model="row.actualQty"
                             :min="0"
                             :precision="3"
                             :step="1"
                             controls-position="right"
                             placeholder="输入实际数量"
                             :formatter="value => stripTrailingZeros(value)"
                             :parser="value => parseFloat(value) || 0"
                             style="width: 100%;"
                             :disabled="row.returned || orderRow?.end"
                             @change="val => handleActualQtyChange(row, val)" />
@@ -115,8 +122,11 @@
                         prop="unit"
                         min-width="100" />
        <el-table-column label="退料汇总数量"
                         prop="returnQtyTotal"
                         min-width="140" />
                         min-width="140">
          <template #default="{ row }">
            {{ stripTrailingZeros(row.returnQtyTotal) }}
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <span class="dialog-footer">
@@ -139,6 +149,14 @@
    updateMaterialPickingLedger,
  } from "@/api/productionManagement/productionOrder.js";
  const stripTrailingZeros = val => {
    const str = String(val ?? "");
    if (str.includes(".")) {
      return parseFloat(str).toString();
    }
    return str;
  };
  const props = defineProps({
    modelValue: { type: Boolean, default: false },
    orderRow: { type: Object, default: null },
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -2,7 +2,7 @@
  <div>
    <el-dialog v-model="dialogVisible"
               title="领料台账"
               width="1200px"
               width="1350px"
               @close="handleClose">
      <div class="material-toolbar">
        <el-button type="primary"
@@ -13,7 +13,7 @@
                border
                row-key="tempId">
        <el-table-column label="工序名称"
                         min-width="140">
                         min-width="80">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
            <el-select v-else
@@ -31,7 +31,7 @@
          </template>
        </el-table-column>
        <el-table-column label="原料名称"
                         min-width="140">
                         min-width="120">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
            <el-button v-else
@@ -43,7 +43,7 @@
          </template>
        </el-table-column>
        <el-table-column label="原料型号"
                         min-width="140">
                         min-width="120">
          <template #default="{ row }">
            {{ row.materialModel || "-" }}
          </template>
@@ -66,21 +66,22 @@
          </template>
        </el-table-column>
        <el-table-column label="需求数量"
                         min-width="120">
                         min-width="100">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.demandedQuantity ?? "-" }}</span>
            <span v-if="row.bom === true">{{ stripTrailingZeros(row.demandedQuantity) ?? "-" }}</span>
            <el-input-number v-else
                             v-model="row.demandedQuantity"
                             :min="0"
                             :precision="3"
                             :step="1"
                             controls-position="right"
                             :formatter="value => stripTrailingZeros(value)"
                             :parser="value => parseFloat(value) || 0"
                             style="width: 100%;"
                             @change="val => handleRequiredQtyChange(row, val)" />
          </template>
        </el-table-column>
        <el-table-column label="计量单位"
                         width="100">
                         width="80">
          <template #default="{ row }">
            {{ row.unit || "-" }}
          </template>
@@ -90,9 +91,10 @@
          <template #default="{ row }">
            <el-input-number v-model="row.pickQty"
                             :min="0"
                             :precision="3"
                             :step="1"
                             controls-position="right"
                             :formatter="value => stripTrailingZeros(value)"
                             :parser="value => parseFloat(value) || 0"
                             style="width: 100%;" />
          </template>
        </el-table-column>
@@ -187,7 +189,20 @@
  const currentMaterialSelectRowIndex = ref(-1);
  let materialTempId = 0;
  const createMaterialRow = (row = {}) => ({
  const getDefaultBatchNo = batchNoList => {
    if (!Array.isArray(batchNoList) || batchNoList.length === 0) return [];
    return [batchNoList[0]];
  };
  const createMaterialRow = (row = {}) => {
    const batchNoList = Array.isArray(row.batchNoList) ? row.batchNoList : [];
    const batchNo = row.batchNo
      ? typeof row.batchNo === "string"
        ? row.batchNo.split(",")
        : row.batchNo
      : getDefaultBatchNo(batchNoList);
    return {
    tempId: row.id || `temp_${++materialTempId}`,
    id: row.id,
    processId: row.processId || row.technologyOperationId,
@@ -199,14 +214,11 @@
    materialModel: row.materialModel || row.model || "",
    demandedQuantity: Number(row.requiredQty ?? row.demandedQuantity ?? 0),
    unit: row.unit || "",
    pickQty: Number(row.pickQty ?? row.pickQuantity ?? 0),
    batchNo: row.batchNo
      ? typeof row.batchNo === "string"
        ? row.batchNo.split(",")
        : row.batchNo
      : [],
    batchNoList: row.batchNoList || [],
  });
      pickQty: Number(row.pickQty ?? row.pickQuantity ?? row.requiredQty ?? row.demandedQuantity ?? 0),
      batchNo,
      batchNoList,
    };
  };
  const getProcessOptions = async () => {
    if (!props.orderRow?.id) return;
@@ -270,6 +282,14 @@
    }
  );
  const stripTrailingZeros = val => {
    const str = String(val);
    if (str.includes(".")) {
      return parseFloat(str).toString();
    }
    return str;
  };
  const handleClose = () => {
    materialTableData.value = [];
    currentMaterialSelectRowIndex.value = -1;
@@ -319,7 +339,8 @@
      product.materialName || product.productName || product.name || "";
    row.materialModel = product.materialModel || product.model || "";
    row.unit = product.unit || product.measureUnit || "";
    row.batchNoList = product.batchNoList;
    row.batchNoList = Array.isArray(product.batchNoList) ? product.batchNoList : [];
    row.batchNo = getDefaultBatchNo(row.batchNoList);
    currentMaterialSelectRowIndex.value = -1;
    materialProductDialogVisible.value = false;
  };
src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
@@ -20,23 +20,33 @@
                       prop="unit"
                       width="100" />
      <el-table-column label="需求数量"
                       prop="demandedQuantity"
                       width="100" />
                       min-width="100">
        <template #default="{ row }">
          {{ stripTrailingZeros(row.demandedQuantity) }}
        </template>
      </el-table-column>
      <el-table-column label="领用数量"
                       prop="pickQuantity"
                       width="100" />
                       min-width="100">
        <template #default="{ row }">
          {{ stripTrailingZeros(row.pickQuantity) }}
        </template>
      </el-table-column>
      <el-table-column label="已补数量"
                       prop="feedingQty"
                       width="100" />
                       min-width="100">
        <template #default="{ row }">
          {{ stripTrailingZeros(row.feedingQty) }}
        </template>
      </el-table-column>
      <el-table-column label="补料数量"
                       min-width="150">
        <template #default="{ row }">
          <el-input-number v-model="row.newSupplementQty"
                           :min="0"
                           :precision="3"
                           :step="1"
                           controls-position="right"
                           placeholder="输入补料数量"
                           :formatter="value => stripTrailingZeros(value)"
                           :parser="value => parseFloat(value) || 0"
                           style="width: 100%;" />
        </template>
      </el-table-column>
@@ -69,6 +79,14 @@
    updateMaterialPickingLedger,
  } from "@/api/productionManagement/productionOrder.js";
  const stripTrailingZeros = val => {
    const str = String(val ?? "");
    if (str.includes(".")) {
      return parseFloat(str).toString();
    }
    return str;
  };
  const props = defineProps({
    modelValue: { type: Boolean, default: false },
    orderRow: { type: Object, default: null },
src/views/productionManagement/productionOrder/index.vue
@@ -8,7 +8,7 @@
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
@@ -16,7 +16,7 @@
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="规格:">
@@ -24,13 +24,13 @@
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="状态:">
          <el-select v-model="searchForm.status"
                     placeholder="请选择"
                     style="width: 160px"
                     style="width: 160px;"
                     @change="handleQuery">
            <el-option label="待开始"
                       value="1" />
@@ -38,8 +38,8 @@
                       value="2" />
            <el-option label="已完成"
                       value="3" />
            <!--            <el-option label="已取消"-->
            <!--                       value="4" />-->
            <el-option label="已取消"
                       value="4" />
            <el-option label="已结束"
                       value="5" />
          </el-select>
@@ -67,17 +67,13 @@
                :tableLoading="tableLoading"
                :row-class-name="tableRowClassName"
                :isSelection="true"
                :selectable="(row) => !row.endOrder"
                :selectable="row => !row.endOrder"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #completionStatus="{ row }">
          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
                       :status="
              toProgressPercentage(row?.completionStatus) >= 100
                ? 'success'
                : ''
            " />
                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
        </template>
        <template #processRouteStatus="{ row }">
          <div v-if="row.processRouteStatus && row.processRouteStatus.length"
@@ -89,14 +85,7 @@
                <div class="step-circle"
                     :class="{ 'is-completed': item.percentage >= 100 }">
                  <span class="step-percentage"
                        :style="{
                      color:
                        item.percentage >= 70
                          ? item.percentage >= 100
                            ? '#67c23a'
                            : '#f56c6c'
                          : '#000',
                    }">{{ item.percentage }}%</span>
                        :style="{ color: item.percentage >= 70 ? item.percentage >= 100 ? '#67c23a' : '#f56c6c' : '#000' }">{{ item.percentage }}%</span>
                </div>
                <div class="step-name">{{ item.name }}</div>
              </div>
@@ -115,7 +104,7 @@
        <el-form-item label="工艺路线">
          <el-select v-model="bindForm.routeId"
                     placeholder="请选择工艺路线"
                     style="width: 100%"
                     style="width: 100%;"
                     :loading="bindRouteLoading">
            <el-option v-for="item in routeOptions"
                       :key="item.id"
@@ -136,23 +125,18 @@
    <!-- æ¥æºæ•°æ®å¼¹çª— -->
    <el-dialog v-model="sourceDataDialogVisible"
               title="来源数据"
               width="1200px"
               top="5vh"
               class="source-data-dialog"
               append-to-body>
               width="1200px">
      <div v-if="sourceRowData"
           class="applyno-summary1">
        <div class="summary-item">
          <span class="summary-label">产品名称:</span>
          <span class="summary-value">
            <el-tag type="primary">{{
              sourceRowData.productName || "-"
            }}</el-tag>
            <el-tag type="primary">{{ sourceRowData.productName || '-' }}</el-tag>
          </span>
        </div>
        <div class="summary-item">
          <span class="summary-label">规格:</span>
          <span class="summary-value">{{ sourceRowData.model || "-" }}</span>
          <span class="summary-value">{{ sourceRowData.model || '-' }}</span>
        </div>
        <div class="summary-item">
          <span class="summary-label">订单需求数量:</span>
@@ -168,49 +152,39 @@
              <div class="info-grid">
                <div class="info-item">
                  <div class="info-label">计划号</div>
                  <div class="info-value">{{ item.mpsNo || "-" }}</div>
                  <div class="info-value">{{ item.mpsNo || '-' }}</div>
                </div>
                <div class="info-item">
                  <div class="info-label">数据来源</div>
                  <div class="info-value">
                    <el-tag :type="item.source === '销售' ? 'primary' : 'warning'">
                      {{ item.source || "未知" }}
                      {{ item.source || '未知' }}
                    </el-tag>
                  </div>
                </div>
                <div class="info-item">
                  <div class="info-label">合同号</div>
                  <div class="info-value">
                    {{ item.salesContractNo || "-" }}
                  </div>
                  <div class="info-value">{{ item.salesContractNo || '-' }}</div>
                </div>
                <div class="info-item">
                  <div class="info-label">客户名称</div>
                  <div class="info-value">{{ item.customerName || "-" }}</div>
                  <div class="info-value">{{ item.customerName || '-' }}</div>
                </div>
                <div class="info-item">
                  <div class="info-label">项目名称</div>
                  <div class="info-value">{{ item.projectName || "-" }}</div>
                  <div class="info-value">{{ item.projectName || '-' }}</div>
                </div>
                <div class="info-item">
                  <div class="info-label">计划需求数量</div>
                  <div class="info-value">
                    {{ item.qtyRequired || 0 }} {{ item.unit || "" }}
                  </div>
                  <div class="info-value">{{ item.qtyRequired || 0 }} {{ item.unit || '' }}</div>
                </div>
                <div class="info-item">
                  <div class="info-label">单位</div>
                  <div class="info-value">{{ item.unit || "-" }}</div>
                  <div class="info-value">{{ item.unit || '-' }}</div>
                </div>
                <div class="info-item">
                  <div class="info-label">需求日期</div>
                  <div class="info-value">
                    {{
                      item.requiredDate
                        ? dayjs(item.requiredDate).format("YYYY-MM-DD")
                        : "-"
                    }}
                  </div>
                  <div class="info-value">{{ item.requiredDate ? dayjs(item.requiredDate).format('YYYY-MM-DD') : '-' }}</div>
                </div>
              </div>
            </div>
@@ -220,7 +194,7 @@
    </el-dialog>
    <MaterialLedgerDialog v-model="materialDialogVisible"
                          :order-row="currentMaterialOrder"
                          @saved="getList" />
                           />
    <MaterialDetailDialog v-model="materialDetailDialogVisible"
                          :order-row="currentMaterialDetailOrder"
                          @confirmed="getList" />
@@ -261,7 +235,6 @@
    getProductOrderSource,
    updateProductOrder,
  } from "@/api/productionManagement/productionOrder.js";
  import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js";
  import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
  import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue";
  import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue";
@@ -404,8 +377,7 @@
        {
          name: "绑定工艺路线",
          type: "text",
          showHide: row =>
            !row.processRouteCode && !row.endOrder && row.status !== 3,
          showHide: row => !row.processRouteCode && !row.endOrder,
          clickFun: row => {
            openBindRouteDialog(row, "add");
          },
@@ -413,8 +385,7 @@
        {
          name: "更换工艺路线",
          type: "text",
          showHide: row =>
            row.processRouteCode && !row.endOrder && row.status !== 3,
          showHide: row => row.processRouteCode && !row.endOrder,
          clickFun: row => {
            openBindRouteDialog(row, "change");
          },
@@ -515,7 +486,7 @@
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return parseFloat(n.toFixed(2));
    return Math.round(n);
  };
  // 30/50/80/100 åˆ†æ®µé¢œè‰²ï¼šçº¢/橙/蓝/绿
@@ -692,37 +663,11 @@
  };
  const getList = () => {
    tableLoading.value = true;
    // æž„造一个新的对象,不包含entryDate字段
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    productOrderListPage(params)
      .then(async res => {
        const records = res.data.records || [];
        // ä¸ºæ¯ä¸ªè®¢å•查询对应的工序进度数据
        const processPromises = records.map(async item => {
          if (item.npsNo) {
            try {
              const workOrderRes = await productWorkOrderPage({
                npsNo: item.npsNo,
                size: 100,
              });
              const workOrders = workOrderRes.data.records || [];
              // æŒ‰ç…§å·¥åºé¡ºåºæŽ’序(如果有顺序字段,假设为 orderNum æˆ–按返回顺序)
              // è½¬æ¢ä¸º processRouteStatus æ ¼å¼
              const processRouteStatus = workOrders.map(wo => ({
                name: wo.operationName || "未知工序",
                percentage: wo.completionStatus > 100 ? 100 : wo.completionStatus,
              }));
              return { ...item, processRouteStatus };
            } catch (error) {
              console.error(`获取工单 ${item.npsNo} è¿›åº¦å¤±è´¥:`, error);
              return { ...item, processRouteStatus: [] };
            }
          }
          return { ...item, processRouteStatus: [] };
        });
        tableData.value = await Promise.all(processPromises);
      .then(res => {
        tableData.value = res.data.records || [];
        page.total = res.data.total;
        tableLoading.value = false;
      })
@@ -754,7 +699,7 @@
          technologyRoutingId: data.technologyRoutingId,
          orderId,
          type: "order",
          editable: !row.endOrder && row.status !== 3,
          editable: !row.endOrder,
        },
      });
    } catch (e) {
@@ -974,19 +919,13 @@
  .source-table-container {
    margin-top: 20px;
    flex: 1;
    min-height: 0;
    max-height: 500px;
    overflow: auto;
  }
  .source-data-cards-container {
    display: flex;
    flex-direction: column;
    gap: 16px;
    flex: 1;
    min-height: 0;
    max-height: none;
    max-height: 500px;
    overflow-y: auto;
    padding: 10px;
    background-color: #f5f7fa;
@@ -1054,22 +993,6 @@
        color: #303133;
        font-weight: 500;
      }
    }
  }
  .source-data-dialog {
    .el-dialog {
      display: flex;
      flex-direction: column;
      max-height: 90vh;
    }
    .el-dialog__body {
      flex: 1;
      min-height: 0;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }
  }
</style>
src/views/productionManagement/productionReporting/index.vue
@@ -163,6 +163,7 @@
  import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
  import dayjs from "dayjs";
  const data = reactive({
    searchForm: {
@@ -179,27 +180,27 @@
    {
      label: "报工单号",
      prop: "productNo",
      width: 120,
      width: 140,
    },
    {
      label: "报工人员",
      prop: "nickName",
      width: 120,
    },
    {
      label: "工时(h)",
      width: 100,
      prop: "workHour",
    },
    // {
    //   label: "工时(h)",
    //   width: 100,
    //   prop: "workHour",
    // },
    {
      label: "工序",
      prop: "process",
      width: 120,
      width: 100,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: 120,
      width: 140,
    },
    {
      label: "销售合同号",
@@ -235,7 +236,8 @@
    {
      label: "创建时间",
      prop: "createTime",
      width: 120,
      width: 160,
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
    },
    {
      dataType: "action",
src/views/productionManagement/productionTraceability/index.vue
@@ -35,7 +35,7 @@
                         border>
          <el-descriptions-item label="生产订单号">{{ rowData.productionOrderDto?.npsNo || '-' }}</el-descriptions-item>
          <el-descriptions-item label="产品名称">{{ rowData.productionOrderDto?.productName || '-' }}</el-descriptions-item>
          <el-descriptions-item label="规格型号">{{ rowData.productionOrderDto?.model || '-' }}</el-descriptions-item>
          <el-descriptions-item label="产品规格">{{ rowData.productionOrderDto?.model || '-' }}</el-descriptions-item>
          <!-- <el-descriptions-item label="物料编码">{{ rowData.productionOrderDto?.materialCode || '-' }}</el-descriptions-item> -->
          <el-descriptions-item label="计划数量">{{ rowData.productionOrderDto?.quantity || 0 }} <span class="unit">{{ rowData.productionOrderDto?.unit || '-' }}</span></el-descriptions-item>
          <el-descriptions-item label="当前状态">
@@ -44,7 +44,7 @@
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="客户名称">{{ rowData.productionOrderDto?.customerName || '-' }}</el-descriptions-item>
          <el-descriptions-item label="开始日期">{{ parseTime(rowData.productionOrderDto?.startTime) || '-' }}</el-descriptions-item>
          <el-descriptions-item label="开始日期">{{ parseTime(rowData.productionOrderDto?.startTime,'{y}-{m}-{d}') || '-' }}</el-descriptions-item>
          <el-descriptions-item label="完成进度">
            <el-progress :percentage="rowData.productionOrderDto?.completionStatus>=100?100:rowData.productionOrderDto?.completionStatus"
                         :color="customColors(rowData.productionOrderDto?.completionStatus)"
@@ -132,8 +132,8 @@
            <el-descriptions-item label="生产工单号">{{ detailData.workOrder.workOrderNo || '-' }}</el-descriptions-item>
            <el-descriptions-item label="计划数量">{{ detailData.workOrder.planQuantity || 0 }}</el-descriptions-item>
            <el-descriptions-item label="完成数量">{{ detailData.workOrder.completeQuantity || 0 }}</el-descriptions-item>
            <el-descriptions-item label="实际开始时间">{{ parseTime(detailData.workOrder.actualStartTime) || '-' }}</el-descriptions-item>
            <el-descriptions-item label="实际结束时间">{{ parseTime(detailData.workOrder.actualEndTime) || '-' }}</el-descriptions-item>
            <el-descriptions-item label="实际开始时间">{{ parseTime(detailData.workOrder.actualStartTime, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
            <el-descriptions-item label="实际结束时间">{{ parseTime(detailData.workOrder.actualEndTime, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
            <!-- <el-descriptions-item label="完成进度">
              <el-progress :percentage="detailData.workOrder.completionStatus >= 100 ? 100 : (detailData.workOrder.completionStatus || 0)"
                           :color="customColors(detailData.workOrder.completionStatus)"
@@ -175,7 +175,7 @@
              <template #default="{ row }">
                <el-button type="primary"
                           link
                           @click="showInput(row.id)">查看投入</el-button>
                           @click="showInput(row.productionProductMainId)">查看投入</el-button>
                <el-button type="primary"
                           link
                           @click="showParamDetail(row.productionOperationParamList)">参数详情</el-button>
@@ -230,19 +230,20 @@
             :key="record.id"
             class="quality-record-block">
          <div class="detail-section">
            <h3 class="section-title">检测记录 {{ index + 1 }} - {{ parseTime(record.createTime) }}</h3>
            <h3 class="section-title">检测记录 {{ index + 1 }} - {{ parseTime(record.createTime, '{y}-{m}-{d}') }}</h3>
            <el-descriptions :column="3"
                             border>
              <el-descriptions-item label="检测日期">{{ parseTime(record.createTime) || '-' }}</el-descriptions-item>
              <el-descriptions-item label="检测日期">{{ parseTime(record.createTime, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
              <el-descriptions-item label="报工单号">{{ record.reportNo || '-' }}</el-descriptions-item>
              <el-descriptions-item label="检验员">{{ record.userName || '-' }}</el-descriptions-item>
              <el-descriptions-item label="报工人员">{{ record.userName || '-' }}</el-descriptions-item>
              <el-descriptions-item label="产品名称">{{ record.productName || '-' }}</el-descriptions-item>
              <el-descriptions-item label="规格型号">{{ record.model || '-' }}</el-descriptions-item>
              <el-descriptions-item label="数量">{{ record.quantity || 0 }} {{ record.unit || '-' }}</el-descriptions-item>
              <el-descriptions-item label="检测单位">{{ record.checkCompany || '-' }}</el-descriptions-item>
              <el-descriptions-item label="检测结果">
                <el-tag :type="record.checkResult === '合格' ? 'success' : 'danger'">
                  {{ record.checkResult || '待检测' }}
              <el-descriptions-item label="检验员">{{ record.checkName || '-' }}</el-descriptions-item>
              <el-descriptions-item label="合格率">
                <el-tag :type="getPassRateTagType(record)">
                  {{ getPassRateDisplay(record) }}
                </el-tag>
              </el-descriptions-item>
            </el-descriptions>
@@ -360,6 +361,28 @@
    return "#67c23a";
  };
  const getPassRate = (record) => {
    const qualified = Number(record.qualifiedQuantity) || 0;
    const unqualified = Number(record.unqualifiedQuantity) || 0;
    const total = qualified + unqualified;
    if (total === 0) return null;
    return Number(((qualified / total) * 100).toFixed(2));
  };
  const getPassRateDisplay = (record) => {
    const rate = getPassRate(record);
    if (rate === null) return '—';
    return `${rate}%`;
  };
  const getPassRateTagType = (record) => {
    const rate = getPassRate(record);
    if (rate === null) return 'info';
    if (rate >= 100) return 'success';
    if (rate >= 75) return 'primary';
    return 'danger';
  };
  // æ¨¡æ‹Ÿæœç´¢æ–¹æ³•
  const handleNpsNoSearch = async query => {
    npsNoLoading.value = true;
src/views/productionManagement/workOrder/index.vue
@@ -93,7 +93,7 @@
              <span class="info-value">{{ transferCardRowData.productName }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">规格型号</span>
              <span class="info-label">产品规格</span>
              <span class="info-value">{{ transferCardRowData.model }}</span>
            </div>
            <!-- <div class="info-item">
@@ -475,7 +475,7 @@
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return parseFloat(n.toFixed(2));
    return Math.round(n);
  };
  const progressColor = percentage => {
    const p = toProgressPercentage(percentage);
src/views/productionManagement/workOrderEdit/index.vue
@@ -297,7 +297,7 @@
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return parseFloat(n.toFixed(2));
    return Math.round(n);
  };
  const progressColor = percentage => {
src/views/productionManagement/workOrderManagement/index.vue
@@ -57,7 +57,7 @@
              <span class="info-value">{{ transferCardRowData.productName }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">规格型号</span>
              <span class="info-label">产品规格</span>
              <span class="info-value">{{ transferCardRowData.model }}</span>
            </div>
            <div class="info-item">
@@ -133,7 +133,7 @@
          <el-input v-model.number="reportForm.quantity"
                    type="number"
                    min="0"
                    step="1"
                    step="0.0001"
                    style="width: 300px"
                    placeholder="请输入生产合格数量"
                    @input="handleQuantityInput" />
@@ -143,7 +143,7 @@
          <el-input v-model.number="reportForm.scrapQty"
                    type="number"
                    min="0"
                    step="1"
                    step="0.0001"
                    style="width: 300px"
                    placeholder="请输入报废数量"
                    @input="handleScrapQtyInput" />
@@ -245,6 +245,7 @@
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     :loading="reportLoading"
                     @click="handleReport">确定</el-button>
          <el-button @click="reportDialogVisible = false">取消</el-button>
        </span>
@@ -363,12 +364,12 @@
      dataType: "action",
      fixed: "right",
      operation: [
        {
        /*{
          name: "流转卡",
          clickFun: row => {
            downloadAndPrintWorkOrder(row);
          },
        },
        },*/
        {
          name: "附件",
          clickFun: row => {
@@ -414,6 +415,7 @@
  const transferCardQrUrl = ref("");
  const transferCardRowData = ref(null);
  const reportDialogVisible = ref(false);
  const reportLoading = ref(false);
  const fileDialogVisible = ref(false);
  const currentWorkOrderId = ref(null);
  const reportFormRef = ref(null);
@@ -421,7 +423,7 @@
  const reportForm = reactive({
    planQuantity: 0,
    quantity: null,
    scrapQty: null,
    scrapQty: 0,
    userName: "",
    workOrderId: "",
    reportWork: "",
@@ -445,8 +447,7 @@
      return;
    }
    const num = Number(value);
    // æ•´æ•°ä¸”大于等于1
    if (isNaN(num) || !Number.isInteger(num) || num < 0) {
    if (isNaN(num) || num < 0) {
      callback(new Error("生产合格数量必须大于等于0"));
      return;
    }
@@ -460,8 +461,7 @@
      return;
    }
    const num = Number(value);
    // æ•´æ•°ä¸”大于等于0
    if (isNaN(num) || !Number.isInteger(num) || num < 0) {
    if (isNaN(num) || num < 0) {
      callback(new Error("报废数量必须大于等于0"));
      return;
    }
@@ -484,20 +484,8 @@
    if (isNaN(num)) {
      return;
    }
    // å¦‚果小于1,清除
    if (num < 0) {
      reportForm.quantity = null;
      return;
    }
    // å¦‚果是小数取整数部分
    if (!Number.isInteger(num)) {
      const intValue = Math.floor(num);
      // å¦‚果取整后小于1,清除
      if (intValue < 0) {
        reportForm.quantity = null;
        return;
      }
      reportForm.quantity = intValue;
      return;
    }
    reportForm.quantity = num;
@@ -510,21 +498,13 @@
      return;
    }
    const num = Number(value);
    // å¦‚果是NaN,保持原值
    if (isNaN(num)) {
      return;
    }
    // å¦‚果是负数,清除输入
    if (num < 0) {
      reportForm.scrapQty = null;
      return;
    }
    // å¦‚果是小数,取整数部分
    if (!Number.isInteger(num)) {
      reportForm.scrapQty = Math.floor(num);
      return;
    }
    // æœ‰æ•ˆçš„非负整数(包括0)
    reportForm.scrapQty = num;
  };
@@ -549,7 +529,13 @@
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return parseFloat(n.toFixed(2));
    return Math.round(n);
  };
  const subtractQuantity = (minuend, subtrahend, precision = 4) => {
    const factor = 10 ** precision;
    const a = Math.round((Number(minuend) || 0) * factor);
    const b = Math.round((Number(subtrahend) || 0) * factor);
    return Math.max(0, (a - b) / factor);
  };
  const progressColor = percentage => {
    const p = toProgressPercentage(percentage);
@@ -673,20 +659,18 @@
      }
    }
    currentReportRowData.value = row;
    const planQuantity = Number(row.planQuantity || 0);
    const completeQuantity = Number(row.completeQuantity || 0);
    const remainingQuantity = Math.max(0, planQuantity - completeQuantity);
    reportForm.planQuantity = remainingQuantity;
    reportForm.planQuantity = subtractQuantity(
      row.planQuantity,
      row.completeQuantity
    );
    reportForm.quantity =
      row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    reportForm.workOrderId = row.id;
    reportForm.reportWork = row.reportWork;
    reportForm.productMainId = row.productMainId;
    reportForm.scrapQty =
      row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
    reportForm.productionOrderRoutingOperationId =
      row.productionOrderRoutingOperationId;
    reportForm.scrapQty = 0;
    reportForm.productionOrderRoutingOperationId = row.productionOrderRoutingOperationId;
    reportForm.productionOrderId = row.productionOrderId;
    if (row.type == 0) {
      reportForm.workHour = row.workHour || 0;
@@ -796,6 +780,7 @@
        productionOperationParamList: productionOperationParamList,
      };
      reportLoading.value = true;
      addProductMain(submitParams)
        .then(res => {
          proxy.$modal.msgSuccess("报工成功");
@@ -806,6 +791,9 @@
          ElMessageBox.alert("报工失败", "提示", {
            confirmButtonText: "确定",
          });
        })
        .finally(() => {
          reportLoading.value = false;
        });
    });
  };
src/views/productionPlan/productionPlan/index.vue
@@ -56,7 +56,7 @@
                    style="width: 160px;"
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="规格型号:"
        <el-form-item label="产品规格:"
                      prop="model">
          <el-input v-model="searchForm.model"
                    placeholder="请输入"
@@ -121,7 +121,7 @@
        </el-row>
        <el-row :gutter="20">
          <el-col>
            <el-form-item label="规格型号">
            <el-form-item label="产品规格">
              <div class="info-display">{{ mergeForm.model || '-' }}</div>
            </el-form-item>
          </el-col>
@@ -139,16 +139,11 @@
                           @change="onBlur"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="创建时间">
          <el-date-picker v-model="mergeCreateTimeDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     :loading="mergeSubmitLoading"
                     @click="handleMergeSubmit">确定下发</el-button>
          <el-button @click="isShowNewModal = false">取消</el-button>
        </span>
@@ -179,7 +174,7 @@
                      prop="mpsNo">
          <el-input v-model="form.mpsNo"
                    disabled
                    placeholder="保存后自动生成" />
                    placeholder="新增后自动生成" />
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productId">
@@ -192,7 +187,7 @@
                          @change="handleProductChange"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="规格型号"
        <el-form-item label="产品规格"
                      prop="productModelId">
          <el-select v-model="form.productModelId"
                     @change="handleChangeSpecification"
@@ -232,14 +227,6 @@
                          value-format="YYYY-MM-DD"
                          style="width: 100%"
                          placeholder="请选择承诺日期" />
        </el-form-item>
        <el-form-item label="创建时间"
                      prop="createTime">
          <el-date-picker v-model="formCreateTimeDate"
                          type="date"
                          value-format="YYYY-MM-DD"
                          style="width: 100%"
                          placeholder="请选择创建时间" />
        </el-form-item>
        <el-form-item label="备注"
                      prop="remark">
@@ -335,7 +322,7 @@
      },
    },
    {
      label: "规格型号",
      label: "产品规格",
      prop: "model",
      width: "150px",
      className: "spec-cell",
@@ -444,7 +431,6 @@
              Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
            mergeForm.planCompleteTime = row.requiredDate || "";
            mergeForm.productId = row.productId || "";
            mergeForm.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
            mergeForm.ids = [row.id];
            sumAssignedQuantity.value =
              Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
@@ -456,7 +442,7 @@
          type: "danger",
          link: true,
          showHide: row => {
            return row.status == 0 && row.source != "销售";
            return row.status == 0;
          },
          clickFun: row => {
            handleDelete(row);
@@ -481,6 +467,7 @@
  // åˆå¹¶ä¸‹å‘弹窗控制
  const isShowNewModal = ref(false);
  const mergeSubmitLoading = ref(false);
  // åˆå¹¶ä¸‹å‘表单数据
  const mergeForm = reactive({
    productName: "",
@@ -488,16 +475,6 @@
    totalAssignedQuantity: 0,
    planCompleteTime: "",
    productId: "",
    createTime: "",
  });
  const mergeCreateTimeDate = computed({
    get: () =>
      mergeForm.createTime ? String(mergeForm.createTime).split(" ")[0] : "",
    set: value => {
      mergeForm.createTime = value
        ? `${value} ${dayjs().format("HH:mm:ss")}`
        : "";
    },
  });
  // å¯¼å…¥ç›¸å…³
@@ -528,18 +505,11 @@
    requiredDate: "",
    promisedDeliveryDate: "",
    remark: "",
    createTime: "",
  });
  const formCreateTimeDate = computed({
    get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
    set: value => {
      form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
    },
  });
  const rules = reactive({
    productId: [{ required: true, message: "请选择产品", trigger: "change" }],
    productModelId: [
      { required: true, message: "请选择规格型号", trigger: "change" },
      { required: true, message: "请选择产品规格", trigger: "change" },
    ],
    qtyRequired: [{ required: true, message: "请输入数量", trigger: "blur" }],
    requiredDate: [
@@ -718,17 +688,17 @@
      });
  };
  // é€‰ä¸­çš„规格型号ID
  // é€‰ä¸­çš„产品规格ID
  const selectedProductModelId = ref("");
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
    // å¦‚果有选中的行,记录第一个选中行的规格型号ID
    // å¦‚果有选中的行,记录第一个选中行的产品规格ID
    if (selection.length > 0) {
      selectedProductModelId.value = selection[0].productModelId;
    } else {
      // å¦‚果没有选中的行,清空规格型号ID
      // å¦‚果没有选中的行,清空产品规格ID
      selectedProductModelId.value = "";
    }
  };
@@ -749,7 +719,7 @@
    if (!selectedProductModelId.value) {
      return true;
    }
    // å¦‚果有选中的行,只有规格型号ID相同的行才可选择
    // å¦‚果有选中的行,只有产品规格ID相同的行才可选择
    return row.productModelId === selectedProductModelId.value;
  };
  // æ‹‰å–数据按钮操作
@@ -778,7 +748,6 @@
    mergeForm.totalAssignedQuantity = totalAssignedQuantity;
    mergeForm.planCompleteTime = firstRow.requiredDate || "";
    mergeForm.productId = firstRow.productId || "";
    mergeForm.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    mergeForm.ids = selectedRows.value.map(row => row.id);
    // æ‰“开弹窗
@@ -811,6 +780,7 @@
    const payload = {
      ...mergeForm,
    };
    mergeSubmitLoading.value = true;
    productionPlanCombine(payload)
      .then(res => {
        if (res.code === 200) {
@@ -826,6 +796,9 @@
      .catch(err => {
        console.error("合并下发异常:", err);
        ElMessage.error("系统异常,合并下发失败");
      })
      .finally(() => {
        mergeSubmitLoading.value = false;
      });
    // å¯ä»¥é€‰æ‹©åˆ·æ–°åˆ—表或其他操作
  };
@@ -922,7 +895,6 @@
      requiredDate: "",
      promisedDeliveryDate: "",
      remark: "",
      createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    });
    dialogVisible.value = true;
    fetchProductOptions();
@@ -943,7 +915,6 @@
      requiredDate: row.requiredDate || "",
      promisedDeliveryDate: row.promisedDeliveryDate || "",
      remark: row.remark || "",
      createTime: row.createTime || "",
    });
    dialogVisible.value = true;
    fetchProductOptions();
src/views/qualityManagement/finalInspection/components/filesDia.vue
@@ -13,7 +13,7 @@
            :action="uploadUrl"
            :on-success="handleUploadSuccess"
            :on-error="handleUploadError"
            name="file"
            name="files"
            :show-file-list="false"
            :headers="headers"
            style="display: inline;margin-right: 10px"
@@ -40,11 +40,12 @@
        </div>
      </template>
    </el-dialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, getCurrentInstance} from "vue";
import {Search} from "@element-plus/icons-vue";
import {
  qualityInspectParamDel,
@@ -53,17 +54,20 @@
} from "@/api/qualityManagement/qualityInspectParam.js";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import filePreview from '@/components/filePreview/index.vue'
import {
  qualityInspectFileAdd,
  qualityInspectFileDel,
  qualityInspectFileListPage
} from "@/api/qualityManagement/qualityInspectFile.js";
  attachmentList,
  createAttachment,
  deleteAttachment
} from "@/api/basicData/storageAttachment.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const currentId = ref('')
const selectedRows = ref([]);
const filePreviewRef = ref()
const tableColumn = ref([
  {
    label: "文件名称",
@@ -80,6 +84,13 @@
        clickFun: (row) => {
          downLoadFile(row);
        },
      },
      {
        name: "预览",
        type: "text",
        clickFun: (row) => {
          lookFile(row);
        },
      }
    ],
  },
@@ -95,7 +106,7 @@
const headers = ref({
  Authorization: "Bearer " + getToken(),
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // ä¸Šä¼ çš„图片服务器地址
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // ä¸Šä¼ çš„æœåŠ¡å™¨åœ°å€
// æ‰“开弹框
const openDialog = (row) => {
@@ -109,9 +120,12 @@
    getList();
};
const getList = () => {
  qualityInspectFileListPage({inspectId: currentId.value, ...page}).then(res => {
    tableData.value = res.data.records;
        page.total = res.data.total;
  attachmentList({recordId: currentId.value, recordType: 'quality_inspect'}).then(res => {
    tableData.value = (res.data || []).map(item => ({
      ...item,
      name: item.originalFilename || item.name
    }));
    page.total = res.data ? res.data.length : 0;
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
@@ -127,24 +141,42 @@
  dialogFormVisible.value = false;
  emit('close')
};
let pendingFiles = [];
let uploadTimer = null;
// ä¸Šä¼ æˆåŠŸå¤„ç†
function handleUploadSuccess(res, file) {
  // å¦‚果上传成功
  if (res.code == 200) {
    const fileRow = {}
    fileRow.name = res.data.originalName
    fileRow.url = res.data.tempPath
    uploadFile(fileRow)
  if (res.code == 200 && res.data && res.data.length > 0) {
    const newFiles = res.data.map(item => ({
      ...item,
      name: item.originalFilename || item.name
    }));
    pendingFiles = [...pendingFiles, ...newFiles];
    // é˜²æŠ–处理,避免多文件同时上传导致列表被覆盖
    if (uploadTimer) clearTimeout(uploadTimer);
    uploadTimer = setTimeout(() => {
      const mergedFiles = [...(tableData.value || []), ...pendingFiles];
      const storageAttachmentDTO = {
        recordType: 'quality_inspect',
        recordId: currentId.value,
        application: "file",
        storageBlobDTOs: mergedFiles
      };
      createAttachment(storageAttachmentDTO).then(r => {
        proxy.$modal.msgSuccess("文件上传成功");
        getList()
      }).catch(() => {
        proxy.$modal.msgError("文件上传失败");
      }).finally(() => {
        pendingFiles = []; // é‡ç½®
      });
    }, 500);
  } else {
    proxy.$modal.msgError("文件上传失败");
  }
}
function uploadFile(file) {
  file.inspectId = currentId.value;
  qualityInspectFileAdd(file).then(res => {
    proxy.$modal.msgSuccess("文件上传成功");
    getList()
  })
}
// ä¸Šä¼ å¤±è´¥å¤„理
function handleUploadError() {
@@ -154,17 +186,17 @@
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
    ids = selectedRows.value.map((item) => item.storageAttachmentId);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    qualityInspectFileDel(ids).then((res) => {
    deleteAttachment(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
      getList();
    });
@@ -172,6 +204,11 @@
    proxy.$modal.msg("已取消");
  });
};
// é¢„览附件
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  openDialog,
});
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -1,86 +1,67 @@
<template>
  <div>
    <el-dialog v-model="dialogFormVisible"
    <el-dialog
        v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增出厂检验' : operationType === 'view' ? '查看出厂检验' : '编辑出厂检验'"
               width="70%"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        @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="productId">
              <el-tree-select v-model="form.productId"
            <el-form-item label="产品名称:" prop="productId">
              <el-tree-select
                  v-model="form.productId"
                              placeholder="请选择"
                              filterable
                              clearable
                              check-strictly
                              @change="getModels"
                              :data="productOptions"
                              :render-after-expand="false"
                              :disabled="isViewMode || operationType === 'edit'"
                              style="width: 100%" />
                  style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号:"
                          prop="productModelId">
              <el-select v-model="form.productModelId"
                         placeholder="请选择"
                         clearable
                         :disabled="isViewMode || operationType === 'edit'"
                         filterable
                         readonly
                         @change="handleChangeModel">
                <el-option v-for="item in modelOptions"
                           :key="item.id"
                           :label="item.model"
                           :value="item.id" />
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select v-model="form.productModelId" placeholder="请选择" clearable :disabled="isViewMode || operationType === 'edit'"
                         filterable readonly @change="handleChangeModel">
                <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="指标选择:"
                          prop="testStandardId">
              <el-select v-model="form.testStandardId"
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select
                v-model="form.testStandardId"
                         placeholder="请选择指标"
                         clearable
                         @change="handleTestStandardChange"
                         style="width: 100%"
                         :disabled="isViewMode">
                <el-option v-for="item in testStandardOptions"
                :disabled="isViewMode"
              >
                <el-option
                  v-for="item in testStandardOptions"
                           :key="item.id"
                           :label="item.standardName || item.standardNo"
                           :value="item.id" />
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:"
                          prop="unit">
              <el-input v-model="form.unit"
                        placeholder="请输入"
                        disabled />
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="form.unit" placeholder="请输入" disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:"
                          prop="quantity">
              <el-input-number :step="0.01"
                               :min="0"
                               style="width: 100%"
                               v-model="form.quantity"
                               placeholder="请输入"
                               clearable
                               :precision="2"
                               :disabled="isViewMode || processQuantityDisabled" />
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2" :disabled="isViewMode || processQuantityDisabled"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -116,79 +97,87 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检测单位:"
                          prop="checkCompany">
              <el-input v-model="form.checkCompany"
                        placeholder="请输入"
                        clearable
                        :disabled="isViewMode" />
            <el-form-item label="是否抽检:"
                          prop="isSampling">
              <el-radio-group v-model="form.isSampling"
                              class="sampling-radio-group"
                              :disabled="isViewMode"
                              @change="handleSamplingChange">
                <el-radio-button :label="1">是</el-radio-button>
                <el-radio-button :label="0">否</el-radio-button>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:"
                          prop="checkResult">
              <el-select v-model="form.checkResult"
                         :disabled="isViewMode">
                <el-option label="合格"
                           value="合格" />
                <el-option label="不合格"
                           value="不合格" />
                <el-option label="部分合格"
                           value="部分合格" />
              </el-select>
          <el-col :span="12"
                  v-if="form.isSampling === 1">
            <el-form-item label="抽检数量:"
                          prop="samplingQuantity">
              <el-input-number :step="0.01"
                               :min="0"
                               style="width: 100%"
                               v-model="form.samplingQuantity"
                               placeholder="请输入"
                               clearable
                               :precision="2"
                               :disabled="isViewMode" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验员:"
                          prop="checkName">
              <el-select v-model="form.checkName"
                         placeholder="请选择"
                         clearable
                         :disabled="isViewMode">
                <el-option v-for="item in userList"
                           :key="item.nickName"
                           :label="item.nickName"
            <el-form-item label="检测单位:" prop="checkCompany">
              <el-input v-model="form.checkCompany" placeholder="请输入" clearable :disabled="isViewMode"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="合格率:">
              <el-tag :type="passRateTagType">{{ passRateDisplayText }}</el-tag>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验员:" prop="checkName">
              <el-select v-model="form.checkName" placeholder="请选择" clearable :disabled="isViewMode">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                           :value="item.nickName" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测日期:"
                          prop="checkTime">
              <el-date-picker v-model="form.checkTime"
            <el-form-item label="检测日期:" prop="checkTime">
              <el-date-picker
                  v-model="form.checkTime"
                              type="date"
                              placeholder="请选择日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%"
                              :disabled="isViewMode" />
                  :disabled="isViewMode"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <PIMTable rowKey="id"
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :tableLoading="tableLoading"
                height="400">
                height="400"
            >
        <template #slot="{ row }">
          <el-input v-model="row.testValue"
                    clearable
                    :disabled="isViewMode" />
                    <el-input v-model="row.testValue" clearable :disabled="isViewMode"/>
        </template>
      </PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <template v-if="!isViewMode">
            <el-button type="primary"
                       @click="submitForm">确认</el-button>
            <el-button type="primary" @click="submitForm">确认</el-button>
            <el-button @click="closeDia">取消</el-button>
          </template>
          <el-button v-else
                     @click="closeDia">关闭</el-button>
          <el-button v-else @click="closeDia">关闭</el-button>
        </div>
      </template>
    </el-dialog>
@@ -196,28 +185,15 @@
</template>
<script setup>
  import {
    ref,
    reactive,
    toRefs,
    computed,
    getCurrentInstance,
    nextTick,
  } from "vue";
import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue";
  import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
  import {
    qualityInspectAdd,
    qualityInspectUpdate,
  } from "@/api/qualityManagement/rawMaterialInspection.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
  import { userListNoPage } from "@/api/system/user.js";
  import {
    qualityInspectDetailByProductId,
    getQualityTestStandardParamByTestStandardId,
  } from "@/api/qualityManagement/metricMaintenance.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
  import { qualityInspectParamInfo } from "@/api/qualityManagement/qualityInspectParam.js";
  const { proxy } = getCurrentInstance();
  const emit = defineEmits(["close"]);
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
  const dialogFormVisible = ref(false);
  const operationType = ref("");
@@ -236,7 +212,8 @@
      qualifiedQuantity: "",
      unqualifiedQuantity: "",
      checkCompany: "",
      checkResult: "",
    isSampling: 0,
    samplingQuantity: "",
    },
    rules: {
      checkTime: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -244,22 +221,50 @@
      checkName: [{ required: false, message: "请输入", trigger: "blur" }],
      productId: [{ required: true, message: "请输入", trigger: "blur" }],
      productModelId: [{ required: true, message: "请选择", trigger: "change" }],
      testStandardId: [
        { required: false, message: "请选择指标", trigger: "change" },
      ],
    testStandardId: [{ required: false, message: "请选择指标", trigger: "change" }],
      unit: [{ required: false, message: "请输入", trigger: "blur" }],
      quantity: [{ required: true, message: "请输入", trigger: "blur" }],
      qualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }],
      unqualifiedQuantity: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
    unqualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }],
      checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
      checkResult: [{ required: true, message: "请输入", trigger: "change" }],
    isSampling: [{ required: true, message: "请选择是否抽检", trigger: "change" }],
    samplingQuantity: [{ required: true, message: "请输入抽检数量", trigger: "blur" }],
    },
  });
  const { form, rules } = toRefs(data);
  // æ˜¯å¦ä¸ºæŸ¥çœ‹æ¨¡å¼
  const isViewMode = computed(() => operationType.value === "view");
const isViewMode = computed(() => operationType.value === 'view');
const passRateValue = computed(() => {
  const fromApi = form.value.passRate;
  if (fromApi != null && fromApi !== '') {
    const n = Number(fromApi);
    if (!Number.isNaN(n)) return n;
  }
  const qualified = Number(form.value.qualifiedQuantity) || 0;
  const unqualified = Number(form.value.unqualifiedQuantity) || 0;
  const total = qualified + unqualified;
  if (!total || total === 0) return null;
  return (qualified / total) * 100;
});
const passRateDisplayText = computed(() => {
  const params = passRateValue.value;
  if (params == null || params === '') return '—';
  const n = Number(params);
  if (Number.isNaN(n)) return '—';
  return `${n.toFixed(2)}%`;
});
const passRateTagType = computed(() => {
  const params = passRateValue.value;
  if (params == null || params === '') return 'info';
  const n = Number(params);
  if (Number.isNaN(n)) return 'info';
  if (n >= 100) return 'success';
  if (n >= 90) return 'warning';
  return 'danger';
});
  // ç¼–辑时:productMainId æˆ– purchaseLedgerId ä»»ä¸€æœ‰å€¼åˆ™å·¥åºã€æ•°é‡ç½®ç°
  const processQuantityDisabled = computed(() => {
    const v = form.value || {};
@@ -287,8 +292,8 @@
    {
      label: "检验值",
      prop: "testValue",
      dataType: "slot",
      slot: "slot",
        dataType: 'slot',
        slot: 'slot',
    },
  ]);
  const tableData = ref([]);
@@ -310,22 +315,22 @@
    const [userListsRes] = await Promise.all([
      userListNoPage(),
      getProductOptions(),
      getOptions().then(res => {
    getOptions().then((res) => {
        supplierList.value = res.data;
      }),
    })
    ]);
    userList.value = userListsRes.data;
    form.value = {};
  form.value = { isSampling: 0, samplingQuantity: "" }
    testStandardOptions.value = [];
    tableData.value = [];
    if (operationType.value === "edit" || operationType.value === "view") {
  if (operationType.value === 'edit' || operationType.value === 'view') {
      // å…ˆä¿å­˜ testStandardId,避免被清空
      const savedTestStandardId = row.testStandardId;
      // å…ˆè®¾ç½®è¡¨å•数据,但暂时清空 testStandardId,等选项加载完成后再设置
      form.value = { ...row, testStandardId: "" };
      currentProductId.value = row.productId || 0;
    const { passRate, ...rowWithoutPassRate } = row;
    form.value = { ...rowWithoutPassRate, testStandardId: '' };
    currentProductId.value = row.productId || 0
      // æ¸…空验证状态,避免数据加载过程中的校验闪烁
      nextTick(() => {
        proxy.$refs.formRef?.clearValidate();
@@ -334,31 +339,26 @@
      // ç¼–辑模式下,并行加载规格型号和指标选项
      if (currentProductId.value) {
        // è®¾ç½®äº§å“åç§°
        form.value.productName = findNodeById(
          productOptions.value,
          currentProductId.value
        );
      form.value.productName = findNodeById(productOptions.value, currentProductId.value);
        // å¹¶è¡ŒåŠ è½½è§„æ ¼åž‹å·å’ŒæŒ‡æ ‡é€‰é¡¹
        const params = {
          productId: currentProductId.value,
          inspectType: 2,
        inspectType: 2
        };
        Promise.all([
          modelList({ id: currentProductId.value }),
          qualityInspectDetailByProductId(params),
        qualityInspectDetailByProductId(params)
        ]).then(([modelRes, testStandardRes]) => {
          // è®¾ç½®è§„格型号选项
          modelOptions.value = modelRes || [];
          // å¦‚果表单中已有 productModelId,设置对应的 model å’Œ unit
          if (form.value.productModelId && modelOptions.value.length > 0) {
            const selectedModel = modelOptions.value.find(
              item => item.id == form.value.productModelId
            );
          const selectedModel = modelOptions.value.find(item => item.id == form.value.productModelId);
            if (selectedModel) {
              form.value.model = selectedModel.model || "";
              form.value.unit = selectedModel.unit || "";
            form.value.model = selectedModel.model || '';
            form.value.unit = selectedModel.unit || '';
            }
          }
@@ -369,22 +369,15 @@
          nextTick(() => {
            if (savedTestStandardId) {
              // ç¡®ä¿ç±»åž‹åŒ¹é…ï¼ˆitem.id å¯èƒ½æ˜¯æ•°å­—或字符串)
              const matchedOption = testStandardOptions.value.find(
                item =>
                  item.id == savedTestStandardId ||
                  String(item.id) === String(savedTestStandardId)
            const matchedOption = testStandardOptions.value.find(item =>
              item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
              );
              if (matchedOption) {
                // ç¡®ä¿ä½¿ç”¨åŒ¹é…é¡¹çš„ id(保持类型一致)
                form.value.testStandardId = matchedOption.id;
              } else {
                // å¦‚果找不到匹配项,尝试直接使用原值
                console.warn(
                  "未找到匹配的指标选项,testStandardId:",
                  savedTestStandardId,
                  "可用选项:",
                  testStandardOptions.value
                );
              console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
                form.value.testStandardId = savedTestStandardId;
              }
            }
@@ -396,34 +389,32 @@
        getQualityInspectParamList(row.id);
      }
    }
  };
}
  const getProductOptions = () => {
    return productTreeList().then(res => {
  return productTreeList().then((res) => {
      productOptions.value = convertIdToValue(res);
    });
  };
  const getModels = value => {
const getModels = (value) => {
    form.value.productModelId = undefined;
    form.value.unit = undefined;
    modelOptions.value = [];
    currentProductId.value = value;
  currentProductId.value = value
    form.value.productName = findNodeById(productOptions.value, value);
    modelList({ id: value }).then(res => {
  modelList({ id: value }).then((res) => {
      modelOptions.value = res;
    });
  })
    if (currentProductId.value) {
      getList();
    }
  };
  const handleChangeModel = value => {
    form.value.model =
      modelOptions.value.find(item => item.id == value)?.model || "";
    form.value.unit =
      modelOptions.value.find(item => item.id == value)?.unit || "";
  };
const handleChangeModel = (value) => {
  form.value.model = modelOptions.value.find(item => item.id == value)?.model || '';
  form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
}
  const handleQualifiedQuantityChange = value => {
const handleQualifiedQuantityChange = (value) => {
    if (value === null || value === undefined) {
      form.value.qualifiedQuantity = 0;
      return;
@@ -434,15 +425,14 @@
    form.value.unqualifiedQuantity = Math.max(0, quantity - qualified);
  };
  const handleUnqualifiedQuantityChange = value => {
const handleUnqualifiedQuantityChange = (value) => {
    if (value === null || value === undefined) {
      form.value.unqualifiedQuantity = 0;
      return;
    }
    const quantity = parseFloat(form.value.quantity) || 0;
    const unqualified = parseFloat(value) || 0;
    form.value.unqualifiedQuantity =
      unqualified > quantity ? quantity : unqualified;
  form.value.unqualifiedQuantity = unqualified > quantity?quantity:unqualified;
    form.value.qualifiedQuantity = Math.max(0, quantity - unqualified);
  };
@@ -461,7 +451,7 @@
    return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
  };
  function convertIdToValue(data) {
    return data.map(item => {
  return data.map((item) => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
@@ -480,7 +470,7 @@
      if (valid) {
        form.value.inspectType = 2;
        if (operationType.value === "add") {
          tableData.value.forEach(item => {
        tableData.value.forEach((item) => {
            delete item.id;
          });
        }
@@ -498,7 +488,7 @@
        }
      }
    });
  };
}
  const getList = () => {
    if (!currentProductId.value) {
      testStandardOptions.value = [];
@@ -507,7 +497,7 @@
    }
    let params = {
      productId: currentProductId.value,
      inspectType: 2,
    inspectType: 2
    };
    qualityInspectDetailByProductId(params).then(res => {
      // ä¿å­˜ä¸‹æ‹‰æ¡†é€‰é¡¹æ•°æ®
@@ -515,51 +505,60 @@
      // æ¸…空表格数据,等待用户选择指标
      tableData.value = [];
      // æ¸…空指标选择
      form.value.testStandardId = "";
    form.value.testStandardId = '';
    });
  };
}
  // æŒ‡æ ‡é€‰æ‹©å˜åŒ–处理
  const handleTestStandardChange = testStandardId => {
const handleTestStandardChange = (testStandardId) => {
    if (!testStandardId) {
      tableData.value = [];
      return;
    }
    tableLoading.value = true;
    getQualityTestStandardParamByTestStandardId(testStandardId)
      .then(res => {
  getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
        tableData.value = res.data || [];
        tableData.value = tableData.value.map(item => ({
          ...item,
          id: null,
      id: null
        }));
      })
      .catch(error => {
        console.error("获取标准参数失败:", error);
  }).catch(error => {
    console.error('获取标准参数失败:', error);
        tableData.value = [];
      })
      .finally(() => {
  }).finally(() => {
        tableLoading.value = false;
      });
  };
  const getQualityInspectParamList = id => {
}
const getQualityInspectParamList = (id) => {
    qualityInspectParamInfo(id).then(res => {
      tableData.value = res.data;
    });
  };
}
// æ˜¯å¦æŠ½æ£€åˆ‡æ¢
const handleSamplingChange = (value) => {
  if (value !== 1) {
    form.value.samplingQuantity = "";
    nextTick(() => {
      proxy.$refs?.formRef?.clearValidate?.("samplingQuantity");
    });
  }
}
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    proxy.resetForm("formRef");
    tableData.value = [];
    testStandardOptions.value = [];
    form.value.testStandardId = "";
  form.value.testStandardId = '';
    dialogFormVisible.value = false;
    emit("close");
  };
  emit('close');
}
  defineExpose({
    openDialog,
  });
</script>
<style scoped>
.sampling-radio-group {
  height: 32px;
}
</style>
src/views/qualityManagement/finalInspection/index.vue
@@ -1,71 +1,33 @@
<template>
  <div class="app-container">
    <div class="search_form mb20">
      <el-form ref="searchFormRef"
               :model="searchForm"
               class="demo-form-inline">
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item label="产品名称"
                          prop="productName">
              <el-input v-model="searchForm.productName"
      <div>
        <span class="search_title">产品名称:</span>
        <el-input
            v-model="searchForm.productName"
                        style="width: 240px"
                        placeholder="请输入产品名称搜索"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="检测日期"
                          prop="entryDate">
              <el-date-picker v-model="searchForm.entryDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="daterange"
                              placeholder="请选择"
            @change="handleQuery"
                              clearable
                              @change="changeDaterange" />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="销售单号"
                          prop="salesContractNo">
              <el-input v-model="searchForm.salesContractNo"
                        style="width: 240px"
                        placeholder="请输入销售单号搜索"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="生产工单号"
                          prop="workOrderNo">
              <el-input v-model="searchForm.workOrderNo"
                        style="width: 240px"
                        placeholder="请输入生产工单号搜索"
                        clearable />
            </el-form-item>
          </el-col>
          <!-- æŒ‰é’® -->
          <el-col :span="4">
            <el-form-item>
              <el-button type="primary"
                         @click="getList">
                æœç´¢
              </el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div class="actions">
        <el-button type="primary"
                   @click="openForm('add')">新增</el-button>
            :prefix-icon="Search"
        />
        <span style="margin-left: 10px" class="search_title">检测日期:</span>
        <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                        placeholder="请选择" clearable @change="changeDaterange"/>
        <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>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
      <PIMTable
          rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
@@ -73,39 +35,25 @@
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
          :total="page.total"
      ></PIMTable>
    </div>
    <InspectionFormDia ref="inspectionFormDia"
                       @close="handleQuery"></InspectionFormDia>
    <FormDia ref="formDia"
             @close="handleQuery"></FormDia>
    <files-dia ref="filesDia"
               @close="handleQuery"></files-dia>
    <el-dialog v-model="dialogFormVisible"
               title="编辑检验员"
               width="30%"
    <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia>
    <FormDia ref="formDia" @close="handleQuery"></FormDia>
    <files-dia ref="filesDia" ></files-dia>
    <el-dialog v-model="dialogFormVisible" title="编辑检验员" width="30%"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        <el-form-item label="检验员:"
                      prop="checkName">
          <el-select v-model="form.checkName"
                     placeholder="请选择"
                     clearable>
            <el-option v-for="item in userList"
                       :key="item.nickName"
                       :label="item.nickName"
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-form-item label="检验员:" prop="checkName">
          <el-select v-model="form.checkName" placeholder="请选择" clearable>
            <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                       :value="item.nickName" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
@@ -115,23 +63,15 @@
<script setup>
  import { Search } from "@element-plus/icons-vue";
  import {
    onMounted,
    ref,
    reactive,
    toRefs,
    getCurrentInstance,
    nextTick,
  } from "vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
  import InspectionFormDia from "@/views/qualityManagement/finalInspection/components/inspectionFormDia.vue";
  import FormDia from "@/views/qualityManagement/finalInspection/components/formDia.vue";
  import { ElMessageBox } from "element-plus";
  import {
    downloadQualityInspect,
    qualityInspectDel,
    qualityInspectListPage,
    qualityInspectUpdate,
    submitQualityInspect,
  qualityInspectListPage, qualityInspectUpdate,
  submitQualityInspect
  } from "@/api/qualityManagement/rawMaterialInspection.js";
  import FilesDia from "@/views/qualityManagement/finalInspection/components/filesDia.vue";
  import dayjs from "dayjs";
@@ -141,8 +81,6 @@
  const data = reactive({
    searchForm: {
      productName: "",
      salesContractNo: "",
      workOrderNo: "",
      entryDate: undefined, // å½•入日期
      entryDateStart: undefined,
      entryDateEnd: undefined,
@@ -156,17 +94,17 @@
    {
      label: "检测日期",
      prop: "checkTime",
      width: 120,
    width: 120
    },
    {
      label: "销售单号",
      prop: "salesContractNo",
      width: 120,
    width: 120
    },
    {
      label: "生产工单号",
      prop: "workOrderNo",
      width: 120,
    width: 120
    },
    {
      label: "检验员",
@@ -187,62 +125,61 @@
    {
      label: "总数量",
      prop: "quantity",
      width: 100,
    width: 100
    },
    {
      label: "合格数量",
      prop: "qualifiedQuantity",
      width: 100,
    width: 100
    },
    {
      label: "不合格数量",
      prop: "unqualifiedQuantity",
      width: 100,
    width: 100
  },
  {
    label: "检测单位",
    prop: "checkCompany",
    width: 120
    },
    {
      label: "合格率",
      prop: "passRate",
      width: 100,
      dataType: "tag",
      formatType: params => {
        if (!params) return "";
        const rate = parseFloat(params);
        if (rate < 90) {
          return "danger";
        } else if (rate === 100) {
          return "success";
        } else {
          return "warning";
        }
    formatData: (params) => {
      if (params == null || params === '') return '—';
      const n = Number(params);
      if (Number.isNaN(n)) return '—';
      return `${n.toFixed(2)}%`;
      },
    },
    {
      label: "检测单位",
      prop: "checkCompany",
      width: 120,
    },
    {
      label: "检测结果",
      prop: "checkResult",
      dataType: "tag",
      formatType: params => {
        if (params == "不合格") {
          return "danger";
        } else if (params == "合格") {
          return "success";
        } else {
          return "danger";
        }
    formatType: (params) => {
      if (params == null || params === '') return 'info';
      const n = Number(params);
      if (Number.isNaN(n)) return 'info';
      if (n === 100) return 'success';
      if (n >= 75) return 'primary';
      if (n >= 50) return 'warning';
      if (n >= 25) return 'danger';
      return 'danger';
      },
    },
    {
      label: "提交状态",
      prop: "inspectState",
      formatData: params => {
    dataType: "tag",
    formatData: (params) => {
        if (params) {
          return "已提交";
        } else {
          return "未提交";
      }
    },
    formatType: (params) => {
      if (params) {
        return "success";
      } else {
        return "info";
        }
      },
    },
@@ -256,10 +193,10 @@
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            openForm("edit", row);
          },
          disabled: row => {
        disabled: (row) => {
            // å·²æäº¤åˆ™ç¦ç”¨
            if (row.inspectState == 1) return true;
            // å¦‚果检验员有值,只有当前登录用户能编辑
@@ -267,29 +204,30 @@
              return row.checkName !== userStore.nickName;
            }
            return false;
          },
        }
        },
        {
          name: "查看",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            openForm("view", row);
          },
        },
        {
          name: "附件",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            openFilesFormDia(row);
          },
        },
        {
          name: "提交",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            submit(row.id);
          },
          disabled: row => {
        loading: (row) => submitLoadingId.value === row.id,
        disabled: (row) => {
            // å·²æäº¤åˆ™ç¦ç”¨
            if (row.inspectState == 1) return true;
            // å¦‚果检验员有值,只有当前登录用户能提交
@@ -297,26 +235,26 @@
              return row.checkName !== userStore.nickName;
            }
            return false;
          },
        }
        },
        {
          name: "分配检验员",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            if (!row.checkName) {
              open(row);
            open(row)
            } else {
              proxy.$modal.msgError("检验员已存在");
            }
          },
          disabled: row => {
        disabled: (row) => {
            return row.inspectState == 1 || row.checkName;
          },
        }
        },
        {
          name: "下载",
          type: "text",
          clickFun: row => {
        clickFun: (row) => {
            downLoadFile(row);
          },
        },
@@ -326,24 +264,25 @@
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const currentRow = ref(null);
const submitLoadingId = ref(null);
const currentRow = ref(null)
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  total: 0
  });
  const formDia = ref();
  const filesDia = ref();
  const inspectionFormDia = ref();
  const { proxy } = getCurrentInstance();
  const userStore = useUserStore();
const formDia = ref()
const filesDia = ref()
const inspectionFormDia = ref()
const {proxy} = getCurrentInstance()
const userStore = useUserStore()
  const userList = ref([]);
  const form = ref({
    checkName: "",
  checkName: ""
  });
  const dialogFormVisible = ref(false);
  const changeDaterange = value => {
const changeDaterange = (value) => {
    searchForm.value.entryDateStart = undefined;
    searchForm.value.entryDateEnd = undefined;
    if (value) {
@@ -358,7 +297,7 @@
    page.current = 1;
    getList();
  };
  const pagination = obj => {
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
@@ -366,57 +305,44 @@
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    qualityInspectListPage({ ...params, inspectType: 2 })
      .then(res => {
  params.entryDate = undefined
  qualityInspectListPage({...params, inspectType: 2}).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => {
          const quantity = parseFloat(item.quantity);
          const qualifiedQuantity = parseFloat(item.qualifiedQuantity);
          let passRate = null;
          if (!isNaN(quantity) && !isNaN(qualifiedQuantity) && quantity > 0) {
            passRate = ((qualifiedQuantity / quantity) * 100).toFixed(2) + "%";
          }
          return {
            ...item,
            passRate: passRate,
          };
        });
    tableData.value = res.data.records
        page.total = res.data.total;
      })
      .catch(err => {
  }).catch(err => {
        tableLoading.value = false;
      });
  })
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    nextTick(() => {
      formDia.value?.openDialog(type, row);
    });
    formDia.value?.openDialog(type, row)
  })
  };
  // æ‰“开新增检验弹框
  const openInspectionForm = (type, row) => {
    nextTick(() => {
      inspectionFormDia.value?.openDialog(type, row);
    });
    inspectionFormDia.value?.openDialog(type, row)
  })
  };
  // æ‰“开附件弹框
  const openFilesFormDia = (type, row) => {
    nextTick(() => {
      filesDia.value?.openDialog(type, row);
    });
    filesDia.value?.openDialog(type, row)
  })
  };
  // åˆ é™¤
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.id);
    ids = selectedRows.value.map((item) => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
@@ -427,7 +353,7 @@
      type: "warning",
    })
      .then(() => {
        qualityInspectDel(ids).then(res => {
        qualityInspectDel(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
@@ -444,25 +370,29 @@
      type: "warning",
    })
      .then(() => {
        proxy.download(
          "/quality/qualityInspect/export",
          { inspectType: 2 },
          "出厂检验.xlsx"
        );
        proxy.download("/quality/qualityInspect/export", {inspectType: 2}, "出厂检验.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // æä»·
  const submit = async id => {
    const res = await submitQualityInspect({ id: id });
// æäº¤
const submit = async (id) => {
  submitLoadingId.value = id;
  try {
    const res = await submitQualityInspect({ id });
    if (res.code === 200) {
      proxy.$modal.msgSuccess("提交成功");
      getList();
      const row = tableData.value.find(item => item.id === id);
      if (row) {
        row.inspectState = 1;
    }
  };
    }
  } finally {
    submitLoadingId.value = null;
  }
}
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
@@ -474,49 +404,43 @@
    if (currentRow.value) {
      const data = {
        ...form.value,
        id: currentRow.value.id,
      };
      id: currentRow.value.id
    }
      qualityInspectUpdate(data).then(res => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
        getList();
      });
    })
    }
  };
  const open = async row => {
const open = async (row) => {
    let userLists = await userListNoPage();
    userList.value = userLists.data;
    currentRow.value = row;
    dialogFormVisible.value = true;
  };
  currentRow.value = row
  dialogFormVisible.value = true
}
  const downLoadFile = row => {
    downloadQualityInspect({ id: row.id }).then(blobData => {
const downLoadFile = (row) => {
  downloadQualityInspect({id: row.id}).then((blobData) => {
      const blob = new Blob([blobData], {
        type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      });
      const downloadUrl = window.URL.createObjectURL(blob);
      type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    })
    const downloadUrl = window.URL.createObjectURL(blob)
      const link = document.createElement("a");
      link.href = downloadUrl;
      link.download = "原材料检验报告.docx";
      document.body.appendChild(link);
      link.click();
    const link = document.createElement('a')
    link.href = downloadUrl
    link.download = '原材料检验报告.docx'
    document.body.appendChild(link)
    link.click()
      document.body.removeChild(link);
      window.URL.revokeObjectURL(downloadUrl);
    });
    document.body.removeChild(link)
    window.URL.revokeObjectURL(downloadUrl)
  })
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  .actions {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 10px;
  }
</style>
<style scoped></style>
src/views/qualityManagement/metricBinding/index.vue
@@ -1,16 +1,11 @@
<template>
  <div class="app-container metric-binding">
    <el-row :gutter="16"
            class="metric-binding-row">
    <el-row :gutter="16" class="metric-binding-row">
      <!-- å·¦ä¾§ï¼šæ£€æµ‹æ ‡å‡†åˆ—表 -->
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="14"
              :xl="14"
              class="left-col">
      <el-col :xs="24" :sm="24" :md="12" :lg="14" :xl="14" class="left-col">
        <div class="panel left-panel">
          <PIMTable rowKey="id"
      <PIMTable
        rowKey="id"
                    :column="standardColumns"
                    :tableData="standardTableData"
                    :page="page"
@@ -19,149 +14,155 @@
                    :tableLoading="tableLoading"
                    :rowClick="handleTableRowClick"
                    @pagination="handlePagination"
                    :total="page.total">
        :total="page.total"
      >
            <template #standardNoCell="{ row }">
              <span class="clickable-link"
                    @click="handleStandardRowClick(row)">
          <span class="clickable-link" @click="handleStandardRowClick(row)">
                {{ row.standardNo }}
              </span>
            </template>
            <!-- è¡¨å¤´æœç´¢ -->
            <template #standardNoHeader>
              <el-input v-model="searchForm.standardNo"
          <el-input
            v-model="searchForm.standardNo"
                        placeholder="标准编号"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            @clear="handleQuery"
          />
            </template>
            <template #standardNameHeader>
              <el-input v-model="searchForm.standardName"
          <el-input
            v-model="searchForm.standardName"
                        placeholder="标准名称"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            @clear="handleQuery"
          />
            </template>
            <template #inspectTypeHeader>
              <el-select v-model="searchForm.inspectType"
          <el-select
            v-model="searchForm.inspectType"
                         placeholder="类别"
                         clearable
                         size="small"
                         style="width: 120px"
                         @change="handleQuery"
                         @clear="handleQuery">
                <el-option label="原材料检验"
                           value="0" />
                <el-option label="过程检验"
                           value="1" />
                <el-option label="出厂检验"
                           value="2" />
            @clear="handleQuery"
          >
            <el-option label="原材料检验" value="0" />
            <el-option label="过程检验" value="1" />
            <el-option label="出厂检验" value="2" />
              </el-select>
            </template>
            <template #stateHeader>
              <el-select v-model="searchForm.state"
          <el-select
            v-model="searchForm.state"
                         placeholder="状态"
                         clearable
                         size="small"
                         style="width: 110px"
                         @change="handleQuery"
                         @clear="handleQuery">
                <el-option label="草稿"
                           value="0" />
                <el-option label="通过"
                           value="1" />
                <el-option label="撤销"
                           value="2" />
            @clear="handleQuery"
          >
            <el-option label="草稿" value="0" />
            <el-option label="通过" value="1" />
            <el-option label="撤销" value="2" />
              </el-select>
            </template>
          </PIMTable>
        </div>
      </el-col>
      <!-- å³ä¾§ï¼šç»‘定列表 -->
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="10"
              :xl="10"
              class="right-col">
      <el-col :xs="24" :sm="24" :md="12" :lg="10" :xl="10" class="right-col">
        <div class="panel right-panel">
          <div class="right-header">
            <div class="title">绑定关系</div>
            <div class="desc"
                 v-if="currentStandard">
        <div class="desc" v-if="currentStandard">
              å½“前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
            </div>
            <div class="desc"
                 v-else>请选择左侧检测标准</div>
        <div class="desc" v-else>请选择左侧检测标准</div>
          </div>
          <div class="right-toolbar">
            <el-button type="primary"
                       :disabled="!currentStandard"
                       @click="openBindingDialog">添加绑定</el-button>
            <el-button type="danger"
                       plain
                       :disabled="!currentStandard"
                       @click="handleBatchUnbind">删除</el-button>
        <el-button type="primary" :disabled="!currentStandard" @click="openBindingDialog">添加绑定</el-button>
        <el-button type="danger" plain :disabled="!currentStandard" @click="handleBatchUnbind">删除</el-button>
          </div>
          <el-table v-loading="bindingLoading"
      <el-table
        v-loading="bindingLoading"
                    :data="bindingTableData"
                    border
                    :row-class-name="() => 'row-center'"
                    class="center-table"
                    style="width: 100%"
                    height="calc(100vh - 220px)"
                    @selection-change="handleBindingSelectionChange">
            <el-table-column type="selection"
                             width="48"
                             align="center" />
            <el-table-column type="index"
                             label="序号"
                             width="60"
                             align="center" />
            <el-table-column prop="productName"
                             label="产品名称"
                             min-width="140" />
            <el-table-column label="操作"
                             width="120"
                             fixed="right"
                             align="center">
        @selection-change="handleBindingSelectionChange"
      >
        <el-table-column type="selection" width="48" align="center" />
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="productName" label="产品名称" min-width="140" />
        <el-table-column label="操作" width="120" fixed="right" align="center">
              <template #default="{ row }">
                <el-button link
                           type="danger"
                           size="small"
                           @click="handleUnbind(row)">删除</el-button>
            <el-button link type="danger" size="small" @click="handleUnbind(row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-col>
    </el-row>
    <!-- æ·»åŠ ç»‘å®šå¼¹æ¡† -->
    <el-dialog v-model="bindingDialogVisible"
    <el-dialog
      v-model="bindingDialogVisible"
               title="添加绑定"
               width="520px"
               @close="closeBindingDialog">
      <el-form label-width="100px">
        <el-form-item label="产品">
          <PIMTreeSelect v-model="selectedProductIds"
                         multiple
                         filterable
                         collapse-tags
                         collapse-tags-tooltip
                         placeholder="请选择产品(可多选)"
      width="640px"
      @close="closeBindingDialog"
    >
      <div class="binding-dialog">
        <el-input
          v-model="productSearchKeyword"
          placeholder="搜索产品"
                         clearable
          prefix-icon="Search"
          class="binding-search"
        />
        <el-tree
          ref="productTreeRef"
          :key="productTreeKey"
          v-loading="productTreeLoading"
          :data="productTreeData"
          show-checkbox
                         check-strictly
                         :data="productOptions"
                         :render-after-expand="false"
                         style="width: 100%" />
        </el-form-item>
      </el-form>
          node-key="id"
          :props="{ label: 'label', children: 'children', disabled: 'disabled' }"
          :filter-node-method="filterProductNode"
          class="product-binding-tree"
          @check="handleProductTreeCheck"
        />
        <div v-if="selectedProductLabels.length" class="selected-products">
          <div class="selected-title">已选择({{ selectedProductLabels.length }})</div>
          <div class="selected-tags">
            <el-tag
              v-for="item in selectedProductLabels"
              :key="item.id"
              closable
              type="info"
              @close="removeSelectedProduct(item.id)"
            >
              {{ item.label }}
            </el-tag>
          </div>
        </div>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="closeBindingDialog">取消</el-button>
          <el-button type="primary"
                     @click="submitBinding">确定</el-button>
          <el-button type="primary" @click="submitBinding">确定</el-button>
        </span>
      </template>
    </el-dialog>
@@ -169,104 +170,90 @@
</template>
<script setup>
  import { Search } from "@element-plus/icons-vue";
  import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
  import { ElMessageBox } from "element-plus";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import PIMTreeSelect from "@/components/PIMTreeSelect/index.vue";
  import { productTreeList } from "@/api/basicData/product.js";
  import { qualityTestStandardListPage } from "@/api/qualityManagement/metricMaintenance.js";
  import { productProcessListPage } from "@/api/basicData/productProcess.js";
import { ref, reactive, toRefs, computed, watch, nextTick, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from 'element-plus'
import PIMTable from '@/components/PIMTable/PIMTable.vue'
import { productTreeList } from '@/api/basicData/product.js'
import {
  qualityTestStandardListPage
} from '@/api/qualityManagement/metricMaintenance.js'
import { productProcessListPage } from '@/api/basicData/productProcess.js'
  import {
    qualityTestStandardBindingList,
    qualityTestStandardBindingAdd,
    qualityTestStandardBindingDel,
  } from "@/api/qualityManagement/qualityTestStandardBinding.js";
  qualityTestStandardBindingDel
} from '@/api/qualityManagement/qualityTestStandardBinding.js'
  const { proxy } = getCurrentInstance();
const { proxy } = getCurrentInstance()
  // å·¦ä¾§æ ‡å‡†åˆ—表:整行内容居中(配合样式)
  const rowClassNameCenter = () => "row-center";
const rowClassNameCenter = () => 'row-center'
  const data = reactive({
    searchForm: {
      standardNo: "",
      standardName: "",
      state: "",
      inspectType: "",
    },
  });
  const { searchForm } = toRefs(data);
    standardNo: '',
    standardName: '',
    state: '',
    inspectType: ''
  }
})
const { searchForm } = toRefs(data)
  // å·¦ä¾§
  const standardTableData = ref([]);
  const tableLoading = ref(false);
  const page = reactive({ current: 1, size: 10, total: 0 });
const standardTableData = ref([])
const tableLoading = ref(false)
const page = reactive({ current: 1, size: 10, total: 0 })
  // å·¥åºä¸‹æ‹‰ï¼ˆç”¨äºŽåˆ—表回显)
  const processOptions = ref([]);
const processOptions = ref([])
  const getProcessList = async () => {
    try {
      const res = await productProcessListPage({ current: 1, size: 1000 });
    const res = await productProcessListPage({ current: 1, size: 1000 })
      if (res?.code === 200) {
        const records = res?.data?.records || [];
        processOptions.value = records.map(item => ({
      const records = res?.data?.records || []
      processOptions.value = records.map((item) => ({
          label: item.processName || item.name || item.label,
          value: item.id || item.processId || item.value,
        }));
        value: item.id || item.processId || item.value
      }))
      }
    } catch (error) {
      console.error("获取工序列表失败:", error);
    console.error('获取工序列表失败:', error)
    }
  };
}
  const standardColumns = ref([
  { label: '标准编号', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, align: 'center', headerSlot: 'standardNoHeader' },
  { label: '标准名称', prop: 'standardName', minWidth: 180, align: 'center', headerSlot: 'standardNameHeader' },
    {
      label: "标准编号",
      prop: "standardNo",
      dataType: "slot",
      slot: "standardNoCell",
      minWidth: 160,
      align: "center",
      headerSlot: "standardNoHeader",
    label: '类别',
    prop: 'inspectType',
    headerSlot: 'inspectTypeHeader',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = { 0: '原材料检验', 1: '过程检验', 2: '出厂检验' }
      return map[val] || val
    }
    },
    {
      label: "标准名称",
      prop: "standardName",
      minWidth: 180,
      align: "center",
      headerSlot: "standardNameHeader",
    },
    {
      label: "类别",
      prop: "inspectType",
      headerSlot: "inspectTypeHeader",
      align: "center",
      dataType: "tag",
      formatData: val => {
        const map = { 0: "原材料检验", 1: "过程检验", 2: "出厂检验" };
        return map[val] || val;
      },
    },
    {
      label: "工序",
      prop: "processId",
      align: "center",
      dataType: "tag",
      formatData: val => {
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
        const target = processOptions.value.find(
          item => String(item.value) === String(val)
        );
        return target?.label || val;
      },
        (item) => String(item.value) === String(val)
      )
      return target?.label || val
    }
    },
    {
      label: "备注",
      prop: "remark",
    label: '备注',
    prop: 'remark',
      minWidth: 160,
      align: "center",
    },
    align: 'center'
  }
    // {
    //   label: '状态',
    //   prop: 'state',
@@ -282,189 +269,240 @@
    //     return 'info'
    //   }
    // }
  ]);
])
  const currentStandard = ref(null);
const currentStandard = ref(null)
  // å³ä¾§ç»‘定
  const bindingTableData = ref([]);
  const bindingLoading = ref(false);
  const bindingSelectedRows = ref([]);
  const bindingDialogVisible = ref(false);
const bindingTableData = ref([])
const bindingLoading = ref(false)
const bindingSelectedRows = ref([])
const bindingDialogVisible = ref(false)
  // äº§å“æ ‘(用于绑定选择)
  const productOptions = ref([]);
  const selectedProductIds = ref([]);
const productTreeData = ref([])
const productTreeLoading = ref(false)
const productTreeRef = ref(null)
const productTreeKey = ref(0)
const productSearchKeyword = ref('')
const selectedProductIds = ref([])
  const getProductOptions = async () => {
    // é¿å…é‡å¤è¯·æ±‚
    if (productOptions.value?.length) return;
    const res = await productTreeList();
    productOptions.value = convertIdToValue(Array.isArray(res) ? res : []);
  };
  function convertIdToValue(data) {
    return (data || []).map(item => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
        value: id,
      };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
const markParentNodesDisabled = (nodes) => {
  return (nodes || []).map((node) => {
    const children = node.children?.length ? markParentNodesDisabled(node.children) : []
    return {
      ...node,
      children,
      disabled: children.length > 0
      }
      return newItem;
    });
  })
}
const buildProductLabelMap = (nodes, map = {}) => {
  ;(nodes || []).forEach((node) => {
    if (node.id != null) {
      map[node.id] = node.label
    }
    if (node.children?.length) {
      buildProductLabelMap(node.children, map)
    }
  })
  return map
}
const productLabelMap = computed(() => buildProductLabelMap(productTreeData.value))
const selectedProductLabels = computed(() =>
  selectedProductIds.value.map((id) => ({
    id,
    label: productLabelMap.value[id] || id
  }))
)
const filterProductNode = (value, data) => {
  if (!value) return true
  return String(data.label || '').includes(value)
}
watch(productSearchKeyword, (val) => {
  productTreeRef.value?.filter(val)
})
const getProductTreeData = async () => {
  if (productTreeData.value?.length) return
  productTreeLoading.value = true
  try {
    const res = await productTreeList()
    productTreeData.value = markParentNodesDisabled(Array.isArray(res) ? res : [])
  } catch (error) {
    console.error('获取产品树失败:', error)
  } finally {
    productTreeLoading.value = false
  }
}
const handleProductTreeCheck = () => {
  selectedProductIds.value = productTreeRef.value?.getCheckedKeys(true) || []
}
const removeSelectedProduct = (id) => {
  selectedProductIds.value = selectedProductIds.value.filter((item) => item !== id)
  productTreeRef.value?.setChecked(id, false, false)
  }
  const handleQuery = () => {
    page.current = 1;
    getStandardList();
  };
  page.current = 1
  getStandardList()
}
  const handlePagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getStandardList();
  };
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
  const getStandardList = () => {
    tableLoading.value = true;
  tableLoading.value = true
    qualityTestStandardListPage({
      ...searchForm.value,
      current: page.current,
      size: page.size,
      state: 1,
    state: 1
    })
      .then(res => {
        const records = res?.data?.records || [];
        standardTableData.value = records;
        page.total = res?.data?.total || records.length;
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
      tableLoading.value = false
    })
}
  // è¡¨æ ¼è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨
  const handleTableRowClick = row => {
    currentStandard.value = row;
    loadBindingList();
  };
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
  // å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨ï¼ˆä¿ç•™ç”¨äºŽæ ‡å‡†ç¼–å·åˆ—çš„ç‚¹å‡»ï¼‰
  const handleStandardRowClick = row => {
    currentStandard.value = row;
    loadBindingList();
  };
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
  const loadBindingList = () => {
    if (!currentStandard.value?.id) {
      bindingTableData.value = [];
      return;
    bindingTableData.value = []
    return
    }
    bindingLoading.value = true;
  bindingLoading.value = true
    qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
      .then(res => {
        const base = res?.data || [];
    .then((res) => {
      const base = res?.data || []
        // å°†å½“前标准的工序和备注带到绑定列表中展示
        bindingTableData.value = base.map(item => ({
      bindingTableData.value = base.map((item) => ({
          ...item,
          processId: currentStandard.value?.processId,
          remark: currentStandard.value?.remark,
        }));
        remark: currentStandard.value?.remark
      }))
      })
      .finally(() => {
        bindingLoading.value = false;
      });
  };
      bindingLoading.value = false
    })
}
  const handleBindingSelectionChange = selection => {
    bindingSelectedRows.value = selection;
  };
const handleBindingSelectionChange = (selection) => {
  bindingSelectedRows.value = selection
}
  const openBindingDialog = () => {
    if (!currentStandard.value?.id) return;
    selectedProductIds.value = [];
    getProductOptions();
    bindingDialogVisible.value = true;
  };
  if (!currentStandard.value?.id) return
  selectedProductIds.value = []
  productSearchKeyword.value = ''
  productTreeKey.value += 1
  bindingDialogVisible.value = true
  nextTick(() => {
    productTreeRef.value?.setCheckedKeys([])
    productTreeRef.value?.filter('')
  })
}
  const closeBindingDialog = () => {
    bindingDialogVisible.value = false;
  };
  bindingDialogVisible.value = false
  selectedProductIds.value = []
  productSearchKeyword.value = ''
  productTreeRef.value?.setCheckedKeys([])
}
  const submitBinding = async () => {
    const testStandardId = currentStandard.value?.id;
    if (!testStandardId) return;
    const ids = (selectedProductIds.value || []).filter(Boolean);
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  const ids = (selectedProductIds.value || []).filter(Boolean)
    if (!ids.length) {
      proxy.$message.warning("请选择产品");
      return;
    proxy.$message.warning('请选择产品')
    return
    }
    const payload = ids.map(pid => ({
  const payload = ids.map((pid) => ({
      productId: pid,
      testStandardId,
    }));
    await qualityTestStandardBindingAdd(payload);
    proxy.$message.success("添加成功");
    bindingDialogVisible.value = false;
    loadBindingList();
  };
    testStandardId
  }))
  await qualityTestStandardBindingAdd(payload)
  proxy.$message.success('添加成功')
  bindingDialogVisible.value = false
  loadBindingList()
}
  const handleUnbind = async row => {
    const id = row?.id ?? row?.qualityTestStandardBindingId;
    if (id == null || id === "") return;
const handleUnbind = async (row) => {
  const id = row?.id ?? row?.qualityTestStandardBindingId
  if (id == null || id === '') return
    try {
      await ElMessageBox.confirm("确认删除该绑定?", "提示", { type: "warning" });
    await ElMessageBox.confirm('确认删除该绑定?', '提示', { type: 'warning' })
    } catch {
      return;
    return
    }
    try {
      await qualityTestStandardBindingDel([id]);
      proxy.$message.success("删除成功");
      loadBindingList();
    await qualityTestStandardBindingDel([id])
    proxy.$message.success('删除成功')
    loadBindingList()
    } catch (err) {
      console.error("删除绑定失败:", err);
      proxy.$message?.error(err?.message || "删除失败");
    console.error('删除绑定失败:', err)
    proxy.$message?.error(err?.message || '删除失败')
    }
  };
}
  const handleBatchUnbind = async () => {
    if (!bindingSelectedRows.value.length) {
      proxy.$message.warning("请选择数据");
      return;
    proxy.$message.warning('请选择数据')
    return
    }
    const ids = bindingSelectedRows.value
      .map(i => i?.id ?? i?.qualityTestStandardBindingId)
      .filter(id => id != null && id !== "");
    .map((i) => i?.id ?? i?.qualityTestStandardBindingId)
    .filter((id) => id != null && id !== '')
    if (!ids.length) {
      proxy.$message.warning("选中数据缺少有效 id");
      return;
    proxy.$message.warning('选中数据缺少有效 id')
    return
    }
    try {
      await ElMessageBox.confirm(
        "选中的内容将被删除,是否确认删除?",
        "删除提示",
        { type: "warning" }
      );
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
    } catch {
      return;
    return
    }
    try {
      await qualityTestStandardBindingDel(ids);
      proxy.$message.success("删除成功");
      loadBindingList();
    await qualityTestStandardBindingDel(ids)
    proxy.$message.success('删除成功')
    loadBindingList()
    } catch (err) {
      console.error("批量删除绑定失败:", err);
      proxy.$message?.error(err?.message || "删除失败");
    console.error('批量删除绑定失败:', err)
    proxy.$message?.error(err?.message || '删除失败')
    }
  };
}
  onMounted(() => {
    getStandardList();
    getProcessList();
  });
  getStandardList()
  getProcessList()
  getProductTreeData()
})
</script>
<style scoped>
@@ -569,4 +607,39 @@
    width: 100%;
    margin-top: 4px;
  }
.binding-dialog {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.binding-search {
  width: 100%;
}
.product-binding-tree {
  max-height: 360px;
  overflow-y: auto;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  padding: 8px;
}
.selected-products {
  border-top: 1px solid #ebeef5;
  padding-top: 12px;
}
.selected-title {
  font-size: 13px;
  color: #606266;
  margin-bottom: 8px;
}
.selected-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
</style>
src/views/qualityManagement/nearExpiryReturn/index.vue
@@ -1,27 +1,25 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams"
             ref="queryForm"
             :inline="true"
             v-show="showSearch"
             label-width="68px">
      <el-form-item label="产品名称"
                    prop="productName">
        <el-input v-model="queryParams.productName"
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="产品名称" prop="productName">
        <el-input
          v-model="queryParams.productName"
                  placeholder="请输入产品名称"
                  clearable
                  @keyup.enter.native="handleQuery" />
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="批次号"
                    prop="batchNumber">
        <el-input v-model="queryParams.batchNumber"
      <el-form-item label="批次号" prop="batchNumber">
        <el-input
          v-model="queryParams.batchNumber"
                  placeholder="请输入批次号"
                  clearable
                  @keyup.enter.native="handleQuery" />
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="退回日期"
                    prop="returnDate">
        <el-date-picker clearable
      <el-form-item label="退回日期" prop="returnDate">
        <el-date-picker
          clearable
                        v-model="queryParams.returnDate"
                        type="date"
                        value-format="YYYY-MM-DD"
@@ -29,160 +27,125 @@
        </el-date-picker>
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   icon="Search"
                   @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh"
                   @click="resetQuery">重置</el-button>
        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10"
            class="mb8">
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button type="primary"
        <el-button
          type="primary"
                   plain
                   icon="Plus"
                   @click="handleAdd">新增</el-button>
          @click="handleAdd"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="success"
        <el-button
          type="success"
                   plain
                   icon="Edit"
                   :disabled="single"
                   @click="handleUpdate">修改</el-button>
          @click="handleUpdate"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="danger"
        <el-button
          type="danger"
                   plain
                   icon="Delete"
                   :disabled="multiple"
                   @click="handleDelete">删除</el-button>
          @click="handleDelete"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="warning"
        <el-button
          type="warning"
                   plain
                   icon="Download"
                   @click="handleExport">导出</el-button>
          @click="handleExport"
        >导出</el-button>
      </el-col>
      <right-toolbar v-model:showSearch="showSearch"
                     @queryTable="getList"></right-toolbar>
      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table v-loading="loading"
              :data="nearExpiryReturnList"
              @selection-change="handleSelectionChange">
      <el-table-column type="selection"
                       width="55"
                       align="center" />
      <el-table-column label="序号"
                       type="index"
                       width="50"
                       align="center" />
      <el-table-column label="产品名称"
                       prop="productName" />
      <el-table-column label="规格型号"
                       prop="productSpec" />
      <el-table-column label="批次号"
                       prop="batchNumber" />
      <el-table-column label="生产日期"
                       prop="productionDate"
                       align="center">
    <el-table v-loading="loading" :data="nearExpiryReturnList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="序号" type="index" width="50" align="center" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="产品规格" prop="productSpec" />
      <el-table-column label="批次号" prop="batchNumber" />
      <el-table-column label="生产日期" prop="productionDate" align="center">
        <template #default="scope">
          <span>{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="到期日期"
                       prop="expiryDate"
                       align="center">
      <el-table-column label="到期日期" prop="expiryDate" align="center">
        <template #default="scope">
          <span>{{ parseTime(scope.row.expiryDate, '{y}-{m}-{d}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="退回数量"
                       prop="returnQuantity" />
      <el-table-column label="退回原因"
                       prop="returnReason" />
      <el-table-column label="退回日期"
                       prop="returnDate"
                       align="center">
      <el-table-column label="退回数量" prop="returnQuantity" />
      <el-table-column label="退回原因" prop="returnReason" />
      <el-table-column label="退回日期" prop="returnDate" align="center">
        <template #default="scope">
          <span>{{ parseTime(scope.row.returnDate, '{y}-{m}-{d}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="处理状态"
                       prop="status"
                       align="center">
      <el-table-column label="处理状态" prop="status" align="center">
        <template #default="scope">
          <dict-tag :options="statusOptions"
                    :value="scope.row.status" />
          <dict-tag :options="statusOptions" :value="scope.row.status"/>
        </template>
      </el-table-column>
      <el-table-column label="操作"
                       align="center"
                       class-name="small-padding fixed-width">
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button size="mini"
                     type="text"
                     icon="Edit"
                     @click="handleUpdate(scope.row)">修改</el-button>
          <el-button size="mini"
                     type="text"
                     icon="Delete"
                     @click="handleDelete(scope.row)">删除</el-button>
          <el-button size="mini" type="text" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
          <el-button size="mini" type="text" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination v-show="total>0"
    <pagination
      v-show="total>0"
                :total="total"
                v-model:page="queryParams.pageNum"
                v-model:limit="queryParams.pageSize"
                @pagination="getList" />
      @pagination="getList"
    />
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ä¸´æœŸé€€å›žå°è´¦å¯¹è¯æ¡† -->
    <el-dialog :title="title"
               v-model="open"
               width="800px"
               append-to-body>
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="100px">
    <el-dialog :title="title" v-model="open" width="800px" append-to-body>
      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
        <el-row>
          <el-col :span="12">
            <el-form-item label="产品名称"
                          prop="productName">
              <el-input v-model="form.productName"
                        placeholder="请输入产品名称" />
            <el-form-item label="产品名称" prop="productName">
              <el-input v-model="form.productName" placeholder="请输入产品名称" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号"
                          prop="productSpec">
              <el-input v-model="form.productSpec"
                        placeholder="请输入规格型号" />
            <el-form-item label="产品规格" prop="productSpec">
              <el-input v-model="form.productSpec" placeholder="请输入产品规格" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="批次号"
                          prop="batchNumber">
              <el-input v-model="form.batchNumber"
                        placeholder="请输入批次号" />
            <el-form-item label="批次号" prop="batchNumber">
              <el-input v-model="form.batchNumber" placeholder="请输入批次号" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="退回数量"
                          prop="returnQuantity">
              <el-input-number v-model="form.returnQuantity"
                               controls-position="right"
                               :min="1" />
            <el-form-item label="退回数量" prop="returnQuantity">
              <el-input-number v-model="form.returnQuantity" controls-position="right" :min="1" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="生产日期"
                          prop="productionDate">
              <el-date-picker clearable
            <el-form-item label="生产日期" prop="productionDate">
              <el-date-picker
                clearable
                              v-model="form.productionDate"
                              type="date"
                              value-format="YYYY-MM-DD"
@@ -191,9 +154,9 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="到期日期"
                          prop="expiryDate">
              <el-date-picker clearable
            <el-form-item label="到期日期" prop="expiryDate">
              <el-date-picker
                clearable
                              v-model="form.expiryDate"
                              type="date"
                              value-format="YYYY-MM-DD"
@@ -204,9 +167,9 @@
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="退回日期"
                          prop="returnDate">
              <el-date-picker clearable
            <el-form-item label="退回日期" prop="returnDate">
              <el-date-picker
                clearable
                              v-model="form.returnDate"
                              type="date"
                              value-format="YYYY-MM-DD"
@@ -215,43 +178,36 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="处理状态"
                          prop="status">
              <el-select v-model="form.status"
                         placeholder="请选择处理状态">
                <el-option v-for="dict in statusOptions"
            <el-form-item label="处理状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择处理状态">
                <el-option
                  v-for="dict in statusOptions"
                           :key="dict.value"
                           :label="dict.label"
                           :value="dict.value"></el-option>
                  :value="dict.value"
                ></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="退回原因"
                          prop="returnReason">
              <el-input v-model="form.returnReason"
                        type="textarea"
                        placeholder="请输入退回原因" />
            <el-form-item label="退回原因" prop="returnReason">
              <el-input v-model="form.returnReason" type="textarea" placeholder="请输入退回原因" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="备注"
                          prop="remark">
              <el-input v-model="form.remark"
                        type="textarea"
                        placeholder="请输入备注" />
            <el-form-item label="备注" prop="remark">
              <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
            </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 type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
@@ -281,7 +237,7 @@
  const statusOptions = ref([
    { label: "待处理", value: "0" },
    { label: "处理中", value: "1" },
    { label: "已完成", value: "2" },
  { label: "已完成", value: "2" }
  ]);
  const data = reactive({
@@ -291,37 +247,37 @@
      pageSize: 10,
      productName: null,
      batchNumber: null,
      returnDate: null,
    returnDate: null
    },
    rules: {
      productName: [
        { required: true, message: "产品名称不能为空", trigger: "blur" },
      { required: true, message: "产品名称不能为空", trigger: "blur" }
      ],
      productSpec: [
        { required: true, message: "规格型号不能为空", trigger: "blur" },
      { required: true, message: "产品规格不能为空", trigger: "blur" }
      ],
      batchNumber: [
        { required: true, message: "批次号不能为空", trigger: "blur" },
      { required: true, message: "批次号不能为空", trigger: "blur" }
      ],
      returnQuantity: [
        { required: true, message: "退回数量不能为空", trigger: "blur" },
      { required: true, message: "退回数量不能为空", trigger: "blur" }
      ],
      productionDate: [
        { required: true, message: "生产日期不能为空", trigger: "blur" },
      { required: true, message: "生产日期不能为空", trigger: "blur" }
      ],
      expiryDate: [
        { required: true, message: "到期日期不能为空", trigger: "blur" },
      { required: true, message: "到期日期不能为空", trigger: "blur" }
      ],
      returnDate: [
        { required: true, message: "退回日期不能为空", trigger: "blur" },
      { required: true, message: "退回日期不能为空", trigger: "blur" }
      ],
      returnReason: [
        { required: true, message: "退回原因不能为空", trigger: "blur" },
      { required: true, message: "退回原因不能为空", trigger: "blur" }
      ],
      status: [
        { required: true, message: "处理状态不能为空", trigger: "change" },
      ],
    },
      { required: true, message: "处理状态不能为空", trigger: "change" }
    ]
  }
  });
  const { queryParams, form, rules } = toRefs(data);
@@ -354,7 +310,7 @@
      returnReason: null,
      returnDate: null,
      status: null,
      remark: null,
    remark: null
    };
    proxy.resetForm("formRef");
  }
@@ -415,21 +371,15 @@
  /** åˆ é™¤æŒ‰é’®æ“ä½œ */
  function handleDelete(row) {
    const deleteIds = row.id || ids.value;
    ElMessageBox.confirm(
      '是否确认删除临期退回台账编号为"' + deleteIds + '"的数据项?',
      "警告",
      {
  ElMessageBox.confirm('是否确认删除临期退回台账编号为"' + deleteIds + '"的数据项?', "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }
    )
      .then(function () {
    type: "warning"
  }).then(function() {
        // ä¸è°ƒç”¨æŽ¥å£ï¼Œåªæ˜¾ç¤ºæˆåŠŸæç¤º
        proxy.$modal.msgSuccess("删除成功");
        getList();
      })
      .catch(() => {});
  }).catch(() => {});
  }
  /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
在上述文件截断后对比
src/views/qualityManagement/nonconformingManagement/components/formDia.vue src/views/qualityManagement/processInspection/components/formDia.vue src/views/qualityManagement/processInspection/index.vue src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue src/views/qualityManagement/rawMaterialInspection/components/formDia.vue src/views/qualityManagement/rawMaterialInspection/index.vue src/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue src/views/reportAnalysis/PSIDataAnalysis/components/ProductTypeSwitch.vue src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue src/views/reportAnalysis/PSIDataAnalysis/index.vue src/views/reportAnalysis/PSIDataAnalysis/psiNavigation.js src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue src/views/reportAnalysis/productionAnalysis/components/PanelHeader.vue src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue src/views/reportAnalysis/productionAnalysis/components/center-center.vue src/views/reportAnalysis/productionAnalysis/components/center-top.vue src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue src/views/reportAnalysis/productionAnalysis/components/left-top.vue src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue src/views/reportAnalysis/productionAnalysis/components/right-top.vue src/views/reportAnalysis/productionAnalysis/productionNavigation.js src/views/reportAnalysis/reportManagement/index.vue src/views/safeProduction/dangerInvestigation/index.vue src/views/safeProduction/safetyTrainingAssessment/index.vue src/views/salesManagement/deliveryLedger/index.vue src/views/salesManagement/indicatorStats/index.vue src/views/salesManagement/receiptPaymentLedger/index.vue src/views/salesManagement/returnOrder/components/detailDia.vue src/views/salesManagement/returnOrder/components/formDia.vue src/views/salesManagement/returnOrder/index.vue src/views/salesManagement/salesLedger/index.vue src/views/salesManagement/salesQuotation/index.vue src/views/system/dept/index.vue src/views/system/dict/data.vue src/views/system/dict/index.vue src/views/system/menu/index.vue src/views/system/post/index.vue src/views/system/role/index.vue src/views/systemArchitecture/index.vue vite.config.js