chenhj
昨天 cac302f302084ab310d0e35339f30966a4829a4e
Merge remote-tracking branch 'origin/dev_New' into dev_New

# Conflicts:
# src/views/equipmentManagement/repair/index.vue
# vite.config.js
已添加63个文件
已重命名2个文件
已修改118个文件
已删除14个文件
37538 ■■■■■ 文件已修改
multiple/config.json 404 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productModel.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productProcess.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/noticeManagement.js 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/shipmentReview.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/measurementEquipment.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/accounting.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/loanManagement.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockIn.js 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInRecord.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInventory.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockManage.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockOut.js 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/employeeRecord.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/onboarding.js 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffAnalytics.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffContract.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffLeave.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffOnJob.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementInvoiceLedger.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementReport.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRoute.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRouteItem.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productBom.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productStructure.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductInput.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductMain.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductOutput.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionReporting.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/workOrder.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/metricMaintenance.js 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/qualityTestStandardBinding.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/reportAnalysis/qualityReport.js 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/indicatorStats.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/receiptPayment.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/message.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/post.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/viewIndex.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard2.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard3.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FileListDialog.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PageHeader/index.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/QRCodeGenerator/index.vue 859 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/NotificationCenter/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/enterpriseBook/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/noticeManagement/index.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/summary/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue 1076 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/sealManagement/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/shipmentReview/fileList.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/shipmentReview/index.vue 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Form.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/formDia.vue 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/components/rowClickData.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/index.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/MaintainForm.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/RepairForm.vue 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/MaintainModal.vue 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/RepairModal.vue 190 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/index.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/spareParts/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanForm.vue 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanModal.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/formDia.vue 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/formDia.vue 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/accounting/index.vue 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/Form.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/Modal.vue 192 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/index.vue 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/financialStatements/index.vue 194 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/loanManagement/Modal.vue 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/loanManagement/index.vue 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/Form.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/Modal.vue 192 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/filesDia.vue 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/index.vue 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/index.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/index.vue 679 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Subtract.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 342 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/ledger/Form.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/analytics/index.vue 228 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/components/formDia.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/filesDia.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/components/formDia.vue 425 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/index.vue 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue 97 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/RenewContract.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/Show.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/index.vue 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/components/formDiaXJHT.vue 386 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/index.vue 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/components/formDia.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/scheduling/index.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/selfService/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/advancedPriceManagement/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/arrivalManagement/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/components/Modal.vue 935 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/indexOld.vue 727 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentEntry/index.vue 370 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentHistory/index.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/priceManagement/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/fileList.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 3248 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementPlan/index.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementReport/index.vue 834 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/purchaseOrder/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/qualityInspection/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/returnManagement/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/transferManagement/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productManagement/productIdentifier/index.vue 1187 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/components/formDia.vue 121 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/index.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Edit.vue 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/ItemsForm.vue 531 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/New.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 876 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/StructureEdit.vue 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/index.vue 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/formDia.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/index.vue 464 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 567 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/Input.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/Output.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/formDia.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 822 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 653 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/filesDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 504 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index.vue 1161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index0.vue 415 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/formDia.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index.vue 843 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/projectProfit/index.vue 186 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 1908 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/deliveryLedger/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/indicatorStats/index.vue 377 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceLedger/index.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceRegistration/index.vue 1254 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/orderManagement/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/paymentShipping/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 386 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentHistory/index.vue 123 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/fileList.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 254 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 639 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salespersonManagement/index.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 451 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 1230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json
@@ -10,412 +10,12 @@
  "TEST": {
    "env": {
      "VITE_APP_TITLE": "中小企业数字化转型二级套餐包",
      "VITE_BASE_API": "http://114.132.189.42:9036",
      "VITE_JAVA_API": "http://114.132.189.42:9037"
      "VITE_BASE_API": "http://1.15.17.182:9003",
      "VITE_JAVA_API": "http://1.15.17.182:9002"
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/ZGLTLogo.png",
    "favicon": "favicon/favicon.ico"
  },
  "LC": {
    "env": {
      "VITE_APP_TITLE": "中小企业数字化转型二级套餐包",
      "VITE_BASE_API": "http://114.132.189.42:9036",
      "VITE_JAVA_API": "http://114.132.189.42:9037"
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/LCLogo.png",
    "favicon": "favicon/favicon.ico"
  },
  "WDSY": {
    "env": {
      "VITE_APP_TITLE": "伟德实业信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:8068",
      "VITE_JAVA_API": "http://114.132.189.42:8085"
    },
    "screen": "screen/WDSYView.png",
    "logo": "logo/WDSYLogo.png",
    "favicon": "favicon/WDSYico.ico"
  },
  "JZYJ": {
    "env": {
      "VITE_APP_TITLE": "基智油井信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:8078",
      "VITE_JAVA_API": "http://114.132.189.42:8086"
    },
    "screen": "screen/JZYJView.png",
    "logo": "logo/JZYJLogo.png",
    "favicon": "favicon/JZYJico.ico"
  },
  "ZQHX": {
    "env": {
      "VITE_APP_TITLE": "中强恒兴信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:1234",
      "VITE_JAVA_API": "http://114.132.189.42:8080"
    },
    "screen": "screen/ZQHXView.png",
    "logo": "logo/ZQHXLogo.png",
    "favicon": "favicon/ZQHXico.ico"
  },
  "XYHB": {
    "env": {
      "VITE_APP_TITLE": "宣屹环保信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9052",
      "VITE_JAVA_API": "http://114.132.189.42:9051"
    },
    "screen": "screen/XYHBView.png",
    "logo": "logo/XYHBLogo.png",
    "favicon": "favicon/XYHBico.ico"
  },
  "BHMY": {
    "env": {
      "VITE_APP_TITLE": "博宏煤业信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9070",
      "VITE_JAVA_API": "http://114.132.189.42:9069"
    },
    "screen": "screen/ZQHXView.png",
    "logo": "logo/BHMYLogo.png",
    "favicon": "favicon/BHMY.ico"
  },
  "HHKJ": {
    "env": {
      "VITE_APP_TITLE": "恒晖科技信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9046",
      "VITE_JAVA_API": "http://114.132.189.42:9047"
    },
    "screen": "screen/HHKJView.png",
    "logo": "logo/HHKJLogo.png",
    "favicon": "favicon/HHKJIco.ico"
  },
  "RZNY": {
    "env": {
      "VITE_APP_TITLE": "锐择能源信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9058",
      "VITE_JAVA_API": "http://114.132.189.42:9057"
    },
    "screen": "screen/RZNYView.png",
    "logo": "logo/RZNYLogo.png",
    "favicon": "favicon/RZNY.ico"
  },
  "TJXM": {
    "env": {
      "VITE_APP_TITLE": "泰江洗煤信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9064",
      "VITE_JAVA_API": "http://114.132.189.42:9063"
    },
    "screen": "screen/TJXMView.png",
    "logo": "logo/TJXMLogo.png",
    "favicon": "favicon/TJXM.ico"
  },
  "HYSN": {
    "env": {
      "VITE_APP_TITLE": "弘也水泥信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9034",
      "VITE_JAVA_API": "http://114.132.189.42:9035"
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/HYSNLogo.png",
    "favicon": "favicon/HYSNico.ico"
  },
  "JYHJ": {
    "env": {
      "VITE_APP_TITLE": "金鹰黄金信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9030",
      "VITE_JAVA_API": "http://114.132.189.42:9031"
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/JYHJLogo.png",
    "favicon": "favicon/JYHJico.ico"
  },
  "DHDC": {
    "env": {
      "VITE_APP_TITLE": "敦煌鼎诚信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9032",
      "VITE_JAVA_API": "http://114.132.189.42:9033"
    },
    "screen": "screen/DHDCView.png",
    "logo": "logo/DHDCLogo.png",
    "favicon": "favicon/DHDCico.ico"
  },
  "MXSC": {
    "env": {
      "VITE_APP_TITLE": "闽兴石材信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9048",
      "VITE_JAVA_API": "http://114.132.189.42:9049"
    },
    "screen": "screen/MXSCBack.png",
    "logo": "logo/MXSCLogo.png",
    "favicon": "favicon/MXSCIco.ico"
  },
  "CJNY": {
    "env": {
      "VITE_APP_TITLE": "创巨能源信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9038",
      "VITE_JAVA_API": "http://114.132.189.42:9039"
    },
    "screen": "screen/MXSCBack.png",
    "logo": "logo/CJNYLogo.png",
    "favicon": "favicon/CJNYico.ico"
  },
  "JSMY": {
    "env": {
      "VITE_APP_TITLE": "金石煤业信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9042",
      "VITE_JAVA_API": "http://114.132.189.42:9043"
    },
    "screen": "screen/MXSCBack.png",
    "logo": "logo/JSMYLogo.png",
    "favicon": "favicon/JSMYico.ico"
  },
  "JSYNY": {
    "env": {
      "VITE_APP_TITLE": "锦盛源能源信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9074",
      "VITE_JAVA_API": "http://114.132.189.42:9073"
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/JSYNYLogo.png",
    "favicon": "favicon/JSYNYico.ico"
  },
  "CMNY": {
    "env": {
      "VITE_APP_TITLE": "创铭能源信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9088",
      "VITE_JAVA_API": "http://114.132.189.42:9087"
    },
    "screen": "screen/DHDCView.png",
    "logo": "logo/CMNYLogo.png",
    "favicon": "favicon/CMNYico.ico"
  },
  "HCKX": {
    "env": {
      "VITE_APP_TITLE": "海川开心食品信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9090",
      "VITE_JAVA_API": "http://114.132.189.42:9089"
    },
    "screen": "screen/HCKXView.png",
    "logo": "logo/HCKXLogo.png",
    "favicon": "favicon/HCKXico.ico"
  },
  "JLSN": {
    "env": {
      "VITE_APP_TITLE": "锦龙水泥信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9094",
      "VITE_JAVA_API": "http://114.132.189.42:9093"
    },
    "screen": "screen/JLSNView.png",
    "logo": "logo/JLSNLogo.png",
    "favicon": "favicon/JLSNico.ico"
  },
  "BDSM": {
    "env": {
      "VITE_APP_TITLE": "博达商贸信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9096",
      "VITE_JAVA_API": "http://114.132.189.42:9095"
    },
    "screen": "screen/BDSMView.png",
    "logo": "logo/BDSMLogo.png",
    "favicon": "favicon/BDSMico.ico"
  },
  "HXGY": {
    "env": {
      "VITE_APP_TITLE": "汇星钙业信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9098",
      "VITE_JAVA_API": "http://114.132.189.42:9097"
    },
    "screen": "screen/HXGYView.png",
    "logo": "logo/HXGYLogo.png",
    "favicon": "favicon/HXGYico.ico"
  },
  "ZDXM": {
    "env": {
      "VITE_APP_TITLE": "昭德型煤信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9100",
      "VITE_JAVA_API": "http://114.132.189.42:9096"
    },
    "screen": "screen/ZDXMView.png",
    "logo": "logo/ZDXMLogo.png",
    "favicon": "favicon/ZDXMico.ico"
  },
  "HSX": {
    "env": {
      "VITE_APP_TITLE": "湟水峡农业发展信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9101",
      "VITE_JAVA_API": "http://114.132.189.42:9098"
    },
    "screen": "screen/HSXView.png",
    "logo": "logo/HSXLogo.png",
    "favicon": "favicon/HSXico.ico"
  },
  "NYDL": {
    "env": {
      "VITE_APP_TITLE": "南洋电缆产链通信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9036",
      "VITE_JAVA_API": "http://114.132.189.42:9037"
    },
    "screen": "screen/NYDLView.png",
    "logo": "logo/NYDLLogo.png",
    "favicon": "favicon/NYDLico.ico"
  },
  "HCMY": {
    "env": {
      "VITE_APP_TITLE": "浩成煤业信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9103",
      "VITE_JAVA_API": "http://114.132.189.42:9094"
    },
    "screen": "screen/HCMYView.png",
    "logo": "logo/HCMYLogo.png",
    "favicon": "favicon/HCMYico.ico"
  },
  "HGJJ": {
    "env": {
      "VITE_APP_TITLE": "汇国洁净型煤信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9107",
      "VITE_JAVA_API": "http://114.132.189.42:9090"
    },
    "screen": "screen/HGJJView.png",
    "logo": "logo/HGJJLogo.png",
    "favicon": "favicon/HGJJico.ico"
  },
  "MKZS": {
    "env": {
      "VITE_APP_TITLE": "模凯再生信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9111",
      "VITE_JAVA_API": "http://114.132.189.42:9086"
    },
    "screen": "screen/MKZSView.png",
    "logo": "logo/MKZSLogo.png",
    "favicon": "favicon/MKZSico.ico"
  },
  "HSMY": {
    "env": {
      "VITE_APP_TITLE": "华顺镁业信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9115",
      "VITE_JAVA_API": "http://114.132.189.42:9082"
    },
    "screen": "screen/HSMYView.png",
    "logo": "logo/HSMYLogo.png",
    "favicon": "favicon/HSMYico.ico"
  },
  "DHHB": {
    "env": {
      "VITE_APP_TITLE": "丹海环保信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9117",
      "VITE_JAVA_API": "http://114.132.189.42:9080"
    },
    "screen": "screen/DHHBView.png",
    "logo": "logo/DHHBLogo.png",
    "favicon": "favicon/DHHBico.ico"
  },
  "PHMK": {
    "env": {
      "VITE_APP_TITLE": "普禾煤矿信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9119",
      "VITE_JAVA_API": "http://114.132.189.42:9078"
    },
    "screen": "screen/PHMKView.png",
    "logo": "logo/PHMKLogo.png",
    "favicon": "favicon/PHMKico.ico"
  },
  "TYMK": {
    "env": {
      "VITE_APP_TITLE": "通源煤矿信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9121",
      "VITE_JAVA_API": "http://114.132.189.42:9076"
    },
    "screen": "screen/TYMKView.png",
    "logo": "logo/TYMKLogo.png",
    "favicon": "favicon/TYMKico.ico"
  },
  "LQM": {
    "env": {
      "VITE_APP_TITLE": "老琪麦食品信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9123",
      "VITE_JAVA_API": "http://114.132.189.42:9074"
    },
    "screen": "screen/LQMView.png",
    "logo": "logo/LQMLogo.png",
    "favicon": "favicon/LQMico.ico"
  },
  "ZYRQ": {
    "env": {
      "VITE_APP_TITLE": "众源燃气信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9123",
      "VITE_JAVA_API": "http://114.132.189.42:9031"
    },
    "screen": "screen/ZYRQView.png",
    "logo": "logo/ZYRQLogo.png",
    "favicon": "favicon/ZYRQico.ico"
  },
  "RTSW": {
    "env": {
      "VITE_APP_TITLE": "润泰生物信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9066",
      "VITE_JAVA_API": "http://114.132.189.42:9064"
    },
    "screen": "screen/RTSWView.png",
    "logo": "logo/RTSWLogo.png",
    "favicon": "favicon/RTSWico.ico"
  },
  "HXSJ": {
    "env": {
      "VITE_APP_TITLE": "华玺砂浆信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9066",
      "VITE_JAVA_API": "http://114.132.189.42:9062"
    },
    "screen": "screen/HXSJView.png",
    "logo": "logo/HXSJLogo.png",
    "favicon": "favicon/HXSJico.ico"
  },
  "QLMC": {
    "env": {
      "VITE_APP_TITLE": "祁连牧场信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9066",
      "VITE_JAVA_API": "http://114.132.189.42:9060"
    },
    "screen": "screen/QLMCView.png",
    "logo": "logo/QLMCLogo.png",
    "favicon": "favicon/QLMCico.ico"
  },
  "AYNM": {
    "env": {
      "VITE_APP_TITLE": "安佑农牧信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9066",
      "VITE_JAVA_API": "http://114.132.189.42:9058"
    },
    "screen": "screen/AYNMView.png",
    "logo": "logo/AYNMLogo.png",
    "favicon": "favicon/AYNMico.ico"
  },
  "JMSL": {
    "env": {
      "VITE_APP_TITLE": "金茂塑料包装信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9066",
      "VITE_JAVA_API": "http://114.132.189.42:9058"
    },
    "screen": "screen/JMSLView.png",
    "logo": "logo/JMSLLogo.png",
    "favicon": "favicon/JMSLico.ico"
  },
  "TJKH": {
    "env": {
      "VITE_APP_TITLE": "天津凯华信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9066",
      "VITE_JAVA_API": "http://114.132.189.42:9058"
    },
    "screen": "screen/TJKHView.png",
    "logo": "logo/TJKHLogo.png",
    "favicon": "favicon/TJKHico.ico"
  },
  "DZYS": {
    "env": {
      "VITE_APP_TITLE": "东泽印刷信息管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9066",
      "VITE_JAVA_API": "http://114.132.189.42:9046"
    },
    "screen": "screen/DZYSView.png",
    "logo": "logo/DZYSLogo.png",
    "favicon": "favicon/DZYSico.ico"
  },
  "screen": "/src/assets/images/login-background.png",
  "logo": "/src/assets/logo/logo.png",
src/api/basicData/productModel.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import request from "@/utils/request.js";
export function productModelList(query) {
    return request({
        url: '/basic/product/pageModel',
        method: 'get',
        params: query
    })
}
src/api/basicData/productProcess.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from '@/utils/request'
// å·¥åºåˆ—表分页查询
export function productProcessListPage(query) {
  return request({
    url: '/productProcess/listPage',
    method: 'get',
    params: query
  })
}
src/api/collaborativeApproval/noticeManagement.js
@@ -1,78 +1,78 @@
import request from '@/utils/request'
import request from "@/utils/request";
// æŸ¥è¯¢å…¬å‘Šåˆ—表
export function listNotice(query) {
    return request({
        url: '/collaborativeApproval/notice/page',
        method: 'get',
        params: query
    })
  return request({
    url: "/collaborativeApproval/notice/page",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢å…¬å‘Šè¯¦ç»†
export function getNotice(noticeId) {
    return request({
        url: '/collaborativeApproval/notice/' + noticeId,
        method: 'get'
    })
  return request({
    url: "/collaborativeApproval/notice/" + noticeId,
    method: "get",
  });
}
// æ–°å¢žå…¬å‘Š
export function addNotice(data) {
    return request({
        url: '/collaborativeApproval/notice/add',
        method: 'post',
        data: data
    })
  return request({
    url: "/collaborativeApproval/notice/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹å…¬å‘Š
export function updateNotice(data) {
    return request({
        url: '/collaborativeApproval/notice/update',
        method: 'put',
        data: data
    })
  return request({
    url: "/collaborativeApproval/notice/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å…¬å‘Š
export function delNotice(ids) {
    return request({
        url: '/collaborativeApproval/notice/' + ids,
        method: 'delete',
    })
  return request({
    url: "/collaborativeApproval/notice/" + ids,
    method: "delete",
  });
}
// èŽ·å–å…¬å‘Šæ•°é‡
export function getCount() {
    return request({
        url: '/collaborativeApproval/notice/count',
        method: 'get',
    })
  return request({
    url: "/collaborativeApproval/notice/count",
    method: "get",
  });
}
// æŸ¥è¯¢å…¬å‘Šç±»åž‹åˆ—表
export function listNoticeType() {
    return request({
        url: '/noticeType/list',
        method: 'get'
    })
  return request({
    url: "/noticeType/list",
    method: "get",
  });
}
// æ–°å¢žå…¬å‘Šç±»åž‹
export function addNoticeType(data) {
    return request({
        url: '/noticeType/add',
        method: 'post',
        data: data
    })
  return request({
    url: "/noticeType/add",
    method: "post",
    data: data,
  });
}
// åˆ é™¤å…¬å‘Šç±»åž‹
export function delNoticeType(id) {
    return request({
        url: '/noticeType/del',
        method: 'delete',
        data: { id }
    })
}
  return request({
    url: "/noticeType/del",
    method: "delete",
    data: [id],
  });
}
src/api/collaborativeApproval/shipmentReview.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
// å‘货审批
import request from "@/utils/request";
// èŽ·å–å‘è´§å®¡æ‰¹åˆ—è¡¨
export function getShipmentApprovalList(query) {
    return request({
        url: '/shipmentApproval/listPage',
        method: 'get',
        params: query,
    })
}
// å‘货申请批准
// /shipmentApproval/update
export function approveShipment(query) {
    return request({
        url: '/shipmentApproval/update',
        method: 'post',
        data: query,
    })
}
src/api/equipmentManagement/measurementEquipment.js
@@ -32,4 +32,24 @@
    method: "post",
    data: query,
  });
}
// è®¡é‡å™¨å…·å°è´¦-新增
// /measuringInstrumentLedger/add
export function addMeasuringInstrumentLedger(data){
    return request({
        url:"/measuringInstrumentLedger/add",
        method:"post",
        data
    })
}
// è®¡é‡å™¨å…·å°è´¦-编辑
// /measuringInstrumentLedger/update
export function updateMeasuringInstrumentLedger(data){
    return request({
        url:"/measuringInstrumentLedger/update",
        method:"post",
        data
    })
}
src/api/financialManagement/accounting.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// èŽ·å–å›ºå®šèµ„äº§æ±‡æ€»ä¿¡æ¯
export const getAccountingTotal = (params) => {
  return request({
    url: "/accounting/total",
    method: "get",
    params,
  });
};
// èŽ·å–è®¾å¤‡ç±»åž‹åˆ†å¸ƒæ•°æ®ï¼ˆé¥¼å›¾å’ŒæŠ˜çº¿å›¾ï¼‰
export const getDeviceTypeDistribution = (params) => {
  return request({
    url: "/accounting/deviceTypeDistribution",
    method: "get",
    params,
  });
};
// èŽ·å–æŠ˜æ—§è®¡ç®—æ•°æ®ï¼ˆè¡¨æ ¼æ•°æ®ï¼‰
export const getCalculateDepreciation = (params) => {
  return request({
    url: "/accounting/calculateDepreciation",
    method: "get",
    params,
  });
};
src/api/financialManagement/loanManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
import request from "@/utils/request";
// æŸ¥è¯¢åˆ—表
export const listPage = (params) => {
  return request({
    url: "/borrowInfo/listPage",
    method: "get",
    params,
  });
};
// æ–°å¢ž
export function add(data) {
  return request({
    url: "/borrowInfo/add",
    method: "post",
    data: data,
  });
}
// ç¼–辑
export function update(data) {
  return request({
    url: "/borrowInfo/update",
    method: "post",
    data: data,
  });
}
// åˆ é™¤
export const delAccountLoan = (data) => {
  return request({
    url: "/borrowInfo/delete",
    method: "delete",
    data,
  });
};
src/api/inventoryManagement/stockIn.js
@@ -9,6 +9,50 @@
    });
};
// æŸ¥è¯¢ç”Ÿäº§å…¥åº“信息列表
export const getStockInPageByProduction = (params) => {
    return request({
        url: "/stockin/listPageByProduction",
        method: "get",
        params,
    });
};
// æŸ¥è¯¢ç”Ÿäº§å…¥åº“信息列表
export const getStockInPageByProductProduction = (params) => {
    return request({
        url: "/stockin/listPageByProductProduction",
        method: "get",
        params,
    });
};
// å‡ºåº“台账-查询自定义入库信息列表
export const getStockInPageByCustom = (params) => {
    return request({
        url: "/stockmanagement/listPageByCustom",
        method: "get",
        params,
    });
};
// å…¥åº“管理-查询自定义入库信息列表
export const getInPageByCustom = (params) => {
    return request({
        url: "/stockin/listPageByCustom",
        method: "get",
        params,
    });
};
// å‡ºåº“台账-查询生产出库信息列表
export const getStockInPageByProduct = (params) => {
    return request({
        url: "/stockmanagement/listPageByProduct",
        method: "get",
        params,
    });
};
// ä¿®æ”¹å…¥åº“存信息
export const updateStockIn = (data) => {
    return request({
@@ -26,6 +70,14 @@
        data,
    });
};
// ä¿®æ”¹ææ–™åº“存信息
export const updateManagementByCustom = (data) => {
    return request({
        url: "/stockin/updateManagementByCustom ",
        method: "post",
        data,
    });
};
// æ–°å¢žå•†å“å…¥åº“信息
export function addSutockIn(data) {
@@ -36,6 +88,32 @@
    })
}
// æ–°å¢žè‡ªå®šä¹‰å…¥åº“信息
export function addStockInCustom(data) {
    return request({
        url: '/stockin/addCustom',
        method: 'post',
        data: data
    })
}
// ç¼–辑自定义入库信息
export function updateStockInCustom(data) {
    return request({
        url: '/stockin/updateCustom',
        method: 'post',
        data: data
    })
}
// ç¼–辑成品入库信息
export function updateProduct(data) {
    return request({
        url: '/stockin/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤å…¥åº“信息
export function delStockIn(ids) {
    return request({
@@ -45,6 +123,15 @@
    })
}
// åˆ é™¤è‡ªå®šä¹‰å…¥åº“信息
export function delStockInCustom(ids) {
    return request({
        url: '/stockin/delteCustom',
        method: 'post',
        data: ids
    })
}
// å¯¼å‡ºå…¥åº“信息
export function exportStockIn(query) {
    return request({
src/api/inventoryManagement/stockInRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from "@/utils/request";
// æŸ¥è¯¢å…¥åº“信息列表
export const getStockInRecordListPage = (params) => {
    return request({
        url: "/stockInRecord/listPage",
        method: "get",
        params,
    });
};
export const updateStockInRecord = (id, data) => {
    return request({
        url: "/stockInRecord/" + id,
        method: "put",
        data: data,
    });
};
export const batchDeleteStockInRecords = (ids) => {
    return request({
        url: "/stockInRecord",
        method: "delete",
        data: ids,
    });
};
src/api/inventoryManagement/stockInventory.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from "@/utils/request.js";
// åˆ†é¡µæŸ¥è¯¢åº“存记录列表
export const getStockInventoryListPage = (params) => {
    return request({
        url: "/stockInventory/pagestockInventory",
        method: "get",
        params,
    });
};
// åˆ›å»ºåº“存记录
export const createStockInventory = (params) => {
    return request({
        url: "/stockInventory/addstockInventory",
        method: "post",
        params,
    });
};
// å‡å°‘库存记录
export const subtractStockInventory = (params) => {
    return request({
        url: "/stockInventory/subtractStockInventory",
        method: "post",
        params,
    });
};
src/api/inventoryManagement/stockManage.js
@@ -9,6 +9,31 @@
    });
};
// æŸ¥è¯¢ç”Ÿäº§å…¥åº“库存信息列表
export const getStockManagePageByProduction = (params) => {
    return request({
        url: "/stockin/listPageCopyByProduction",
        method: "get",
        params,
    });
};
// æŸ¥è¯¢æˆå“åº“存信息列表
export const getStockManageProduction = (params) => {
    return request({
        url: "/stockin/listPageProductionStock",
        method: "get",
        params,
    });
};
// æŸ¥è¯¢è‡ªå®šä¹‰å…¥åº“库存信息列表
export const getStockManagePageByCustom = (params) => {
    return request({
        url: "/stockin/listPageCopyByCustom",
        method: "get",
        params,
    });
};
// ä¿®æ”¹åº“存信息
export const updateStockManage = (data) => {
@@ -38,7 +63,7 @@
    })
}
//出库接口
// å‡ºåº“管理-领用接口
export const stockOut = (data) => {
    return request({
        url: '/stockmanagement/stockout',
src/api/inventoryManagement/stockOut.js
@@ -1,47 +1,19 @@
import request from "@/utils/request";
//查询出库列表
// å‡ºåº“台账-采购出库查询出库列表
export const getStockOutPage = (params) => {
    return request({
        url: "/stockmanagement/listPage",
        url: "/stockOutRecord/listPage",
        method: "get",
        params,
    });
};
//新增出库信息
export const addStockOut = (data) => {
    return request({
        url: '/stockout/add',
        method: 'post',
        data: data
    })
}
//修改出库信息
export const updateStockOut = (data) => {
    return request({
        url: "/stockout/update",
        method: "put",
        data,
    });
}
//删除出库信息
export const delStockOut = (ids) => {
    return request({
        url: '/stockmanagement/del',
        method: 'post',
        data: ids
    })
        url: "/stockOutRecord",
        method: "delete",
        data: ids,
    });
}
//导出出库信息
export const exportStockOut = (query) => {
    return request({
        url: '/stockmanagement/export',
        method: 'get',
        params: query,
        responseType: 'blob'
    })
}
src/api/personnelManagement/employeeRecord.js
@@ -15,4 +15,13 @@
        method: 'get',
        params: query,
    })
}
// å¯¼å‡ºåˆåŒå‰¯æœ¬
export function staffOnJobExportCopy(data) {
    return request({
        url: '/staff/staffOnJob/exportCopy',
        method: 'post',
        data: data,
    })
}
src/api/personnelManagement/onboarding.js
ÎļþÒÑɾ³ý
src/api/personnelManagement/staffAnalytics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from "@/utils/request.js";
// ç¦»èŒåŽŸå› åˆ†æž
export function findStaffLeaveReasonAnalysis() {
    return request({
        url: "/staff/analytics/reason",
        method: "get"
    });
}
// 12个月员工流动流失率分析
export function findStaffAnalysisMonthlyTurnoverRateFor12Months() {
    return request({
        url: "/staff/analytics/monthly_turnover_rate",
        method: "get"
    });
}
export function findStaffAnalysisTotalStatistic() {
    return request({
        url: "/staff/analytics/total_statistic",
        method: "get"
    });
}
src/api/personnelManagement/staffContract.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from "@/utils/request.js";
export function findStaffContractListPage(query) {
    return request({
        url: "/staff/staffContract/listPage",
        method: "get",
        params: query,
    });
}
src/api/personnelManagement/staffLeave.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
import request from "@/utils/request.js";
export function findStaffLeaveListPage(query) {
    return request({
        url: "/staff/staffLeave/listPage",
        method: "get",
        params: query,
    });
}
export function createStaffLeave(data) {
    return request({
        url: "/staff/staffLeave",
        method: "post",
        data: data,
    });
}
export function updateStaffLeave(id, data) {
    return request({
        url: "/staff/staffLeave/" + id,
        method: "put",
        data: data,
    });
}
export function batchDeleteStaffLeaves(data) {
    return request({
        url: "/staff/staffLeave/del",
        method: "delete",
        data: data,
    });
}
src/api/personnelManagement/staffOnJob.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
import request from '@/utils/request'
// æŸ¥è¯¢åœ¨èŒå‘˜å·¥å°è´¦
export function staffOnJobListPage(query) {
    return request({
        url: '/staff/staffOnJob/listPage',
        method: 'get',
        params: query,
    })
}
// æŸ¥è¯¢å‘˜å·¥å…¥èŒä¿¡æ¯
export function staffOnJobInfo(id, query) {
    return request({
        url: '/staff/staffOnJob/' + id,
        method: 'get',
        params: query,
    })
}
// æ–°å¢žå‘˜å·¥
export function createStaffOnJob(params) {
    return request({
        url: "/staff/staffOnJob",
        method: "post",
        data: params,
    });
}
// ä¿®æ”¹å‘˜å·¥
export function updateStaffOnJob(id, params) {
    return request({
        url: "/staff/staffOnJob/" + id,
        method: "put",
        data: params,
    });
}
// åˆ é™¤å‘˜å·¥
export function batchDeleteStaffOnJobs(query) {
    return request({
        url: "/staff/staffOnJob/del",
        method: "delete",
        data: query,
    });
}
// ç»­ç­¾åˆåŒ
export function renewContract(id, params) {
    return request({
        url: "/staff/staffOnJob/renewContract/" + id,
        method: "post",
        data: params,
    });
}
src/api/procurementManagement/procurementInvoiceLedger.js
@@ -61,7 +61,7 @@
// æŸ¥è¯¢åˆ—表
export function invoiceListPage(query) {
  return request({
    url: "/purchase/registration/listPage",
    url: "/sales/product/listPagePurchaseLedger",
    method: "get",
    params: query,
  });
src/api/procurementManagement/procurementReport.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// é‡‡è´­æŠ¥è¡¨é¡µé¢æŽ¥å£
import request from "@/utils/request";
// é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨åˆ†é¡µæŸ¥è¯¢
export function procurementBusinessSummaryListPage(query) {
  return request({
    url: "/procurementBusinessSummary/listPage",
    method: "get",
    params: query,
  });
}
src/api/productionManagement/processRoute.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
// å·¥è‰ºè·¯çº¿é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/processRoute/page",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/processRoute",
    method: "post",
    data: data,
  });
}
export function del(ids) {
  return request({
    url: '/processRoute/' + ids,
    method: 'delete',
  })
}
export function update(data) {
  return request({
    url: '/processRoute',
    method: 'put',
    data: data,
  })
}
// èŽ·å–è¯¦æƒ…
export function getById(id) {
  return request({
    url: `/processRoute/${id}`,
    method: 'get',
  })
}
src/api/productionManagement/processRouteItem.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// å·¥è‰ºè·¯çº¿é¡¹ç›®é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ—表查询
export function findProcessRouteItemList(query) {
  return request({
    url: "/processRouteItem/list",
    method: "get",
    params: query,
  });
}
export function addOrUpdateProcessRouteItem(data) {
  return request({
    url: "/processRouteItem",
    method: "post",
    data: data,
  });
}
// æŽ’序接口
export function sortProcessRouteItem(data) {
  return request({
    url: "/processRouteItem/sort",
    method: "post",
    data: data,
  });
}
// æ‰¹é‡åˆ é™¤æŽ¥å£
export function batchDeleteProcessRouteItem(ids) {
  // å°†id数组转换为逗号分隔的字符串,拼接到URL后面
  const idsStr = Array.isArray(ids) ? ids.join(",") : ids;
  return request({
    url: `/processRouteItem/batchDelete/${idsStr}`,
    method: "delete",
  });
}
src/api/productionManagement/productBom.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
// äº§å“BOM页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/productBom/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢ž
export function add(data) {
  return request({
    url: "/productBom/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹
export function update(data) {
  return request({
    url: "/productBom/update",
    method: "put",
    data: data,
  });
}
// æ‰¹é‡åˆ é™¤
export function batchDelete(ids) {
  return request({
    url: "/productBom/batchDelete",
    method: "delete",
    data: ids,
  });
}
// æ ¹æ®äº§å“åž‹å·ID查询BOM
export function getByModel(productModelId) {
  return request({
    url: "/productBom/getByModel",
    method: "get",
    params: { productModelId },
  });
}
src/api/productionManagement/productProcessRoute.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
// å·¥è‰ºè·¯çº¿é¡¹ç›®é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ—表查询
export function findProductProcessRouteItemList(query) {
  return request({
    url: "/productProcessRoute/list",
    method: "get",
    params: query,
  });
}
export function addOrUpdateProductProcessRouteItem(data) {
  return request({
    url: "/productProcessRoute/updateRouteItem",
    method: "post",
    data: data,
  });
}
// ç”Ÿäº§è®¢å•下:新增工艺路线项目
export function addRouteItem(data) {
  return request({
    url: "/productProcessRoute/addRouteItem",
    method: "post",
    data,
  });
}
// èŽ·å–ç”Ÿäº§è®¢å•å…³è”çš„å·¥è‰ºè·¯çº¿ä¸»ä¿¡æ¯
export function listMain(orderId) {
  return request({
    url: "/productProcessRoute/listMain",
    method: "get",
    params: { orderId },
  });
}
// åˆ é™¤å·¥è‰ºè·¯çº¿é¡¹ç›®ï¼ˆè·¯ç”±åŽæ‹¼æŽ¥ id)
export function deleteRouteItem(id) {
  return request({
    url: `/productProcessRoute/deleteRouteItem/${id}`,
    method: "delete",
  });
}
// ç”Ÿäº§è®¢å•下:排序工艺路线项目
export function sortRouteItem(data) {
  return request({
    url: "/productProcessRoute/sortRouteItem",
    method: "post",
    data,
  });
}
src/api/productionManagement/productStructure.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
// äº§å“ç»“构页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function queryList(id) {
  return request({
    url: "/productStructure/listBybomId/" + id,
    method: "get",
  });
}
export function add(data) {
  return request({
    url: "/productStructure",
    method: "post",
    data: data,
  });
}
src/api/productionManagement/productionOrder.js
@@ -9,6 +9,69 @@
    params: query,
  });
}
export function productOrderListPage(query) {
  return request({
    url: "/productOrder/page",
    method: "get",
    params: query,
  });
}
// ç”Ÿäº§è®¢å•-按产品型号查询可用工艺路线列表
export function listProcessRoute(query) {
  return request({
    url: "/productOrder/listProcessRoute",
    method: "get",
    params: query,
  });
}
// ç”Ÿäº§è®¢å•-绑定工艺路线
export function bindingRoute(data) {
  return request({
    url: "/productOrder/bindingRoute",
    method: "post",
    data,
  });
}
// ç”Ÿäº§è®¢å•-查询产品结构列表
export function listProcessBom(query) {
  return request({
    url: "/productOrder/listProcessBom",
    method: "get",
    params: query,
  });
}
// èŽ·å–ç‚’æœºæ­£åœ¨å·¥ä½œé‡æ•°æ®
export function schedulingList(query) {
  return request({
    url: "/salesLedger/scheduling/list",
    method: "get",
    params: query,
  });
}
// ä¿å­˜ç‚’机设置
export function addSpeculatTrading(data) {
  return request({
    url: "/salesLedger/scheduling/addSpeculatTrading",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹ç‚’机设置
export function updateSpeculatTrading(data) {
  return request({
    url: "/salesLedger/scheduling/updateSpeculatTrading",
    method: "post",
    data: data,
  });
}
// ç”Ÿäº§æ´¾å·¥
export function productionDispatch(query) {
  return request({
@@ -16,4 +79,38 @@
    method: "post",
    data: query,
  });
}
// è‡ªåŠ¨æ´¾å·¥
export function productionDispatchList(query) {
  return request({
    url: "/salesLedger/scheduling/productionDispatchList",
    method: "post",
    data: query,
  });
}
// æŸ¥è¯¢æŸè€—率
export function getLossRate() {
  return request({
    url: "/salesLedger/scheduling/loss",
    method: "get",
  });
}
// æ–°å¢žæŸè€—率
export function addLossRate(data) {
  return request({
    url: "/salesLedger/scheduling/addLoss",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹æŸè€—率
export function updateLossRate(data) {
  return request({
    url: "/salesLedger/scheduling/updateLoss",
    method: "post",
    data: data,
  });
}
src/api/productionManagement/productionProcess.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
// å·¥åºé¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/productProcess/listPage",
    method: "get",
    params: query,
  });
}
export function processList(query) {
  return request({
    url: "/productProcess/list",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/productProcess",
    method: "post",
    data: data,
  });
}
export function del(data) {
  return request({
    url: '/productProcess/batchDelete',
    method: 'delete',
    data: data,
  })
}
export function update(data) {
  return request({
    url: '/productProcess/update',
    method: 'put',
    data: data,
  })
}
// å·¥åºæŸ¥è¯¢
export function list() {
    return request({
        url: "/productProcess/list",
        method: "get",
    });
}
// å¯¼å…¥æ•°æ®
export function importData(data) {
  return request({
    url: "/productProcess/importData",
    method: "post",
    data: data,
  });
}
// ä¸‹è½½æ¨¡æ¿
export function downloadTemplate() {
  return request({
    url: "/productProcess/downloadTemplate",
    method: "post",
    responseType: "blob",
  });
}
src/api/productionManagement/productionProductInput.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§æŠ•入页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductInputListPage(query) {
    return request({
        url: "/productionProductInput/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionProductMain.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§æŠ¥å·¥é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductMainListPage(query) {
    return request({
        url: "/productionProductMain/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionProductOutput.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§äº§å‡ºé¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductOutputListPage(query) {
    return request({
        url: "/productionProductOutput/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionReporting.js
@@ -32,4 +32,12 @@
    method: "post",
    data: query,
  });
}
}
// ç”Ÿäº§æŠ¥å·¥-删除
export function productionReportDelete(query) {
  return request({
    url: "/productionProductMain/delete",
    method: "delete",
    data: query,
  });
}
src/api/productionManagement/workOrder.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import request from "@/utils/request";
export function productWorkOrderPage(query) {
  return request({
    url: "/productWorkOrder/page",
    method: "get",
    params: query,
  });
}
export function updateProductWorkOrder(data) {
  return request({
    url: "/productWorkOrder/updateProductWorkOrder",
    method: "post",
    data: data,
  });
}
export function addProductMain(data) {
  return request({
    url: "/productionProductMain/addProductMain",
    method: "post",
    data: data,
  });
}
src/api/qualityManagement/metricMaintenance.js
@@ -1,45 +1,110 @@
import request from '@/utils/request'
import request from "@/utils/request";
// æŸ¥è¯¢æŒ‡æ ‡åˆ—表
export function qualityTestStandardListPage(query) {
    return request({
        url: '/quality/qualityTestStandard/listPage',
        method: 'get',
        params: query,
    })
  return request({
    url: "/qualityTestStandard/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žæŒ‡æ ‡åˆ—表
export function qualityTestStandardAdd(query) {
    return request({
        url: '/quality/qualityTestStandard/add',
        method: 'post',
        data: query,
    })
  return request({
    url: "/qualityTestStandard/add",
    method: "post",
    data: query,
  });
}
// ä¿®æ”¹æŒ‡æ ‡åˆ—表
export function qualityTestStandardUpdate(query) {
    return request({
        url: '/quality/qualityTestStandard/update',
        method: 'post',
        data: query,
    })
  return request({
    url: "/qualityTestStandard/update",
    method: "post",
    data: query,
  });
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityTestStandardDel(query) {
    return request({
        url: '/quality/qualityTestStandard/del',
        method: 'delete',
        data: query,
    })
  return request({
    url: "/qualityTestStandard/del",
    method: "delete",
    data: query,
  });
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityInspectDetailByProductId(productId) {
    return request({
        url: '/quality/qualityTestStandard/product/' + productId,
        method: 'get',
    })
}
export function qualityInspectDetailByProductId(params) {
  return request({
    url: "/qualityTestStandard/getQualityTestStandardByProductId",
    method: "get",
    params: params,
  });
}
// å¤åˆ¶æ ‡å‡†å‚æ•°
export function qualityTestStandardCopyParam(id) {
  return request({
    url: "/qualityTestStandard/copyParam",
    method: "post",
    data: { id },
  });
}
// æ‰¹é‡å®¡æ ¸ï¼ˆçŠ¶æ€ï¼š1=通过/批准,2=撤销)
// ä¼ å‚:[{ id, state }]
export function qualityTestStandardAudit(data) {
  return request({
    url: "/qualityTestStandard/qualityTestStandardAudit",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:列表(不分页)
export function qualityTestStandardParamList(query) {
  return request({
    url: "/qualityTestStandardParam/list",
    method: "get",
    params: query,
  });
}
// æ ‡å‡†å‚数:新增
export function qualityTestStandardParamAdd(data) {
  return request({
    url: "/qualityTestStandardParam/add",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:修改
export function qualityTestStandardParamUpdate(data) {
  return request({
    url: "/qualityTestStandardParam/update",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:删除(传 id æ•°ç»„)
export function qualityTestStandardParamDel(ids) {
  return request({
    url: "/qualityTestStandardParam/del",
    method: "delete",
    data: ids,
  });
}
// æ ¹æ®æ ‡å‡†ID获取标准参数
export function getQualityTestStandardParamByTestStandardId(testStandardId) {
  return request({
    url: "/qualityTestStandard/getQualityTestStandardParamByTestStandardId",
    method: "get",
    params: { testStandardId },
  });
}
src/api/qualityManagement/qualityTestStandardBinding.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// ç»‘定列表(不分页)
export function qualityTestStandardBindingList(query) {
  return request({
    url: "/qualityTestStandardBinding/list",
    method: "get",
    params: query,
  });
}
// æ–°å¢žç»‘定(支持批量)
export function qualityTestStandardBindingAdd(data) {
  return request({
    url: "/qualityTestStandardBinding/add",
    method: "post",
    data,
  });
}
// åˆ é™¤ç»‘定(传 id æ•°ç»„)
export function qualityTestStandardBindingDel(ids) {
  return request({
    url: "/qualityTestStandardBinding/del",
    method: "delete",
    data: ids,
  });
}
src/api/reportAnalysis/qualityReport.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
import request from '@/utils/request'
// èŽ·å–å„ç±»åž‹å®Œæˆæ•°é‡
export function getInspectStatistics() {
  return request({
    url: '/qualityReport/getInspectStatistics',
    method: 'get'
  })
}
// èŽ·å–è´¨æ£€åˆæ ¼çŽ‡ç»Ÿè®¡
export function getPassRateStatistics() {
  return request({
    url: '/qualityReport/getPassRateStatistics',
    method: 'get'
  })
}
// èŽ·å–æœˆåº¦åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
export function getMonthlyPassRateStatistics(year) {
  return request({
    url: '/qualityReport/getMonthlyPassRateStatistics',
    method: 'get',
    params: { year }
  })
}
// èŽ·å–å¹´åº¦æ€»åˆæ ¼çŽ‡ç»Ÿè®¡æ•°æ®
export function getYearlyPassRateStatistics(year) {
  return request({
    url: '/qualityReport/getYearlyPassRateStatistics',
    method: 'get',
    params: { year }
  })
}
// èŽ·å–æœˆåº¦å®Œæˆæ˜Žç»†æ•°æ®
export function getMonthlyCompletionDetails(year) {
  return request({
    url: '/qualityReport/getMonthlyCompletionDetails',
    method: 'get',
    params: { year }
  })
}
// èŽ·å–çƒ­ç‚¹æ£€æµ‹æŒ‡æ ‡ç»Ÿè®¡
export function getTopParameters(inspectType) {
  return request({
    url: '/qualityReport/getTopParameters',
    method: 'get',
    params: { inspectType }
  })
}
src/api/salesManagement/indicatorStats.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
// æŒ‡æ ‡ç»Ÿè®¡é¡µé¢æŽ¥å£
import request from "@/utils/request";
// å¤´éƒ¨ç»Ÿè®¡æŽ¥å£
export function getTotalStatistics(query) {
  return request({
    url: "/metricStatistics/total",
    method: "get",
    params: query,
  });
}
// æŸ±çŠ¶å›¾æ•°æ®æŽ¥å£
export function getStatisticsTable(query) {
  return request({
    url: "/metricStatistics/statisticsTable",
    method: "get",
    params: query,
  });
}
src/api/salesManagement/receiptPayment.js
@@ -40,7 +40,7 @@
// æŸ¥è¯¢å·²ç»ç»‘定发票的开票台账
export function bindInvoiceNoRegPage(query) {
    return request({
        url: '/receiptPayment/bindInvoiceNoRegPage',
        url: '/sales/product/listPageSalesLedger',
        method: 'get',
        params: query
    })
src/api/system/message.js
@@ -30,7 +30,7 @@
// ä¸€é”®æ ‡è®°æ‰€æœ‰æ¶ˆæ¯ä¸ºå·²è¯»
export function markAllAsRead() {
  return request({
    url: "/system/notice/markAllAsRead",
    url: "/system/notice/readAll",
    method: "post",
  });
}
src/api/system/post.js
@@ -9,6 +9,15 @@
  })
}
export function findPostOptions(query) {
  return request({
    url: '/system/post/optionselect',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å²—位详细
export function getPost(postId) {
  return request({
src/api/viewIndex.js
@@ -44,4 +44,22 @@
        url: '/sales/ledger/getAmountHalfYear',
        method: 'get'
    })
}
// å„生产订单的完成进度统计
// /home/progressStatistics
export const getProgressStatistics = ()=>{
    return request({
        url: '/home/progressStatistics',
        method: 'get'
    })
}
//在制品周转情况
//home/workInProcessTurnover
export const getWorkInProcessTurnover= ()=>{
    return request({
        url: '/home/workInProcessTurnover',
        method: 'get'
    })
}
src/assets/images/chartCard.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#0092FF" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/assets/images/chartCard2.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#5EB334" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/assets/images/chartCard3.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#8000FF" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/components/Dialog/FileListDialog.vue
@@ -229,10 +229,9 @@
const handleUpload = async () => {
  if (props.uploadMethod) {
    const newItem = await props.uploadMethod()
    if (newItem) {
      addAttachment(newItem)
    }
    // å¦‚果提供了自定义上传方法,由父组件负责更新列表(通过 setList)
    // è¿™é‡Œä¸å†è‡ªåŠ¨æ·»åŠ ï¼Œé¿å…ä¸Žçˆ¶ç»„ä»¶çš„ setList é‡å¤
    await props.uploadMethod()
  }
  emit('upload')
}
src/components/PIMTable/PIMTable.vue
@@ -40,12 +40,22 @@
      :fixed="item.fixed"
      :label="item.label"
      :prop="item.prop"
      show-overflow-tooltip
      :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
      :align="item.align"
      :sortable="!!item.sortable"
      :type="item.type"
      :width="item.width"
    >
      <template #header="scope">
        <div class="pim-table-header-cell">
          <div class="pim-table-header-title">
            {{ item.label }}
          </div>
          <div v-if="item.headerSlot" class="pim-table-header-extra">
            <slot :name="item.headerSlot" :column="scope.column" />
          </div>
        </div>
      </template>
      <template
        v-if="item.hasOwnProperty('colunmTemplate')"
        #[item.colunmTemplate]="scope"
@@ -120,7 +130,7 @@
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'">
        <div v-else-if="item.dataType == 'action'" @click.stop>
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button
              v-show="o.type != 'upload'"
@@ -135,7 +145,7 @@
                    : o.color,
              }"
              link
              @click="o.clickFun(scope.row)"
              @click.stop="o.clickFun(scope.row)"
              :key="key"
            >
              {{ o.name }}
@@ -204,6 +214,7 @@
    </el-table-column>
  </el-table>
  <pagination
        v-if="isShowPagination"
    :total="page.total"
    :layout="page.layout"
    :page="page.current"
@@ -266,6 +277,10 @@
  isSelection: {
    type: Boolean,
    default: false,
  },
    isShowPagination: {
    type: Boolean,
    default: true,
  },
  isShowSummary: {
    type: Boolean,
@@ -429,4 +444,9 @@
  padding-right: 0 !important;
  padding-left: 0 !important;
}
.pim-table-header-extra :deep(.el-input),
.pim-table-header-extra :deep(.el-select) {
  width: 100%;
}
</style>
src/components/PageHeader/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
<template>
  <div class="page-header-wrapper">
    <el-page-header @back="handleBack" :content="content">
      <template #icon v-if="$slots.icon">
        <slot name="icon"></slot>
      </template>
      <template #title v-if="$slots.title">
        <slot name="title"></slot>
      </template>
      <template #content v-if="$slots.content">
        <slot name="content"></slot>
      </template>
      <template #extra>
        <slot name="extra">
          <slot name="right-button"></slot>
        </slot>
      </template>
    </el-page-header>
  </div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const props = defineProps({
  content: {
    type: String,
    default: ''
  }
})
const emit = defineEmits(['back'])
const router = useRouter()
const handleBack = () => {
  emit('back')
  // é»˜è®¤è¿”回到上一级
  router.back()
}
</script>
<style scoped>
.page-header-wrapper {
  margin-bottom: 16px;
}
.page-header-wrapper :deep(.el-page-header__extra) {
  display: flex;
  align-items: center;
  gap: 8px;
}
</style>
src/components/QRCodeGenerator/index.vue
@@ -1,70 +1,79 @@
<template>
  <div class="qr-code-generator">
    <!-- äºŒç»´ç ç”Ÿæˆè¡¨å• -->
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form">
    <el-form :model="form"
             :rules="rules"
             ref="formRef"
             label-width="120px"
             class="qr-form">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="标识类型" prop="type">
            <el-select v-model="form.type" placeholder="请选择标识类型" style="width: 100%">
              <el-option label="二维码" value="qrcode"></el-option>
              <el-option label="防伪码" value="security"></el-option>
          <el-form-item label="标识类型"
                        prop="type">
            <el-select v-model="form.type"
                       placeholder="请选择标识类型"
                       style="width: 100%">
              <el-option label="二维码"
                         value="qrcode"></el-option>
              <el-option label="防伪码"
                         value="security"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="内容" prop="content">
            <el-input
              v-model="form.content"
              placeholder="请输入要编码的内容"
              :type="form.type === 'security' ? 'textarea' : 'text'"
              :rows="form.type === 'security' ? 3 : 1"
            ></el-input>
          <el-form-item label="内容"
                        prop="content">
            <el-input v-model="form.content"
                      placeholder="请输入要编码的内容"
                      :type="form.type === 'security' ? 'textarea' : 'text'"
                      :rows="form.type === 'security' ? 3 : 1"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="尺寸" prop="size">
            <el-input-number
              v-model="form.size"
              :min="100"
              :max="500"
              :step="50"
              style="width: 100%"
            ></el-input-number>
          <el-form-item label="尺寸"
                        prop="size">
            <el-input-number v-model="form.size"
                             :min="100"
                             :max="500"
                             :step="50"
                             style="width: 100%"></el-input-number>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="边距" prop="margin">
            <el-input-number
              v-model="form.margin"
              :min="0"
              :max="10"
              :step="1"
              style="width: 100%"
            ></el-input-number>
          <el-form-item label="边距"
                        prop="margin">
            <el-input-number v-model="form.margin"
                             :min="0"
                             :max="10"
                             :step="1"
                             style="width: 100%"></el-input-number>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="前景色" prop="foregroundColor">
            <el-color-picker v-model="form.foregroundColor" style="width: 100%"></el-color-picker>
          <el-form-item label="前景色"
                        prop="foregroundColor">
            <el-color-picker v-model="form.foregroundColor"
                             style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="背景色" prop="backgroundColor">
            <el-color-picker v-model="form.backgroundColor" style="width: 100%"></el-color-picker>
          <el-form-item label="背景色"
                        prop="backgroundColor">
            <el-color-picker v-model="form.backgroundColor"
                             style="width: 100%"></el-color-picker>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="24">
          <el-form-item>
            <el-button type="primary" @click="generateCode" :loading="generating">
            <el-button type="primary"
                       @click="generateCode"
                       :loading="generating">
              ç”Ÿæˆ{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}
            </el-button>
            <el-button @click="resetForm">重置</el-button>
@@ -72,18 +81,17 @@
        </el-col>
      </el-row>
    </el-form>
    <!-- ç”Ÿæˆçš„码显示区域 -->
    <div v-if="generatedCodeUrl" class="code-display">
    <div v-if="generatedCodeUrl"
         class="code-display">
      <el-divider content-position="center">
        {{ form.type === 'qrcode' ? '生成的二维码' : '生成的防伪码' }}
      </el-divider>
      <div class="code-container">
        <div class="code-image">
          <img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" />
          <img :src="generatedCodeUrl"
               :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" />
        </div>
        <div class="code-info">
          <p><strong>内容:</strong>{{ form.content }}</p>
          <p><strong>类型:</strong>{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}</p>
@@ -91,60 +99,71 @@
          <p><strong>生成时间:</strong>{{ generateTime }}</p>
        </div>
      </div>
      <div class="code-actions">
        <el-button type="success" @click="downloadCode" icon="Download">
        <el-button type="success"
                   @click="downloadCode"
                   icon="Download">
          ä¸‹è½½å›¾ç‰‡
        </el-button>
        <el-button type="primary" @click="copyToClipboard" icon="CopyDocument">
        <el-button type="primary"
                   @click="copyToClipboard"
                   icon="CopyDocument">
          å¤åˆ¶å†…容
        </el-button>
        <el-button @click="printCode" icon="Printer">
        <el-button @click="printCode"
                   icon="Printer">
          æ‰“印
        </el-button>
      </div>
    </div>
    <!-- æ‰¹é‡ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="batchDialogVisible" title="批量生成" width="600px">
      <el-form :model="batchForm" label-width="120px">
    <el-dialog v-model="batchDialogVisible"
               title="批量生成"
               width="600px">
      <el-form :model="batchForm"
               label-width="120px">
        <el-form-item label="生成数量">
          <el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number>
          <el-input-number v-model="batchForm.quantity"
                           :min="1"
                           :max="100"
                           style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="前缀">
          <el-input v-model="batchForm.prefix" placeholder="请输入前缀,如:PROD_"></el-input>
          <el-input v-model="batchForm.prefix"
                    placeholder="请输入前缀,如:PROD_"></el-input>
        </el-form-item>
        <el-form-item label="起始编号">
          <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number>
          <el-input-number v-model="batchForm.startNumber"
                           :min="1"
                           style="width: 100%"></el-input-number>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="generateBatchCodes">开始生成</el-button>
          <el-button @click="batchDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="generateBatchCodes">开始生成</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰¹é‡ç”Ÿæˆç»“æžœ -->
    <div v-if="batchCodes.length > 0" class="batch-results">
    <div v-if="batchCodes.length > 0"
         class="batch-results">
      <el-divider content-position="center">批量生成结果</el-divider>
      <div class="batch-grid">
        <div
          v-for="(code, index) in batchCodes"
          :key="index"
          class="batch-item"
        >
          <img :src="code.url" :alt="code.content" />
        <div v-for="(code, index) in batchCodes"
             :key="index"
             class="batch-item">
          <img :src="code.url"
               :alt="code.content" />
          <p class="batch-content">{{ code.content }}</p>
          <el-button size="small" @click="downloadSingleCode(code)">下载</el-button>
          <el-button size="small"
                     @click="downloadSingleCode(code)">下载</el-button>
        </div>
      </div>
      <div class="batch-actions">
        <el-button type="success" @click="downloadAllCodes">下载全部</el-button>
        <el-button type="success"
                   @click="downloadAllCodes">下载全部</el-button>
        <el-button @click="clearBatchCodes">清空结果</el-button>
      </div>
    </div>
@@ -152,390 +171,396 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import QRCode from 'qrcode'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Download, CopyDocument, Printer } from '@element-plus/icons-vue'
  import { ref, reactive, computed, onMounted } from "vue";
  import QRCode from "qrcode";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Download, CopyDocument, Printer } from "@element-plus/icons-vue";
// å®šä¹‰ç»„件名称
defineOptions({
  name: 'QRCodeGenerator'
})
  // å®šä¹‰ç»„件名称
  defineOptions({
    name: "QRCodeGenerator",
  });
// è¡¨å•数据
const form = reactive({
  type: 'qrcode',
  content: '',
  size: 200,
  margin: 2,
  foregroundColor: '#000000',
  backgroundColor: '#FFFFFF'
})
  // è¡¨å•数据
  const form = reactive({
    type: "qrcode",
    content: "",
    size: 200,
    margin: 2,
    foregroundColor: "#000000",
    backgroundColor: "#FFFFFF",
  });
// è¡¨å•验证规则
const rules = {
  type: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}
  // è¡¨å•验证规则
  const rules = {
    type: [{ required: true, message: "请选择标识类型", trigger: "change" }],
    content: [{ required: true, message: "请输入内容", trigger: "blur" }],
  };
// å“åº”式数据
const formRef = ref()
const generating = ref(false)
const generatedCodeUrl = ref('')
const generateTime = ref('')
const batchDialogVisible = ref(false)
const batchForm = reactive({
  quantity: 10,
  prefix: '',
  startNumber: 1
})
const batchCodes = ref([])
  // å“åº”式数据
  const formRef = ref();
  const generating = ref(false);
  const generatedCodeUrl = ref("");
  const generateTime = ref("");
  const batchDialogVisible = ref(false);
  const batchForm = reactive({
    quantity: 10,
    prefix: "",
    startNumber: 1,
  });
  const batchCodes = ref([]);
// ç”ŸæˆäºŒç»´ç æˆ–防伪码
const generateCode = async () => {
  try {
    await formRef.value.validate()
    if (!form.content.trim()) {
      ElMessage.warning('请输入要编码的内容')
      return
    }
    generating.value = true
    if (form.type === 'qrcode') {
      // ç”ŸæˆäºŒç»´ç 
      generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
        },
        errorCorrectionLevel: 'M'
      })
    } else {
      // ç”Ÿæˆé˜²ä¼ªç ï¼ˆä½¿ç”¨äºŒç»´ç æŠ€æœ¯ï¼Œä½†å†…容格式不同)
      const securityContent = generateSecurityCode(form.content)
      generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
        },
        errorCorrectionLevel: 'H' // é˜²ä¼ªç ä½¿ç”¨æœ€é«˜çº é”™çº§åˆ«
      })
    }
    generateTime.value = new Date().toLocaleString()
    ElMessage.success('生成成功!')
  } catch (error) {
    console.error('生成失败:', error)
    ElMessage.error('生成失败:' + error.message)
  } finally {
    generating.value = false
  }
}
  // ç”ŸæˆäºŒç»´ç æˆ–防伪码
  const generateCode = async () => {
    try {
      await formRef.value.validate();
// ç”Ÿæˆé˜²ä¼ªç å†…容
const generateSecurityCode = (content) => {
  const timestamp = Date.now()
  const random = Math.random().toString(36).substr(2, 8)
  return `SEC_${content}_${timestamp}_${random}`
}
      if (!form.content.trim()) {
        ElMessage.warning("请输入要编码的内容");
        return;
      }
// ä¸‹è½½ç”Ÿæˆçš„码
const downloadCode = () => {
  if (!generatedCodeUrl.value) {
    ElMessage.warning('请先生成码')
    return
  }
  const a = document.createElement('a')
  a.href = generatedCodeUrl.value
  a.download = `${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
      generating.value = true;
// å¤åˆ¶å†…容到剪贴板
const copyToClipboard = async () => {
  try {
    await navigator.clipboard.writeText(form.content)
    ElMessage.success('内容已复制到剪贴板')
  } catch (error) {
    // é™çº§æ–¹æ¡ˆ
    const textArea = document.createElement('textarea')
    textArea.value = form.content
    document.body.appendChild(textArea)
    textArea.select()
    document.execCommand('copy')
    document.body.removeChild(textArea)
    ElMessage.success('内容已复制到剪贴板')
  }
}
// æ‰“印码
const printCode = () => {
  if (!generatedCodeUrl.value) {
    ElMessage.warning('请先生成码')
    return
  }
  const printWindow = window.open('', '_blank')
  printWindow.document.write(`
    <html>
      <head>
        <title>打印${form.type === 'qrcode' ? '二维码' : '防伪码'}</title>
        <style>
          body { text-align: center; padding: 20px; }
          img { max-width: 100%; height: auto; }
          .info { margin: 20px 0; }
        </style>
      </head>
      <body>
        <h2>${form.type === 'qrcode' ? '二维码' : '防伪码'}</h2>
        <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? '二维码' : '防伪码'}" />
        <div class="info">
          <p><strong>内容:</strong>${form.content}</p>
          <p><strong>生成时间:</strong>${generateTime.value}</p>
        </div>
      </body>
    </html>
  `)
  printWindow.document.close()
  printWindow.print()
}
// é‡ç½®è¡¨å•
const resetForm = () => {
  formRef.value.resetFields()
  generatedCodeUrl.value = ''
  generateTime.value = ''
  batchCodes.value = []
}
// æ‰¹é‡ç”Ÿæˆ
const generateBatchCodes = async () => {
  if (!batchForm.prefix.trim()) {
    ElMessage.warning('请输入前缀')
    return
  }
  batchCodes.value = []
  generating.value = true
  try {
    for (let i = 0; i < batchForm.quantity; i++) {
      const number = batchForm.startNumber + i
      const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}`
      let codeUrl
      if (form.type === 'qrcode') {
        codeUrl = await QRCode.toDataURL(content, {
      if (form.type === "qrcode") {
        // ç”ŸæˆäºŒç»´ç 
        generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
            light: form.backgroundColor,
          },
          errorCorrectionLevel: "M",
        });
      } else {
        const securityContent = generateSecurityCode(content)
        codeUrl = await QRCode.toDataURL(securityContent, {
        // ç”Ÿæˆé˜²ä¼ªç ï¼ˆä½¿ç”¨äºŒç»´ç æŠ€æœ¯ï¼Œä½†å†…容格式不同)
        const securityContent = generateSecurityCode(form.content);
        generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
            light: form.backgroundColor,
          },
          errorCorrectionLevel: "H", // é˜²ä¼ªç ä½¿ç”¨æœ€é«˜çº é”™çº§åˆ«
        });
      }
      batchCodes.value.push({
        content,
        url: codeUrl
      })
      generateTime.value = new Date().toLocaleString();
      ElMessage.success("生成成功!");
    } catch (error) {
      console.error("生成失败:", error);
      ElMessage.error("生成失败:" + error.message);
    } finally {
      generating.value = false;
    }
    ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} ä¸ªç `)
    batchDialogVisible.value = false
  } catch (error) {
    console.error('批量生成失败:', error)
    ElMessage.error('批量生成失败:' + error.message)
  } finally {
    generating.value = false
  }
}
  };
// ä¸‹è½½å•个批量生成的码
const downloadSingleCode = (code) => {
  const a = document.createElement('a')
  a.href = code.url
  a.download = `${code.content}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}
  // ç”Ÿæˆé˜²ä¼ªç å†…容
  const generateSecurityCode = content => {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substr(2, 8);
    return `SEC_${content}_${timestamp}_${random}`;
  };
// ä¸‹è½½æ‰€æœ‰æ‰¹é‡ç”Ÿæˆçš„码
const downloadAllCodes = async () => {
  if (batchCodes.value.length === 0) {
    ElMessage.warning('没有可下载的码')
    return
  }
  try {
    // ä½¿ç”¨JSZip打包下载
    const JSZip = await import('jszip')
    const zip = new JSZip.default()
    batchCodes.value.forEach((code, index) => {
      // å°†base64转换为blob
      const base64Data = code.url.split(',')[1]
      const byteCharacters = atob(base64Data)
      const byteNumbers = new Array(byteCharacters.length)
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i)
  // ä¸‹è½½ç”Ÿæˆçš„码
  const downloadCode = () => {
    if (!generatedCodeUrl.value) {
      ElMessage.warning("请先生成码");
      return;
    }
    const a = document.createElement("a");
    a.href = generatedCodeUrl.value;
    a.download = `${
      form.type === "qrcode" ? "二维码" : "防伪码"
    }_${new Date().getTime()}.png`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    ElMessage.success("下载成功!");
  };
  // å¤åˆ¶å†…容到剪贴板
  const copyToClipboard = async () => {
    try {
      await navigator.clipboard.writeText(form.content);
      ElMessage.success("内容已复制到剪贴板");
    } catch (error) {
      // é™çº§æ–¹æ¡ˆ
      const textArea = document.createElement("textarea");
      textArea.value = form.content;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
      ElMessage.success("内容已复制到剪贴板");
    }
  };
  // æ‰“印码
  const printCode = () => {
    if (!generatedCodeUrl.value) {
      ElMessage.warning("请先生成码");
      return;
    }
    const printWindow = window.open("", "_blank");
    printWindow.document.write(`
      <html>
        <head>
          <title>打印${form.type === "qrcode" ? "二维码" : "防伪码"}</title>
          <style>
            body { text-align: center; padding: 20px; }
            img { max-width: 100%; height: auto; }
            .info { margin: 20px 0; }
          </style>
        </head>
        <body>
          <h2>${form.type === "qrcode" ? "二维码" : "防伪码"}</h2>
          <img src="${generatedCodeUrl.value}" alt="${
      form.type === "qrcode" ? "二维码" : "防伪码"
    }" />
          <div class="info">
            <p><strong>内容:</strong>${form.content}</p>
            <p><strong>生成时间:</strong>${generateTime.value}</p>
          </div>
        </body>
      </html>
    `);
    printWindow.document.close();
    printWindow.print();
  };
  // é‡ç½®è¡¨å•
  const resetForm = () => {
    formRef.value.resetFields();
    generatedCodeUrl.value = "";
    generateTime.value = "";
    batchCodes.value = [];
  };
  // æ‰¹é‡ç”Ÿæˆ
  const generateBatchCodes = async () => {
    if (!batchForm.prefix.trim()) {
      ElMessage.warning("请输入前缀");
      return;
    }
    batchCodes.value = [];
    generating.value = true;
    try {
      for (let i = 0; i < batchForm.quantity; i++) {
        const number = batchForm.startNumber + i;
        const content = `${batchForm.prefix}${number
          .toString()
          .padStart(6, "0")}`;
        let codeUrl;
        if (form.type === "qrcode") {
          codeUrl = await QRCode.toDataURL(content, {
            width: form.size,
            margin: form.margin,
            color: {
              dark: form.foregroundColor,
              light: form.backgroundColor,
            },
          });
        } else {
          const securityContent = generateSecurityCode(content);
          codeUrl = await QRCode.toDataURL(securityContent, {
            width: form.size,
            margin: form.margin,
            color: {
              dark: form.foregroundColor,
              light: form.backgroundColor,
            },
          });
        }
        batchCodes.value.push({
          content,
          url: codeUrl,
        });
      }
      const byteArray = new Uint8Array(byteNumbers)
      zip.file(`${code.content}.png`, byteArray)
    })
    const content = await zip.generateAsync({ type: 'blob' })
    const a = document.createElement('a')
    a.href = URL.createObjectURL(content)
    a.download = `批量${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.zip`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(a.href)
    ElMessage.success('批量下载完成!')
  } catch (error) {
    console.error('批量下载失败:', error)
    ElMessage.error('批量下载失败,请逐个下载')
  }
}
// æ¸…空批量生成结果
const clearBatchCodes = () => {
  batchCodes.value = []
}
      ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} ä¸ªç `);
      batchDialogVisible.value = false;
    } catch (error) {
      console.error("批量生成失败:", error);
      ElMessage.error("批量生成失败:" + error.message);
    } finally {
      generating.value = false;
    }
  };
// æš´éœ²æ–¹æ³•给父组件
defineExpose({
  generateCode,
  downloadCode,
  resetForm,
  form
})
  // ä¸‹è½½å•个批量生成的码
  const downloadSingleCode = code => {
    const a = document.createElement("a");
    a.href = code.url;
    a.download = `${code.content}.png`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  };
  // ä¸‹è½½æ‰€æœ‰æ‰¹é‡ç”Ÿæˆçš„码
  const downloadAllCodes = async () => {
    if (batchCodes.value.length === 0) {
      ElMessage.warning("没有可下载的码");
      return;
    }
    try {
      // ä½¿ç”¨JSZip打包下载
      const JSZip = await import("jszip");
      const zip = new JSZip.default();
      batchCodes.value.forEach((code, index) => {
        // å°†base64转换为blob
        const base64Data = code.url.split(",")[1];
        const byteCharacters = atob(base64Data);
        const byteNumbers = new Array(byteCharacters.length);
        for (let i = 0; i < byteCharacters.length; i++) {
          byteNumbers[i] = byteCharacters.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        zip.file(`${code.content}.png`, byteArray);
      });
      const content = await zip.generateAsync({ type: "blob" });
      const a = document.createElement("a");
      a.href = URL.createObjectURL(content);
      a.download = `批量${
        form.type === "qrcode" ? "二维码" : "防伪码"
      }_${new Date().getTime()}.zip`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(a.href);
      ElMessage.success("批量下载完成!");
    } catch (error) {
      console.error("批量下载失败:", error);
      ElMessage.error("批量下载失败,请逐个下载");
    }
  };
  // æ¸…空批量生成结果
  const clearBatchCodes = () => {
    batchCodes.value = [];
  };
  // æš´éœ²æ–¹æ³•给父组件
  defineExpose({
    generateCode,
    downloadCode,
    resetForm,
    form,
  });
</script>
<style scoped>
.qr-code-generator {
  padding: 20px;
}
  .qr-code-generator {
    padding: 20px;
  }
.qr-form {
  background: #f8f9fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
}
  .qr-form {
    background: #f8f9fa;
    padding: 20px;
    border-radius: 8px;
    margin-bottom: 20px;
  }
.code-display {
  margin-top: 30px;
}
  .code-display {
    margin-top: 30px;
  }
.code-container {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 40px;
  margin: 20px 0;
}
.code-image img {
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.code-info {
  text-align: left;
  min-width: 200px;
}
.code-info p {
  margin: 8px 0;
  color: #666;
}
.code-actions {
  text-align: center;
  margin: 20px 0;
}
.code-actions .el-button {
  margin: 0 10px;
}
.batch-results {
  margin-top: 30px;
}
.batch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 20px;
  margin: 20px 0;
}
.batch-item {
  text-align: center;
  padding: 15px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: #fff;
}
.batch-item img {
  width: 100px;
  height: 100px;
  margin-bottom: 10px;
}
.batch-content {
  font-size: 12px;
  color: #666;
  margin: 10px 0;
  word-break: break-all;
}
.batch-actions {
  text-align: center;
  margin: 20px 0;
}
.batch-actions .el-button {
  margin: 0 10px;
}
@media (max-width: 768px) {
  .code-container {
    flex-direction: column;
    align-items: center;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: 40px;
    margin: 20px 0;
  }
  .code-image img {
    border: 2px solid #e0e0e0;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
  .code-info {
    text-align: left;
    min-width: 200px;
  }
  .code-info p {
    margin: 8px 0;
    color: #666;
  }
  .code-actions {
    text-align: center;
    margin: 20px 0;
  }
  .code-actions .el-button {
    margin: 0 10px;
  }
  .batch-results {
    margin-top: 30px;
  }
  .batch-grid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 20px;
    margin: 20px 0;
  }
}
  .batch-item {
    text-align: center;
    padding: 15px;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    background: #fff;
  }
  .batch-item img {
    width: 100px;
    height: 100px;
    margin-bottom: 10px;
  }
  .batch-content {
    font-size: 12px;
    color: #666;
    margin: 10px 0;
    word-break: break-all;
  }
  .batch-actions {
    text-align: center;
    margin: 20px 0;
  }
  .batch-actions .el-button {
    margin: 0 10px;
  }
  @media (max-width: 768px) {
    .code-container {
      flex-direction: column;
      align-items: center;
    }
    .batch-grid {
      grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    }
  }
</style>
src/layout/components/Navbar.vue
@@ -6,23 +6,6 @@
      <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
    </div>
    <!--    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
    <div class="center-menu">
      <span class="label">{{ userStore.currentFactoryName }}</span>
      <el-dropdown @command="handleFactoryChange" class="right-menu-item hover-effect" trigger="click">
        <div>
          <el-icon size="20">
            <Switch />
          </el-icon>
        </div>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item v-for="item in factoryList" :key="item.deptId" :command="item">
              {{ item.deptName }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div class="right-menu">
      <!-- æ¶ˆæ¯é€šçŸ¥ -->
      <el-popover
@@ -87,14 +70,10 @@
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
import { userLoginFacotryList } from "@/api/system/user.js"
import Cookies from "js-cookie";
import { decrypt } from "@/utils/jsencrypt"
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const factoryList = ref([])
const notificationVisible = ref(false)
const notificationCenterRef = ref(null)
const unreadCount = ref(0)
@@ -139,42 +118,6 @@
function toggleTheme() {
  settingsStore.toggleTheme()
}
function getUserLoginFacotryList() {
  if (userStore.id) {
    userLoginFacotryList({ userId: userStore.id }).then(res => {
      console.log('res', res)
      factoryList.value = res.data
    })
  } else {
    factoryList.value = []
  }
}
function handleFactoryChange(command) {
  console.log('command', command)
  handleLogin(command.deptId);
}
function handleLogin(currentFatoryId) {
  const loginForm = {
    username: Cookies.get("username"),
    password: Cookies.get("password") === undefined ? null : decrypt(Cookies.get("password")),
    currentFatoryId: currentFatoryId
  }
  userStore.loginCheckFactory(loginForm).then(res => {
    forceReload();
  }).catch((err) => {
    console.log(err)
  })
}
function forceReload() {
  const currentUrl = window.location.origin + window.location.pathname;
  const timestamp = new Date().getTime();
  window.location.href = `${currentUrl}?reload=${timestamp}`;
}
getUserLoginFacotryList();
// æ¶ˆæ¯é€šçŸ¥ç›¸å…³
function handleUnreadCountChange(count) {
@@ -221,22 +164,6 @@
  position: relative;
  background: var(--navbar-bg);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .center-menu {
    line-height: 50px;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    .label {
      font-weight: bold;
      font-size: 18px;
      color: #333333;
      margin-right: 10px;
    }
  }
  .hamburger-container {
    line-height: 46px;
src/layout/components/NotificationCenter/index.vue
@@ -107,8 +107,8 @@
    }
    const params = {
      consigneeId: consigneeId,
      pageNum: pageNum.value,
      pageSize: pageSize.value,
      current: pageNum.value,
      size: pageSize.value,
      status: activeTab.value === 'read' ? 1 : 0
    }
    const res = await listMessage(params)
src/main.js
@@ -52,6 +52,8 @@
import DictTag from "@/components/DictTag";
// è¡¨æ ¼ç»„ä»¶
import PIMTable from "@/components/PIMTable/PIMTable.vue";
// é¡µé¢å¤´éƒ¨ç»„ä»¶
import PageHeader from "@/components/PageHeader/index.vue";
import { getToken } from "@/utils/auth";
import {
@@ -93,6 +95,7 @@
app.component("RightToolbar", RightToolbar);
app.component("Editor", Editor);
app.component("PIMTable", PIMTable);
app.component("PageHeader", PageHeader);
app.use(router);
app.use(store);
src/store/modules/user.js
@@ -99,9 +99,8 @@
      loginCheckFactory(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        const factoryId = userInfo.currentFatoryId
        return new Promise((resolve, reject) => {
          loginCheckFactory(username, password, factoryId).then(res => {
          loginCheckFactory(username, password).then(res => {
            setToken(res.token)
            this.token = res.token
            resolve()
src/views/basicData/product/ProductSelectDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
<template>
  <el-dialog
      v-model="visible"
      title="选择产品"
      width="900px"
      destroy-on-close
      :close-on-click-modal="false"
  >
    <el-form :inline="true" :model="query" class="mb-2">
      <el-form-item label="产品大类">
        <el-input
            v-model="query.productName"
            placeholder="输入产品大类"
            clearable
            @keyup.enter="onSearch"
        />
      </el-form-item>
      <el-form-item label="型号名称">
        <el-input
            v-model="query.model"
            placeholder="输入型号名称"
            clearable
            @keyup.enter="onSearch"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button @click="onReset">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- åˆ—表 -->
    <el-table
        v-loading="loading"
        :data="tableData"
        height="420"
        highlight-current-row
        row-key="id"
        @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" />
      <el-table-column type="index" label="#" width="60"/>
      <el-table-column prop="productName" label="产品大类" min-width="160"/>
      <el-table-column prop="model" label="型号名称" min-width="200"/>
      <el-table-column prop="unit" label="单位" min-width="160"/>
    </el-table>
    <div class="mt-3 flex justify-end">
      <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          :total="total"
          v-model:page-size="page.pageSize"
          v-model:current-page="page.pageNum"
          :page-sizes="[10, 20, 50, 100]"
          @size-change="onPageChange"
          @current-change="onPageChange"
      />
    </div>
    <template #footer>
      <el-button @click="close()">取消</el-button>
      <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm">
        ç¡®å®š
      </el-button>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
import {computed, onMounted, reactive, ref, watch} from "vue";
import {ElMessage} from "element-plus";
import {productModelList} from '@/api/basicData/productModel'
export type ProductRow = {
  id: number;
  productName: string;
  model: string;
  unit?: string;
};
const props = defineProps<{
  modelValue: boolean;
}>();
const emit = defineEmits(['update:modelValue', 'confirm']);
const visible = computed({
  get: () => props.modelValue,
  set: (v) => emit("update:modelValue", v),
});
const query = reactive({
  productName: "",
  model: "",
});
const page = reactive({
  pageNum: 1,
  pageSize: 10,
});
const loading = ref(false);
const tableData = ref<ProductRow[]>([]);
const total = ref(0);
const multipleSelection = ref<ProductRow[]>([])
function close() {
  visible.value = false;
}
const handleSelectionChange = (val: ProductRow[]) => {
  multipleSelection.value = val
}
function onSearch() {
  page.pageNum = 1;
  loadData();
}
function onReset() {
  query.productName = "";
  query.model = "";
  page.pageNum = 1;
  loadData();
}
function onPageChange() {
  loadData();
}
function onConfirm() {
  if (multipleSelection.value.length === 0) {
    ElMessage.warning("请选择一条产品");
    return;
  }
  emit("confirm", multipleSelection.value);
  close();
}
async function loadData() {
  loading.value = true;
  try {
    multipleSelection.value = []; // ç¿»é¡µ/搜索后清空选择更符合预期
    const res = await productModelList({
      productName: query.productName.trim(),
      model: query.model.trim(),
      current: page.pageNum,
      size: page.pageSize,
    });
    tableData.value = res.records;
    total.value = res.total;
  } finally {
    loading.value = false;
  }
}
onMounted(() => {
  loadData()
})
</script>
src/views/basicData/product/index.vue
@@ -25,9 +25,7 @@
          :data="list"
          @node-click="handleNodeClick"
          :expand-on-click-node="false"
          default-expand-all
          :default-expanded-keys="expandedKeys"
          :draggable="true"
          :filter-node-method="filterNode"
          :props="{ children: 'children', label: 'label' }"
          highlight-current
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -16,7 +16,7 @@
                </el-row>
                <el-row>
                    <el-col :span="24">
                        <el-form-item label="申请部门:" prop="approveDeptId">
                        <el-form-item label="申请部门:">
                            <el-select
                                disabled
                                v-model="form.approveDeptId"
@@ -32,7 +32,7 @@
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row>
                <el-row v-if="!isQuotationApproval">
                    <el-col :span="24">
                        <el-form-item label="审批事由:" prop="approveReason">
                            <el-input v-model="form.approveReason" placeholder="请输入" clearable type="textarea" disabled/>
@@ -73,6 +73,54 @@
                    </el-col>
                </el-row>
            </el-form>
      <!-- æŠ¥ä»·å®¡æ‰¹ï¼šå±•示报价详情(复用销售报价“查看详情对话框”内容结构) -->
      <div v-if="isQuotationApproval" style="margin: 10px 0 18px;">
        <el-divider content-position="left">报价详情</el-divider>
        <el-skeleton :loading="quotationLoading" animated>
          <template #template>
            <el-skeleton-item variant="h3" style="width: 30%" />
            <el-skeleton-item variant="text" style="width: 100%" />
            <el-skeleton-item variant="text" style="width: 100%" />
          </template>
          <template #default>
            <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="未查询到对应报价详情" />
            <template v-else>
              <el-descriptions :column="2" border>
                <el-descriptions-item label="报价单号">{{ currentQuotation.quotationNo }}</el-descriptions-item>
                <el-descriptions-item label="客户名称">{{ currentQuotation.customer }}</el-descriptions-item>
                <el-descriptions-item label="业务员">{{ currentQuotation.salesperson }}</el-descriptions-item>
                <el-descriptions-item label="报价日期">{{ currentQuotation.quotationDate }}</el-descriptions-item>
                <el-descriptions-item label="有效期至">{{ currentQuotation.validDate }}</el-descriptions-item>
                <el-descriptions-item label="付款方式">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
                <el-descriptions-item label="报价总额" :span="2">
                  <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
                    Â¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }}
                  </span>
                </el-descriptions-item>
              </el-descriptions>
              <div style="margin-top: 20px;">
                <h4>产品明细</h4>
                <el-table :data="currentQuotation.products || []" border style="width: 100%">
                  <el-table-column prop="product" label="产品名称" />
                  <el-table-column prop="specification" label="规格型号" />
                  <el-table-column prop="unit" label="单位" />
                  <el-table-column prop="unitPrice" label="单价">
                    <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template>
                  </el-table-column>
                </el-table>
              </div>
              <div v-if="currentQuotation.remark" style="margin-top: 20px;">
                <h4>备注</h4>
                <p>{{ currentQuotation.remark }}</p>
              </div>
            </template>
          </template>
        </el-skeleton>
      </div>
      <el-form :model="{ activities }" ref="formRef" label-position="top">
        <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical">
          <el-step
@@ -130,7 +178,7 @@
</template>
<script setup>
import { getCurrentInstance, reactive, ref, toRefs } from "vue";
import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue";
import {
    approveProcessDetails,
    getDept,
@@ -139,8 +187,16 @@
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue'
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
const emit = defineEmits(['close'])
const { proxy } = getCurrentInstance()
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 0
  }
})
const dialogFormVisible = ref(false);
const operationType = ref('')
@@ -149,6 +205,10 @@
const userStore = useUserStore()
const productOptions = ref([]);
const userList = ref([])
const quotationLoading = ref(false)
const currentQuotation = ref({})
const isQuotationApproval = computed(() => Number(props.approveType) === 6)
const data = reactive({
    form: {
        approveTime: "",
@@ -186,11 +246,51 @@
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
  currentQuotation.value = {}
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    form.value = {...row}
    getProductOptions()
    // ç«‹å³æ¸…除表单验证状态(因为字段是disabled的,不需要验证)
    nextTick(() => {
        if (formRef.value) {
            formRef.value.clearValidate();
        }
    });
    // ç¡®ä¿é€‰é¡¹åŠ è½½å®ŒæˆåŽå†åŒ¹é…å€¼ç±»åž‹
    getProductOptions().then(() => {
        // ç¡®ä¿å€¼ç±»åž‹åŒ¹é…ï¼ˆå¦‚果选项已加载)
        if (productOptions.value.length > 0 && form.value.approveDeptId) {
            const matchedOption = productOptions.value.find(opt =>
                opt.deptId == form.value.approveDeptId ||
                String(opt.deptId) === String(form.value.approveDeptId)
            );
            if (matchedOption) {
                form.value.approveDeptId = matchedOption.deptId;
            }
        }
        // å†æ¬¡æ¸…除验证,确保选项加载后值匹配正确
        nextTick(() => {
            if (formRef.value) {
                formRef.value.clearValidate();
            }
        });
    });
  // æŠ¥ä»·å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的“报价单号”去查报价列表
  if (isQuotationApproval.value) {
    const quotationNo = row?.approveReason;
    if (quotationNo) {
      quotationLoading.value = true
      getQuotationList({ quotationNo }).then((res) => {
        const records = res?.data?.records || []
        currentQuotation.value = records[0] || {}
      }).finally(() => {
        quotationLoading.value = false
      })
    }
  }
  approveProcessDetails(row.approveId).then((res) => {
    activities.value = res.data
    // å¢žåŠ isApproval字段
@@ -211,17 +311,26 @@
  })
}
const getProductOptions = () => {
    getDept().then((res) => {
    return getDept().then((res) => {
        productOptions.value = res.data;
    });
};
// æäº¤å®¡æ‰¹
const submitForm = (status) => {
  const filteredActivities = activities.value.filter(activity => activity.isShen);
  filteredActivities[0].approveNodeStatus = status;
  if (!filteredActivities || filteredActivities.length === 0) {
    proxy.$modal.msgError("未找到待审批的节点");
    return;
  }
  const currentActivity = filteredActivities[0];
  if (!currentActivity) {
    proxy.$modal.msgError("未找到待审批的节点");
    return;
  }
  currentActivity.approveNodeStatus = status;
  // åˆ¤æ–­æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
  const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1;
  updateApproveNode({ ...filteredActivities[0], isLast }).then(() => {
  updateApproveNode({ ...currentActivity, isLast }).then(() => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
  });
@@ -230,6 +339,8 @@
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  quotationLoading.value = false
  currentQuotation.value = {}
  emit('close')
};
defineExpose({
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -17,19 +17,19 @@
        <el-row>
          <el-col :span="24">
            <el-form-item label="申请部门:" prop="approveDeptName">
              <el-input v-model="form.approveDeptName" placeholder="请输入" clearable/>
<!--                            <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/>-->
                            <el-select
                                v-model="form.approveDeptId"
                                placeholder="选择部门"
                @change="handleDeptChange"
                            >
                                <el-option
                                    v-for="user in productOptions"
                                    :key="user.deptId"
                                    :label="user.deptName"
                                    :value="user.deptId"
                                />
                            </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -217,6 +217,7 @@
const emit = defineEmits(['close'])
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import log from "@/views/monitor/job/log.vue";
const userStore = useUserStore();
const dialogFormVisible = ref(false);
@@ -279,7 +280,17 @@
function removeApproverNode(index) {
  approverNodes.value.splice(index, 1)
}
// å¤„理部门选择变化
const handleDeptChange = (deptId) => {
  if (deptId) {
    const selectedDept = productOptions.value.find(dept => dept.deptId === deptId);
    if (selectedDept) {
      form.value.approveDeptName = selectedDept.deptName;
    }
  } else {
    form.value.approveDeptName = '';
  }
};
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
@@ -287,7 +298,6 @@
    userListNoPageByTenantId().then((res) => {
    userList.value = res.data;
  });
  getProductOptions();
    form.value = {}
    approverNodes.value = [
        { id: 1, userId: null }
@@ -297,6 +307,9 @@
  
  // èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯å¹¶è®¾ç½®éƒ¨é—¨ID
  form.value.approveDeptId = userStore.currentDeptId
  // åŠ è½½éƒ¨é—¨é€‰é¡¹ï¼Œå¹¶åœ¨åŠ è½½å®ŒæˆåŽè®¾ç½®éƒ¨é—¨åç§°
  getProductOptions();
  if (operationType.value === 'edit') {
    fileList.value = row.commonFileList
    form.value.tempFileIds = fileList.value.map(file => file.id)
@@ -319,8 +332,18 @@
  }
}
const getProductOptions = () => {
  getDept().then((res) => {
  return getDept().then((res) => {
    productOptions.value = res.data;
    // å¦‚果已有部门ID,自动设置部门名称(用于验证)
    if (form.value.approveDeptId && productOptions.value.length > 0) {
      const matchedDept = productOptions.value.find(dept =>
        dept.deptId == form.value.approveDeptId ||
        String(dept.deptId) === String(form.value.approveDeptId)
      );
      if (matchedDept) {
        form.value.approveDeptName = matchedDept.deptName;
      }
    }
  });
};
function convertIdToValue(data) {
src/views/collaborativeApproval/approvalProcess/index.vue
@@ -35,7 +35,7 @@
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增</el-button>
        <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 6">新增</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
@@ -54,7 +54,7 @@
      ></PIMTable>
    </div>
    <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia>
    <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia>
    <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia>
    <FileList ref="fileListRef" />
  </div>
</template>
@@ -103,6 +103,7 @@
const tableColumnCopy = computed(() => {
  const isLeaveType = currentApproveType.value === 2; // è¯·å‡ç®¡ç†
  const isReimburseType = currentApproveType.value === 4; // æŠ¥é”€ç®¡ç†
  const isQuotationType = currentApproveType.value === 6; // æŠ¥ä»·å®¡æ‰¹
  
  // åŸºç¡€åˆ—配置
  const baseColumns = [
@@ -149,7 +150,7 @@
      width: 220
    },
    {
      label: "审批事由",
      label: isQuotationType ? "报价单号" : "审批事由",
      prop: "approveReason",
      width: 200
    },
@@ -204,7 +205,7 @@
        clickFun: (row) => {
          openForm("edit", row);
        },
        disabled: (row) => row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
        disabled: (row) => currentApproveType.value === 6 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
      },
      {
        name: "审核",
src/views/collaborativeApproval/enterpriseBook/index.vue
@@ -295,7 +295,6 @@
  getEmployeeDetail
} from '@/api/collaborativeApproval/enterpriseBook.js'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {
  changeUserStatus,
  listUser,
@@ -306,6 +305,7 @@
  addUser,
  deptTreeSelect,
} from "@/api/system/user";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ ‡ç­¾é¡µçŠ¶æ€
const activeTab = ref('personal')
@@ -395,7 +395,7 @@
}
  //获取员工列表
const getEmployeeList = async () => {
  staffJoinListPage(publicSearch.value).then(res => {
  staffOnJobListPage(Object.assign({current: -1, size: -1},publicSearch.value)).then(res => {
    console.log(res.data.records)
      EmployeeList.value = res.data.records
    }).catch(err => {})
@@ -403,7 +403,7 @@
// èŽ·å–å•ä½é€šè®¯å½•åˆ—è¡¨
const getCompanyContactsList = async () => {
  loading.value = true
    staffJoinListPage(companySearch.value).then(res => {
  staffOnJobListPage(Object.assign({current: -1, size: -1},companySearch.value)).then(res => {
    // console.log(res.data.records)
      companyContacts.value = res.data.records
    }).catch(err => {})
src/views/collaborativeApproval/noticeManagement/index.vue
@@ -617,34 +617,23 @@
  };
  
  if (row.id) {
    // ç¼–辑模式 - å…ˆåˆ é™¤å†æ·»åŠ ï¼ˆå› ä¸ºåªæœ‰ add å’Œ del æŽ¥å£ï¼‰
    delNoticeType(row.id).then(res => {
      if (res.code === 200) {
        addNoticeType(data).then(addRes => {
          if (addRes.code === 200) {
            ElMessage.success('编辑成功');
            row.editing = false;
            delete row.originalNoticeType;
            fetchNoticeTypeList().then(() => {
              // å¦‚果当前选中的类型被编辑,需要重新获取数据
              if (activeNoticeTypeTab.value === String(row.id)) {
                fetchNoticesByType(addRes.data?.id || row.id);
              }
            });
          }
        });
      }
    });
  } else {
    // æ–°å¢žæ¨¡å¼
    addNoticeType(data).then(res => {
      if (res.code === 200) {
        ElMessage.success('新增成功');
        row.editing = false;
        fetchNoticeTypeList();
      }
    });
    // ç¼–辑模式 - ä¼ å…¥id
    data.id = row.id;
  }
  addNoticeType(data).then(res => {
    if (res.code === 200) {
      ElMessage.success(row.id ? '编辑成功' : '新增成功');
      row.editing = false;
      delete row.originalNoticeType;
      fetchNoticeTypeList().then(() => {
        // å¦‚果当前选中的类型被编辑,需要重新获取数据
        if (row.id && activeNoticeTypeTab.value === String(row.id)) {
          fetchNoticesByType(res.data?.id || row.id);
        }
      });
    }
  });
};
const handleDeleteNoticeType = (row) => {
@@ -855,6 +844,9 @@
  color: #606266;
  line-height: 1.6;
  font-size: 14px;
  word-break: break-all;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}
.card-footer {
src/views/collaborativeApproval/notificationManagement/index.vue
@@ -322,7 +322,7 @@
import { ElMessage, ElMessageBox } from "element-plus";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js";
import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
import { listNotification, addNotification, updateNotification, delNotification,addOnlineMeeting,addFileSharing } from "@/api/collaborativeApproval/notificationManagement.js";
import { id } from "element-plus/es/locales.mjs";
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
@@ -127,7 +127,7 @@
            <el-option
                v-for="person in employees"
                :key="person.id"
                :label="`${person.staffName} (${person.postJob})`"
                :label="`${person.staffName} (${person.postName})`"
                :value="person.id"
            />
          </el-select>
@@ -156,7 +156,7 @@
import {ElMessage} from 'element-plus'
import {Plus, Document, Promotion, Bell} from '@element-plus/icons-vue'
import {getRoomEnum, saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// å½“前申请类型
const currentType = ref('department') // approval: å®¡æ‰¹æµç¨‹, department: éƒ¨é—¨çº§, notification: é€šçŸ¥å‘布
@@ -302,8 +302,12 @@
  getRoomEnum().then(res => {
    meetingRooms.value = res.data
  })
  getStaffOnJob().then(res => {
    employees.value = res.data.sort((a, b) => a.postJob.localeCompare(b.postJob))
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    employees.value = res.data.records.sort((a, b) => a.postName.localeCompare(b.postName))
  })
})
</script>
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
@@ -188,8 +188,8 @@
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getExamineList,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
@@ -240,7 +240,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -342,9 +342,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
@@ -186,8 +186,8 @@
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getMeetingPublish,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
@@ -239,7 +239,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -340,9 +340,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -160,8 +160,8 @@
import Pagination from '@/components/Pagination/index.vue'
import Editor from '@/components/Editor/index.vue'
import { getRoomEnum, getMeetingPublish ,getMeetingMinutesByMeetingId,saveMeetingMinutes} from '@/api/collaborativeApproval/meeting.js'
import { getStaffOnJob } from "@/api/personnelManagement/onboarding.js"
import dayjs from "dayjs"
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
@@ -214,7 +214,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -337,9 +337,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2] = await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2] = await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -1,101 +1,166 @@
<template>
  <div class="app-container">
        <!-- è§„章制度管理-->
          <el-card class="box-card">
            <template #header>
              <div class="card-header">
                <span>规章制度发布</span>
              </div>
    <!-- è§„章制度管理-->
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>规章制度发布</span>
        </div>
      </template>
      <div class="tab-content">
        <el-row :gutter="20"
                class="mb-20">
          <span class="ml-10">制度标题:</span>
          <el-col :span="6">
            <el-input v-model="regulationSearchForm.title"
                      placeholder="请输入制度标题"
                      clearable />
          </el-col>
          <span class="search_title">制度分类:</span>
          <el-col :span="4">
            <el-select v-model="regulationSearchForm.category"
                       placeholder="制度分类"
                       clearable>
              <el-option label="人事制度"
                         value="hr" />
              <el-option label="财务制度"
                         value="finance" />
              <el-option label="安全制度"
                         value="safety" />
              <el-option label="技术制度"
                         value="tech" />
            </el-select>
          </el-col>
          <el-col :span="8">
            <el-button type="primary"
                       @click="searchRegulations">搜索</el-button>
            <el-button @click="resetRegulationSearch">重置</el-button>
            <el-button @click="handleExport">导出</el-button>
            <el-button type="success"
                       @click="handleAdd">
              å‘布制度
            </el-button>
          </el-col>
        </el-row>
        <el-table :data="regulations"
                  border
                  v-loading="tableLoading"
                  style="width: 100%">
          <el-table-column prop="regulationNum"
                           label="制度编号"
                           width="120" />
          <el-table-column prop="title"
                           label="制度标题"
                           min-width="150" />
          <el-table-column prop="category"
                           label="分类"
                           width="120">
            <template #default="scope">
              <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
            </template>
            <div class="tab-content">
              <el-row :gutter="20" class="mb-20">
                <span class="ml-10">制度标题:</span>
                <el-col :span="6">
                  <el-input v-model="regulationSearchForm.title" placeholder="请输入制度标题" clearable />
                </el-col>
                <span class="search_title">制度分类:</span>
                <el-col :span="4">
                  <el-select v-model="regulationSearchForm.category" placeholder="制度分类" clearable>
                    <el-option label="人事制度" value="hr" />
                    <el-option label="财务制度" value="finance" />
                    <el-option label="安全制度" value="safety" />
                    <el-option label="技术制度" value="tech" />
                  </el-select>
                </el-col>
                <el-col :span="8">
                  <el-button type="primary" @click="searchRegulations">搜索</el-button>
                  <el-button @click="resetRegulationSearch">重置</el-button>
                  <el-button @click="handleExport">导出</el-button>
                  <el-button type="success" @click="handleAdd">
                    å‘布制度
                  </el-button>
                </el-col>
              </el-row>
              <el-table :data="regulations" border v-loading="tableLoading"  style="width: 100%">
                <el-table-column prop="regulationNum" label="制度编号" width="120" />
                <el-table-column prop="title" label="制度标题" min-width="150" />
                <el-table-column prop="category" label="分类" width="120">
                  <template #default="scope">
                    <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="version" label="版本" width="120" />
                <el-table-column prop="createUserName" label="发布人" width="120" />
                <el-table-column prop="createTime" label="发布时间" width="180" />
                <el-table-column prop="status" label="状态" width="100">
                  <template #default="scope">
                    <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                      {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="readCount" label="已读人数" width="100" />
                <el-table-column label="操作" width="320" fixed="right">
                  <template #default="scope">
                    <el-button link @click="viewRegulation(scope.row)">查看</el-button>
                    <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
                    <el-button link type="danger" @click="repealEdit(scope.row)">废弃</el-button>
                    <el-button link type="success" @click="viewVersionHistory(scope.row)">版本历史</el-button>
                    <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button>
                    <el-button link type="primary" @click="openFileDialog(scope.row)">附件</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-card>
          </el-table-column>
          <el-table-column prop="version"
                           label="版本"
                           width="120" />
          <el-table-column prop="createUserName"
                           label="发布人"
                           width="120" />
          <el-table-column prop="createTime"
                           label="发布时间"
                           width="180" />
          <el-table-column prop="status"
                           label="状态"
                           width="100">
            <template #default="scope">
              <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="readCount"
                           label="已读人数"
                           width="100" />
          <el-table-column label="操作"
                           width="320"
                           fixed="right">
            <template #default="scope">
              <el-button link
                         @click="viewRegulation(scope.row)">查看</el-button>
              <el-button link
                         type="primary"
                         @click="handleEdit(scope.row)">编辑</el-button>
              <el-button link
                         type="danger"
                         @click="repealEdit(scope.row)">废弃</el-button>
              <el-button link
                         type="success"
                         @click="viewVersionHistory(scope.row)">版本历史</el-button>
              <!-- <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button> -->
              <el-button link
                         type="primary"
                         @click="openFileDialog(scope.row)">附件</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-card>
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡†ï¼ˆå·²ç§»é™¤ï¼‰ -->
    <!-- è§„章制度发布对话框 -->
    <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px">
      <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px">
        <el-form-item label="制度编号" prop="regulationNum">
          <el-input v-model="regulationForm.regulationNum" placeholder="请输入制度编号" />
    <el-dialog v-model="showRegulationDialog"
               :title="operationType === 'add' ? '发布制度' : '编辑制度'"
               width="800px">
      <el-form :model="regulationForm"
               :rules="regulationRules"
               ref="regulationFormRef"
               label-width="100px">
        <el-form-item label="制度编号"
                      prop="regulationNum">
          <el-input v-model="regulationForm.regulationNum"
                    placeholder="请输入制度编号" />
        </el-form-item>
        <el-form-item label="制度标题" prop="title">
          <el-input v-model="regulationForm.title" placeholder="请输入制度标题" />
        <el-form-item label="制度标题"
                      prop="title">
          <el-input v-model="regulationForm.title"
                    placeholder="请输入制度标题" />
        </el-form-item>
        <el-form-item label="制度分类" prop="category">
          <el-select v-model="regulationForm.category" placeholder="请选择制度分类" style="width: 100%">
            <el-option label="人事制度" value="hr" />
            <el-option label="财务制度" value="finance" />
            <el-option label="安全制度" value="safety" />
            <el-option label="技术制度" value="tech" />
        <el-form-item label="制度分类"
                      prop="category">
          <el-select v-model="regulationForm.category"
                     placeholder="请选择制度分类"
                     style="width: 100%">
            <el-option label="人事制度"
                       value="hr" />
            <el-option label="财务制度"
                       value="finance" />
            <el-option label="安全制度"
                       value="safety" />
            <el-option label="技术制度"
                       value="tech" />
          </el-select>
        </el-form-item>
        <el-form-item label="制度内容" prop="content">
          <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="请输入制度详细内容" />
        <el-form-item label="制度内容"
                      prop="content">
          <el-input v-model="regulationForm.content"
                    type="textarea"
                    :rows="10"
                    placeholder="请输入制度详细内容" />
        </el-form-item>
        <el-form-item label="制度版本" prop="version">
          <el-input v-model="regulationForm.version" placeholder="请输入制度版本" />
        <el-form-item label="制度版本"
                      prop="version">
          <el-input v-model="regulationForm.version"
                    placeholder="请输入制度版本" />
        </el-form-item>
        <el-form-item label="生效时间" prop="effectiveTime">
          <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" format="YYYY-MM-DD HH:mm:ss"
             value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择生效时间" style="width: 100%" />
        <el-form-item label="生效时间"
                      prop="effectiveTime">
          <el-date-picker v-model="regulationForm.effectiveTime"
                          type="datetime"
                          format="YYYY-MM-DD HH:mm:ss"
                          value-format="YYYY-MM-DD HH:mm:ss"
                          placeholder="选择生效时间"
                          style="width: 100%" />
        </el-form-item>
        <el-form-item label="适用范围" prop="scope">
        <el-form-item label="适用范围"
                      prop="scope">
          <el-checkbox-group v-model="regulationForm.scope">
            <el-checkbox label="all">全体员工</el-checkbox>
            <el-checkbox label="manager">管理层</el-checkbox>
@@ -104,7 +169,8 @@
            <el-checkbox label="tech">技术部门</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
        <el-form-item label="是否需要确认" prop="requireConfirm">
        <el-form-item label="是否需要确认"
                      prop="requireConfirm">
          <el-switch v-model="regulationForm.requireConfirm" />
          <span class="ml-10">开启后员工需要阅读确认</span>
        </el-form-item>
@@ -112,17 +178,19 @@
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showRegulationDialog = false">取消</el-button>
          <el-button type="primary" @click="submitRegulation">发布制度</el-button>
          <el-button type="primary"
                     @click="submitRegulation">发布制度</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç”¨å°è¯¦æƒ…对话框(已移除) -->
    <!-- è§„章制度详情对话框 -->
    <el-dialog v-model="showRegulationDetailDialog" title="规章制度详情" width="800px">
    <el-dialog v-model="showRegulationDetailDialog"
               title="规章制度详情"
               width="800px">
      <div v-if="currentRegulationDetail">
        <el-descriptions :column="2" border>
        <el-descriptions :column="2"
                         border>
          <el-descriptions-item label="制度编号">{{ currentRegulationDetail.id }}</el-descriptions-item>
          <el-descriptions-item label="制度标题">{{ currentRegulationDetail.title }}</el-descriptions-item>
          <el-descriptions-item label="分类">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item>
@@ -135,19 +203,30 @@
          <div class="regulation-content">{{ currentRegulationDetail.content }}</div>
        </div>
        <!-- å¦‚æžœtableData>0 æ˜¾ç¤º -->
        <div style="margin: 10px 0;" v-if="tableData && tableData.length > 0" >
          <el-button type="success" @click="resetForm(currentRegulationDetail)">确认查看</el-button>
        <div style="margin: 10px 0;"
             v-if="tableData && tableData.length > 0">
          <el-button type="success"
                     @click="resetForm(currentRegulationDetail)">确认查看</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- ç‰ˆæœ¬åŽ†å²å¯¹è¯æ¡† -->
    <el-dialog v-model="showVersionHistoryDialog" title="版本历史" width="800px">
      <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="version" label="版本号" width="100" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column prop="createUserName" label="更新人" width="120" />
        <el-table-column prop="changeLog" label="变更说明">
    <el-dialog v-model="showVersionHistoryDialog"
               title="版本历史"
               width="800px">
      <el-table :data="versionHistory"
                style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="version"
                         label="版本号"
                         width="100" />
        <el-table-column prop="updateTime"
                         label="更新时间"
                         width="180" />
        <el-table-column prop="createUserName"
                         label="更新人"
                         width="120" />
        <el-table-column prop="changeLog"
                         label="变更说明">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
              {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
@@ -156,15 +235,27 @@
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- é˜…读状态对话框 -->
    <el-dialog v-model="showReadStatusDialog" title="阅读状态" width="800px">
      <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="employee" label="员工姓名" width="120" />
        <el-table-column prop="department" label="所属部门" width="150" />
        <el-table-column prop="createTime" label="阅读时间" width="180" />
        <el-table-column prop="confirmTime" label="确认时间" width="180" />
        <el-table-column prop="status" label="状态" width="100">
    <el-dialog v-model="showReadStatusDialog"
               title="阅读状态"
               width="800px">
      <el-table :data="readStatusList"
                style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="employee"
                         label="员工姓名"
                         width="120" />
        <el-table-column prop="department"
                         label="所属部门"
                         width="150" />
        <el-table-column prop="createTime"
                         label="阅读时间"
                         width="180" />
        <el-table-column prop="confirmTime"
                         label="确认时间"
                         width="180" />
        <el-table-column prop="status"
                         label="状态"
                         width="100">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'">
              {{ scope.row.status === 'confirmed' ? '已确认' : '未确认' }}
@@ -173,412 +264,437 @@
        </el-table-column>
      </el-table>
    </el-dialog>
    <FileListDialog
      ref="fileListDialogRef"
      v-model="fileDialogVisible"
      :show-upload-button="true"
      :show-delete-button="true"
      :delete-method="handleAttachmentDelete"
      :rules-regulations-management-id="currentFileRuleId"
      :name-column-label="'附件名称'"
      @upload="handleAttachmentUpload"
    />
    <FileListDialog ref="fileListDialogRef"
                    v-model="fileDialogVisible"
                    :show-upload-button="true"
                    :show-delete-button="true"
                    :delete-method="handleAttachmentDelete"
                    :rules-regulations-management-id="currentFileRuleId"
                    :name-column-label="'附件名称'"
                    @upload="handleAttachmentUpload" />
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import FileListDialog from '@/components/Dialog/FileListDialog.vue'
import { listRuleFiles, delRuleFile, addRuleFile } from '@/api/collaborativeApproval/rulesRegulationsManagementFile.js'
  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import {
    listRuleManagement,
    addRuleManagement,
    updateRuleManagement,
    delRuleManagement,
    getReadingStatusByRuleId,
    addReadingStatus,
    updateReadingStatus,
  } from "@/api/collaborativeApproval/sealManagement.js";
  import FileListDialog from "@/components/Dialog/FileListDialog.vue";
  import {
    listRuleFiles,
    delRuleFile,
    addRuleFile,
  } from "@/api/collaborativeApproval/rulesRegulationsManagementFile.js";
// å“åº”式数据
const operationType = ref('add')
const tableData = ref([])
const tableLoading = ref(false)
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// é™„件弹窗
const fileDialogVisible = ref(false)
const fileListDialogRef = ref(null)
const currentFileRuleId = ref(null)
const filePage = reactive({
  current: 1,
  size: 10,
  total: 0
})
// è§„章制度相关
const showRegulationDialog = ref(false)
const showRegulationDetailDialog = ref(false)
const showVersionHistoryDialog = ref(false)
const showReadStatusDialog = ref(false)
const currentRegulationDetail = ref(null)
const regulationFormRef = ref()
const regulationForm = reactive({
  id: '',
  regulationNum: '',
  title: '',
  category: '',
  content: '',
  version: '',
  status: 'active',
  readCount: 0,
  effectiveTime: '',
  scope: [],
  requireConfirm: false
})
  // å“åº”式数据
  const operationType = ref("add");
  const tableData = ref([]);
  const tableLoading = ref(false);
  // åˆ†é¡µå‚æ•°
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // é™„件弹窗
  const fileDialogVisible = ref(false);
  const fileListDialogRef = ref(null);
  const currentFileRuleId = ref(null);
  const filePage = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // è§„章制度相关
  const showRegulationDialog = ref(false);
  const showRegulationDetailDialog = ref(false);
  const showVersionHistoryDialog = ref(false);
  const showReadStatusDialog = ref(false);
  const currentRegulationDetail = ref(null);
  const regulationFormRef = ref();
  const regulationForm = reactive({
    id: "",
    regulationNum: "",
    title: "",
    category: "",
    content: "",
    version: "",
    status: "active",
    readCount: 0,
    effectiveTime: "",
    scope: [],
    requireConfirm: false,
  });
const readStatus = ref({
  id: '',
  ruleId: '',
  employee: '',
  department: '',
  createTime: '',
  confirmTime: '',
  status: 'unconfirmed'
})
  const readStatus = ref({
    id: "",
    ruleId: "",
    employee: "",
    department: "",
    createTime: "",
    confirmTime: "",
    status: "unconfirmed",
  });
const regulationRules = {
  title: [{ required: true, message: '请输入制度标题', trigger: 'blur' }],
  category: [{ required: true, message: '请选择制度分类', trigger: 'change' }],
  content: [{ required: true, message: '请输入制度内容', trigger: 'blur' }],
  effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'change' }],
  scope: [{ required: true, message: '请选择适用范围', trigger: 'change' }]
}
  const regulationRules = {
    title: [{ required: true, message: "请输入制度标题", trigger: "blur" }],
    category: [{ required: true, message: "请选择制度分类", trigger: "change" }],
    content: [{ required: true, message: "请输入制度内容", trigger: "blur" }],
    effectiveTime: [
      { required: true, message: "请选择生效时间", trigger: "change" },
    ],
    scope: [{ required: true, message: "请选择适用范围", trigger: "change" }],
  };
const regulationSearchForm = reactive({
  title: '',
  category: ''
})
  const regulationSearchForm = reactive({
    title: "",
    category: "",
  });
const regulations = ref([])
  const regulations = ref([]);
const versionHistory = ref([])
  const versionHistory = ref([]);
const readStatusList = ref([])
  const readStatusList = ref([]);
  // { employee: '陈志强', department: '销售部', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' },
  // { employee: '刘雅婷', department: '技术部', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' },
  // { employee: '王建国', department: '财务部', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' }
// åˆ¶åº¦åˆ†ç±»
const getCategoryText = (category) => {
  const categoryMap = {
    hr: '人事制度',
    finance: '财务制度',
    safety: '安全制度',
    tech: '技术制度'
  }
  return categoryMap[category] || '未知'
}
// æœç´¢åˆ¶åº¦
const searchRegulations = () => {
  page.current=1
  getRegulationList()
}
// é‡ç½®åˆ¶åº¦æœç´¢
const resetRegulationSearch = () => {
  regulationSearchForm.title = ''
  regulationSearchForm.category = ''
  searchRegulations()
}
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add'
  resetRegulationForm()
  showRegulationDialog.value = true
}
  // åˆ¶åº¦åˆ†ç±»
  const getCategoryText = category => {
    const categoryMap = {
      hr: "人事制度",
      finance: "财务制度",
      safety: "安全制度",
      tech: "技术制度",
    };
    return categoryMap[category] || "未知";
  };
  // æœç´¢åˆ¶åº¦
  const searchRegulations = () => {
    page.current = 1;
    getRegulationList();
  };
  // é‡ç½®åˆ¶åº¦æœç´¢
  const resetRegulationSearch = () => {
    regulationSearchForm.title = "";
    regulationSearchForm.category = "";
    searchRegulations();
  };
  // æ–°å¢ž
  const handleAdd = () => {
    operationType.value = "add";
    resetRegulationForm();
    showRegulationDialog.value = true;
  };
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(regulationForm, row)
  showRegulationDialog.value = true
}
// åºŸå¼ƒ
const repealEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(regulationForm, row)
  regulationForm.status = 'repealed'
  ElMessageBox.confirm('确认废弃该制度?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    updateRuleManagement(regulationForm).then(res => {
      if(res.code == 200){
        ElMessage.success('制度废弃成功')
        // showRegulationDialog.value = false
        getRegulationList()
        resetRegulationForm()
      }
  // ç¼–辑
  const handleEdit = row => {
    operationType.value = "edit";
    Object.assign(regulationForm, row);
    showRegulationDialog.value = true;
  };
  // åºŸå¼ƒ
  const repealEdit = row => {
    operationType.value = "edit";
    Object.assign(regulationForm, row);
    regulationForm.status = "repealed";
    ElMessageBox.confirm("确认废弃该制度?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
  }).catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消废弃'
    })
  })
}
// å‘布制度
const submitRegulation = async () => {
  try {
    await regulationFormRef.value.validate()
    if(operationType.value == 'add'){
      addRuleManagement(regulationForm).then(res => {
        if(res.code == 200){
          ElMessage.success('制度发布成功')
          showRegulationDialog.value = false
          getRegulationList()
          resetRegulationForm()
        }
      .then(() => {
        updateRuleManagement(regulationForm).then(res => {
          if (res.code == 200) {
            ElMessage.success("制度废弃成功");
            // showRegulationDialog.value = false
            getRegulationList();
            resetRegulationForm();
          }
        });
      })
    }else{
      updateRuleManagement(regulationForm).then(res => {
        if(res.code == 200){
          ElMessage.success('制度编辑成功')
          showRegulationDialog.value = false
          resetRegulationForm()
          getRegulationList()
      }})}
  }catch(err){
    ElMessage.error(err.msg)
  }
}
//重置制度表单
const resetRegulationForm = () => {
  Object.assign(regulationForm, {
    id: '',
    regulationNum: '',
    title: '',
    category: '',
    content: '',
    version: '',
    status: 'active',
    readCount: 0,
    effectiveTime: '',
    scope: [],
    requireConfirm: false
})
}
// æŸ¥çœ‹åˆ¶åº¦ç‰ˆæœ¬åŽ†å²
const viewVersionHistory = (row) => {
  showVersionHistoryDialog.value = true
  const params = {
    category: row.category
  }
  listRuleManagement(page,params).then(res => {
    if(res.code == 200){
      versionHistory.value = res.data.records
      .catch(() => {
        ElMessage({
          type: "info",
          message: "已取消废弃",
        });
      });
  };
  // å‘布制度
  const submitRegulation = async () => {
    try {
      await regulationFormRef.value.validate();
      if (operationType.value == "add") {
        addRuleManagement(regulationForm).then(res => {
          if (res.code == 200) {
            ElMessage.success("制度发布成功");
            showRegulationDialog.value = false;
            getRegulationList();
            resetRegulationForm();
          }
        });
      } else {
        updateRuleManagement(regulationForm).then(res => {
          if (res.code == 200) {
            ElMessage.success("制度编辑成功");
            showRegulationDialog.value = false;
            resetRegulationForm();
            getRegulationList();
          }
        });
      }
    } catch (err) {
      ElMessage.error(err.msg);
    }
  })
}
// æŸ¥çœ‹åˆ¶åº¦è¯¦æƒ…
const viewRegulation = (row) => {
  currentRegulationDetail.value = row
  showRegulationDetailDialog.value = true
  getReadingStatusByRuleId(row.id).then(res => {
    if(res.code == 200){
      readStatusList.value = res.data
      if(readStatusList.value.length==0 && tableData.value.length>0){
  };
  //重置制度表单
  const resetRegulationForm = () => {
    Object.assign(regulationForm, {
      id: "",
      regulationNum: "",
      title: "",
      category: "",
      content: "",
      version: "",
      status: "active",
      readCount: 0,
      effectiveTime: "",
      scope: [],
      requireConfirm: false,
    });
  };
  // æŸ¥çœ‹åˆ¶åº¦ç‰ˆæœ¬åŽ†å²
  const viewVersionHistory = row => {
    showVersionHistoryDialog.value = true;
    const params = {
      category: row.category,
    };
    listRuleManagement(page, params).then(res => {
      if (res.code == 200) {
        versionHistory.value = res.data.records;
      }
    });
  };
  // æŸ¥çœ‹åˆ¶åº¦è¯¦æƒ…
  const viewRegulation = row => {
    currentRegulationDetail.value = row;
    showRegulationDetailDialog.value = true;
    getReadingStatusByRuleId(row.id).then(res => {
      if (res.code == 200) {
        readStatusList.value = res.data;
        if (readStatusList.value.length == 0 && tableData.value.length > 0) {
          const params = {
          ruleId: row.id,
          employee: tableData.value[0].staffName,
          department: tableData.value[0].postJob,
          status: 'unconfirmed'
            ruleId: row.id,
            employee: tableData.value[0].staffName,
            department: tableData.value[0].postJob,
            status: "unconfirmed",
          };
          addReadingStatus(params).then(res => {
            if (res.code == 200) {
              ElMessage.success("制度阅读成功");
            }
          });
        }
        addReadingStatus(params).then(res => {
          if(res.code == 200){
            ElMessage.success('制度阅读成功')
          }
        })
      }
    }
  })
}
// æŸ¥çœ‹åˆ¶åº¦é˜…读状态
const viewReadStatus = (row) => {
  showReadStatusDialog.value = true
  //查看阅读状态列表
  getReadingStatusByRuleId(row.id).then(res => {
    if(res.code == 200){
      readStatusList.value = res.data
    }
  })
}
//确认查看
const resetForm = (row) => {
  console.log("row",row)
  row.readCount = row.readCount + 1
  updateRuleManagement(row).then(res => {
    if(res.code == 200){
      ElMessage.success('查看数量修改成功')
      //修改阅读状态
      //根据制度id和当前登录的员工得到阅读状态
      // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName )
      // if(item.length>0){
      //   item[0].status = 'confirmed',
      //   item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
      // }
      // ç­›é€‰å½“前员工对应该制度的阅读状态记录
      let statusItem = readStatusList.value.find(item => item.employee === tableData.value[0].staffName && item.ruleId === row.id);
      if (statusItem) {
        // å¦‚果找到记录,更新状态和确认时间
        statusItem.status = 'confirmed';
        // æ ¼å¼åŒ–时间为"YYYY-MM-DD HH:mm:ss"格式
        const now = new Date();
        statusItem.confirmTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
        // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
        updateReadingStatus(statusItem).then(res => {
          if(res.code == 200){
            ElMessage.success('制度阅读状态修改成功')
          }
        })
    });
  };
  // æŸ¥çœ‹åˆ¶åº¦é˜…读状态
  const viewReadStatus = row => {
    showReadStatusDialog.value = true;
    //查看阅读状态列表
    getReadingStatusByRuleId(row.id).then(res => {
      if (res.code == 200) {
        readStatusList.value = res.data;
      }
    });
  };
  //确认查看
  const resetForm = row => {
    console.log("row", row);
    row.readCount = row.readCount + 1;
    updateRuleManagement(row).then(res => {
      if (res.code == 200) {
        ElMessage.success("查看数量修改成功");
        //修改阅读状态
        //根据制度id和当前登录的员工得到阅读状态
        // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName )
        // if(item.length>0){
        //   item[0].status = 'confirmed',
        //   item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
        // }
        // ç­›é€‰å½“前员工对应该制度的阅读状态记录
        let statusItem = readStatusList.value.find(
          item =>
            item.employee === tableData.value[0].staffName &&
            item.ruleId === row.id
        );
        if (statusItem) {
          // å¦‚果找到记录,更新状态和确认时间
          statusItem.status = "confirmed";
          // æ ¼å¼åŒ–时间为"YYYY-MM-DD HH:mm:ss"格式
          const now = new Date();
          statusItem.confirmTime = `${now.getFullYear()}-${String(
            now.getMonth() + 1
          ).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(
            now.getHours()
          ).padStart(2, "0")}:${String(now.getMinutes()).padStart(
            2,
            "0"
          )}:${String(now.getSeconds()).padStart(2, "0")}`;
          // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
          updateReadingStatus(statusItem).then(res => {
            if (res.code == 200) {
              ElMessage.success("制度阅读状态修改成功");
            }
          });
        }
      }
    });
  };
  // å¯¼å‡ºè§„章制度
  const { proxy } = getCurrentInstance();
  const handleExport = () => {
    proxy.download(
      "/rulesRegulationsManagement/export",
      { ...regulationSearchForm },
      "规章制度.xlsx"
    );
  };
  // é™„件:查询
  const fetchRuleFiles = async rulesRegulationsManagementId => {
    const params = {
      current: filePage.current,
      size: filePage.size,
      rulesRegulationsManagementId,
    };
    const res = await listRuleFiles(params);
    const records = res?.data?.records || [];
    filePage.total = res?.data?.total || records.length;
    const mapped = records.map(item => ({
      id: item.id,
      name: item.fileName || item.name,
      url: item.fileUrl || item.url,
      raw: item,
    }));
    fileListDialogRef.value?.setList(mapped);
  };
  // æ‰“开附件弹窗
  const openFileDialog = async row => {
    currentFileRuleId.value = row.id;
    fileDialogVisible.value = true;
    await fetchRuleFiles(row.id);
  };
  // åˆ·æ–°é™„件列表
  const refreshFileList = async () => {
    if (!currentFileRuleId.value) return;
    await fetchRuleFiles(currentFileRuleId.value);
  };
  // ä¸Šä¼ é™„件(由子组件触发)
  const handleAttachmentUpload = async filePayload => {
    if (!currentFileRuleId.value) return;
    const payload = {
      name: filePayload?.fileName || filePayload?.name,
      url: filePayload?.fileUrl || filePayload?.url,
      rulesRegulationsManagementId: currentFileRuleId.value,
    };
    await addRuleFile(payload);
    ElMessage.success("文件上传成功");
    await refreshFileList();
  };
  // åˆ é™¤é™„ä»¶
  const handleAttachmentDelete = async row => {
    if (!row?.id) return false;
    try {
      await ElMessageBox.confirm("确认删除该附件?", "提示", { type: "warning" });
    } catch {
      return false;
    }
  })
}
    await delRuleFile([row.id]);
    ElMessage.success("删除成功");
    await refreshFileList();
  };
// å¯¼å‡ºè§„章制度
const { proxy } = getCurrentInstance()
const handleExport = () => {
  proxy.download('/rulesRegulationsManagement/export', { ...regulationSearchForm }, '规章制度.xlsx')
}
  // èŽ·å–è§„ç« åˆ¶åº¦åˆ—è¡¨æ•°æ®
  const getRegulationList = async () => {
    tableLoading.value = true;
    listRuleManagement(page, regulationSearchForm)
      .then(res => {
        regulations.value = res.data.records;
        // è¿‡æ»¤æŽ‰å·²åºŸå¼ƒçš„制度
        // regulations.value = res.data.records.filter(item => item.status !== 'repealed')
        page.value.total = res.data.total;
        tableLoading.value = false;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
// é™„件:查询
const fetchRuleFiles = async (rulesRegulationsManagementId) => {
  const params = {
    current: filePage.current,
    size: filePage.size,
    rulesRegulationsManagementId
  }
  const res = await listRuleFiles(params)
  const records = res?.data?.records || []
  filePage.total = res?.data?.total || records.length
  const mapped = records.map(item => ({
    id: item.id,
    name: item.fileName || item.name,
    url: item.fileUrl || item.url,
    raw: item
  }))
  fileListDialogRef.value?.setList(mapped)
}
// æ‰“开附件弹窗
const openFileDialog = async (row) => {
  currentFileRuleId.value = row.id
  fileDialogVisible.value = true
  await fetchRuleFiles(row.id)
}
// åˆ·æ–°é™„件列表
const refreshFileList = async () => {
  if (!currentFileRuleId.value) return
  await fetchRuleFiles(currentFileRuleId.value)
}
// ä¸Šä¼ é™„件(由子组件触发)
const handleAttachmentUpload = async (filePayload) => {
  if (!currentFileRuleId.value) return
  const payload = {
    name: filePayload?.fileName || filePayload?.name,
    url: filePayload?.fileUrl || filePayload?.url,
    rulesRegulationsManagementId: currentFileRuleId.value
  }
  await addRuleFile(payload)
  ElMessage.success('文件上传成功')
  await refreshFileList()
}
// åˆ é™¤é™„ä»¶
const handleAttachmentDelete = async (row) => {
  if (!row?.id) return false
  try {
    await ElMessageBox.confirm('确认删除该附件?', '提示', { type: 'warning' })
  } catch {
    return false
  }
  await delRuleFile([row.id])
  ElMessage.success('删除成功')
  await refreshFileList()
}
// èŽ·å–è§„ç« åˆ¶åº¦åˆ—è¡¨æ•°æ®
const getRegulationList = async () => {
  tableLoading.value = true
  listRuleManagement(page,regulationSearchForm)
  .then(res => {
    regulations.value = res.data.records
    // è¿‡æ»¤æŽ‰å·²åºŸå¼ƒçš„制度
    // regulations.value = res.data.records.filter(item => item.status !== 'repealed')
    page.value.total = res.data.total;
    tableLoading.value = false;
  }).catch(err => {
    tableLoading.value = false;
  })
}
onMounted(() => {
  // åˆå§‹åŒ–
  getRegulationList()
})
  onMounted(() => {
    // åˆå§‹åŒ–
    getRegulationList();
  });
</script>
<style scoped>
.app-container {
  padding: 20px;
}
  .app-container {
    padding: 20px;
  }
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
.tab-content {
  padding: 20px 0;
}
  .tab-content {
    padding: 20px 0;
  }
.mb-20 {
  margin-bottom: 20px;
}
  .mb-20 {
    margin-bottom: 20px;
  }
.mt-20 {
  margin-top: 20px;
}
  .mt-20 {
    margin-top: 20px;
  }
.ml-10 {
  margin-left: 10px;
}
  .ml-10 {
    margin-left: 10px;
  }
.regulation-content {
  background-color: #f5f5f5;
  padding: 15px;
  border-radius: 4px;
  line-height: 1.6;
  white-space: pre-wrap;
  height: 200px;
}
  .regulation-content {
    background-color: #f5f5f5;
    padding: 15px;
    border-radius: 4px;
    line-height: 1.6;
    white-space: pre-wrap;
    height: 200px;
  }
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
  .dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }
</style>
src/views/collaborativeApproval/sealManagement/index.vue
@@ -261,9 +261,9 @@
import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile, userListNoPageByTenantId } from '@/api/system/user.js'
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// å“åº”式数据
const currentUser = ref(null)
@@ -583,7 +583,7 @@
      currentUser.value = res.data.userName
    }
  })
  staffJoinListPage({staffState: 1, ...page}).then(res => {
  staffOnJobListPage({staffState: 1, ...page}).then(res => {
    tableLoading.value = false;
    // tableData.value = res.data.records
    // //筛选出和currentUser同名的人员
src/views/collaborativeApproval/shipmentReview/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <el-table :data="tableData" border height="40vh">
      <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="100" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">下载</el-button>
          <el-button link type="primary" size="small" @click="lookFile(scope.row)">预览</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import filePreview from '@/components/filePreview/index.vue'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  open
})
</script>
<style></style>
src/views/collaborativeApproval/shipmentReview/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,340 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">销售合同号:</span>
        <el-input
            v-model="searchForm.salesContractNo"
            style="width: 240px"
            placeholder="请输入销售合同号搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <span class="search_title ml10">审批状态:</span>
        <el-select v-model="searchForm.approveStatus" clearable @change="handleQuery" style="width: 240px">
          <el-option label="待审核" :value="2" />
          <el-option label="审核成功" :value="3" />
          <el-option label="审核失败" :value="4" />
        </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
      </div>
      <div>
<!--        <el-button type="primary" @click="openForm('add')">新增</el-button>-->
        <el-button @click="handleOut">导出</el-button>
<!--        <el-button type="danger" plain @click="handleDelete">删除</el-button>-->
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      ></PIMTable>
    </div>
    <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="approveType"></info-form-dia>
    <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia>
    <FileList ref="fileListRef" />
  </div>
</template>
<script setup>
import FileList from "./fileList.vue";
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {ElMessageBox} from "element-plus";
import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
import {getShipmentApprovalList, approveShipment} from "@/api/collaborativeApproval/shipmentReview.js";
// import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
// å®šä¹‰ç»„件接收的props
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 6
  }
});
const userList = ref([]);
const userStore = useUserStore();
const data = reactive({
  searchForm: {
    approveId: "",
    approveStatus: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  {
    label: "审批状态",
    prop: "approveStatus",
    dataType: "tag",
    width: 100,
    formatData: (params) => {
      if (params === 2) {
        return "待审核";
      } else if (params === 3) {
        return "审核完成";
      } else if (params === 4) {
        return "审核驳回";
      } else {
        return '未知状态';
      }
    },
    formatType: (params) => {
      if (params === 0) {
        return "warning";
      } else if (params === 2) {
        return "info";
      } else if (params === 3) {
        return "success";
      } else if (params === 4) {
        return "danger";
      } else {
        return 'danger';
      }
    },
  },
  {
    label: "销售合同号",
    prop: "salesContractNo",
    width: 170
  },
  {
    label: "客户名称",
    prop: "customerName",
    width: 200
  },
  {
    label: "产品大类",
    prop: "productCategory",
    width: 200
  },
  {
    label: "规格型号",
    prop: "specificationModel",
    width: 220
  },
  {
    label: "申请人",
    prop: "approveUserId",
    width: 120,
    align: "center",
    formatData:(params)=>{
      const user = userList.value.find(item => item.userId === params)
      return user ? user.nickName : '--'
    }
  },
  {
    label: "车牌号",
    prop: "shippingCarNumber",
    width: 120,
  },
  {
    label: "申请人",
    prop: "approveUserId",
    width: 120,
  },
  {
    label: "申请日期",
    prop: "executionDate",
    width: 200
  },
  {
    label: "当前审批人",
    prop: "salesman",
    width: 120
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 120,
    operation: [
      {
        name: "通过",
        type: "text",
        clickFun: (row) => {
          handleApproval("通过", row);
        },
        disabled: (row) => row.approveStatus !== 2
      },
      {
        name: "驳回",
        type: "text",
        clickFun: (row) => {
          handleApproval("驳回", row);
        },
        disabled: (row) => row.approveStatus !== 2
      },
      // {
      //   name: "编辑",
      //   type: "text",
      //   clickFun: (row) => {
      //     openForm("edit", row);
      //   },
      //   disabled: (row) => row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
      // },
      // {
      //   name: "审核",
      //   type: "text",
      //   clickFun: (row) => {
      //     openApprovalDia("approval", row);
      //   },
      //   disabled: (row) => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id
      // },
      // {
      //   name: "详情",
      //   type: "text",
      //   clickFun: (row) => {
      //     openApprovalDia('view', row);
      //   },
      // },
      // {
      //   name: "附件",
      //   type: "text",
      //   clickFun: (row) => {
      //     downLoadFile(row);
      //   },
      // },
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0
});
const infoFormDia = ref()
const approvalDia = ref()
const { proxy } = getCurrentInstance()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const fileListRef = ref(null)
const downLoadFile = (row) => {
  fileListRef.value.open(row.commonFileList)
}
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList =async () => {
  let userLists = await userListNoPage();
  userList.value = userLists.data;
  tableLoading.value = true;
  getShipmentApprovalList({...page, ...searchForm.value,approveType:props.approveType}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// å¯¼å‡º
const handleOut = () => {
  const type = Number(props.approveType || 6)
  const urlMap = {
    0: "/shipmentApproval/export",
  }
  const url = urlMap[type] || urlMap[0]
  const nameMap = {
    0: "发货审核表",
  }
  const fileName = nameMap[type] || nameMap[0]
  proxy.download(url, {}, `${fileName}.xlsx`)
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开新增、编辑弹框
const openForm = (type, row) => {
  nextTick(() => {
    infoFormDia.value?.openDialog(type, row)
  })
};
// æ‰“开新增检验弹框
const openApprovalDia = (type, row) => {
  nextTick(() => {
    approvalDia.value?.openDialog(type, row)
  })
};
// å®¡æ ¸é€šè¿‡/驳回
const handleApproval = (name = "审核",row) => {
  ElMessageBox.confirm(`选中的内容将被${name},是否确认${name}?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async()=>{
    let res = await approveShipment({
      id: row.id,
      approveStatus: name === "通过" ? 3 : 4
    });
    if(res.code === 200){
      proxy.$modal.msgSuccess(`${name}成功`);
    }else{
      proxy.$modal.msgError(`${name}失败`);
    }
    await getList()
  }).catch(err=>{
    proxy.$modal.msgError(`未知错误,请联系管理员`);
  })
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.approveId);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        approveProcessDelete(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList();
});
</script>
<style scoped></style>
src/views/equipmentManagement/ledger/Form.vue
@@ -1,5 +1,5 @@
<template>
  <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
  <el-form :model="form" label-width="120px" :rules="formRules" ref="formRef">
    <el-row :gutter="20">
      <el-col :span="12">
        <el-form-item label="设备名称" prop="deviceName">
@@ -14,6 +14,27 @@
      <el-col :span="12">
        <el-form-item label="设备品牌" prop="deviceBrand">
          <el-input v-model="form.deviceBrand" placeholder="请输入设备品牌" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="设备类型" prop="type">
          <el-select
            v-model="form.type"
            placeholder="请选择或输入设备类型"
            clearable
            filterable
            allow-create
            default-first-option
            style="width: 100%"
            @change="handleDeviceTypeChange"
          >
            <el-option
              v-for="item in deviceTypeOptions"
              :key="item"
              :label="item"
              :value="item"
            />
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -32,8 +53,19 @@
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="启用折旧" prop="enableDepreciation">
          <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" />
        <el-form-item label="启用折旧" prop="isDepr">
          <el-switch v-model="form.isDepr" :active-value="1" :inactive-value="2" />
        </el-form-item>
      </el-col>
      <el-col :span="12" v-if="form.isDepr === 1">
        <el-form-item label="每年折旧金额" prop="annualDepreciationAmount">
          <el-input-number
            :step="0.01"
            :min="0"
            style="width: 100%"
            v-model="form.annualDepreciationAmount"
            placeholder="请输入每年折旧金额"
          />
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -149,24 +181,47 @@
});
const formRef = ref(null);
const operationType = ref('');
// è®¾å¤‡ç±»åž‹å›ºå®šé€‰é¡¹
const deviceTypeOptions = ref([
  '生产设备',
  '办公设备',
  '检测设备',
  '运输设备',
  '其他设备'
]);
const formRules = {
    deviceName: [{ required: true, trigger: "blur", message: "请输入" }],
    deviceModel: [{ required: true, trigger: "blur", message: "请输入" }],
    type: [{ required: true, trigger: "change", message: "请选择或输入设备类型" }],
    supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
    unit: [{ required: true, trigger: "blur", message: "请输入" }],
    number: [{ required: true, trigger: "blur", message: "请输入" }],
    taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "请输入" }],
    taxRate: [{ required: true, trigger: "change", message: "请输入" }],
    planRuntimeTime: [{ required: true, trigger: "change", message: "请选择" }],
    annualDepreciationAmount: [
        {
            validator: (rule, value, callback) => {
                if (form.isDepr === 1 && (value === undefined || value === null || value === '')) {
                    callback(new Error('启用折旧时,请输入每年折旧金额'));
                } else {
                    callback();
                }
            },
            trigger: "blur"
        }
    ],
}
const { form, resetForm } = useFormData({
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  deviceBrand: undefined, // è®¾å¤‡å“ç‰Œ
  type: undefined, // è®¾å¤‡ç±»åž‹
  supplierName: undefined, // ä¾›åº”商
  storageLocation: undefined, // å­˜æ”¾ä½ç½®
  enableDepreciation: false, // æ˜¯å¦å¯ç”¨æŠ˜æ—§
  isDepr: 2, // æ˜¯å¦å¯ç”¨æŠ˜æ—§ 1-是 2-否
  annualDepreciationAmount: undefined, // æ¯å¹´æŠ˜æ—§é‡‘额
  unit: undefined, // å•位
  number: 1, // æ•°é‡
  taxIncludingPriceUnit: undefined, // å«ç¨Žå•ä»·
@@ -187,9 +242,11 @@
    form.deviceName = data.deviceName;
    form.deviceModel = data.deviceModel;
    form.deviceBrand = data.deviceBrand;
    form.type = data.type;
    form.supplierName = data.supplierName;
    form.storageLocation = data.storageLocation;
    form.enableDepreciation = data.enableDepreciation;
    form.isDepr = data.isDepr;
    form.annualDepreciationAmount = data.annualDepreciationAmount;
    form.unit = data.unit;
    form.number = 1;
    form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
@@ -200,6 +257,13 @@
  }
};
const handleDeviceTypeChange = (value) => {
  // å¦‚果输入的新值不在固定选项中,则添加到选项列表
  if (value && !deviceTypeOptions.value.includes(value)) {
    deviceTypeOptions.value.push(value);
  }
};
const mathNum = () => {
  if (!form.taxIncludingPriceUnit) {
    ElMessage.error("请输入单价");
src/views/equipmentManagement/ledger/index.vue
@@ -7,7 +7,6 @@
          style="width: 240px"
          placeholder="请输入设备名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
@@ -17,7 +16,6 @@
            style="width: 240px"
            placeholder="请输入规格型号"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
@@ -27,17 +25,6 @@
            style="width: 240px"
            placeholder="请输入供应商"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="单位">
        <el-input
            v-model="filters.unit"
            style="width: 240px"
            placeholder="请输入单位"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
@@ -130,81 +117,53 @@
    deviceName: undefined,
    deviceModel: undefined,
    supplierName: undefined,
    unit: undefined,
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
  [
    {
      label: "设备名称",
      align: "center",
      prop: "deviceName",
    },
    {
      label: "规格型号",
      align: "center",
      prop: "deviceModel",
    },
    {
      label: "设备品牌",
      align: "center",
      prop: "deviceBrand",
    },
    {
      label: "设备类型",
      prop: "type",
    },
    {
      label: "供应商",
      align: "center",
      prop: "supplierName",
    },
    {
      label: "单位",
      align: "center",
      prop: "unit",
    },
    {
      label: "存放位置",
      align: "center",
      prop: "storageLocation",
    },
    {
      label: "数量",
      align: "center",
      prop: "number",
    },
    {
      label: "含税单价",
      align: "center",
      prop: "taxIncludingPriceUnit",
    },
    {
      label: "含税总价",
      align: "center",
      prop: "taxIncludingPriceTotal",
    },
    {
      label: "税率",
      align: "center",
      prop: "taxRate",
    },
    {
      label: "不含税总价",
      align: "center",
      prop: "unTaxIncludingPriceTotal",
    },
    {
      label: "启用折旧",
      align: "center",
      prop: "enableDepreciation",
      formatData: (v) => (v ? "是" : "否"),
    },
    {
      label: "录入人",
      align: "center",
      prop: "createUser",
    },
    {
      label: "录入日期",
      align: "center",
      prop: "createTime",
      formatData: (v) => {
        if (!v) return '';
        // å¦‚果包含时分秒,只取日期部分
        if (v.includes(' ')) {
          return v.split(' ')[0];
        }
        return v;
      },
    },
        {
            dataType: "action",
@@ -215,14 +174,12 @@
            operation: [
                {
                    name: "编辑",
                    type: "text",
                    clickFun: (row) => {
                        edit(row.id)
                    },
                },
                {
                    name: "生成二维码",
                    type: "text",
                    clickFun: (row) => {
                        showQRCode(row)
                    },
src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
<template>
</template>
<script setup>
</script>
src/views/equipmentManagement/measurementEquipment/components/formDia.vue
@@ -15,19 +15,10 @@
                ref="formRef"
            >
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="计量器具编号:" prop="code">
                    <el-col :span="24">
                        <el-form-item label="出厂编号:" prop="code">
                            <el-input
                                v-model="form.code"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="计量器具名称:" prop="name">
                            <el-input
                                v-model="form.name"
                                placeholder="请输入"
                                clearable
                            />
@@ -36,28 +27,70 @@
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="规格型号:" prop="model">
                        <el-form-item label="安装位置:" prop="installationLocation">
                            <el-input
                                v-model="form.model"
                                v-model="form.installationLocation"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="预计下次检定日期:" prop="nextDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.nextDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                clearable
                            />
                        <el-form-item label="检定单位:" prop="unit">
              <el-input
                  v-model="form.unit"
                  placeholder="请输入检定单位"
                  clearable
              />
                        </el-form-item>
                    </el-col>
                </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="证书编号:" prop="model">
              <el-input
                  v-model="form.model"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="最新鉴定日期:" prop="mostDate">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.mostDate"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="有效日期(天):" prop="valid">
              <el-input
                  v-model="form.valid"
                  placeholder="请输入有效期天数"
                  clearable
              >
              <template #append>日</template>
              </el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检定周期:" prop="cycle">
              <el-input
                  v-model="form.cycle"
                  placeholder="请输入检定周期"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="录入人:" prop="userId">
@@ -84,6 +117,7 @@
                                style="width: 100%"
                                v-model="form.recordDate"
                                value-format="YYYY-MM-DD"
                disabled
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
@@ -126,7 +160,7 @@
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import {getToken} from "@/utils/auth.js";
import {measuringInstrumentAdd, measuringInstrumentUpdate} from "@/api/equipmentManagement/measurementEquipment.js";
import {addMeasuringInstrumentLedger, updateMeasuringInstrumentLedger} from "@/api/equipmentManagement/measurementEquipment.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -137,8 +171,10 @@
const data = reactive({
    form: {
        code: "",
        name: "",
    installationLocation: "",
    mostDate:"",
        model: "",
    cycle:"",
        validDate: "",
        nextDate: "",
        userId: "",
@@ -147,12 +183,16 @@
    },
    rules: {
        code: [{required: true, message: "请输入", trigger: "blur"}],
        name: [{required: true, message: "请输入", trigger: "blur"}],
        model: [{required: true, message: "请输入", trigger: "blur"}],
        validDate: [{required: true, message: "请输入", trigger: "blur"}],
        nextDate: [{required: true, message: "请选择", trigger: "change"}],
        userId: [{required: true, message: "请选择", trigger: "change"}],
        recordDate: [{required: true, message: "请选择", trigger: "change"}],
    installationLocation: [{required: true, message: "请输入", trigger: "blur"}],
    mostDate: [{required: true, message: "请选择", trigger: "change"}],
    cycle: [{required: true, message: "请选择", trigger: "blur"}],
    valid: [{required: true, message: "请输入", trigger: "blur"}],
    unit: [{required: true, message: "请输入", trigger: "blur"}],
    }
})
const { form, rules } = toRefs(data);
@@ -217,13 +257,13 @@
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            if (operationType.value === "add") {
                measuringInstrumentAdd(form.value).then(response => {
        addMeasuringInstrumentLedger(form.value).then(response => {
                    proxy.$modal.msgSuccess("新增成功")
          form.value.tempFileIds = []
                    closeDia()
                })
            } else {
                measuringInstrumentUpdate(form.value).then(response => {
        updateMeasuringInstrumentLedger(form.value).then(response => {
                    proxy.$modal.msgSuccess("修改成功")
          form.value.tempFileIds = []
                    closeDia()
src/views/equipmentManagement/measurementEquipment/components/rowClickData.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="检定校准记录"
        width="50%"
        @close="closeDia"
    >
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :tableLoading="tableLoading"
          @selection-change="handleSelectionChange"
          height="500"
          :isPagination="false"
      >
      </PIMTable>
      <pagination
          style="margin: 10px 0"
          v-show="total > 0"
          @pagination="paginationSearch"
          :total="total"
          :page="page.current"
          :limit="page.size"
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
<script setup>
import {ref} from "vue";
import filePreview from '@/components/filePreview/index.vue'
import {ledgerRecordListPage} from "@/api/equipmentManagement/calibration.js";
import Pagination from "@/components/PIMTable/Pagination.vue";
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const currentId = ref('')
const selectedRows = ref([]);
const filePreviewRef = ref()
const tableColumn = ref([
  {
    label: "检定日期",
    prop: "recordDate",
    width: 130,
  },
  {
    label: "计量器具编号",
    prop: "code",
    width: 150,
  },
  {
    label: "计量器具名称",
    prop: "name",
    width: 200,
  },
  {
    label: "规格型号",
    prop: "model",
    width:200
  },
  {
    label: "有效期",
    prop: "valid",
    width: 100,
  },
  {
    label: "录入人",
    prop: "userName",
  },
  {
    label: "录入日期",
    prop: "entryDate",
    width: 130,
  },
]);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const tableData = ref([]);
const tableLoading = ref(false);
// æ‰“开弹框
const openDialog = (row,type) => {
  dialogFormVisible.value = true;
  currentId.value = row.id;
  getList()
}
const paginationSearch = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  let query = {
    measuringInstrumentLedgerId:currentId.value,
    current : page.current,
    size : page.size
  }
  ledgerRecordListPage(query).then(res => {
    tableData.value = res?.data?.records || [];
    total.value = res?.data?.total;
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
src/views/equipmentManagement/measurementEquipment/index.vue
@@ -40,11 +40,13 @@
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
        :dbRowClick="dbRowClick"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <calibration-dia ref="calibrationDia" @close="handleQuery"></calibration-dia>
    <files-dia ref="filesDia"></files-dia>
    <rowClickDataForm ref="rowClickData"></rowClickDataForm>
    </div>
</template>
@@ -55,10 +57,11 @@
import useUserStore from "@/store/modules/user.js";
import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue";
import {
    measuringInstrumentDelete,
    measuringInstrumentListPage
  measuringInstrumentDelete,
  measuringInstrumentListPage,
} from "@/api/equipmentManagement/measurementEquipment.js";
import FilesDia from "./filesDia.vue";
import rowClickDataForm from "./components/rowClickData.vue"
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -73,67 +76,80 @@
const tableColumn = ref([
    {
        label: "状态",
        prop: "status",
        dataType: "tag",
        formatData: (params) => {
            if (params == 1) {
                return "有效";
            } else if (params == 2) {
                return "逾期";
            } else {
                return null;
            }
        },
        formatType: (params) => {
            if (params == 1) {
                return "success";
            } else if (params == 2) {
                return "danger";
            } else {
                return null;
            }
        },
        label: "出厂编号",
        prop: "code",
    minWidth:150,
    align:"center"
    },
    {
        label: "最近一次检定日期",
        label: "部门",
        prop: "deptName",
        width: 130,
    align:"center"
    },
    {
        label: "安装位置",
        prop: "installationLocation",
        width: 150,
    align:"center"
    },
    {
        label: "检定单位",
        prop: "unit",
        width: 200,
    align:"center"
    },
    {
        label: "证书编号",
        prop: "model",
        width:200,
    align:"center"
    },
    {
        label: "最新鉴定日期",
        prop: "mostDate",
        width: 130,
    },
    {
        label: "计量器具编号",
        prop: "code",
        width: 150,
    },
    {
        label: "计量器具名称",
        prop: "name",
        width: 200,
    },
    {
        label: "规格型号",
        prop: "model",
        width:200
    },
    {
        label: "有效期",
        prop: "valid",
        width: 130,
    },
    {
        label: "预计下次检定日期",
        prop: "nextDate",
        width: 130,
    align:"center"
    },
    {
        label: "录入人",
        prop: "userName",
        width: 130,
    align:"center"
    },
    {
        label: "录入日期",
        prop: "recordDate",
        width: 130,
    align:"center",
    minWidth: 130
    },
    {
        label: "有效日期",
        prop: "valid",
        width: 130,
    align:"center"
    },
  {
    label: "检定周期(天)",
    prop: "cycle",
    width: 130,
    align:"center"
  },
  {
    label: "状态",
    prop: "status",
    width: 130,
    align: "center",
    formatData: (params) => {
      if (params === 1) {
        return "有效";
      } else if (params === 2) {
        return "逾期";
      } else {
        return null;
      }
    }
  },
    {
        dataType: "action",
        label: "操作",
@@ -141,25 +157,26 @@
        width: '130',
        fixed: 'right',
        operation: [
      {
          name: "附件",
          type: "text",
          clickFun: (row) => {
          openFilesFormDia(row);
          },
      },
            {
                name: "检定校准",
                name: "查看",
                type: "text",
                clickFun: (row) => {
                    openCalibrationDia("verifying", row);
                },
            },
            // {
            //     name: "附件",
            //     type: "text",
            //     clickFun: (row) => {
      //     openFilesFormDia(row);
            //     },
            // },
        ],
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const rowClickData = ref([])
const filesDia = ref()
const page = reactive({
    current: 1,
@@ -170,12 +187,13 @@
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  console.log(row)
  nextTick(() => {
    filesDia.value?.openDialog( row,'计量器具台账')
  })
    filesDia.value?.openDialog(row,'计量器具台账')
};
const dbRowClick = (row)=>{
  rowClickData.value?.openDialog(row)
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
src/views/equipmentManagement/repair/Form/MaintainForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/repair/Form/RepairForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -1,53 +1,108 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr" draggable>
    <MaintainForm ref="maintainFormRef" />
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="visible"
    :title="'设备维修'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="80px">
      <el-form-item label="维修人">
        <el-input v-model="form.maintenanceName" placeholder="请输入维修人" />
      </el-form-item>
      <el-form-item label="维修结果">
        <el-input v-model="form.maintenanceResult" placeholder="请输入维修结果" />
      </el-form-item>
      <el-form-item label="维修状态">
        <el-select v-model="form.status">
          <el-option label="待报修" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="维修日期">
        <el-date-picker
          v-model="form.maintenanceTime"
          placeholder="请选择维修日期"
          format="YYYY-MM-DD HH:mm:ss"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="datetime"
          clearable
          style="width: 100%"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import MaintainForm from "../Form/MaintainForm.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { addMaintain } from "@/api/equipmentManagement/repair";
import useFormData from "@/hooks/useFormData";
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
import { ElMessage } from "element-plus";
defineOptions({
  name: "维修模态框",
});
const maintainFormRef = ref();
const emits = defineEmits(["ok"]);
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "设备维修" });
// ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
const repairId = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const { form, resetForm } = useFormData({
  maintenanceName: undefined, // ç»´ä¿®åç§°
  maintenanceResult: undefined, // ç»´ä¿®ç»“æžœ
  maintenanceTime: undefined, // ç»´ä¿®æ—¥æœŸ
  status: 0,
});
const setForm = (data) => {
  form.maintenanceName = data.maintenanceName ?? userStore.nickName;
  form.maintenanceResult = data.maintenanceResult;
  form.maintenanceTime =
    data.maintenanceTime
      ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss")
      : dayjs().format("YYYY-MM-DD HH:mm:ss");
  form.status = 1; // é»˜è®¤çŠ¶æ€ä¸ºå®Œç»“
};
const sendForm = async () => {
  loading.value = true;
  const form = await maintainFormRef.value.getForm();
  const { code } = await addMaintain({ id: id.value, ...form });
  if (code == 200) {
    emits("ok");
    maintainFormRef.value.resetForm();
    closeModal();
  try {
    const { code } = await addMaintain({ id: repairId.value, ...form });
    if (code == 200) {
      ElMessage.success("维修成功");
      emits("ok");
      resetForm();
      visible.value = false;
    }
  } finally {
    loading.value = false;
  }
  loading.value = false;
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const open = async (id, row) => {
  openModal(id);
  repairId.value = id; // ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
  visible.value = true;
  await nextTick();
  maintainFormRef.value.setForm(row);
  setForm(row);
};
defineExpose({
src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,93 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" @close="close" draggable>
    <RepairForm ref="repairFormRef" :id="id" />
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备报修' : '新增设备报修'"
    width="800px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-row>
        <el-col :span="12">
          <el-form-item label="设备名称">
            <el-select v-model="form.deviceLedgerId" @change="setDeviceModel" filterable>
              <el-option
                v-for="(item, index) in deviceOptions"
                :key="index"
                :label="item.deviceName"
                :value="item.id"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号">
            <el-input
              v-model="form.deviceModel"
              placeholder="请输入规格型号"
              disabled
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修日期">
            <el-date-picker
              v-model="form.repairTime"
              placeholder="请选择报修日期"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              type="date"
              clearable
              style="width: 100%"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修人">
            <el-input v-model="form.repairName" placeholder="请输入报修人" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row v-if="id">
        <el-col :span="12">
          <el-form-item label="报修状态">
            <el-select v-model="form.status">
              <el-option label="待维修" :value="0"></el-option>
              <el-option label="完结" :value="1"></el-option>
              <el-option label="失败" :value="2"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="故障现象">
            <el-input
              v-model="form.remark"
              :rows="2"
              type="textarea"
              placeholder="请输入故障现象"
            />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import RepairForm from "../Form/RepairForm.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addRepair,
  editRepair,
  getRepairById,
} from "@/api/equipmentManagement/repair";
import { ElMessage } from "element-plus";
import dayjs from "dayjs";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import useUserStore from "@/store/modules/user";
defineOptions({
  name: "设备报修弹窗",
@@ -26,48 +95,83 @@
const emits = defineEmits(["ok"]);
const repairFormRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "设备报修" });
const id = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const deviceOptions = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined, // è®¾å¤‡Id
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  repairTime: dayjs().format("YYYY-MM-DD"), // æŠ¥ä¿®æ—¥æœŸï¼Œé»˜è®¤å½“天
  repairName: userStore.nickName, // æŠ¥ä¿®äºº
  remark: undefined, // æ•…障现象
  status: 0, // æŠ¥ä¿®çŠ¶æ€
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
};
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.repairTime = data.repairTime;
  form.repairName = data.repairName;
  form.remark = data.remark;
  form.status = data.status;
};
const sendForm = async () => {
  loading.value = true;
  const form = await repairFormRef.value.getForm();
  const { code } = id.value
    ? await editRepair({ id: unref(id), ...form })
    : await addRepair(form);
  if (code == 200) {
    ElMessage.success(`${id ? "编辑" : "新增"}报修成功`);
    closeModal();
    emits("ok");
  try {
    const { code } = id.value
      ? await editRepair({ id: unref(id), ...form })
      : await addRepair(form);
    if (code == 200) {
      ElMessage.success(`${id.value ? "编辑" : "新增"}报修成功`);
      visible.value = false;
      emits("ok");
    }
  } finally {
    loading.value = false;
  }
  loading.value = false;
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const openAdd = async () => {
  openModal();
  id.value = undefined;
  visible.value = true;
  await nextTick();
  await repairFormRef.value.loadDeviceName();
  await loadDeviceName();
};
const openEdit = async (id) => {
  const { data } = await getRepairById(id);
  openModal(id);
const openEdit = async (editId) => {
  const { data } = await getRepairById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  await repairFormRef.value.loadDeviceName();
  await repairFormRef.value.setForm(data);
};
const close = () => {
  repairFormRef.value.resetForm();
  closeModal();
  await loadDeviceName();
  setForm(data);
};
defineExpose({
@@ -75,3 +179,5 @@
  openEdit,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/repair/index.vue
@@ -75,10 +75,10 @@
            å¯¼å‡º
          </el-button>
          <el-button
              type="danger"
              icon="Delete"
              :disabled="multipleList.length <= 0"
              @click="delRepairByIds(multipleList.map((item) => item.id))"
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0 || hasFinishedStatus"
            @click="delRepairByIds(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
          </el-button>
@@ -104,25 +104,26 @@
        </template>
        <template #operation="{ row }">
          <el-button
              type="primary"
              text
              @click="addMaintain(row)"
          >
            æ–°å¢žç»´ä¿®
          </el-button>
          <el-button
              type="primary"
              text
              icon="editPen"
              @click="editRepair(row.id)"
            type="primary"
            link
            :disabled="row.status === 1"
            @click="editRepair(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
              type="danger"
              text
              icon="delete"
              @click="delRepairByIds(row.id)"
            type="success"
            link
            :disabled="row.status === 1"
            @click="addMaintain(row)"
          >
            ç»´ä¿®
          </el-button>
          <el-button
            type="danger"
            link
            :disabled="row.status === 1"
            @click="delRepairByIds(row.id)"
          >
            åˆ é™¤
          </el-button>
@@ -135,9 +136,10 @@
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { onMounted, getCurrentInstance, computed } from "vue";
import {usePaginationApi} from "@/hooks/usePaginationApi";
import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
import {onMounted, getCurrentInstance} from "vue";
import RepairModal from "./Modal/RepairModal.vue";
import {ElMessageBox, ElMessage} from "element-plus";
import dayjs from "dayjs";
@@ -257,6 +259,11 @@
  multipleList.value = selectionList;
};
// æ£€æŸ¥é€‰ä¸­çš„记录中是否有完结状态的
const hasFinishedStatus = computed(() => {
  return multipleList.value.some(item => item.status === 1)
})
// æ–°å¢žæŠ¥ä¿®
const addRepair = () => {
  repairModalRef.value.openAdd();
@@ -280,6 +287,18 @@
// å•行删除
const delRepairByIds = async (ids) => {
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  const idsArray = Array.isArray(ids) ? ids : [ids];
  const hasFinished = idsArray.some(id => {
    const record = dataList.value.find(item => item.id === id);
    return record && record.status === 1;
  });
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录');
    return;
  }
  ElMessageBox.confirm("确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?", "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
src/views/equipmentManagement/spareParts/index.vue
@@ -37,6 +37,7 @@
          </template>
        </el-table-column>
        <el-table-column prop="price" label="ä»·æ ¼" width="140"></el-table-column>
        <el-table-column prop="quantity" label="数量" width="140"></el-table-column>
        <el-table-column prop="description" label="描述" width="150"></el-table-column>
        <el-table-column label="操作" width="150" fixed="right" align="center">
          <template #default="{ row }">
@@ -85,6 +86,9 @@
        </el-form-item>
        <el-form-item label="备件编号" prop="sparePartsNo">
          <el-input v-model="form.sparePartsNo"></el-input>
        </el-form-item>
        <el-form-item label="数量" prop="quantity">
          <el-input type="number" v-model="form.quantity"></el-input>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-select v-model="form.status" placeholder="请选择状态">
@@ -162,6 +166,9 @@
  sparePartsNo: [
    { required: true, message: '请输入备件编号', trigger: 'blur' }
  ],
  quantity:[
    { required: true, message: '请输入数量', trigger: 'blur' }
  ],
  status: [
    { required: true, message: '请选择状态', trigger: 'change' }
  ],
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
<template>
  <FormDialog
    v-model="visible"
    :title="'设备保养'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-form-item label="实际保养人">
        <el-input
          v-model="form.maintenanceActuallyName"
          placeholder="请输入实际保养人"
        ></el-input>
      </el-form-item>
      <el-form-item label="实际保养日期">
        <el-date-picker
          v-model="form.maintenanceActuallyTime"
          placeholder="请选择实际保养日期"
          format="YYYY-MM-DD HH:mm:ss"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="datetime"
          clearable
          style="width: 100%"
        />
      </el-form-item>
      <el-form-item label="保养状态">
        <el-select v-model="form.status">
          <el-option label="待保养" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="保养结果">
        <el-input
          v-model="form.maintenanceResult"
          placeholder="请输入保养结果"
          type="text" />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { addMaintenance } from "@/api/equipmentManagement/upkeep";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
import useUserStore from "@/store/modules/user";
import { ElMessage } from "element-plus";
defineOptions({
  name: "保养模态框",
});
const emits = defineEmits(["ok"]);
// ä¿å­˜è®¡åˆ’保养记录的id
const planId = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const { form, resetForm } = useFormData({
  maintenanceActuallyName: undefined, // å®žé™…保养人
  maintenanceActuallyTime: undefined, // å®žé™…保养日期
  maintenanceResult: undefined, // ä¿å…»ç»“æžœ
  status: 0, // ä¿å…»çŠ¶æ€
});
const setForm = (data) => {
  form.maintenanceActuallyName =
    data.maintenanceActuallyName ?? userStore.nickName;
  form.maintenanceActuallyTime =
    data.maintenanceActuallyTime
      ? dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss")
      : dayjs().format("YYYY-MM-DD HH:mm:ss");
  form.maintenanceResult = data.maintenanceResult;
  form.status = 1; // é»˜è®¤çŠ¶æ€ä¸ºå®Œç»“
};
/**
 * @desc ä¿å­˜ä¿å…»
 */
const sendForm = async () => {
  loading.value = true;
  try {
    const { code } = await addMaintenance({ id: planId.value, ...form });
    if (code == 200) {
      ElMessage.success("保养成功");
      emits("ok");
      resetForm();
      visible.value = false;
    }
  } finally {
    loading.value = false;
  }
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const open = async (id, row) => {
  planId.value = id; // ä¿å­˜è®¡åˆ’保养记录的id
  visible.value = true;
  await nextTick();
  setForm(row);
};
defineExpose({
  open,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/upkeep/Form/PlanForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Form/PlanModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,188 @@
<template>
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备保养计划' : '新增设备保养计划'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-form-item label="设备名称">
        <el-select
          v-model="form.deviceLedgerId"
          @change="setDeviceModel"
          placeholder="请选择设备"
          filterable
          default-first-option
          :reserve-keyword="false"
        >
          <el-option
            v-for="(item, index) in deviceOptions"
            :key="index"
            :label="item.deviceName"
            :value="item.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="规格型号">
        <el-input
          v-model="form.deviceModel"
          placeholder="请输入规格型号"
          disabled
        />
      </el-form-item>
      <el-form-item label="录入人">
        <el-select
          v-model="form.createUser"
          placeholder="请选择"
          filterable
          default-first-option
          :reserve-keyword="false"
          clearable
        >
          <el-option
            v-for="item in userList"
            :key="item.userId"
            :label="item.nickName"
            :value="item.userId"
          />
        </el-select>
      </el-form-item>
      <el-form-item v-if="id" label="保修状态">
        <el-select v-model="form.status">
          <el-option label="待保修" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="计划保养日期">
        <el-date-picker
          style="width: 100%"
          v-model="form.maintenancePlanTime"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="date"
          placeholder="请选择计划保养日期日期"
          clearable
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addUpkeep,
  editUpkeep,
  getUpkeepById,
} from "@/api/equipmentManagement/upkeep";
import { ElMessage } from "element-plus";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { onMounted } from "vue";
import dayjs from "dayjs";
import { userListNoPage } from "@/api/system/user.js";
defineOptions({
  name: "设备保养新增计划",
});
const emits = defineEmits(["ok"]);
const id = ref();
const visible = ref(false);
const loading = ref(false);
const deviceOptions = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined, // è®¾å¤‡Id
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  maintenancePlanTime: undefined, // è®¡åˆ’保养日期
  createUser: undefined, // å½•入人
  status: 0, //保修状态
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
};
/**
 * @desc è®¾ç½®è¡¨å•内容
 * @param data è®¾å¤‡ä¿¡æ¯
 */
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.createUser = Number(data.createUser);
  form.status = data.status;
  form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
    "YYYY-MM-DD HH:mm:ss"
  );
};
// ç”¨æˆ·åˆ—表
const userList = ref([]);
onMounted(() => {
  loadDeviceName();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
});
const openEdit = async (editId) => {
  const { data } = await getUpkeepById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  setForm(data);
};
const sendForm = async () => {
  loading.value = true;
  try {
    const { code } = id.value
      ? await editUpkeep({ id: unref(id), ...form })
      : await addUpkeep(form);
    if (code == 200) {
      ElMessage.success(`${id.value ? "编辑" : "新增"}计划成功`);
      visible.value = false;
      emits("ok");
    }
  } finally {
    loading.value = false;
  }
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const openModal = () => {
  id.value = undefined;
  visible.value = true;
};
defineExpose({
  openModal,
  openEdit,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/upkeep/Form/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,304 @@
<template>
    <FormDialog
        v-model="dialogVisitable"
        :title="operationType === 'add' ? '新增保养任务' : '编辑保养任务'"
        width="800px"
        :operation-type="operationType"
        @confirm="submitForm"
        @cancel="cancel"
        @close="cancel"
    >
        <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
            <el-row>
                <el-col :span="12">
                    <el-form-item label="设备名称" prop="taskId">
                        <el-select v-model="form.taskId" @change="setDeviceModel" filterable>
                            <el-option
                                v-for="(item, index) in deviceOptions"
                                :key="index"
                                :label="item.deviceName"
                                :value="item.id"
                            ></el-option>
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="规格型号">
                        <el-input
                            v-model="form.deviceModel"
                            placeholder="请输入规格型号"
                            disabled
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="录入人" prop="inspector">
                        <el-select
                            v-model="form.inspector"
                            filterable
                            default-first-option
                            :reserve-keyword="false"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in userList"
                                :label="item.nickName"
                                :value="item.userId"
                                :key="item.userId"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="登记时间" prop="registrationDate">
                        <el-date-picker
                            v-model="form.registrationDate"
                            type="date"
                            placeholder="选择登记日期"
                            format="YYYY-MM-DD"
                            value-format="YYYY-MM-DD"
                            style="width: 100%"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="任务频率" prop="frequencyType">
                        <el-select v-model="form.frequencyType" placeholder="请选择" clearable>
                            <el-option label="每日" value="DAILY"/>
                            <el-option label="每周" value="WEEKLY"/>
                            <el-option label="每月" value="MONTHLY"/>
                            <el-option label="季度" value="QUARTERLY"/>
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm" />
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-select v-model="form.week" placeholder="请选择" clearable style="width: 50%">
                            <el-option label="周一" value="MON"/>
                            <el-option label="周二" value="TUE"/>
                            <el-option label="周三" value="WED"/>
                            <el-option label="周四" value="THU"/>
                            <el-option label="周五" value="FRI"/>
                            <el-option label="周六" value="SAT"/>
                            <el-option label="周日" value="SUN"/>
                        </el-select>
                        <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm"  style="width: 50%"/>
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-date-picker
                            v-model="form.frequencyDetail"
                            type="datetime"
                            clearable
                            placeholder="选择开始日期"
                            format="DD,HH:mm"
                            value-format="DD,HH:mm"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-date-picker
                            v-model="form.frequencyDetail"
                            type="datetime"
                            clearable
                            placeholder="选择开始日期"
                            format="MM,DD,HH:mm"
                            value-format="MM,DD,HH:mm"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="备注" prop="remarks">
                        <el-input v-model="form.remarks" placeholder="请输入备注" type="textarea" />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
    </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { reactive, ref, getCurrentInstance, toRefs } from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { deviceMaintenanceTaskAdd, deviceMaintenanceTaskEdit } from "@/api/equipmentManagement/upkeep";
import { getCurrentDate } from "@/utils/index.js";
import useUserStore from "@/store/modules/user.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const dialogVisitable = ref(false);
const operationType = ref('add');
const deviceOptions = ref([]);
const userStore = useUserStore();
const data = reactive({
    form: {
        taskId: undefined,
        taskName: undefined,
        // å½•入人:单选一个用户 id
        inspector: undefined,
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        deviceModel: undefined, // è§„格型号
        registrationDate: ''
    },
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
        inspector: [{ required: true, message: "请选择录入人", trigger: "blur" },],
        registrationDate: [{ required: true, message: "请选择登记时间", trigger: "change" }]
    }
})
const { form, rules } = toRefs(data)
const userList = ref([])
const loadDeviceName = async () => {
    const { data } = await getDeviceLedger();
    deviceOptions.value = data;
};
// é€‰æ‹©è®¾å¤‡æ—¶ï¼Œå›žå¡«è®¾å¤‡åç§°(taskName)和规格型号(deviceModel)
const setDeviceModel = (id) => {
    const option = deviceOptions.value.find((item) => item.id === id);
    if (option) {
        form.value.taskId = option.id;
        form.value.taskName = option.deviceName;
        form.value.deviceModel = option.deviceModel;
    }
}
// æ‰“开弹框
const openDialog = async (type, row) => {
    dialogVisitable.value = true
    operationType.value = type
    // é‡ç½®è¡¨å•
    resetForm();
    // åŠ è½½ç”¨æˆ·åˆ—è¡¨
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    // åŠ è½½è®¾å¤‡åˆ—è¡¨
    await loadDeviceName();
    if (type === 'edit' && row) {
        form.value = { ...row }
        // ç¼–辑时用接口返回的 registrantId å›žæ˜¾å½•入人
        if (row.registrantId) {
            form.value.inspector = row.registrantId
        }
        // å¦‚果有设备ID,自动设置设备信息
        if (form.value.taskId) {
            setDeviceModel(form.value.taskId);
        }
    } else if (type === 'add') {
        // æ–°å¢žæ—¶è®¾ç½®ç™»è®°æ—¥æœŸä¸ºå½“天
        form.value.registrationDate = getCurrentDate();
        // æ–°å¢žæ—¶è®¾ç½®å½•入人为当前登录账户
        form.value.inspector = userStore.id;
    }
}
// å…³é—­å¯¹è¯æ¡†
const cancel = () => {
    resetForm()
    dialogVisitable.value = false
    emit('closeDia')
}
// é‡ç½®è¡¨å•函数
const resetForm = () => {
    if (proxy.$refs.formRef) {
        proxy.$refs.formRef.resetFields()
    }
    // é‡ç½®è¡¨å•数据确保设备信息正确重置
    form.value = {
        taskId: undefined,
        taskName: undefined,
        inspector: undefined,
        inspector: undefined,
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        deviceModel: undefined,
        registrationDate: ''
    }
}
// æäº¤è¡¨å•
const submitForm = () => {
    proxy.$refs["formRef"].validate(async valid => {
        if (valid) {
            try {
                const payload = { ...form.value }
                // ä¸å†å‘后端传保养人字段,仅使用接口要求的 registrant / registrantId
                // æ ¹æ®é€‰æ‹©çš„"录入人"设置 registrant / registrantId
                if (payload.inspector) {
                    const selectedUser = userList.value.find(
                        (u) => String(u.userId) === String(payload.inspector)
                    )
                    if (selectedUser) {
                        payload.registrantId = selectedUser.userId
                        payload.registrant = selectedUser.nickName
                    }
                }
                delete payload.inspector
                delete payload.inspectorIds
                if (payload.frequencyType === 'WEEKLY') {
                    let frequencyDetail = ''
                    frequencyDetail = payload.week + ',' + payload.time
                    payload.frequencyDetail = frequencyDetail
                }
                // å½•入日期:直接使用表单里的 registrationDate å­—段
                // ä¸€äº›é»˜è®¤çŠ¶æ€å­—æ®µ
                if (payload.status === undefined || payload.status === null || payload.status === '') {
                    payload.status = '0' // é»˜è®¤çŠ¶æ€ï¼Œå¯æŒ‰å®žé™…æžšä¸¾è°ƒæ•´
                }
                payload.active = true
                payload.deleted = 0
                if (operationType.value === 'edit') {
                    await deviceMaintenanceTaskEdit(payload)
                } else {
                    await deviceMaintenanceTaskAdd(payload)
                }
                cancel()
                proxy.$modal.msgSuccess('提交成功')
            } catch (error) {
                proxy.$modal.msgError('提交失败,请重试')
            }
        }
    })
}
defineExpose({ openDialog })
</script>
<style scoped>
</style>
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Modal/formDia.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/index.vue
@@ -64,16 +64,14 @@
            <template #operation="{ row }">
              <el-button
                type="primary"
                text
                icon="editPen"
                link
                @click="editScheduledTask(row)"
              >
                ç¼–辑
              </el-button>
              <el-button
                type="danger"
                text
                icon="delete"
                link
                @click="delScheduledTaskByIds(row.id)"
              >
                åˆ é™¤
@@ -144,7 +142,7 @@
              <el-button
                type="danger"
                icon="Delete"
                :disabled="multipleList.length <= 0"
                :disabled="multipleList.length <= 0 || hasFinishedStatus"
                @click="delRepairByIds(multipleList.map((item) => item.id))"
              >
                æ‰¹é‡åˆ é™¤
@@ -182,16 +180,24 @@
          </el-button>
          <el-button
            type="primary"
            text
            icon="editPen"
            link
            :disabled="row.status === 1"
            @click="editPlan(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="success"
            link
            :disabled="row.status === 1"
            @click="addMaintain(row)"
          >
            ä¿å…»
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            link
            :disabled="row.status === 1"
            @click="delRepairByIds(row.id)"
          >
            åˆ é™¤
@@ -208,12 +214,12 @@
</template>
<script setup>
import { ref, onMounted, reactive, getCurrentInstance, nextTick } from 'vue'
import { ref, onMounted, reactive, getCurrentInstance, nextTick, computed } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import PlanModal from './Modal/PlanModal.vue'
import MaintenanceModal from './Modal/MaintenanceModal.vue'
import FormDia from './Modal/formDia.vue'
import PlanModal from './Form/PlanModal.vue'
import MaintenanceModal from './Form/MaintenanceModal.vue'
import FormDia from './Form/formDia.vue'
import {
  getUpkeepPage,
  delUpkeep,
@@ -493,6 +499,11 @@
  multipleList.value = selection
}
// æ£€æŸ¥é€‰ä¸­çš„记录中是否有完结状态的
const hasFinishedStatus = computed(() => {
  return multipleList.value.some(item => item.status === 1)
})
const changePage = (page) => {
  pagination.value.currentPage = page.page
  pagination.value.pageSize = page.limit
@@ -512,6 +523,13 @@
}
const delRepairByIds = async (ids) => {
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  const hasFinished = multipleList.value.some(item => item.status === 1)
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?', '警告', {
      confirmButtonText: '确定',
src/views/financialManagement/accounting/index.vue
@@ -2,14 +2,31 @@
  <div style="padding: 20px;">
    <!-- é¡µé¢æ ‡é¢˜å’Œç­›é€‰æ¡ä»¶ -->
    <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
      <el-button
        type="primary"
        icon="Refresh"
        @click="resetFilters"
        size="default"
      >
        æŸ¥è¯¢
      </el-button>
      <el-form :inline="true">
        <el-form-item label="年份">
          <el-date-picker
            v-model="selectedYear"
            type="year"
            placeholder="请选择年份"
            format="YYYY"
            value-format="YYYY"
            clearable
            @change="fetchData()"
            style="width: 200px"
            :disabled-date="(date) => date.getFullYear() > new Date().getFullYear()"
          />
        </el-form-item>
        <el-form-item>
          <el-button
            type="primary"
            icon="Refresh"
            @click="resetFilters"
            size="default"
          >
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
    </div>
    <main class="container mx-auto px-4 pb-10">
@@ -27,7 +44,7 @@
        <el-card class="bg3">
          <p>资产原值</p>
          <h3>
            Â¥{{ assetInfo.totalOriginalValue }}
            Â¥{{ formatCurrency(assetInfo.totalOriginalValue) }}
          </h3>
        </el-card>
@@ -35,7 +52,7 @@
        <el-card class="bg4">
          <p>累计折旧</p>
          <h3>
            Â¥{{ assetInfo.totalDepreciation }}
            Â¥{{ formatCurrency(assetInfo.totalDepreciation) }}
          </h3>
        </el-card>
@@ -43,7 +60,21 @@
        <el-card class="bg5">
          <p>净值</p>
          <h3>
            Â¥{{ assetInfo.totalNetValue }}
            Â¥{{ formatCurrency(assetInfo.totalNetValue) }}
          </h3>
        </el-card>
        <!-- è´Ÿå€º -->
        <el-card class="bg2">
          <p>负债</p>
          <h3>
            Â¥{{ formatCurrency(assetInfo.debt) }}
          </h3>
        </el-card>
        <!-- åº“存资产 -->
        <el-card class="bg3">
          <p>库存资产</p>
          <h3>
            Â¥{{ formatCurrency(assetInfo.inventoryValue) }}
          </h3>
        </el-card>
      </div>
@@ -62,7 +93,7 @@
                style="height: 260px; width: 35%;">
              <div class="chart-num">
                <span style="font-size: 22px;">设备类型</span>
                <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ assetInfo.totalEquipment }}</span>
                <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ deviceTypeTotalCount }}</span>
              </div>
            </Echarts>
            <Echarts
@@ -86,7 +117,6 @@
          style="width: 100%"
          :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
        >
          <el-table-column prop="id" label="资产编号" width="120" />
          <el-table-column prop="deviceName" label="设备名称" width="250" />
          <el-table-column prop="deviceModel" label="型号规格" min-width="150" />
          <el-table-column prop="supplierName" label="供应商" min-width="120" />
@@ -94,27 +124,17 @@
          <el-table-column prop="number" label="数量" width="120" />
          <el-table-column prop="originalValue" label="原值(元)" width="120">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.taxIncludingPriceTotal) }}
              {{ formatCurrency(row.taxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="depreciation" label="累计折旧(元)" width="140">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.taxIncludingPriceTotal-row.unTaxIncludingPriceTotal) }}
              {{ formatCurrency(row.deprAmount) }}
            </template>
          </el-table-column>
          <el-table-column prop="netValue" label="净值(元)" width="120">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.unTaxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="status" label="状态" width="100">
            <template #default="{ row }">
              <el-tag
                :type="getStatusTagType(row.status)"
                size="small"
              >
                {{ row.status }}
              </el-tag>
              {{ formatCurrency(row.netValue) }}
            </template>
          </el-table-column>
        </el-table>
@@ -141,21 +161,28 @@
import { ref, computed, onMounted, reactive } from 'vue';
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { getLedgerPage, getAssetInfo } from "@/api/equipmentManagement/ledger";
import { getLedgerPage } from "@/api/equipmentManagement/ledger";
import { getAccountingTotal, getDeviceTypeDistribution, getCalculateDepreciation } from "@/api/financialManagement/accounting";
import dayjs from "dayjs";
// ç­›é€‰æ¡ä»¶
const dateRange = ref(null);
const equipmentType = ref('');
const selectedYear = ref(dayjs().format('YYYY')); // é»˜è®¤å½“前年份
// å›ºå®šèµ„产信息
const assetInfo = ref({
  totalEquipment: 0,
  totalOriginalValue: 0,
  totalDepreciation: 0,
  totalNetValue: 0
  totalEquipment: 0, // deviceTotal
  totalOriginalValue: 0, // deviceAmount
  totalDepreciation: 0, // deprAmount
  totalNetValue: 0, // netValue
  debt: 0, // è´Ÿå€º
  inventoryValue: 0 // åº“存资产
});
// è®¾å¤‡ç±»åž‹æ€»æ•°ï¼ˆç”¨äºŽå›¾è¡¨æ˜¾ç¤ºï¼‰
const deviceTypeTotalCount = ref(0);
// è®¾å¤‡åˆ—表
const equipmentList = ref([]);
@@ -306,63 +333,96 @@
const fetchData = async () => {
  try {
    // èŽ·å–å›ºå®šèµ„äº§æ±‡æ€»ä¿¡æ¯
    const assetInfoRes = await getAssetInfo({
    const assetInfoRes = await getAccountingTotal({
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
      equipmentType: equipmentType.value,
      year: selectedYear.value
    });
    if (assetInfoRes.code === 200) {
      assetInfo.value = assetInfoRes.data;
      // æ˜ å°„后端字段到前端字段
      const data = assetInfoRes.data;
      assetInfo.value = {
        totalEquipment: data.deviceTotal || 0, // è®¾å¤‡æ€»æ•°
        totalOriginalValue: data.deviceAmount || 0, // èµ„产原值
        totalDepreciation: data.deprAmount || 0, // ç´¯è®¡æŠ˜æ—§
        totalNetValue: data.netValue || 0, // å‡€å€¼
        debt: data.debt || 0, // è´Ÿå€º
        inventoryValue: data.inventoryValue || 0 // åº“存资产
      };
    }
    // èŽ·å–è®¾å¤‡åˆ—è¡¨
    const equipmentListRes = await getLedgerPage({
      current: pagination.value.currentPage,
      size: pagination.value.pageSize,
    // èŽ·å–è®¾å¤‡ç±»åž‹åˆ†å¸ƒæ•°æ®ï¼ˆé¥¼å›¾å’ŒæŠ˜çº¿å›¾ï¼‰
    const distributionRes = await getDeviceTypeDistribution({
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
      equipmentType: equipmentType.value,
      year: selectedYear.value
    });
    if (equipmentListRes.code === 200) {
      equipmentList.value = equipmentListRes.data.records;
      pagination.value.total = equipmentListRes.data.total;
      // æ ¹æ® equipmentList æŒ‰ deviceName è¿›è¡Œåˆ†ç±»ç»Ÿè®¡
      const deviceNameMap = {};
      equipmentList.value.forEach(item => {
        const deviceName = item.deviceName;
        if (!deviceNameMap[deviceName]) {
          deviceNameMap[deviceName] = {
            name: deviceName,
            count: 0,
            totalValue: 0
          };
        }
        deviceNameMap[deviceName].count += item.number || 1; // å‡è®¾ number ä¸ºè®¾å¤‡æ•°é‡
        deviceNameMap[deviceName].totalValue += item.taxIncludingPriceTotal || 0; // ç´¯åŠ å«ç¨Žæ€»ä»·
      });
      // è½¬æ¢ä¸º typeDistributionData æ ¼å¼
      typeDistributionData.value = Object.values(deviceNameMap).map(item => ({
        name: item.name,
        value: item.count,
        count: item.count,
        amount: `Â¥${formatCurrency(item.totalValue)}`
      }));
    if (distributionRes.code === 200) {
      const data = distributionRes.data;
      // æ›´æ–°è®¾å¤‡ç±»åž‹æ€»æ•°
      deviceTypeTotalCount.value = data.totalCount || 0;
      // è½¬æ¢é¥¼å›¾æ•°æ®æ ¼å¼
      if (data.details && data.details.length > 0) {
        typeDistributionData.value = data.details.map(item => ({
          name: item.type || '',
          value: Number(item.count || 0),
          count: Number(item.count || 0),
          amount: `Â¥${formatCurrency(item.amount || 0)}`
        }));
      } else if (data.categories && data.categories.length > 0) {
        // å¦‚果没有 details,使用 categories、countData å’Œ amountData æž„建
        typeDistributionData.value = data.categories.map((category, index) => ({
          name: category,
          value: Number(data.countData[index] || 0),
          count: Number(data.countData[index] || 0),
          amount: `Â¥${formatCurrency(data.amountData[index] || 0)}`
        }));
      } else {
        typeDistributionData.value = [];
      }
      // æ›´æ–°x轴数据
      xAxis.value[0].data = typeDistributionData.value.map(item => item.name);
      xAxis.value[0].data = data.categories || typeDistributionData.value.map(item => item.name);
      // æž„建折线图数据
      typeDistributionLineSeries.value = [
        {
          name: '设备数量',
          type: 'line',
          data: typeDistributionData.value.map(item => item.count)
          data: data.countData || typeDistributionData.value.map(item => item.count)
        }
      ];
    }
    // èŽ·å–è®¾å¤‡åˆ—è¡¨ï¼ˆæŠ˜æ—§è®¡ç®—æ•°æ®ï¼‰
    const equipmentListRes = await getCalculateDepreciation({
      current: pagination.value.currentPage,
      size: pagination.value.pageSize,
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value,
      year: selectedYear.value
    });
    if (equipmentListRes.code === 200) {
      // å¦‚果返回的是分页数据
      if (equipmentListRes.data.records) {
        equipmentList.value = equipmentListRes.data.records;
        pagination.value.total = equipmentListRes.data.total;
      } else if (Array.isArray(equipmentListRes.data)) {
        // å¦‚果返回的是数组
        equipmentList.value = equipmentListRes.data;
        pagination.value.total = equipmentListRes.data.length;
      } else {
        equipmentList.value = [];
        pagination.value.total = 0;
      }
    }
  } catch (error) {
    console.error('获取固定资产数据失败:', error);
@@ -401,6 +461,7 @@
const resetFilters = () => {
  dateRange.value = null;
  equipmentType.value = '';
  selectedYear.value = dayjs().format('YYYY'); // é‡ç½®ä¸ºå½“前年份
  fetchData();
};
@@ -486,10 +547,10 @@
  }
}
/* å¤§å±å¹•及以上 (lg:grid-cols-5) */
/* å¤§å±å¹•及以上 (lg:grid-cols-6) */
@media (min-width: 1024px) {
  .grid-container {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}
src/views/financialManagement/expenseManagement/Form.vue
ÎļþÒÑɾ³ý
src/views/financialManagement/expenseManagement/Modal.vue
@@ -1,20 +1,75 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close" width="30%">
    <Form ref="formRef"></Form>
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="dialogVisible"
    :title="dialogTitle"
    :operationType="operationType"
    width="50%"
    @confirm="sendForm"
    @close="close"
    @cancel="close"
  >
    <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
      <el-form-item label="支出日期" prop="expenseDate">
        <el-date-picker
          style="width: 100%"
          v-model="form.expenseDate"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          type="date"
          placeholder="请选择日期"
          clearable
        />
      </el-form-item>
      <el-form-item label="支出类型" prop="expenseType">
        <el-select
          v-model="form.expenseType"
          placeholder="请选择"
          clearable
        >
          <el-option :label="item.label" :value="item.value" v-for="(item,index) in expense_types" :key="index" />
        </el-select>
      </el-form-item>
      <el-form-item label="供应商名称" prop="supplierName">
        <el-input v-model="form.supplierName" placeholder="请输入" />
      </el-form-item>
      <el-form-item label="支出金额" prop="expenseMoney">
        <el-input-number :step="0.01" :min="0" style="width: 100%"
          v-model="form.expenseMoney"
          placeholder="请输入"
        />
      </el-form-item>
      <el-form-item label="支出描述" prop="expenseDescribed">
        <el-input v-model="form.expenseDescribed" placeholder="请输入" />
      </el-form-item>
      <el-form-item label="付款方式" prop="expenseMethod">
        <el-select
          v-model="form.expenseMethod"
          placeholder="请选择"
          clearable
        >
          <el-option :label="item.label" :value="item.value" v-for="(item,index) in checkout_payment" :key="index" />
        </el-select>
      </el-form-item>
      <el-form-item label="发票号码" prop="invoiceNumber">
        <el-input v-model="form.invoiceNumber" placeholder="请输入" />
      </el-form-item>
      <el-form-item label="备注" prop="note">
        <el-input
          v-model="form.note"
          placeholder="备注"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import { add, update } from "@/api/financialManagement/expenseManagement";
import Form from "./Form.vue";
import { add, update, getAccountExpense } from "@/api/financialManagement/expenseManagement";
import { ElMessage } from "element-plus";
import useFormData from "@/hooks/useFormData";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { ref } from "vue";
const { proxy } = getCurrentInstance()
defineOptions({
@@ -23,43 +78,96 @@
const emits = defineEmits(["success"]);
const formRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "支出" });
const formRef = ref(null);
const dialogVisible = ref(false);
const operationType = ref("add"); // add | edit
const id = ref(undefined);
const submitting = ref(false);
const dialogTitle = (type) => {
  if (type === "edit") return "编辑支出";
  return "新增支出";
};
const { expense_types } = proxy.useDict("expense_types");
const { checkout_payment } = proxy.useDict("checkout_payment");
const formRules = {
  supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
  expenseMoney: [{ required: true, trigger: "blur", message: "请输入" }],
  expenseDescribed: [{ required: true, trigger: "blur", message: "请输入" }],
  expenseDate: [{ required: true, trigger: "change", message: "请选择" }],
  expenseType: [{ required: true, trigger: "change", message: "请选择" }],
  expenseMethod: [{ required: true, trigger: "change", message: "请选择" }],
}
const { form, resetForm } = useFormData({
  expenseDate: undefined, // æ”¯å‡ºæ—¥æœŸ
  expenseType: undefined, // æ”¯å‡ºç±»åž‹
  supplierName: undefined, // ä¾›åº”商名称
  expenseMoney: undefined, // æ”¯å‡ºé‡‘额
  expenseDescribed: undefined, // æ”¯å‡ºæè¿°
  expenseMethod: undefined, // ä»˜æ¬¾æ–¹å¼
  invoiceNumber: undefined, // å‘票号码
  note: undefined, // å¤‡æ³¨
});
const sendForm = () => {
    proxy.$refs.formRef.$refs.formRef.validate(async valid => {
        if (valid) {
            const {code} = id.value
                ? await update({id: id.value, ...formRef.value.form})
                : await add(formRef.value.form);
            if (code == 200) {
                emits("success");
                ElMessage({message: "操作成功", type: "success"});
                close();
            } else {
                loading.value = false;
            }
        }
    })
  if (submitting.value) return;
  formRef.value?.validate(async (valid) => {
    if (valid) {
      submitting.value = true;
      try {
        const { code } = id.value
          ? await update({ id: id.value, ...form })
          : await add(form);
        if (code == 200) {
          emits("success");
          ElMessage({ message: "操作成功", type: "success" });
          close();
        }
      } finally {
        submitting.value = false;
      }
    }
  })
};
const close = () => {
    formRef.value.resetFormAndValidate();
  closeModal();
  resetForm();
  formRef.value?.clearValidate();
  id.value = undefined;
  dialogVisible.value = false;
};
const loadForm = async (id) => {
  openModal(id);
  await nextTick();
  formRef.value.loadForm(id);
const loadForm = async (rowId) => {
  operationType.value = "edit";
  id.value = rowId;
  dialogVisible.value = true;
  if (rowId) {
    const { code, data } = await getAccountExpense(rowId);
    if (code == 200) {
      form.expenseDate = data.expenseDate;
      form.expenseType = data.expenseType;
      form.supplierName = data.supplierName;
      form.expenseMoney = data.expenseMoney;
      form.expenseDescribed = data.expenseDescribed;
      form.expenseMethod = data.expenseMethod;
      form.invoiceNumber = data.invoiceNumber;
      form.note = data.note;
    }
  } else {
    resetForm();
    formRef.value?.clearValidate();
  }
};
const openModal = () => {
  operationType.value = "add";
  id.value = undefined;
  resetForm();
  formRef.value?.clearValidate();
  dialogVisible.value = true;
};
defineExpose({
src/views/financialManagement/expenseManagement/index.vue
@@ -34,8 +34,8 @@
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="deleteRow(multipleList.map((item) => item.id))"
            :disabled="multipleList.length <= 0 || hasBusinessIdInSelection"
            @click="handleBatchDelete"
          >
            æ‰¹é‡åˆ é™¤
          </el-button>
@@ -55,12 +55,17 @@
        @pagination="changePage"
      >
        <template #operation="{ row }">
          <el-button type="primary" text @click="edit(row.id)" icon="editPen">
          <el-button
            type="primary"
            link
            :disabled="!!row.businessId"
            @click="edit(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="primary"
            text
            link
            @click="openFilesFormDia(row)"
          >
            é™„ä»¶
@@ -69,18 +74,27 @@
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <files-dia ref="filesDia"></files-dia>
    <FileListDialog
      ref="fileListRef"
      v-model="fileListDialogVisible"
      :show-upload-button="true"
      :show-delete-button="true"
      :upload-method="handleUpload"
      :delete-method="handleFileDelete"
    />
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { listPage, delAccountExpense } from "@/api/financialManagement/expenseManagement";
import { onMounted, getCurrentInstance } from "vue";
import { listPage, delAccountExpense, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/expenseManagement";
import { onMounted, getCurrentInstance, ref, computed } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import FilesDia from "../revenueManagement/filesDia.vue";
import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import request from "@/utils/request";
import { getToken } from "@/utils/auth";
defineOptions({
  name: "支出管理",
@@ -92,7 +106,10 @@
const modalRef = ref();
const { checkout_payment } = proxy.useDict("checkout_payment");
const { expense_types } = proxy.useDict("expense_types");
const filesDia = ref()
const fileListRef = ref(null);
const fileListDialogVisible = ref(false);
const currentFileRow = ref(null);
const accountType = ref('支出');
const {
  filters,
@@ -111,7 +128,6 @@
  [
    {
      label: "支出日期",
      align: "center",
      prop: "expenseDate",
    },
    {
@@ -129,19 +145,16 @@
    },
    {
      label: "供应商名称",
      align: "center",
      prop: "supplierName",
    },
    {
      label: "支出金额",
      align: "center",
      prop: "expenseMoney",
    },
    {
      label: "支出描述",
      align: "center",
      prop: "expenseDescribed",
    },
@@ -149,6 +162,7 @@
      label: "付款方式",
      align: "center",
      prop: "expenseMethod",
            width: '120',
      dataType: "tag",
      formatData: (params) => {
        if (checkout_payment.value.find((m) => m.value == params)) {
@@ -160,24 +174,20 @@
    },
    {
      label: "发票号码",
      align: "center",
      prop: "invoiceNumber",
    },
    {
      label: "备注",
      align: "center",
      prop: "note",
    },
    {
      label: "录入人",
      align: "center",
      prop: "inputUser",
    },
    {
      label: "录入日期",
      align: "center",
      prop: "inputTime",
    },
@@ -187,7 +197,7 @@
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
      width: "160px",
    },
  ]
);
@@ -197,10 +207,21 @@
  multipleList.value = selectionList;
};
// åˆ¤æ–­é€‰ä¸­çš„项中是否有 businessId
const hasBusinessIdInSelection = computed(() => {
  return multipleList.value.some(item => item.businessId);
});
const add = () => {
  modalRef.value.openModal();
};
const edit = (id) => {
  // æ£€æŸ¥å½“前行是否有 businessId
  const row = dataList.value.find(item => item.id === id);
  if (row && row.businessId) {
    proxy.$modal.msgWarning("该记录已关联业务,不能编辑");
    return;
  }
  modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
@@ -209,6 +230,25 @@
  onCurrentChange(page);
};
const deleteRow = (id) => {
  // å¦‚果是数组,检查是否有 businessId
  if (Array.isArray(id)) {
    const hasBusinessId = id.some(itemId => {
      const row = dataList.value.find(item => item.id === itemId);
      return row && row.businessId;
    });
    if (hasBusinessId) {
      proxy.$modal.msgWarning("选中的记录中包含已关联业务的记录,不能删除");
      return;
    }
  } else {
    // å•个删除,检查是否有 businessId
    const row = dataList.value.find(item => item.id === id);
    if (row && row.businessId) {
      proxy.$modal.msgWarning("该记录已关联业务,不能删除");
      return;
    }
  }
  ElMessageBox.confirm("此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
@@ -223,6 +263,23 @@
      getTableData();
    }
  });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (multipleList.value.length === 0) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  // æ£€æŸ¥æ˜¯å¦æœ‰ businessId
  if (hasBusinessIdInSelection.value) {
    proxy.$modal.msgWarning("选中的记录中包含已关联业务的记录,不能删除");
    return;
  }
  const ids = multipleList.value.map((item) => item.id);
  deleteRow(ids);
};
const changeDaterange = (value) => {
@@ -252,10 +309,154 @@
    });
};
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  nextTick(() => {
    filesDia.value?.openDialog( row,'支出')
  })
const openFilesFormDia = async (row) => {
  currentFileRow.value = row;
  accountType.value = '支出';
  try {
    const res = await fileListPage({
      accountId: row.id,
      accountType: accountType.value,
      current: 1,
      size: 100
    });
    if (res.code === 200 && fileListRef.value) {
      // å°†æ•°æ®è½¬æ¢ä¸º FileListDialog éœ€è¦çš„æ ¼å¼
      const fileList = (res.data?.records || []).map(item => ({
        name: item.name,
        url: item.url,
        id: item.id,
        ...item
      }));
      fileListRef.value.open(fileList);
      fileListDialogVisible.value = true;
    }
  } catch (error) {
    proxy.$modal.msgError("获取附件列表失败");
  }
};
// ä¸Šä¼ é™„ä»¶
const handleUpload = async () => {
  if (!currentFileRow.value) {
    proxy.$modal.msgWarning("请先选择数据");
    return null;
  }
  return new Promise((resolve) => {
    // åˆ›å»ºä¸€ä¸ªéšè—çš„æ–‡ä»¶è¾“入元素
    const input = document.createElement('input');
    input.type = 'file';
    input.style.display = 'none';
    input.onchange = async (e) => {
      const file = e.target.files[0];
      if (!file) {
        resolve(null);
        return;
      }
      try {
        // ä½¿ç”¨ FormData ä¸Šä¼ æ–‡ä»¶
        const formData = new FormData();
        formData.append('file', file);
        const uploadRes = await request({
          url: '/file/upload',
          method: 'post',
          data: formData,
          headers: {
            'Content-Type': 'multipart/form-data',
            Authorization: `Bearer ${getToken()}`
          }
        });
        if (uploadRes.code === 200) {
          // ä¿å­˜é™„件信息
          const fileData = {
            accountId: currentFileRow.value.id,
            accountType: accountType.value,
            name: uploadRes.data.originalName || file.name,
            url: uploadRes.data.tempPath || uploadRes.data.url
          };
          const saveRes = await fileAdd(fileData);
          if (saveRes.code === 200) {
            proxy.$modal.msgSuccess("文件上传成功");
            // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
            const listRes = await fileListPage({
              accountId: currentFileRow.value.id,
              accountType: accountType.value,
              current: 1,
              size: 100
            });
            if (listRes.code === 200 && fileListRef.value) {
              const fileList = (listRes.data?.records || []).map(item => ({
                name: item.name,
                url: item.url,
                id: item.id,
                ...item
              }));
              fileListRef.value.setList(fileList);
            }
            // è¿”回新文件信息
            resolve({
              name: fileData.name,
              url: fileData.url,
              id: saveRes.data?.id
            });
          } else {
            proxy.$modal.msgError(saveRes.msg || "文件保存失败");
            resolve(null);
          }
        } else {
          proxy.$modal.msgError(uploadRes.msg || "文件上传失败");
          resolve(null);
        }
      } catch (error) {
        proxy.$modal.msgError("文件上传失败");
        resolve(null);
      } finally {
        document.body.removeChild(input);
      }
    };
    document.body.appendChild(input);
    input.click();
  });
};
// åˆ é™¤é™„ä»¶
const handleFileDelete = async (row) => {
  try {
    const res = await fileDel([row.id]);
    if (res.code === 200) {
      proxy.$modal.msgSuccess("删除成功");
      // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
      if (currentFileRow.value && fileListRef.value) {
        const listRes = await fileListPage({
          accountId: currentFileRow.value.id,
          accountType: accountType.value,
          current: 1,
          size: 100
        });
        if (listRes.code === 200) {
          const fileList = (listRes.data?.records || []).map(item => ({
            name: item.name,
            url: item.url,
            id: item.id,
            ...item
          }));
          fileListRef.value.setList(fileList);
        }
      }
      return true; // è¿”回 true è¡¨ç¤ºåˆ é™¤æˆåŠŸï¼Œç»„ä»¶ä¼šæ›´æ–°åˆ—è¡¨
    } else {
      proxy.$modal.msgError(res.msg || "删除失败");
      return false;
    }
  } catch (error) {
    proxy.$modal.msgError("删除失败");
    return false;
  }
};
onMounted(() => {
src/views/financialManagement/financialStatements/index.vue
@@ -1,16 +1,16 @@
 <template>
  <div style="padding: 20px;">
    <!-- é¡µé¢æ ‡é¢˜å’Œæ—¥æœŸç­›é€‰ -->
    <!-- é¡µé¢æ ‡é¢˜å’Œæœˆä»½ç­›é€‰ -->
    <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
      <el-date-picker
        v-model="dateRange"
        type="daterange"
        format="YYYY-MM-DD"
        value-format="YYYY-MM-DD"
        type="monthrange"
        format="YYYY-MM"
        value-format="YYYY-MM"
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        clearable
        start-placeholder="开始月份"
        end-placeholder="结束月份"
        :disabled-date="disabledDate"
        @change="handleDateChange"
        class="w-full md:w-auto"
        style="margin-right: 30px;"
@@ -130,7 +130,7 @@
</template>
<script setup>
import { ref, computed, onMounted, reactive } from 'vue';
import { ref, computed, onMounted, reactive, nextTick, getCurrentInstance } from 'vue';
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements";
@@ -138,6 +138,7 @@
// æ—¥æœŸèŒƒå›´
const dateRange = ref(null);
const { proxy } = getCurrentInstance();
const chartStyle = {
    width: '100%',
    height: '100%', // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
@@ -172,22 +173,35 @@
    return `<div>${axisLabel}</div><div>${rows}</div>`
  }
})
const months = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
const lineSeries0 = ref([])
const lineSeries1 = ref([])
// æ ¹æ®æœˆä»½èŒƒå›´ç”Ÿæˆ x è½´æ•°æ®
const generateMonthLabels = (startMonth, endMonth) => {
  const labels = [];
  let current = dayjs(startMonth);
  const end = dayjs(endMonth);
  while (current.isBefore(end) || current.isSame(end, 'month')) {
    labels.push(`${current.month() + 1}月`);
    current = current.add(1, 'month');
  }
  return labels;
};
const xAxis0 = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: months,
    data: [],
  },
]);
const xAxis1 = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: months,
    data: [],
  },
]);
const yAxis0 = [
@@ -232,9 +246,10 @@
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: pieData0.value.map(item => item.name),
  data: (pieData0.value || []).filter(item => item && item.name).map(item => item.name),
  formatter: function(name) {
    const item = pieData0.value.find(i => i.name === name);
    if (!name) return '';
    const item = pieData0.value.find(i => i && i.name === name);
    if (!item) return name;
    return `${name} | ${item.percent} ${item.amount}`;
  },
@@ -250,9 +265,10 @@
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: pieData1.value.map(item => item.name),
  data: (pieData1.value || []).filter(item => item && item.name).map(item => item.name),
  formatter: function(name) {
    const item = pieData1.value.find(i => i.name === name);
    if (!name) return '';
    const item = pieData1.value.find(i => i && i.name === name);
    if (!item) return name;
    return `${name} | ${item.percent} ${item.amount}`;
  },
@@ -276,7 +292,7 @@
    label: {
      show: false
    },
    data: pieData0.value,
    data: (pieData0.value || []).filter(item => item && item.name),
    color: pieColors
  }
]);
@@ -293,7 +309,7 @@
    label: {
      show: false
    },
    data: pieData1.value,
    data: (pieData1.value || []).filter(item => item && item.name),
    color: pieColors
  }
]);
@@ -318,53 +334,81 @@
const pageInfo = ref({
})
// èŽ·å–æœ€è¿‘å…­ä¸ªæœˆçš„èŒƒå›´
const getLastSixMonths = () => {
  const endMonth = dayjs().format('YYYY-MM');
  const startMonth = dayjs().subtract(5, 'month').format('YYYY-MM');
  return [startMonth, endMonth];
};
const getData = async () => {
  if (!dateRange.value || !dateRange.value.length) {
  if (!dateRange.value || !Array.isArray(dateRange.value) || dateRange.value.length !== 2) {
    return;
  }
  const startDateStr = dateRange.value[0];
  const endDateStr = dateRange.value[1];
  if (!startDateStr || !endDateStr) {
    return;
  }
  // éªŒè¯æ—¥æœŸæ ¼å¼å¹¶è½¬æ¢ä¸ºå®Œæ•´æ—¥æœŸ
  const startDate = dayjs(startDateStr);
  const endDate = dayjs(endDateStr);
  if (!startDate.isValid() || !endDate.isValid()) {
    console.error('无效的日期格式');
    return;
  }
  // æ›´æ–° x è½´æ•°æ®
  const monthLabels = generateMonthLabels(startDateStr, endDateStr);
  xAxis0.value[0].data = monthLabels;
  xAxis1.value[0].data = monthLabels;
  // å¼€å§‹æœˆä»½æ‹¼æŽ¥ç¬¬ä¸€å¤©ï¼Œç»“束月份拼接最后一天
  const entryDateStart = startDate.startOf('month').format('YYYY-MM-DD');
  const entryDateEnd = endDate.endOf('month').format('YYYY-MM-DD');
  try {
    const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]});
    if(code === 200) {
      pageInfo.value = data
      pieData0.value = data.incomeType.map(item=>({
        name:item.typeName,
        value:item.account,
        percent:`${item.proportion*100}%`,
        amount:`Â¥${item.account}`
    const {code,data} = await reportForms({entryDateStart, entryDateEnd});
    if(code === 200 && data) {
      pageInfo.value = data || {};
      // å®‰å…¨å¤„理数据,过滤掉 null æˆ– undefined
      pieData0.value = (data.incomeType || []).filter(item => item && item.typeName).map(item=>({
        name:item.typeName || '',
        value:item.account || 0,
        percent:`${((item.proportion || 0) * 100).toFixed(2)}%`,
        amount:`Â¥${(item.account || 0).toFixed(2)}`
      }))
      pieData1.value = data.expenseType.map(item=>({
        name:item.typeName,
        value:item.account,
        percent:`${item.proportion*100}%`,
        amount:`Â¥${item.account}`
      pieData1.value = (data.expenseType || []).filter(item => item && item.typeName).map(item=>({
        name:item.typeName || '',
        value:item.account || 0,
        percent:`${((item.proportion || 0) * 100).toFixed(2)}%`,
        amount:`Â¥${(item.account || 0).toFixed(2)}`
      }))
    }
  } catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
  try{
    const {code,data} = await reportIncome();
    if(code==200){
      lineSeries0.value = data.map(item=>({
        name:item.typeName,
    const {code,data} = await reportIncome({entryDateStart, entryDateEnd});
    if(code==200 && data && Array.isArray(data)){
      lineSeries0.value = data.filter(item => item && item.typeName).map(item=>({
        name:item.typeName || '',
        type: 'line',
        data:item.account.map(item=>Number(item))
        data:(item.account || []).map(val => Number(val) || 0)
      }))
    }
  }catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
  try{
    const {code,data} = await reportExpense();
    if(code==200){
      lineSeries1.value = data.map(item=>({
        name:item.typeName,
    const {code,data} = await reportExpense({entryDateStart, entryDateEnd});
    if(code==200 && data && Array.isArray(data)){
      lineSeries1.value = data.filter(item => item && item.typeName).map(item=>({
        name:item.typeName || '',
        type: 'line',
        data:item.account.map(item=>Number(item))
        data:(item.account || []).map(val => Number(val) || 0)
      }))
    }
  }catch (error) {
    console.error('获取财务指标数据失败:', error);
@@ -374,20 +418,66 @@
// åˆå§‹åŒ–
onMounted(() => {
  // ä¸è®¾ç½®é»˜è®¤æ—¥æœŸï¼Œç”±ç”¨æˆ·æ‰‹åŠ¨é€‰æ‹©
  // è®¾ç½®é»˜è®¤å€¼ä¸ºæœ€è¿‘六个月
  const defaultRange = getLastSixMonths();
  dateRange.value = defaultRange;
  // ä½¿ç”¨ nextTick ç¡®ä¿ç»„件完全渲染后再调用
  nextTick(() => {
    getData();
  });
});
// å¤„理日期范围变化
const handleDateChange = (newRange) => {
  dateRange.value = newRange;
  if (newRange && newRange.length === 2) {
    getData()
// é™åˆ¶æœˆä»½é€‰æ‹©èŒƒå›´ï¼ˆæœ€å¤š12个月)
const disabledDate = (time) => {
  // å¦‚果没有选择开始月份,不禁用任何日期
  if (!dateRange.value || !Array.isArray(dateRange.value) || !dateRange.value[0]) {
    return false;
  }
  const startMonth = dayjs(dateRange.value[0]);
  const currentMonth = dayjs(time);
  // å¦‚果当前月份在开始月份之前,禁用
  if (currentMonth.isBefore(startMonth, 'month')) {
    return true;
  }
  // è®¡ç®—最大允许的月份(开始月份 + 11个月 = 12个月)
  const maxMonth = startMonth.add(11, 'month');
  // ç¦ç”¨è¶…过12个月的月份
  return currentMonth.isAfter(maxMonth, 'month');
};
// é‡ç½®æ—¥æœŸèŒƒå›´
// å¤„理月份范围变化
const handleDateChange = (newRange) => {
  if (!newRange || !Array.isArray(newRange) || newRange.length !== 2) {
    return;
  }
  // éªŒè¯æœˆä»½èŒƒå›´ä¸è¶…过12个月
  const startDate = dayjs(newRange[0]);
  const endDate = dayjs(newRange[1]);
  const monthDiff = endDate.diff(startDate, 'month');
  if (monthDiff > 11) {
    proxy.$modal.msgWarning('最多只能选择12个月份');
    // è‡ªåŠ¨è°ƒæ•´ä¸º12个月
    const adjustedEnd = startDate.add(11, 'month').format('YYYY-MM');
    dateRange.value = [newRange[0], adjustedEnd];
    getData();
    return;
  }
  dateRange.value = newRange;
  getData();
};
// é‡ç½®æœˆä»½èŒƒå›´
const resetDateRange = () => {
  dateRange.value = null;
  // é‡ç½®ä¸ºæœ€è¿‘六个月
  dateRange.value = getLastSixMonths();
  getData();
};
</script>
src/views/financialManagement/loanManagement/Modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,222 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    :title="dialogTitle"
    :operationType="operationType"
    width="60%"
    @confirm="sendForm"
    @close="close"
    @cancel="close"
  >
    <el-form
      ref="formRef"
      :model="form"
      :rules="formRules"
      label-width="120px"
    >
      <el-form-item label="借款人姓名" prop="borrowerName">
        <el-input v-model="form.borrowerName" placeholder="请输入借款人姓名" />
      </el-form-item>
      <el-form-item label="借款金额(元)" prop="borrowAmount">
        <el-input-number
          :step="0.01"
          :min="0"
          :precision="2"
          style="width: 100%"
          v-model="form.borrowAmount"
          placeholder="请输入借款金额"
        />
      </el-form-item>
      <el-form-item label="借款利率(%)" prop="interestRate">
        <el-input-number
          :step="0.01"
          :min="0"
          :precision="2"
          style="width: 100%"
          v-model="form.interestRate"
          placeholder="请输入借款利率,如:5.85"
        />
      </el-form-item>
      <el-form-item label="借款日期" prop="borrowDate">
        <el-date-picker
          style="width: 100%"
          v-model="form.borrowDate"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          type="date"
          placeholder="请选择借款日期"
          clearable
        />
      </el-form-item>
      <!-- å®žé™…还款日期:仅“还款”时可填 -->
      <el-form-item
        v-if="operationType === 'repay'"
        label="实际还款日期"
        prop="repayDate"
      >
        <el-date-picker
          style="width: 100%"
          v-model="form.repayDate"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          type="date"
          placeholder="请选择实际还款日期(还款后填写)"
          clearable
        />
      </el-form-item>
      <el-form-item label="备注" prop="remark">
        <el-input
          v-model="form.remark"
          type="textarea"
          :rows="3"
          placeholder="请输入备注(借款说明)"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { add, update } from "@/api/financialManagement/loanManagement";
import useFormData from "@/hooks/useFormData";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { ElMessage } from "element-plus";
import { ref } from "vue";
defineOptions({
  name: "借款新增编辑",
});
const emits = defineEmits(["success"]);
const formRef = ref(null);
const dialogVisible = ref(false);
const operationType = ref("add"); // add | edit
const id = ref(undefined);
const submitting = ref(false);
const dialogTitle = (type) => {
  if (type === "edit") return "编辑借款";
  if (type === "repay") return "还款";
  return "新增借款";
};
const formRules = {
  borrowerName: [{ required: true, trigger: "blur", message: "请输入借款人姓名" }],
  borrowAmount: [{ required: true, trigger: "blur", message: "请输入借款金额" }],
  interestRate: [{ required: true, trigger: "blur", message: "请输入借款利率" }],
  borrowDate: [{ required: true, trigger: "change", message: "请选择借款日期" }],
  repayDate: [
    {
      validator: (_rule, value, callback) => {
        if (operationType.value === "repay" && !value) {
          callback(new Error("请选择实际还款日期"));
          return;
        }
        callback();
      },
      trigger: "change",
    },
  ],
};
const { form, resetForm } = useFormData({
  borrowerName: undefined, // å€Ÿæ¬¾äººå§“名
  borrowAmount: undefined, // å€Ÿæ¬¾é‡‘额(元)
  interestRate: undefined, // å€Ÿæ¬¾åˆ©çŽ‡ï¼ˆå¦‚ï¼š5.85 ä»£è¡¨5.85%)
  borrowDate: undefined, // å€Ÿæ¬¾æ—¥æœŸ
  repayDate: undefined, // å®žé™…还款日期(还款后填充)
  remark: undefined, // å¤‡æ³¨ï¼ˆå€Ÿæ¬¾è¯´æ˜Žï¼‰
});
const sendForm = () => {
  if (submitting.value) return;
  formRef.value?.validate(async (valid) => {
    if (valid) {
      submitting.value = true;
      try {
        const isRepay = operationType.value === "repay";
        // è¿˜æ¬¾ï¼šä¸å±•示 status,但提交时强制传 status=2,走更新接口
        const payload = isRepay
          ? { id: id.value, ...form, status: 2 }
          : id.value
            ? { id: id.value, ...form }
            : form;
        const { code } = isRepay
          ? await update(payload)
          : id.value
            ? await update(payload)
            : await add(payload);
        if (code == 200) {
          emits("success");
          ElMessage({ message: "操作成功", type: "success" });
          close();
        }
      } finally {
        submitting.value = false;
      }
    }
  });
};
const close = () => {
  resetForm();
  formRef.value?.clearValidate();
  id.value = undefined;
  dialogVisible.value = false;
};
// ç¼–辑:直接用列表行数据回填(避免依赖详情接口)
const loadForm = async (row) => {
  const rowId = row?.id;
  operationType.value = "edit";
  id.value = rowId;
  dialogVisible.value = true;
  if (rowId) {
    form.borrowerName = row.borrowerName;
    form.borrowAmount = row.borrowAmount;
    form.interestRate = row.interestRate;
    form.borrowDate = row.borrowDate;
    form.repayDate = row.repayDate;
    form.remark = row.remark;
  } else {
    resetForm();
    formRef.value?.clearValidate();
  }
};
// è¿˜æ¬¾ï¼šæ‰“开弹窗,仅填写实际还款日期,提交时强制 status=2
const repay = async (row) => {
  const rowId = row?.id;
  operationType.value = "repay";
  id.value = rowId;
  dialogVisible.value = true;
  if (rowId) {
    // ä¸ºäº†èµ° update æŽ¥å£æ›´ç¨³å¦¥ï¼Œå¸¦ä¸ŠåŽŸæœ‰æ•°æ®ï¼›åªè®©ç”¨æˆ·é€‰ repayDate
    form.borrowerName = row.borrowerName;
    form.borrowAmount = row.borrowAmount;
    form.interestRate = row.interestRate;
    form.borrowDate = row.borrowDate;
    form.remark = row.remark;
    form.repayDate = undefined;
  } else {
    resetForm();
    formRef.value?.clearValidate();
  }
};
const openModal = () => {
  operationType.value = "add";
  id.value = undefined;
  resetForm();
  formRef.value?.clearValidate();
  dialogVisible.value = true;
};
defineExpose({
  openModal,
  loadForm,
  repay,
});
</script>
src/views/financialManagement/loanManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,271 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="借款人姓名:">
        <el-input
          v-model="filters.borrowerName"
          placeholder="请输入借款人姓名"
          clearable
          style="width: 200px;"
        />
      </el-form-item>
      <el-form-item label="借款日期:">
        <el-date-picker
          v-model="filters.borrowDate"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          clearable
          @change="changeDaterange"
        />
      </el-form-item>
      <el-form-item label="借款状态:">
        <el-select
          v-model="filters.status"
          placeholder="请选择"
          clearable
          style="width: 200px;"
        >
          <el-option label="待还款" :value="1" />
          <el-option label="已还款" :value="2" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
          <el-button @click="handleOut" icon="download">导出</el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="deleteRow(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
          </el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
        <template #operation="{ row }">
          <el-button type="primary" link @click="edit(row)">
            ç¼–辑
          </el-button>
          <el-button
            :disabled="row.status !== 1"
            type="primary"
            link
            @click="repay(row)"
          >
            è¿˜æ¬¾
          </el-button>
        </template>
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { listPage, delAccountLoan } from "@/api/financialManagement/loanManagement";
import { onMounted, getCurrentInstance, ref, nextTick } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
defineOptions({
  name: "借款管理",
});
// è¡¨æ ¼å¤šé€‰æ¡†é€‰ä¸­é¡¹
const multipleList = ref([]);
const { proxy } = getCurrentInstance();
const modalRef = ref();
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  listPage,
  {
    borrowerName: undefined,
    borrowDate: undefined,
    status: undefined,
  },
  [
    {
      label: "借款人姓名",
      prop: "borrowerName",
    },
    {
      label: "借款金额(元)",
      prop: "borrowAmount",
      formatData: (val) => {
        return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00';
      },
    },
    {
      label: "借款利率(%)",
      prop: "interestRate",
      formatData: (val) => {
        return val ? `${parseFloat(val).toFixed(2)}%` : '-';
      },
    },
    {
      label: "借款日期",
      prop: "borrowDate",
    },
    {
      label: "实际还款日期",
      prop: "repayDate",
    },
    {
      label: "借款状态",
      prop: "status",
      dataType: "tag",
            align: 'center',
      formatData: (params) => {
        if (params == 1) {
          return "待还款";
        } else if (params == 2) {
          return "已还款";
        }
        return null;
      },
      formatType: (params) => {
        if (params == 1) {
          return "error";
        } else if (params == 2) {
          return "success";
        }
        return null;
      },
    },
    {
      fixed: "right",
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "120px",
    },
  ],
  null,
  {
    // å°†å‰ç«¯å€Ÿæ¬¾æ—¥æœŸèŒƒå›´è½¬æ¢ä¸ºåŽç«¯éœ€è¦çš„ entryDateStart / entryDateEnd,并且不传 borrowDate
    borrowDate: (val) => {
      if (val && val.length === 2) {
        return {
          entryDateStart: dayjs(val[0]).format("YYYY-MM-DD"),
          entryDateEnd: dayjs(val[1]).format("YYYY-MM-DD"),
        };
      }
      return {};
    },
  }
);
// å¤šé€‰åŽåšä»€ä¹ˆ
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
const add = () => {
  modalRef.value.openModal();
};
const edit = (row) => {
  modalRef.value.loadForm(row);
};
const repay = (row) => {
  modalRef.value.repay(row);
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  onCurrentChange(page);
};
const deleteRow = (id) => {
  ElMessageBox.confirm("此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    const { code } = await delAccountLoan(id);
    if (code == 200) {
      ElMessage({
        type: "success",
        message: "删除成功",
      });
      getTableData();
    }
  });
};
const changeDaterange = (value) => {
  if (value) {
    filters.borrowDate = value;
  } else {
    filters.borrowDate = null;
  }
  getTableData();
};
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download(`/borrowInfo/export`, {}, "借款台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/financialManagement/revenueManagement/Form.vue
ÎļþÒÑɾ³ý
src/views/financialManagement/revenueManagement/Modal.vue
@@ -1,20 +1,75 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close" width="30%">
    <Form ref="formRef"></Form>
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="dialogVisible"
    :title="dialogTitle"
    :operationType="operationType"
    width="30%"
    @confirm="sendForm"
    @close="close"
    @cancel="close"
  >
    <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
      <el-form-item label="收入日期" prop="incomeDate">
        <el-date-picker
          style="width: 100%"
          v-model="form.incomeDate"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          type="date"
          placeholder="请选择日期"
          clearable
        />
      </el-form-item>
      <el-form-item label="收入类型" prop="incomeType">
        <el-select
          v-model="form.incomeType"
          placeholder="请选择"
          clearable
        >
          <el-option :label="item.label" :value="item.value" v-for="(item,index) in income_types" :key="index" />
        </el-select>
      </el-form-item>
      <el-form-item label="客户名称" prop="customerName">
        <el-input v-model="form.customerName" placeholder="请输入" />
      </el-form-item>
      <el-form-item label="收入金额" prop="incomeMoney">
        <el-input-number :step="0.01" :min="0" style="width: 100%"
          v-model="form.incomeMoney"
          placeholder="请输入"
        />
      </el-form-item>
      <el-form-item label="收入描述" prop="incomeDescribed">
        <el-input v-model="form.incomeDescribed" placeholder="请输入" />
      </el-form-item>
      <el-form-item label="收款方式" prop="incomeMethod">
        <el-select
          v-model="form.incomeMethod"
          placeholder="请选择"
          clearable
        >
          <el-option :label="item.label" :value="item.value" v-for="(item,index) in payment_methods" :key="index" />
        </el-select>
      </el-form-item>
      <el-form-item label="发票号码" prop="invoiceNumber">
        <el-input v-model="form.invoiceNumber" placeholder="请输入" />
      </el-form-item>
      <el-form-item label="备注" prop="note">
        <el-input
          v-model="form.note"
          placeholder="备注"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import { add, update } from "@/api/financialManagement/revenueManagement";
import Form from "./Form.vue";
import { add, update, getAccountIncome } from "@/api/financialManagement/revenueManagement";
import { ElMessage } from "element-plus";
import useFormData from "@/hooks/useFormData";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { ref } from "vue";
const { proxy } = getCurrentInstance()
defineOptions({
@@ -23,43 +78,96 @@
const emits = defineEmits(["success"]);
const formRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "收入" });
const formRef = ref(null);
const dialogVisible = ref(false);
const operationType = ref("add"); // add | edit
const id = ref(undefined);
const submitting = ref(false);
const dialogTitle = (type) => {
  if (type === "edit") return "编辑收入";
  return "新增收入";
};
const { income_types } = proxy.useDict("income_types");
const { payment_methods } = proxy.useDict("payment_methods");
const formRules = {
  customerName: [{ required: true, trigger: "blur", message: "请输入" }],
  incomeMoney: [{ required: true, trigger: "blur", message: "请输入" }],
  incomeDescribed: [{ required: true, trigger: "blur", message: "请输入" }],
  incomeDate: [{ required: true, trigger: "change", message: "请选择" }],
  incomeType: [{ required: true, trigger: "change", message: "请选择" }],
  incomeMethod: [{ required: true, trigger: "change", message: "请选择" }],
}
const { form, resetForm } = useFormData({
  incomeDate: undefined, // æ”¶å…¥æ—¥æœŸ
  incomeType: undefined, // æ”¶å…¥ç±»åž‹
  customerName: undefined, // å®¢æˆ·åç§°
  incomeMoney: undefined, // æ”¶å…¥é‡‘额
  incomeDescribed: undefined, // æ”¶å…¥æè¿°
  incomeMethod: undefined, // æ”¶æ¬¾æ–¹å¼
  invoiceNumber: undefined, // å‘票号码
  note: undefined, // å¤‡æ³¨
});
const sendForm = () => {
    proxy.$refs.formRef.$refs.formRef.validate(async valid => {
        if (valid) {
            const {code} = id.value
                ? await update({id: id.value, ...formRef.value.form})
                : await add(formRef.value.form);
            if (code == 200) {
                emits("success");
                ElMessage({message: "操作成功", type: "success"});
                close();
            } else {
                loading.value = false;
            }
        }
    })
  if (submitting.value) return;
  formRef.value?.validate(async (valid) => {
    if (valid) {
      submitting.value = true;
      try {
        const { code } = id.value
          ? await update({ id: id.value, ...form })
          : await add(form);
        if (code == 200) {
          emits("success");
          ElMessage({ message: "操作成功", type: "success" });
          close();
        }
      } finally {
        submitting.value = false;
      }
    }
  })
};
const close = () => {
    formRef.value.resetFormAndValidate();
  closeModal();
  resetForm();
  formRef.value?.clearValidate();
  id.value = undefined;
  dialogVisible.value = false;
};
const loadForm = async (id) => {
  openModal(id);
  await nextTick();
  formRef.value.loadForm(id);
const loadForm = async (rowId) => {
  operationType.value = "edit";
  id.value = rowId;
  dialogVisible.value = true;
  if (rowId) {
    const { code, data } = await getAccountIncome(rowId);
    if (code == 200) {
      form.incomeDate = data.incomeDate;
      form.incomeType = data.incomeType;
      form.customerName = data.customerName;
      form.incomeMoney = data.incomeMoney;
      form.incomeDescribed = data.incomeDescribed;
      form.incomeMethod = data.incomeMethod;
      form.invoiceNumber = data.invoiceNumber;
      form.note = data.note;
    }
  } else {
    resetForm();
    formRef.value?.clearValidate();
  }
};
const openModal = () => {
  operationType.value = "add";
  id.value = undefined;
  resetForm();
  formRef.value?.clearValidate();
  dialogVisible.value = true;
};
defineExpose({
src/views/financialManagement/revenueManagement/filesDia.vue
ÎļþÒÑɾ³ý
src/views/financialManagement/revenueManagement/index.vue
@@ -34,8 +34,8 @@
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="deleteRow(multipleList.map((item) => item.id))"
            :disabled="multipleList.length <= 0 || hasBusinessIdInSelection"
            @click="handleBatchDelete"
          >
            æ‰¹é‡åˆ é™¤
          </el-button>
@@ -55,12 +55,17 @@
        @pagination="changePage"
      >
        <template #operation="{ row }">
          <el-button type="primary" text @click="edit(row.id)" icon="editPen">
          <el-button
            type="primary"
            link
            :disabled="!!row.businessId"
            @click="edit(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="primary"
            text
                        link
            @click="openFilesFormDia(row)"
          >
            é™„ä»¶
@@ -69,18 +74,27 @@
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <files-dia ref="filesDia"></files-dia>
    <FileListDialog
      ref="fileListRef"
      v-model="fileListDialogVisible"
      :show-upload-button="true"
      :show-delete-button="true"
      :upload-method="handleUpload"
      :delete-method="handleFileDelete"
    />
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { listPage, delAccountIncome } from "@/api/financialManagement/revenueManagement";
import { onMounted, getCurrentInstance } from "vue";
import { listPage, delAccountIncome, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/revenueManagement";
import { onMounted, getCurrentInstance, ref, computed } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import FilesDia from "./filesDia.vue";
import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import request from "@/utils/request";
import { getToken } from "@/utils/auth";
defineOptions({
  name: "收入管理",
@@ -92,7 +106,10 @@
const modalRef = ref();
const { payment_methods } = proxy.useDict("payment_methods");
const { income_types } = proxy.useDict("income_types");
const filesDia = ref()
const fileListRef = ref(null);
const fileListDialogVisible = ref(false);
const currentFileRow = ref(null);
const accountType = ref('收入');
const {
  filters,
@@ -111,12 +128,10 @@
  [
    {
      label: "收入日期",
      align: "center",
      prop: "incomeDate",
    },
    {
      label: "收入类型",
      align: "center",
      prop: "incomeType",
      dataType: "tag",
      formatData: (params) => {
@@ -129,26 +144,25 @@
    },
    {
      label: "客户名称",
      align: "center",
      prop: "customerName",
            width: '200'
    },
    {
      label: "收入金额",
      align: "center",
      prop: "incomeMoney",
    },
    {
      label: "收入描述",
      align: "center",
      prop: "incomeDescribed",
    },
    {
      label: "收款方式",
      align: "center",
      prop: "incomeMethod",
            align: 'center',
            width: '100',
      dataType: "tag",
      formatData: (params) => {
        if (payment_methods.value.find((m) => m.value == params)) {
@@ -160,24 +174,20 @@
    },
    {
      label: "发票号码",
      align: "center",
      prop: "invoiceNumber",
    },
    {
      label: "备注",
      align: "center",
      prop: "note",
    },
    {
      label: "录入人",
      align: "center",
      prop: "inputUser",
    },
    {
      label: "录入日期",
      align: "center",
      prop: "inputTime",
    },
@@ -187,7 +197,7 @@
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
      width: "160px",
    },
  ]
);
@@ -197,10 +207,21 @@
  multipleList.value = selectionList;
};
// åˆ¤æ–­é€‰ä¸­çš„项中是否有 businessId
const hasBusinessIdInSelection = computed(() => {
  return multipleList.value.some(item => item.businessId);
});
const add = () => {
  modalRef.value.openModal();
};
const edit = (id) => {
  // æ£€æŸ¥å½“前行是否有 businessId
  const row = dataList.value.find(item => item.id === id);
  if (row && row.businessId) {
    proxy.$modal.msgWarning("该记录已关联业务,不能编辑");
    return;
  }
  modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
@@ -209,6 +230,25 @@
  onCurrentChange(page);
};
const deleteRow = (id) => {
  // å¦‚果是数组,检查是否有 businessId
  if (Array.isArray(id)) {
    const hasBusinessId = id.some(itemId => {
      const row = dataList.value.find(item => item.id === itemId);
      return row && row.businessId;
    });
    if (hasBusinessId) {
      proxy.$modal.msgWarning("选中的记录中包含已关联业务的记录,不能删除");
      return;
    }
  } else {
    // å•个删除,检查是否有 businessId
    const row = dataList.value.find(item => item.id === id);
    if (row && row.businessId) {
      proxy.$modal.msgWarning("该记录已关联业务,不能删除");
      return;
    }
  }
  ElMessageBox.confirm("此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
@@ -223,6 +263,23 @@
      getTableData();
    }
  });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (multipleList.value.length === 0) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  // æ£€æŸ¥æ˜¯å¦æœ‰ businessId
  if (hasBusinessIdInSelection.value) {
    proxy.$modal.msgWarning("选中的记录中包含已关联业务的记录,不能删除");
    return;
  }
  const ids = multipleList.value.map((item) => item.id);
  deleteRow(ids);
};
const changeDaterange = (value) => {
@@ -252,10 +309,154 @@
    });
};
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  nextTick(() => {
    filesDia.value?.openDialog( row,'收入')
  })
const openFilesFormDia = async (row) => {
  currentFileRow.value = row;
  accountType.value = '收入';
  try {
    const res = await fileListPage({
      accountId: row.id,
      accountType: accountType.value,
      current: 1,
      size: 100
    });
    if (res.code === 200 && fileListRef.value) {
      // å°†æ•°æ®è½¬æ¢ä¸º FileListDialog éœ€è¦çš„æ ¼å¼
      const fileList = (res.data?.records || []).map(item => ({
        name: item.name,
        url: item.url,
        id: item.id,
        ...item
      }));
      fileListRef.value.open(fileList);
      fileListDialogVisible.value = true;
    }
  } catch (error) {
    proxy.$modal.msgError("获取附件列表失败");
  }
};
// ä¸Šä¼ é™„ä»¶
const handleUpload = async () => {
  if (!currentFileRow.value) {
    proxy.$modal.msgWarning("请先选择数据");
    return null;
  }
  return new Promise((resolve) => {
    // åˆ›å»ºä¸€ä¸ªéšè—çš„æ–‡ä»¶è¾“入元素
    const input = document.createElement('input');
    input.type = 'file';
    input.style.display = 'none';
    input.onchange = async (e) => {
      const file = e.target.files[0];
      if (!file) {
        resolve(null);
        return;
      }
      try {
        // ä½¿ç”¨ FormData ä¸Šä¼ æ–‡ä»¶
        const formData = new FormData();
        formData.append('file', file);
        const uploadRes = await request({
          url: '/file/upload',
          method: 'post',
          data: formData,
          headers: {
            'Content-Type': 'multipart/form-data',
            Authorization: `Bearer ${getToken()}`
          }
        });
        if (uploadRes.code === 200) {
          // ä¿å­˜é™„件信息
          const fileData = {
            accountId: currentFileRow.value.id,
            accountType: accountType.value,
            name: uploadRes.data.originalName || file.name,
            url: uploadRes.data.tempPath || uploadRes.data.url
          };
          const saveRes = await fileAdd(fileData);
          if (saveRes.code === 200) {
            proxy.$modal.msgSuccess("文件上传成功");
            // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
            const listRes = await fileListPage({
              accountId: currentFileRow.value.id,
              accountType: accountType.value,
              current: 1,
              size: 100
            });
            if (listRes.code === 200 && fileListRef.value) {
              const fileList = (listRes.data?.records || []).map(item => ({
                name: item.name,
                url: item.url,
                id: item.id,
                ...item
              }));
              fileListRef.value.setList(fileList);
            }
            // è¿”回新文件信息
            resolve({
              name: fileData.name,
              url: fileData.url,
              id: saveRes.data?.id
            });
          } else {
            proxy.$modal.msgError(saveRes.msg || "文件保存失败");
            resolve(null);
          }
        } else {
          proxy.$modal.msgError(uploadRes.msg || "文件上传失败");
          resolve(null);
        }
      } catch (error) {
        proxy.$modal.msgError("文件上传失败");
        resolve(null);
      } finally {
        document.body.removeChild(input);
      }
    };
    document.body.appendChild(input);
    input.click();
  });
};
// åˆ é™¤é™„ä»¶
const handleFileDelete = async (row) => {
  try {
    const res = await fileDel([row.id]);
    if (res.code === 200) {
      proxy.$modal.msgSuccess("删除成功");
      // é‡æ–°åŠ è½½æ–‡ä»¶åˆ—è¡¨
      if (currentFileRow.value && fileListRef.value) {
        const listRes = await fileListPage({
          accountId: currentFileRow.value.id,
          accountType: accountType.value,
          current: 1,
          size: 100
        });
        if (listRes.code === 200) {
          const fileList = (listRes.data?.records || []).map(item => ({
            name: item.name,
            url: item.url,
            id: item.id,
            ...item
          }));
          fileListRef.value.setList(fileList);
        }
      }
      return true; // è¿”回 true è¡¨ç¤ºåˆ é™¤æˆåŠŸï¼Œç»„ä»¶ä¼šæ›´æ–°åˆ—è¡¨
    } else {
      proxy.$modal.msgError(res.msg || "删除失败");
      return false;
    }
  } catch (error) {
    proxy.$modal.msgError("删除失败");
    return false;
  }
};
onMounted(() => {
src/views/inventoryManagement/dispatchLog/index.vue
@@ -2,15 +2,6 @@
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">供应商名称:</span>
                <el-input
                    v-model="searchForm.supplierName"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search"
                />
                <span class="search_title ml10">出库日期:</span>
                <el-date-picker
                    v-model="searchForm.timeStr"
@@ -26,7 +17,6 @@
                >
            </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="primary" plain @click="handlePrint">打印</el-button>
@@ -40,95 +30,47 @@
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="(row) => row.id"
                show-summary
                style="width: 100%"
                :summary-method="summarizeMainTable"
                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="出库日期"
                    prop="createTime"
                    min-width="250"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="供应商名称"
                    prop="supplierName"
                    width="250"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="产品大类"
                    prop="productCategory"
                    width="100"
                    prop="productName"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="规格型号"
                    prop="specificationModel"
                    width="100"
                    prop="model"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="单位"
                    prop="unit"
                    width="80"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="出库数量"
                    prop="inboundNum"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="含税单价(元)"
                    prop="taxInclusiveUnitPrice"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="含税总价(元)"
                    prop="taxInclusiveTotalPrice"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="税率(%)"
                    prop="taxRate"
                    width="100"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="不含税总价(元)"
                    prop="taxExclusiveTotalPrice"
                    width="180"
                    prop="stockOutNum"
                    show-overflow-tooltip
                />
                <el-table-column
                    label="出库人"
                    prop="createBy"
                    width="80"
                    show-overflow-tooltip
                />
                <!-- <el-table-column
                    fixed="right"
                    label="操作"
                    min-width="60"
                    align="center"
                >
                    <template #default="scope">
                        <el-button
                            link
                            type="primary"
                            size="small"
                            @click="openForm('edit', scope.row)"
                            >编辑</el-button
                        >
                    </template>
                </el-table-column> -->
            </el-table>
            <pagination
                v-show="total > 0"
@@ -139,120 +81,6 @@
                @pagination="paginationChange"
            />
        </div>
        <!-- æ‰“印预览弹窗 -->
        <el-dialog
            v-model="printPreviewVisible"
            title="打印预览"
            width="90%"
            :close-on-click-modal="false"
            class="print-preview-dialog"
        >
            <div class="print-preview-container">
                <div class="print-preview-header">
                    <el-button type="primary" @click="executePrint">执行打印</el-button>
                    <el-button @click="printPreviewVisible = false">关闭预览</el-button>
                </div>
                <div class="print-preview-content">
                    <div v-if="printData.length === 0" style="text-align: center; padding: 50px; color: #999;">
                        æš‚无打印数据
                    </div>
                    <div v-else style="text-align: center; padding: 10px; color: #666; font-size: 14px; background: #e8f4fd; margin-bottom: 10px;">
                        å…± {{ printData.length }} æ¡æ•°æ®å¾…打印
                    </div>
                    <div v-for="(item, index) in printData" :key="index" class="print-page">
                        <div class="delivery-note">
                            <div class="header">
                                <div class="company-name">鼎诚瑞实业有限责任公司</div>
                                <div class="document-title">零售发货单</div>
                            </div>
                            <div class="info-section">
                                <div class="info-row">
                                    <div>
                                        <span class="label">发货日期:</span>
                                        <span class="value">{{ formatDate(item.createTime) }}</span>
                                    </div>
                                    <div>
                                        <span class="label">客户名称:</span>
                                        <span class="value">{{ item.supplierName || '张爱有' }}</span>
                                    </div>
                                </div>
                                <div class="info-row">
                                    <span class="label">单号:</span>
                                    <span class="value">{{ item.code }}</span>
                                </div>
                            </div>
                            <div class="table-section">
                                <table class="product-table">
                                    <thead>
                                    <tr>
                                        <th>产品名称</th>
                                        <th>规格型号</th>
                                        <th>单位</th>
                                        <th>单价</th>
                                        <th>零售数量</th>
                                        <th>零售金额</th>
                                    </tr>
                                    </thead>
                                    <tbody>
                                    <tr>
                                        <td>{{ item.productCategory || '砂灰砖' }}</td>
                                        <td>{{ item.specificationModel || '标准' }}</td>
                                        <td>{{ item.unit || '块' }}</td>
                                        <td>{{ item.taxInclusiveUnitPrice || '0' }}</td>
                                        <td>{{ item.inboundNum || '2000' }}</td>
                                        <td>{{ item.taxInclusiveTotalPrice || '0' }}</td>
                                    </tr>
                                    </tbody>
                                    <tfoot>
                                    <tr>
                                        <td class="label">合计</td>
                                        <td class="total-value"></td>
                                        <td class="total-value"></td>
                                        <td class="total-value"></td>
                                        <td class="total-value">{{ item.inboundNum || '2000' }}</td>
                                        <td class="total-value">{{ item.taxInclusiveTotalPrice || '0' }}</td>
                                    </tr>
                                    </tfoot>
                                </table>
                            </div>
                            <div class="footer-section">
                                <div class="footer-row">
                                    <div class="footer-item">
                                        <span class="label">收货电话:</span>
                                        <span class="value"></span>
                                    </div>
                                    <div class="footer-item">
                                        <span class="label">收货人:</span>
                                        <span class="value"></span>
                                    </div>
                                    <div class="footer-item address-item">
                                        <span class="label">收货地址:</span>
                                        <span class="value address-value"></span>
                                    </div>
                                </div>
                                <div class="footer-row">
                                    <div class="footer-item">
                                        <span class="label">操作员:</span>
                                        <span class="value">{{ userStore.nickName || '撕开前' }}</span>
                                    </div>
                                    <div class="footer-item">
                                        <span class="label">打印日期:</span>
                                        <span class="value">{{ formatDateTime(new Date()) }}</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </el-dialog>
    </div>
</template>
@@ -345,15 +173,6 @@
};
const expandedRowKeys = ref([]);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, [
        "contractAmount",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
    ]);
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("是否确认导出?", "导出", {
@@ -384,7 +203,7 @@
        type: "warning",
    })
        .then(() => {
            delStockOut({ids:ids}).then((res) => {
            delStockOut(ids).then((res) => {
                proxy.$modal.msgSuccess("删除成功");
                getList();
            });
@@ -590,8 +409,8 @@
              </thead>
              <tbody>
                <tr>
                  <td>${item.productCategory || '砂灰砖'}</td>
                  <td>${item.specificationModel || '标准'}</td>
                  <td>${item.productName || '砂灰砖'}</td>
                  <td>${item.model || '标准'}</td>
                  <td>${item.unit || '块'}</td>
                  <td>${item.taxInclusiveUnitPrice || '0'}</td>
                  <td>${item.inboundNum || '2000'}</td>
src/views/inventoryManagement/receiptManagement/index.vue
@@ -2,550 +2,219 @@
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">供应商名称:</span>
        <el-input v-model="searchForm.supplierName" style="width: 240px" placeholder="请输入" @change="handleQuery"
          clearable prefix-icon="Search" />
        <span class="search_title ml10">入库日期:</span>
                <el-date-picker
                    v-model="searchForm.timeStr"
                    type="date"
                    placeholder="请选择日期"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    clearable
                    @change="handleQuery"
                />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
        <el-date-picker v-model="searchForm.timeStr"
                        type="date"
                        placeholder="请选择日期"
                        value-format="YYYY-MM-DD"
                        format="YYYY-MM-DD"
                        clearable
                        @change="handleQuery"/>
        <span class="search_title ml10">产品大类:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 240px"
                  placeholder="请输入"
                  clearable/>
        <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">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%"
        :summary-method="summarizeMainTable" 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="createTime" width="100" show-overflow-tooltip />
        <el-table-column label="入库批次" prop="inboundBatches" width="160" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" width="240" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" width="100" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" width="200" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="70" show-overflow-tooltip />
        <el-table-column label="入库数量" prop="inboundNum" width="90" show-overflow-tooltip />
        <el-table-column label="含税单价" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
        <el-table-column label="含税总价" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="税率(%)" prop="taxRate" width="80" show-overflow-tooltip />
        <el-table-column label="不含税总价" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="入库人" prop="createBy" width="80" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">编辑</el-button>
          </template>
        </el-table-column>
      <el-table :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="row => row.id"
                show-summary
                style="width: 100%"
                :summary-method="summarizeMainTable"
                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="inboundBatches"
                         width="280"
                         show-overflow-tooltip/>
        <el-table-column label="入库时间"
                         prop="createTime"
                         show-overflow-tooltip/>
        <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="stockInNum"
                         show-overflow-tooltip/>
        <el-table-column label="入库人"
                         prop="createBy"
                         show-overflow-tooltip/>
      </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="pageProductChange"/>
    </div>
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增入库' : '编辑入库'" width="70%"
      @close="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-form-item label="采购订单号" prop="purchaseContractNumber">
              <el-select
                  v-model="form.purchaseContractNumber"
                  placeholder="请选择采购订单号"
                  clearable
                  filterable
                  remote
                  :remote-method="loadPurchaseOptions"
                  :loading="loadingPurchaseOptions"
                  @change="handlePurchaseChange"
                  :disabled="operationType === 'edit'"
                  style="width: 100%"
              >
                <el-option
                    v-for="item in purchaseOptions"
                    :key="item.purchaseContractNumber"
                    :label="formatPurchaseOption(item)"
                    :value="item.purchaseContractNumber"
                />
              </el-select>
            </el-form-item>
        <el-table
          :data="productList"
          border
          v-loading="loadingProducts"
          @selection-change="handleSelectionChange"
        >
          <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="supplierName" width="100" />
          <el-table-column label="采购数量" prop="quantity" width="100" />
          <el-table-column label="待入库数量" prop="quantity0" width="100" />
          <el-table-column label="本次入库数量" prop="quantityStock" width="150">
            <template #default="scope">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.quantityStock" />
            </template>
          </el-table-column>
          <el-table-column label="税率(%)" prop="taxRate" width="120" />
          <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>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import pagination from "@/components/PIMTable/Pagination.vue";
import {
  getStockInPage,
  updateStockIn,
  addSutockIn,
  delStockIn,
  selectProductRecordListByPuechaserId
} from "@/api/inventoryManagement/stockIn.js";
import { purchaseListPage } from "@/api/procurementManagement/procurementLedger.js";
import { getCurrentDate } from "@/utils/index.js";
  ref,
  reactive,
  toRefs,
  onMounted,
  getCurrentInstance,
  nextTick,
} from "vue";
import {ElMessageBox} from "element-plus";
import {
  getStockInRecordListPage,
  batchDeleteStockInRecords,
} from "@/api/inventoryManagement/stockInRecord.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const {proxy} = getCurrentInstance();
const tableData = ref([])
const selectedRows = ref([])
const userList = ref([])
const purchaseOptions = ref([])
const loadingPurchaseOptions = ref(false)
const loading = ref(false);
const tableLoading = ref(false)
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const activeTab = ref("production"); // å½“前激活的 tab
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
});
const total = ref(0);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const productSelectedRows = ref([]) // äº§å“è¡¨æ ¼é€‰ä¸­è¡Œ
const data = reactive({
  searchForm: {
    supplierName: '',
        timeStr: '',
    productName: "",
    timeStr: "",
  },
  form: {
    id: null,
    purchaseContractNumber: '', // é‡‡è´­è®¢å•号
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    inboundTime: '',        // å…¥åº“æ—¶é—´
    inboundBatch: '',       // å…¥åº“批次
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
    supplierId: [{ required: true, message: "请选择供应商", trigger: "change" }],
    inboundTime: [{ required: true, message: "请选择入库时间", trigger: "change" }],
    inboundBatch: [{ required: true, message: "请输入入库批次", trigger: "blur" }]
  }
})
const { searchForm, form, rules } = toRefs(data)
const formatPurchaseOption = (item = {}) => {
  const contract = item.purchaseContractNumber || '--';
  const supplier = item.supplierName ? ` Â· ${item.supplierName}` : '';
  return `${contract}${supplier}`;
};
const loadPurchaseOptions = async (keyword = '') => {
  try {
    loadingPurchaseOptions.value = true;
    const res = await purchaseListPage({
      current: -1,
      size: -1,
      purchaseContractNumber: keyword,
    });
    const records = res.data?.records || [];
    purchaseOptions.value = records;
    if (
      form.value.purchaseContractNumber &&
      !purchaseOptions.value.find(
        (item) => item.purchaseContractNumber === form.value.purchaseContractNumber
      )
    ) {
      purchaseOptions.value.push({
        purchaseContractNumber: form.value.purchaseContractNumber,
        supplierName: form.value.supplierName,
        supplierId: form.value.supplierId,
      });
    }
  } finally {
    loadingPurchaseOptions.value = false;
  }
};
const handlePurchaseChange = (value) => {
  form.value.purchaseContractNumber = value || '';
  const matched = purchaseOptions.value.find(
    (item) => item.purchaseContractNumber === value
  );
  if (matched) {
    form.value.supplierName = matched.supplierName || form.value.supplierName;
    form.value.supplierId = matched.supplierId || form.value.supplierId;
  }
  if (!value) {
    productList.value = [];
    return;
  }
  fetchProductsByContract();
};
const exceedsAddLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting)) {
    return false;
  }
  return stock > waiting;
};
const exceedsEditLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  const original = Number(product?.originalQuantityStock ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting) || !Number.isFinite(original)) {
    return false;
  }
  return stock > waiting + original;
};
const formattedNumber = (row, column, cellValue) => {
    return parseFloat(cellValue).toFixed(2);
};
});
const {searchForm} = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const paginationChange = (obj) => {
  page.current = 1;
  getList();
};
const paginationChange = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList()
}
  getList();
};
const pageProductChange = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true
  getStockInPage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    console.log('tableData:', tableData.value)
  }).catch(() => {
    tableLoading.value = false
  tableLoading.value = true;
  const params = {...page};
  params.timeStr = searchForm.value.timeStr;
  params.productName = searchForm.value.productName;
  getStockInRecordListPage(params)
      .then(res => {
        tableData.value = res.data.records;
      }).finally(() => {
        tableLoading.value = false;
  })
}
};
// è°ƒç”¨selectProductRecordListByPuechaserId这个方法根据合同查询到id,再调用getProductRecordByhetong这个方法根据id查询到产品订单记录
// æ–°å¢žæ ¹æ®åˆåŒå·æŸ¥è¯¢äº§å“è®°å½•的方法
const fetchProductsByContract = async () =>
{
  if (!form.value.purchaseContractNumber) {
    proxy.$modal.msgWarning('请选择合同号')
    return
  }
  try {
    loadingProducts.value = true
    // æ ¹æ®åˆåŒæŸ¥è¯¢äº§å“è®°å½•
    const productRes = await selectProductRecordListByPuechaserId({
      purchaseContractNumber: form.value.purchaseContractNumber
    });
    console.log('productRes:', productRes)
    if (!productRes.data || productRes.data.length === 0) {
      proxy.$modal.msgWarning('该合同下没有产品记录')
      productList.value = [];
      return
    }
    // å¤„理产品数据,添加本次入库数量字段
    productList.value = productRes.data.map(item => ({
      ...item,
      quantityStock: 0,
      originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? 0),
    }))
  } catch (error) {
    console.error('查询产品记录失败:', error)
    proxy.$modal.msgError('查询产品记录失败')
    productList.value = [];
  } finally {
    loadingProducts.value = false
  }
}
// æ‰“开弹框
  const openForm = async (type, row) => {
    operationType.value = type
    dialogFormVisible.value = true
    selectedRows.value = []
        await loadPurchaseOptions();
    if (type === 'add') {
      // æ–°å¢žæ—¶åˆå§‹åŒ–表单
      form.value = {
        id: null,
        purchaseContractNumber: '',
        supplierId: null,
        supplierName: '',
        inboundTime: '',
        inboundBatch: '',
        recorderId: userStore.userId,
        recorderName: userStore.name,
        entryDate: getCurrentDate(),
        remark: ''
      }
      productList.value = [] // æ¸…空产品列表
    } else {
      form.value = JSON.parse(JSON.stringify(row))
      try {
        loadingProducts.value = true
        // æ ¹æ®åˆåŒå·åŠ è½½å¯¹åº”çš„äº§å“åˆ—è¡¨ï¼ˆå‡è®¾ getProductByContract æ˜¯å¯ç”¨æŽ¥å£ï¼‰
        const res = await selectProductRecordListByPuechaserId({
          purchaseContractNumber: form.value.purchaseContractNumber,
          id: row.id
        });
                productList.value = res.data.map(item => ({
                    ...item,
                    quantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
                    originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
                }))
        selectedRows.value = productList.value
      } catch (error) {
        console.error('加载产品失败:', error)
        proxy.$modal.msgError('加载产品失败')
        productList.value = []
      } finally {
        loadingProducts.value = false
      }
    }
  }
  const updatePro = async () => {
     // å‡†å¤‡æäº¤æ•°æ®
     // å‡†å¤‡æäº¤æ•°æ® - ä¿®æ”¹ä¸ºåŽç«¯éœ€è¦çš„æ ¼å¼
    if (selectedRows.value.length === 0) {
      proxy.$modal.msgWarning('请先选择产品');
      return;
    }
    const target = selectedRows.value[0];
    const stock = Number(target?.quantityStock ?? 0);
    if (!Number.isFinite(stock) || stock <= 0) {
      proxy.$modal.msgWarning('请填写有效的入库数量');
      return;
    }
    if (exceedsEditLimit(target)) {
      proxy.$modal.msgError('本次入库数量不能超过原入库数量与待入库数量之和');
      return;
    }
    const stockInData = {
      id: selectedRows.value[0].recordId,
      quantityStock: Number(selectedRows.value[0].quantityStock),// ä½¿ç”¨æ–°æ ¼å¼åŒ–函数
    };
    await updateStockIn(stockInData)
    proxy.$modal.msgSuccess('修改入库成功')
    closeDia()
    getList() // åˆ·æ–°åˆ—表
  }
// æäº¤è¡¨å•
  const submitForm = async () => {
    // éªŒè¯è‡³å°‘选择了一个产品
    if (selectedRows.value.length === 0) {
      proxy.$modal.msgWarning('请先选择采购合同并选择产品')
      return
    }
    if(operationType.value !== 'add'){
      await updatePro()
      return
    }
    try {
      await proxy.$refs.formRef.validate()
      // éªŒè¯å…¥åº“数量
      const invalidProducts = selectedRows.value.filter((product) => {
          const stock = Number(product?.quantityStock ?? 0);
          if (!Number.isFinite(stock) || stock <= 0) {
            return true;
          }
          return exceedsAddLimit(product);
      })
      if (invalidProducts.length > 0) {
        proxy.$modal.msgError('本次入库数量需大于0,且不能超过待入库数量')
        return
      }
      // å‡†å¤‡æäº¤æ•°æ® - ä¿®æ”¹ä¸ºåŽç«¯éœ€è¦çš„æ ¼å¼
      const stockInData = {
        // å…¥åº“单基本信息
        ...form.value,
        inboundTime: formatDateTime(form.value.inboundTime),
        nickName: userStore.nickName,
        details: selectedRows.value.map(product => ({
          id: product.id,
          // id: product.salesLedgerProductId,
          inboundQuantity: Number(product.quantityStock)
        })),
      };
      // è°ƒç”¨API
      loading.value = true
      await addSutockIn(stockInData)
      proxy.$modal.msgSuccess('新增入库成功')
      closeDia()
      getList() // åˆ·æ–°åˆ—表
    } catch (error) {
      console.error('提交失败:', error)
      if (!error.errors) {
        proxy.$modal.msgError('操作失败,请重试')
      }
    } finally {
      loading.value = false
    }
  }
// å…³é—­å¼¹æ¡†
  const closeDia = () => {
    proxy.$refs.formRef.resetFields()
    dialogFormVisible.value = false
  }
// è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = (selection) => {
    // è¿‡æ»¤æŽ‰å­æ•°æ®
    selectedRows.value = selection.filter(item => item.id);
  }
const handleSelectionChange = selection => {
  selectedRows.value = selection.filter(item => item.id);
};
  const expandedRowKeys = ref([])
const expandedRowKeys = ref([]);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
  };
const summarizeMainTable = param => {
  return proxy.summarizeTable(param, [
    "contractAmount",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// å¯¼å‡º
  const handleOut = () => {
    ElMessageBox.confirm(
        '是否确认导出?',
        '导出', {
          confirmButtonText: '确认',
          cancelButtonText: '取消',
          type: 'warning',
        }
    ).then(() => {
      proxy.download("/stockin/export", {}, '入库台账.xlsx')
    }).catch(() => {
      proxy.$modal.msg("已取消")
    })
  }
// åˆ é™¤
  const handleDelete = () => {
    let ids = []
    if (selectedRows.value.length > 0) {
            // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
            const unauthorizedData = selectedRows.value.filter(item => item.createUser !== userStore.id);
            if (unauthorizedData.length > 0) {
                proxy.$modal.msgWarning("不可删除他人维护的数据");
                return;
            }
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning('请选择数据')
      return
    }
    ElMessageBox.confirm(
        '选中的内容将被删除,是否确认删除?',
        '导出', {
          confirmButtonText: '确认',
          cancelButtonText: '取消',
          type: 'warning',
        }
    ).then(() => {
      delStockIn({ids:ids}).then(res => {
        proxy.$modal.msgSuccess("删除成功")
        getList()
      })
    }).catch(() => {
      proxy.$modal.msg("已取消")
    })
  }
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
// ä¿®æ”¹ä¸ºæ›´é€šç”¨çš„æ—¥æœŸæ—¶é—´æ ¼å¼åŒ–函数
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`; // ä¿æŒåŽŸæœ‰ getCurrentDate åŠŸèƒ½
  }
  // æ–°å¢žæ—¶é—´éƒ¨åˆ†æ ¼å¼åŒ–
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
  onMounted(() => {
    getList()
const handleOut = () => {
  ElMessageBox.confirm("是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„导出接口
        let exportUrl = "/stockin/export";
        if (activeTab.value === "production") {
          exportUrl = "/stockin/exportOne";
        }
        proxy.download(exportUrl, {}, "入库台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// åˆ é™¤
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        batchDeleteStockInRecords(ids)
            .then(() => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("删除失败");
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/stockManagement/New.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="新增库存"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.productModelName"  disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        </el-form-item>
        <el-form-item
            label="数量"
            prop="qualitity"
        >
          <el-input-number v-model="formState.qualitity" :step="1" :min="0" style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {createStockInventory} from "@/api/inventoryManagement/stockInventory.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
});
const emit = defineEmits(['update:visible', 'completed']);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
  unit: "",
  qualitity: 0,
  remark: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    description: '',
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    console.log(product)
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      createStockInventory(formState.value).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/inventoryManagement/stockManagement/Subtract.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,183 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="领用"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true" disabled>
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.model"  disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        </el-form-item>
        <el-form-item
            label="数量"
            prop="qualitity"
        >
          <el-input-number v-model="formState.qualitity" :step="1" :min="1" :max="maxQuality" style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {subtractStockInventory} from "@/api/inventoryManagement/stockInventory.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  record: {
    type: Object,
    default: () => {},
  }
});
const emit = defineEmits(['update:visible', 'completed']);
onMounted(() => {
  initFormData()
})
const maxQuality = computed(() => {
  return props.record.qualitity ? props.record.qualitity :  0;
})
const initFormData = () => {
  if (props.record) {
    formState.value = {
      ...props.record,
    }
  }
}
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  model: "",
  unit: "",
  qualitity: 0,
  remark: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    description: '',
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    console.log(product)
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      subtractStockInventory(formState.value).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/inventoryManagement/stockManagement/index.vue
@@ -2,150 +2,48 @@
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">供应商名称:</span>
        <el-input v-model="searchForm.supplierName" style="width: 240px" placeholder="请输入" @change="handleQuery"
          clearable prefix-icon="Search" />
                <span class="search_title ml10">入库日期:</span>
                <el-date-picker
                    v-model="searchForm.timeStr"
                    type="date"
                    placeholder="请选择日期"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    clearable
                    @change="handleQuery"
                />
        <span class="search_title ml10">产品大类:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 240px"
                  placeholder="请输入"
                  clearable/>
        <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="isShowNewModal = true">新增库存</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%"
        :row-class-name="tableRowClassName"
        :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)">
        :expand-row-keys="expandedRowKeys" :row-key="row => row.id" 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="入库日期" prop="createTime" width="100" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" width="240" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" width="100" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" width="200" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="80" show-overflow-tooltip />
        <el-table-column label="库存数量" prop="inboundNum0" width="100" show-overflow-tooltip />
        <el-table-column label="库存预警数量" prop="warnNum" width="130" show-overflow-tooltip />
        <el-table-column label="含税单价" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
        <el-table-column label="含税总价" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="税率(%)" prop="taxRate" width="100" show-overflow-tooltip />
        <el-table-column label="不含税总价" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="入库人" prop="createBy" width="80" show-overflow-tooltip />
        <el-table-column label="入库日期" prop="createTime" show-overflow-tooltip />
        <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="qualitity" show-overflow-tooltip />
        <el-table-column label="库存预警数量" prop="warnNum"  show-overflow-tooltip />
        <el-table-column label="最近更新时间" prop="updateTime" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">编辑</el-button>
            <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.qualitity === 0">领用</el-button>
          </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" />
    </div>
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增库存' : '编辑库存'" width="70%"
      @close="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input disabled v-model="form.supplierName" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="产品大类:" prop="productId">
              <el-select disabled v-model="form.productCategory" placeholder="请选择" clearable filterable>
                <el-option v-for="item in productList" :key="item.id" :label="item.productName"
                           :value="item.productName" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="productManageId">
              <el-select disabled v-model="form.specificationModel" placeholder="请先选择产品大类" clearable filterable :disabled="!form.productCategory">
                <el-option v-for="item in productModelList" :key="item.id" :label="item.model"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item  label="单位:" prop="customerId">
              <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="库存时间:" prop="projectName">
              <el-date-picker style="width: 100%" v-model="form.updateTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                type="date" placeholder="请选择" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="入库时间:" prop="projectName">
              <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                type="date" placeholder="请选择" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
    <new-stock-inventory v-if="isShowNewModal"
                 v-model:visible="isShowNewModal"
                 @completed="handleQuery" />
          <el-col :span="12">
            <el-form-item  label="含税单价:" prop="customerId">
              <el-input disabled v-model="form.taxInclusiveUnitPrice" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item  label="含税总价:" prop="customerContractNo">
              <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item  label="税率:" prop="customerId">
              <el-input disabled v-model="form.taxRate" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="不含税总价:" prop="entryDate">
              <el-input disabled v-model="form.taxExclusiveTotalPrice" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="出库人:" prop="entryPerson">
              <el-select v-model="form.createUser" placeholder="请选择" clearable>
                <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="warnNum">-->
<!--            <el-input v-model="form.warnNum" placeholder="请输入最低库存" clearable />-->
<!--          </el-form-item>-->
<!--        </el-col>-->
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <subtract-stock-inventory v-if="isShowSubtractModal"
                 v-model:visible="isShowSubtractModal"
                 :record="record"
                 @completed="handleQuery" />
  </div>
</template>
@@ -153,84 +51,30 @@
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { productTreeList,modelList } from "@/api/basicData/product.js"
import { getCurrentDate } from "@/utils/index.js";
import {
  getStockManagePage,
  delStockManage,
} from "@/api/inventoryManagement/stockManage.js";
import {
  updateManagement,updateStockIn
} from "@/api/inventoryManagement/stockIn.js";
import { getStockInventoryListPage } from "@/api/inventoryManagement/stockInventory.js";
const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue"));
const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue"));
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const tableData = ref([])
const productData = ref([])
const selectedRows = ref([])
const userList = ref([])
const productList = ref([])
const productModelList = ref([])
// const customerOption = ref([])
const record = ref({})
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
const fileList = ref([])
const loading = ref(false);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref('')
const dialogFormVisible = ref(false)
// æ˜¯å¦æ˜¾ç¤ºæ–°å¢žå¼¹æ¡†
const isShowNewModal = ref(false)
// æ˜¯å¦æ˜¾ç¤ºé¢†ç”¨å¼¹æ¡†
const isShowSubtractModal = ref(false)
const data = reactive({
  searchForm: {
    supplierName: '',
        timeStr: '',
  },
  form: {
    supplierId: null,
    supplierName: '',
    productId: null,
    productName: '',
    userId: userStore.userId,
    nickName: '',
    productModelId: null,
    model: '',
    unit: '',
    productrecordId: null,
    taxInclusiveUnitPrice: '',
    taxInclusiveTotalPrice: '',
    taxRate: '',
    taxExclusiveTotalPrice: '',
    inboundTime: '',
    inboundBatch: '',
    stockQuantity: '',
    boundTime: '',
        warnNum: '', // æ–°å¢žæœ€ä½Žåº“存字段
    salesLedgerProductId: null,
  },
  rules: {
    supplierName: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }],
    productCategory: [{ required: true, message: '请选择产品大类', trigger: 'change' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    stockQuantity: [{ required: true, message: '请输入出库数量', trigger: 'blur' }],
    taxInclusiveUnitPrice: [{ required: true, message: '请输入含税单价', trigger: 'blur' }],
    taxInclusiveTotalPrice: [{ required: true, message: '请输入含税总价', trigger: 'blur' }],
    taxRate: [{ required: true, message: '请输入税率', trigger: 'blur' }],
    taxExclusiveTotalPrice: [{ required: true, message: '请输入不含税总价', trigger: 'blur' }],
    boundTime: [{ required: true, message: '请选择库存时间', trigger: 'change' }],
    inboundTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundPerson: [{ required: true, message: '请选择出库人', trigger: 'change' }],
        warnNum: [{ required: true, message: '请输入最低库存', trigger: 'blur' }],
  }
})
const { searchForm, form, rules } = toRefs(data)
const { searchForm } = toRefs(data)
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -245,7 +89,7 @@
}
const getList = () => {
  tableLoading.value = true
  getStockManagePage({ ...searchForm.value, ...page }).then(res => {
  getStockInventoryListPage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
@@ -256,19 +100,19 @@
  })
}
// ç‚¹å‡»é¢†ç”¨
const showSubtractModal = (row) => {
  record.value = row
  isShowSubtractModal.value = true
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
};
// è¡¨æ ¼è¡Œç±»å
const tableRowClassName = ({ row }) => {
@@ -279,74 +123,6 @@
  }
  return stock < warn ? 'row-low-stock' : '';
};
// æ‰“开弹框
const openForm = async (type, row) => {
  operationType.value = type
  form.value = {}
  productData.value = []
  let userLists = await userListNoPageByTenantId()
  userList.value = userLists.data
  if (type === 'edit') {
    form.value = { ...row }
    productTreeList().then(res =>{
      productList.value = res
      productList.value.forEach(i =>{
        if (i.label === row.productCategory) {
          modelList({ id: i.id }).then((res) => {
            productModelList.value = res;
          });
        }
      })
    })
  }
  form.value.entryDate = getCurrentDate() // è®¾ç½®é»˜è®¤å½•入日期为当前日期
  dialogFormVisible.value = true
}
// æäº¤è¡¨å•
const submitForm = () => {
  console.log(form.value)
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      updateManagement(form.value).then(res => {
        proxy.$modal.msgSuccess("提交成功")
        closeDia()
        getList()
        // æäº¤åŽæ£€æŸ¥åº“存并尝试创建请购单
        // checkStockAndCreatePurchase();
      })
    }
  })
}
// æ£€æŸ¥åº“存并创建请购单
// const checkStockAndCreatePurchase = async () => {
//   const stockList = tableData.value;
//   // handList()
//   for (const item of stockList) {
//     if (item.inboundNum0 < item.warnNum) {
//       try {
//                 const stockInData = {
//                     id: item.id,
//                     quantityStock: item.warnNum + item.totalInboundNum,// ä½¿ç”¨æ–°æ ¼å¼åŒ–函数
//                 };
//                 loading.value = true
//                 await updateStockIn(stockInData)
//                 proxy.$modal.msgSuccess(`产品 ${item.productCategory} ä¿®æ”¹å…¥åº“成功`)
//                 loading.value = false
//       } catch (error) {
//         proxy.$modal.msgError(`产品 ${item.productCategory} ç”Ÿæˆè¯·è´­å•失败,请手动处理`);
//
//       }
//     }
//   }
// };
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
  dialogFormVisible.value = false
}
// å¯¼å‡º
const handleOut = () => {
@@ -363,47 +139,9 @@
    proxy.$modal.msg("已取消")
  })
}
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.createUser !== userStore.id);
        if (unauthorizedData.length > 0) {
            proxy.$modal.msgWarning("不可删除他人维护的数据");
            return;
        }
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
    '选中的内容将被删除,是否确认删除?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    delStockManage({ids:ids}).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
  // checkStockAndCreatePurchase();
    // æ¯å°æ—¶æ£€æŸ¥ä¸€æ¬¡åº“å­˜
    // const intervalId = setInterval(checkStockAndCreatePurchase, 60 * 60 * 1000);
// onUnmounted(() => {
//   // ç»„件卸载时清除定时器
//   clearInterval(intervalId);
// });
})
</script>
src/views/lavorissue/ledger/Form.vue
@@ -73,12 +73,12 @@
import useFormData from "@/hooks/useFormData";
import {ref,onMounted} from "vue";
import useUserStore from "@/store/modules/user";
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import {deepCopySameProperties} from '@/utils/util'
const userStore = useUserStore();
import {
  getDept
} from "@/api/collaborativeApproval/approvalProcess.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
const { proxy } = getCurrentInstance();
@@ -112,8 +112,12 @@
  issueDate: undefined,
});
const getPersonList = () => {
  getStaffOnJob().then(res => {
    personList.value = res.data
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    personList.value = res.data.records
  })
};
const loadForm = (data) => {
src/views/login.vue
@@ -10,7 +10,6 @@
          size="large"
          auto-complete="off"
          placeholder="账号"
          @input="getUserLoginFacotryList"
        >
          <template #prefix><el-icon><User /></el-icon></template>
        </el-input>
@@ -27,11 +26,6 @@
        >
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="currentFatoryId">
        <el-select v-model="loginForm.currentFatoryId" placeholder="请选择公司" >
          <el-option  v-for="item in factoryList" :key="item.deptId" :label="item.deptName" :value="item.deptId" />
        </el-select>
      </el-form-item>
<!--      <el-form-item prop="code" v-if="captchaEnabled">-->
<!--        <el-input-->
@@ -77,7 +71,6 @@
import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from '@/store/modules/user'
import {userLoginFacotryList} from "@/api/system/user.js"
const title = import.meta.env.VITE_APP_TITLE
const userStore = useUserStore()
@@ -89,7 +82,6 @@
  username: "",
  password: "",
  rememberMe: false,
  currentFatoryId:'',
})
const loginRules = {
@@ -105,9 +97,6 @@
// æ³¨å†Œå¼€å…³
const register = ref(false)
const redirect = ref(undefined)
const factoryList = ref([])
const currentFatoryId = ref('')
watch(route, (newRoute) => {
    redirect.value = newRoute.query && newRoute.query.redirect
@@ -162,20 +151,8 @@
  }
}
function getUserLoginFacotryList() {
  if(loginForm.value.username){
    userLoginFacotryList({userName:loginForm.value.username}).then(res => {
      console.log('res', res)
      factoryList.value = res.data
    })
  }else {
    factoryList.value = []
  }
}
getCode()
getCookie()
getUserLoginFacotryList()
</script>
<style lang='scss' scoped>
src/views/personnelManagement/analytics/index.vue
@@ -1,5 +1,5 @@
<template>
  <div class="app-container analytics-container">
  <div class="app-container analytics-container" v-loading="loading">
    <!-- å…³é”®æŒ‡æ ‡å¡ç‰‡ -->
    <el-row :gutter="20" class="metrics-cards">
@@ -11,18 +11,18 @@
                <component :is="item.icon" />
              </el-icon>
            </div>
              <div class="card-info">
            <div class="card-info">
              <div class="card-number">
                <el-skeleton-item v-if="loading" variant="text" style="width: 60px; height: 32px;" />
                <span v-else>{{ item.value }}{{ item.unit }}</span>
              </div>
              <div class="card-label">{{ item.label }}</div>
              <div class="card-trend" :class="item.trend > 0 ? 'positive' : 'negative'" v-if="item.showTrend !== false">
                <el-icon>
                  <component :is="item.trend > 0 ? 'ArrowUp' : 'ArrowDown'" />
                </el-icon>
                {{ Math.abs(item.trend) }}%
              </div>
<!--              <div class="card-trend" :class="item.trend > 0 ? 'positive' : 'negative'" v-if="item.showTrend !== false">-->
<!--                <el-icon>-->
<!--                  <component :is="item.trend > 0 ? 'ArrowUp' : 'ArrowDown'" />-->
<!--                </el-icon>-->
<!--                {{ Math.abs(item.trend) }}%-->
<!--              </div>-->
            </div>
          </div>
        </el-card>
@@ -64,21 +64,6 @@
    <!-- ç¬¬äºŒè¡Œå›¾è¡¨ -->
    <el-row :gutter="20" class="charts-section">
      <!-- ç¼–制达成率 -->
      <el-col :span="12">
        <el-card class="chart-card">
          <template #header>
            <div class="card-header">
              <span>编制达成率</span>
              <el-tag type="warning">各部门对比</el-tag>
            </div>
          </template>
          <div class="chart-container">
            <div ref="staffingChartRef" class="chart"></div>
          </div>
        </el-card>
      </el-col>
      <!-- å‘˜å·¥æµå¤±åŽŸå› åˆ†æž -->
      <el-col :span="12">
        <el-card class="chart-card">
@@ -98,19 +83,15 @@
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import {
  Refresh,
  User,
  TrendCharts,
  DataAnalysis,
  PieChart,
  ArrowUp,
  ArrowDown
} from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { staffOnJobListPage } from '@/api/personnelManagement/employeeRecord.js'
import {listDept} from "@/api/system/dept.js";
import {
  findStaffAnalysisMonthlyTurnoverRateFor12Months,
  findStaffLeaveReasonAnalysis,
  findStaffAnalysisTotalStatistic
} from "@/api/personnelManagement/staffAnalytics.js";
// å“åº”式数据
const loading = ref(false)
@@ -151,14 +132,6 @@
    trend: 0
  },
  {
    label: '编制达成率',
    value: 0,
    unit: '%',
    icon: 'DataAnalysis',
    type: 'success',
    trend: 0
  },
  {
    label: '在职员工数',
    value: 0,
    unit: '人',
@@ -171,16 +144,56 @@
// éƒ¨é—¨æ•°æ®
const departmentData = ref([])
// å‘˜å·¥æµå¤±åŽŸå› åˆ†æžæ•°æ®
const staffLeaveReasons = ref([])
// 12个月员工流动流失率分析数据
const turnoverRateStatistics = ref([])
// èŽ·å–åœ¨èŒå‘˜å·¥æ•°
const getStaffCount = async () => {
// èŽ·å–éƒ¨é—¨æ•°æ®
const getDepartmentData = async () => {
  try {
    const res = await staffOnJobListPage({ staffState: 1, current: 1, size: 1 })
    const res = await listDept()
    if (res && res.data) {
      keyMetrics.value[3].value = res.data.total || 0
      departmentData.value = res.data
    }
  } catch (error) {
    console.error('获取在职员工数失败:', error)
    console.error('获取部门数据失败:', error)
  }
}
const getStaffLeaveReasonAnalysis = async () => {
  try {
    const res = await findStaffLeaveReasonAnalysis()
    if (res && res.data) {
      staffLeaveReasons.value = res.data || []
    }
  } catch (error) {
    console.error('获取员工流失原因分析失败:', error)
  }
}
// ä¿®æ”¹ä¸ºè¿”回Promise的异步函数
const getMonthlyTurnoverRateFor12Months = async () => {
  try {
    const res = await findStaffAnalysisMonthlyTurnoverRateFor12Months()
    if (res && res.data) {
      turnoverRateStatistics.value = res.data || []
    }
  } catch (error) {
    console.error('获取12个月员工流动流失率分析数据失败:', error)
  }
}
const getStaffAnalysisTotalStatistic = async () => {
  try {
    const res = await findStaffAnalysisTotalStatistic()
    if (res && res.data) {
      keyMetrics.value[0].value = res.data.totalFlowRate || 0
      keyMetrics.value[1].value = res.data.totalTurnoverRate || 0
      keyMetrics.value[2].value = res.data.currentOnJobCount || 0
    }
  } catch (error) {
    console.error('获取员工分析总统计数据失败:', error)
  }
}
@@ -213,49 +226,28 @@
  }
}
// ç”Ÿæˆæ¨¡æ‹Ÿæ•°æ®
const generateMockData = () => {
  // ç”Ÿæˆå…³é”®æŒ‡æ ‡æ•°æ®
  keyMetrics.value[0].value = (Math.random() * 5 + 2).toFixed(1)
  keyMetrics.value[0].trend = (Math.random() * 3 - 1.5).toFixed(1)
  keyMetrics.value[1].value = (Math.random() * 3 + 1).toFixed(1)
  keyMetrics.value[1].trend = (Math.random() * 2 - 1).toFixed(1)
  keyMetrics.value[2].value = (Math.random() * 15 + 85).toFixed(1)
  keyMetrics.value[2].trend = (Math.random() * 3 - 1.5).toFixed(1)
  // ç”Ÿæˆéƒ¨é—¨æ•°æ®
  const departments = ['技术部', '销售部', '人事部', '财务部', '生产部', '市场部']
  departmentData.value = departments.map(dept => ({
    department: dept,
    currentStaff: Math.floor(Math.random() * 30 + 20),
    plannedStaff: Math.floor(Math.random() * 10 + 35),
    staffingRate: Math.floor(Math.random() * 20 + 80),
    turnoverRate: (Math.random() * 4 + 1).toFixed(1),
    attritionRate: (Math.random() * 2 + 0.5).toFixed(1),
    newHires: Math.floor(Math.random() * 5 + 1),
    resignations: Math.floor(Math.random() * 3 + 1),
    status: Math.random() > 0.7 ? '异常' : '正常'
  }))
}
// åˆ·æ–°æ•°æ®
// ä¿®æ”¹ä¸ºå¼‚步函数,确保数据加载完成后再渲染图表
const refreshData = async () => {
  loading.value = true
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    generateMockData()
    loading.value = true
    // ç­‰å¾…所有数据加载完成
    await Promise.all([
      getDepartmentData(),
      getStaffLeaveReasonAnalysis(),
      getMonthlyTurnoverRateFor12Months(),
      getStaffAnalysisTotalStatistic()
    ])
    await nextTick()
    renderAllCharts()
    if (!autoRefreshEnabled.value) {
      ElMessage.success('数据刷新成功')
    }
  } catch (error) {
    console.error('刷新数据失败:', error)
    ElMessage.error('刷新数据失败')
    console.error('数据刷新失败:', error)
    ElMessage.error('数据刷新失败')
  } finally {
    loading.value = false
  }
@@ -276,8 +268,9 @@
    if (attritionChartRef.value) {
      attritionChart = echarts.init(attritionChartRef.value)
    }
    renderAllCharts()
    // åˆå§‹åŒ–时也先加载数据再渲染图表
    refreshData()
  }, 300)
}
@@ -289,14 +282,15 @@
  renderAttritionChart()
}
// æ¸²æŸ“员工流动率趋势图
// ä¿®æ”¹ä¸ºä½¿ç”¨API返回的实际数据
const renderTurnoverChart = () => {
  if (!turnoverChart) return
  const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
  const turnoverData = months.map(() => (Math.random() * 5 + 2).toFixed(1))
  const attritionData = months.map(() => (Math.random() * 3 + 1).toFixed(1))
  // ä½¿ç”¨API返回的实际数据
  const months = turnoverRateStatistics.value.map(item => item.month)
  const turnoverData = turnoverRateStatistics.value.map(item => item.flowRate || 0)
  const attritionData = turnoverRateStatistics.value.map(item => item.turnoverRate || 0)
  const option = {
    title: {
      text: '员工流动率趋势',
@@ -346,19 +340,19 @@
      }
    ]
  }
  turnoverChart.setOption(option)
}
// æ¸²æŸ“部门人员分布图
const renderDepartmentChart = () => {
  if (!departmentChart) return
  const data = departmentData.value.map(item => ({
    name: item.department,
    value: item.currentStaff
    name: item.deptName,
    value: item.staffCount
  }))
  const option = {
    title: {
      text: '部门人员分布',
@@ -391,17 +385,17 @@
      }
    ]
  }
  departmentChart.setOption(option)
}
// æ¸²æŸ“编制达成率图
const renderStaffingChart = () => {
  if (!staffingChart) return
  const departments = departmentData.value.map(item => item.department)
  const departments = departmentData.value.map(item => item.deptName)
  const rates = departmentData.value.map(item => item.staffingRate)
  const option = {
    title: {
      text: '编制达成率',
@@ -445,17 +439,17 @@
      }
    ]
  }
  staffingChart.setOption(option)
}
// æ¸²æŸ“员工流失原因分析图
const renderAttritionChart = () => {
  if (!attritionChart) return
  const reasons = ['薪资待遇', '职业发展', '工作环境', '个人原因', '其他']
  const data = reasons.map(() => Math.floor(Math.random() * 20 + 5))
  const reasons = staffLeaveReasons.value.map(item => item.reasonText)
  const data = staffLeaveReasons.value.map(item => item.count)
  const option = {
    title: {
      text: '员工流失原因分析',
@@ -491,14 +485,12 @@
      }
    ]
  }
  attritionChart.setOption(option)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  generateMockData()
  getStaffCount()
  initCharts()
  startAutoRefresh()
})
@@ -663,32 +655,32 @@
  .analytics-container {
    padding: 10px;
  }
  .page-header {
    padding: 15px;
  }
  .page-header h2 {
    font-size: 24px;
  }
  .header-controls {
    flex-direction: column;
    gap: 15px;
  }
  .refresh-btn {
    margin-left: 0;
  }
  .metrics-cards .el-col {
    margin-bottom: 15px;
  }
  .charts-section .el-col {
    margin-bottom: 20px;
  }
  .chart-container {
    height: 300px;
  }
@@ -698,11 +690,11 @@
  .page-header h2 {
    font-size: 20px;
  }
  .card-number {
    font-size: 24px;
  }
  .chart-container {
    height: 250px;
  }
src/views/personnelManagement/contractManagement/components/formDia.vue
@@ -19,22 +19,24 @@
        </div>
      </template>
    </el-dialog>
    <Files ref="filesDia"></Files>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {staffOnJobInfo} from "@/api/personnelManagement/employeeRecord.js";
import {findStaffContractListPage} from "@/api/personnelManagement/staffContract.js";
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 tableColumn = ref([
  // {
  //   label: "合同年限",
  //   prop: "contractTerm",
  // },
  {
    label: "合同年限",
    prop: "contractTerm",
  },
  {
    label: "合同开始日期",
    prop: "contractStartTime",
@@ -42,6 +44,22 @@
  {
    label: "合同结束日期",
    prop: "contractEndTime",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: 120,
    operation: [
      {
        name: "上传附件",
        type: "text",
        clickFun: (row) => {
          filesDia.value.openDialog( row,'合同')
        },
      }
    ],
  },
]);
const tableData = ref([]);
@@ -52,12 +70,17 @@
  operationType.value = type;
  dialogFormVisible.value = true;
  if (operationType.value === 'edit') {
    staffOnJobInfo({staffNo: row.staffNo}).then(res => {
      tableData.value = res.data
    findStaffContractListPage({staffOnJobId: row.id}).then(res => {
      tableData.value = res.data.records
    })
  }
}
const openUploadFile = (row) => {
  filesDia.value.open = true
  filesDia.value.row = row
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
src/views/personnelManagement/contractManagement/filesDia.vue
@@ -30,16 +30,10 @@
          :isSelection="true"
          @selection-change="handleSelectionChange"
          height="500"
          @pagination="paginationSearch"
          :total="page.total"
      >
      </PIMTable>
            <pagination
                style="margin: 10px 0"
                v-show="total > 0"
                @pagination="paginationSearch"
                :total="total"
                :page="page.current"
                :limit="page.size"
            />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
src/views/personnelManagement/contractManagement/index.vue
@@ -74,7 +74,7 @@
import { onMounted, ref } from "vue";
import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue";
import { ElMessageBox } from "element-plus";
import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js";
import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
import dayjs from "dayjs";
import { getToken } from "@/utils/auth.js";
import FilesDia from "./filesDia.vue";
@@ -191,14 +191,7 @@
        clickFun: (row) => {
          openForm("edit", row);
        },
      },
      {
        name: "附件",
        type: "text",
        clickFun: (row) => {
          openFilesFormDia(row);
        },
      },
      }
    ],
  },
]);
@@ -245,6 +238,7 @@
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  params.entryDate = undefined
  params.staffState = 1
  staffOnJobListPage(params).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
@@ -272,7 +266,7 @@
    type: "warning",
  })
    .then(() => {
      proxy.download("/staff/staffOnJob/export", {}, "合同管理.xlsx");
      proxy.download("/staff/staffOnJob/export", {staffState: 1}, "合同管理.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
src/views/personnelManagement/dimission/components/formDia.vue
@@ -9,145 +9,143 @@
      <!-- å‘˜å·¥ä¿¡æ¯å±•示区域 -->
      <div class="info-section">
        <div class="info-title">员工信息</div>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">姓名:</span>
              <el-select v-model="form.staffName" placeholder="请选择人员" style="width: 100%" @change="handleSelect">
                <el-option
                  v-for="item in personList"
                  :key="item.id"
                  :label="item.staffName"
                  :value="item.staffName"
        <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-select v-model="form.staffOnJobId"
                           placeholder="请选择人员"
                           style="width: 100%"
                           :disabled="operationType === 'edit'"
                           @change="handleSelect">
                  <el-option
                      v-for="item in personList"
                      :key="item.id"
                      :label="item.staffName"
                      :value="item.id"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="员工编号:">
                {{ currentStaffRecord.staffNo || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="性别:">
                {{ 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>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="第一学历:">
                {{ currentStaffRecord.firstStudy || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="专业:">
                {{ currentStaffRecord.profession || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="年龄:">
                {{ currentStaffRecord.age || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="联系电话:">
                {{ currentStaffRecord.phone || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="紧急联系人:">
                {{ currentStaffRecord.emergencyContact || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="紧急联系人联系电话:">
                {{ currentStaffRecord.emergencyContactPhone || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <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"
                      :key="index"
                      :label="item.label"
                      :value="item.value"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <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
                />
              </el-select>
            </div>
          </el-col>
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">员工编号:</span>
              <span class="info-value">{{ form.staffNo || '-' }}</span>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">性别:</span>
              <span class="info-value">{{ form.sex || '-' }}</span>
            </div>
          </el-col>
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">户籍住址:</span>
              <span class="info-value">{{ form.nativePlace || '-' }}</span>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">岗位:</span>
              <span class="info-value">{{ form.postJob || '-' }}</span>
            </div>
          </el-col>
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">现住址:</span>
              <span class="info-value">{{ form.adress || '-' }}</span>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">第一学历:</span>
              <span class="info-value">{{ form.firstStudy || '-' }}</span>
            </div>
          </el-col>
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">专业:</span>
              <span class="info-value">{{ form.profession || '-' }}</span>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">年龄:</span>
              <span class="info-value">{{ form.age || '-' }}</span>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">联系电话:</span>
              <span class="info-value">{{ form.phone || '-' }}</span>
            </div>
          </el-col>
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">紧急联系人:</span>
              <span class="info-value">{{ form.emergencyContact || '-' }}</span>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">紧急联系人联系电话:</span>
              <span class="info-value">{{ form.emergencyContactPhone || '-' }}</span>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">合同开始日期:</span>
              <span class="info-value">{{ form.contractStartTime || '-' }}</span>
            </div>
          </el-col>
          <el-col :span="12">
            <div class="info-item">
              <span class="info-label">合同结束日期:</span>
              <span class="info-value">{{ form.contractEndTime || '-' }}</span>
            </div>
          </el-col>
        </el-row>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
<!--        <el-row :gutter="30">-->
<!--          <el-col :span="12">-->
<!--            <div class="info-item">-->
<!--              <span class="info-label">离职原因:</span>-->
<!--              <el-select v-model="form.reason" placeholder="请选择人员" style="width: 100%" @change="handleSelect">-->
<!--                <el-option-->
<!--                    v-for="(item, index) in dimissionReasonOptions"-->
<!--                    :key="index"-->
<!--                    :label="item.label"-->
<!--                    :value="item.value"-->
<!--                />-->
<!--              </el-select>-->
<!--            </div>-->
<!--          </el-col>-->
<!--          <el-col :span="12">-->
<!--            <div class="info-item">-->
<!--              <span class="info-label">员工编号:</span>-->
<!--              <span class="info-value">{{ form.staffNo || '-' }}</span>-->
<!--            </div>-->
<!--          </el-col>-->
<!--        </el-row>-->
      </div>
      <!-- ç¦»èŒä¿¡æ¯å¡«å†™åŒºåŸŸ -->
      <!-- <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef" style="margin-top: 20px">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="离职日期:" prop="dimissionDate">
              <el-date-picker
                v-model="form.dimissionDate"
                type="date"
                placeholder="请选择离职日期"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                clearable
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="离职原因:" prop="dimissionReason">
              <el-input
                v-model="form.dimissionReason"
                type="textarea"
                :rows="3"
                placeholder="请输入离职原因"
                maxlength="500"
                show-word-limit
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form> -->
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
@@ -160,8 +158,8 @@
<script setup>
import {ref, reactive, toRefs, getCurrentInstance} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
import {createStaffLeave, updateStaffLeave} from "@/api/personnelManagement/staffLeave.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -169,86 +167,80 @@
const operationType = ref('')
const data = reactive({
  form: {
    staffNo: "",
    staffName: "",
    sex: "",
    nativePlace: "",
    postJob: "",
    adress: "",
    firstStudy: "",
    profession: "",
    age: 0,
    phone: "",
    emergencyContact: "",
    emergencyContactPhone: "",
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
    dimissionDate: "",
    dimissionReason: "",
    staffState: "",
    staffOnJobId: undefined,
    reason: "",
    remark: "",
  },
  rules: {
    staffName: [{ required: true, message: "请选择人员", trigger: "change" }],
    dimissionDate: [{ required: true, message: "请选择离职日期", trigger: "change" }],
    dimissionReason: [{ required: true, message: "请输入离职原因", trigger: "blur" }],
    staffName: [{ required: true, message: "请选择人员" }],
    reason: [{ required: true, message: "请选择离职原因"}],
  },
  dimissionReasonOptions: [
      {label: '薪资待遇', value: 'salary'},
      {label: '职业发展', value: 'career_development'},
      {label: '工作环境', value: 'work_environment'},
      {label: '个人原因', value: 'personal_reason'},
      {label: '其他', value: 'other'},
  ],
  currentStaffRecord: {},
});
const { form, rules } = toRefs(data);
const { form, rules, dimissionReasonOptions, currentStaffRecord } = toRefs(data);
// æ‰“开弹框
const openDialog = (type, row) => {
  getList()
  operationType.value = type;
  dialogFormVisible.value = true;
  if (operationType.value === 'edit') {
    getStaffJoinInfo(row.id).then(res => {
      form.value = {...res.data}
    })
    currentStaffRecord.value = row
    form.value.staffOnJobId = row.staffOnJobId
    form.value.reason = row.reason
    form.value.remark = row.remark
    personList.value = [
      {
        staffName: row.staffName,
        id: row.staffOnJobId,
      }
    ]
  } else {
    getList()
  }
}
const handleSelectDimissionReason = (val) => {
  if (val === 'other') {
    form.value.remark = ''
  }
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  // è¡¨å•已注释,直接提交,不进行验证
  if (!form.value.staffName) {
    proxy.$modal.msgError("请选择人员");
    return;
  }
  form.value.staffState = 0
  if (operationType.value === "add") {
    staffJoinAdd(form.value).then(res => {
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
    })
  } else {
    staffJoinUpdate(form.value).then(res => {
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
    })
  if (form.value.reason !== 'other') {
    form.value.remark = ''
  }
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (operationType.value === "add") {
        createStaffLeave(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      } else {
        updateStaffLeave(currentStaffRecord.value.id, form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      }
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  // è¡¨å•已注释,手动重置表单数据
  form.value = {
    staffNo: "",
    staffName: "",
    sex: "",
    nativePlace: "",
    postJob: "",
    adress: "",
    firstStudy: "",
    profession: "",
    age: 0,
    phone: "",
    emergencyContact: "",
    emergencyContactPhone: "",
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
    dimissionDate: "",
    dimissionReason: "",
    staffState: "",
    staffOnJobId: undefined,
    reason: "",
    remark: "",
  };
  dialogFormVisible.value = false;
  emit('close')
@@ -270,44 +262,11 @@
};
const handleSelect = (val) => {
  let obj = personList.value.find(item => item.staffName === val)
  let obj = personList.value.find(item => item.id === val)
  currentStaffRecord.value = {}
  if (obj) {
    let {
      sex,
      phone,
      staffNo,
      nativePlace,
      postJob,
      adress,
      firstStudy,
      profession,
      age,
      emergencyContact,
      emergencyContactPhone,
      contractTerm,
      contractStartTime,
      contractEndTime,
      staffName
    } = obj
    // ä¿ç•™ç¦»èŒæ—¥æœŸå’Œç¦»èŒåŽŸå› ï¼Œåªæ›´æ–°å‘˜å·¥ä¿¡æ¯
    form.value = {
      ...form.value,
      sex,
      phone,
      staffNo,
      nativePlace,
      postJob,
      adress,
      firstStudy,
      profession,
      age,
      emergencyContact,
      emergencyContactPhone,
      contractTerm,
      contractStartTime,
      contractEndTime,
      staffName
    }
    currentStaffRecord.value = obj
  }
}
defineExpose({
src/views/personnelManagement/dimission/index.vue
@@ -11,22 +11,6 @@
            clearable
            :prefix-icon="Search"
        />
        <span style="margin-left: 10px;"  class="search_title">合同开始日期:</span>
        <el-date-picker
            v-model="searchForm.entryDateStart"
            type="date"
            placeholder="请选择合同开始日期"
            size="default"
            @change="(date) => handleDateChange(date,1)"
        />
        <span style="margin-left: 10px;" class="search_title">合同结束日期:</span>
        <el-date-picker
            v-model="searchForm.entryDateEnd"
            type="date"
            placeholder="请选择合同结束日期"
            size="default"
            @change="(date) => handleDateChange(date,2)"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
@@ -58,9 +42,8 @@
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import FormDia from "@/views/personnelManagement/dimission/components/formDia.vue";
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {findStaffLeaveListPage, batchDeleteStaffLeaves} from "@/api/personnelManagement/staffLeave.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
const data = reactive({
  searchForm: {
@@ -109,8 +92,12 @@
    prop: "nativePlace",
  },
  {
    label: "部门",
    prop: "deptName",
  },
  {
    label: "岗位",
    prop: "postJob",
    prop: "postName",
  },
  {
    label: "现住址",
@@ -145,24 +132,11 @@
    prop: "emergencyContactPhone",
    width:150
  },
  // {
  //   label: "合同年限",
  //   prop: "contractTerm",
  // },
  {
    label: "合同开始日期",
    prop: "contractStartTime",
    width: 120
  },
  {
    label: "合同结束日期",
    prop: "contractEndTime",
    width: 120
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    operation: [
      {
        name: "编辑",
@@ -186,20 +160,6 @@
const { proxy } = getCurrentInstance()
const handleDateChange = (value,type) => {
  searchForm.value.entryDateEnd = null
  searchForm.value.entryDateStart = null
  if(type === 1){
    if (value) {
      searchForm.value.entryDateStart = dayjs(value).format("YYYY-MM-DD");
    }
  }else{
    if (value) {
      searchForm.value.entryDateEnd = dayjs(value).format("YYYY-MM-DD");
    }
  }
  getList();
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
@@ -213,7 +173,7 @@
};
const getList = () => {
  tableLoading.value = true;
  staffJoinListPage({...page, ...searchForm.value, staffState: 0}).then(res => {
  findStaffLeaveListPage({...page, ...searchForm.value}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
@@ -248,7 +208,7 @@
    type: "warning",
  })
      .then(() => {
        staffJoinDel(ids).then((res) => {
        batchDeleteStaffLeaves(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
@@ -265,7 +225,7 @@
    type: "warning",
  })
      .then(() => {
        proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 0}, "人员离职.xlsx");
        proxy.download("/staff/staffLeave/export", {}, "人员离职.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue
ÎļþÃû´Ó src/views/personnelManagement/onboarding/components/formDia.vue ÐÞ¸Ä
@@ -36,13 +36,40 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="岗位:" prop="postJob">
              <el-input v-model="form.postJob" placeholder="请输入" clearable/>
            <el-form-item label="岗位:" prop="sysPostId">
              <el-select v-model="form.sysPostId" placeholder="请选择岗位" clearable>
                <el-option
                    v-for="item in postOptions"
                    :key="item.postId"
                    :label="item.postName"
                    :value="item.postId"
                    :disabled="item.status === '1'"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="现住址:" prop="adress">
              <el-input v-model="form.adress" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="部门:" prop="sysDeptId">
              <el-tree-select
                  v-model="form.sysDeptId"
                  :data="deptOptions"
                  :props="{ value: 'id', label: 'label', children: 'children' }"
                  value-key="id"
                  placeholder="请选择部门"
                  check-strictly
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="年龄:" prop="age">
              <el-input-number v-model="form.age" :precision="0" :step="1" style="width: 100%"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -55,13 +82,6 @@
          <el-col :span="12">
            <el-form-item label="专业:" prop="profession">
              <el-input v-model="form.profession" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="年龄:" prop="age">
              <el-input-number v-model="form.age" :precision="0" :step="1" style="width: 100%"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -131,13 +151,17 @@
</template>
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {ref, onMounted} from "vue";
import {findPostOptions} from "@/api/system/post.js";
import {listDept} from "@/api/system/dept.js";
import {staffOnJobInfo, createStaffOnJob, updateStaffOnJob} from "@/api/personnelManagement/staffOnJob.js";
import {deptTreeSelect} from "@/api/system/user.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const id = ref(0)
const data = reactive({
  form: {
    staffNo: "",
@@ -156,6 +180,8 @@
    contractStartTime: "",
    contractEndTime: "",
    staffState: "",
    sysPostId: undefined,
    sysDeptId: undefined,
  },
  rules: {
    staffNo: [{ required: true, message: "请输入", trigger: "blur" },],
@@ -174,35 +200,76 @@
    contractStartTime: [{ required: true, message: "请输入", trigger: "blur" }],
    contractEndTime: [{ required: true, message: "请输入", trigger: "blur" }],
  },
  postOptions: [], // å²—位选项
  deptOptions: [], // éƒ¨é—¨é€‰é¡¹
});
const { form, rules } = toRefs(data);
const { form, rules, postOptions, deptOptions } = toRefs(data);
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
  if (operationType.value === 'edit') {
    getStaffJoinInfo(row.id).then(res => {
    id.value = row.id
    staffOnJobInfo(id.value, {}).then(res => {
      form.value = {...res.data}
      if (form.value.sysPostId === 0) {
        form.value.sysPostId = undefined
      }
      // ç¼–辑时也计算一次合同年限
      calculateContractTerm();
    })
  } else {
        form.value.id = ''
    }
}
onMounted(() => {
  fetchPostOptions()
  fetchDeptOptions()
})
const fetchPostOptions = () => {
  findPostOptions().then(res => {
    postOptions.value = res.data
  })
}
// æŸ¥è¯¢éƒ¨é—¨åˆ—表
const fetchDeptOptions = () => {
  deptTreeSelect().then(response => {
    deptOptions.value = filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
  })
}
/** è¿‡æ»¤ç¦ç”¨çš„部门 */
function filterDisabledDept(deptList) {
  return deptList.filter(dept => {
    if (dept.disabled) {
      return false
    }
    if (dept.children && dept.children.length) {
      dept.children = filterDisabledDept(dept.children)
    }
    return true
  })
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  if (!form.value.sysPostId) {
    form.value.sysPostId = 0;
  }
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      form.value.staffState = 1
      if (operationType.value === "add") {
        staffJoinAdd(form.value).then(res => {
        createStaffOnJob(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      } else {
        staffJoinUpdate(form.value).then(res => {
        updateStaffOnJob(id.value, form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
src/views/personnelManagement/employeeRecord/components/RenewContract.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
<template>
  <el-dialog
      v-model="isShow"
      title="续签合同"
      width="800px"
      @close="closeModal"
  >
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-form-item label="合同开始日期:" prop="contractStartTime">
        <el-date-picker
            v-model="form.contractStartTime"
            type="date"
            placeholder="请选择日期"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            clearable
            style="width: 100%"
            @change="calculateContractTerm"
        />
      </el-form-item>
      <el-form-item label="合同结束日期:" prop="contractEndTime">
        <el-date-picker
            v-model="form.contractEndTime"
            type="date"
            placeholder="请选择日期"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            clearable
            style="width: 100%"
            @change="calculateContractTerm"
        />
      </el-form-item>
      <el-form-item label="合同年限:" prop="contractTerm">
        <el-input-number v-model="form.contractTerm" :precision="0" :step="1" style="width: 100%" :disabled="true"/>
      </el-form-item>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitForm">确认</el-button>
        <el-button @click="closeModal">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
// ç»­ç­¾åˆåŒ
import { renewContract } from "@/api/personnelManagement/staffOnJob.js";
import {computed, getCurrentInstance,} from "vue";
const emit = defineEmits(['update:visible', 'completed']);
const data = reactive({
  form: {
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
  },
  rules: {
    contractTerm: [{ required: true, message: "请输入", trigger: "blur" }],
    contractStartTime: [{ required: true, message: "请输入", trigger: "blur" }],
    contractEndTime: [{ required: true, message: "请输入", trigger: "blur" }],
  }
});
const { form, rules } = toRefs(data);
let { proxy } = getCurrentInstance()
const props = defineProps({
  id: {
    type: Number,
    default: 0,
  },
  visible: {
    type: Boolean,
    required: true,
  },
})
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
// è®¡ç®—合同年限
const calculateContractTerm = () => {
  if (form.value.contractStartTime && form.value.contractEndTime) {
    const startDate = new Date(form.value.contractStartTime);
    const endDate = new Date(form.value.contractEndTime);
    if (endDate > startDate) {
      // è®¡ç®—年份差
      const yearDiff = endDate.getFullYear() - startDate.getFullYear();
      const monthDiff = endDate.getMonth() - startDate.getMonth();
      const dayDiff = endDate.getDate() - startDate.getDate();
      let years = yearDiff;
      // å¦‚果结束日期的月日小于开始日期的月日,则减去1å¹´
      if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
        years = yearDiff - 1;
      }
      form.value.contractTerm = Math.max(0, years);
    } else {
      form.value.contractTerm = 0;
    }
  } else {
    form.value.contractTerm = 0;
  }
};
const submitForm = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      renewContract(props.id, form.value).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("续签合同成功");
          emit('completed');
          closeModal();
        }
      })
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  form.value = {
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
  };
  isShow.value = false;
};
</script>
src/views/personnelManagement/employeeRecord/components/Show.vue
ÎļþÃû´Ó src/views/personnelManagement/employeeRecord/components/formDia.vue ÐÞ¸Ä
@@ -24,7 +24,7 @@
<script setup>
import {ref} from "vue";
import {staffOnJobInfo} from "@/api/personnelManagement/employeeRecord.js";
import {staffOnJobInfo} from "@/api/personnelManagement/staffOnJob.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
src/views/personnelManagement/employeeRecord/index.vue
@@ -19,9 +19,9 @@
        >
      </div>
      <div>
<!--        <el-button type="primary" @click="openForm('add')">新增入职</el-button>-->
        <el-button type="primary" @click="openFormNewOrEditFormDia('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">
@@ -37,17 +37,26 @@
          :total="page.total"
      ></PIMTable>
    </div>
    <form-dia ref="formDia" @close="handleQuery"></form-dia>
    <show-form-dia ref="formDia" @close="handleQuery"></show-form-dia>
    <new-or-edit-form-dia ref="formDiaNewOrEditFormDia" @close="handleQuery"></new-or-edit-form-dia>
    <renew-contract
        v-if="isShowRenewContractModal"
        v-model:visible="isShowRenewContractModal"
        :id="id"
        @completed="handleQuery"
    />
  </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import FormDia from "@/views/personnelManagement/employeeRecord/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js";
import {batchDeleteStaffOnJobs, staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
import dayjs from "dayjs";
const NewOrEditFormDia = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue"));
const ShowFormDia = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/Show.vue"));
const RenewContract = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/RenewContract.vue"));
const data = reactive({
  searchForm: {
@@ -58,6 +67,8 @@
  },
});
const { searchForm } = toRefs(data);
const isShowRenewContractModal = ref(false);
const id = ref(0);
const tableColumn = ref([
  {
    label: "状态",
@@ -97,6 +108,10 @@
  {
    label: "户籍住址",
    prop: "nativePlace",
  },
  {
    label: "部门",
    prop: "deptName",
  },
  {
    label: "岗位",
@@ -154,14 +169,31 @@
    label: "操作",
    align: "center",
    fixed: 'right',
    width: 180,
    operation: [
      {
        name: "详情",
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
          openFormNewOrEditFormDia("edit", row);
        },
      },
      {
        name: "续签合同",
        type: "text",
        showHide: row => row.staffState === 1,
        clickFun: (row) => {
          isShowRenewContractModal.value = true;
          id.value = row.id;
        },
      },
      // {
      //   name: "详情",
      //   type: "text",
      //   clickFun: (row) => {
      //     openForm("edit", row);
      //   },
      // },
    ],
  },
]);
@@ -174,6 +206,7 @@
  total: 0
});
const formDia = ref()
const formDiaNewOrEditFormDia = ref()
const { proxy } = getCurrentInstance()
const changeDaterange = (value) => {
@@ -200,7 +233,7 @@
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  params.entryDate = undefined
  staffOnJobListPage({...params, staffState: 1}).then(res => {
  staffOnJobListPage({...params}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
@@ -219,6 +252,37 @@
    formDia.value?.openDialog(type, row)
  })
};
const openFormNewOrEditFormDia = (type, row) => {
  nextTick(() => {
    formDiaNewOrEditFormDia.value?.openDialog(type, row)
  })
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        batchDeleteStaffOnJobs(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
@@ -227,7 +291,7 @@
    type: "warning",
  })
      .then(() => {
        proxy.download("/staff/staffOnJob/export", {staffState: 1}, "在职员工台账.xlsx");
        proxy.download("/staff/staffOnJob/export", {staffState: 1}, "员工台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
src/views/personnelManagement/onboarding/components/formDiaXJHT.vue
ÎļþÒÑɾ³ý
src/views/personnelManagement/onboarding/index.vue
ÎļþÒÑɾ³ý
src/views/personnelManagement/payrollManagement/components/formDia.vue
@@ -168,8 +168,8 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, getStaffOnJob, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {compensationAdd, compensationUpdate} from "@/api/personnelManagement/payrollManagement.js";
import {staffOnJobInfo, staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -234,12 +234,16 @@
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    getStaffOnJob().then(res => {
        personList.value = res.data
    })
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    personList.value = res.data.records || []
  })
    form.value = {}
  if (operationType.value === 'edit') {
    getStaffJoinInfo(row.id).then(res => {
    staffOnJobInfo(row.staffId).then(res => {
            form.value = {...row}
            form.value.payDate = form.value.payDate + '-01'
    })
src/views/personnelManagement/payrollManagement/index.vue
@@ -53,7 +53,6 @@
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/personnelManagement/payrollManagement/components/formDia.vue";
import {staffJoinDel} from "@/api/personnelManagement/onboarding.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
import {compensationDelete, compensationListPage} from "@/api/personnelManagement/payrollManagement.js";
src/views/personnelManagement/scheduling/index.vue
@@ -253,9 +253,9 @@
import {useDict} from "@/utils/dict.js"
import {Plus, Download, Search, Refresh} from '@element-plus/icons-vue'
import {save, del, delByIds, listPage} from "@/api/personnelManagement/scheduling.js"
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import pagination from "@/components/PIMTable/Pagination.vue";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
const { proxy } = getCurrentInstance();
@@ -317,8 +317,12 @@
 * èŽ·å–å½“å‰åœ¨èŒäººå‘˜åˆ—è¡¨
 */
const getPersonList = () => {
  getStaffOnJob().then(res => {
    personList.value = res.data
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    personList.value = res.data.records || []
  })
};
const paginationChange = (obj) => {
src/views/personnelManagement/selfService/index.vue
@@ -221,8 +221,7 @@
const { proxy } = getCurrentInstance()
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinUpdate, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import { fa, id } from 'element-plus/es/locales.mjs'
import {staffOnJobListPage, updateStaffOnJob} from "@/api/personnelManagement/staffOnJob.js";
const tableLoading = ref(false)
// åˆ†é¡µå‚æ•°
@@ -572,7 +571,7 @@
      currentUser.value = res.data
      // console.log("----",currentUser.value)
        //得到人员列表
        staffJoinListPage({staffState: 1}).then(res => {
      staffOnJobListPage({staffState: 1}).then(res => {
          //筛选出和currentUser同名的人员
          // let tableData = res.data.records
          user.value = res.data.records.find(item => item.staffName === currentUser.value.userName)
@@ -604,18 +603,15 @@
    const userRes = await getUserProfile();
    if (userRes.code === 200) {
      currentUser.value = userRes.data;
      const staffListRes = await staffJoinListPage({ staffState: 1 });
      const staffListRes = await staffOnJobListPage({ staffState: 1 });
      user.value = staffListRes.data.records.find(item => item.staffName === currentUser.value.userName);
      // console.log("++++", user.value);
      Object.assign(joinForm, user.value);
      joinForm.staffName = profileForm.name;
      joinForm.phone = profileForm.phone;
      joinForm.email = profileForm.email;
      joinForm.adress = profileForm.adress; 
      console.log(joinForm)
      // è°ƒç”¨æ›´æ–°ä¸ªäººä¿¡æ¯çš„æŽ¥å£
      staffJoinUpdate(joinForm).then(res => {
      updateStaffOnJob(user.value.id, joinForm).then(res => {
        if (res.code === 200) {
          ElMessage.success('个人信息保存成功');
          getProfile();
src/views/procurementManagement/advancedPriceManagement/index.vue
@@ -139,7 +139,7 @@
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增价格' : '编辑价格'" width="800px">
    <FormDialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增价格' : '编辑价格'" :width="'800px'" :operation-type="dialogType" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
      <el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px">
        <el-row :gutter="20">
          <el-col :span="12">
@@ -266,14 +266,10 @@
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
      </template>
    </el-dialog>
    </FormDialog>
    <!-- æ‰¹é‡æŠ˜æ‰£å¯¹è¯æ¡† -->
    <el-dialog v-model="batchDiscountVisible" title="批量设置折扣" width="600px">
    <FormDialog v-model="batchDiscountVisible" title="批量设置折扣" :width="'600px'" @close="batchDiscountVisible = false" @confirm="handleBatchDiscount" @cancel="batchDiscountVisible = false">
      <el-form :model="batchDiscountForm" label-width="120px">
        <el-form-item label="折扣类型">
          <el-select v-model="batchDiscountForm.discountType" placeholder="请选择折扣类型" style="width: 100%">
@@ -303,14 +299,10 @@
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="batchDiscountVisible = false">取消</el-button>
        <el-button type="primary" @click="handleBatchDiscount">确定</el-button>
      </template>
    </el-dialog>
    </FormDialog>
    <!-- ä»·æ ¼æŽ§åˆ¶å¯¹è¯æ¡† -->
    <el-dialog v-model="priceControlVisible" title="价格控制设置" width="700px">
    <FormDialog v-model="priceControlVisible" title="价格控制设置" :width="'700px'" @close="priceControlVisible = false" @confirm="handlePriceControl" @cancel="priceControlVisible = false">
      <el-form :model="priceControlForm" label-width="120px">
        <el-form-item label="默认最低价格">
          <el-input-number v-model="priceControlForm.defaultMinPrice" :min="0" :precision="2" style="width: 200px" />
@@ -322,16 +314,13 @@
          <el-input-number v-model="priceControlForm.changeThreshold" :min="0" :max="100" :precision="1" style="width: 200px" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="priceControlVisible = false">取消</el-button>
        <el-button type="primary" @click="handlePriceControl">保存设置</el-button>
      </template>
    </el-dialog>
    </FormDialog>
  </div>
</template>
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import {ref, reactive, computed, onMounted, getCurrentInstance} from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
src/views/procurementManagement/arrivalManagement/index.vue
@@ -51,7 +51,7 @@
      />
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增到货' : '编辑到货'" width="600px">
    <FormDialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增到货' : '编辑到货'" :width="'600px'" :operation-type="dialogType" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
      <el-form :model="formData" label-width="120px">
        <el-form-item label="到货单号">
          <el-input v-model="formData.arrivalNo" placeholder="到货单号" />
@@ -69,15 +69,12 @@
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
    </FormDialog>
  </div>
</template>
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref, reactive,onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {listPage,add,update,del} from "@/api/procurementManagement/arrivalManagement.js"
src/views/procurementManagement/invoiceEntry/components/Modal.vue
@@ -1,160 +1,202 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" width="70%">
    <el-form
      ref="formRef"
      :model="form"
      :rules="rules"
      label-width="120px"
      label-position="top"
    >
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="采购合同号:" prop="purchaseLedgerNo">
            <el-input v-model="form.purchaseLedgerNo" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="销售合同号:" prop="salesContractNo">
            <el-input
              v-model="form.salesContractNo"
              placeholder="自动填充"
              clearable
              disabled
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="供应商名称:" prop="supplierName">
            <el-input
              v-model="form.supplierName"
              placeholder="自动填充"
              clearable
              disabled
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="项目名称:" prop="projectName">
            <el-input
              v-model="form.projectName"
              placeholder="自动填充"
              clearable
              disabled
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="发票号:" prop="invoiceNumber">
            <el-input
              v-model="form.invoiceNumber"
              placeholder="请输入"
              clearable
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="发票金额(元):" prop="invoiceAmount">
            <el-input-number :step="0.01" :min="0" style="width: 100%"
              v-model="form.invoiceAmount"
              placeholder="自动填充"
              clearable
              :disabled="true"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="录入人:" prop="issUer">
            <el-input
              v-model="form.issUer"
              placeholder="请输入"
              clearable
              disabled
            />
          </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"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              clearable
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="上传附件">
            <FileUpload
              :showTip="false"
              accept="*"
              :autoUpload="true"
              :action="action"
              :headers="{
    <el-dialog :title="modalOptions.title" v-model="visible" width="70%" draggable>
        <el-form
            ref="formRef"
            :model="form"
            :rules="rules"
            label-width="120px"
            label-position="top"
        >
            <el-row :gutter="30">
                <el-col :span="12">
                    <el-form-item label="采购合同号:" prop="purchaseLedgerNo">
                        <el-input v-model="form.purchaseLedgerNo" disabled placeholder="多合同批量处理(具体合同号见产品列表)" />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="销售合同号:" prop="salesContractNo">
                        <el-input
                            v-model="form.salesContractNo"
                            placeholder="自动填充"
                            clearable
                            disabled
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="供应商名称:" prop="supplierName">
                        <el-input
                            v-model="form.supplierName"
                            placeholder="自动填充"
                            clearable
                            disabled
                        />
                    </el-form-item>
                </el-col>
<!--                <el-col :span="12">-->
<!--                    <el-form-item label="项目名称:" prop="projectName">-->
<!--                        <el-input-->
<!--                            v-model="form.projectName"-->
<!--                            placeholder="自动填充"-->
<!--                            clearable-->
<!--                            disabled-->
<!--                        />-->
<!--                    </el-form-item>-->
<!--                </el-col>-->
                <el-col :span="12">
                    <el-form-item label="发票号:" prop="invoiceNumber">
                        <el-input
                            v-model="form.invoiceNumber"
                            placeholder="请输入"
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="发票金额(元):" prop="invoiceAmount">
                        <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                         v-model="form.invoiceAmount"
                                                         placeholder="请输入发票金额"
                                                         clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="录入人:" prop="issUer">
                        <el-input
                            v-model="form.issUer"
                            placeholder="请输入"
                            clearable
                            disabled
                        />
                    </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"
                            type="date"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="录入日期:" prop="enterDate">
                        <el-date-picker
                            style="width: 100%"
                            v-model="form.enterDate"
                            type="date"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="上传附件">
                        <FileUpload
                            :showTip="false"
                            accept="*"
                            :autoUpload="true"
                            :action="action"
                            :headers="{
                Authorization: 'Bearer ' + getToken(),
              }"
              :limit="10"
              @success="uploadSuccess"
              @remove="removeFile"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="录入日期:" prop="enterDate">
            <el-date-picker
              style="width: 100%"
              v-model="form.enterDate"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              clearable
            />
          </el-form-item>
        </el-col>
      </el-row>
      <el-form-item label="产品信息:"> </el-form-item>
      <PIMTable
        rowKey="id"
        :column="columns"
        :tableData="form.productData"
                :summaryMethod="summarizeChildrenTable"
                :isShowSummary="true"
        height="auto"
      >
        <template #ticketsNumRef="{ row }">
          <el-input-number
            v-model="row.ticketsNum"
            placeholder="请输入"
            :min="0"
            :step="0.1"
                        :precision="2"
            clearable
            style="width: 100%"
            @change="invoiceNumBlur(row)"
          />
        </template>
        <template #ticketsAmountRef="{ row }">
          <el-input-number
            v-model="row.ticketsAmount"
            placeholder="请输入"
            :min="0"
                        :precision="2"
            :step="0.1"
            clearable
            style="width: 100%"
            @change="invoiceAmountBlur(row)"
          />
        </template>
      </PIMTable>
    </el-form>
    <template #footer>
                            :limit="10"
                            @success="uploadSuccess"
                            @remove="removeFile"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-form-item label="产品信息:"> </el-form-item>
            <el-table
                :data="form.productData"
                border
                show-summary
                :summary-method="summarizeChildrenTable"
            >
                <el-table-column align="center" label="序号" type="index" width="60" />
                <el-table-column label="所属合同" prop="purchaseLedgerNo" width="200">
                    <template #default="{ row }">
                        <el-tag type="primary">{{ row.purchaseLedgerNo }}</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="产品大类" prop="productCategory" />
                <el-table-column label="规格型号" prop="specificationModel" width="150" />
                <el-table-column label="单位" prop="unit" width="70" />
                <el-table-column label="数量" prop="quantity" width="70" />
                <el-table-column label="税率(%)" prop="taxRate" width="80" />
                <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="ticketsNum" width="180">
                    <template #default="scope">
                        <el-input-number :step="0.1" :min="0" style="width: 100%"
                                                         :precision="2"
                                                         v-model="scope.row.ticketsNum"
                                                         @change="invoiceNumBlur(scope.row)"
                        />
                    </template>
                </el-table-column>
                <el-table-column
                    label="本次开票金额(元)"
                    prop="ticketsAmount"
                    width="180"
                >
                    <template #default="scope">
                        <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                         :precision="2"
                                                         v-model="scope.row.ticketsAmount"
                                                         @change="invoiceAmountBlur(scope.row)"
                        />
                    </template>
                </el-table-column>
                <el-table-column
                    label="未来票数"
                    prop="futureTickets"
                    :formatter="formattedNumber"
                />
                <el-table-column
                    label="本次来票金额(元)"
                    prop="ticketsAmount"
                    :formatter="formattedNumber"
                />
                <el-table-column
                    label="未来票数"
                    prop="futureTickets"
                    :formatter="formattedNumber"
                />
                <el-table-column
                    label="未来票金额(元)"
                    prop="futureTicketsAmount"
                    :formatter="formattedNumber"
                />
            </el-table>
        </el-form>
        <template #footer>
            <el-button type="primary" :loading="modalLoading" @click="submitForm">
                {{ modalOptions.confirmText }}
                ç¡®è®¤
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
            <el-button @click="closeModal">取消</el-button>
        </template>
    </el-dialog>
</template>
<script setup>
@@ -164,9 +206,9 @@
import useFormData from "@/hooks/useFormData";
import FileUpload from "@/components/Upload/FileUpload.vue";
import {
  getPurchaseNoById,
  getInfo,
  addOrUpdateRegistration,
    getPurchaseNoById,
    getInfo,
    addOrUpdateRegistration,
} from "@/api/procurementManagement/invoiceEntry.js";
import { getPurchaseById } from "@/api/procurementManagement/procurementLedger.js";
import { getToken } from "@/utils/auth";
@@ -174,7 +216,7 @@
import dayjs from "dayjs";
defineOptions({
  name: "来票登记模态框",
    name: "来票登记模态框",
});
const userStore = useUserStore();
@@ -182,155 +224,237 @@
const formRef = ref();
const { proxy } = getCurrentInstance();
const { form } = useFormData({
  purchaseLedgerNo: undefined, // é‡‡è´­åˆåŒå·
  salesContractNo: undefined, // é”€å”®åˆåŒå·
  supplierName: undefined, // ä¾›åº”商名称
  projectName: undefined, // é¡¹ç›®åç§°
  invoiceNumber: undefined, // å‘票号
  invoiceAmount: undefined, // å‘票金额(元)
  issUerId: userStore.id, // å½•入人
  issUer: userStore.nickName, // å½•入人
  entryDate: undefined, // å¼€ç¥¨æ—¥æœŸ
  salesContractNoId: undefined, // å¼€ç¥¨æ—¥æœŸ
  enterDate: dayjs().format("YYYY-MM-DD"),
  productData: [], // è¡¨æ ¼
  tempFileIds: [], // æ–‡ä»¶
    purchaseLedgerNo: undefined, // é‡‡è´­åˆåŒå·
    salesContractNo: undefined, // é”€å”®åˆåŒå·
    supplierName: undefined, // ä¾›åº”商名称
    projectName: undefined, // é¡¹ç›®åç§°
    invoiceNumber: undefined, // å‘票号
    invoiceAmount: undefined, // å‘票金额(元)
    issUerId: userStore.id, // å½•入人
    issUer: userStore.nickName, // å½•入人
    entryDate: undefined, // å¼€ç¥¨æ—¥æœŸ
    salesContractNoId: undefined, // å¼€ç¥¨æ—¥æœŸ
    enterDate: dayjs().format("YYYY-MM-DD"),
    productData: [], // è¡¨æ ¼
    tempFileIds: [], // æ–‡ä»¶
});
const selectedContracts = ref([]); // å­˜å‚¨é€‰ä¸­çš„合同数据
const rules = ref({
  invoiceNumber: [
    { required: true, message: "请输入发票号", trigger: "blur" },
    { type: "string" },
  ],
  invoiceAmount: [
    { required: true, message: "请输入发票金额", trigger: "blur" },
  ],
  entryDate: [{ required: true, message: "请选择开票日期", trigger: "change" }],
  enterDate: [{ required: true, message: "请选择录入日期", trigger: "change" }],
    invoiceNumber: [
        { required: true, message: "请输入发票号", trigger: "blur" },
        { type: "string" },
    ],
    invoiceAmount: [
        { required: true, message: "请输入发票金额", trigger: "blur" },
    ],
    entryDate: [{ required: true, message: "请选择开票日期", trigger: "change" }],
    enterDate: [{ required: true, message: "请选择录入日期", trigger: "change" }],
});
const {
  id,
  visible,
  loading: modalLoading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
    id,
    visible,
    loading: modalLoading,
    openModal,
    modalOptions,
    handleConfirm,
    closeModal,
} = useModal({
  title: "来票登记",
    title: "来票登记",
});
const emit = defineEmits(['refreshList']);
const columns = [
  {
    label: "产品大类",
    prop: "productCategory",
    {
        label: "产品大类",
        prop: "productCategory",
        width: 120,
  },
  {
    label: "规格型号",
    prop: "specificationModel",
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 120,
  },
  {
    label: "单位",
    prop: "unit",
    width: 80,
  },
  {
    label: "数量",
    prop: "quantity",
    width: 80,
  },
  {
    label: "税率(%)",
    prop: "taxRate",
    width: 80,
  },
  {
    label: "录入日期",
    prop: "registerDate",
    width: 120,
  },
  {
    label: "含税单价(元)",
    prop: "taxInclusiveUnitPrice",
    width: 150,
    formatData: (val) => {
      return val ? parseFloat(val).toFixed(2) : 0;
    },
  },
  {
    label: "含税总价(元)",
    prop: "taxInclusiveTotalPrice",
    width: 150,
    formatData: (val) => {
      return parseFloat(val).toFixed(2) ?? 0;
    },
  },
  {
    label: "不含税总价(元)",
    prop: "taxExclusiveTotalPrice",
    width: 150,
    formatData: (val) => {
      return parseFloat(val).toFixed(2) ?? 0;
    },
  },
  {
    label: "本次来票数",
    prop: "ticketsNum",
    dataType: "slot",
    slot: "ticketsNumRef",
    width: 180,
    align: "center",
  },
  {
    label: "本次来票金额(元)",
    prop: "ticketsAmount",
    dataType: "slot",
    slot: "ticketsAmountRef",
    width: 180,
    align: "center",
  },
  {
    label: "未来票数",
    prop: "futureTickets",
    },
    {
        label: "单位",
        prop: "unit",
        width: 80,
    },
    {
        label: "数量",
        prop: "quantity",
        width: 80,
    },
    {
        label: "税率(%)",
        prop: "taxRate",
        width: 80,
    },
    {
        label: "录入日期",
        prop: "registerDate",
        width: 120,
    },
    {
        label: "含税单价(元)",
        prop: "taxInclusiveUnitPrice",
        width: 150,
        formatData: (val) => {
            return val ? parseFloat(val).toFixed(2) : 0;
        },
    },
    {
        label: "含税总价(元)",
        prop: "taxInclusiveTotalPrice",
        width: 150,
        formatData: (val) => {
            return parseFloat(val).toFixed(2) ?? 0;
        },
    },
    {
        label: "不含税总价(元)",
        prop: "taxExclusiveTotalPrice",
        width: 150,
        formatData: (val) => {
            return parseFloat(val).toFixed(2) ?? 0;
        },
    },
    {
        label: "本次来票数",
        prop: "ticketsNum",
        dataType: "slot",
        slot: "ticketsNumRef",
        width: 180,
        align: "center",
    },
    {
        label: "本次来票金额(元)",
        prop: "ticketsAmount",
        dataType: "slot",
        slot: "ticketsAmountRef",
        width: 180,
        align: "center",
    },
    {
        label: "未来票数",
        prop: "futureTickets",
        width: 100,
  },
  {
    label: "未来票金额(元)",
    prop: "futureTicketsAmount",
    },
    {
        label: "未来票金额(元)",
        prop: "futureTicketsAmount",
        width: 200,
  },
    },
];
const getTableData = async (type, id) => {
  if (type == "add") {
    const { data } = await getPurchaseNoById({ id });
    form.purchaseLedgerNo = data.purchaseContractNumber;
    form.invoiceAmount = data.invoiceAmount;
    form.invoiceNumber = data.invoiceNumber;
    form.entryDate = data.entryDate;
    form.salesContractNoId = data.salesContractNoId;
    const { data: infoData } = await getInfo({ id });
    form.salesContractNo = infoData.salesContractNo;
    form.projectName = infoData.projectName;
    form.supplierName = infoData.supplierName;
    form.productData = infoData.productData;
  } else if (type == "edit") {
    const data = await getPurchaseById({ id, type: 2 });
    form.purchaseLedgerNo = data.purchaseContractNumber;
    form.invoiceAmount = data.invoiceAmount;
    form.invoiceNumber = data.invoiceNumber;
    form.salesContractNo = data.salesContractNo;
    form.projectName = data.projectName;
    form.supplierName = data.supplierName;
    form.entryDate = data.entryDate;
    form.productData = data.productData;
  }
const formattedNumber = (row, column, cellValue) => {
    if (cellValue == 0) {
        return parseFloat(cellValue).toFixed(2);
    }
    if (cellValue) {
        return parseFloat(cellValue).toFixed(2);
    } else {
        return cellValue;
    }
};
const getTableData = async (type, selectedRows) => {
    if (type == "add") {
        // æ£€æŸ¥æ‰€æœ‰é€‰æ‹©çš„合同是否具有相同的供应商名称
        const firstRow = selectedRows[0];
        const isSameSupplier = selectedRows.every(row =>
            row.supplierName === firstRow.supplierName
        );
        if (!isSameSupplier) {
            proxy.$modal.msgError("请选择相同供应商名称的合同");
            return;
        }
        // å…è®¸ä¸åŒçš„采购合同号批量处理,无需检查重复
        // æ¸…空表单数据
        Object.keys(form).forEach(key => {
            if (key !== 'productData') {
                form[key] = undefined;
            }
        });
        form.productData = [];
        // åŠ è½½æ‰€æœ‰é€‰ä¸­åˆåŒçš„äº§å“æ•°æ®
        const promises = selectedRows.map(row =>
            getInfo({ id: row.id })
        );
        Promise.all(promises).then(results => {
            // åˆå¹¶æ‰€æœ‰åˆåŒçš„产品数据,并为每个产品添加对应的合同信息
            const allProductData = [];
            results.forEach((result, index) => {
                const contract = selectedRows[index];
                const contractId = contract.id;
                if (result.data && result.data.productData) {
                    result.data.productData.forEach(item => {
                        allProductData.push({
                            ...item,
                            id: contractId, // æ˜Žç¡®è®¾ç½®åˆåŒID
                            purchaseLedgerNo: contract.purchaseContractNumber, // æ·»åŠ é‡‡è´­åˆåŒå·
                            supplierName: contract.supplierName, // æ·»åŠ ä¾›åº”å•†åç§°
                            projectName: contract.projectName // æ·»åŠ é¡¹ç›®åç§°
                        });
                    });
                }
            });
            // è®¾ç½®è¡¨å•数据(使用第一个合同的基本信息,采购合同号留空)
            form.purchaseLedgerNo = ""; // é‡‡è´­åˆåŒå·ç•™ç©ºï¼Œå› ä¸ºä¼šåœ¨äº§å“è¡¨æ ¼ä¸­åˆ†åˆ«æ˜¾ç¤º
            form.invoiceNumber = "";
            form.entryDate = dayjs().format("YYYY-MM-DD");
            form.enterDate = dayjs().format("YYYY-MM-DD");
            form.salesContractNo = results[0].data.salesContractNo;
            form.projectName = results[0].data.projectName;
            form.supplierName = results[0].data.supplierName;
            // ä¿ç•™å½•入人信息
            form.issUerId = userStore.id;
            form.issUer = userStore.nickName;
            // è®¾ç½®äº§å“æ•°æ®ï¼Œå¹¶åˆå§‹åŒ–开票数量和金额
            allProductData.forEach(item => {
                // æœ¬æ¬¡å¼€ç¥¨æ•°é»˜è®¤ä¸ºæ€»æ•°é‡
                item.ticketsNum = Number(item.quantity || 0);
                // æœ¬æ¬¡å¼€ç¥¨é‡‘额默认为含税总价
                item.ticketsAmount = Number(item.taxInclusiveTotalPrice || 0);
                // ä¿å­˜åŽŸå§‹æœªæ¥ç¥¨æ•°å’Œé‡‘é¢ï¼ˆç”¨äºŽè®¡ç®—ï¼‰
                item.tempFutureTickets = Number(item.quantity || 0);
                item.tempFutureTicketsAmount = Number(item.taxInclusiveTotalPrice || 0);
                // æœªæ¥ç¥¨æ•°å’Œé‡‘额初始为0(因为全部开票)
                item.futureTickets = 0;
                item.futureTicketsAmount = 0;
            });
            form.productData = allProductData;
            // è®¡ç®—发票金额:所有产品的含税总价之和
            const totalAmount = allProductData.reduce((sum, item) => {
                return sum + (Number(item.taxInclusiveTotalPrice) || 0);
            }, 0);
            form.invoiceAmount = totalAmount.toFixed(2);
            // å­˜å‚¨é€‰ä¸­çš„合同数据
            selectedContracts.value = selectedRows;
        });
    } else if (type == "edit") {
        const id = Array.isArray(selectedRows) ? selectedRows[0].id : selectedRows;
        const data = await getPurchaseById({ id, type: 2 });
        form.purchaseLedgerNo = data.purchaseContractNumber;
        form.invoiceAmount = data.invoiceAmount;
        form.invoiceNumber = data.invoiceNumber;
        form.salesContractNo = data.salesContractNo;
        form.projectName = data.projectName;
        form.supplierName = data.supplierName;
        form.entryDate = data.entryDate;
        form.productData = data.productData;
    }
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
@@ -347,109 +471,186 @@
};
//本次来票数失焦操作
const invoiceNumBlur = (row) => {
  if (!row.ticketsNum || row.ticketsNum === "") {
    row.ticketsNum = 0;
  }
  if (Number(row.ticketsNum) > Number(row.tempFutureTickets)) {
    proxy.$modal.msgWarning("本次开票数不得大于未开票数");
    row.ticketsNum = 0;
    return;
  }
  // è®¡ç®—本次来票金额
  row.ticketsAmount = (row.ticketsNum * row.taxInclusiveUnitPrice).toFixed(2)
  // è®¡ç®—未来票数
  row.futureTickets = (row.tempFutureTickets - row.ticketsNum).toFixed(2)
  // è®¡ç®—未来票金额
  row.futureTicketsAmount = (row.tempFutureTicketsAmount - row.ticketsAmount).toFixed(2)
  calculateinvoiceAmount();
    if (!row.ticketsNum || row.ticketsNum === "") {
        row.ticketsNum = 0;
    }
    if (Number(row.ticketsNum) > Number(row.tempFutureTickets)) {
        proxy.$modal.msgWarning("本次开票数不得大于未开票数");
        row.ticketsNum = 0;
        return;
    }
    // è®¡ç®—本次来票金额
    row.ticketsAmount = (row.ticketsNum * row.taxInclusiveUnitPrice).toFixed(2)
    // è®¡ç®—未来票数
    row.futureTickets = (row.tempFutureTickets - row.ticketsNum).toFixed(2)
    // è®¡ç®—未来票金额
    row.futureTicketsAmount = (row.tempFutureTicketsAmount - row.ticketsAmount).toFixed(2)
    calculateinvoiceAmount();
};
// æœ¬æ¬¡æ¥ç¥¨é‡‘额失焦操作
const invoiceAmountBlur = (row) => {
  if (!row.ticketsAmount) {
    row.ticketsAmount = 0;
  }
  // è®¡ç®—是否超过来票总金额
  if (row.ticketsAmount > row.tempFutureTicketsAmount) {
    proxy.$modal.msgWarning("本次来票金额不得大于未来票金额");
    row.ticketsAmount = 0;
  }
  // è®¡ç®—本次来票数
  row.ticketsNum = Number(
    (row.ticketsAmount / row.taxInclusiveUnitPrice).toFixed(2)
  );
  // è®¡ç®—未来票数
  row.futureTickets = (row.tempFutureTickets - row.ticketsNum).toFixed(2)
  // è®¡ç®—未来票金额
  row.futureTicketsAmount = (row.tempFutureTicketsAmount - row.ticketsAmount).toFixed(2)
  calculateinvoiceAmount();
    if (!row.ticketsAmount) {
        row.ticketsAmount = 0;
    }
    // è®¡ç®—是否超过来票总金额
    if (row.ticketsAmount > row.tempFutureTicketsAmount) {
        proxy.$modal.msgWarning("本次来票金额不得大于未来票金额");
        row.ticketsAmount = 0;
    }
    // è®¡ç®—本次来票数
    row.ticketsNum = Number(
        (row.ticketsAmount / row.taxInclusiveUnitPrice).toFixed(2)
    );
    // è®¡ç®—未来票数
    row.futureTickets = (row.tempFutureTickets - row.ticketsNum).toFixed(2)
    // è®¡ç®—未来票金额
    row.futureTicketsAmount = (row.tempFutureTicketsAmount - row.ticketsAmount).toFixed(2)
    calculateinvoiceAmount();
};
const calculateinvoiceAmount = () => {
  let invoiceAmountTotal = 0;
  form.productData.forEach((item) => {
    if (item.ticketsAmount) {
      invoiceAmountTotal += Number(item.ticketsAmount);
    }
  });
  form.invoiceAmount = invoiceAmountTotal.toFixed(2);
    let invoiceAmountTotal = 0;
    form.productData.forEach((item) => {
        if (item.ticketsAmount) {
            invoiceAmountTotal += Number(item.ticketsAmount);
        }
    });
    form.invoiceAmount = invoiceAmountTotal.toFixed(2);
};
const open = (type, eid) => {
  openModal();
  getTableData(type, eid);
  id.value = eid;
const open = async (type, selectedRows) => {
    visible.value = true;
    // å¦‚果是批量操作,设置标题
    if (Array.isArray(selectedRows) && selectedRows.length > 1) {
        modalOptions.title = `批量新增 (${selectedRows.length}条)`;
    } else {
        modalOptions.title = type === "add" ? "新增" : "编辑";
    }
    // å¦‚果是单个操作,获取id
    if (!Array.isArray(selectedRows) || selectedRows.length === 1) {
        const idValue = Array.isArray(selectedRows) ? selectedRows[0].id : selectedRows;
        id.value = idValue;
    }
    await getTableData(type, selectedRows);
};
const uploadSuccess = (response) => {
  form.tempFileIds.push(response.data.tempId);
  console.log(form);
    form.tempFileIds.push(response.data.tempId);
    console.log(form);
};
const removeFile = (file) => {
  const { tempId } = file.response.data;
  form.tempFileIds = form.tempFileIds.filter((item) => item !== tempId);
    const { tempId } = file.response.data;
    form.tempFileIds = form.tempFileIds.filter((item) => item !== tempId);
};
const closeAndRefresh = () => {
  closeModal();
  emit('refreshList');
    closeModal();
    emit('refreshList');
};
const submitForm = () => {
  formRef.value.validate(async (valid, fields) => {
    if (valid) {
      // modalLoading.value = true;
      const { code } = await addOrUpdateRegistration({
        purchaseLedgerId: id.value,
        purchaseContractNumber: form.purchaseLedgerNo,
        invoiceNumber: form.invoiceNumber,
        invoiceAmount: form.invoiceAmount,
        salesContractNo: form.salesContractNo,
        projectName: form.projectName,
        productData: form.productData,
        issueDate: form.entryDate,
        issUerId: form.issUerId, // å½•入人id
        issUer: form.issUer, // å½•入人
        salesContractNoId: form.salesContractNoId,
        supplierName: form.supplierName,
        tempFileIds: form.tempFileIds,
        enterDate: form.enterDate,
        type: 4,
      });
      modalLoading.value = false;
      if (code == 200) {
        closeAndRefresh();
      }
    } else {
      modalLoading.value = false;
    }
  });
    proxy.$refs["formRef"].validate((valid) => {
        if (valid) {
            // å¦‚果是批量操作,将所有合同的数据放在一个数组里,只调用一次接口
            if (selectedContracts.value.length > 1) {
                // åˆ›å»ºåŒ…含所有合同数据的数组
                const batchData = selectedContracts.value.map(contract => {
                    // ç­›é€‰å‡ºå±žäºŽå½“前合同的产品数据
                    const contractProductData = form.productData.filter(item =>
                        item.id === contract.id
                    );
                    // ä¸ºæ¯ä¸ªé‡‡è´­åˆåŒåˆ›å»ºç‹¬ç«‹çš„对象
                    return {
                        // åŸºç¡€è¡¨å•数据
                        invoiceNumber: form.invoiceNumber,
                        invoiceAmount: form.invoiceAmount,
                        entryDate: form.entryDate,
                        enterDate: form.enterDate,
                        issUerId: form.issUerId, // å½•入人id
                        issUer: form.issUer, // å½•入人
                        tempFileIds: form.tempFileIds,
                        // åˆåŒå®žé™…信息
                        purchaseLedgerId: contract.id, // ä½¿ç”¨id作为字段名,值为purchaseLedgerId
                        purchaseContractNumber: contract.purchaseContractNumber, // ä½¿ç”¨å®žé™…的采购合同号
                        salesContractNo: contract.salesContractNo, // ä½¿ç”¨å®žé™…的销售合同号
                        supplierName: contract.supplierName, // ä½¿ç”¨å®žé™…的供应商名称
                        projectName: contract.projectName, // ä½¿ç”¨å®žé™…的项目名称
                        // äº§å“æ•°æ®
                        productData: proxy.HaveJson(contractProductData),
                        // æ‰¹é‡æ ‡è¯†
                        isBatch: true,
                        type: 4
                    };
                });
                // åªè°ƒç”¨ä¸€æ¬¡æŽ¥å£ï¼Œä¼ é€’包含所有合同数据的数组
                modalLoading.value = true;
                addOrUpdateRegistration(batchData).then((res) => {
                    modalLoading.value = false;
                    if (res.code === 200) {
                        proxy.$modal.msgSuccess("批量登记成功");
                        closeAndRefresh();
                    }
                }).catch(() => {
                    modalLoading.value = false;
                    proxy.$modal.msgError("批量登记失败");
                });
            } else {
                    // å•个合同提交逻辑 - ä»¥æ•°ç»„格式传递
                    const singleContract = selectedContracts.value[0];
                    const singleFormArray = [{
                        // åŸºç¡€è¡¨å•数据
                        invoiceNumber: form.invoiceNumber,
                        invoiceAmount: form.invoiceAmount,
                        entryDate: form.entryDate,
                        enterDate: form.enterDate,
                        issUerId: form.issUerId, // å½•入人id
                        issUer: form.issUer, // å½•入人
                        tempFileIds: form.tempFileIds,
                        // åˆåŒå®žé™…信息
                        purchaseLedgerId: singleContract.id, // ä½¿ç”¨id作为字段名,值为purchaseLedgerId
                        purchaseContractNumber: singleContract.purchaseContractNumber, // ä½¿ç”¨å®žé™…的采购合同号
                        salesContractNo: singleContract.salesContractNo, // ä½¿ç”¨å®žé™…的销售合同号
                        supplierName: singleContract.supplierName, // ä½¿ç”¨å®žé™…的供应商名称
                        projectName: singleContract.projectName, // ä½¿ç”¨å®žé™…的项目名称
                        // äº§å“æ•°æ®
                        productData: proxy.HaveJson(form.productData),
                        // æ‰¹é‡æ ‡è¯†
                        isBatch: false,
                        type: 4
                    }];
                    modalLoading.value = true;
                    addOrUpdateRegistration(singleFormArray).then((res) => {
                        modalLoading.value = false;
                        if (res.code === 200) {
                            proxy.$modal.msgSuccess("登记成功");
                            closeAndRefresh();
                        }
                    }).catch(() => {
                        modalLoading.value = false;
                        proxy.$modal.msgError("登记失败");
                    });
                }
        }
    });
};
defineExpose({
  open,
  closeAndRefresh,
    open,
    closeAndRefresh,
});
</script>
src/views/procurementManagement/invoiceEntry/index.vue
@@ -28,13 +28,6 @@
              clearable
          />
        </el-form-item>
        <el-form-item label="项目名称">
          <el-input
              v-model="filters.projectName"
              placeholder="请输入项目名称"
              clearable
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="getTableData"> æœç´¢ </el-button>
          <el-button @click="resetFilters"> é‡ç½® </el-button>
@@ -47,7 +40,7 @@
        <div>
          <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
          <el-button type="primary" @click="handleAdd('add')">
            æ–°å¢žç™»è®°
            æ¥ç¥¨ç™»è®°
          </el-button>
<!--          <el-button type="danger" plain @click="handleDelete">删除</el-button>-->
        </div>
@@ -143,11 +136,6 @@
      width:300
    },
    {
      label: "项目名称",
      prop: "projectName",
      width:400
    },
    {
      label: "录入人",
      prop: "recorderName",
    },
@@ -198,11 +186,11 @@
};
const handleAdd = (type) => {
  if (selectedRows.value.length !== 1) {
    proxy.$modal.msgWarning("请先选中一条数据");
    return;
  }
  modalRef.value.open(type, selectedRows.value[0].id);
    if (selectedRows.value.length < 1) {
        proxy.$modal.msgWarning("请至少选中一条数据");
        return;
    }
    modalRef.value.open(type, selectedRows.value);
};
const handleEdit = (type, id) => {
src/views/procurementManagement/invoiceEntry/indexOld.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/paymentEntry/index.vue
@@ -27,10 +27,10 @@
          </el-col>
          <el-col :span="4">
            <el-form-item style="float: right; margin-right: unset">
              <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
              <el-button type="primary" @click="openForm('add')">
                æ–°å¢žä»˜æ¬¾
              </el-button>
              <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
<!--              <el-button type="danger" plain @click="handleDelete">-->
<!--                åˆ é™¤-->
<!--              </el-button>-->
@@ -127,145 +127,100 @@
                </template>
            </PIMTable>
    </div>
    <el-dialog
    <FormDialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增付款登记' : '编辑付款登记'"
      width="60%"
      title="新增付款页面"
      :width="'90%'"
      @close="closeDia"
      @confirm="submitForm"
      @cancel="closeDia"
    >
      <el-form
        :model="form"
        label-width="140px"
        label-position="top"
        :rules="rules"
        ref="formRef"
      <el-table
        v-if="forms.length"
        :data="forms"
        border
        style="width: 100%"
        size="small"
      >
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购合同号:" prop="purchaseContractNumber">
              <el-input
                v-model="form.purchaseContractNumber"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="销售合同号:" prop="salesContractNo">
              <el-input
                v-model="form.salesContractNo"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                v-model="form.supplierName"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="发票号:" prop="invoiceNumber">
              <el-input
                v-model="form.invoiceNumber"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票金额(元):" prop="invoiceAmount">
              <el-input
                v-model="form.invoiceAmount"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="本次付款金额:" prop="currentPaymentAmount">
              <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                             :precision="2"
                v-model="form.currentPaymentAmount"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="付款方式:" prop="paymentMethod">
              <el-select
                v-model="form.paymentMethod"
                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="paymentDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.paymentDate"
                                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="registrant">
                            <el-input
                                v-model="form.registrant"
                                placeholder="请输入"
                                clearable
                                disabled
                            />
                        </el-form-item>
                    </el-col>
          <el-col :span="12">
            <el-form-item label="登记日期:" prop="registrationtDate">
              <el-input
                v-model="form.registrationtDate"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
        <el-table-column type="index" label="序号" width="50" align="center"/>
        <el-table-column label="采购合同号" prop="purchaseContractNumber" show-overflow-tooltip />
        <el-table-column label="销售合同号" prop="salesContractNo" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" show-overflow-tooltip />
        <el-table-column
          label="产品大类"
          prop="productCategory"
          show-overflow-tooltip
          width="100"
        />
        <el-table-column
          label="规格型号"
          prop="specificationModel"
          show-overflow-tooltip
          width="150"
        />
        <el-table-column
          label="待付款金额(元)"
          prop="pendingTicketsTotal"
          show-overflow-tooltip
          width="170"
        >
          <template #default="{ row, column }">
            <el-text type="danger">
              {{ formattedNumber(row, column, row.pendingTicketsTotal) }}
            </el-text>
          </template>
        </el-table-column>
        <el-table-column label="本次付款金额(元)" width="180">
          <template #default="{ row }">
            <el-input-number
              v-model="row.currentPaymentAmount"
              :step="0.01"
              :min="0"
              :max="Number(row.pendingTicketsTotal || 0)"
              :precision="2"
              style="width: 100%"
              placeholder="请输入"
            />
          </template>
        </el-table-column>
        <el-table-column label="付款方式" width="160">
          <template #default="{ row }">
            <el-select v-model="row.paymentMethod" placeholder="请选择" clearable>
              <el-option label="电汇" value="电汇" />
              <el-option label="承兑" value="承兑" />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="付款日期" width="170">
          <template #default="{ row }">
            <el-date-picker
              v-model="row.paymentDate"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              type="date"
              placeholder="请选择"
              style="width: 100%"
            />
          </template>
        </el-table-column>
        <el-table-column label="登记人" width="140">
          <template #default="{ row }">
            <el-input v-model="row.registrant" disabled />
          </template>
        </el-table-column>
        <el-table-column label="登记日期" width="170">
          <template #default="{ row }">
            <el-input v-model="row.registrationtDate" />
          </template>
        </el-table-column>
      </el-table>
      <div v-else class="empty-tip">请选择需要付款的记录</div>
    </FormDialog>
  </div>
</template>
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref, reactive, toRefs, getCurrentInstance, nextTick, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
@@ -296,10 +251,12 @@
  {
    label: "采购合同号",
    prop: "purchaseContractNumber",
    width:160
  },
  {
    label: "销售合同号",
    prop: "salesContractNo",
    width:160
  },
  {
    label: "供应商名称",
@@ -309,6 +266,7 @@
    {
        label: "付款状态",
        prop: "statusName",
    width:110,
        dataType: "tag",
        formatType: (params) => {
            if (params == '未完成付款') {
@@ -320,35 +278,33 @@
            }
        },
    },
  {
    label: "发票号",
    prop: "invoiceNumber",
    width:200
  },
  {
    label: "发票金额(元)",
    prop: "invoiceAmount",
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
    {
        label: "产品大类",
        prop: "productCategory",
        showOverflowTooltip: true,
        width: 100
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        showOverflowTooltip: true,
        width: 150
    },
  {
    label: "已付款金额(元)",
    prop: "paymentAmountTotal",
    prop: "ticketsTotal",
    width: 120,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "待付款金额(元)",
    prop: "unPaymentAmountTotal",
    prop: "pendingTicketsTotal",
    width: 120,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "录入人",
    prop: "issUer",
  },
]);
const tableData = ref([]);
@@ -356,6 +312,7 @@
const selectedRows = ref([]);
const tableLoading = ref(false);
const childrenLoading = ref(false);
const forms = ref([]);
const userStore = useUserStore();
const page = reactive({
  current: 1,
@@ -376,8 +333,6 @@
    purchaseLedgerId: "",
    salesContractNo: "",
    supplierName: "",
    invoiceNumber: "",
    invoiceAmount: "",
    taxRate: "",
    currentPaymentAmount: "",
    paymentMethod: "",
@@ -394,9 +349,6 @@
      { required: true, message: "请输入", trigger: "blur" },
    ],
    paymentMethod: [{ required: true, message: "请选择", trigger: "change" }],
    invoiceNumber: [
      { required: true, message: "请选择采购合同号", trigger: "change" },
    ],
  },
});
const { form, rules } = toRefs(data);
@@ -409,11 +361,16 @@
    if (!normalized) return 'info';
    return normalized === '未完成付款' ? 'danger' : 'success';
};
const formattedNumber = (row, column, cellValue) => {
  const val = Number(cellValue ?? 0);
  return Number.isFinite(val) ? val.toFixed(2) : "0.00";
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable1 = (param) => {
  return proxy.summarizeTable(
    param,
    ["invoiceAmount", "paymentAmountTotal", "unPaymentAmountTotal"],
    ["ticketsTotal", "pendingTicketsTotal"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
@@ -442,8 +399,8 @@
  tableLoading.value = true;
  invoiceListPage({ ...searchForm, ...page }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.records;
        page.total = res.total;
    tableData.value = res.data.records;
        page.total = res.data.total;
        if (expandedRowKeys.value.length > 0) {
            const arr = []
            const index = tableData.value.findIndex(item => item.id === expandedRowKeys.value[0]);
@@ -501,48 +458,66 @@
};
// æ‰“开弹框
const openForm = (type, row) => {
  if (selectedRows.value.length !== 1) {
    proxy.$message.error("请选择一条发票数据");
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgError("请选择至少一条数据");
    return;
  }
    if (selectedRows.value[0].unPaymentAmountTotal == 0) {
        proxy.$message.warning("无需再付款");
        return;
    }
  operationType.value = type;
  form.value = {};
  form.value = { ...selectedRows.value[0] };
  form.value.ticketRegistrationId = selectedRows.value[0].id;
  form.value.id = null;
  // æŸ¥è¯¢é‡‡è´­åˆåŒå·
  form.value.registrationtDate = getCurrentDate();
  form.value.paymentDate = getCurrentDate();
  form.value.registrant = userStore.name;
  const validRows = selectedRows.value.filter((item) => Number(item.pendingTicketsTotal || 0) !== 0);
  if (validRows.length === 0) {
    proxy.$modal.msgWarning("所选记录均无需付款");
    return;
  }
  forms.value = validRows.map((row) => ({
    purchaseContractNumber: row.purchaseContractNumber || "",
    salesContractNo: row.salesContractNo || "",
    supplierName: row.supplierName || "",
    supplierId: row.supplierId,
    productCategory: row.productCategory || "",
    specificationModel: row.specificationModel || "",
    pendingTicketsTotal: Number(row.pendingTicketsTotal || 0),
    currentPaymentAmount: "",
    paymentMethod: "",
    paymentDate: "",
    registrant: userStore.nickName,
    registrationtDate: getCurrentDate(),
    ticketRegistrationId: row.id,
    purchaseLedgerId: row.salesLedgerId,
    salesLedgerProductId: row.id,
  }));
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
      } else {
        submitAdd();
      }
  if (forms.value.length === 0) {
    proxy.$modal.msgError("请选择付款记录");
    return;
  }
  for (let i = 0; i < forms.value.length; i++) {
    const item = forms.value[i];
    const pendingAmount = Number(item.pendingTicketsTotal || 0);
    const currentAmount = Number(item.currentPaymentAmount);
    if (!item.currentPaymentAmount && item.currentPaymentAmount !== 0) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·å¡«å†™ä»˜æ¬¾é‡‘额`);
      return;
    }
  });
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  paymentRegistrationAdd(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  paymentRegistrationEdit(form.value).then((res) => {
    if (currentAmount > pendingAmount) {
      proxy.$modal.msgError(
        `第 ${i + 1} æ¡ï¼šä»˜æ¬¾é‡‘额不能超过待付款金额(待付款:${pendingAmount.toFixed(
          2
        )})`
      );
      return;
    }
    if (!item.paymentMethod) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·é€‰æ‹©ä»˜æ¬¾æ–¹å¼`);
      return;
    }
    if (!item.paymentDate) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·é€‰æ‹©ä»˜æ¬¾æ—¥æœŸ`);
      return;
    }
  }
  paymentRegistrationAdd(forms.value).then(() => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
@@ -550,7 +525,7 @@
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  forms.value = [];
  dialogFormVisible.value = false;
};
// åˆ é™¤
@@ -562,7 +537,7 @@
  })
    .then(() => {
      tableLoading.value = true;
            delPaymentRegistration(row.id)
            delPaymentRegistration([row.id])
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
@@ -602,4 +577,9 @@
::v-deep(.el-checkbox__label) {
  font-weight: bold;
}
.empty-tip {
  text-align: center;
  padding: 20px 0;
  color: #909399;
}
</style>
src/views/procurementManagement/paymentHistory/index.vue
@@ -43,6 +43,13 @@
          æœç´¢
        </el-button>
        <el-button @click="handleExport">导出</el-button>
        <el-button
          type="danger"
          :disabled="selectedRows.length === 0"
          @click="handleBatchDelete"
        >
          æ‰¹é‡åˆ é™¤ ({{ selectedRows.length }})
        </el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
@@ -58,7 +65,18 @@
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      ></PIMTable>
      >
        <template #operation="{ row }">
          <el-button
            type="primary"
            link
            size="small"
            @click="handleDelete(row)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </PIMTable>
    </div>
  </div>
</template>
@@ -66,7 +84,9 @@
<script setup>
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import { paymentHistoryListPage } from "@/api/procurementManagement/paymentEntry.js";
import { ElMessageBox } from "element-plus";
import { paymentHistoryListPage} from "@/api/procurementManagement/paymentEntry.js";
import {delPaymentRegistration } from "@/api/procurementManagement/procurementInvoiceLedger.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
@@ -104,6 +124,13 @@
  {
    label: "登记日期",
    prop: "registrationtDate",
  },
  {
    label: "操作",
    dataType: "slot",
    slot: "operation",
    width: 100,
    align: "center",
  },
]);
const tableData = ref([]);
@@ -170,6 +197,62 @@
  getList();
};
// åˆ é™¤
const handleDelete = (row) => {
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      tableLoading.value = true;
      delPaymentRegistration([row.id])
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  ElMessageBox.confirm(
    `确定要删除选中的 ${selectedRows.value.length} æ¡æ•°æ®å—?`,
    "删除提示",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  )
    .then(() => {
      tableLoading.value = true;
      const ids = selectedRows.value.map((item) => item.id);
      delPaymentRegistration(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          selectedRows.value = [];
          getList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// å¯¼å‡º
const handleExport = () => {
  const { paymentDate, ...rest } = searchForm;
src/views/procurementManagement/paymentLedger/index.vue
@@ -43,7 +43,7 @@
            />
            <el-table-column label="供应商名称" prop="supplierName" />
            <el-table-column
              label="发票金额(元)"
              label="合同金额(元)"
              prop="invoiceAmount"
              show-overflow-tooltip
              :formatter="formattedNumber"
@@ -83,6 +83,7 @@
            :column="tableColumnSon"
            :tableData="originalTableDataSon"
            :isSelection="false"
            :isShowPagination="false"
            :tableLoading="tableLoadingSon"
            :isShowSummary="isShowSummarySon"
            :summaryMethod="summarizeMainTable1"
@@ -94,14 +95,6 @@
              </el-text>
            </template>
          </PIMTable>
          <pagination
            v-show="sonTotal > 0"
            :total="sonTotal"
            @pagination="sonPaginationSearch"
            :layout="page.layout"
            :page="sonPage.current"
            :limit="sonPage.size"
          />
        </div>
      </el-col>
    </el-row>
@@ -117,25 +110,6 @@
} from "@/api/procurementManagement/paymentLedger.js";
import Pagination from "../../../components/PIMTable/Pagination.vue";
const tableColumn = ref([
  {
    label: "供应商名称",
    prop: "supplierName",
    width:240
  },
  {
    label: "发票金额(元)",
    prop: "invoiceAmount",
  },
  {
    label: "付款金额(元)",
    prop: "paymentAmount",
  },
  {
    label: "应付金额(元)",
    prop: "payableAmount",
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const data = reactive({
@@ -164,11 +138,16 @@
const tableColumnSon = ref([
  {
    label: "发生日期",
    prop: "happenTime",
    prop: "paymentDate",
        width: 110,
  },
  {
    label: "发票金额(元)",
    label: "采购合同号",
    prop: "purchaseContractNumber",
        width: 150,
  },
  {
    label: "合同金额(元)",
    prop: "invoiceAmount",
        width: 200,
    formatData: (params) => {
@@ -177,7 +156,7 @@
  },
  {
    label: "付款金额(元)",
    prop: "currentPaymentAmount",
    prop: "paymentAmount",
        width: 200,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
@@ -214,7 +193,7 @@
const summarizeMainTable1 = (param) => {
  let summarizeTable = proxy.summarizeTable(
    param,
    ["invoiceAmount", "currentPaymentAmount"],
    ["invoiceAmount", "paymentAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
@@ -245,8 +224,6 @@
  paymentLedgerList({
    ...searchForm.value,
    ...page,
    detailPageNum: detailPageNum.value, // æ–°å¢ž
    detailPageSize: detailPageSize.value, // æ–°å¢ž
  }).then((res) => {
    let result = res.data;
    tableLoading.value = false;
src/views/procurementManagement/priceManagement/index.vue
@@ -62,7 +62,7 @@
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增价格' : '编辑价格'" width="600px">
    <FormDialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增价格' : '编辑价格'" :width="'600px'" :operation-type="dialogType" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
      <el-form :model="formData" label-width="120px">
        <el-form-item label="商品名称">
          <el-select v-model="formData.productName" placeholder="请选择商品" style="width: 100%">
@@ -102,15 +102,12 @@
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
    </FormDialog>
  </div>
</template>
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
src/views/procurementManagement/procurementLedger/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <el-table :data="tableData" border height="40vh">
      <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="150" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">下载</el-button>
          <el-button link type="primary" size="small" @click="lookFile(scope.row)">预览</el-button>
          <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import filePreview from '@/components/filePreview/index.vue'
import { delCommonFile } from '@/api/publicApi/commonFile.js'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
// åˆ é™¤é™„ä»¶
const handleDelete = (row) => {
  ElMessageBox.confirm(`确认删除附件"${row.name}"吗?`, '删除确认', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    delCommonFile([row.id]).then(() => {
      ElMessage.success('删除成功')
      // ä»Žåˆ—表中移除已删除的附件
      const index = tableData.value.findIndex(item => item.id === row.id)
      if (index !== -1) {
        tableData.value.splice(index, 1)
      }
    }).catch(() => {
      ElMessage.error('删除失败')
    })
  }).catch(() => {
    proxy.$modal.msg('已取消删除')
  })
}
defineExpose({
  open
})
</script>
<style></style>
src/views/procurementManagement/procurementLedger/index.vue
@@ -2,466 +2,442 @@
  <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-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>
            <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
            <el-button type="primary"
                       @click="handleQuery"> æœç´¢ </el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>
    <div class="table_list">
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
        <el-button type="primary" @click="openForm('add')">新增台账</el-button>
        <el-button type="success" @click="openScanAddDialog">扫码新增</el-button>
        <el-button type="primary"
                   @click="openForm('add')">新增台账</el-button>
        <el-button type="success"
                   @click="openScanAddDialog">扫码新增</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>
      <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 - 18.5em)"
          :row-class-name="tableRowClassName"
      >
        <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 - 19em)"
                :row-class-name="tableRowClassName">
        <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="quantity" />
              <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 :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="quantity" />
              <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="200"
            show-overflow-tooltip
        />
        <el-table-column
            label="销售合同号"
            prop="salesContractNo"
            width="200"
            show-overflow-tooltip
        />
        <el-table-column
            label="供应商名称"
            width="240"
            prop="supplierName"
            show-overflow-tooltip
        />
        <el-table-column label="订单状态" width="100" align="center">
        <el-table-column align="center"
                         label="序号"
                         type="index"
                         width="60" />
        <el-table-column label="采购合同号"
                         prop="purchaseContractNumber"
                         width="200"
                         show-overflow-tooltip />
        <el-table-column label="销售合同号"
                         prop="salesContractNo"
                         show-overflow-tooltip />
        <el-table-column label="供应商名称"
                         prop="supplierName"
                         show-overflow-tooltip />
        <el-table-column label="订单状态"
                         width="100"
                         align="center">
          <template #default="scope">
            <el-tag v-if="scope.row.isInvalid" type="danger" size="small">失效</el-tag>
            <el-tag v-else type="success" size="small">正常</el-tag>
            <el-tag v-if="scope.row.isInvalid"
                    type="danger"
                    size="small">失效</el-tag>
            <el-tag v-else
                    type="success"
                    size="small">正常</el-tag>
          </template>
        </el-table-column>
        <el-table-column
            label="项目名称"
            prop="projectName"
            width="420"
            show-overflow-tooltip
        />
        <el-table-column
            label="审批状态"
            prop="approvalStatus"
            width="200"
            show-overflow-tooltip
        >
        <el-table-column label="项目名称"
                         prop="projectName"
                         width="420"
                         show-overflow-tooltip />
        <el-table-column label="审批状态"
                         prop="approvalStatus"
                         width="200"
                         show-overflow-tooltip>
          <template #default="scope">
            <el-tag
                size="small"
            >
            <el-tag size="small">
              {{ approvalStatusText[scope.row.approvalStatus] || '未知状态' }}
            </el-tag>
          </template>
        </el-table-column>
        <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="recorderName"
            width="100"
            show-overflow-tooltip
        />
        <el-table-column
            label="录入日期"
            prop="entryDate"
            width="100"
            show-overflow-tooltip
        />
        <el-table-column
            fixed="right"
            label="操作"
            min-width="150"
            align="center"
        >
        <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="recorderName"
                         width="120"
                         show-overflow-tooltip />
        <el-table-column label="录入日期"
                         prop="entryDate"
                         width="100"
                         show-overflow-tooltip />
        <el-table-column fixed="right"
                         label="操作"
                         width="180"
                         align="center">
          <template #default="scope">
            <el-button
                link
                type="primary"
                size="small"
                @click="openForm('edit', scope.row)"
            >编辑</el-button
            >
            <el-button
                link
                type="success"
                size="small"
                @click="showQRCode(scope.row)"
            >生成二维码</el-button
            >
            <el-button link
                       type="primary"
                       size="small"
                       @click="openForm('edit', scope.row)">编辑</el-button>
            <el-button link
                       type="success"
                       size="small"
                       @click="showQRCode(scope.row)">生成二维码</el-button>
            <el-button link
                       type="primary"
                       size="small"
                       @click="downLoadFile(scope.row)">附件</el-button>
          </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>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增采购台账页面' : '编辑采购台账页面'"
        width="70%"
        @close="closeDia"
    >
      <el-form
          :model="form"
          label-width="140px"
          label-position="top"
          :rules="rules"
          ref="formRef"
      >
    <el-dialog v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增采购台账页面' : '编辑采购台账页面'"
               width="70%"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购合同号:" prop="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="请选择"
                  clearable
                  @change="salesLedgerChange"
              >
                <el-option
                    v-for="item in salesContractList"
                    :key="item.id"
                    :label="item.salesContractNo"
                    :value="item.id"
                />
            <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="请选择"
                  clearable
                  filterable
                  allow-create
              >
                <el-option
                    v-for="item in supplierList"
                    :key="item.id"
                    :label="item.supplierName"
                    :value="item.id"
                />
            <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" />
              </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="approverId">
              <el-select
                  v-model="form.approverId"
                  placeholder="请选择审批人"
                  clearable
              >
                <el-option
                    v-for="item in userList"
                    :key="item.userId"
                    :label="item.nickName"
                    :value="item.userId"
                />
            <el-form-item label="审批人:"
                          prop="approverId">
              <el-select v-model="form.approverId"
                         placeholder="请选择审批人"
                         clearable>
                <el-option v-for="item in userList"
                           :key="item.userId"
                           :label="item.nickName"
                           :value="item.userId" />
              </el-select>
            </el-form-item>
            <el-form-item label="录入人:" prop="recorderId" v-show="false">
              <el-select
                  v-model="form.recorderId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                    v-for="item in userList"
                    :key="item.userId"
                    :label="item.nickName"
                    :value="item.userId"
                />
            <el-form-item label="录入人:"
                          prop="recorderId"
                          v-show="false">
              <el-select v-model="form.recorderId"
                         placeholder="请选择"
                         clearable
                         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: 220px; margin: 20px 0;" v-if="operationType === 'add'">
            <el-select
                filterable
                allow-create
                :reserve-keyword="true"
                :default-first-option="false"
                v-model="templateName"
                :input-value="filterInputValue"
                @filter-change="onTemplateFilterChange"
                @change="onTemplateChange"
                style="width: 180px; border-right: none; border-radius: 4px 0 0 4px;"
                placeholder="请选择"
                class="no-arrow-select"
            >
              <el-option
                  v-for="item in templateList"
                  :key="item.value"
                  :label="item.templateName"
                  :value="item.templateName"
              ></el-option>
          <div class="select-button-group"
               style="width: 220px; margin: 20px 0;"
               v-if="operationType === 'add'">
            <el-select filterable
                       allow-create
                       :reserve-keyword="true"
                       :default-first-option="false"
                       v-model="templateName"
                       :input-value="filterInputValue"
                       @filter-change="onTemplateFilterChange"
                       @change="onTemplateChange"
                       style="width: 180px; border-right: none; border-radius: 4px 0 0 4px;"
                       placeholder="请选择"
                       class="no-arrow-select">
              <el-option v-for="item in templateList"
                         :key="item.value"
                         :label="item.templateName"
                         :value="item.templateName"></el-option>
            </el-select>
            <!-- æŒ‰é’®ï¼šä¸Ž Select é«˜åº¦åŒ¹é…ï¼ŒåŽ»æŽ‰å·¦ä¾§è¾¹æ¡†ï¼Œæ— ç¼è¡”æŽ¥ -->
            <el-button
                size="small"
                style="height: 32px; border-radius: 0 4px 4px 0; margin-left: -1px;"
                @click="handleButtonClick"
                :disabled="!templateName || templateName.trim() === '' || isTemplateNameDuplicate"
            >
            <el-button size="small"
                       style="height: 32px; border-radius: 0 4px 4px 0; margin-left: -1px;"
                       @click="handleButtonClick"
                       :disabled="!templateName || templateName.trim() === '' || isTemplateNameDuplicate">
              ä¿å­˜
            </el-button>
          </div>
        </el-row>
        <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
              fixed="right"
              label="操作"
              min-width="60"
              align="center"
          >
        <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">
            <template #default="scope">
              <el-button
                  link
                  type="primary"
                  size="small"
                  @click="openProductForm('edit', scope.row, scope.$index)"
              >编辑</el-button
              >
              <el-tag :type="scope.row.isChecked ? 'success' : 'info'">
                {{ scope.row.isChecked ? '是' : '否' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column fixed="right"
                           label="操作"
                           min-width="60"
                           align="center">
            <template #default="scope">
              <el-button link
                         type="primary"
                         size="small"
                         @click="openProductForm('edit', scope.row, scope.$index)">编辑</el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注·:" prop="remark">
              <el-input
                  v-model="form.remark"
                  placeholder="请输入"
                  clearable
                  type="textarea"
                  :rows="2"
              />
            <el-form-item label="备注·:"
                          prop="remark">
              <el-input v-model="form.remark"
                        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="remark">
              <el-upload
                  v-model:file-list="fileList"
                  :action="upload.url"
                  multiple
                  ref="fileUpload"
                  auto-upload
                  :headers="upload.headers"
                  :before-upload="handleBeforeUpload"
                  :on-error="handleUploadError"
                  :on-success="handleUploadSuccess"
                  :on-remove="handleRemove"
              >
            <el-form-item label="附件材料:"
                          prop="remark">
              <el-upload v-model:file-list="fileList"
                         :action="upload.url"
                         multiple
                         ref="fileUpload"
                         auto-upload
                         :headers="upload.headers"
                         :before-upload="handleBeforeUpload"
                         :on-error="handleUploadError"
                         :on-success="handleUploadSuccess"
                         :on-remove="handleRemove">
                <el-button type="primary">上传</el-button>
                <template #tip>
                  <div class="el-upload__tip">
@@ -476,335 +452,332 @@
      </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
        v-model="productFormVisible"
        :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
        width="40%"
        @close="closeProductDia"
    >
      <el-form
          :model="productForm"
          label-width="140px"
          label-position="top"
          :rules="productRules"
          ref="productFormRef"
      >
    <el-dialog v-model="productFormVisible"
               :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
               width="40%"
               @close="closeProductDia">
      <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
                  check-strictly
                  @change="getModels"
                  :data="productOptions"
                  :render-after-expand="false"
                  style="width: 100%"
              />
            <el-form-item label="产品大类:"
                          prop="productId">
              <el-tree-select v-model="productForm.productId"
                              placeholder="请选择"
                              clearable
                              check-strictly
                              @change="getModels"
                              :data="productOptions"
                              :render-after-expand="false"
                              style="width: 100%" />
            </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="请选择"
                  clearable
                  @change="getProductModel"
              >
                <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="productForm.productModelId"
                         placeholder="请选择"
                         clearable
                         @change="getProductModel">
                <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="unit">
              <el-input
                  v-model="productForm.unit"
                  placeholder="请输入"
                  clearable
              />
            <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 label="1" value="1" />
                <el-option label="6" value="6" />
                <el-option label="13" value="13" />
            <el-form-item label="税率(%):"
                          prop="taxRate">
              <el-select v-model="productForm.taxRate"
                         placeholder="请选择"
                         clearable
                         @change="mathNum">
                <el-option label="1"
                           value="1" />
                <el-option label="6"
                           value="6" />
                <el-option label="13"
                           value="13" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
              <el-input-number
                  v-model="productForm.taxInclusiveUnitPrice"
                  :precision="2"
                  :step="0.1"
                  clearable
                  style="width: 100%"
                  @change="mathNum"
              />
            <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"
                  style="width: 100%"
                  v-model="productForm.quantity"
                  placeholder="请输入"
                  @change="mathNum"
              />
            <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"
                  clearable
                  style="width: 100%"
                  @change="reverseMathNum('taxInclusiveTotalPrice')"
              />
            <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
                  v-model="productForm.taxExclusiveTotalPrice"
                  @change="reverseMathNum('taxExclusiveTotalPrice')"
              />
            <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-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"
                  clearable
                  style="width: 100%"
              />
            <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-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>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitProduct">确认</el-button>
          <el-button type="primary"
                     @click="submitProduct">确认</el-button>
          <el-button @click="closeProductDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- äºŒç»´ç æ˜¾ç¤ºå¯¹è¯æ¡† -->
    <el-dialog
        v-model="qrCodeDialogVisible"
        title="采购合同号二维码"
        width="400px"
        center
    >
    <el-dialog v-model="qrCodeDialogVisible"
               title="采购合同号二维码"
               width="400px"
               center>
      <div style="text-align: center;">
        <img :src="qrCodeUrl" alt="二维码" style="width:200px;height:200px;" />
        <img :src="qrCodeUrl"
             alt="二维码"
             style="width:200px;height:200px;" />
        <div style="margin: 20px;">
          <el-button type="primary" @click="downloadQRCode">下载二维码图片</el-button>
          <el-button type="primary"
                     @click="downloadQRCode">下载二维码图片</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- æ‰«ç æ–°å¢žå¯¹è¯æ¡† -->
    <el-dialog
        v-model="scanAddDialogVisible"
        title="扫码新增采购台账"
        width="70%"
        @close="closeScanAddDialog"
    >
      <el-form
          :model="scanAddForm"
          label-width="140px"
          label-position="top"
          :rules="scanAddRules"
          ref="scanAddFormRef"
      >
    <el-dialog v-model="scanAddDialogVisible"
               title="扫码新增采购台账"
               width="70%"
               @close="closeScanAddDialog">
      <el-form :model="scanAddForm"
               label-width="140px"
               label-position="top"
               :rules="scanAddRules"
               ref="scanAddFormRef">
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码内容:">
              <el-input
                  v-model="scanAddForm.scanContent"
                  type="textarea"
                  :rows="3"
                  placeholder="请扫描二维码或手动输入采购合同信息"
                  @input="parseScanContent"
              />
              <el-input v-model="scanAddForm.scanContent"
                        type="textarea"
                        :rows="3"
                        placeholder="请扫描二维码或手动输入采购合同信息"
                        @input="parseScanContent" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:" prop="purchaseContractNumber">
              <el-input
                  v-model="scanAddForm.purchaseContractNumber"
                  placeholder="请输入"
                  clearable
              />
            <el-form-item label="采购合同号:"
                          prop="purchaseContractNumber">
              <el-input v-model="scanAddForm.purchaseContractNumber"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                  v-model="scanAddForm.supplierName"
                  placeholder="请输入"
                  clearable
              />
            <el-form-item label="供应商名称:"
                          prop="supplierName">
              <el-input v-model="scanAddForm.supplierName"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input
                  v-model="scanAddForm.projectName"
                  placeholder="请输入"
                  clearable
              />
            <el-form-item label="项目名称:"
                          prop="projectName">
              <el-input v-model="scanAddForm.projectName"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="合同金额(元):" prop="contractAmount">
              <el-input-number
                  v-model="scanAddForm.contractAmount"
                  :precision="2"
                  :step="0.1"
                  clearable
                  style="width: 100%"
                  placeholder="请输入"
              />
            <el-form-item label="合同金额(元):"
                          prop="contractAmount">
              <el-input-number v-model="scanAddForm.contractAmount"
                               :precision="2"
                               :step="0.1"
                               clearable
                               style="width: 100%"
                               placeholder="请输入" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款方式:">
              <el-input
                  v-model="scanAddForm.paymentMethod"
                  placeholder="请输入"
                  clearable
              />
              <el-input v-model="scanAddForm.paymentMethod"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入人:">
              <el-input v-model="scanAddForm.recorderName" disabled />
              <el-input v-model="scanAddForm.recorderName"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注:">
              <el-input
                  v-model="scanAddForm.remark"
                  type="textarea"
                  :rows="2"
                  placeholder="请输入备注信息"
                  clearable
              />
              <el-input v-model="scanAddForm.remark"
                        type="textarea"
                        :rows="2"
                        placeholder="请输入备注信息"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitScanAdd">确认新增</el-button>
          <el-button type="primary"
                     @click="submitScanAdd">确认新增</el-button>
          <el-button @click="closeScanAddDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰«ç ç™»è®°å¯¹è¯æ¡† -->
    <el-dialog
        v-model="scanDialogVisible"
        title="扫码登记"
        width="60%"
        @close="closeScanDialog"
    >
      <el-form
          :model="scanForm"
          label-width="120px"
          label-position="left"
          :rules="scanRules"
          ref="scanFormRef"
      >
    <el-dialog v-model="scanDialogVisible"
               title="扫码登记"
               width="60%"
               @close="closeScanDialog">
      <el-form :model="scanForm"
               label-width="120px"
               label-position="left"
               :rules="scanRules"
               ref="scanFormRef">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:">
              <el-input v-model="scanForm.purchaseContractNumber" disabled />
              <el-input v-model="scanForm.purchaseContractNumber"
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:">
              <el-input v-model="scanForm.supplierName" disabled />
              <el-input v-model="scanForm.supplierName"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:">
              <el-input v-model="scanForm.projectName" disabled />
              <el-input v-model="scanForm.projectName"
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="扫码时间:">
              <el-input v-model="scanForm.scanTime" disabled />
              <el-input v-model="scanForm.scanTime"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="扫码人:">
              <el-input v-model="scanForm.scannerName" disabled />
              <el-input v-model="scanForm.scannerName"
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -818,30 +791,40 @@
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码备注:">
              <el-input
                  v-model="scanForm.scanRemark"
                  type="textarea"
                  :rows="3"
                  placeholder="请输入扫码备注信息"
              />
              <el-input v-model="scanForm.scanRemark"
                        type="textarea"
                        :rows="3"
                        placeholder="请输入扫码备注信息" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码记录:">
              <el-table :data="scanRecords" border style="width: 100%">
                <el-table-column label="序号" type="index" width="60" align="center" />
                <el-table-column label="扫码时间" prop="scanTime" width="180" />
                <el-table-column label="扫码人" prop="scannerName" width="120" />
                <el-table-column label="扫码状态" prop="scanStatus" width="100">
              <el-table :data="scanRecords"
                        border
                        style="width: 100%">
                <el-table-column label="序号"
                                 type="index"
                                 width="60"
                                 align="center" />
                <el-table-column label="扫码时间"
                                 prop="scanTime"
                                 width="180" />
                <el-table-column label="扫码人"
                                 prop="scannerName"
                                 width="120" />
                <el-table-column label="扫码状态"
                                 prop="scanStatus"
                                 width="100">
                  <template #default="scope">
                    <el-tag :type="scope.row.scanStatus === '已扫码' ? 'success' : 'warning'">
                      {{ scope.row.scanStatus }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="备注" prop="scanRemark" />
                <el-table-column label="备注"
                                 prop="scanRemark" />
              </el-table>
            </el-form-item>
          </el-col>
@@ -849,362 +832,382 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitScan">确认扫码</el-button>
          <el-button type="primary"
                     @click="submitScan">确认扫码</el-button>
          <el-button @click="closeScanDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <FileList ref="fileListRef" />
  </div>
</template>
<script setup>
import {getToken} from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import {getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs} from "vue";
import {Search} from "@element-plus/icons-vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {userListNoPage} from "@/api/system/user.js";
import {
  addOrUpdateSalesLedgerProduct,
  delLedgerFile,
  delProduct,
  getProductInfoByContractNo,
  getSalesLedgerWithProducts,
} from "@/api/salesManagement/salesLedger.js";
import {
  addOrEditPurchase,
  addPurchaseTemplate,
  createPurchaseNo,
  delPurchase,
  getOptions,
  getPurchaseById,
  getPurchaseTemplateList,
  getSalesNo,
  productList,
  purchaseListPage
} from "@/api/procurementManagement/procurementLedger.js";
import useFormData from "@/hooks/useFormData.js";
import QRCode from "qrcode";
import useUserStore from "@/store/modules/user";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import dayjs from "dayjs";
import { getCurrentDate } from "@/utils/index.js";
  import { getToken } from "@/utils/auth";
  import pagination from "@/components/PIMTable/Pagination.vue";
  import {
    ref,
    onMounted,
    reactive,
    toRefs,
    getCurrentInstance,
    nextTick,
  } from "vue";
  import { Search } from "@element-plus/icons-vue";
  import { ElMessageBox, ElMessage } from "element-plus";
  import { userListNoPage } from "@/api/system/user.js";
  import FileList from "./fileList.vue";
  import {
    getSalesLedgerWithProducts,
    addOrUpdateSalesLedgerProduct,
    delProduct,
    delLedgerFile,
    getProductInfoByContractNo,
  } from "@/api/salesManagement/salesLedger.js";
  import {
    addOrEditPurchase,
    addPurchaseTemplate,
    createPurchaseNo,
    delPurchase,
    getSalesNo,
    purchaseListPage,
    productList,
    getPurchaseById,
    getOptions,
    getPurchaseTemplateList,
  } from "@/api/procurementManagement/procurementLedger.js";
  import useFormData from "@/hooks/useFormData.js";
  import QRCode from "qrcode";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const productData = ref([]);
const selectedRows = ref([]);
const productSelectedRows = ref([]);
const modelOptions = ref([]);
const userList = ref([]);
const productOptions = ref([]);
const salesContractList = ref([]);
const supplierList = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const fileList = ref([]);
  const { proxy } = getCurrentInstance();
  const tableData = ref([]);
  const productData = ref([]);
  const selectedRows = ref([]);
  const productSelectedRows = ref([]);
  const modelOptions = ref([]);
  const userList = ref([]);
  const productOptions = ref([]);
  const salesContractList = ref([]);
  const supplierList = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
  });
  const total = ref(0);
  const fileList = ref([]);
  import useUserStore from "@/store/modules/user";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
  import dayjs from "dayjs";
const userStore = useUserStore();
  const userStore = useUserStore();
// äºŒç»´ç ç›¸å…³å˜é‡
const qrCodeDialogVisible = ref(false);
const qrCodeUrl = ref("");
  // äºŒç»´ç ç›¸å…³å˜é‡
  const qrCodeDialogVisible = ref(false);
  const qrCodeUrl = ref("");
// è®¢å•审批状态显示文本
const approvalStatusText = {
  0: '审批中',
  1: '审批通过',
  2: '审批失败'
};
  // è®¢å•审批状态显示文本
  const approvalStatusText = {
    0: "审批中",
    1: "审批通过",
    2: "审批失败",
  };
const templateName = ref('');
const filterInputValue = ref('');
const templateList = ref([]);
const isTemplateNameDuplicate = ref(false); // æ ‡è®°æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
// æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
const checkTemplateNameDuplicate = (name) => {
  if (!name || name.trim() === '') {
    isTemplateNameDuplicate.value = false;
    return false;
  }
  const isDuplicate = templateList.value.some(item => item.templateName === name.trim());
  isTemplateNameDuplicate.value = isDuplicate;
  return isDuplicate;
};
// é˜²æŠ–定时器
let duplicateCheckTimer = null;
const onTemplateFilterChange = (val) => {
  filterInputValue.value = val ?? '';
  // æ¸…除之前的定时器
  if (duplicateCheckTimer) {
    clearTimeout(duplicateCheckTimer);
  }
  // å®žæ—¶æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤ï¼ˆé˜²æŠ–处理,避免频繁提示)
  if (val && val.trim()) {
    duplicateCheckTimer = setTimeout(() => {
      const isDuplicate = checkTemplateNameDuplicate(val);
      if (isDuplicate) {
        ElMessage({
          message: '模板名称已存在,请更换模板名称',
          type: 'warning',
          duration: 2000
        });
      }
    }, 300); // 300ms é˜²æŠ–
  } else {
    isTemplateNameDuplicate.value = false;
  }
};
// allow-create æ—¶ï¼Œè¾“入不存在的内容会作为 string å€¼è¿”回;这里同步回输入框以确保文字不丢
const onTemplateChange = async (val) => {
  if (typeof val === 'string') {
    filterInputValue.value = val;
    // é€‰æ‹©æˆ–输入时检查重复
    checkTemplateNameDuplicate(val);
  }
  // è¿‡æ»¤æ•°æ®ï¼ŒæŸ¥æ‰¾åŒ¹é…çš„æ¨¡æ¿
  const matchedTemplate = templateList.value.find(item => item.templateName === val);
  if (matchedTemplate?.id) {
    // å¦‚果找到模板,加载模板数据
    form.value = {
      ...form.value,
      ...matchedTemplate,
    };
    productData.value = matchedTemplate.productData || [];
    // ç”Ÿæˆæ–°çš„采购合同号
    try {
      const res = await createPurchaseNo();
      if (res?.data) {
        form.value.purchaseContractNumber = res.data;
      }
    } catch (error) {
      console.error('生成采购合同号失败:', error);
    }
  } else {
    // å¦‚果没有找到模板,重置表单(保持当前表单状态)
    const currentFormData = { ...form.value };
    const currentProductData = [...productData.value];
    // å¦‚果对话框未打开,先打开
    if (!dialogFormVisible.value) {
      operationType.value = 'add';
      dialogFormVisible.value = true;
    }
    // ç­‰å¾…下一个 tick åŽæ¢å¤æ•°æ®
    await nextTick();
    form.value = {
      ...form.value,
      ...currentFormData,
    };
    productData.value = currentProductData;
  }
};
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "", // ä¾›åº”商名称
    purchaseContractNumber: "", // é‡‡è´­åˆåŒç¼–号
    salesContractNo: "", // é”€å”®åˆåŒç¼–号
    projectName: "", // é¡¹ç›®åç§°
    entryDate: null, // å½•入日期
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
  form: {
    purchaseContractNumber: "",
    salesLedgerId: "",
    projectName: "",
    recorderId: "",
    entryDate: "",
    productData: [],
    supplierName: "",
    supplierId: "",
    paymentMethod: "",
    executionDate: "",
  },
  rules: {
    purchaseContractNumber: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    projectName: [{ required: true, message: "请输入", trigger: "blur" }],
    supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
    entryDate: [{ required: true, message: "请选择", trigger: "change" }],
    approverId:[{ required: true, message: "请选择审批人", trigger: "change" }],
    executionDate: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const {  form, rules } = toRefs(data);
const { form: searchForm } = useFormData(data.searchForm);
// äº§å“è¡¨å•弹框数据
const productFormVisible = ref(false);
const productOperationType = ref("");
const productOperationIndex = ref("");
const currentId = ref("");
const productFormData = reactive({
  productForm: {
    productId: "",
    productCategory: "",
    productModelId: "",
    specificationModel: "",
    unit: "",
    quantity: "",
    taxInclusiveUnitPrice: "",
    taxRate: "",
    taxInclusiveTotalPrice: "",
    taxExclusiveTotalPrice: "",
    invoiceType: "",
    warnNum: "",
  },
  productRules: {
    productId: [{ 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: false, message: "请选择", trigger: "change" }],
    taxInclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxExclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const { productForm, productRules } = toRefs(productFormData);
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
const changeDaterange = (value) => {
  if (value) {
    searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    searchForm.entryDateStart = undefined;
    searchForm.entryDateEnd = undefined;
  }
  handleQuery();
};
const formattedNumber = (row, column, cellValue) => {
  if (cellValue === null || cellValue === undefined || cellValue === '') {
    return '0.00';
  }
  const num = parseFloat(cellValue);
  if (isNaN(num)) {
    return '0.00';
  }
  return num.toFixed(2);
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
// ä¿å­˜æ¨¡æ¿
const handleButtonClick = async () => {
  // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦ä¸ºç©º
  if (!templateName.value || templateName.value.trim() === '') {
    ElMessage({
      message: '请输入模板名称',
      type: 'warning',
    });
    return;
  }
  const templateName = ref("");
  const filterInputValue = ref("");
  const templateList = ref([]);
  const isTemplateNameDuplicate = ref(false); // æ ‡è®°æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
  // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
  const isDuplicate = checkTemplateNameDuplicate(templateName.value);
  if (isDuplicate) {
    ElMessage({
      message: '模板名称已存在,请更换模板名称',
      type: 'warning',
    });
    return;
  }
  // æ£€æŸ¥ä¾›åº”商是否选择
  if (!form.value.supplierId) {
    ElMessage({
      message: '请先选择供应商',
      type: 'warning',
    });
    return;
  }
  // æ£€æŸ¥æ˜¯å¦æœ‰äº§å“æ•°æ®
  // if (!productData.value || productData.value.length === 0) {
  //   ElMessage({
  //     message: '请先添加产品信息',
  //     type: 'warning',
  //   });
  //   return;
  // }
  try {
    let params = {
      productData: proxy.HaveJson(productData.value),
      supplierId: form.value.supplierId,
      paymentMethod: form.value.paymentMethod,
      recorderId: form.value.recorderId,
      approverId: form.value.approverId,
      templateName: templateName.value.trim()
    };
    console.log(params);
    let res = await addPurchaseTemplate(params);
    if (res && res.code === 200) {
      ElMessage({
        message: '模板保存成功',
        type: 'success',
      });
      // ä¿å­˜æˆåŠŸåŽé‡æ–°èŽ·å–æ¨¡æ¿åˆ—è¡¨
      await getTemplateList();
      // æ¸…空模板名称输入
      templateName.value = '';
      filterInputValue.value = '';
  const checkTemplateNameDuplicate = name => {
    if (!name || name.trim() === "") {
      isTemplateNameDuplicate.value = false;
      return false;
    }
    const isDuplicate = templateList.value.some(
      item => item.templateName === name.trim()
    );
    isTemplateNameDuplicate.value = isDuplicate;
    return isDuplicate;
  };
  // é˜²æŠ–定时器
  let duplicateCheckTimer = null;
  const onTemplateFilterChange = val => {
    filterInputValue.value = val ?? "";
    // æ¸…除之前的定时器
    if (duplicateCheckTimer) {
      clearTimeout(duplicateCheckTimer);
    }
    // å®žæ—¶æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤ï¼ˆé˜²æŠ–处理,避免频繁提示)
    if (val && val.trim()) {
      duplicateCheckTimer = setTimeout(() => {
        const isDuplicate = checkTemplateNameDuplicate(val);
        if (isDuplicate) {
          ElMessage({
            message: "模板名称已存在,请更换模板名称",
            type: "warning",
            duration: 2000,
          });
        }
      }, 300); // 300ms é˜²æŠ–
    } else {
      isTemplateNameDuplicate.value = false;
    }
  };
  // allow-create æ—¶ï¼Œè¾“入不存在的内容会作为 string å€¼è¿”回;这里同步回输入框以确保文字不丢
  const onTemplateChange = async val => {
    if (typeof val === "string") {
      filterInputValue.value = val;
      // é€‰æ‹©æˆ–输入时检查重复
      checkTemplateNameDuplicate(val);
    }
    // è¿‡æ»¤æ•°æ®ï¼ŒæŸ¥æ‰¾åŒ¹é…çš„æ¨¡æ¿
    const matchedTemplate = templateList.value.find(
      item => item.templateName === val
    );
    if (matchedTemplate?.id) {
      // å¦‚果找到模板,加载模板数据
      form.value = {
        ...form.value,
        ...matchedTemplate,
      };
      productData.value = matchedTemplate.productData || [];
      // ç”Ÿæˆæ–°çš„采购合同号
      try {
        const res = await createPurchaseNo();
        if (res?.data) {
          form.value.purchaseContractNumber = res.data;
        }
      } catch (error) {
        console.error("生成采购合同号失败:", error);
      }
    } else {
      // å¦‚果没有找到模板,重置表单(保持当前表单状态)
      const currentFormData = { ...form.value };
      const currentProductData = [...productData.value];
      // å¦‚果对话框未打开,先打开
      if (!dialogFormVisible.value) {
        operationType.value = "add";
        dialogFormVisible.value = true;
      }
      // ç­‰å¾…下一个 tick åŽæ¢å¤æ•°æ®
      await nextTick();
      form.value = {
        ...form.value,
        ...currentFormData,
      };
      productData.value = currentProductData;
    }
  };
  // ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
  const operationType = ref("");
  const dialogFormVisible = ref(false);
  const data = reactive({
    searchForm: {
      supplierName: "", // ä¾›åº”商名称
      purchaseContractNumber: "", // é‡‡è´­åˆåŒç¼–号
      salesContractNo: "", // é”€å”®åˆåŒç¼–号
      projectName: "", // é¡¹ç›®åç§°
      entryDate: null, // å½•入日期
      entryDateStart: undefined,
      entryDateEnd: undefined,
    },
    form: {
      purchaseContractNumber: "",
      salesLedgerId: "",
      projectName: "",
      recorderId: "",
      entryDate: "",
      productData: [],
      supplierName: "",
      supplierId: "",
      paymentMethod: "",
      executionDate: "",
      isChecked: true,
    },
    rules: {
      purchaseContractNumber: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      approverId: [
        { required: true, message: "请选择审批人", trigger: "change" },
      ],
      projectName: [
        { required: true, message: "请输入项目名称", trigger: "blur" },
      ],
      supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
      entryDate: [{ required: true, message: "请选择", trigger: "change" }],
      executionDate: [{ required: true, message: "请选择", trigger: "change" }],
    },
  });
  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 productOperationType = ref("");
  const productOperationIndex = ref("");
  const currentId = ref("");
  const productFormData = reactive({
    productForm: {
      productId: "",
      productCategory: "",
      productModelId: "",
      specificationModel: "",
      unit: "",
      quantity: "",
      taxInclusiveUnitPrice: "",
      taxRate: "",
      taxInclusiveTotalPrice: "",
      taxExclusiveTotalPrice: "",
      invoiceType: "",
      warnNum: "",
      isChecked: true,
    },
    productRules: {
      productId: [{ 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" }],
      taxInclusiveTotalPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      taxExclusiveTotalPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
      isChecked: [{ required: true, message: "请选择", trigger: "change" }],
    },
  });
  const { productForm, productRules } = toRefs(productFormData);
  const upload = reactive({
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
  });
  const changeDaterange = value => {
    if (value) {
      searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
      searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
    } else {
      searchForm.entryDateStart = undefined;
      searchForm.entryDateEnd = undefined;
    }
    handleQuery();
  };
  const formattedNumber = (row, column, cellValue) => {
    return parseFloat(cellValue).toFixed(2);
  };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  // ä¿å­˜æ¨¡æ¿
  const handleButtonClick = async () => {
    // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦ä¸ºç©º
    if (!templateName.value || templateName.value.trim() === "") {
      ElMessage({
        message: res?.msg || '模板保存失败',
        type: 'error',
        message: "请输入模板名称",
        type: "warning",
      });
      return;
    }
    // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
    const isDuplicate = checkTemplateNameDuplicate(templateName.value);
    if (isDuplicate) {
      ElMessage({
        message: "模板名称已存在,请更换模板名称",
        type: "warning",
      });
      return;
    }
    // æ£€æŸ¥ä¾›åº”商是否选择
    if (!form.value.supplierId) {
      ElMessage({
        message: "请先选择供应商",
        type: "warning",
      });
      return;
    }
    // æ£€æŸ¥æ˜¯å¦æœ‰äº§å“æ•°æ®
    // if (!productData.value || productData.value.length === 0) {
    //   ElMessage({
    //     message: '请先添加产品信息',
    //     type: 'warning',
    //   });
    //   return;
    // }
    try {
      let params = {
        productData: proxy.HaveJson(productData.value),
        supplierId: form.value.supplierId,
        paymentMethod: form.value.paymentMethod,
        recorderId: form.value.recorderId,
        approverId: form.value.approverId,
        templateName: templateName.value.trim(),
      };
      console.log(params);
      let res = await addPurchaseTemplate(params);
      if (res && res.code === 200) {
        ElMessage({
          message: "模板保存成功",
          type: "success",
        });
        // ä¿å­˜æˆåŠŸåŽé‡æ–°èŽ·å–æ¨¡æ¿åˆ—è¡¨
        await getTemplateList();
        // æ¸…空模板名称输入
        templateName.value = "";
        filterInputValue.value = "";
        isTemplateNameDuplicate.value = false;
      } else {
        ElMessage({
          message: res?.msg || "模板保存失败",
          type: "error",
        });
      }
    } catch (error) {
      console.error("保存模板失败:", error);
      ElMessage({
        message: "模板保存失败,请稍后重试",
        type: "error",
      });
    }
  } catch (error) {
    console.error('保存模板失败:', error);
    ElMessage({
      message: '模板保存失败,请稍后重试',
      type: 'error',
    });
  }
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  return proxy.summarizeTable(
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeChildrenTable = param => {
    return proxy.summarizeTable(
      param,
      [
        "taxInclusiveUnitPrice",
@@ -1219,27 +1222,26 @@
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
  );
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const { entryDate, ...rest } = searchForm;
  purchaseListPage({ ...rest, ...page })
      .then((res) => {
    );
  };
  const paginationChange = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    const { entryDate, ...rest } = searchForm;
    purchaseListPage({ ...rest, ...page })
      .then(res => {
        tableLoading.value = false;
        // tableData.value = res.data.records;
        // å¤„理数据,添加失效状态标记
        tableData.value = res.data.records.map(record => ({
          ...record,
          isInvalid: record.isWhite === 1
          isInvalid: record.isWhite === 1,
        }));
        // åˆå§‹åŒ–子数据数组
        tableData.value.forEach((item) => {
        tableData.value.forEach(item => {
          item.children = [];
        });
        total.value = res.data.total;
@@ -1248,742 +1250,758 @@
      .catch(() => {
        tableLoading.value = false;
      });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const productSelected = (selectedRows) => {
  productSelectedRows.value = selectedRows;
};
const expandedRowKeys = ref([]);
// å±•开行
const expandChange = async (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      const res = await productList({ salesLedgerId: row.id, type: 2 });
      const index = tableData.value.findIndex((item) => item.id === row.id);
      if (index > -1) {
        tableData.value[index].children = res.data || [];
        expandedRowKeys.value.push(row.id);
      }
    } catch (error) {
      console.error('加载产品列表失败:', error);
      proxy.$modal.msgError('加载产品列表失败');
      // å±•开失败时,移除展开状态
      const index = expandedRows.findIndex(item => item.id === row.id);
      if (index > -1) {
        expandedRows.splice(index, 1);
      }
    }
  } else {
    expandedRowKeys.value = [];
  }
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ["contractAmount"]);
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeProTable = (param) => {
  return proxy.summarizeTable(param, [
    "taxInclusiveUnitPrice",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// æ‰“开弹框
const openForm = async (type, row) => {
  await getTemplateList()
  operationType.value = type;
  form.value = {};
  productData.value = [];
  fileList.value = [];
  templateName.value = '';
  filterInputValue.value = '';
  isTemplateNameDuplicate.value = false;
  try {
    // å¹¶è¡ŒåŠ è½½åŸºç¡€æ•°æ®
    const [userRes, salesRes, supplierRes] = await Promise.all([
      userListNoPage(),
      getSalesNo(),
      getOptions()
    ]);
    userList.value = userRes.data || [];
    salesContractList.value = salesRes || [];
    // ä¾›åº”商过滤出isWhite=0 çš„æ•°æ®
    supplierList.value = (supplierRes.data || []).filter((item) => item.isWhite === 0);
    // è®¾ç½®é»˜è®¤å€¼
    form.value.recorderId = userStore.id;
    form.value.entryDate = getCurrentDate();
    if (type === "add") {
      // æ–°å¢žæ—¶ç”Ÿæˆé‡‡è´­åˆåŒå·
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  const productSelected = selectedRows => {
    productSelectedRows.value = selectedRows;
  };
  const expandedRowKeys = ref([]);
  // å±•开行
  const expandChange = async (row, expandedRows) => {
    if (expandedRows.length > 0) {
      expandedRowKeys.value = [];
      try {
        const purchaseNoRes = await createPurchaseNo();
        if (purchaseNoRes?.data) {
          form.value.purchaseContractNumber = purchaseNoRes.data;
        const res = await productList({ salesLedgerId: row.id, type: 2 });
        const index = tableData.value.findIndex(item => item.id === row.id);
        if (index > -1) {
          tableData.value[index].children = res.data || [];
          expandedRowKeys.value.push(row.id);
        }
      } catch (error) {
        console.error('生成采购合同号失败:', error);
        proxy.$modal.msgWarning('生成采购合同号失败');
        console.error("加载产品列表失败:", error);
        proxy.$modal.msgError("加载产品列表失败");
        // å±•开失败时,移除展开状态
        const index = expandedRows.findIndex(item => item.id === row.id);
        if (index > -1) {
          expandedRows.splice(index, 1);
        }
      }
    } else if (type === "edit" && row?.id) {
      // ç¼–辑时加载数据
      currentId.value = row.id;
      try {
        const purchaseRes = await getPurchaseById({ id: row.id, type: 2 });
        form.value = { ...purchaseRes };
        productData.value = purchaseRes.productData || [];
        fileList.value = purchaseRes.salesLedgerFiles || [];
      } catch (error) {
        console.error('加载采购台账数据失败:', error);
        proxy.$modal.msgError('加载数据失败');
        return;
      }
    }
    dialogFormVisible.value = true;
  } catch (error) {
    console.error('打开表单失败:', error);
    proxy.$modal.msgError('加载基础数据失败');
  }
};
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (file.size > 1024 * 1024 * 10) {
    proxy.$modal.msgError("上传文件大小不能超过10MB!");
    return false;
  }
  proxy.$modal.loading("正在上传文件,请稍候...");
  return true;
}
// ä¸Šä¼ å¤±è´¥
function handleUploadError(err) {
  proxy.$modal.msgError("上传文件失败");
  proxy.$modal.closeLoading();
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
function handleUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
  if (res.code === 200) {
    file.tempId = res.data.tempId;
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.fileUpload.handleRemove(file);
  }
}
// ç§»é™¤æ–‡ä»¶
async function handleRemove(file) {
  if (!file?.id) {
    return;
  }
  if (file.size > 1024 * 1024 * 10) {
    // ä»…前端清理,不调用删除接口和提示
    return;
  }
  if (operationType.value === "edit" && file.id) {
    try {
      await delLedgerFile([file.id]);
      proxy.$modal.msgSuccess("删除成功");
    } catch (error) {
      console.error('删除文件失败:', error);
      proxy.$modal.msgError("删除文件失败");
    }
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    const valid = await proxy.$refs["formRef"].validate().catch(() => false);
    if (!valid) {
      return;
    }
    if (!productData.value || productData.value.length === 0) {
      proxy.$modal.msgWarning("请添加产品信息");
      return;
    }
    form.value.productData = proxy.HaveJson(productData.value);
    form.value.tempFileIds = fileList.value
      .filter(item => item.tempId)
      .map((item) => item.tempId);
    form.value.type = 2;
    try {
      await addOrEditPurchase(form.value);
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
      getList();
    } catch (error) {
      console.error('提交表单失败:', error);
      proxy.$modal.msgError("提交失败,请稍后重试");
    }
  } catch (error) {
    console.error('表单验证失败:', error);
  }
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// æ‰“开产品弹框
const openProductForm = (type, row, index) => {
  productOperationType.value = type;
  productOperationIndex.value = index;
  productForm.value = {};
  proxy.resetForm("productFormRef");
  if (type === "edit") {
    productForm.value = { ...row };
  }
  productFormVisible.value = true;
  getProductOptions();
};
const getProductOptions = async () => {
  try {
    const res = await productTreeList();
    productOptions.value = convertIdToValue(res);
  } catch (error) {
    console.error('加载产品选项失败:', error);
    proxy.$modal.msgError('加载产品选项失败');
  }
};
const getModels = async (value) => {
  if (value) {
    productForm.value.productCategory = findNodeById(productOptions.value, value) || "";
    try {
      const res = await modelList({ id: value });
      modelOptions.value = res || [];
    } catch (error) {
      console.error('加载规格型号失败:', error);
      proxy.$modal.msgError('加载规格型号失败');
      modelOptions.value = [];
    }
  } else {
    productForm.value.productCategory = "";
    modelOptions.value = [];
  }
};
const getProductModel = (value) => {
  const index = modelOptions.value.findIndex((item) => item.id === value);
  if (index !== -1) {
    productForm.value.specificationModel = modelOptions.value[index].model;
    productForm.value.unit = modelOptions.value[index].unit;
  } else {
    productForm.value.specificationModel = null;
    productForm.value.unit = null;
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹çš„label
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œç›´æŽ¥è¿”å›žï¼ˆå·²ç»æ˜¯label字符串)
      }
    }
  }
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
      ...rest,
      value: id, // å°† id æ”¹ä¸º value
    };
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children);
    }
    return newItem;
  });
}
// æäº¤äº§å“è¡¨å•
const submitProduct = async () => {
  try {
    const valid = await proxy.$refs["productFormRef"].validate().catch(() => false);
    if (!valid) {
      return;
    }
    if (operationType.value === "edit") {
      await submitProductEdit();
    } else {
      if (productOperationType.value === "add") {
        productData.value.push({ ...productForm.value });
      } else {
        productData.value[productOperationIndex.value] = {
          ...productForm.value,
        };
      }
      closeProductDia();
      expandedRowKeys.value = [];
    }
  } catch (error) {
    console.error('提交产品表单失败:', error);
  }
};
const submitProductEdit = async () => {
  try {
    productForm.value.salesLedgerId = currentId.value;
    productForm.value.type = 2;
    await addOrUpdateSalesLedgerProduct(productForm.value);
    proxy.$modal.msgSuccess("提交成功");
    closeProductDia();
    // é‡æ–°åŠ è½½äº§å“æ•°æ®
  };
  // ä¸»è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["contractAmount"]);
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeProTable = param => {
    return proxy.summarizeTable(param, [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ]);
  };
  // æ‰“开弹框
  const openForm = async (type, row) => {
    await getTemplateList();
    operationType.value = type;
    form.value = {};
    productData.value = [];
    fileList.value = [];
    templateName.value = "";
    filterInputValue.value = "";
    isTemplateNameDuplicate.value = false;
    try {
      const res = await getPurchaseById({ id: currentId.value, type: 2 });
      productData.value = res.productData || [];
    } catch (error) {
      console.error('重新加载产品数据失败:', error);
    }
  } catch (error) {
    console.error('提交产品编辑失败:', error);
    proxy.$modal.msgError("提交失败,请稍后重试");
  }
};
// åˆ é™¤äº§å“
const deleteProduct = async () => {
  if (productSelectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  if (operationType.value === "add") {
    // æ–°å¢žæ¨¡å¼ä¸‹ï¼Œç›´æŽ¥ä»Žå‰ç«¯æ•°æ®ä¸­åˆ é™¤
    productSelectedRows.value.forEach((selectedRow) => {
      const index = productData.value.findIndex(
          (product) => product.id === selectedRow.id
      // å¹¶è¡ŒåŠ è½½åŸºç¡€æ•°æ®
      const [userRes, salesRes, supplierRes] = await Promise.all([
        userListNoPage(),
        getSalesNo(),
        getOptions(),
      ]);
      userList.value = userRes.data || [];
      salesContractList.value = salesRes || [];
      // ä¾›åº”商过滤出isWhite=0 çš„æ•°æ®
      supplierList.value = (supplierRes.data || []).filter(
        item => item.isWhite === 0
      );
      if (index !== -1) {
        productData.value.splice(index, 1);
      // è®¾ç½®é»˜è®¤å€¼
      form.value.recorderId = userStore.id;
      form.value.entryDate = getCurrentDate();
      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 {
          const purchaseRes = await getPurchaseById({ id: row.id, type: 2 });
          form.value = { ...purchaseRes };
          productData.value = purchaseRes.productData || [];
          fileList.value = purchaseRes.salesLedgerFiles || [];
        } catch (error) {
          console.error("加载采购台账数据失败:", error);
          proxy.$modal.msgError("加载数据失败");
          return;
        }
      }
      if (form.value.salesLedgerId == -1) {
        form.value.salesLedgerId = null;
      }
      console.log(form.value, "form.value===========");
      dialogFormVisible.value = true;
    } catch (error) {
      console.error("打开表单失败:", error);
      proxy.$modal.msgError("加载基础数据失败");
    }
  };
  // ä¸Šä¼ å‰æ ¡æ£€
  function handleBeforeUpload(file) {
    // æ ¡æ£€æ–‡ä»¶å¤§å°
    if (file.size > 1024 * 1024 * 10) {
      proxy.$modal.msgError("上传文件大小不能超过10MB!");
      return false;
    }
    proxy.$modal.loading("正在上传文件,请稍候...");
    return true;
  }
  // ä¸Šä¼ å¤±è´¥
  function handleUploadError(err) {
    proxy.$modal.msgError("上传文件失败");
    proxy.$modal.closeLoading();
  }
  // ä¸Šä¼ æˆåŠŸå›žè°ƒ
  function handleUploadSuccess(res, file, uploadFiles) {
    proxy.$modal.closeLoading();
    if (res.code === 200) {
      file.tempId = res.data.tempId;
      proxy.$modal.msgSuccess("上传成功");
    } else {
      proxy.$modal.msgError(res.msg);
      proxy.$refs.fileUpload.handleRemove(file);
    }
  }
  // ç§»é™¤æ–‡ä»¶
  async function handleRemove(file) {
    if (!file?.id) {
      return;
    }
    console.log("handleRemove", file.id);
    if (file.size > 1024 * 1024 * 10) {
      // ä»…前端清理,不调用删除接口和提示
      return;
    }
    if (operationType.value === "edit" && file.id) {
      try {
        await delLedgerFile([file.id]);
        proxy.$modal.msgSuccess("删除成功");
      } catch (error) {
        console.error("删除文件失败:", error);
        proxy.$modal.msgError("删除文件失败");
      }
    }
  }
  // æäº¤è¡¨å•
  const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        if (productData.value.length > 0) {
          form.value.productData = proxy.HaveJson(productData.value);
        } else {
          proxy.$modal.msgWarning("请添加产品信息");
          return;
        }
        let tempFileIds = [];
        if (fileList.value.length > 0) {
          tempFileIds = fileList.value.map(item => item.tempId);
        }
        form.value.tempFileIds = tempFileIds;
        form.value.type = 2;
        // å¦‚æžœsalesLedgerId为空,则不传递salesContractNo
        if (!form.value.salesLedgerId) {
          form.value.salesContractNo = "";
        }
        addOrEditPurchase(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
          getList();
        });
      }
    });
    proxy.$modal.msgSuccess("删除成功");
  } else {
    // ç¼–辑模式下,需要调用接口删除
    const ids = productSelectedRows.value
      .filter(item => item.id)
      .map((item) => item.id);
    if (ids.length === 0) {
      proxy.$modal.msgWarning("请选择有效的数据");
  };
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
  };
  // æ‰“开产品弹框
  const openProductForm = (type, row, index) => {
    productOperationType.value = type;
    productOperationIndex.value = index;
    productForm.value = {};
    proxy.resetForm("productFormRef");
    if (type === "edit") {
      productForm.value = { ...row };
    }
    productFormVisible.value = true;
    getProductOptions();
  };
  const getProductOptions = () => {
    productTreeList().then(res => {
      productOptions.value = convertIdToValue(res);
    });
  };
  const getModels = value => {
    if (value) {
      productForm.value.productCategory =
        findNodeById(productOptions.value, value) || "";
      modelList({ id: value }).then(res => {
        modelOptions.value = res;
      });
    } else {
      productForm.value.productCategory = "";
      modelOptions.value = [];
    }
  };
  const getProductModel = value => {
    const index = modelOptions.value.findIndex(item => item.id === value);
    if (index !== -1) {
      productForm.value.specificationModel = modelOptions.value[index].model;
      productForm.value.unit = modelOptions.value[index].unit;
    } else {
      productForm.value.specificationModel = null;
      productForm.value.unit = null;
    }
  };
  const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].value === productId) {
        return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹çš„label
      }
      if (nodes[i].children && nodes[i].children.length > 0) {
        const foundNode = findNodeById(nodes[i].children, productId);
        if (foundNode) {
          return foundNode; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œç›´æŽ¥è¿”å›žï¼ˆå·²ç»æ˜¯label字符串)
        }
      }
    }
    return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
  };
  function convertIdToValue(data) {
    return data.map(item => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
        value: id, // å°† id æ”¹ä¸º value
      };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
      }
      return newItem;
    });
  }
  // æäº¤äº§å“è¡¨å•
  const submitProduct = () => {
    proxy.$refs["productFormRef"].validate(valid => {
      if (valid) {
        if (operationType.value === "edit") {
          submitProductEdit();
        } else {
          if (productOperationType.value === "add") {
            productData.value.push({ ...productForm.value });
            console.log("productData.value---", productData.value);
          } else {
            productData.value[productOperationIndex.value] = {
              ...productForm.value,
            };
          }
          closeProductDia();
        }
      }
    });
  };
  const submitProductEdit = () => {
    productForm.value.salesLedgerId = currentId.value;
    productForm.value.type = 2;
    addOrUpdateSalesLedgerProduct(productForm.value).then(res => {
      proxy.$modal.msgSuccess("提交成功");
      closeProductDia();
      getPurchaseById({ id: currentId.value, type: 2 }).then(res => {
        productData.value = res.productData;
      });
    });
  };
  // åˆ é™¤äº§å“
  const deleteProduct = () => {
    if (productSelectedRows.value.length === 0) {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    try {
      await ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除确认", {
    if (operationType.value === "add") {
      productSelectedRows.value.forEach(selectedRow => {
        const index = productData.value.findIndex(
          product => product.id === selectedRow.id
        );
        if (index !== -1) {
          productData.value.splice(index, 1);
        }
      });
    } else {
      let ids = [];
      if (productSelectedRows.value.length > 0) {
        ids = productSelectedRows.value.map(item => item.id);
      }
      ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          delProduct(ids).then(res => {
            proxy.$modal.msgSuccess("删除成功");
            closeProductDia();
            getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then(
              res => {
                productData.value = res.productData;
              }
            );
          });
        })
        .catch(() => {
          proxy.$modal.msg("已取消");
        });
    }
  };
  // å…³é—­äº§å“å¼¹æ¡†
  const closeProductDia = () => {
    proxy.resetForm("productFormRef");
    productFormVisible.value = false;
  };
  // å¯¼å‡º
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/purchase/ledger/export", {}, "采购台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
      await delProduct(ids);
      proxy.$modal.msgSuccess("删除成功");
      closeProductDia();
      // é‡æ–°åŠ è½½äº§å“æ•°æ®
      try {
        const res = await getSalesLedgerWithProducts({ id: currentId.value, type: 2 });
        productData.value = res.productData || [];
      } catch (error) {
        console.error('重新加载产品数据失败:', error);
      }
    } catch (error) {
      if (error !== 'cancel') {
        console.error('删除产品失败:', error);
        proxy.$modal.msgError("删除失败,请稍后重试");
      }
    }
  }
};
// å…³é—­äº§å“å¼¹æ¡†
const closeProductDia = () => {
  proxy.resetForm("productFormRef");
  productFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = async () => {
  try {
    await ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出确认", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    });
    proxy.download("/purchase/ledger/export", {}, "采购台账.xlsx");
  } catch (error) {
    if (error !== 'cancel') {
      console.error('导出失败:', error);
      proxy.$modal.msgError("导出失败,请稍后重试");
    }
  }
};
// åˆ é™¤
const handleDelete = async () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
  const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName);
  if (unauthorizedData.length > 0) {
    proxy.$modal.msgWarning("不可删除他人维护的数据");
    return;
  }
  const ids = selectedRows.value
    .filter(item => item.id)
    .map((item) => item.id);
  if (ids.length === 0) {
    proxy.$modal.msgWarning("请选择有效的数据");
    return;
  }
  try {
    await ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除确认", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    });
    await delPurchase(ids);
    proxy.$modal.msgSuccess("删除成功");
    getList();
  } catch (error) {
    if (error !== 'cancel') {
      console.error('删除失败:', error);
      proxy.$modal.msgError("删除失败,请稍后重试");
    }
  }
};
const mathNum = () => {
  if (!productForm.value.taxRate) {
    proxy.$modal.msgWarning("请先选择税率");
    return;
  }
  if (!productForm.value.taxInclusiveUnitPrice) {
    return;
  }
  if (!productForm.value.quantity) {
    return;
  }
  // å«ç¨Žæ€»ä»·è®¡ç®—
  productForm.value.taxInclusiveTotalPrice =
      proxy.calculateTaxIncludeTotalPrice(
          productForm.value.taxInclusiveUnitPrice,
          productForm.value.quantity
  };
  // åˆ é™¤
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
      const unauthorizedData = selectedRows.value.filter(
        item => item.recorderName !== userStore.nickName
      );
  if (productForm.value.taxRate) {
    // ä¸å«ç¨Žæ€»ä»·è®¡ç®—
    productForm.value.taxExclusiveTotalPrice =
        proxy.calculateTaxExclusiveTotalPrice(
            productForm.value.taxInclusiveTotalPrice,
            productForm.value.taxRate
        );
  }
};
const reverseMathNum = (field) => {
  if (!productForm.value.taxRate) {
    proxy.$modal.msgWarning("请先选择税率");
    return;
  }
  const taxRate = Number(productForm.value.taxRate);
  if (!taxRate) return;
  if (field === 'taxInclusiveTotalPrice') {
    // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œæ•°é‡ï¼Œåç®—含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
          (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
    }
    // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œå«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
          (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
    }
    // åç®—不含税总价
    productForm.value.taxExclusiveTotalPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2);
  } else if (field === 'taxExclusiveTotalPrice') {
    // åç®—含税总价
    productForm.value.taxInclusiveTotalPrice =
        (Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)).toFixed(2);
    // å·²çŸ¥æ•°é‡ï¼Œåç®—含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
          (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
    }
    // å·²çŸ¥å«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
          (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
    }
  }
};
// é”€å”®åˆåŒé€‰æ‹©æ”¹å˜æ–¹æ³•
const salesLedgerChange = async (row) => {
  const index = salesContractList.value.findIndex((item) => item.id === row);
  if (index > -1) {
    form.value.projectName = salesContractList.value[index].projectName;
    await querygProductInfoByContractNo();
  }
};
const querygProductInfoByContractNo = async () => {
  const { code, data } = await getProductInfoByContractNo({
    contractNo: form.value.salesLedgerId,
  });
  if (code == 200) {
    productData.value = data;
  }
};
// æ˜¾ç¤ºäºŒç»´ç 
const showQRCode = async (row) => {
  try {
    // æž„建二维码内容,只包含采购合同号(纯文本)
    const qrContent = row.purchaseContractNumber || '';
    // æ£€æŸ¥å†…容是否为空
    if (!qrContent || qrContent.trim() === '') {
      proxy.$modal.msgWarning("该行没有采购合同号,无法生成二维码");
      if (unauthorizedData.length > 0) {
        proxy.$modal.msgWarning("不可删除他人维护的数据");
        return;
      }
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
      width: 200,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF'
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        delPurchase(ids).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
  function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  }
  const mathNum = () => {
    if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
    }
    if (!productForm.value.taxInclusiveUnitPrice) {
      return;
    }
    if (!productForm.value.quantity) {
      return;
    }
    // å«ç¨Žæ€»ä»·è®¡ç®—
    productForm.value.taxInclusiveTotalPrice =
      proxy.calculateTaxIncludeTotalPrice(
        productForm.value.taxInclusiveUnitPrice,
        productForm.value.quantity
      );
    if (productForm.value.taxRate) {
      // ä¸å«ç¨Žæ€»ä»·è®¡ç®—
      productForm.value.taxExclusiveTotalPrice =
        proxy.calculateTaxExclusiveTotalPrice(
          productForm.value.taxInclusiveTotalPrice,
          productForm.value.taxRate
        );
    }
  };
  const reverseMathNum = field => {
    if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
    }
    const taxRate = Number(productForm.value.taxRate);
    if (!taxRate) return;
    // ç¡®ä¿è¾“入值不为负数
    if (
      field === "taxInclusiveTotalPrice" ||
      field === "taxExclusiveTotalPrice"
    ) {
      const value = Number(productForm.value[field]);
      if (value < 0) {
        productForm.value[field] = "0";
        proxy.$modal.msgWarning("值不能小于0");
        return;
      }
    }
    if (field === "taxInclusiveTotalPrice") {
      // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œæ•°é‡ï¼Œåç®—含税单价
      if (productForm.value.quantity) {
        productForm.value.taxInclusiveUnitPrice = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
        }
      }
      // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œå«ç¨Žå•价,反算数量
      else if (productForm.value.taxInclusiveUnitPrice) {
        productForm.value.quantity = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.taxInclusiveUnitPrice)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.quantity) < 0) {
          productForm.value.quantity = "0";
        }
      }
      // åç®—不含税总价
      productForm.value.taxExclusiveTotalPrice = (
        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 = (
        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 = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
        }
      }
      // å·²çŸ¥å«ç¨Žå•价,反算数量
      else if (productForm.value.taxInclusiveUnitPrice) {
        productForm.value.quantity = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.taxInclusiveUnitPrice)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.quantity) < 0) {
          productForm.value.quantity = "0";
        }
      }
    }
  };
  // é”€å”®åˆåŒé€‰æ‹©æ”¹å˜æ–¹æ³•
  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();
    }
  };
  const querygProductInfoByContractNo = async () => {
    const { code, data } = await getProductInfoByContractNo({
      contractNo: form.value.salesLedgerId,
    });
    qrCodeDialogVisible.value = true;
  } catch (error) {
    console.error('生成二维码失败:', error);
    proxy.$modal.msgError("生成二维码失败:" + error.message);
  }
};
    if (code == 200) {
      productData.value = data;
    }
  };
// ä¸‹è½½äºŒç»´ç 
const downloadQRCode = () => {
  if (!qrCodeUrl.value) {
    proxy.$modal.msgWarning("二维码未生成");
    return;
  }
  const fileListRef = ref(null);
  const downLoadFile = row => {
    fileListRef.value.open(row.salesLedgerFiles);
  };
  try {
    const a = document.createElement('a');
  // æ˜¾ç¤ºäºŒç»´ç 
  const showQRCode = async row => {
    try {
      // æž„建二维码内容,只包含采购合同号(纯文本)
      const qrContent = row.purchaseContractNumber || "";
      // æ£€æŸ¥å†…容是否为空
      if (!qrContent || qrContent.trim() === "") {
        proxy.$modal.msgWarning("该行没有采购合同号,无法生成二维码");
        return;
      }
      qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
        width: 200,
        margin: 2,
        color: {
          dark: "#000000",
          light: "#FFFFFF",
        },
      });
      qrCodeDialogVisible.value = true;
    } catch (error) {
      console.error("生成二维码失败:", error);
      proxy.$modal.msgError("生成二维码失败:" + error.message);
    }
  };
  // ä¸‹è½½äºŒç»´ç 
  const downloadQRCode = () => {
    if (!qrCodeUrl.value) {
      proxy.$modal.msgWarning("二维码未生成");
      return;
    }
    const a = document.createElement("a");
    a.href = qrCodeUrl.value;
    a.download = `采购合同号二维码_${dayjs().format('YYYYMMDDHHmmss')}.png`;
    a.download = `采购合同号二维码_${new Date().getTime()}.png`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    proxy.$modal.msgSuccess("下载成功");
  } catch (error) {
    console.error('下载二维码失败:', error);
    proxy.$modal.msgError("下载失败,请稍后重试");
  }
};
  };
// æ‰«ç æ–°å¢žå¯¹è¯æ¡†ç›¸å…³å˜é‡
const scanAddDialogVisible = ref(false);
const scanAddForm = reactive({
  scanContent: "",
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  contractAmount: "",
  paymentMethod: "",
  recorderName: "",
  scanRemark: "",
});
const scanAddRules = {
  purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
  supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }],
  projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
};
  // æ‰«ç æ–°å¢žå¯¹è¯æ¡†ç›¸å…³å˜é‡
  const scanAddDialogVisible = ref(false);
  const scanAddForm = reactive({
    scanContent: "",
    purchaseContractNumber: "",
    supplierName: "",
    projectName: "",
    contractAmount: "",
    paymentMethod: "",
    recorderName: "",
    scanRemark: "",
  });
  const scanAddRules = {
    purchaseContractNumber: [
      { required: true, message: "请输入采购合同号", trigger: "blur" },
    ],
    supplierName: [
      { required: true, message: "请输入供应商名称", trigger: "blur" },
    ],
    projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
  };
// æ‰«ç ç™»è®°å¯¹è¯æ¡†ç›¸å…³å˜é‡
const scanDialogVisible = ref(false);
const scanForm = reactive({
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  scanTime: "",
  scannerName: "",
  scanStatus: "未扫码",
  scanRemark: "",
});
const scanRules = {
  scanRemark: [{ required: true, message: "请输入扫码备注", trigger: "blur" }],
};
const scanRecords = ref([]);
  // æ‰«ç ç™»è®°å¯¹è¯æ¡†ç›¸å…³å˜é‡
  const scanDialogVisible = ref(false);
  const scanForm = reactive({
    purchaseContractNumber: "",
    supplierName: "",
    projectName: "",
    scanTime: "",
    scannerName: "",
    scanStatus: "未扫码",
    scanRemark: "",
  });
  const scanRules = {
    scanRemark: [{ required: true, message: "请输入扫码备注", trigger: "blur" }],
  };
  const scanRecords = ref([]);
// æ‰“开扫码新增对话框
const openScanAddDialog = () => {
  scanAddForm.scanContent = "";
  scanAddForm.purchaseContractNumber = "";
  scanAddForm.supplierName = "";
  scanAddForm.projectName = "";
  scanAddForm.contractAmount = "";
  scanAddForm.paymentMethod = "";
  scanAddForm.recorderName = userStore.nickName;
  scanAddForm.scanRemark = "";
  scanAddDialogVisible.value = true;
};
  // æ‰“开扫码新增对话框
  const openScanAddDialog = () => {
    scanAddForm.scanContent = "";
    scanAddForm.purchaseContractNumber = "";
    scanAddForm.supplierName = "";
    scanAddForm.projectName = "";
    scanAddForm.contractAmount = "";
    scanAddForm.paymentMethod = "";
    scanAddForm.recorderName = userStore.nickName;
    scanAddForm.scanRemark = "";
    scanAddDialogVisible.value = true;
  };
// è§£æžæ‰«ç å†…容(模拟解析二维码数据)
const parseScanContent = (content) => {
  if (!content) return;
  // è§£æžæ‰«ç å†…容(模拟解析二维码数据)
  const parseScanContent = content => {
    if (!content) return;
  // æ¨¡æ‹Ÿè§£æžäºŒç»´ç å†…容,这里可以根据实际需求调整解析逻辑
  // å‡è®¾æ‰«ç å†…容格式为:合同号|供应商|项目|金额|付款方式
  const parts = content.split('|');
  if (parts.length >= 3) {
    scanAddForm.purchaseContractNumber = parts[0] || "";
    scanAddForm.supplierName = parts[1] || "";
    scanAddForm.projectName = parts[2] || "";
    scanAddForm.contractAmount = parts[3] || "";
    scanAddForm.paymentMethod = parts[4] || "";
  }
};
// å…³é—­æ‰«ç æ–°å¢žå¯¹è¯æ¡†
const closeScanAddDialog = () => {
  scanAddDialogVisible.value = false;
  proxy.resetForm("scanAddFormRef");
};
// æäº¤æ‰«ç æ–°å¢ž
const submitScanAdd = async () => {
  try {
    const valid = await proxy.$refs["scanAddFormRef"].validate().catch(() => false);
    if (!valid) {
      return;
    // æ¨¡æ‹Ÿè§£æžäºŒç»´ç å†…容,这里可以根据实际需求调整解析逻辑
    // å‡è®¾æ‰«ç å†…容格式为:合同号|供应商|金额|付款方式
    const parts = content.split("|");
    if (parts.length >= 2) {
      scanAddForm.purchaseContractNumber = parts[0] || "";
      scanAddForm.supplierName = parts[1] || "";
      scanAddForm.contractAmount = parts[2] || "";
      scanAddForm.paymentMethod = parts[3] || "";
      scanAddForm.projectName = parts[4] || "";
      // scanAddForm.contractAmount = parts[3] || "";
      // scanAddForm.paymentMethod = parts[4] || "";
    }
    // æž„建新增数据
    const newData = {
      purchaseContractNumber: scanAddForm.purchaseContractNumber,
      supplierName: scanAddForm.supplierName,
      projectName: scanAddForm.projectName,
      contractAmount: scanAddForm.contractAmount,
      paymentMethod: scanAddForm.paymentMethod,
      recorderName: scanAddForm.recorderName,
      entryDate: getCurrentDate(),
      remark: scanAddForm.scanRemark,
      type: 2
    };
  };
    // await addOrEditPurchase(newData);
    proxy.$modal.msgSuccess("扫码新增成功!");
    closeScanAddDialog();
    getList(); // åˆ·æ–°åˆ—表
  } catch (error) {
    console.error('提交扫码新增失败:', error);
    proxy.$modal.msgError("提交失败,请稍后重试");
  }
};
  // å…³é—­æ‰«ç æ–°å¢žå¯¹è¯æ¡†
  const closeScanAddDialog = () => {
    scanAddDialogVisible.value = false;
    proxy.resetForm("scanAddFormRef");
  };
// æ‰“开扫码登记对话框
const openScanDialog = (row) => {
  scanForm.purchaseContractNumber = row.purchaseContractNumber;
  scanForm.supplierName = row.supplierName;
  scanForm.projectName = row.projectName;
  scanForm.scanTime = getCurrentDateTime();
  scanForm.scannerName = userStore.nickName;
  scanForm.scanStatus = "未扫码";
  scanForm.scanRemark = "";
  scanRecords.value = [];
  scanDialogVisible.value = true;
};
  // æäº¤æ‰«ç æ–°å¢ž
  const submitScanAdd = () => {
    proxy.$refs["scanAddFormRef"].validate(valid => {
      if (valid) {
        // æž„建新增数据
        const newData = {
          purchaseContractNumber: scanAddForm.purchaseContractNumber,
          supplierName: scanAddForm.supplierName,
          projectName: scanAddForm.projectName,
          contractAmount: scanAddForm.contractAmount,
          paymentMethod: scanAddForm.paymentMethod,
          recorderName: scanAddForm.recorderName,
          entryDate: getCurrentDate(),
          remark: scanAddForm.scanRemark,
          type: 2,
        };
// å…³é—­æ‰«ç ç™»è®°å¯¹è¯æ¡†
const closeScanDialog = () => {
  scanDialogVisible.value = false;
  proxy.resetForm("scanFormRef");
};
        // æ¨¡æ‹Ÿæ–°å¢žæˆåŠŸ
        proxy.$modal.msgSuccess("扫码新增成功!");
        closeScanAddDialog();
// æäº¤æ‰«ç ç™»è®°
const submitScan = async () => {
  try {
    const valid = await proxy.$refs["scanFormRef"].validate().catch(() => false);
    if (!valid) {
      return;
    }
    // æ·»åŠ æ‰«ç è®°å½•
    scanRecords.value.push({
      ...scanForm,
      id: Date.now(), // æ¨¡æ‹ŸID
      scanTime: getCurrentDateTime(),
        // å¯ä»¥é€‰æ‹©æ˜¯å¦åˆ·æ–°åˆ—表
        // getList();
      }
    });
    scanForm.scanStatus = "已扫码";
    scanForm.scanRemark = scanForm.scanRemark || "无";
    proxy.$modal.msgSuccess("扫码登记成功!");
    closeScanDialog();
  } catch (error) {
    console.error('提交扫码登记失败:', error);
    proxy.$modal.msgError("提交失败,请稍后重试");
  };
  // æ‰“开扫码登记对话框
  const openScanDialog = row => {
    scanForm.purchaseContractNumber = row.purchaseContractNumber;
    scanForm.supplierName = row.supplierName;
    scanForm.projectName = row.projectName;
    scanForm.scanTime = getCurrentDateTime();
    scanForm.scannerName = userStore.nickName;
    scanForm.scanStatus = "未扫码";
    scanForm.scanRemark = "";
    scanRecords.value = [];
    scanDialogVisible.value = true;
  };
  // å…³é—­æ‰«ç ç™»è®°å¯¹è¯æ¡†
  const closeScanDialog = () => {
    scanDialogVisible.value = false;
    proxy.resetForm("scanFormRef");
  };
  // æäº¤æ‰«ç ç™»è®°
  const submitScan = () => {
    proxy.$refs["scanFormRef"].validate(valid => {
      if (valid) {
        // æ·»åŠ æ‰«ç è®°å½•
        scanRecords.value.push({
          ...scanForm,
          id: Date.now(), // æ¨¡æ‹ŸID
          scanTime: getCurrentDateTime(),
        });
        scanForm.scanStatus = "已扫码";
        scanForm.scanRemark = scanForm.scanRemark || "无";
        proxy.$modal.msgSuccess("扫码登记成功!");
        closeScanDialog();
      }
    });
  };
  // èŽ·å–å½“å‰æ—¥æœŸæ—¶é—´
  function getCurrentDateTime() {
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, "0");
    const day = String(now.getDate()).padStart(2, "0");
    const hours = String(now.getHours()).padStart(2, "0");
    const minutes = String(now.getMinutes()).padStart(2, "0");
    const seconds = String(now.getSeconds()).padStart(2, "0");
    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  }
};
// èŽ·å–å½“å‰æ—¥æœŸæ—¶é—´
function getCurrentDateTime() {
  return dayjs().format("YYYY-MM-DD HH:mm:ss");
}
  // æ·»åŠ è¡Œç±»åæ–¹æ³•
  const tableRowClassName = ({ row }) => {
    return row.isInvalid ? "invalid-row" : "";
  };
// æ·»åŠ è¡Œç±»åæ–¹æ³•
const tableRowClassName = ({ row }) => {
  return row.isInvalid ? 'invalid-row' : '';
};
  // èŽ·å–æ¨¡æ¿ä¿¡æ¯
  const getTemplateList = async () => {
    let res = await getPurchaseTemplateList();
    if (res && res.code === 200 && Array.isArray(res.data)) {
      templateList.value = res.data;
    }
  };
// èŽ·å–æ¨¡æ¿ä¿¡æ¯
const getTemplateList =async ()=>{
  let res = await getPurchaseTemplateList()
  if(res && res.code===200 && Array.isArray(res.data)){
    templateList.value = res.data
  }
}
onMounted(() => {
  getList();
  getTemplateList();
});
  onMounted(() => {
    getList();
    getTemplateList();
  });
</script>
<style scoped lang="scss">
.invalid-row {
  opacity: 0.6;
  background-color: #f5f7fa;
}
.el-row{
  justify-content: space-between;
  align-items: center
}
.no-arrow-select {
  --el-select-suffix-icon-color: transparent; /* éšè—é»˜è®¤ä¸‹æ‹‰ç®­å¤´ */
}
.select-button-group {
  display: flex;
  align-items: center;
}
  .invalid-row {
    opacity: 0.6;
    background-color: #f5f7fa;
  }
  .el-row {
    justify-content: space-between;
    align-items: center;
  }
  .no-arrow-select {
    --el-select-suffix-icon-color: transparent; /* éšè—é»˜è®¤ä¸‹æ‹‰ç®­å¤´ */
  }
  .select-button-group {
    display: flex;
    align-items: center;
  }
</style>
src/views/procurementManagement/procurementPlan/index.vue
@@ -88,11 +88,15 @@
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog
    <FormDialog
      v-model="dialogVisible"
      :title="dialogType === 'add' ? '新增采购计划' : '编辑采购计划'"
      width="1000px"
      :width="'1000px'"
      :operation-type="dialogType"
      :close-on-click-modal="false"
      @close="dialogVisible = false"
      @confirm="handleSubmit"
      @cancel="dialogVisible = false"
    >
      <div class="form-container">
        <!-- åŸºæœ¬ä¿¡æ¯ -->
@@ -202,21 +206,17 @@
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
        </div>
      </template>
    </el-dialog>
    </FormDialog>
    <!-- äº§å“é€‰æ‹©å¯¹è¯æ¡† -->
    <el-dialog
    <FormDialog
      v-model="productSelectDialogVisible"
      title="选择产品"
      width="800px"
      :width="'800px'"
      :close-on-click-modal="false"
      @close="productSelectDialogVisible = false"
      @confirm="handleConfirmProductSelection"
      @cancel="productSelectDialogVisible = false"
    >
      <div class="product-select">
        <el-alert
@@ -247,23 +247,17 @@
          <el-table-column prop="inboundNum0" label="预计入库" width="100" align="right" />
        </el-table>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="productSelectDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleConfirmProductSelection" :disabled="selectedProducts.length === 0">
            ç¡®è®¤è®¡ç®—
          </el-button>
        </div>
      </template>
    </el-dialog>
    </FormDialog>
    <!-- è®¡ç®—结果对话框 -->
    <el-dialog
    <FormDialog
      v-model="calculateDialogVisible"
      title="采购计算结果"
      width="1000px"
      :width="'1000px'"
      :close-on-click-modal="false"
      @close="calculateDialogVisible = false"
      @confirm="handleCreatePurchaseOrder"
      @cancel="calculateDialogVisible = false"
    >
      <div class="calculate-result">
        <el-alert
@@ -300,18 +294,12 @@
          </el-table-column>
        </el-table>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="calculateDialogVisible = false">关闭</el-button>
          <el-button type="primary" @click="handleCreatePurchaseOrder">确认</el-button>
        </div>
      </template>
    </el-dialog>
    </FormDialog>
  </div>
</template>
<script setup>
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'
src/views/procurementManagement/procurementReport/index.vue
@@ -1,46 +1,7 @@
<template>
  <div class="app-container">
    <!-- æŠ¥è¡¨é€‰æ‹©å™¨ -->
    <el-card class="report-selector" shadow="never">
      <el-tabs v-model="activeReport" @tab-change="handleReportChange">
        <el-tab-pane label="采购订单执行汇总表" name="orderSummary">
          <template #label>
            <span class="tab-label">
              <el-icon><Document /></el-icon>
              é‡‡è´­è®¢å•执行汇总表
            </span>
          </template>
        </el-tab-pane>
        <el-tab-pane label="采购订单执行明细表" name="orderDetail">
          <template #label>
            <span class="tab-label">
              <el-icon><List /></el-icon>
              é‡‡è´­è®¢å•执行明细表
            </span>
          </template>
        </el-tab-pane>
        <el-tab-pane label="采购业务汇总表" name="businessSummary">
          <template #label>
            <span class="tab-label">
              <el-icon><TrendCharts /></el-icon>
              é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨
            </span>
          </template>
        </el-tab-pane>
        <el-tab-pane label="供应商供货汇总表" name="supplierSummary">
          <template #label>
            <span class="tab-label">
              <el-icon><Shop /></el-icon>
              ä¾›åº”商供货汇总表
            </span>
          </template>
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <!-- æŸ¥è¯¢æ¡ä»¶ -->
    <el-card class="search-card" shadow="never">
      <el-form :model="searchForm" :inline="true" class="search-form">
    <el-form :model="searchForm" :inline="true" class="search-form">
        <el-form-item label="时间范围:">
          <el-date-picker
            v-model="searchForm.dateRange"
@@ -53,139 +14,35 @@
            style="width: 240px"
          />
        </el-form-item>
        <el-form-item label="供应商:" v-if="activeReport === 'supplierSummary'">
          <el-select v-model="searchForm.supplierId" placeholder="请选择供应商" clearable style="width: 200px">
            <el-option
              v-for="supplier in supplierList"
              :key="supplier.id"
              :label="supplier.name"
              :value="supplier.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="商品类别:" v-if="activeReport === 'businessSummary'">
          <el-select v-model="searchForm.categoryId" placeholder="请选择商品类别" clearable style="width: 200px">
            <el-option
              v-for="category in categoryList"
              :key="category.id"
              :label="category.name"
              :value="category.id"
            />
          </el-select>
        <el-form-item label="产品大类:">
          <el-tree-select
            v-model="searchForm.productCategory"
            placeholder="请选择商品类别"
            clearable
            check-strictly
            :data="productOptions"
            :render-after-expand="false"
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch" :loading="loading">
            <el-icon><Search /></el-icon>
            æŸ¥è¯¢
          </el-button>
          <el-button @click="resetSearch">
            <el-icon><Refresh /></el-icon>
            é‡ç½®
          </el-button>
          <el-button type="success" @click="exportReport">
          <el-button type="info" @click="exportReport">
            <el-icon><Download /></el-icon>
            å¯¼å‡º
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- æŠ¥è¡¨å†…容 -->
    <el-card class="report-content" shadow="never">
      <!-- é‡‡è´­è®¢å•执行汇总表 -->
      <div v-if="activeReport === 'orderSummary'" class="report-section">
        <div class="section-header">
          <h3>采购订单执行汇总表</h3>
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">总订单数:</span>
              <span class="stat-value">{{ orderSummaryStats.totalOrders }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">总金额:</span>
              <span class="stat-value">Â¥{{ orderSummaryStats.totalAmount.toLocaleString() }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">完成率:</span>
              <span class="stat-value">{{ orderSummaryStats.completionRate }}%</span>
            </div>
          </div>
        </div>
        <el-table :data="orderSummaryData" border v-loading="loading" stripe style="width: 100%">
          <el-table-column label="订单编号" prop="orderNo" width="180" fixed="left" />
          <el-table-column label="供应商名称" prop="supplierName" min-width="150" />
          <el-table-column label="订单日期" prop="orderDate" width="120" />
          <el-table-column label="计划交期" prop="plannedDelivery" width="120" />
          <el-table-column label="实际交期" prop="actualDelivery" width="120" />
          <el-table-column label="订单金额" prop="orderAmount" width="120">
            <template #default="{ row }">Â¥{{ row.orderAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="已付金额" prop="paidAmount" width="120">
            <template #default="{ row }">Â¥{{ row.paidAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="完成状态" prop="status" width="100">
            <template #default="{ row }">
              <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="完成率" prop="completionRate" width="100">
            <template #default="{ row }">{{ row.completionRate }}%</template>
          </el-table-column>
          <el-table-column label="延迟天数" prop="delayDays" width="100">
            <template #default="{ row }">
              <span :class="{ 'delay-text': row.delayDays > 0 }">{{ row.delayDays }}</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <!-- é‡‡è´­è®¢å•执行明细表 -->
      <div v-if="activeReport === 'orderDetail'" class="report-section">
        <div class="section-header">
          <h3>采购订单执行明细表</h3>
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">明细条数:</span>
              <span class="stat-value">{{ orderDetailStats.totalItems }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">已收货:</span>
              <span class="stat-value">{{ orderDetailStats.receivedItems }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">待收货:</span>
              <span class="stat-value">{{ orderDetailStats.pendingItems }}</span>
            </div>
          </div>
        </div>
        <el-table :data="orderDetailData" border v-loading="loading" stripe style="width: 100%">
          <el-table-column label="订单编号" prop="orderNo" width="150" fixed="left" />
          <el-table-column label="商品编码" prop="productCode" width="120" />
          <el-table-column label="商品名称" prop="productName" min-width="200" />
          <el-table-column label="规格型号" prop="specification" min-width="150" />
          <el-table-column label="单位" prop="unit" width="80" />
          <el-table-column label="计划数量" prop="plannedQuantity" width="100" />
          <el-table-column label="已收货数量" prop="receivedQuantity" width="120" />
          <el-table-column label="待收货数量" prop="pendingQuantity" width="120" />
          <el-table-column label="单价" prop="unitPrice" width="100">
            <template #default="{ row }">Â¥{{ row.unitPrice.toFixed(2) }}</template>
          </el-table-column>
          <el-table-column label="小计" prop="subtotal" width="120">
            <template #default="{ row }">Â¥{{ row.subtotal.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="收货状态" prop="status" width="100">
            <template #default="{ row }">
              <el-tag :type="getReceiptStatusType(row.status)">{{ getReceiptStatusText(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="最后收货日期" prop="lastReceiptDate" width="120" />
        </el-table>
      </div>
      <!-- é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨ -->
      <div v-if="activeReport === 'businessSummary'" class="report-section">
      <div class="report-section">
        <div class="section-header">
          <h3>采购业务汇总表</h3>
          <div class="summary-stats">
@@ -197,80 +54,19 @@
              <span class="stat-label">商品种类:</span>
              <span class="stat-value">{{ businessSummaryStats.productTypes }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">供应商数:</span>
              <span class="stat-value">{{ businessSummaryStats.supplierCount }}</span>
            </div>
          </div>
        </div>
        
        <el-table :data="businessSummaryData" border v-loading="loading" stripe style="width: 100%">
          <el-table-column label="商品类别" prop="category" width="150" fixed="left" />
          <el-table-column label="商品编码" prop="productCode" width="120" />
          <el-table-column label="商品名称" prop="productName" min-width="200" />
          <el-table-column label="规格型号" prop="specification" min-width="150" />
          <el-table-column label="采购数量" prop="purchaseQuantity" width="120" />
          <el-table-column label="采购金额" prop="purchaseAmount" width="120">
            <template #default="{ row }">Â¥{{ row.purchaseAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="平均单价" prop="avgPrice" width="100">
            <template #default="{ row }">Â¥{{ row.avgPrice.toFixed(2) }}</template>
          </el-table-column>
          <el-table-column label="采购次数" prop="purchaseCount" width="100" />
          <el-table-column label="主要供应商" prop="mainSupplier" min-width="150" />
          <el-table-column label="最后采购日期" prop="lastPurchaseDate" width="120" />
        </el-table>
      </div>
      <!-- ä¾›åº”商供货汇总表 -->
      <div v-if="activeReport === 'supplierSummary'" class="report-section">
        <div class="section-header">
          <h3>供应商供货汇总表</h3>
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">供应商总数:</span>
              <span class="stat-value">{{ supplierSummaryStats.totalSuppliers }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">供货总额:</span>
              <span class="stat-value">Â¥{{ supplierSummaryStats.totalAmount.toLocaleString() }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">平均评分:</span>
              <span class="stat-value">{{ supplierSummaryStats.avgRating.toFixed(1) }}</span>
            </div>
          </div>
        </div>
        <el-table :data="supplierSummaryData" border v-loading="loading" stripe style="width: 100%">
          <el-table-column label="供应商编码" prop="supplierCode" width="120" fixed="left" />
          <el-table-column label="供应商名称" prop="supplierName" min-width="200" />
          <el-table-column label="联系人" prop="contactPerson" width="120" />
          <el-table-column label="联系电话" prop="phone" width="130" />
          <el-table-column label="供货订单数" prop="orderCount" width="120" />
          <el-table-column label="供货金额" prop="supplyAmount" width="120">
            <template #default="{ row }">Â¥{{ row.supplyAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="已付金额" prop="paidAmount" width="120">
            <template #default="{ row }">Â¥{{ row.paidAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="未付金额" prop="unpaidAmount" width="120">
            <template #default="{ row }">Â¥{{ row.unpaidAmount.toLocaleString() }}</template>
          </el-table-column>
          <el-table-column label="按时交货率" prop="onTimeRate" width="120">
            <template #default="{ row }">{{ row.onTimeRate }}%</template>
          </el-table-column>
          <el-table-column label="质量评分" prop="qualityRating" width="100">
            <template #default="{ row }">
              <el-rate v-model="row.qualityRating" disabled show-score text-color="#ff9900" />
            </template>
          </el-table-column>
          <el-table-column label="合作状态" prop="status" width="100">
            <template #default="{ row }">
              <el-tag :type="getSupplierStatusType(row.status)">{{ getSupplierStatusText(row.status) }}</el-tag>
            </template>
          </el-table-column>
        </el-table>
        <PIMTable
          :table-data="businessSummaryData"
          :column="tableColumns"
          :table-loading="loading"
          :is-selection="false"
          :border="true"
          :is-show-pagination="true"
          :page="page"
          @pagination="handlePagination"
        />
      </div>
    </el-card>
  </div>
@@ -279,354 +75,200 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Document, List, TrendCharts, Shop, Search, Refresh, Download } from '@element-plus/icons-vue'
import { Download } from '@element-plus/icons-vue'
import PIMTable from '@/components/PIMTable/PIMTable.vue'
import { procurementBusinessSummaryListPage } from '@/api/procurementManagement/procurementReport'
import { productTreeList } from '@/api/basicData/product'
// å“åº”式数据
const loading = ref(false)
const activeReport = ref('orderSummary')
// æœç´¢è¡¨å•
const searchForm = reactive({
  dateRange: [],
  supplierId: '',
  categoryId: ''
  productCategory: ''
})
// ä¾›åº”商列表
const supplierList = ref([
  { id: 1, name: '江苏华联电子科技有限公司' },
  { id: 2, name: '上海精密机械制造有限公司' },
  { id: 3, name: '深圳智能设备有限公司' },
  { id: 4, name: '北京新材料科技有限公司' },
  { id: 5, name: '广州电子元器件有限公司' }
])
// å•†å“ç±»åˆ«åˆ—表
const categoryList = ref([
  { id: 1, name: '电子元器件' },
  { id: 2, name: '机械设备' },
  { id: 3, name: '原材料' },
  { id: 4, name: '办公用品' },
  { id: 5, name: '包装材料' }
])
// äº§å“ç±»åˆ«æ ‘选项
const productOptions = ref([])
// ç»Ÿè®¡æ•°æ®
const orderSummaryStats = ref({
  totalOrders: 156,
  totalAmount: 2580000,
  completionRate: 87.5
})
const orderDetailStats = ref({
  totalItems: 1248,
  receivedItems: 1089,
  pendingItems: 159
})
const businessSummaryStats = ref({
  totalAmount: 2580000,
  productTypes: 89,
  supplierCount: 25
  totalAmount: 0,
  productTypes: 0
})
const supplierSummaryStats = ref({
  totalSuppliers: 25,
  totalAmount: 2580000,
  avgRating: 4.2
})
// é‡‡è´­è®¢å•执行汇总表数据
const orderSummaryData = ref([
// è¡¨æ ¼åˆ—配置(根据后端字段定义)
const tableColumns = ref([
  {
    orderNo: 'PO20241201001',
    supplierName: '江苏华联电子科技有限公司',
    orderDate: '2024-12-01',
    plannedDelivery: '2024-12-15',
    actualDelivery: '2024-12-14',
    orderAmount: 125000,
    paidAmount: 100000,
    status: 'completed',
    completionRate: 100,
    delayDays: -1
    label: '产品大类',
    prop: 'productCategory',
    width: 150,
  },
  {
    orderNo: 'PO20241201002',
    supplierName: '上海精密机械制造有限公司',
    orderDate: '2024-12-02',
    plannedDelivery: '2024-12-20',
    actualDelivery: '2024-12-22',
    orderAmount: 280000,
    paidAmount: 140000,
    status: 'partial',
    completionRate: 75,
    delayDays: 2
    label: '规格型号',
    prop: 'specificationModel',
    width: 180
  },
  {
    orderNo: 'PO20241201003',
    supplierName: '深圳智能设备有限公司',
    orderDate: '2024-12-03',
    plannedDelivery: '2024-12-25',
    actualDelivery: '',
    orderAmount: 180000,
    paidAmount: 0,
    status: 'pending',
    completionRate: 0,
    delayDays: 0
    label: '采购数量',
    prop: 'purchaseNum',
    width: 120,
    formatData: (val) => {
      return val ? parseFloat(val).toLocaleString() : '0'
    }
  },
  {
    orderNo: 'PO20241201004',
    supplierName: '北京新材料科技有限公司',
    orderDate: '2024-12-04',
    plannedDelivery: '2024-12-18',
    actualDelivery: '2024-12-18',
    orderAmount: 95000,
    paidAmount: 95000,
    status: 'completed',
    completionRate: 100,
    delayDays: 0
    label: '采购金额',
    prop: 'purchaseAmount',
    width: 140,
    formatData: (val) => {
      return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00'
    }
  },
  {
    orderNo: 'PO20241201005',
    supplierName: '广州电子元器件有限公司',
    orderDate: '2024-12-05',
    plannedDelivery: '2024-12-28',
    actualDelivery: '',
    orderAmount: 220000,
    paidAmount: 0,
    status: 'pending',
    completionRate: 0,
    delayDays: 0
  }
])
// é‡‡è´­è®¢å•执行明细表数据
const orderDetailData = ref([
  {
    orderNo: 'PO20241201001',
    productCode: 'EL001',
    productName: '电阻器 1KΩ Â±5%',
    specification: '1/4W ç¢³è†œç”µé˜»',
    unit: '个',
    plannedQuantity: 1000,
    receivedQuantity: 1000,
    pendingQuantity: 0,
    unitPrice: 0.15,
    subtotal: 150,
    status: 'completed',
    lastReceiptDate: '2024-12-14'
    label: '采购次数',
    prop: 'purchaseTimes',
    width: 100
  },
  {
    orderNo: 'PO20241201001',
    productCode: 'EL002',
    productName: '电容器 100μF',
    specification: '25V é“ç”µè§£ç”µå®¹',
    unit: '个',
    plannedQuantity: 500,
    receivedQuantity: 500,
    pendingQuantity: 0,
    unitPrice: 0.85,
    subtotal: 425,
    status: 'completed',
    lastReceiptDate: '2024-12-14'
    label: '平均单价',
    prop: 'averagePrice',
    width: 120,
    formatData: (val) => {
      return val ? `Â¥${parseFloat(val).toFixed(2)}` : 'Â¥0.00'
    }
  },
  {
    orderNo: 'PO20241201002',
    productCode: 'ME001',
    productName: '精密轴承',
    specification: '6205-2RS æ·±æ²Ÿçƒè½´æ‰¿',
    unit: '个',
    plannedQuantity: 200,
    receivedQuantity: 150,
    pendingQuantity: 50,
    unitPrice: 25.5,
    subtotal: 5100,
    status: 'partial',
    lastReceiptDate: '2024-12-20'
    label: '供应商名称',
    prop: 'supplierName',
    width: 200
  },
  {
    orderNo: 'PO20241201002',
    productCode: 'ME002',
    productName: '不锈钢螺丝',
    specification: 'M8×20 304不锈钢',
    unit: '个',
    plannedQuantity: 1000,
    receivedQuantity: 1000,
    pendingQuantity: 0,
    unitPrice: 0.8,
    subtotal: 800,
    status: 'completed',
    lastReceiptDate: '2024-12-20'
  },
  {
    orderNo: 'PO20241201003',
    productCode: 'SM001',
    productName: '智能传感器',
    specification: '温度传感器 DS18B20',
    unit: '个',
    plannedQuantity: 300,
    receivedQuantity: 0,
    pendingQuantity: 300,
    unitPrice: 12.5,
    subtotal: 3750,
    status: 'pending',
    lastReceiptDate: ''
    label: '录入日期',
    prop: 'entryDate',
    width: 120
  }
])
// é‡‡è´­ä¸šåŠ¡æ±‡æ€»è¡¨æ•°æ®
const businessSummaryData = ref([
  {
    category: '电子元器件',
    productCode: 'EL001',
    productName: '电阻器 1KΩ Â±5%',
    specification: '1/4W ç¢³è†œç”µé˜»',
    purchaseQuantity: 5000,
    purchaseAmount: 750,
    avgPrice: 0.15,
    purchaseCount: 8,
    mainSupplier: '江苏华联电子科技有限公司',
    lastPurchaseDate: '2024-12-01'
  },
  {
    category: '电子元器件',
    productCode: 'EL002',
    productName: '电容器 100μF',
    specification: '25V é“ç”µè§£ç”µå®¹',
    purchaseQuantity: 2500,
    purchaseAmount: 2125,
    avgPrice: 0.85,
    purchaseCount: 6,
    mainSupplier: '江苏华联电子科技有限公司',
    lastPurchaseDate: '2024-12-01'
  },
  {
    category: '机械设备',
    productCode: 'ME001',
    productName: '精密轴承',
    specification: '6205-2RS æ·±æ²Ÿçƒè½´æ‰¿',
    purchaseQuantity: 800,
    purchaseAmount: 20400,
    avgPrice: 25.5,
    purchaseCount: 4,
    mainSupplier: '上海精密机械制造有限公司',
    lastPurchaseDate: '2024-12-02'
  },
  {
    category: '机械设备',
    productCode: 'ME002',
    productName: '不锈钢螺丝',
    specification: 'M8×20 304不锈钢',
    purchaseQuantity: 5000,
    purchaseAmount: 4000,
    avgPrice: 0.8,
    purchaseCount: 12,
    mainSupplier: '上海精密机械制造有限公司',
    lastPurchaseDate: '2024-12-02'
  },
  {
    category: '智能设备',
    productCode: 'SM001',
    productName: '智能传感器',
    specification: '温度传感器 DS18B20',
    purchaseQuantity: 1200,
    purchaseAmount: 15000,
    avgPrice: 12.5,
    purchaseCount: 5,
    mainSupplier: '深圳智能设备有限公司',
    lastPurchaseDate: '2024-12-03'
  }
])
const businessSummaryData = ref([])
// ä¾›åº”商供货汇总表数据
const supplierSummaryData = ref([
  {
    supplierCode: 'SUP001',
    supplierName: '江苏华联电子科技有限公司',
    contactPerson: '张经理',
    phone: '0512-88888888',
    orderCount: 45,
    supplyAmount: 850000,
    paidAmount: 680000,
    unpaidAmount: 170000,
    onTimeRate: 95,
    qualityRating: 4.5,
    status: 'active'
  },
  {
    supplierCode: 'SUP002',
    supplierName: '上海精密机械制造有限公司',
    contactPerson: '李总',
    phone: '021-66666666',
    orderCount: 32,
    supplyAmount: 1200000,
    paidAmount: 900000,
    unpaidAmount: 300000,
    onTimeRate: 88,
    qualityRating: 4.2,
    status: 'active'
  },
  {
    supplierCode: 'SUP003',
    supplierName: '深圳智能设备有限公司',
    contactPerson: '王工程师',
    phone: '0755-77777777',
    orderCount: 28,
    supplyAmount: 680000,
    paidAmount: 400000,
    unpaidAmount: 280000,
    onTimeRate: 92,
    qualityRating: 4.3,
    status: 'active'
  },
  {
    supplierCode: 'SUP004',
    supplierName: '北京新材料科技有限公司',
    contactPerson: '陈博士',
    phone: '010-55555555',
    orderCount: 18,
    supplyAmount: 320000,
    paidAmount: 250000,
    unpaidAmount: 70000,
    onTimeRate: 85,
    qualityRating: 4.0,
    status: 'active'
  },
  {
    supplierCode: 'SUP005',
    supplierName: '广州电子元器件有限公司',
    contactPerson: '刘经理',
    phone: '020-44444444',
    orderCount: 22,
    supplyAmount: 480000,
    paidAmount: 200000,
    unpaidAmount: 280000,
    onTimeRate: 78,
    qualityRating: 3.8,
    status: 'warning'
  }
])
// åˆ†é¡µå‚数(后端返回:total/size/current/pages)
const page = reactive({
  total: 0,
  current: 1,
  size: 50,
})
// æ–¹æ³•
const handleReportChange = (tabName) => {
  activeReport.value = tabName
  handleSearch()
// è½¬æ¢äº§å“æ ‘数据,将 id æ”¹ä¸º value
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)
    }
    return newItem
  })
}
const handleSearch = () => {
  loading.value = true
  // æ¨¡æ‹ŸAPI调用
  setTimeout(() => {
// èŽ·å–äº§å“ç±»åˆ«æ ‘æ•°æ®
const getProductOptions = () => {
  return productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res)
  }).catch((error) => {
    console.error('获取产品树失败:', error)
    ElMessage.error('获取产品类别失败')
  })
}
// æ ¹æ® id æŸ¥æ‰¾äº§å“ç±»åˆ«åç§°
const findNodeLabelById = (nodes, id) => {
  if (!id) return null
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === id) {
      return nodes[i].label
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const found = findNodeLabelById(nodes[i].children, id)
      if (found) return found
    }
  }
  return null
}
// æŸ¥è¯¢åˆ—表
const handleSearch = async () => {
  try {
    loading.value = true
    const params = {}
    // æ—¶é—´èŒƒå›´
    if (searchForm.dateRange && searchForm.dateRange.length === 2) {
      params.entryDateStart = searchForm.dateRange[0]
      params.entryDateEnd = searchForm.dateRange[1]
    }
    // äº§å“ç±»åˆ«
    if (searchForm.productCategory) {
      const categoryName = findNodeLabelById(productOptions.value, searchForm.productCategory)
      if (categoryName) {
        params.productCategory = categoryName
      }
    }
    // åˆ†é¡µå‚æ•°
    params.current = page.current
    params.size = page.size
    const res = await procurementBusinessSummaryListPage(params)
    if (res && res.data) {
      // å…¼å®¹åŽç«¯å¯èƒ½ç›´æŽ¥è¿”回数组/或返回分页对象
      businessSummaryData.value = Array.isArray(res.data) ? res.data : (res.data.records || [])
      if (!Array.isArray(res.data)) {
        page.total = Number(res.data.total ?? 0)
        page.current = Number(res.data.current ?? page.current)
        page.size = Number(res.data.size ?? page.size)
      }
      // è®¡ç®—统计数据
      if (businessSummaryData.value.length > 0) {
        businessSummaryStats.value.totalAmount = businessSummaryData.value.reduce((sum, item) => {
          return sum + (parseFloat(item.purchaseAmount) || 0)
        }, 0)
        businessSummaryStats.value.productTypes = new Set(businessSummaryData.value.map(item => item.productCategory)).size
      } else {
        businessSummaryStats.value = {
          totalAmount: 0,
          productTypes: 0
        }
      }
    }
  } catch (error) {
    console.error('查询失败:', error)
  } finally {
    loading.value = false
    ElMessage.success('查询完成')
  }, 1000)
  }
}
// ç¿»é¡µ/切换每页条数
const handlePagination = ({ page: current, limit }) => {
  page.current = current
  page.size = limit
  handleSearch()
}
const resetSearch = () => {
  Object.assign(searchForm, {
    dateRange: [],
    supplierId: '',
    categoryId: ''
    productCategory: ''
  })
  page.current = 1
  handleSearch()
}
@@ -634,62 +276,11 @@
  ElMessage.success('导出功能开发中...')
}
// çŠ¶æ€ç›¸å…³æ–¹æ³•
const getStatusType = (status) => {
  const statusMap = {
    completed: 'success',
    partial: 'warning',
    pending: 'info'
  }
  return statusMap[status] || 'info'
}
const getStatusText = (status) => {
  const statusMap = {
    completed: '已完成',
    partial: '部分完成',
    pending: '待执行'
  }
  return statusMap[status] || '未知'
}
const getReceiptStatusType = (status) => {
  const statusMap = {
    completed: 'success',
    partial: 'warning',
    pending: 'info'
  }
  return statusMap[status] || 'info'
}
const getReceiptStatusText = (status) => {
  const statusMap = {
    completed: '已收货',
    partial: '部分收货',
    pending: '待收货'
  }
  return statusMap[status] || '未知'
}
const getSupplierStatusType = (status) => {
  const statusMap = {
    active: 'success',
    warning: 'warning',
    inactive: 'info'
  }
  return statusMap[status] || 'info'
}
const getSupplierStatusText = (status) => {
  const statusMap = {
    active: '正常合作',
    warning: '需关注',
    inactive: '暂停合作'
  }
  return statusMap[status] || '未知'
}
onMounted(() => {
  // åˆå§‹åŒ–产品类别树
  getProductOptions()
  // è®¾ç½®é»˜è®¤æ—¶é—´èŒƒå›´ä¸ºæœ€è¿‘30天
  const endDate = new Date()
  const startDate = new Date()
@@ -699,6 +290,9 @@
    startDate.toISOString().split('T')[0],
    endDate.toISOString().split('T')[0]
  ]
  // åˆå§‹åŠ è½½æ•°æ®
  handleSearch()
})
</script>
@@ -728,26 +322,6 @@
  margin: 0;
  font-size: 16px;
  opacity: 0.9;
}
.report-selector {
  margin-bottom: 20px;
  border-radius: 8px;
}
.tab-label {
  display: flex;
  align-items: center;
  gap: 8px;
}
.search-card {
  margin-bottom: 20px;
  border-radius: 8px;
}
.search-form {
  margin-bottom: 0;
}
.report-content {
@@ -802,55 +376,5 @@
  font-weight: 600;
}
:deep(.el-table) {
  border-radius: 8px;
  overflow: hidden;
  width: 100% !important;
}
:deep(.el-table__body-wrapper) {
  width: 100% !important;
}
:deep(.el-table__header-wrapper) {
  width: 100% !important;
}
:deep(.el-table th) {
  background-color: #f8f9fa;
  color: #606266;
  font-weight: 600;
}
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
  background-color: #fafafa;
}
:deep(.el-tabs__header) {
  margin-bottom: 0;
}
:deep(.el-tabs__nav-wrap) {
  padding: 0 20px;
}
:deep(.el-tabs__item) {
  font-size: 16px;
  font-weight: 500;
}
:deep(.el-tabs__item.is-active) {
  color: #409EFF;
}
:deep(.el-rate) {
  display: flex;
  align-items: center;
}
:deep(.el-rate__text) {
  margin-left: 8px;
  font-size: 14px;
  color: #606266;
}
</style>
src/views/procurementManagement/purchaseOrder/index.vue
@@ -48,7 +48,7 @@
      </el-table>
    </el-card>
    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增采购订单' : '编辑采购订单'" width="800px">
    <FormDialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增采购订单' : '编辑采购订单'" :width="'800px'" :operation-type="dialogType" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
      <el-form :model="formData" ref="formRef" label-width="120px">
        <el-form-item label="供应商名称" prop="supplierName">
          <el-select v-model="formData.supplierName" placeholder="请选择供应商" style="width: 100%">
@@ -60,15 +60,12 @@
          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
    </FormDialog>
  </div>
</template>
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
在上述文件截断后对比
src/views/procurementManagement/qualityInspection/index.vue src/views/procurementManagement/returnManagement/index.vue src/views/procurementManagement/transferManagement/index.vue src/views/productManagement/productIdentifier/index.vue src/views/productionManagement/operationScheduling/components/formDia.vue src/views/productionManagement/operationScheduling/index.vue src/views/productionManagement/processRoute/Edit.vue src/views/productionManagement/processRoute/ItemsForm.vue src/views/productionManagement/processRoute/New.vue src/views/productionManagement/processRoute/index.vue src/views/productionManagement/processRoute/processRouteItem/index.vue src/views/productionManagement/productStructure/Detail/index.vue src/views/productionManagement/productStructure/StructureEdit.vue src/views/productionManagement/productStructure/index.vue src/views/productionManagement/productionCosting/index.vue src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue src/views/productionManagement/productionDispatching/components/formDia.vue src/views/productionManagement/productionDispatching/index.vue src/views/productionManagement/productionOrder/index.vue src/views/productionManagement/productionProcess/Edit.vue src/views/productionManagement/productionProcess/New.vue src/views/productionManagement/productionProcess/index.vue src/views/productionManagement/productionReporting/Input.vue src/views/productionManagement/productionReporting/Output.vue src/views/productionManagement/productionReporting/components/formDia.vue src/views/productionManagement/productionReporting/index.vue src/views/productionManagement/workOrder/index.vue src/views/qualityManagement/finalInspection/components/filesDia.vue src/views/qualityManagement/finalInspection/components/formDia.vue src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue src/views/qualityManagement/metricBinding/index.vue src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue src/views/qualityManagement/metricMaintenance/index.vue src/views/qualityManagement/metricMaintenance/index0.vue src/views/qualityManagement/nonconformingManagement/components/formDia.vue src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue src/views/qualityManagement/processInspection/components/formDia.vue src/views/qualityManagement/processInspection/components/inspectionFormDia.vue src/views/qualityManagement/rawMaterialInspection/components/formDia.vue src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue src/views/reportAnalysis/dataDashboard/index.vue src/views/reportAnalysis/projectProfit/index.vue src/views/reportAnalysis/reportManagement/index.vue src/views/salesManagement/deliveryLedger/index.vue src/views/salesManagement/indicatorStats/index.vue src/views/salesManagement/invoiceLedger/index.vue src/views/salesManagement/invoiceRegistration/index.vue src/views/salesManagement/orderManagement/index.vue src/views/salesManagement/paymentShipping/index.vue src/views/salesManagement/receiptPayment/index.vue src/views/salesManagement/receiptPaymentHistory/index.vue src/views/salesManagement/receiptPaymentLedger/index.vue src/views/salesManagement/salesLedger/fileList.vue src/views/salesManagement/salesLedger/index.vue src/views/salesManagement/salesQuotation/index.vue src/views/salesManagement/salespersonManagement/index.vue src/views/system/dept/index.vue src/views/system/user/index.vue vite.config.js