From 1c0863efe062af3ebcdecb8c10568d779f5c8295 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期一, 26 一月 2026 15:10:55 +0800
Subject: [PATCH] Merge remote-tracking branch 'refs/remotes/origin/dev_New' into dev_tide_mis_xindao
---
src/api/basicData/enum.js | 23
src/assets/BI/yuancailiaoyijianicon@2x.png | 0
src/api/inventoryManagement/stockInRecord.js | 27
src/assets/icons/png/walletGreen@2x.png | 0
src/views/lavorissue/ledger/filesDia.vue | 0
src/views/productionManagement/productionDispatching/index.vue | 510
src/views/fileManagement/document/attachmentManager.vue | 426
src/views/productionManagement/productStructure/index.vue | 416
src/api/salesManagement/strategyControl.js | 202
src/views/salesManagement/invoiceLedger/index.vue | 112
multiple/assets/screen/JLSNView.png | 0
src/api/inspectionManagement/index.js | 61
src/views/inventoryManagement/receiptManagement/index.vue | 226
multiple/assets/favicon/MXSCIco.ico | 0
multiple/assets/screen/HXSJView.png | 0
src/views/personnelManagement/analytics/index.vue | 702
src/views/reportAnalysis/projectProfit/index.vue | 190
src/assets/BI/hetongicon.png | 0
src/assets/BI/backImage@2x.png | 0
src/api/salesManagement/salesLedger.js | 8
src/api/collaborativeApproval/attendanceManagement.js | 162
multiple/assets/favicon/BHMY.ico | 0
src/views/customerService/feedbackRegistration/index.vue | 21
src/views/salesManagement/orderManagement/index.vue | 490
src/api/fileManagement/return.js | 61
multiple/assets/favicon/BDSMico.ico | 0
multiple/assets/screen/JZYJView.png | 0
src/views/collaborativeApproval/noticeManagement/index.vue | 43
multiple/assets/screen/DHDCView.png | 0
src/views/basicData/supplierManage/index.vue | 549
src/views/equipmentManagement/upkeep/Form/PlanModal.vue | 188
src/views/reportAnalysis/dataDashboard/index.vue | 2036 +
src/views/lavorissue/statistics/index.vue | 285
src/views/procurementManagement/paymentEntry/index.vue | 428
multiple/assets/favicon/CMNYico.ico | 0
src/views/personnelManagement/selfService/index.vue | 800
src/assets/BI/hetongjineback@2x.png | 0
src/views/fileManagement/bookshelf/detail.vue | 110
src/views/customerService/afterSalesHandling/index.vue | 19
src/views/energyManagement/energyPeriodTime/index.vue | 32
src/views/system/dept/index.vue | 452
src/api/procurementManagement/procurementInvoiceLedger.js | 15
src/views/equipmentManagement/inspectionManagement/components/qrCodeDia.vue | 132
src/views/equipmentManagement/brand/index.vue | 217
multiple/assets/favicon/ZYRQico.ico | 0
src/views/equipmentManagement/inspectionManagement/index.vue | 354
src/views/productionManagement/productionReporting/Input.vue | 115
src/api/collaborativeApproval/shipmentReview.js | 21
src/assets/icons/png/walletRed@2x.png | 0
src/views/equipmentManagement/deviceInfo/index.vue | 190
src/assets/images/video.png | 0
src/api/inventoryManagement/stockOut.js | 28
src/views/reportAnalysis/taxComparison/index.vue | 21
src/views/equipmentManagement/ledger/Modal.vue | 2
src/api/basicData/productModel.js | 9
src/views/equipmentManagement/upkeep/Form/formDia.vue | 304
src/views/inventoryManagement/stockManagement/Subtract.vue | 199
src/views/lavorissue/ledger/Modal.vue | 70
multiple/assets/screen/LQMView.png | 0
src/components/DynamicTable/index.vue | 402
multiple/assets/logo/DZYSLogo.png | 0
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue | 996
multiple/assets/logo/MKZSLogo.png | 0
src/api/salesManagement/salespersonManagement.js | 35
multiple/assets/favicon/ZQHXico.ico | 0
src/assets/icons/png/circleOrange@2x.png | 0
src/views/productionManagement/productionProcess/Edit.vue | 132
multiple/assets/screen/JMSLView.png | 0
multiple/assets/screen/DHHBView.png | 0
src/api/personnelManagement/staffAnalytics.js | 26
src/layout/components/Sidebar/Logo.vue | 6
src/views/qualityManagement/visualization/qualityDashboard.vue | 307
src/views/inventoryManagement/receiptManagement/Record.vue | 250
src/views/financialManagement/financialStatements/index.vue | 941
src/views/salesManagement/deliveryLedger/index.vue | 661
src/views/productionManagement/operationScheduling/components/formDia.vue | 130
src/views/equipmentManagement/repair/Modal/MaintainModal.vue | 115
src/views/customerService/afterSalesHandling/components/formDia.vue | 9
src/views/personnelManagement/employeeRecord/index.vue | 108
multiple/assets/screen/HCKXView.png | 0
src/api/productionManagement/productProcessRoute.js | 54
src/views/basicData/supplierManage/filesDia.vue | 203
multiple/assets/logo/TYMKLogo.png | 0
src/views/qualityManagement/nonconformingManagement/components/formDia.vue | 5
multiple/assets/favicon/HCKXico.ico | 0
src/api/procurementManagement/arrivalManagement.js | 43
multiple/assets/screen/HHKJView.png | 0
multiple/assets/favicon/JYHJico.ico | 0
src/views/monitorManagement/videoMonitor/index.vue | 990
src/api/equipmentManagement/calibration.js | 8
src/assets/BI/icon@2x.png | 0
src/api/publicApi/index.js | 42
src/views/energyManagement/energyTrends/index.vue | 23
src/views/inventoryManagement/dispatchLog/Record.vue | 711
multiple/assets/favicon/TYMKico.ico | 0
multiple/assets/favicon/DHDCico.ico | 0
multiple/assets/screen/HSXView.png | 0
multiple/assets/favicon/DZYSico.ico | 0
multiple/assets/favicon/HSMYico.ico | 0
src/assets/BI/pieback@2x.png | 0
src/views/basicData/supplierManage/components/HomeTab.vue | 569
src/views/collaborativeApproval/enterpriseBook/index.vue | 6
src/views/financialManagement/expenseManagement/Modal.vue | 192
src/assets/BI/kehuhetongback@2x.png | 0
multiple/assets/favicon/TJKHico.ico | 0
src/views/procurementManagement/arrivalManagement/index.vue | 237
src/views/procurementManagement/priceManagement/index.vue | 273
multiple/assets/screen/HXGYView.png | 0
src/views/oaSystem/projectManagement/components/milestoneList.vue | 289
src/views/personnelManagement/dimission/index.vue | 71
src/assets/images/chartCard3.svg | 1
src/views/collaborativeApproval/notificationManagement/summary/index.vue | 8
src/api/productionManagement/workOrder.js | 25
src/api/inventoryManagement/stockUninventory.js | 27
src/assets/BI/jiantou@2x.png | 0
src/views/basicData/product/index.vue | 2
multiple/assets/screen/NYDLView.png | 0
multiple/assets/screen/RTSWView.png | 0
src/views/qualityManagement/metricMaintenance/index0.vue | 415
multiple/assets/favicon/TJXM.ico | 0
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue | 1
src/api/equipmentManagement/upkeep.js | 32
src/api/customerService/index.js | 36
src/views/qualityManagement/finalInspection/index.vue | 9
src/views/salesManagement/paymentShipping/index.vue | 497
src/api/basicData/supplierManageFile.js | 25
src/api/financialManagement/expenseManagement.js | 7
multiple/assets/favicon/RZNY.ico | 0
multiple/assets/logo/BHMYLogo.png | 0
src/views/customerService/expiryAfterSales/components/formDia.vue | 283
src/views/salesManagement/salespersonManagement/index.vue | 371
src/views/equipmentManagement/gasTank/simple.vue | 6
src/views/productionManagement/processRoute/New.vue | 194
src/views/personnelManagement/contractManagement/components/formDia.vue | 31
multiple/assets/favicon/JSMYico.ico | 0
multiple/assets/screen/HCMYView.png | 0
multiple/assets/screen/BDSMView.png | 0
src/assets/BI/hetongjineicon1@2x.png | 0
src/views/collaborativeApproval/shipmentReview/index.vue | 340
src/views/inventoryManagement/stockManagement/index.vue | 398
src/views/equipmentManagement/ledger/index.vue | 77
src/views/inventoryManagement/dispatchLog/index.vue | 397
multiple/multiple-build.js | 86
src/api/inspectionUpload/index.js | 43
src/assets/BI/shujutongji@2x.png | 0
src/api/procurementManagement/transferManagement.js | 34
src/views/financialManagement/revenueManagement/index.vue | 249
src/api/procurementManagement/advancedPriceManagement.js | 38
src/api/procurementManagement/returnManagement.js | 35
src/api/fileManagement/statistics.js | 75
multiple/assets/favicon/PHMKico.ico | 0
src/views/energyManagement/waterManagement/components/formDia.vue | 9
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue | 8
multiple/assets/logo/ZYRQLogo.png | 0
multiple/assets/screen/HSMYView.png | 0
src/api/procurementManagement/procurementPlan.js | 47
src/views/inventoryManagement/stockReport/index.vue | 6
src/views/procurementManagement/returnManagement/index.vue | 271
multiple/assets/logo/JSYNYLogo.png | 0
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue | 1
src/views/productionManagement/processRoute/processRouteItem/index.vue | 876
src/views/collaborativeApproval/approvalProcess/index.vue | 30
src/views/oaSystem/projectManagement/components/projectForm.vue | 221
src/assets/BI/zonghetongbingtubiankuang@2x.png | 0
multiple/assets/logo/JLSNLogo.png | 0
src/api/personnelManagement/employeeRecord.js | 9
src/views/fileManagement/return/index.vue | 699
src/views/oaSystem/projectManagement/components/phaseGoalList.vue | 165
multiple/assets/screen/QLMCView.png | 0
multiple/assets/logo/DHHBLogo.png | 0
src/api/productionManagement/processRoute.js | 42
src/layout/components/Sidebar/index.vue | 1
src/views/reportAnalysis/reportManagement.vue | 733
multiple/assets/logo/CMNYLogo.png | 0
src/views/chatHome/chatHomeIndex/MobileChat.vue | 6
src/views/example/DynamicTableExample.vue | 354
src/views/fileManagement/bookshelf/index.vue | 688
src/assets/BI/shijianmingchengbeijing@2x.png | 0
multiple/assets/favicon/HXGYico.ico | 0
src/views/equipmentManagement/defectManagement/index.vue | 221
src/components/PIMTable/PIMTable.vue | 36
multiple/assets/screen/TJXMView.png | 0
src/views/qualityManagement/nearExpiryReturn/index.vue | 395
multiple/assets/logo/JSMYLogo.png | 0
src/api/collaborativeApproval/noticeManagement.js | 86
multiple/assets/favicon/HXSJico.ico | 0
src/api/equipmentManagement/deviceInfo.js | 10
src/api/basicData/productProcess.js | 10
multiple/assets/logo/JMSLLogo.png | 0
src/views/oaSystem/projectManagement/projectDetail.vue | 565
src/views/index.vue | 245
multiple/assets/favicon/HSXico.ico | 0
src/views/energyManagement/energyPower/index.vue | 19
src/views/energyManagement/meterCollection/index.vue | 6
src/components/PageHeader/index.vue | 53
src/views/personnelManagement/contractManagement/index.vue | 40
multiple/assets/favicon/QLMCico.ico | 0
src/assets/images/chartCard2.svg | 1
src/views/basicData/customerFile/index.vue | 57
multiple/assets/logo/logo.png | 0
src/views/fileManagement/statistics/index.vue | 539
src/views/procurementManagement/advancedPriceManagement/index.vue | 773
src/permission.js | 2
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue | 8
src/views/salesManagement/strategyControl/index.vue | 1587 +
src/views/inventoryManagement/issueManagement/index.vue | 170
src/views/salesManagement/salesQuotation/index.vue | 1035
src/views/productionManagement/operationScheduling/index.vue | 71
src/views/energyManagement/gasManagement/index.vue | 2
README.md | 1
src/views/oaSystem/projectManagement/components/taskTree.vue | 834
multiple/assets/screen/MXSCBack.png | 0
src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue | 153
src/assets/BI/hetongjineicon@2x.png | 0
src/views/productionManagement/productionReporting/index.vue | 796
src/main.js | 229
src/layout/components/index.js | 1
src/api/productionManagement/productionProductInput.js | 11
src/views/equipmentManagement/measurementEquipment/components/rowClickData.vue | 128
src/assets/indexViews/LCLogo.png | 0
src/views/procurementManagement/qualityInspection/index.vue | 282
multiple/assets/screen/AYNMView.png | 0
src/layout/components/Navbar.vue | 159
src/views/equipmentManagement/inspectionManagement/components/formDia.vue | 302
src/views/procurementManagement/purchaseOrder/index.vue | 185
multiple/assets/logo/RZNYLogo.png | 0
multiple/assets/screen/XYHBView.png | 0
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue | 45
multiple/assets/screen/TYMKView.png | 0
multiple/assets/favicon/favicon.ico | 0
multiple/assets/logo/TJKHLogo.png | 0
multiple/assets/logo/ZQHXLogo.png | 0
multiple/assets/logo/HXGYLogo.png | 0
src/views/energyManagement/waterManagement/index.vue | 19
src/views/oaSystem/projectManagement/index.vue | 481
src/views/qualityManagement/finalInspection/components/formDia.vue | 114
multiple/assets/logo/WDSYLogo.png | 0
src/views/equipmentManagement/repair/index.vue | 244
multiple/assets/logo/HXSJLogo.png | 0
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue | 246
multiple/assets/logo/ZGLTLogo.png | 0
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue | 129
multiple/assets/screen/ZDXMView.png | 0
src/api/system/post.js | 9
multiple/assets/logo/HCMYLogo.png | 0
src/api/equipmentManagement/brand.js | 93
src/api/financialManagement/accounting.js | 28
src/views/energyManagement/dynamicEnergySaving/index.vue | 6
src/views/financialManagement/accounting/index.vue | 197
src/api/collaborativeApproval/enterpriseBook.js | 72
src/views/personnelManagement/scheduling/index.vue | 622
multiple/assets/favicon/HCMYico.ico | 0
multiple/assets/screen/TJKHView.png | 0
src/api/inventoryManagement/stockInventory.js | 37
src/views/collaborativeApproval/sealManagement/index.vue | 6
multiple/assets/logo/新缆(江苏)数字科技有限公司.png | 0
src/api/inventoryManagement/stockManage.js | 9
src/api/equipmentManagement/spareParts.js | 58
src/views/energyManagement/energyPower/components/formDia.vue | 9
src/views/personnelManagement/employeeRecord/components/RenewContract.vue | 141
src/views/equipmentManagement/calibration/index.vue | 50
multiple/assets/logo/MXSCLogo.png | 0
src/views/procurementManagement/invoiceEntry/components/Modal.vue | 935
src/assets/BI/guochengyijianicon@2x.png | 0
multiple/assets/logo/南通云从工业互联网有限公司.png | 0
src/api/productionManagement/productionReporting.js | 10
src/api/qualityManagement/metricMaintenance.js | 119
src/views/productionManagement/productStructure/StructureEdit.vue | 311
.env.development | 4
src/layout/components/NotificationCenter/index.vue | 372
multiple/assets/favicon/AYNMico.ico | 0
src/views/reportAnalysis/reportManagement/index.vue | 1471 +
src/api/equipmentManagement/defectManagement.js | 44
src/api/viewIndex.js | 18
src/api/reportAnalysis/qualityReport.js | 52
src/views/example/SimpleExample.vue | 135
multiple/assets/favicon/NYDLico.ico | 0
multiple/assets/logo/RTSWLogo.png | 0
multiple/assets/favicon/DHHBico.ico | 0
multiple/assets/logo/NYDLLogo.png | 0
multiple/assets/logo/BDSMLogo.png | 0
src/views/procurementManagement/invoiceEntry/index.vue | 44
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue | 78
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue | 12
src/api/qualityManagement/nearExpiryReturn.js | 46
src/components/Dialog/ImportDialog.vue | 172
src/views/qualityManagement/metricBinding/index.vue | 504
src/assets/icons/png/walletBlue@2x.png | 0
src/utils/util.js | 33
src/views/financialManagement/inventoryAccounting/index.vue | 2
src/api/oaSystem/projectManagement.js | 154
src/api/fileManagement/bookshelf.js | 128
src/api/salesManagement/receiptPayment.js | 2
src/views/fileManagement/document/index.vue | 1416 +
src/views/procurementManagement/procurementReport/index.vue | 380
src/components/QRCodeGenerator/index.vue | 566
src/views/lavorissue/ledger/Form.vue | 158
src/views/procurementManagement/transferManagement/index.vue | 431
src/views/productionManagement/productionReporting/components/formDia.vue | 39
src/views/productionManagement/productionProcess/index.vue | 302
src/views/basicData/product/ProductSelectDialog.vue | 180
src/views/personnelManagement/payrollManagement/components/formDia.vue | 14
src/views/personnelManagement/payrollManagement/index.vue | 1
multiple/assets/favicon/HHKJIco.ico | 0
multiple/assets/logo/XYHBLogo.png | 0
multiple/assets/screen/ZYRQView.png | 0
src/api/procurementManagement/procurementLedger.js | 27
src/api/productionManagement/productionOrder.js | 102
src/api/salesManagement/deliveryLedger.js | 39
src/views/financialManagement/revenueManagement/Modal.vue | 192
index.html | 386
src/views/procurementManagement/procurementPlan/index.vue | 772
multiple/assets/screen/RZNYView.png | 0
src/assets/BI/border@2x.png | 0
src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue | 7
multiple/assets/favicon/ZDXMico.ico | 0
src/views/equipmentManagement/upkeep/index.vue | 764
src/views/salesManagement/customerManagement/index.vue | 423
src/views/collaborativeApproval/shipmentReview/fileList.vue | 43
src/views/equipmentManagement/operationManagement/index.vue | 370
multiple/assets/screen/ZQHXView.png | 0
src/views/equipmentManagement/inspectionManagement/components/viewQrCodeFiles.vue | 169
multiple/assets/logo/HYSNLogo.png | 0
src/router/index.js | 213
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue | 112
src/api/productionManagement/productionProductMain.js | 11
multiple/assets/logo/HGJJLogo.png | 0
src/components/Dialog/FormDialog.vue | 73
src/assets/icons/png/walletYellow@2x.png | 0
src/api/personnelManagement/scheduling.js | 32
src/views/customerService/feedbackRegistration/components/formDia.vue | 9
multiple/assets/favicon/HGJJico.ico | 0
src/views/productionManagement/workOrder/index.vue | 653
src/views/equipmentManagement/iotMonitor/index.vue | 8
src/views/procurementManagement/index.vue | 418
src/views/productionManagement/productionDispatching/components/formDia.vue | 44
multiple/assets/logo/PHMKLogo.png | 0
src/views/productionManagement/processRoute/ItemsForm.vue | 531
src/api/personnelManagement/staffContract.js | 10
src/api/fileManagement/borrow.js | 47
src/api/productionManagement/productStructure.js | 18
src/api/productionManagement/productBom.js | 57
src/components/Dialog/FileListDialog.vue | 309
multiple/assets/favicon/RTSWico.ico | 0
src/api/salesManagement/paymentShipping.js | 35
src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue | 322
src/views/productionManagement/productionCosting/index.vue | 32
multiple/assets/logo/JLMYLogo.png | 0
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue | 21
multiple/assets/favicon/HYSNico.ico | 0
src/views/equipmentManagement/kplMonitor/index.vue | 714
src/views/monitorManagement/areaControl/index.vue | 264
src/views/productionManagement/productStructure/Detail/index.vue | 449
src/views/system/user/index.vue | 1231
src/views/login.vue | 23
src/api/personnelManagement/staffLeave.js | 33
multiple/assets/screen/WDSYView.png | 0
src/views/personnelManagement/contractManagement/filesDia.vue | 10
multiple/assets/logo/ZDXMLogo.png | 0
src/views/basicData/supplierManage/components/BlacklistTab.vue | 564
src/views/productionManagement/processRoute/Edit.vue | 252
src/api/salesManagement/indicatorStats.js | 20
multiple/assets/logo/AYNMLogo.png | 0
src/views/financialManagement/loanManagement/Modal.vue | 222
src/views/qualityManagement/rawMaterialInspection/index.vue | 9
multiple/assets/screen/HGJJView.png | 0
src/assets/icons/png/walletOrange@2x.png | 0
src/assets/BI/shujutongjiicon@2x.png | 0
src/views/qualityManagement/processInspection/index.vue | 9
src/assets/BI/shijianmingxiicon@2x.png | 0
src/views/energyManagement/waterManagement/components/waterBillForm.vue | 9
multiple/assets/screen/HYSNView.png | 0
multiple/assets/logo/LCLogo.png | 0
src/api/qualityManagement/qualityTestStandardBinding.js | 28
src/assets/BI/chuchangyijianicon@2x.png | 0
multiple/assets/logo/JYHJLogo.png | 0
src/api/personnelManagement/selfService.js | 71
src/views/personnelManagement/employeeRecord/components/Show.vue | 73
multiple/assets/logo/JZYJLogo.png | 0
src/api/productionManagement/productionProductOutput.js | 11
multiple/assets/logo/芯导软件(江苏)有限公司.png | 0
src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue | 7
multiple/assets/favicon/JZYJico.ico | 0
src/views/qualityManagement/nonconformingManagement/index.vue | 16
src/views/salesManagement/receiptPaymentHistory/index.vue | 141
multiple/assets/favicon/JLMYico.ico | 0
src/views/procurementManagement/procurementInvoiceLedger/index.vue | 16
src/views/productionManagement/processRoute/index.vue | 204
src/views/salesManagement/receiptPaymentLedger/index.vue | 29
src/views/salesManagement/salesLedger/index.vue | 2465 +
multiple/assets/logo/CJNYLogo.png | 0
multiple/assets/logo/DHDCLogo.png | 0
multiple/assets/favicon/WDSYico.ico | 0
src/api/productionManagement/processRouteItem.js | 38
src/views/inventoryManagement/stockManagement/Unqualified.vue | 158
src/api/collaborativeApproval/rpaManagement.js | 13
src/assets/BI/biaoti.png | 0
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue | 1
src/views/equipmentManagement/spareParts/index.vue | 401
src/views/salesManagement/receiptPayment/index.vue | 439
src/views/productionManagement/productionReporting/Output.vue | 106
multiple/assets/favicon/JSYNYico.ico | 0
src/assets/images/chartCard.svg | 1
src/views/inventoryManagement/stockManagement/Qualified.vue | 158
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue | 30
src/views/equipmentManagement/iotMonitor/indexWD.vue | 8
src/views/salesManagement/invoiceRegistration/index.vue | 1268
src/views/qualityManagement/finalInspection/components/filesDia.vue | 1
src/utils/request.js | 2
multiple/assets/logo/TJXMLogo.png | 0
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue | 321
src/views/customerService/expiryAfterSales/index.vue | 258
src/layout/components/AppMain.vue | 14
multiple/assets/favicon/CJNYico.ico | 0
multiple/assets/favicon/LQMico.ico | 0
src/views/equipmentManagement/measurementEquipment/components/formDia.vue | 111
src/api/inventoryManagement/stockIn.js | 17
src/views/equipmentManagement/ledger/Form.vue | 115
src/components/Echarts/echarts.vue | 8
multiple/assets/logo/QLMCLogo.png | 0
src/views/fileManagement/borrow/index.vue | 647
src/views/qualityManagement/metricMaintenance/index.vue | 1161
src/views/lavorissue/ledger/index.vue | 300
multiple/assets/logo/HCKXLogo.png | 0
multiple/assets/screen/PHMKView.png | 0
src/api/financialManagement/loanManagement.js | 37
src/views/qualityManagement/processInspection/components/formDia.vue | 129
src/views/inventoryManagement/stockManagement/New.vue | 180
multiple/assets/favicon/JMSLico.ico | 0
src/assets/BI/caiwufenxiback@2x.png | 0
src/views/equipmentManagement/repair/Modal/RepairModal.vue | 190
multiple/assets/logo/HHKJLogo.png | 0
multiple/assets/logo/LQMLogo.png | 0
src/assets/indexViews/login-background.png | 0
src/views/productionManagement/safetyMonitoring/index.vue | 4
multiple/assets/logo/HSMYLogo.png | 0
multiple/assets/favicon/XYHBico.ico | 0
src/views/financialManagement/loanManagement/index.vue | 271
src/views/financialManagement/expenseManagement/index.vue | 245
src/views/procurementManagement/procurementLedger/index.vue | 3055 +-
src/views/equipmentManagement/measurementEquipment/index.vue | 154
src/views/productionManagement/productionOrder/index.vue | 723
src/api/system/message.js | 45
.env.production | 10
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue | 123
src/views/productionManagement/productionProcess/New.vue | 99
src/views/procurementManagement/paymentHistory/index.vue | 116
multiple/assets/favicon/JLSNico.ico | 0
multiple/assets/screen/MKZSView.png | 0
multiple/config.json | 23
multiple/assets/screen/DZYSView.png | 0
src/assets/icons/png/circleBlue@2x.png | 0
src/views/productManagement/productIdentifier/index.vue | 819
.env.staging | 6
src/views/equipmentManagement/measurementEquipment/filesDia.vue | 18
src/assets/logo/敦煌鼎诚.png | 0
src/assets/icons/png/circleYellow@2x.png | 0
multiple/assets/favicon/MKZSico.ico | 0
src/api/productionManagement/productionProcess.js | 69
src/assets/icons/png/circleRed@2x.png | 0
multiple/assets/logo/HSXLogo.png | 0
src/api/fileManagement/document.js | 189
src/api/equipmentManagement/measurementEquipment.js | 20
src/views/procurementManagement/paymentLedger/index.vue | 45
/dev/null | 730
src/views/personnelManagement/dimission/components/formDia.vue | 445
src/assets/BI/hetongtitleback@2x.png | 0
src/views/salesManagement/indicatorStats/index.vue | 399
src/assets/icons/png/circleGreen@2x.png | 0
src/views/energyManagement/carbonManagement/index.vue | 1553 +
src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue | 96
src/api/procurementManagement/procurementReport.js | 11
472 files changed, 62,900 insertions(+), 11,357 deletions(-)
diff --git a/.env.development b/.env.development
index 1a63995..e3c6324 100644
--- a/.env.development
+++ b/.env.development
@@ -1,8 +1,8 @@
# 椤甸潰鏍囬
-VITE_APP_TITLE = 鑺浜戯紙绠$悊淇℃伅绯荤粺锛�
+VITE_APP_TITLE = 涓皬浼佷笟鏁板瓧鍖栬浆鍨嬩簩绾у椁愬寘
# 寮�鍙戠幆澧冮厤缃�
VITE_APP_ENV = 'development'
-# 鑺浜戯紙绠$悊淇℃伅绯荤粺锛�/寮�鍙戠幆澧�
+# 涓皬浼佷笟鏁板瓧鍖栬浆鍨嬩簩绾у椁愬寘/寮�鍙戠幆澧�
VITE_APP_BASE_API = '/dev-api'
diff --git a/.env.production b/.env.production
index ce7dfa1..5185d9a 100644
--- a/.env.production
+++ b/.env.production
@@ -1,13 +1,5 @@
# 椤甸潰鏍囬
-VITE_APP_TITLE =鑺-浠撳偍鐗╂祦绯荤粺
-#10.136.58.65 鑺-璐㈠姟绠$悊绯荤粺 cwglxt
-#10.136.58.66 鑺-璁惧绠$悊绯荤粺 sbglxt
-#10.136.58.67 鑺-鐢熶骇绠℃帶绯荤粺 scgkxt
-#10.136.58.68 鑺-鍗忓悓鍔炲叕绯荤粺 xtbgxt
-#10.136.58.69 鑺-閲囪喘绠$悊绯荤粺 cgglxt
-#10.136.58.70 鑺-浠撳偍鐗╂祦绯荤粺 ccwlxt
-#10.136.58.71 鑺-钀ラ攢绠$悊绯荤粺 yxglxt
-#10.136.58.72 鑺-浜哄姏璧勬簮绯荤粺 rlzyxt
+VITE_APP_TITLE = MIS绠$悊绯荤粺
# 鐢熶骇鐜閰嶇疆
VITE_APP_ENV = 'production'
diff --git a/.env.staging b/.env.staging
index 468abb3..61211c6 100644
--- a/.env.staging
+++ b/.env.staging
@@ -1,11 +1,11 @@
# 椤甸潰鏍囬
-VITE_APP_TITLE = 鑺浜戯紙绠$悊淇℃伅绯荤粺锛�
+VITE_APP_TITLE = 涓皬浼佷笟鏁板瓧鍖栬浆鍨嬩簩绾у椁愬寘
# 鐢熶骇鐜閰嶇疆
VITE_APP_ENV = 'staging'
-# 鑺浜戯紙绠$悊淇℃伅绯荤粺锛�/鐢熶骇鐜
+# 涓皬浼佷笟鏁板瓧鍖栬浆鍨嬩簩绾у椁愬寘/鐢熶骇鐜
VITE_APP_BASE_API = '/stage-api'
# 鏄惁鍦ㄦ墦鍖呮椂寮�鍚帇缂╋紝鏀寔 gzip 鍜� brotli
-VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
+VITE_BUILD_COMPRESS = gzip
diff --git a/README.md b/README.md
index 2387eaf..073e101 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@
# 鏋勫缓娴嬭瘯鐜 yarn build:stage
# 鏋勫缓鐢熶骇鐜 yarn build:prod
+# 鏋勫缓鐢熶骇鐜 yarn build:prod -- --company="AAA"
# 鍓嶇璁块棶鍦板潃 http://localhost:80
```
diff --git a/index.html b/index.html
index 2e6580e..a684690 100644
--- a/index.html
+++ b/index.html
@@ -1,215 +1,217 @@
<!DOCTYPE html>
<html>
-
-<head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
- <meta name="renderer" content="webkit">
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
- <link rel="icon" href="/favicon.ico">
- <title>%VITE_APP_TITLE%</title>
- <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
- <style>
- html,
- body,
- #app {
- height: 100%;
- margin: 0px;
- padding: 0px;
- }
-
- .chromeframe {
- margin: 0.2em 0;
- background: #ccc;
- color: #000;
- padding: 0.2em 0;
- }
-
- #loader-wrapper {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 999999;
- }
-
- #loader {
- display: block;
- position: relative;
- left: 50%;
- top: 50%;
- width: 150px;
- height: 150px;
- margin: -75px 0 0 -75px;
- border-radius: 50%;
- border: 3px solid transparent;
- border-top-color: #FFF;
- -webkit-animation: spin 2s linear infinite;
- -ms-animation: spin 2s linear infinite;
- -moz-animation: spin 2s linear infinite;
- -o-animation: spin 2s linear infinite;
- animation: spin 2s linear infinite;
- z-index: 1001;
- }
-
- #loader:before {
- content: "";
- position: absolute;
- top: 5px;
- left: 5px;
- right: 5px;
- bottom: 5px;
- border-radius: 50%;
- border: 3px solid transparent;
- border-top-color: #FFF;
- -webkit-animation: spin 3s linear infinite;
- -moz-animation: spin 3s linear infinite;
- -o-animation: spin 3s linear infinite;
- -ms-animation: spin 3s linear infinite;
- animation: spin 3s linear infinite;
- }
-
- #loader:after {
- content: "";
- position: absolute;
- top: 15px;
- left: 15px;
- right: 15px;
- bottom: 15px;
- border-radius: 50%;
- border: 3px solid transparent;
- border-top-color: #FFF;
- -moz-animation: spin 1.5s linear infinite;
- -o-animation: spin 1.5s linear infinite;
- -ms-animation: spin 1.5s linear infinite;
- -webkit-animation: spin 1.5s linear infinite;
- animation: spin 1.5s linear infinite;
- }
-
-
- @-webkit-keyframes spin {
- 0% {
- -webkit-transform: rotate(0deg);
- -ms-transform: rotate(0deg);
- transform: rotate(0deg);
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+ <meta name="renderer" content="webkit" />
+ <meta
+ name="viewport"
+ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
+ />
+ <link rel="icon" href="/favicon.ico" />
+ <title>%VITE_APP_TITLE%</title>
+ <!--[if lt IE 11
+ ]><script>
+ window.location.href = "/html/ie.html";
+ </script><!
+ [endif]-->
+ <style>
+ html,
+ body,
+ #app {
+ height: 100%;
+ margin: 0px;
+ padding: 0px;
}
- 100% {
- -webkit-transform: rotate(360deg);
- -ms-transform: rotate(360deg);
- transform: rotate(360deg);
- }
- }
-
- @keyframes spin {
- 0% {
- -webkit-transform: rotate(0deg);
- -ms-transform: rotate(0deg);
- transform: rotate(0deg);
+ .chromeframe {
+ margin: 0.2em 0;
+ background: #ccc;
+ color: #000;
+ padding: 0.2em 0;
}
- 100% {
- -webkit-transform: rotate(360deg);
- -ms-transform: rotate(360deg);
- transform: rotate(360deg);
+ #loader-wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 999999;
}
- }
+ #loader {
+ display: block;
+ position: relative;
+ left: 50%;
+ top: 50%;
+ width: 150px;
+ height: 150px;
+ margin: -75px 0 0 -75px;
+ border-radius: 50%;
+ border: 3px solid transparent;
+ border-top-color: #fff;
+ -webkit-animation: spin 2s linear infinite;
+ -ms-animation: spin 2s linear infinite;
+ -moz-animation: spin 2s linear infinite;
+ -o-animation: spin 2s linear infinite;
+ animation: spin 2s linear infinite;
+ z-index: 1001;
+ }
- #loader-wrapper .loader-section {
- position: fixed;
- top: 0;
- width: 51%;
- height: 100%;
- background: #7171C6;
- z-index: 1000;
- -webkit-transform: translateX(0);
- -ms-transform: translateX(0);
- transform: translateX(0);
- }
+ #loader:before {
+ content: "";
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ right: 5px;
+ bottom: 5px;
+ border-radius: 50%;
+ border: 3px solid transparent;
+ border-top-color: #fff;
+ -webkit-animation: spin 3s linear infinite;
+ -moz-animation: spin 3s linear infinite;
+ -o-animation: spin 3s linear infinite;
+ -ms-animation: spin 3s linear infinite;
+ animation: spin 3s linear infinite;
+ }
- #loader-wrapper .loader-section.section-left {
- left: 0;
- }
+ #loader:after {
+ content: "";
+ position: absolute;
+ top: 15px;
+ left: 15px;
+ right: 15px;
+ bottom: 15px;
+ border-radius: 50%;
+ border: 3px solid transparent;
+ border-top-color: #fff;
+ -moz-animation: spin 1.5s linear infinite;
+ -o-animation: spin 1.5s linear infinite;
+ -ms-animation: spin 1.5s linear infinite;
+ -webkit-animation: spin 1.5s linear infinite;
+ animation: spin 1.5s linear infinite;
+ }
- #loader-wrapper .loader-section.section-right {
- right: 0;
- }
+ @-webkit-keyframes spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ -ms-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ -ms-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+ }
- .loaded #loader-wrapper .loader-section.section-left {
- -webkit-transform: translateX(-100%);
- -ms-transform: translateX(-100%);
- transform: translateX(-100%);
- -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
- transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
- }
+ @keyframes spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ -ms-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
- .loaded #loader-wrapper .loader-section.section-right {
- -webkit-transform: translateX(100%);
- -ms-transform: translateX(100%);
- transform: translateX(100%);
- -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
- transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
- }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ -ms-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+ }
- .loaded #loader {
- opacity: 0;
- -webkit-transition: all 0.3s ease-out;
- transition: all 0.3s ease-out;
- }
+ #loader-wrapper .loader-section {
+ position: fixed;
+ top: 0;
+ width: 51%;
+ height: 100%;
+ background: #7171c6;
+ z-index: 1000;
+ -webkit-transform: translateX(0);
+ -ms-transform: translateX(0);
+ transform: translateX(0);
+ }
- .loaded #loader-wrapper {
- visibility: hidden;
- -webkit-transform: translateY(-100%);
- -ms-transform: translateY(-100%);
- transform: translateY(-100%);
- -webkit-transition: all 0.3s 1s ease-out;
- transition: all 0.3s 1s ease-out;
- }
+ #loader-wrapper .loader-section.section-left {
+ left: 0;
+ }
- .no-js #loader-wrapper {
- display: none;
- }
+ #loader-wrapper .loader-section.section-right {
+ right: 0;
+ }
- .no-js h1 {
- color: #222222;
- }
+ .loaded #loader-wrapper .loader-section.section-left {
+ -webkit-transform: translateX(-100%);
+ -ms-transform: translateX(-100%);
+ transform: translateX(-100%);
+ -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+ transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+ }
- #loader-wrapper .load_title {
- font-family: 'Open Sans';
- color: #FFF;
- font-size: 19px;
- width: 100%;
- text-align: center;
- z-index: 9999999999999;
- position: absolute;
- top: 60%;
- opacity: 1;
- line-height: 30px;
- }
+ .loaded #loader-wrapper .loader-section.section-right {
+ -webkit-transform: translateX(100%);
+ -ms-transform: translateX(100%);
+ transform: translateX(100%);
+ -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+ transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+ }
- #loader-wrapper .load_title span {
- font-weight: normal;
- font-style: italic;
- font-size: 13px;
- color: #FFF;
- opacity: 0.5;
- }
- </style>
-</head>
+ .loaded #loader {
+ opacity: 0;
+ -webkit-transition: all 0.3s ease-out;
+ transition: all 0.3s ease-out;
+ }
-<body>
- <div id="app">
- <div id="loader-wrapper">
- <div id="loader"></div>
- <div class="loader-section section-left"></div>
- <div class="loader-section section-right"></div>
- <div class="load_title">姝e湪鍔犺浇绯荤粺璧勬簮锛岃鑰愬績绛夊緟</div>
+ .loaded #loader-wrapper {
+ visibility: hidden;
+ -webkit-transform: translateY(-100%);
+ -ms-transform: translateY(-100%);
+ transform: translateY(-100%);
+ -webkit-transition: all 0.3s 1s ease-out;
+ transition: all 0.3s 1s ease-out;
+ }
+
+ .no-js #loader-wrapper {
+ display: none;
+ }
+
+ .no-js h1 {
+ color: #222222;
+ }
+
+ #loader-wrapper .load_title {
+ font-family: "Open Sans";
+ color: #fff;
+ font-size: 19px;
+ width: 100%;
+ text-align: center;
+ z-index: 9999999999999;
+ position: absolute;
+ top: 60%;
+ opacity: 1;
+ line-height: 30px;
+ }
+
+ #loader-wrapper .load_title span {
+ font-weight: normal;
+ font-style: italic;
+ font-size: 13px;
+ color: #fff;
+ opacity: 0.5;
+ }
+ </style>
+ </head>
+
+ <body>
+ <div id="app">
+ <div id="loader-wrapper">
+ <div id="loader"></div>
+ <div class="loader-section section-left"></div>
+ <div class="loader-section section-right"></div>
+ <div class="load_title">姝e湪鍔犺浇绯荤粺璧勬簮锛岃鑰愬績绛夊緟</div>
+ </div>
</div>
- </div>
- <script type="module" src="/src/main.js"></script>
-</body>
-
+ <script type="module" src="/src/main.js"></script>
+ </body>
</html>
diff --git a/multiple/assets/favicon/AYNMico.ico b/multiple/assets/favicon/AYNMico.ico
new file mode 100644
index 0000000..42f5b4a
--- /dev/null
+++ b/multiple/assets/favicon/AYNMico.ico
Binary files differ
diff --git a/multiple/assets/favicon/BDSMico.ico b/multiple/assets/favicon/BDSMico.ico
new file mode 100644
index 0000000..890782c
--- /dev/null
+++ b/multiple/assets/favicon/BDSMico.ico
Binary files differ
diff --git a/multiple/assets/favicon/BHMY.ico b/multiple/assets/favicon/BHMY.ico
new file mode 100644
index 0000000..868632b
--- /dev/null
+++ b/multiple/assets/favicon/BHMY.ico
Binary files differ
diff --git a/multiple/assets/favicon/CJNYico.ico b/multiple/assets/favicon/CJNYico.ico
new file mode 100644
index 0000000..5ad6d83
--- /dev/null
+++ b/multiple/assets/favicon/CJNYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/CMNYico.ico b/multiple/assets/favicon/CMNYico.ico
new file mode 100644
index 0000000..4536de7
--- /dev/null
+++ b/multiple/assets/favicon/CMNYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/DHDCico.ico b/multiple/assets/favicon/DHDCico.ico
new file mode 100644
index 0000000..b4c4363
--- /dev/null
+++ b/multiple/assets/favicon/DHDCico.ico
Binary files differ
diff --git a/multiple/assets/favicon/DHHBico.ico b/multiple/assets/favicon/DHHBico.ico
new file mode 100644
index 0000000..4bfd006
--- /dev/null
+++ b/multiple/assets/favicon/DHHBico.ico
Binary files differ
diff --git a/multiple/assets/favicon/DZYSico.ico b/multiple/assets/favicon/DZYSico.ico
new file mode 100644
index 0000000..abbfb0a
--- /dev/null
+++ b/multiple/assets/favicon/DZYSico.ico
Binary files differ
diff --git a/multiple/assets/favicon/HCKXico.ico b/multiple/assets/favicon/HCKXico.ico
new file mode 100644
index 0000000..22f62da
--- /dev/null
+++ b/multiple/assets/favicon/HCKXico.ico
Binary files differ
diff --git a/multiple/assets/favicon/HCMYico.ico b/multiple/assets/favicon/HCMYico.ico
new file mode 100644
index 0000000..d7ed334
--- /dev/null
+++ b/multiple/assets/favicon/HCMYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/HGJJico.ico b/multiple/assets/favicon/HGJJico.ico
new file mode 100644
index 0000000..19b583b
--- /dev/null
+++ b/multiple/assets/favicon/HGJJico.ico
Binary files differ
diff --git a/multiple/assets/favicon/HHKJIco.ico b/multiple/assets/favicon/HHKJIco.ico
new file mode 100644
index 0000000..2554cbf
--- /dev/null
+++ b/multiple/assets/favicon/HHKJIco.ico
Binary files differ
diff --git a/multiple/assets/favicon/HSMYico.ico b/multiple/assets/favicon/HSMYico.ico
new file mode 100644
index 0000000..d43a485
--- /dev/null
+++ b/multiple/assets/favicon/HSMYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/HSXico.ico b/multiple/assets/favicon/HSXico.ico
new file mode 100644
index 0000000..500081c
--- /dev/null
+++ b/multiple/assets/favicon/HSXico.ico
Binary files differ
diff --git a/multiple/assets/favicon/HXGYico.ico b/multiple/assets/favicon/HXGYico.ico
new file mode 100644
index 0000000..6182687
--- /dev/null
+++ b/multiple/assets/favicon/HXGYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/HXSJico.ico b/multiple/assets/favicon/HXSJico.ico
new file mode 100644
index 0000000..004fef6
--- /dev/null
+++ b/multiple/assets/favicon/HXSJico.ico
Binary files differ
diff --git a/public/HYSNico.ico b/multiple/assets/favicon/HYSNico.ico
similarity index 100%
rename from public/HYSNico.ico
rename to multiple/assets/favicon/HYSNico.ico
Binary files differ
diff --git a/multiple/assets/favicon/JLMYico.ico b/multiple/assets/favicon/JLMYico.ico
new file mode 100644
index 0000000..c41fc33
--- /dev/null
+++ b/multiple/assets/favicon/JLMYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/JLSNico.ico b/multiple/assets/favicon/JLSNico.ico
new file mode 100644
index 0000000..ea7aed7
--- /dev/null
+++ b/multiple/assets/favicon/JLSNico.ico
Binary files differ
diff --git a/multiple/assets/favicon/JMSLico.ico b/multiple/assets/favicon/JMSLico.ico
new file mode 100644
index 0000000..2b049ed
--- /dev/null
+++ b/multiple/assets/favicon/JMSLico.ico
Binary files differ
diff --git a/multiple/assets/favicon/JSMYico.ico b/multiple/assets/favicon/JSMYico.ico
new file mode 100644
index 0000000..81352d8
--- /dev/null
+++ b/multiple/assets/favicon/JSMYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/JSYNYico.ico b/multiple/assets/favicon/JSYNYico.ico
new file mode 100644
index 0000000..0d831be
--- /dev/null
+++ b/multiple/assets/favicon/JSYNYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/JYHJico.ico b/multiple/assets/favicon/JYHJico.ico
new file mode 100644
index 0000000..47dbc30
--- /dev/null
+++ b/multiple/assets/favicon/JYHJico.ico
Binary files differ
diff --git a/public/JZYJico.ico b/multiple/assets/favicon/JZYJico.ico
similarity index 100%
rename from public/JZYJico.ico
rename to multiple/assets/favicon/JZYJico.ico
Binary files differ
diff --git a/multiple/assets/favicon/LQMico.ico b/multiple/assets/favicon/LQMico.ico
new file mode 100644
index 0000000..f59c6a9
--- /dev/null
+++ b/multiple/assets/favicon/LQMico.ico
Binary files differ
diff --git a/multiple/assets/favicon/MKZSico.ico b/multiple/assets/favicon/MKZSico.ico
new file mode 100644
index 0000000..559a417
--- /dev/null
+++ b/multiple/assets/favicon/MKZSico.ico
Binary files differ
diff --git a/multiple/assets/favicon/MXSCIco.ico b/multiple/assets/favicon/MXSCIco.ico
new file mode 100644
index 0000000..2214848
--- /dev/null
+++ b/multiple/assets/favicon/MXSCIco.ico
Binary files differ
diff --git a/multiple/assets/favicon/NYDLico.ico b/multiple/assets/favicon/NYDLico.ico
new file mode 100644
index 0000000..73c9f4a
--- /dev/null
+++ b/multiple/assets/favicon/NYDLico.ico
Binary files differ
diff --git a/multiple/assets/favicon/PHMKico.ico b/multiple/assets/favicon/PHMKico.ico
new file mode 100644
index 0000000..dd5b404
--- /dev/null
+++ b/multiple/assets/favicon/PHMKico.ico
Binary files differ
diff --git a/multiple/assets/favicon/QLMCico.ico b/multiple/assets/favicon/QLMCico.ico
new file mode 100644
index 0000000..8a51d67
--- /dev/null
+++ b/multiple/assets/favicon/QLMCico.ico
Binary files differ
diff --git a/multiple/assets/favicon/RTSWico.ico b/multiple/assets/favicon/RTSWico.ico
new file mode 100644
index 0000000..585d1be
--- /dev/null
+++ b/multiple/assets/favicon/RTSWico.ico
Binary files differ
diff --git a/multiple/assets/favicon/RZNY.ico b/multiple/assets/favicon/RZNY.ico
new file mode 100644
index 0000000..c2f4ba5
--- /dev/null
+++ b/multiple/assets/favicon/RZNY.ico
Binary files differ
diff --git a/multiple/assets/favicon/TJKHico.ico b/multiple/assets/favicon/TJKHico.ico
new file mode 100644
index 0000000..550db27
--- /dev/null
+++ b/multiple/assets/favicon/TJKHico.ico
Binary files differ
diff --git a/multiple/assets/favicon/TJXM.ico b/multiple/assets/favicon/TJXM.ico
new file mode 100644
index 0000000..dcc4fa5
--- /dev/null
+++ b/multiple/assets/favicon/TJXM.ico
Binary files differ
diff --git a/multiple/assets/favicon/TYMKico.ico b/multiple/assets/favicon/TYMKico.ico
new file mode 100644
index 0000000..0fbc2d1
--- /dev/null
+++ b/multiple/assets/favicon/TYMKico.ico
Binary files differ
diff --git a/public/WDSYico.ico b/multiple/assets/favicon/WDSYico.ico
similarity index 100%
rename from public/WDSYico.ico
rename to multiple/assets/favicon/WDSYico.ico
Binary files differ
diff --git a/multiple/assets/favicon/XYHBico.ico b/multiple/assets/favicon/XYHBico.ico
new file mode 100644
index 0000000..3bee1fc
--- /dev/null
+++ b/multiple/assets/favicon/XYHBico.ico
Binary files differ
diff --git a/multiple/assets/favicon/ZDXMico.ico b/multiple/assets/favicon/ZDXMico.ico
new file mode 100644
index 0000000..0fb0a1d
--- /dev/null
+++ b/multiple/assets/favicon/ZDXMico.ico
Binary files differ
diff --git a/public/ZQHXico.ico b/multiple/assets/favicon/ZQHXico.ico
similarity index 100%
rename from public/ZQHXico.ico
rename to multiple/assets/favicon/ZQHXico.ico
Binary files differ
diff --git a/multiple/assets/favicon/ZYRQico.ico b/multiple/assets/favicon/ZYRQico.ico
new file mode 100644
index 0000000..8be95d0
--- /dev/null
+++ b/multiple/assets/favicon/ZYRQico.ico
Binary files differ
diff --git a/multiple/assets/favicon/favicon.ico b/multiple/assets/favicon/favicon.ico
new file mode 100644
index 0000000..0fb0a1d
--- /dev/null
+++ b/multiple/assets/favicon/favicon.ico
Binary files differ
diff --git a/multiple/assets/logo/AYNMLogo.png b/multiple/assets/logo/AYNMLogo.png
new file mode 100644
index 0000000..7f64b20
--- /dev/null
+++ b/multiple/assets/logo/AYNMLogo.png
Binary files differ
diff --git a/multiple/assets/logo/BDSMLogo.png b/multiple/assets/logo/BDSMLogo.png
new file mode 100644
index 0000000..07d0fe9
--- /dev/null
+++ b/multiple/assets/logo/BDSMLogo.png
Binary files differ
diff --git a/multiple/assets/logo/BHMYLogo.png b/multiple/assets/logo/BHMYLogo.png
new file mode 100644
index 0000000..f0e779c
--- /dev/null
+++ b/multiple/assets/logo/BHMYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/CJNYLogo.png b/multiple/assets/logo/CJNYLogo.png
new file mode 100644
index 0000000..e4e9d73
--- /dev/null
+++ b/multiple/assets/logo/CJNYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/CMNYLogo.png b/multiple/assets/logo/CMNYLogo.png
new file mode 100644
index 0000000..202664a
--- /dev/null
+++ b/multiple/assets/logo/CMNYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/DHDCLogo.png b/multiple/assets/logo/DHDCLogo.png
new file mode 100644
index 0000000..139bdd1
--- /dev/null
+++ b/multiple/assets/logo/DHDCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/DHHBLogo.png b/multiple/assets/logo/DHHBLogo.png
new file mode 100644
index 0000000..0c6d832
--- /dev/null
+++ b/multiple/assets/logo/DHHBLogo.png
Binary files differ
diff --git a/multiple/assets/logo/DZYSLogo.png b/multiple/assets/logo/DZYSLogo.png
new file mode 100644
index 0000000..53986b5
--- /dev/null
+++ b/multiple/assets/logo/DZYSLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HCKXLogo.png b/multiple/assets/logo/HCKXLogo.png
new file mode 100644
index 0000000..e15f67f
--- /dev/null
+++ b/multiple/assets/logo/HCKXLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HCMYLogo.png b/multiple/assets/logo/HCMYLogo.png
new file mode 100644
index 0000000..5910ffd
--- /dev/null
+++ b/multiple/assets/logo/HCMYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HGJJLogo.png b/multiple/assets/logo/HGJJLogo.png
new file mode 100644
index 0000000..a1bc1f1
--- /dev/null
+++ b/multiple/assets/logo/HGJJLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HHKJLogo.png b/multiple/assets/logo/HHKJLogo.png
new file mode 100644
index 0000000..018abe6
--- /dev/null
+++ b/multiple/assets/logo/HHKJLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HSMYLogo.png b/multiple/assets/logo/HSMYLogo.png
new file mode 100644
index 0000000..daec820
--- /dev/null
+++ b/multiple/assets/logo/HSMYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HSXLogo.png b/multiple/assets/logo/HSXLogo.png
new file mode 100644
index 0000000..c4150c4
--- /dev/null
+++ b/multiple/assets/logo/HSXLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HXGYLogo.png b/multiple/assets/logo/HXGYLogo.png
new file mode 100644
index 0000000..d21365d
--- /dev/null
+++ b/multiple/assets/logo/HXGYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HXSJLogo.png b/multiple/assets/logo/HXSJLogo.png
new file mode 100644
index 0000000..920c1dc
--- /dev/null
+++ b/multiple/assets/logo/HXSJLogo.png
Binary files differ
diff --git a/public/HYSNico.ico b/multiple/assets/logo/HYSNLogo.png
similarity index 100%
copy from public/HYSNico.ico
copy to multiple/assets/logo/HYSNLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JLMYLogo.png b/multiple/assets/logo/JLMYLogo.png
new file mode 100644
index 0000000..4a09b97
--- /dev/null
+++ b/multiple/assets/logo/JLMYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JLSNLogo.png b/multiple/assets/logo/JLSNLogo.png
new file mode 100644
index 0000000..ff94cd7
--- /dev/null
+++ b/multiple/assets/logo/JLSNLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JMSLLogo.png b/multiple/assets/logo/JMSLLogo.png
new file mode 100644
index 0000000..8835d46
--- /dev/null
+++ b/multiple/assets/logo/JMSLLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JSMYLogo.png b/multiple/assets/logo/JSMYLogo.png
new file mode 100644
index 0000000..512ad75
--- /dev/null
+++ b/multiple/assets/logo/JSMYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JSYNYLogo.png b/multiple/assets/logo/JSYNYLogo.png
new file mode 100644
index 0000000..6d8f65d
--- /dev/null
+++ b/multiple/assets/logo/JSYNYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JYHJLogo.png b/multiple/assets/logo/JYHJLogo.png
new file mode 100644
index 0000000..c121f92
--- /dev/null
+++ b/multiple/assets/logo/JYHJLogo.png
Binary files differ
diff --git a/src/assets/indexViews/JZYJLogo.png b/multiple/assets/logo/JZYJLogo.png
similarity index 100%
rename from src/assets/indexViews/JZYJLogo.png
rename to multiple/assets/logo/JZYJLogo.png
Binary files differ
diff --git a/multiple/assets/logo/LCLogo.png b/multiple/assets/logo/LCLogo.png
new file mode 100644
index 0000000..d18f9fd
--- /dev/null
+++ b/multiple/assets/logo/LCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/LQMLogo.png b/multiple/assets/logo/LQMLogo.png
new file mode 100644
index 0000000..84ef782
--- /dev/null
+++ b/multiple/assets/logo/LQMLogo.png
Binary files differ
diff --git a/multiple/assets/logo/MKZSLogo.png b/multiple/assets/logo/MKZSLogo.png
new file mode 100644
index 0000000..a7079a5
--- /dev/null
+++ b/multiple/assets/logo/MKZSLogo.png
Binary files differ
diff --git a/multiple/assets/logo/MXSCLogo.png b/multiple/assets/logo/MXSCLogo.png
new file mode 100644
index 0000000..1ece4b5
--- /dev/null
+++ b/multiple/assets/logo/MXSCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/NYDLLogo.png b/multiple/assets/logo/NYDLLogo.png
new file mode 100644
index 0000000..27738e4
--- /dev/null
+++ b/multiple/assets/logo/NYDLLogo.png
Binary files differ
diff --git a/multiple/assets/logo/PHMKLogo.png b/multiple/assets/logo/PHMKLogo.png
new file mode 100644
index 0000000..e179c2c
--- /dev/null
+++ b/multiple/assets/logo/PHMKLogo.png
Binary files differ
diff --git a/multiple/assets/logo/QLMCLogo.png b/multiple/assets/logo/QLMCLogo.png
new file mode 100644
index 0000000..f94d826
--- /dev/null
+++ b/multiple/assets/logo/QLMCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/RTSWLogo.png b/multiple/assets/logo/RTSWLogo.png
new file mode 100644
index 0000000..e53803a
--- /dev/null
+++ b/multiple/assets/logo/RTSWLogo.png
Binary files differ
diff --git a/multiple/assets/logo/RZNYLogo.png b/multiple/assets/logo/RZNYLogo.png
new file mode 100644
index 0000000..5296331
--- /dev/null
+++ b/multiple/assets/logo/RZNYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/TJKHLogo.png b/multiple/assets/logo/TJKHLogo.png
new file mode 100644
index 0000000..f369979
--- /dev/null
+++ b/multiple/assets/logo/TJKHLogo.png
Binary files differ
diff --git a/multiple/assets/logo/TJXMLogo.png b/multiple/assets/logo/TJXMLogo.png
new file mode 100644
index 0000000..0f260e6
--- /dev/null
+++ b/multiple/assets/logo/TJXMLogo.png
Binary files differ
diff --git a/multiple/assets/logo/TYMKLogo.png b/multiple/assets/logo/TYMKLogo.png
new file mode 100644
index 0000000..1da706f
--- /dev/null
+++ b/multiple/assets/logo/TYMKLogo.png
Binary files differ
diff --git a/src/assets/indexViews/WDSYLogo.png b/multiple/assets/logo/WDSYLogo.png
similarity index 100%
rename from src/assets/indexViews/WDSYLogo.png
rename to multiple/assets/logo/WDSYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/XYHBLogo.png b/multiple/assets/logo/XYHBLogo.png
new file mode 100644
index 0000000..b7989d8
--- /dev/null
+++ b/multiple/assets/logo/XYHBLogo.png
Binary files differ
diff --git a/multiple/assets/logo/ZDXMLogo.png b/multiple/assets/logo/ZDXMLogo.png
new file mode 100644
index 0000000..072a936
--- /dev/null
+++ b/multiple/assets/logo/ZDXMLogo.png
Binary files differ
diff --git a/multiple/assets/logo/ZGLTLogo.png b/multiple/assets/logo/ZGLTLogo.png
new file mode 100644
index 0000000..a4a4587
--- /dev/null
+++ b/multiple/assets/logo/ZGLTLogo.png
Binary files differ
diff --git a/src/assets/indexViews/ZQHXLogo.png b/multiple/assets/logo/ZQHXLogo.png
similarity index 100%
rename from src/assets/indexViews/ZQHXLogo.png
rename to multiple/assets/logo/ZQHXLogo.png
Binary files differ
diff --git a/multiple/assets/logo/ZYRQLogo.png b/multiple/assets/logo/ZYRQLogo.png
new file mode 100644
index 0000000..bc6fe5d
--- /dev/null
+++ b/multiple/assets/logo/ZYRQLogo.png
Binary files differ
diff --git a/multiple/assets/logo/logo.png b/multiple/assets/logo/logo.png
new file mode 100644
index 0000000..072a936
--- /dev/null
+++ b/multiple/assets/logo/logo.png
Binary files differ
diff --git "a/multiple/assets/logo/\345\215\227\351\200\232\344\272\221\344\273\216\345\267\245\344\270\232\344\272\222\350\201\224\347\275\221\346\234\211\351\231\220\345\205\254\345\217\270.png" "b/multiple/assets/logo/\345\215\227\351\200\232\344\272\221\344\273\216\345\267\245\344\270\232\344\272\222\350\201\224\347\275\221\346\234\211\351\231\220\345\205\254\345\217\270.png"
new file mode 100644
index 0000000..d7ecf59
--- /dev/null
+++ "b/multiple/assets/logo/\345\215\227\351\200\232\344\272\221\344\273\216\345\267\245\344\270\232\344\272\222\350\201\224\347\275\221\346\234\211\351\231\220\345\205\254\345\217\270.png"
Binary files differ
diff --git "a/multiple/assets/logo/\346\226\260\347\274\206\357\274\210\346\261\237\350\213\217\357\274\211\346\225\260\345\255\227\347\247\221\346\212\200\346\234\211\351\231\220\345\205\254\345\217\270.png" "b/multiple/assets/logo/\346\226\260\347\274\206\357\274\210\346\261\237\350\213\217\357\274\211\346\225\260\345\255\227\347\247\221\346\212\200\346\234\211\351\231\220\345\205\254\345\217\270.png"
new file mode 100644
index 0000000..4481de2
--- /dev/null
+++ "b/multiple/assets/logo/\346\226\260\347\274\206\357\274\210\346\261\237\350\213\217\357\274\211\346\225\260\345\255\227\347\247\221\346\212\200\346\234\211\351\231\220\345\205\254\345\217\270.png"
Binary files differ
diff --git "a/multiple/assets/logo/\350\212\257\345\257\274\350\275\257\344\273\266\357\274\210\346\261\237\350\213\217\357\274\211\346\234\211\351\231\220\345\205\254\345\217\270.png" "b/multiple/assets/logo/\350\212\257\345\257\274\350\275\257\344\273\266\357\274\210\346\261\237\350\213\217\357\274\211\346\234\211\351\231\220\345\205\254\345\217\270.png"
new file mode 100644
index 0000000..5b49e96
--- /dev/null
+++ "b/multiple/assets/logo/\350\212\257\345\257\274\350\275\257\344\273\266\357\274\210\346\261\237\350\213\217\357\274\211\346\234\211\351\231\220\345\205\254\345\217\270.png"
Binary files differ
diff --git a/multiple/assets/screen/AYNMView.png b/multiple/assets/screen/AYNMView.png
new file mode 100644
index 0000000..f1928d9
--- /dev/null
+++ b/multiple/assets/screen/AYNMView.png
Binary files differ
diff --git a/multiple/assets/screen/BDSMView.png b/multiple/assets/screen/BDSMView.png
new file mode 100644
index 0000000..3822931
--- /dev/null
+++ b/multiple/assets/screen/BDSMView.png
Binary files differ
diff --git a/multiple/assets/screen/DHDCView.png b/multiple/assets/screen/DHDCView.png
new file mode 100644
index 0000000..7d3e8af
--- /dev/null
+++ b/multiple/assets/screen/DHDCView.png
Binary files differ
diff --git a/multiple/assets/screen/DHHBView.png b/multiple/assets/screen/DHHBView.png
new file mode 100644
index 0000000..f1928d9
--- /dev/null
+++ b/multiple/assets/screen/DHHBView.png
Binary files differ
diff --git a/multiple/assets/screen/DZYSView.png b/multiple/assets/screen/DZYSView.png
new file mode 100644
index 0000000..958c5cc
--- /dev/null
+++ b/multiple/assets/screen/DZYSView.png
Binary files differ
diff --git a/multiple/assets/screen/HCKXView.png b/multiple/assets/screen/HCKXView.png
new file mode 100644
index 0000000..e8574c8
--- /dev/null
+++ b/multiple/assets/screen/HCKXView.png
Binary files differ
diff --git a/multiple/assets/screen/HCMYView.png b/multiple/assets/screen/HCMYView.png
new file mode 100644
index 0000000..bf0a9dc
--- /dev/null
+++ b/multiple/assets/screen/HCMYView.png
Binary files differ
diff --git a/multiple/assets/screen/HGJJView.png b/multiple/assets/screen/HGJJView.png
new file mode 100644
index 0000000..00575e8
--- /dev/null
+++ b/multiple/assets/screen/HGJJView.png
Binary files differ
diff --git a/multiple/assets/screen/HHKJView.png b/multiple/assets/screen/HHKJView.png
new file mode 100644
index 0000000..9c39567
--- /dev/null
+++ b/multiple/assets/screen/HHKJView.png
Binary files differ
diff --git a/multiple/assets/screen/HSMYView.png b/multiple/assets/screen/HSMYView.png
new file mode 100644
index 0000000..201c406
--- /dev/null
+++ b/multiple/assets/screen/HSMYView.png
Binary files differ
diff --git a/multiple/assets/screen/HSXView.png b/multiple/assets/screen/HSXView.png
new file mode 100644
index 0000000..f619ab0
--- /dev/null
+++ b/multiple/assets/screen/HSXView.png
Binary files differ
diff --git a/multiple/assets/screen/HXGYView.png b/multiple/assets/screen/HXGYView.png
new file mode 100644
index 0000000..a1094e8
--- /dev/null
+++ b/multiple/assets/screen/HXGYView.png
Binary files differ
diff --git a/multiple/assets/screen/HXSJView.png b/multiple/assets/screen/HXSJView.png
new file mode 100644
index 0000000..02dde1c
--- /dev/null
+++ b/multiple/assets/screen/HXSJView.png
Binary files differ
diff --git a/src/assets/indexViews/HYSNView.png b/multiple/assets/screen/HYSNView.png
similarity index 100%
rename from src/assets/indexViews/HYSNView.png
rename to multiple/assets/screen/HYSNView.png
Binary files differ
diff --git a/multiple/assets/screen/JLSNView.png b/multiple/assets/screen/JLSNView.png
new file mode 100644
index 0000000..663b5d5
--- /dev/null
+++ b/multiple/assets/screen/JLSNView.png
Binary files differ
diff --git a/multiple/assets/screen/JMSLView.png b/multiple/assets/screen/JMSLView.png
new file mode 100644
index 0000000..65fcfd8
--- /dev/null
+++ b/multiple/assets/screen/JMSLView.png
Binary files differ
diff --git a/src/assets/indexViews/JZYJView.png b/multiple/assets/screen/JZYJView.png
similarity index 100%
rename from src/assets/indexViews/JZYJView.png
rename to multiple/assets/screen/JZYJView.png
Binary files differ
diff --git a/multiple/assets/screen/LQMView.png b/multiple/assets/screen/LQMView.png
new file mode 100644
index 0000000..8c0e23d
--- /dev/null
+++ b/multiple/assets/screen/LQMView.png
Binary files differ
diff --git a/multiple/assets/screen/MKZSView.png b/multiple/assets/screen/MKZSView.png
new file mode 100644
index 0000000..9033a9a
--- /dev/null
+++ b/multiple/assets/screen/MKZSView.png
Binary files differ
diff --git a/multiple/assets/screen/MXSCBack.png b/multiple/assets/screen/MXSCBack.png
new file mode 100644
index 0000000..0beafd5
--- /dev/null
+++ b/multiple/assets/screen/MXSCBack.png
Binary files differ
diff --git a/multiple/assets/screen/NYDLView.png b/multiple/assets/screen/NYDLView.png
new file mode 100644
index 0000000..02249e1
--- /dev/null
+++ b/multiple/assets/screen/NYDLView.png
Binary files differ
diff --git a/multiple/assets/screen/PHMKView.png b/multiple/assets/screen/PHMKView.png
new file mode 100644
index 0000000..3fd67ee
--- /dev/null
+++ b/multiple/assets/screen/PHMKView.png
Binary files differ
diff --git a/multiple/assets/screen/QLMCView.png b/multiple/assets/screen/QLMCView.png
new file mode 100644
index 0000000..814b628
--- /dev/null
+++ b/multiple/assets/screen/QLMCView.png
Binary files differ
diff --git a/multiple/assets/screen/RTSWView.png b/multiple/assets/screen/RTSWView.png
new file mode 100644
index 0000000..02dde1c
--- /dev/null
+++ b/multiple/assets/screen/RTSWView.png
Binary files differ
diff --git a/multiple/assets/screen/RZNYView.png b/multiple/assets/screen/RZNYView.png
new file mode 100644
index 0000000..4905096
--- /dev/null
+++ b/multiple/assets/screen/RZNYView.png
Binary files differ
diff --git a/multiple/assets/screen/TJKHView.png b/multiple/assets/screen/TJKHView.png
new file mode 100644
index 0000000..958c5cc
--- /dev/null
+++ b/multiple/assets/screen/TJKHView.png
Binary files differ
diff --git a/multiple/assets/screen/TJXMView.png b/multiple/assets/screen/TJXMView.png
new file mode 100644
index 0000000..e56a8c7
--- /dev/null
+++ b/multiple/assets/screen/TJXMView.png
Binary files differ
diff --git a/multiple/assets/screen/TYMKView.png b/multiple/assets/screen/TYMKView.png
new file mode 100644
index 0000000..3449ceb
--- /dev/null
+++ b/multiple/assets/screen/TYMKView.png
Binary files differ
diff --git a/src/assets/indexViews/WDSYView.png b/multiple/assets/screen/WDSYView.png
similarity index 100%
rename from src/assets/indexViews/WDSYView.png
rename to multiple/assets/screen/WDSYView.png
Binary files differ
diff --git a/multiple/assets/screen/XYHBView.png b/multiple/assets/screen/XYHBView.png
new file mode 100644
index 0000000..5878cd5
--- /dev/null
+++ b/multiple/assets/screen/XYHBView.png
Binary files differ
diff --git a/multiple/assets/screen/ZDXMView.png b/multiple/assets/screen/ZDXMView.png
new file mode 100644
index 0000000..6f6b11d
--- /dev/null
+++ b/multiple/assets/screen/ZDXMView.png
Binary files differ
diff --git a/src/assets/indexViews/ZQHXView.png b/multiple/assets/screen/ZQHXView.png
similarity index 100%
rename from src/assets/indexViews/ZQHXView.png
rename to multiple/assets/screen/ZQHXView.png
Binary files differ
diff --git a/multiple/assets/screen/ZYRQView.png b/multiple/assets/screen/ZYRQView.png
new file mode 100644
index 0000000..3487d39
--- /dev/null
+++ b/multiple/assets/screen/ZYRQView.png
Binary files differ
diff --git a/multiple/config.json b/multiple/config.json
new file mode 100644
index 0000000..7ad6795
--- /dev/null
+++ b/multiple/config.json
@@ -0,0 +1,23 @@
+{
+ "default": {
+ "env": {
+ "VITE_APP_TITLE": "鑺浜戯紙绠$悊淇℃伅绯荤粺锛�"
+ },
+ "screen": "screen/JZYJView.png",
+ "logo": "logo/HYSNLogo.png",
+ "favicon": "favicon/HYSNico.ico"
+ },
+ "TEST": {
+ "env": {
+ "VITE_APP_TITLE": "涓皬浼佷笟鏁板瓧鍖栬浆鍨嬩簩绾у椁愬寘",
+ "VITE_BASE_API": "http://1.15.17.182:9003",
+ "VITE_JAVA_API": "http://1.15.17.182:9002"
+ },
+ "screen": "screen/HYSNView.png",
+ "logo": "logo/ZGLTLogo.png",
+ "favicon": "favicon/favicon.ico"
+ },
+ "screen": "/src/assets/images/login-background.png",
+ "logo": "/src/assets/logo/logo.png",
+ "favicon": "/public/favicon.ico"
+}
diff --git a/multiple/multiple-build.js b/multiple/multiple-build.js
new file mode 100644
index 0000000..8e078e1
--- /dev/null
+++ b/multiple/multiple-build.js
@@ -0,0 +1,86 @@
+import fs from 'fs/promises';
+import fsSync from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import { execSync } from "child_process";
+
+// 鑾峰彇 __dirname
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// 璇诲彇 JSON 閰嶇疆
+const data = await fs.readFile(path.join(__dirname, 'config.json'), 'utf-8');
+const config = JSON.parse(data);
+
+// 椤圭洰璺緞
+const rootPath = path.resolve(__dirname, '..');
+const resourcePath = path.join(rootPath, 'multiple', 'assets');
+const replacePath = path.join(rootPath, 'replace');
+
+// 鑾峰彇鍛戒护琛屽弬鏁�
+const params = parseArgs(process.argv);
+const company = params["company"] ?? "default";
+const companyMap = config[company];
+
+const envFilePath = path.join(process.cwd(), '.env.production.local');
+
+try {
+ // 1锔忊儯 鐢熸垚 .env
+ console.log("=======鐢熸垚.env=======");
+ const envContent = Object.entries(companyMap.env)
+ .map(([key, value]) => `${key}='${value}'`)
+ .join('\n') + '\n';
+ await fs.writeFile(envFilePath, envContent, 'utf-8');
+
+ // 2锔忊儯 澶囦唤鍘熷璧勬簮骞舵浛鎹�
+ console.log("=======淇敼璧勬簮=======");
+ for (const [key, value] of Object.entries(companyMap)) {
+ if (key === 'env') continue;
+
+ const originFile = path.join(rootPath, config[key]);
+ const backupFile = path.join(replacePath, config[key]);
+ const replaceFile = path.join(resourcePath, companyMap[key]);
+
+ await fs.mkdir(path.dirname(backupFile), { recursive: true });
+ await fs.copyFile(originFile, backupFile);
+ await fs.copyFile(replaceFile, originFile);
+ }
+
+ console.log("=====寮�濮嬫墦鍖�======");
+ execSync("vite build", { stdio: "inherit" });
+ console.log("=====鎵撳寘瀹屾垚======");
+} finally {
+ console.log("=====鎭㈠璧勬簮======");
+
+ // 鍒犻櫎涓存椂 .env 鏂囦欢
+ if (fsSync.existsSync(envFilePath)) {
+ await fs.unlink(envFilePath);
+ console.log(`馃棏锔� 宸插垹闄� ${envFilePath}`);
+ }
+
+ // 鎭㈠璧勬簮鏂囦欢
+ if (fsSync.existsSync(replacePath)) {
+ for (const [key, value] of Object.entries(companyMap)) {
+ if (key === 'env') continue;
+
+ const originFile = path.join(rootPath, config[key]);
+ const backupFile = path.join(replacePath, config[key]);
+
+ await fs.copyFile(backupFile, originFile);
+ }
+ await fs.rm(replacePath, { recursive: true, force: true });
+ console.log(`馃棏锔� 宸插垹闄� ${replacePath}`);
+ }
+}
+
+// 绠�鍗曞懡浠よ鍙傛暟瑙f瀽
+function parseArgs(argv) {
+ const params = {};
+ for (const arg of argv.slice(2)) {
+ if (arg.startsWith('--')) {
+ const [key, value] = arg.slice(2).split('=');
+ params[key] = value ?? true;
+ }
+ }
+ return params;
+}
diff --git a/src/api/basicData/enum.js b/src/api/basicData/enum.js
new file mode 100644
index 0000000..0d35692
--- /dev/null
+++ b/src/api/basicData/enum.js
@@ -0,0 +1,23 @@
+import request from "@/utils/request.js";
+
+export function findAllStockRecordTypeOptions() {
+ return request({
+ url: '/basic/enum/stockRecordType',
+ method: 'get'
+ })
+}
+
+
+export function findAllQualifiedStockRecordTypeOptions() {
+ return request({
+ url: '/basic/enum/StockQualifiedRecordTypeEnum',
+ method: 'get'
+ })
+}
+
+export function findAllUnqualifiedStockRecordTypeOptions() {
+ return request({
+ url: '/basic/enum/StockUnQualifiedRecordTypeEnum',
+ method: 'get'
+ })
+}
diff --git a/src/api/basicData/productModel.js b/src/api/basicData/productModel.js
new file mode 100644
index 0000000..f048f9e
--- /dev/null
+++ b/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
+ })
+}
\ No newline at end of file
diff --git a/src/api/basicData/productProcess.js b/src/api/basicData/productProcess.js
new file mode 100644
index 0000000..e0208fa
--- /dev/null
+++ b/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
+ })
+}
diff --git a/src/api/basicData/supplierManageFile.js b/src/api/basicData/supplierManageFile.js
index 3b1ab97..6d3f28b 100644
--- a/src/api/basicData/supplierManageFile.js
+++ b/src/api/basicData/supplierManageFile.js
@@ -49,4 +49,27 @@
data: ids
})
}
-
+// 鏌ヨ闄勪欢鍒楄〃
+export function fileListPage(query) {
+ return request({
+ url: "/basic/supplierManageFile/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 淇濆瓨闄勪欢鍒楄〃
+export function fileAdd(query) {
+ return request({
+ url: "/basic/supplierManageFile/add",
+ method: "post",
+ data: query,
+ });
+}
+// 鍒犻櫎闄勪欢鍒楄〃
+export function fileDel(query) {
+ return request({
+ url: "/basic/supplierManageFile/del",
+ method: "delete",
+ data: query,
+ });
+}
diff --git a/src/api/collaborativeApproval/attendanceManagement.js b/src/api/collaborativeApproval/attendanceManagement.js
index f9529c2..3c99775 100644
--- a/src/api/collaborativeApproval/attendanceManagement.js
+++ b/src/api/collaborativeApproval/attendanceManagement.js
@@ -2,133 +2,135 @@
// 鏌ヨ鍋囨湡璁剧疆鍒楄〃
export function listHolidaySettings(query) {
- return request({
- url: "/holidaySettings/getList",
- method: "get",
- params: query,
- });
+ return request({
+ url: "/holidaySettings/getList",
+ method: "get",
+ params: query,
+ });
}
//鏌ヨ骞村亣瑙勫垯鍒楄〃
export function listAnnualLeaveSettingList(query) {
- return request({
- url: "/holidaySettings/getAnnualLeaveSettingList",
- method: "get",
- params: query,
- });
+ return request({
+ url: "/holidaySettings/getAnnualLeaveSettingList",
+ method: "get",
+ params: query,
+ });
}
//鏌ヨ鍔犵彮瑙勫垯鍒楄〃
export function listOvertimeSettingList(query) {
- return request({
- url: "/holidaySettings/getOvertimeSettingList",
- method: "get",
- params: query,
- });
+ return request({
+ url: "/holidaySettings/getOvertimeSettingList",
+ method: "get",
+ params: query,
+ });
}
//鏌ヨ宸ヤ綔鏃堕棿瑙勫垯鍒楄〃
export function listWorkingHoursSettingList(query) {
- return request({
- url: "/holidaySettings/getWorkingHoursSettingList",
- method: "get",
- params: query,
- });
+ return request({
+ url: "/holidaySettings/getWorkingHoursSettingList",
+ method: "get",
+ params: query,
+ });
}
// 鏂板鍋囨湡璁剧疆
export function addHolidaySettings(data) {
- return request({
- url: "/holidaySettings/add",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/add",
+ method: "post",
+ data: data,
+ });
}
//鏂板骞村亣瑙勫垯
export function addAnnualLeaveSetting(data) {
- return request({
- url: "/holidaySettings/addAnnualLeaveSetting",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/addAnnualLeaveSetting",
+ method: "post",
+ data: data,
+ });
}
//鏂板鍔犵彮瑙勫垯
export function addOvertimeSetting(data) {
- return request({
- url: "/holidaySettings/addOvertimeSetting",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/addOvertimeSetting",
+ method: "post",
+ data: data,
+ });
}
//鏂板宸ヤ綔鏃堕棿瑙勫垯
export function addWorkingHoursSetting(data) {
- return request({
- url: "/holidaySettings/addWorkingHoursSetting",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/addWorkingHoursSetting",
+ method: "post",
+ data: data,
+ });
}
// 淇敼鍋囨湡璁剧疆
export function updateHolidaySettings(data) {
- return request({
- url: "/holidaySettings/update",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/update",
+ method: "post",
+ data: data,
+ });
}
// 淇敼骞村亣瑙勫垯
export function updateAnnualLeaveSetting(data) {
- return request({
- url: "/holidaySettings/updateAnnualLeaveSetting",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/updateAnnualLeaveSetting",
+ method: "post",
+ data: data,
+ });
}
// 淇敼鍔犵彮瑙勫垯
export function updateOvertimeSetting(data) {
- return request({
- url: "/holidaySettings/updateOvertimeSetting",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/updateOvertimeSetting",
+ method: "post",
+ data: data,
+ });
}
// 淇敼宸ヤ綔鏃堕棿瑙勫垯
export function updateWorkingHoursSetting(data) {
- return request({
- url: "/holidaySettings/updateWorkingHoursSetting",
- method: "post",
- data: data,
- });
+ return request({
+ url: "/holidaySettings/updateWorkingHoursSetting",
+ method: "post",
+ data: data,
+ });
}
// 鎵归噺鍒犻櫎鍋囨湡璁剧疆
export function delHolidaySettings(query) {
- return request({
- url: "/holidaySettings/delete",
- method: "delete",
- data: query,
- });
+ return request({
+ url: "/holidaySettings/delete",
+ method: "delete",
+ data: query,
+ });
}
// 鎵归噺鍒犻櫎骞村亣瑙勫垯
export function delAnnualLeaveSetting(query) {
- return request({
- url: "/holidaySettings/deleteAnnualLeaveSetting",
- method: "delete",
- data: query,
- });
+ return request({
+ url: "/holidaySettings/deleteAnnualLeaveSetting",
+ method: "delete",
+ data: query,
+ });
}
// 鎵归噺鍒犻櫎鍔犵彮瑙勫垯
export function delOvertimeSetting(query) {
- return request({
- url: "/holidaySettings/deleteOvertimeSetting",
- method: "delete",
- data: query,
- });
+ return request({
+ url: "/holidaySettings/deleteOvertimeSetting",
+ method: "delete",
+ data: query,
+ });
}
// 鎵归噺鍒犻櫎宸ヤ綔鏃堕棿瑙勫垯
export function delWorkingHoursSetting(query) {
- return request({
- url: "/holidaySettings/deleteWorkingHoursSetting",
- method: "delete",
- data: query,
- });
+ return request({
+ url: "/holidaySettings/deleteWorkingHoursSetting",
+ method: "delete",
+ data: query,
+ });
}
+
+
diff --git a/src/api/collaborativeApproval/enterpriseBook.js b/src/api/collaborativeApproval/enterpriseBook.js
index da6a139..2140b89 100644
--- a/src/api/collaborativeApproval/enterpriseBook.js
+++ b/src/api/collaborativeApproval/enterpriseBook.js
@@ -3,65 +3,65 @@
// 鏌ヨ涓汉閫氳褰�
// 涓汉閫氳褰曢�氬父鏄敤鎴锋敹钘忔垨棰戠箒鑱旂郴鐨勪汉鍛�
export function getPersonalContacts(page,query) {
- return request({
- url: '/staffContactsPersonal/getList',
- method: 'get',
- params: {
- ...page,
- ...query
- }
- })
+ return request({
+ url: '/staffContactsPersonal/getList',
+ method: 'get',
+ params: {
+ ...page,
+ ...query
+ }
+ })
}
// 娣诲姞鑱旂郴浜哄埌涓汉閫氳褰�
export function addPersonalContact(data) {
- return request({
- url: '/staffContactsPersonal/add',
- method: 'post',
- data: data
- })
+ return request({
+ url: '/staffContactsPersonal/add',
+ method: 'post',
+ data: data
+ })
}
// 浠庝釜浜洪�氳褰曠Щ闄よ仈绯讳汉
export function removePersonalContact(id) {
- return request({
- url: '/staffContactsPersonal/delete/' + id,
- method: 'delete'
- })
+ return request({
+ url: '/staffContactsPersonal/delete/' + id,
+ method: 'delete'
+ })
}
// 鏌ヨ鍏叡閫氳褰�
// 鍏叡閫氳褰曢�氬父鏄墍鏈夊憳宸ュ彲瑙佺殑鑱旂郴鏂瑰紡
export function getPublicContacts(query) {
- return request({
- url: '/staff/contacts/public/list',
- method: 'get',
- params: query
- })
+ return request({
+ url: '/staff/contacts/public/list',
+ method: 'get',
+ params: query
+ })
}
// 鏌ヨ鍗曚綅閫氳褰�
// 鍗曚綅閫氳褰曢�氬父鎸夐儴闂ㄧ粍缁囩殑鍛樺伐鑱旂郴鏂瑰紡
export function getCompanyContacts(query) {
- return request({
- url: '/staff/contacts/company/list',
- method: 'get',
- params: query
- })
+ return request({
+ url: '/staff/contacts/company/list',
+ method: 'get',
+ params: query
+ })
}
// 鏌ヨ閮ㄩ棬閫氳褰曟爲缁撴瀯
export function getDepartmentTree() {
- return request({
- url: '/staff/contacts/department/tree',
- method: 'get'
- })
+ return request({
+ url: '/staff/contacts/department/tree',
+ method: 'get'
+ })
}
// 鑾峰彇鍛樺伐璇︾粏淇℃伅
export function getEmployeeDetail(employeeId) {
- return request({
- url: '/staff/staffOnJob/' + employeeId,
- method: 'get'
- })
-}
+ return request({
+ url: '/staff/staffOnJob/' + employeeId,
+ method: 'get'
+ })
+}
\ No newline at end of file
diff --git a/src/api/collaborativeApproval/noticeManagement.js b/src/api/collaborativeApproval/noticeManagement.js
index aae4db7..c89f6c4 100644
--- a/src/api/collaborativeApproval/noticeManagement.js
+++ b/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 }
- })
-}
\ No newline at end of file
+ return request({
+ url: "/noticeType/del",
+ method: "delete",
+ data: [id],
+ });
+}
diff --git a/src/api/collaborativeApproval/rpaManagement.js b/src/api/collaborativeApproval/rpaManagement.js
index 6fc3368..273bf9f 100644
--- a/src/api/collaborativeApproval/rpaManagement.js
+++ b/src/api/collaborativeApproval/rpaManagement.js
@@ -3,7 +3,7 @@
// 鏌ヨRPA鍒楄〃
export function listRpa(query) {
return request({
- url: "/collaborativeApproval/rpa/list",
+ url: "/rpaProcessAutomation/getList",
method: "get",
params: query,
});
@@ -20,7 +20,7 @@
// 鏂板RPA
export function addRpa(data) {
return request({
- url: "/collaborativeApproval/rpa",
+ url: "/rpaProcessAutomation/add",
method: "post",
data: data,
});
@@ -29,17 +29,18 @@
// 淇敼RPA
export function updateRpa(data) {
return request({
- url: "/collaborativeApproval/rpa",
- method: "put",
+ url: "/rpaProcessAutomation/update",
+ method: "post",
data: data,
});
}
// 鍒犻櫎RPA
-export function delRpa(rpaId) {
+export function delRpa(query) {
return request({
- url: "/collaborativeApproval/rpa/" + rpaId,
+ url: "/rpaProcessAutomation/delete",
method: "delete",
+ data: query,
});
}
diff --git a/src/api/collaborativeApproval/shipmentReview.js b/src/api/collaborativeApproval/shipmentReview.js
new file mode 100644
index 0000000..64fac69
--- /dev/null
+++ b/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,
+ })
+}
\ No newline at end of file
diff --git a/src/api/customerService/index.js b/src/api/customerService/index.js
index e83cdfb..b92033b 100644
--- a/src/api/customerService/index.js
+++ b/src/api/customerService/index.js
@@ -40,4 +40,40 @@
method: 'post',
data: query,
})
+}
+
+// 涓存湡鍞悗绠$悊-鍒嗛〉鏌ヨ
+export function expiryAfterSalesListPage(query) {
+ return request({
+ url: '/expiryAfterSales/listPage',
+ method: 'get',
+ params: query,
+ })
+}
+
+// 涓存湡鍞悗绠$悊-鏂板
+export function expiryAfterSalesAdd(query) {
+ return request({
+ url: '/expiryAfterSales/add',
+ method: 'post',
+ data: query,
+ })
+}
+
+// 涓存湡鍞悗绠$悊-鏇存柊
+export function expiryAfterSalesUpdate(query) {
+ return request({
+ url: '/expiryAfterSales/update',
+ method: 'post',
+ data: query,
+ })
+}
+
+// 涓存湡鍞悗绠$悊-鍒犻櫎
+export function expiryAfterSalesDelete(query) {
+ return request({
+ url: '/expiryAfterSales/delete',
+ method: 'delete',
+ data: query,
+ })
}
\ No newline at end of file
diff --git a/src/api/equipmentManagement/brand.js b/src/api/equipmentManagement/brand.js
new file mode 100644
index 0000000..040cb38
--- /dev/null
+++ b/src/api/equipmentManagement/brand.js
@@ -0,0 +1,93 @@
+// 璁惧鍝佺墝绠$悊 - 鏈湴鍋囨暟鎹� API锛堜娇鐢� localStorage 鎸佷箙鍖栵級
+
+const STORAGE_KEY = 'EQUIPMENT_BRANDS';
+
+function readStore() {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (raw) {
+ const parsed = JSON.parse(raw);
+ if (Array.isArray(parsed)) return parsed;
+ }
+ } catch (e) {
+ // ignore
+ }
+ // 鍒濆鍖栦竴浜涚ず渚嬫暟鎹�
+ const initial = [
+ { id: 1, name: '瑗块棬瀛�', country: '寰峰浗', description: '宸ヤ笟鑷姩鍖栦笌鐢垫皵宸ョ▼鍝佺墝', createdAt: Date.now() - 86400000 * 10 },
+ { id: 2, name: '鏂借�愬痉', country: '娉曞浗', description: '鑳芥簮绠$悊涓庤嚜鍔ㄥ寲', createdAt: Date.now() - 86400000 * 7 },
+ { id: 3, name: '涓夎彵鐢垫満', country: '鏃ユ湰', description: '鐢垫皵涓庤嚜鍔ㄥ寲璁惧', createdAt: Date.now() - 86400000 * 3 },
+ ];
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(initial));
+ return initial;
+}
+
+function writeStore(list) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
+}
+
+function nextId(list) {
+ const maxId = list.reduce((max, item) => Math.max(max, Number(item.id) || 0), 0);
+ return maxId + 1;
+}
+
+export function getBrandPage(params = {}) {
+ const { current = 1, size = 10, name } = params;
+ const list = readStore();
+ let filtered = list;
+ if (name) {
+ const kw = String(name).trim();
+ filtered = filtered.filter((b) =>
+ (b.name && b.name.includes(kw)) || (b.country && b.country.includes(kw))
+ );
+ }
+ const start = (current - 1) * size;
+ const end = start + Number(size);
+ const records = filtered.slice(start, end);
+ return Promise.resolve({
+ code: 200,
+ data: {
+ total: filtered.length,
+ records,
+ },
+ msg: 'ok',
+ });
+}
+
+export function getBrandById(id) {
+ const list = readStore();
+ const item = list.find((i) => String(i.id) === String(id));
+ return Promise.resolve({ code: 200, data: item || null, msg: 'ok' });
+}
+
+export function addBrand(data) {
+ const list = readStore();
+ const item = { ...data };
+ item.id = nextId(list);
+ item.createdAt = Date.now();
+ list.unshift(item);
+ writeStore(list);
+ return Promise.resolve({ code: 200, data: item, msg: '鏂板鎴愬姛' });
+}
+
+export function editBrand(data) {
+ const list = readStore();
+ const index = list.findIndex((i) => String(i.id) === String(data.id));
+ if (index !== -1) {
+ list[index] = { ...list[index], ...data };
+ writeStore(list);
+ return Promise.resolve({ code: 200, data: list[index], msg: '淇敼鎴愬姛' });
+ }
+ return Promise.resolve({ code: 404, data: null, msg: '鏈壘鍒拌鍝佺墝' });
+}
+
+export function delBrand(idOrIds) {
+ const list = readStore();
+ const ids = Array.isArray(idOrIds) ? idOrIds.map(String) : [String(idOrIds)];
+ const newList = list.filter((i) => !ids.includes(String(i.id)));
+ writeStore(newList);
+ return Promise.resolve({ code: 200, data: null, msg: '鍒犻櫎鎴愬姛' });
+}
+
+
+
diff --git a/src/api/equipmentManagement/calibration.js b/src/api/equipmentManagement/calibration.js
index 54b99f9..b0a9844 100644
--- a/src/api/equipmentManagement/calibration.js
+++ b/src/api/equipmentManagement/calibration.js
@@ -24,4 +24,12 @@
method: "post",
data: query,
});
+}
+// 鍒犻櫎璁板綍
+export function ledgerRecordDelete(ids) {
+ return request({
+ url: "/measuringInstrumentLedgerRecord/delete",
+ method: "delete",
+ data: ids,
+ });
}
\ No newline at end of file
diff --git a/src/api/equipmentManagement/defectManagement.js b/src/api/equipmentManagement/defectManagement.js
new file mode 100644
index 0000000..c52eff9
--- /dev/null
+++ b/src/api/equipmentManagement/defectManagement.js
@@ -0,0 +1,44 @@
+import request from '@/utils/request';
+
+// 鐧昏缂洪櫡
+export function registerDefect(data) {
+ return request({
+ url: '/defect/add',
+ method: 'post',
+ data
+ });
+}
+
+// 鑾峰彇缂洪櫡鍒楄〃
+export function getDefectList() {
+ return request({
+ url: '/defect/page',
+ method: 'get'
+ });
+}
+
+// 娑堥櫎缂洪櫡-淇敼鐘舵��
+export function eliminateDefect(data) {
+ return request({
+ url: '/defect/update',
+ method: 'post',
+ data
+ });
+}
+//鍒犻櫎
+export function deleteDefect(id) {
+ return request({
+ url: '/defect/delete',
+ method: 'delete',
+ id
+ });
+}
+
+
+// 鑾峰彇缂洪櫡璁惧鍙拌处
+export function getDefectLedger(deviceLedgerId) {
+ return request({
+ url: '/defect//find/' + deviceLedgerId,
+ method: 'get'
+ });
+}
\ No newline at end of file
diff --git a/src/api/equipmentManagement/deviceInfo.js b/src/api/equipmentManagement/deviceInfo.js
new file mode 100644
index 0000000..d71b713
--- /dev/null
+++ b/src/api/equipmentManagement/deviceInfo.js
@@ -0,0 +1,10 @@
+import request from "@/utils/request";
+
+// 鑾峰彇璁惧鍩烘湰淇℃伅
+export function getDeviceInfo(params) {
+ return request({
+ url: "/device/ledger/scanDevice",
+ method: "get",
+ params,
+ });
+}
diff --git a/src/api/equipmentManagement/measurementEquipment.js b/src/api/equipmentManagement/measurementEquipment.js
index a22c034..8bb8a7f 100644
--- a/src/api/equipmentManagement/measurementEquipment.js
+++ b/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
+ })
}
\ No newline at end of file
diff --git a/src/api/equipmentManagement/spareParts.js b/src/api/equipmentManagement/spareParts.js
new file mode 100644
index 0000000..2b64689
--- /dev/null
+++ b/src/api/equipmentManagement/spareParts.js
@@ -0,0 +1,58 @@
+import request from "@/utils/request";
+/**
+ * 澶囦欢鍒嗙被-鏍戝垪琛�
+ */
+export const getSparePartsTree = (params) => {
+ return request({
+ url: "/spareParts/getTree",
+ method: "get",
+ params,
+ });
+};
+/**
+ * 澶囦欢鍒嗙被-鍒嗛〉鏌ヨ鍒楄〃
+ */
+export const getSparePartsList = (params) => {
+ return request({
+ url: "/spareParts/listPage",
+ method: "get",
+ params,
+ });
+};
+
+/**
+ * @desc 鏂板
+ */
+export const addSparePart = (data) => {
+ return request({
+ url: "/spareParts/add",
+ method: "post",
+ data,
+ });
+};
+
+/**
+ * @desc 缂栬緫
+ */
+export const editSparePart = (data) => {
+ return request({
+ url: "/spareParts/update",
+ method: "post",
+ data,
+ });
+};
+
+/**
+ * @desc 鍒犻櫎鎶ヤ慨
+ * @param {缂栧彿} ids
+ * @returns
+ */
+export const delSparePart = (id) => {
+ return request({
+ url: '/spareParts/delete/'+id,
+ method: "delete",
+
+ });
+};
+
+
diff --git a/src/api/equipmentManagement/upkeep.js b/src/api/equipmentManagement/upkeep.js
index c091670..234d2a5 100644
--- a/src/api/equipmentManagement/upkeep.js
+++ b/src/api/equipmentManagement/upkeep.js
@@ -70,3 +70,35 @@
method: "delete",
});
};
+// 娣诲姞璁惧淇濆吇瀹氭椂浠诲姟
+export const deviceMaintenanceTaskAdd = (params) => {
+ return request({
+ url: '/deviceMaintenanceTask/add',
+ method: "post",
+ data: params,
+ });
+};
+// 淇敼璁惧淇濆吇瀹氭椂浠诲姟
+export const deviceMaintenanceTaskEdit = (params) => {
+ return request({
+ url: '/deviceMaintenanceTask/update',
+ method: "post",
+ data: params,
+ });
+};
+// 璁惧淇濆吇瀹氭椂浠诲姟鍒楄〃
+export const deviceMaintenanceTaskList = (params) => {
+ return request({
+ url: '/deviceMaintenanceTask/listPage',
+ method: "get",
+ params: params,
+ });
+};
+// 璁惧淇濆吇瀹氭椂浠诲姟鍒楄〃
+export const deviceMaintenanceTaskDel = (params) => {
+ return request({
+ url: '/deviceMaintenanceTask/delete',
+ method: "delete",
+ data: params,
+ });
+};
diff --git a/src/api/fileManagement/bookshelf.js b/src/api/fileManagement/bookshelf.js
new file mode 100644
index 0000000..0a4a748
--- /dev/null
+++ b/src/api/fileManagement/bookshelf.js
@@ -0,0 +1,128 @@
+import request from "@/utils/request";
+
+/**
+ * 涔︽灦绠$悊鐩稿叧API鎺ュ彛
+ * 鍖呭惈浠撳簱绠$悊銆佽揣鏋剁鐞嗐�佸浘涔︾鐞嗙瓑鍔熻兘鐨勬帴鍙�
+ */
+
+/**
+ * 鑾峰彇浠撳簱鍒楄〃
+ * @description 鑾峰彇鎵�鏈変粨搴撶殑鍩烘湰淇℃伅鍒楄〃
+ * @returns {Promise} 杩斿洖浠撳簱鍒楄〃鏁版嵁
+ */
+export function getWarehouseList() {
+ return request({
+ url: "/warehouse/tree",
+ method: "get",
+ });
+}
+
+/**
+ * 鏂板浠撳簱
+ * @description 鍒涘缓鏂扮殑浠撳簱璁板綍
+ * @param {Object} data 浠撳簱淇℃伅瀵硅薄锛屽寘鍚粨搴撳悕绉扮瓑瀛楁
+ * @returns {Promise} 杩斿洖鏂板缁撴灉
+ */
+export function addWarehouse(data) {
+ return request({
+ url: "/warehouse/add",
+ method: "post",
+ data,
+ });
+}
+
+/**
+ * 鏇存柊浠撳簱淇℃伅
+ * @description 淇敼鐜版湁浠撳簱鐨勫熀鏈俊鎭�
+ * @param {Object} data 浠撳簱淇℃伅瀵硅薄锛屽繀椤诲寘鍚粨搴揑D
+ * @returns {Promise} 杩斿洖鏇存柊缁撴灉
+ */
+export function updateWarehouse(data) {
+ return request({
+ url: "/warehouse/update",
+ method: "put",
+ data,
+ });
+}
+
+/**
+ * 鍒犻櫎浠撳簱
+ * @description 鏍规嵁浠撳簱ID鍒犻櫎鎸囧畾鐨勪粨搴撹褰�
+ * @param {string|number} id 浠撳簱ID
+ * @returns {Promise} 杩斿洖鍒犻櫎缁撴灉
+ */
+export function deleteWarehouse(data) {
+ return request({
+ url: `/warehouse/delete/`,
+ method: "delete",
+ data,
+ });
+}
+
+/**
+ * 鑾峰彇璐ф灦鍒楄〃
+ * @description 鏍规嵁浠撳簱ID鑾峰彇璇ヤ粨搴撲笅鐨勬墍鏈夎揣鏋朵俊鎭�
+ * @param {string|number} warehouseId 浠撳簱ID
+ * @returns {Promise} 杩斿洖璐ф灦鍒楄〃鏁版嵁
+ */
+export function getShelfList(warehouseId) {
+ return request({
+ url: `/shelf/list/${warehouseId}`,
+ method: "get",
+ });
+}
+
+/**
+ * 鏂板璐ф灦
+ * @description 鍦ㄦ寚瀹氫粨搴撲笅鍒涘缓鏂扮殑璐ф灦璁板綍
+ * @param {Object} data 璐ф灦淇℃伅瀵硅薄锛屽寘鍚揣鏋跺悕绉般�佸眰鏁般�佸垪鏁扮瓑瀛楁
+ * @returns {Promise} 杩斿洖鏂板缁撴灉
+ */
+export function addShelf(data) {
+ return request({
+ url: "/warehouse/goodsShelves/add",
+ method: "post",
+ data,
+ });
+}
+
+/**
+ * 鏇存柊璐ф灦淇℃伅
+ * @description 淇敼鐜版湁璐ф灦鐨勫熀鏈俊鎭�
+ * @param {Object} data 璐ф灦淇℃伅瀵硅薄锛屽繀椤诲寘鍚揣鏋禝D
+ * @returns {Promise} 杩斿洖鏇存柊缁撴灉
+ */
+export function updateShelf(data) {
+ return request({
+ url: "/warehouse/goodsShelves/update",
+ method: "put",
+ data,
+ });
+}
+
+/**
+ * 鍒犻櫎璐ф灦
+ * @description 鏍规嵁璐ф灦ID鍒犻櫎鎸囧畾鐨勮揣鏋惰褰�
+ * @param {string|number} id 璐ф灦ID
+ * @returns {Promise} 杩斿洖鍒犻櫎缁撴灉
+ */
+export function deleteShelf(id) {
+ return request({
+ url: `/warehouse/goodsShelves/delete/${id}`,
+ method: "delete",
+ });
+}
+
+/**
+ * 鑾峰彇浠撳簱缁撴瀯
+ * @description 鑾峰彇鎸囧畾浠撳簱鐨勫畬鏁寸粨鏋勪俊鎭紝鍖呮嫭璐ф灦銆佸眰鏁般�佸垪鏁扮瓑
+ * @param {string|number} warehouseId 浠撳簱ID
+ * @returns {Promise} 杩斿洖浠撳簱鐨勫畬鏁寸粨鏋勬暟鎹�
+ */
+export function getWarehouseStructure(data) {
+ return request({
+ url: `/warehouse/goodsShelvesRowcol/list`,
+ method: "get",
+ params: data,
+ });
+}
diff --git a/src/api/fileManagement/borrow.js b/src/api/fileManagement/borrow.js
new file mode 100644
index 0000000..1f4c72c
--- /dev/null
+++ b/src/api/fileManagement/borrow.js
@@ -0,0 +1,47 @@
+import request from "@/utils/request";
+
+// 鏂囨。鍊熼槄绠$悊鐩稿叧鎺ュ彛
+
+// 鑾峰彇鏂囨。鍒楄〃锛堢敤浜庡�熼槄涔︾睄閫夋嫨锛�
+export function getDocumentList() {
+ return request({
+ url: "/documentation/list",
+ method: "get",
+ });
+}
+
+// 鍊熼槄鍒嗛〉鏌ヨ
+export function getBorrowList(params) {
+ return request({
+ url: "/documentationBorrowManagement/listPage",
+ method: "get",
+ params: params,
+ });
+}
+
+// 鏂板鍊熼槄
+export function addBorrow(data) {
+ return request({
+ url: "/documentationBorrowManagement/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鏇存柊鍊熼槄
+export function updateBorrow(data) {
+ return request({
+ url: "/documentationBorrowManagement/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鍒犻櫎鍊熼槄
+export function deleteBorrow(ids) {
+ return request({
+ url: "/documentationBorrowManagement/delete",
+ method: "delete",
+ data: ids,
+ });
+}
diff --git a/src/api/fileManagement/document.js b/src/api/fileManagement/document.js
new file mode 100644
index 0000000..f3d5f4f
--- /dev/null
+++ b/src/api/fileManagement/document.js
@@ -0,0 +1,189 @@
+import request from "@/utils/request";
+
+// 鑾峰彇鍒嗙被鏍�
+export function getCategoryTree() {
+ return request({
+ url: "/warehouse/documentClassification/getList",
+ method: "get",
+ });
+}
+
+// 鏂板鍒嗙被
+export function addCategory(data) {
+ return request({
+ url: "/warehouse/documentClassification/add",
+ method: "post",
+ data: {
+ category: data.category,
+ parentId: data.parentId,
+ },
+ });
+}
+
+// 淇敼鍒嗙被
+export function updateCategory(data) {
+ return request({
+ url: "/warehouse/documentClassification/update",
+ method: "put",
+ data: {
+ id: data.id,
+ category: data.category,
+ },
+ });
+}
+
+// 鍒犻櫎鍒嗙被
+export function deleteCategory(ids) {
+ return request({
+ url: "/warehouse/documentClassification/delete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 鑾峰彇鏂囨。鍒楄〃锛堝垎椤碉級
+export function getDocumentList(query) {
+ return request({
+ url: "/documentation/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板鏂囨。
+export function addDocument(data) {
+ return request({
+ url: "/documentation/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼鏂囨。
+export function updateDocument(data) {
+ return request({
+ url: "/documentation/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鍒犻櫎鏂囨。
+export function deleteDocument(ids) {
+ return request({
+ url: "/documentation/delete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 鑾峰彇鏂囨。璇︽儏
+export function getDocumentDetail(id) {
+ return request({
+ url: "/document/" + id,
+ method: "get",
+ });
+}
+
+// 鎼滅储鏂囨。
+export function searchDocument(query) {
+ return request({
+ url: "/document/search",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇浠撳簱缁撴瀯
+export function getWarehouseStructure() {
+ return request({
+ url: "/document/warehouse/structure",
+ method: "get",
+ });
+}
+
+// 闄勪欢绠$悊鐩稿叧鎺ュ彛
+// 娣诲姞闄勪欢
+export function addDocumentationFile(data) {
+ return request({
+ url: "/documentation/documentationFile/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鑾峰彇闄勪欢鍒楄〃
+export function getDocumentationFileList(params) {
+ return request({
+ url: "/documentation/documentationFile/listPage",
+ method: "get",
+ params: params,
+ });
+}
+
+// 鍒犻櫎闄勪欢
+export function deleteDocumentationFile(ids) {
+ return request({
+ url: "/documentation/documentationFile/del",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 鏂囨。鍊熼槄绠$悊鐩稿叧鎺ュ彛
+export function getBorrowList(params) {
+ return request({
+ url: "/documentationBorrowManagement/listPage",
+ method: "get",
+ params: params,
+ });
+}
+
+export function addBorrow(data) {
+ return request({
+ url: "/documentationBorrowManagement/add",
+ method: "post",
+ data: data,
+ });
+}
+
+export function updateBorrow(data) {
+ return request({
+ url: "/documentationBorrowManagement/update",
+ method: "put",
+ data: data,
+ });
+}
+
+export function deleteBorrow(ids) {
+ return request({
+ url: "/documentationBorrowManagement/delete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 缁熻鐩稿叧鎺ュ彛
+// 鑾峰彇鎬讳綋缁熻鏁版嵁
+export function getDocumentationOverview() {
+ return request({
+ url: "/documentation/overview",
+ method: "get",
+ });
+}
+
+// 鑾峰彇鍒嗙被缁熻鏁版嵁
+export function getDocumentationCategoryStats() {
+ return request({
+ url: "/documentation/category",
+ method: "get",
+ });
+}
+
+// 鑾峰彇鐘舵�佺粺璁℃暟鎹�
+export function getDocumentationStatusStats() {
+ return request({
+ url: "/documentation/status",
+ method: "get",
+ });
+}
diff --git a/src/api/fileManagement/return.js b/src/api/fileManagement/return.js
new file mode 100644
index 0000000..9021ac9
--- /dev/null
+++ b/src/api/fileManagement/return.js
@@ -0,0 +1,61 @@
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ褰掕繕璁板綍
+export function getReturnListPage(query) {
+ return request({
+ url: "/documentationBorrowManagement/listPageReturn",
+ method: "get",
+ params: query,
+ });
+}
+
+// 褰掕繕鎿嶄綔
+export function returnDocument(data) {
+ return request({
+ url: "/documentationBorrowManagement/revent",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鍒犻櫎褰掕繕璁板綍
+export function deleteReturn(ids) {
+ return request({
+ url: "/documentationBorrowManagement/reventDelete",
+ method: "delete",
+ data: ids,
+ });
+}
+//鏍规嵁涔︾睄id鏌ヨ鍊熼槄璁板綍
+export function getBorrowListByDocumentationId(id) {
+ return request({
+ url: "/documentationBorrowManagement/getByDocumentationId/"+id,
+ method: "get"
+ });
+}
+
+// 鏇存柊鍊熼槄璁板綍
+export function updateBorrow(data) {
+ return request({
+ url: "/documentationBorrowManagement/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 褰掕繕鏇存柊
+export function reventUpdate(data) {
+ return request({
+ url: "/documentationBorrowManagement/reventUpdate",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鑾峰彇鏂囨。鍒楄〃
+export function getDocumentList() {
+ return request({
+ url: "/documentationBorrowManagement/list",
+ method: "get",
+ });
+}
diff --git a/src/api/fileManagement/statistics.js b/src/api/fileManagement/statistics.js
new file mode 100644
index 0000000..d77375c
--- /dev/null
+++ b/src/api/fileManagement/statistics.js
@@ -0,0 +1,75 @@
+import request from "@/utils/request";
+
+// 鑾峰彇妗f鎬讳綋缁熻
+export function getDocumentStatistics() {
+ return request({
+ url: "/fileManagement/statistics/overview",
+ method: "get",
+ });
+}
+
+// 鑾峰彇妗f鍒嗙被缁熻
+export function getCategoryStatistics() {
+ return request({
+ url: "/fileManagement/statistics/category",
+ method: "get",
+ });
+}
+
+// 鑾峰彇妗f鐘舵�佺粺璁�
+export function getStatusStatistics() {
+ return request({
+ url: "/fileManagement/statistics/status",
+ method: "get",
+ });
+}
+
+// 鑾峰彇妗f鍊熼槄缁熻
+export function getBorrowStatistics() {
+ return request({
+ url: "/fileManagement/statistics/borrow",
+ method: "get",
+ });
+}
+
+// 鑾峰彇妗f骞村害缁熻
+export function getYearStatistics() {
+ return request({
+ url: "/fileManagement/statistics/year",
+ method: "get",
+ });
+}
+
+// 鑾峰彇妗f浣嶇疆缁熻
+export function getLocationStatistics() {
+ return request({
+ url: "/fileManagement/statistics/location",
+ method: "get",
+ });
+}
+
+// 鑾峰彇妗f瓒嬪娍缁熻
+export function getTrendStatistics(params) {
+ return request({
+ url: "/fileManagement/statistics/trend",
+ method: "get",
+ params: params,
+ });
+}
+
+// 鑾峰彇妗f鍊熼槄鎺掕
+export function getBorrowRanking() {
+ return request({
+ url: "/fileManagement/statistics/borrowRanking",
+ method: "get",
+ });
+}
+
+// 鑾峰彇妗f鍒嗙被璇︽儏缁熻
+export function getCategoryDetailStatistics(categoryId) {
+ return request({
+ url: `/fileManagement/statistics/categoryDetail/${categoryId}`,
+ method: "get",
+ });
+}
+
diff --git a/src/api/financialManagement/accounting.js b/src/api/financialManagement/accounting.js
new file mode 100644
index 0000000..69bc7cd
--- /dev/null
+++ b/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,
+ });
+};
diff --git a/src/api/financialManagement/expenseManagement.js b/src/api/financialManagement/expenseManagement.js
index 317dc47..a22af1b 100644
--- a/src/api/financialManagement/expenseManagement.js
+++ b/src/api/financialManagement/expenseManagement.js
@@ -8,6 +8,13 @@
params,
});
};
+export const listPageAnalysis = (params) => {
+ return request({
+ url: "/account/accountExpense/report/analysis",
+ method: "get",
+ params,
+ });
+};
// 鏂板
export function add(data) {
diff --git a/src/api/financialManagement/loanManagement.js b/src/api/financialManagement/loanManagement.js
new file mode 100644
index 0000000..46ea749
--- /dev/null
+++ b/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,
+ });
+};
diff --git a/src/api/inspectionManagement/index.js b/src/api/inspectionManagement/index.js
new file mode 100644
index 0000000..d0c444a
--- /dev/null
+++ b/src/api/inspectionManagement/index.js
@@ -0,0 +1,61 @@
+// 宸℃绠$悊
+import request from '@/utils/request'
+
+// 宸℃浠诲姟琛ㄨ〃鏌ヨ
+export function inspectionTaskList(query) {
+ return request({
+ url: '/inspectionTask/list',
+ method: 'get',
+ params: query
+ })
+}
+// 宸℃浠诲姟琛ㄦ柊澧炰慨鏀�
+export function addOrEditInspectionTask(query) {
+ return request({
+ url: '/inspectionTask/addOrEditInspectionTask',
+ method: 'post',
+ data: query
+ })
+}
+// 宸℃浠诲姟琛ㄥ垹闄�
+export function delInspectionTask(query) {
+ return request({
+ url: '/inspectionTask/delInspectionTask',
+ method: 'delete',
+ data: query
+ })
+}
+// 瀹氭椂宸℃浠诲姟琛ㄥ垹闄�
+export function delTimingTask(query) {
+ return request({
+ url: '/timingTask/delTimingTask',
+ method: 'delete',
+ data: query
+ })
+}
+
+// /inspectionTask/addOrEditInspectionTask
+// 宸℃涓婁紶
+export function uploadInspectionTask(query) {
+ return request({
+ url: '/inspectionTask/addOrEditInspectionTask',
+ method: 'post',
+ data: query
+ })
+}
+// 瀹氭椂宸℃浠诲姟琛ㄦ煡璇�
+export function timingTaskList(query) {
+ return request({
+ url: '/timingTask/list',
+ method: 'get',
+ params: query
+ })
+}
+// 瀹氭椂宸℃浠诲姟琛ㄦ柊澧炰慨鏀�
+export function addOrEditTimingTask(query) {
+ return request({
+ url: '/timingTask/addOrEditTimingTask',
+ method: 'post',
+ data: query
+ })
+}
\ No newline at end of file
diff --git a/src/api/inspectionUpload/index.js b/src/api/inspectionUpload/index.js
new file mode 100644
index 0000000..0d954e2
--- /dev/null
+++ b/src/api/inspectionUpload/index.js
@@ -0,0 +1,43 @@
+// 宸℃涓婁紶
+import request from '@/utils/request'
+
+// 浜岀淮鐮佺鐞嗚〃鏌ヨ
+export function qrCodeList(query) {
+ return request({
+ url: '/qrCode/list',
+ method: 'get',
+ params: query
+ })
+}
+// 浜岀淮鐮佹壂鐮佽褰曡〃鏌ヨ
+export function qrCodeScanRecordList(query) {
+ return request({
+ url: '/qrCodeScanRecord/list',
+ method: 'get',
+ params: query
+ })
+}
+// 浜岀淮鐮佺鐞嗚〃鏂板淇敼
+export function addOrEditQrCode(query) {
+ return request({
+ url: '/qrCode/addOrEditQrCode',
+ method: 'post',
+ data: query
+ })
+}
+// 浜岀淮鐮佹壂鐮佽褰曡〃鏂板淇敼
+export function addOrEditQrCodeRecord(query) {
+ return request({
+ url: '/qrCodeScanRecord/addOrEditQrCodeRecord',
+ method: 'post',
+ data: query
+ })
+}
+// 浜岀淮鐮佹壂鐮佽褰曡〃鏂板淇敼
+export function delQrCode(query) {
+ return request({
+ url: '/qrCode/delQrCode',
+ method: 'delete',
+ data: query
+ })
+}
\ No newline at end of file
diff --git a/src/api/inventoryManagement/stockIn.js b/src/api/inventoryManagement/stockIn.js
index 70f07a7..3481415 100644
--- a/src/api/inventoryManagement/stockIn.js
+++ b/src/api/inventoryManagement/stockIn.js
@@ -18,6 +18,15 @@
});
};
+// 鏌ヨ鐢熶骇鍏ュ簱淇℃伅鍒楄〃
+export const getStockInPageByProductProduction = (params) => {
+ return request({
+ url: "/stockin/listPageByProductProduction",
+ method: "get",
+ params,
+ });
+};
+
// 鍑哄簱鍙拌处-鏌ヨ鑷畾涔夊叆搴撲俊鎭垪琛�
export const getStockInPageByCustom = (params) => {
return request({
@@ -142,5 +151,11 @@
}
-//
+//鏌ヨ搴撳瓨鍥捐〃鏁版嵁
+export function getStockInChartData() {
+ return request({
+ url: '/stockin/listReport',
+ method: 'get'
+ })
+}
diff --git a/src/api/inventoryManagement/stockInRecord.js b/src/api/inventoryManagement/stockInRecord.js
new file mode 100644
index 0000000..1746bfe
--- /dev/null
+++ b/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,
+ });
+};
\ No newline at end of file
diff --git a/src/api/inventoryManagement/stockInventory.js b/src/api/inventoryManagement/stockInventory.js
new file mode 100644
index 0000000..a50194a
--- /dev/null
+++ b/src/api/inventoryManagement/stockInventory.js
@@ -0,0 +1,37 @@
+import request from "@/utils/request.js";
+// 鍒嗛〉鏌ヨ搴撳瓨璁板綍鍒楄〃
+export const getStockInventoryListPage = (params) => {
+ return request({
+ url: "/stockInventory/pagestockInventory",
+ method: "get",
+ params,
+ });
+};
+
+// 鍒涘缓搴撳瓨璁板綍
+export const createStockInventory = (params) => {
+ return request({
+ url: "/stockInventory/addstockInventory",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鍑忓皯搴撳瓨璁板綍
+export const subtractStockInventory = (params) => {
+ return request({
+ url: "/stockInventory/subtractStockInventory",
+ method: "post",
+ data: params,
+ });
+};
+
+export const exportStockInventory = (params) => {
+ return request({
+ url: "/stockInventory/exportStockInventory",
+ method: "post",
+ data: params,
+ });
+};
+
+
diff --git a/src/api/inventoryManagement/stockManage.js b/src/api/inventoryManagement/stockManage.js
index 4f5d957..e2a4ebf 100644
--- a/src/api/inventoryManagement/stockManage.js
+++ b/src/api/inventoryManagement/stockManage.js
@@ -17,7 +17,14 @@
params,
});
};
-
+// 鏌ヨ鎴愬搧搴撳瓨淇℃伅鍒楄〃
+export const getStockManageProduction = (params) => {
+ return request({
+ url: "/stockin/listPageProductionStock",
+ method: "get",
+ params,
+ });
+};
// 鏌ヨ鑷畾涔夊叆搴撳簱瀛樹俊鎭垪琛�
export const getStockManagePageByCustom = (params) => {
return request({
diff --git a/src/api/inventoryManagement/stockOut.js b/src/api/inventoryManagement/stockOut.js
index 7d188af..3d260b3 100644
--- a/src/api/inventoryManagement/stockOut.js
+++ b/src/api/inventoryManagement/stockOut.js
@@ -3,35 +3,17 @@
// 鍑哄簱鍙拌处-閲囪喘鍑哄簱鏌ヨ鍑哄簱鍒楄〃
export const getStockOutPage = (params) => {
return request({
- url: "/stockmanagement/listPage",
+ url: "/stockOutRecord/listPage",
method: "get",
params,
});
};
-//鏂板鍑哄簱淇℃伅
-export const addStockOut = (data) => {
- return request({
- url: '/stockout/add',
- method: 'post',
- data: data
- })
-}
-
-//淇敼鍑哄簱淇℃伅
-export const updateStockOut = (data) => {
- return request({
- url: "/stockout/update",
- method: "put",
- data,
- });
-}
-
//鍒犻櫎鍑哄簱淇℃伅
export const delStockOut = (ids) => {
return request({
- url: '/stockmanagement/del',
- method: 'post',
- data: ids
- })
+ url: "/stockOutRecord",
+ method: "delete",
+ data: ids,
+ });
}
diff --git a/src/api/inventoryManagement/stockUninventory.js b/src/api/inventoryManagement/stockUninventory.js
new file mode 100644
index 0000000..bdde283
--- /dev/null
+++ b/src/api/inventoryManagement/stockUninventory.js
@@ -0,0 +1,27 @@
+import request from "@/utils/request.js";
+// 鍒嗛〉鏌ヨ搴撳瓨璁板綍鍒楄〃
+export const getStockUninventoryListPage = (params) => {
+ return request({
+ url: "/stockUninventory/pagestockUninventory",
+ method: "get",
+ params,
+ });
+};
+
+// 鍒涘缓搴撳瓨璁板綍
+export const createStockUnInventory = (params) => {
+ return request({
+ url: "/stockUninventory/addstockUninventory",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鍑忓皯搴撳瓨璁板綍
+export const subtractStockUnInventory = (params) => {
+ return request({
+ url: "/stockUninventory/subtractstockUninventory",
+ method: "post",
+ data: params,
+ });
+};
diff --git a/src/api/oaSystem/projectManagement.js b/src/api/oaSystem/projectManagement.js
new file mode 100644
index 0000000..56c9287
--- /dev/null
+++ b/src/api/oaSystem/projectManagement.js
@@ -0,0 +1,154 @@
+import request from "@/utils/request";
+import { parseStrEmpty } from "@/utils/ruoyi";
+
+// 鏌ヨ椤圭洰鍒楄〃
+export function listProject(query) {
+ return request({
+ url: "/oA/project/listPage",
+ method: "get",
+ params: query
+ });
+}
+
+// 鏌ヨ椤圭洰鍒楄〃璇︾粏
+export function getProject(query) {
+ return request({
+ url: "oA/project/getList",
+ method: "get"
+ });
+}
+
+// 鏂板椤圭洰
+export function addProject(data) {
+ return request({
+ url: "/oA/project/add",
+ method: "post",
+ data: data
+ });
+}
+
+// 淇敼椤圭洰
+export function updateProject(data) {
+ return request({
+ url: "/oA/project/update",
+ method: "post",
+ data: data
+ });
+}
+
+// 鍒犻櫎椤圭洰
+export function delProject(projectId) {
+ return request({
+ url: "/oA/project/delete/" + projectId,
+ method: "delete"
+ });
+}
+// 瀵煎嚭椤圭洰
+export function exportProject(data) {
+ return request({
+ url: "/oA/project/export",
+ method: "post",
+ data: data
+ });
+}
+// // 鎵归噺鍒犻櫎椤圭洰
+// export function delProjectBatch(projectIds) {
+// return request({
+// url: "/oaSystem/project/batch",
+// method: "delete",
+// data: projectIds
+// });
+// }
+
+// 鏍规嵁椤圭洰闃舵id鏌ヨ椤圭洰闃舵浠诲姟鍒楄〃
+export function listProjectTask(phaseId) {
+ return request({
+ url: "/oA/projectPhaseTask/listByPhaseId/"+ phaseId,
+ method: "get"
+ });
+}
+
+// // 鏌ヨ椤圭洰浠诲姟璇︾粏
+// export function getProjectTask(taskId) {
+// return request({
+// url: "/oaSystem/project/task/" + taskId,
+// method: "get"
+// });
+// }
+
+// 鏂板椤圭洰闃舵浠诲姟
+export function addProjectTask(data) {
+ return request({
+ url: "/oA/projectPhaseTask/add",
+ method: "post",
+ data: data
+ });
+}
+
+// 淇敼椤圭洰闃舵浠诲姟
+export function updateProjectTask(data) {
+ return request({
+ url: "/oA/projectPhaseTask/update",
+ method: "post",
+ data: data
+ });
+}
+
+// 鍒犻櫎椤圭洰闃舵浠诲姟
+export function delProjectTask(taskId) {
+ return request({
+ url: "/oA/projectPhaseTask/delete/" + taskId,
+ method: "delete"
+ });
+}
+
+// 椤圭洰id鏌ヨ椤圭洰闃舵鍒楄〃
+export function listProjectPhase(projectId) {
+ return request({
+ url: "/oA/projectPhase/listByProjectId/" + projectId,
+ method: "get"
+ });
+}
+// 鏂板椤圭洰闃舵
+export function addProjectPhase(data) {
+ return request({
+ url: "/oA/projectPhase/add",
+ method: "post",
+ data: data
+ });
+}
+
+// 淇敼椤圭洰闃舵
+export function updateProjectPhase(data) {
+ return request({
+ url: "/oA/projectPhase/update",
+ method: "post",
+ data: data
+ });
+
+}
+// 鍒犻櫎椤圭洰闃舵
+export function delProjectPhase(phaseId) {
+ return request({
+ url: "/oA/projectPhase/delete/" + phaseId,
+ method: "delete"
+ })
+}
+//
+
+// // 鏌ヨ椤圭洰閲岀▼纰戝垪琛�
+// export function listProjectMilestone(query) {
+// return request({
+// url: "/oaSystem/project/milestone/list",
+// method: "get",
+// params: query
+// });
+// }
+
+// // 椤圭洰缁熻淇℃伅
+// export function getProjectStatistics() {
+// return request({
+// url: "/oaSystem/project/statistics",
+// method: "get"
+// });
+// }
\ No newline at end of file
diff --git a/src/api/personnelManagement/employeeRecord.js b/src/api/personnelManagement/employeeRecord.js
index 378756a..a4ad34b 100644
--- a/src/api/personnelManagement/employeeRecord.js
+++ b/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,
+ })
}
\ No newline at end of file
diff --git a/src/api/personnelManagement/onboarding.js b/src/api/personnelManagement/onboarding.js
deleted file mode 100644
index 39dbf22..0000000
--- a/src/api/personnelManagement/onboarding.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import request from "@/utils/request";
-
-// 鏌ヨ浜哄憳鍏ヨ亴鍒楄〃
-export function staffJoinListPage(query) {
- return request({
- url: "/staff/staffJoinLeaveRecord/listPage",
- method: "get",
- params: query,
- });
-}
-// 鏂板浜哄憳鍏ヨ亴
-export function staffJoinAdd(query) {
- return request({
- url: "/staff/staffJoinLeaveRecord/add",
- method: "post",
- data: query,
- });
-}
-// 淇敼浜哄憳鍏ヨ亴
-export function staffJoinUpdate(query) {
- return request({
- url: "/staff/staffJoinLeaveRecord/update",
- method: "post",
- data: query,
- });
-}
-// 鏌ヨ鍛樺伐鍏ヨ亴淇℃伅
-export function getStaffJoinInfo(query) {
- return request({
- url: "/staff/staffJoinLeaveRecord/" + query,
- method: "get",
- data: query,
- });
-}
-// 鍒犻櫎鍛樺伐
-export function staffJoinDel(query) {
- return request({
- url: "/staff/staffJoinLeaveRecord/del",
- method: "delete",
- data: query,
- });
-}
-
-export function getStaffOnJob() {
- return request({
- url: "/staff/staffOnJob/list",
- method: "get",
- });
-}
diff --git a/src/api/personnelManagement/scheduling.js b/src/api/personnelManagement/scheduling.js
new file mode 100644
index 0000000..8e4b058
--- /dev/null
+++ b/src/api/personnelManagement/scheduling.js
@@ -0,0 +1,32 @@
+// 鎺掔彮绠$悊
+import request from "@/utils/request";
+
+export function save(data) {
+ return request({
+ url: "/staff/staffScheduling/save",
+ method: "post",
+ data: data,
+ });
+}
+
+export function del(id) {
+ return request({
+ url: "/staff/staffScheduling/del/"+id,
+ method: "delete",
+ });
+}
+
+export function delByIds(data) {
+ return request({
+ url: "/staff/staffScheduling/save",
+ method: "post",
+ data: data,
+ });
+}
+export function listPage(data){
+ return request({
+ url: "/staff/staffScheduling/listPage",
+ method: "post",
+ data: data
+ })
+}
diff --git a/src/api/personnelManagement/selfService.js b/src/api/personnelManagement/selfService.js
new file mode 100644
index 0000000..c95436a
--- /dev/null
+++ b/src/api/personnelManagement/selfService.js
@@ -0,0 +1,71 @@
+// 钖叕绠$悊
+import request from "@/utils/request";
+
+// 鏌ヨ鑰冨嫟鍒楄〃
+export function personalAttendanceRecordsListPage(query) {
+ return request({
+ url: "/staff/personalAttendanceRecords/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏌ヨ鍋囨湡鐢宠鍒楄〃
+export function holidayApplicationListPage(query) {
+ return request({
+ url: "/staff/holidayApplication/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏂板
+export function personalAttendanceRecordsAdd(query) {
+ return request({
+ url: "/staff/personalAttendanceRecords/add",
+ method: "post",
+ data: query,
+ });
+}
+// 鏂板鍋囨湡鐢宠
+export function holidayApplicationAdd(query) {
+ return request({
+ url: "/staff/holidayApplication/add",
+ method: "post",
+ data: query,
+ });
+}
+// 淇敼
+export function personalAttendanceRecordsUpdate(query) {
+ return request({
+ url: "/staff/personalAttendanceRecords/update",
+ method: "put",
+ data: query,
+ });
+}
+// 淇敼鍋囨湡鐢宠
+export function holidayApplicationUpdate(query) {
+ return request({
+ url: "/staff/holidayApplication/update",
+ method: "post",
+ data: query,
+ });
+}
+// 鍒犻櫎
+export function personalAttendanceRecordsDelete(id) {
+ return request({
+ url: "/staff/personalAttendanceRecords/delete/"+id,
+ method: "delete",
+ });
+}
+// 鍒犻櫎鍋囨湡鐢宠
+export function holidayApplicationDelete(id) {
+ return request({
+ url: "/staff/holidayApplication/delete/"+id,
+ method: "delete",
+ });
+}
+// export function del(id) {
+// return request({
+// url: "/staff/staffScheduling/del/"+id,
+// method: "delete",
+// });
+// }
\ No newline at end of file
diff --git a/src/api/personnelManagement/staffAnalytics.js b/src/api/personnelManagement/staffAnalytics.js
new file mode 100644
index 0000000..83eb375
--- /dev/null
+++ b/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"
+ });
+}
+
+
diff --git a/src/api/personnelManagement/staffContract.js b/src/api/personnelManagement/staffContract.js
new file mode 100644
index 0000000..a6b71cb
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/personnelManagement/staffLeave.js b/src/api/personnelManagement/staffLeave.js
new file mode 100644
index 0000000..d675996
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/procurementManagement/advancedPriceManagement.js b/src/api/procurementManagement/advancedPriceManagement.js
new file mode 100644
index 0000000..a30921c
--- /dev/null
+++ b/src/api/procurementManagement/advancedPriceManagement.js
@@ -0,0 +1,38 @@
+// 楂樼骇閲囪喘浠锋牸绠$悊API鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ浠锋牸鍒楄〃
+export function listPage(query) {
+ return request({
+ url: "/procurementPriceManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板浠锋牸
+export function add(data) {
+ return request({
+ url: "/procurementPriceManagement/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鏇存柊浠锋牸
+export function update(data) {
+ return request({
+ url: "/procurementPriceManagement/update",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鍒犻櫎浠锋牸
+export function del(data) {
+ return request({
+ url: `/procurementPriceManagement/del`,
+ method: "delete",
+ data
+ });
+}
diff --git a/src/api/procurementManagement/arrivalManagement.js b/src/api/procurementManagement/arrivalManagement.js
new file mode 100644
index 0000000..107fc3c
--- /dev/null
+++ b/src/api/procurementManagement/arrivalManagement.js
@@ -0,0 +1,43 @@
+// 閿�鍞彴璐﹂〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/inboundManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+export function listPageCopy(query) {
+ return request({
+ url: "/inboundManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/inboundManagement/add",
+ method: "post",
+ data
+ });
+}
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/inboundManagement/update",
+ method: "post",
+ data
+ });
+}
+// 鍒犻櫎閿�鍞彴璐�
+export function del(data) {
+ return request({
+ url: "/inboundManagement/del",
+ method: "delete",
+ data
+ });
+}
\ No newline at end of file
diff --git a/src/api/procurementManagement/procurementInvoiceLedger.js b/src/api/procurementManagement/procurementInvoiceLedger.js
index 76f8410..038e94f 100644
--- a/src/api/procurementManagement/procurementInvoiceLedger.js
+++ b/src/api/procurementManagement/procurementInvoiceLedger.js
@@ -61,7 +61,7 @@
// 鏌ヨ鍒楄〃
export function invoiceListPage(query) {
return request({
- url: "/purchase/registration/listPage",
+ url: "/sales/product/listPagePurchaseLedger",
method: "get",
params: query,
});
@@ -83,11 +83,18 @@
});
}
-export function getProductRecordById(params) {
+// export function getProductRecordById(params) {
+// return request({
+// url: "/purchase/registration/getProductRecordById",
+// method: "get",
+// params: params,
+// });
+// }
+export function getProductRecordById(data) {
return request({
url: "/purchase/registration/getProductRecordById",
- method: "get",
- params: params,
+ method: "post",
+ data: data,
});
}
diff --git a/src/api/procurementManagement/procurementLedger.js b/src/api/procurementManagement/procurementLedger.js
index 5b6327d..06a0c7e 100644
--- a/src/api/procurementManagement/procurementLedger.js
+++ b/src/api/procurementManagement/procurementLedger.js
@@ -99,3 +99,30 @@
params: query,
});
}
+export function updateApprovalStatus(query) {
+ return request({
+ url: "/purchase/ledger/updateApprovalStatus",
+ method: "post",
+ data: query,
+ });
+}
+
+// 淇濆瓨閲囪喘妯℃澘
+// /purchase/ledger/addPurchaseTemplate
+export function addPurchaseTemplate(data) {
+ return request({
+ url: "/purchase/ledger/addPurchaseTemplate",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鏌ヨ閲囪喘妯℃澘
+// /purchase/ledger/getPurchaseTemplateList
+export function getPurchaseTemplateList(query) {
+ return request({
+ url: "/purchase/ledger/getPurchaseTemplateList",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/api/procurementManagement/procurementPlan.js b/src/api/procurementManagement/procurementPlan.js
new file mode 100644
index 0000000..48a1a74
--- /dev/null
+++ b/src/api/procurementManagement/procurementPlan.js
@@ -0,0 +1,47 @@
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ閲囪喘璁″垝鍒楄〃
+export function listPage(query) {
+ return request({
+ url: "/procurementPlan/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板閲囪喘璁″垝
+export function add(data) {
+ return request({
+ url: "/procurementPlan/add",
+ method: "post",
+ data,
+ });
+}
+
+// 淇敼閲囪喘璁″垝
+export function update(data) {
+ return request({
+ url: "/procurementPlan/update",
+ method: "post",
+ data,
+ });
+}
+
+// 鍒犻櫎閲囪喘璁″垝
+export function del(data) {
+ return request({
+ url: "/procurementPlan/del",
+ method: "delete",
+ data,
+ });
+}
+
+// 鍒犻櫎閲囪喘璁″垝
+export function listPageCopy(query) {
+ return request({
+ url: "/stockin/listPageCopy",
+ method: "get",
+ params: query,
+ });
+}
+
diff --git a/src/api/procurementManagement/procurementReport.js b/src/api/procurementManagement/procurementReport.js
new file mode 100644
index 0000000..32c50c7
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/procurementManagement/returnManagement.js b/src/api/procurementManagement/returnManagement.js
new file mode 100644
index 0000000..e765701
--- /dev/null
+++ b/src/api/procurementManagement/returnManagement.js
@@ -0,0 +1,35 @@
+// 閿�鍞彴璐﹂〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/returnManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/returnManagement/add",
+ method: "post",
+ data
+ });
+}
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/returnManagement/update",
+ method: "post",
+ data
+ });
+}
+// 鍒犻櫎閿�鍞彴璐�
+export function del(data) {
+ return request({
+ url: "/returnManagement/del",
+ method: "delete",
+ data
+ });
+}
\ No newline at end of file
diff --git a/src/api/procurementManagement/transferManagement.js b/src/api/procurementManagement/transferManagement.js
new file mode 100644
index 0000000..fa404d1
--- /dev/null
+++ b/src/api/procurementManagement/transferManagement.js
@@ -0,0 +1,34 @@
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function getPurchaseOrders(query) {
+ return request({
+ url: "/purchase/ledger/listPage",
+ method: "get",
+ params: query,
+ });
+}
+//
+export function confirmReceipt(query) {
+ return request({
+ url: "",
+ method: "post",
+ data: query,
+ });
+}
+// 澧炴坊閲囪喘寮傚父璁板綍
+export function addPurchaseException(query) {
+ return request({
+ url: "/procurementExceptionRecord/add",
+ method: "post",
+ data: query,
+ });
+}
+// 淇敼閲囪喘寮傚父璁板綍
+export function updatePurchaseException(query) {
+ return request({
+ url: "/procurementExceptionRecord/update",
+ method: "post",
+ data: query,
+ });
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/processRoute.js b/src/api/productionManagement/processRoute.js
new file mode 100644
index 0000000..c13b2fc
--- /dev/null
+++ b/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',
+ })
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/processRouteItem.js b/src/api/productionManagement/processRouteItem.js
new file mode 100644
index 0000000..9e81406
--- /dev/null
+++ b/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) {
+ // 灏唅d鏁扮粍杞崲涓洪�楀彿鍒嗛殧鐨勫瓧绗︿覆锛屾嫾鎺ュ埌URL鍚庨潰
+ const idsStr = Array.isArray(ids) ? ids.join(",") : ids;
+ return request({
+ url: `/processRouteItem/batchDelete/${idsStr}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/productionManagement/productBom.js b/src/api/productionManagement/productBom.js
new file mode 100644
index 0000000..8132e9c
--- /dev/null
+++ b/src/api/productionManagement/productBom.js
@@ -0,0 +1,57 @@
+// 浜у搧BOM椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/productBom/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/productBom/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/productBom/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鎵归噺鍒犻櫎
+export function batchDelete(ids) {
+ return request({
+ url: "/productBom/batchDelete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 鏍规嵁浜у搧鍨嬪彿ID鏌ヨBOM
+export function getByModel(productModelId) {
+ return request({
+ url: "/productBom/getByModel",
+ method: "get",
+ params: { productModelId },
+ });
+}
+
+// 瀵煎嚭BOM
+export function exportBom(bomId) {
+ return request({
+ url: "/productBom/exportBom",
+ method: "post",
+ params: { bomId },
+ responseType: "blob",
+ });
+}
diff --git a/src/api/productionManagement/productProcessRoute.js b/src/api/productionManagement/productProcessRoute.js
new file mode 100644
index 0000000..e8d5da5
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/productionManagement/productStructure.js b/src/api/productionManagement/productStructure.js
new file mode 100644
index 0000000..e69e14a
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/productionManagement/productionOrder.js b/src/api/productionManagement/productionOrder.js
index 29cff35..9f110a7 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -4,11 +4,74 @@
// 鍒嗛〉鏌ヨ
export function schedulingListPage(query) {
return request({
- url: "/productionOrder/listPage",
+ url: "/salesLedger/scheduling/listPage",
method: "get",
params: query,
});
}
+
+export function productOrderListPage(query) {
+ return request({
+ url: "/productOrder/page",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鐢熶骇璁㈠崟-鎸変骇鍝佸瀷鍙锋煡璇㈠彲鐢ㄥ伐鑹鸿矾绾垮垪琛�
+export function listProcessRoute(query) {
+ return request({
+ url: "/productOrder/listProcessRoute",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鐢熶骇璁㈠崟-缁戝畾宸ヨ壓璺嚎
+export function bindingRoute(data) {
+ return request({
+ url: "/productOrder/bindingRoute",
+ method: "post",
+ data,
+ });
+}
+
+// 鐢熶骇璁㈠崟-鏌ヨ浜у搧缁撴瀯鍒楄〃
+export function listProcessBom(query) {
+ return request({
+ url: "/productOrder/listProcessBom",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+export function schedulingList(query) {
+ return request({
+ url: "/salesLedger/scheduling/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 淇濆瓨鐐掓満璁剧疆
+export function addSpeculatTrading(data) {
+ return request({
+ url: "/salesLedger/scheduling/addSpeculatTrading",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼鐐掓満璁剧疆
+export function updateSpeculatTrading(data) {
+ return request({
+ url: "/salesLedger/scheduling/updateSpeculatTrading",
+ method: "post",
+ data: data,
+ });
+}
+
// 鐢熶骇娲惧伐
export function productionDispatch(query) {
return request({
@@ -17,28 +80,37 @@
data: query,
});
}
+// 鑷姩娲惧伐
+export function productionDispatchList(query) {
+ return request({
+ url: "/salesLedger/scheduling/productionDispatchList",
+ method: "post",
+ data: query,
+ });
+}
-// 鏂板鐢熶骇璁㈠崟
-export function addProductionOrder(query) {
+// 鏌ヨ鎹熻�楃巼
+export function getLossRate() {
return request({
- url: "/productionOrder/addProductionOrder",
- method: "post",
- data: query,
+ url: "/salesLedger/scheduling/loss",
+ method: "get",
});
}
-// 淇敼鐢熶骇璁㈠崟
-export function updateProductionOrder(query) {
+
+// 鏂板鎹熻�楃巼
+export function addLossRate(data) {
return request({
- url: "/productionOrder/updateProductionOrder",
+ url: "/salesLedger/scheduling/addLoss",
method: "post",
- data: query,
+ data: data,
});
}
-// 鍒犻櫎鐢熶骇璁㈠崟
-export function deleteProductionOrder(query) {
+
+// 淇敼鎹熻�楃巼
+export function updateLossRate(data) {
return request({
- url: "/productionOrder/deleteProductionOrder",
- method: "delete",
- data: query,
+ url: "/salesLedger/scheduling/updateLoss",
+ method: "post",
+ data: data,
});
}
\ No newline at end of file
diff --git a/src/api/productionManagement/productionProcess.js b/src/api/productionManagement/productionProcess.js
new file mode 100644
index 0000000..d3a453c
--- /dev/null
+++ b/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",
+ });
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/productionProductInput.js b/src/api/productionManagement/productionProductInput.js
new file mode 100644
index 0000000..f72cd9b
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/productionManagement/productionProductMain.js b/src/api/productionManagement/productionProductMain.js
new file mode 100644
index 0000000..0493f8b
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/productionManagement/productionProductOutput.js b/src/api/productionManagement/productionProductOutput.js
new file mode 100644
index 0000000..10095e9
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/productionManagement/productionReporting.js b/src/api/productionManagement/productionReporting.js
index fdec712..3e29943 100644
--- a/src/api/productionManagement/productionReporting.js
+++ b/src/api/productionManagement/productionReporting.js
@@ -32,4 +32,12 @@
method: "post",
data: query,
});
-}
\ No newline at end of file
+}
+// 鐢熶骇鎶ュ伐-鍒犻櫎
+export function productionReportDelete(query) {
+ return request({
+ url: "/productionProductMain/delete",
+ method: "delete",
+ data: query,
+ });
+}
diff --git a/src/api/productionManagement/workOrder.js b/src/api/productionManagement/workOrder.js
new file mode 100644
index 0000000..bf4b381
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/publicApi/index.js b/src/api/publicApi/index.js
new file mode 100644
index 0000000..8022592
--- /dev/null
+++ b/src/api/publicApi/index.js
@@ -0,0 +1,42 @@
+// 鏂囨。绠$悊
+import request from '@/utils/request'
+
+
+// /system/user/listAll
+// 鏌ヨ鎵�鏈夌敤鎴峰垪琛�
+export function userListAll() {
+ return request({
+ url: '/system/user/listAll',
+ method: 'get'
+ })
+}
+
+// /equipmentManagement/equipmentList
+// 鏌ヨ璁惧鍒楄〃
+export function getEquipmentList(query) {
+ return request({
+ url: '/equipmentManagement/equipmentList',
+ method: 'get',
+ params: query
+ })
+}
+
+// /coalInfo/coalInfoList
+// 鏌ヨ鐓ょ鍒楄〃
+export function getCoalInfoList(query) {
+ return request({
+ url: '/coalInfo/coalInfoList',
+ method: 'get',
+ params: query
+ })
+}
+
+// /coalField/coalFieldList
+// 鏌ヨ鐓よ川瀛楁鍒楄〃
+export function getCoalFieldList(query) {
+ return request({
+ url: '/coalField/coalFieldList',
+ method: 'get',
+ params: query
+ })
+}
\ No newline at end of file
diff --git a/src/api/qualityManagement/metricMaintenance.js b/src/api/qualityManagement/metricMaintenance.js
index 9bdff23..1ee9cad 100644
--- a/src/api/qualityManagement/metricMaintenance.js
+++ b/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',
- })
-}
\ No newline at end of file
+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 },
+ });
+}
diff --git a/src/api/qualityManagement/nearExpiryReturn.js b/src/api/qualityManagement/nearExpiryReturn.js
new file mode 100644
index 0000000..4b70de5
--- /dev/null
+++ b/src/api/qualityManagement/nearExpiryReturn.js
@@ -0,0 +1,46 @@
+import request from '@/utils/request'
+
+// 鏌ヨ涓存湡閫�鍥炲彴璐﹀垪琛�
+export function nearExpiryReturnListPage(query) {
+ return request({
+ url: '/quality/nearExpiryReturn/listPage',
+ method: 'get',
+ params: query,
+ })
+}
+
+// 鏂板涓存湡閫�鍥炲彴璐�
+export function nearExpiryReturnAdd(data) {
+ return request({
+ url: '/quality/nearExpiryReturn/add',
+ method: 'post',
+ data: data,
+ })
+}
+
+// 淇敼涓存湡閫�鍥炲彴璐�
+export function nearExpiryReturnUpdate(data) {
+ return request({
+ url: '/quality/nearExpiryReturn/update',
+ method: 'post',
+ data: data,
+ })
+}
+
+// 鍒犻櫎涓存湡閫�鍥炲彴璐�
+export function nearExpiryReturnDel(ids) {
+ return request({
+ url: '/quality/nearExpiryReturn/del',
+ method: 'delete',
+ data: ids,
+ })
+}
+
+// 鑾峰彇涓存湡閫�鍥炲彴璐﹁鎯�
+export function nearExpiryReturnDetail(id) {
+ return request({
+ url: '/quality/nearExpiryReturn/' + id,
+ method: 'get',
+ })
+}
+
diff --git a/src/api/qualityManagement/qualityTestStandardBinding.js b/src/api/qualityManagement/qualityTestStandardBinding.js
new file mode 100644
index 0000000..e4432a6
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/reportAnalysis/qualityReport.js b/src/api/reportAnalysis/qualityReport.js
new file mode 100644
index 0000000..a179a5f
--- /dev/null
+++ b/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 }
+ })
+}
diff --git a/src/api/salesManagement/deliveryLedger.js b/src/api/salesManagement/deliveryLedger.js
new file mode 100644
index 0000000..bb8689f
--- /dev/null
+++ b/src/api/salesManagement/deliveryLedger.js
@@ -0,0 +1,39 @@
+// 鍙戣揣鍙拌处椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function deliveryLedgerListPage(query) {
+ return request({
+ url: "/shippingInfo/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 淇敼鍙戣揣鍙拌处
+export function addOrUpdateDeliveryLedger(query) {
+ return request({
+ url: "/shippingInfo/update",
+ method: "post",
+ data: query,
+ });
+}
+
+// 鍒犻櫎鍙戣揣鍙拌处
+export function delDeliveryLedger(query) {
+ return request({
+ url: "/shippingInfo/delete",
+ method: "delete",
+ data: query,
+ });
+}
+
+// 鏂板鍙戣揣淇℃伅
+export function addShippingInfo(data) {
+ return request({
+ url: "/shippingInfo/add",
+ method: "post",
+ data,
+ });
+}
+
diff --git a/src/api/salesManagement/indicatorStats.js b/src/api/salesManagement/indicatorStats.js
new file mode 100644
index 0000000..47d7794
--- /dev/null
+++ b/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,
+ });
+}
diff --git a/src/api/salesManagement/paymentShipping.js b/src/api/salesManagement/paymentShipping.js
new file mode 100644
index 0000000..c163540
--- /dev/null
+++ b/src/api/salesManagement/paymentShipping.js
@@ -0,0 +1,35 @@
+// 閿�鍞彴璐﹂〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/paymentShipping/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/paymentShipping/add",
+ method: "post",
+ data
+ });
+}
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/paymentShipping/update",
+ method: "post",
+ data
+ });
+}
+// 鍒犻櫎閿�鍞彴璐�
+export function deletePaymentShipping(data) {
+ return request({
+ url: "/paymentShipping/delete",
+ method: "delete",
+ data
+ });
+}
\ No newline at end of file
diff --git a/src/api/salesManagement/receiptPayment.js b/src/api/salesManagement/receiptPayment.js
index b5d0cf5..0f0529d 100644
--- a/src/api/salesManagement/receiptPayment.js
+++ b/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
})
diff --git a/src/api/salesManagement/salesLedger.js b/src/api/salesManagement/salesLedger.js
index 3a2fa22..6548927 100644
--- a/src/api/salesManagement/salesLedger.js
+++ b/src/api/salesManagement/salesLedger.js
@@ -109,3 +109,11 @@
params: query,
});
}
+// 閿�鍞彴璐﹂〉闈㈠彂璐э紝鏌ヨ搴撳瓨鏄惁鍏呰冻
+export function getProductInventory(query) {
+ return request({
+ url: "/sales/ledger/getProductInventory",
+ method: "get",
+ params: query,
+ });
+}
\ No newline at end of file
diff --git a/src/api/salesManagement/salespersonManagement.js b/src/api/salesManagement/salespersonManagement.js
new file mode 100644
index 0000000..5c5cf66
--- /dev/null
+++ b/src/api/salesManagement/salespersonManagement.js
@@ -0,0 +1,35 @@
+// 閿�鍞彴璐﹂〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/salespersonManagement/listPage",
+ method: "get",
+ params: query,
+ });
+}
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/salespersonManagement/add",
+ method: "post",
+ data
+ });
+}
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/salespersonManagement/update",
+ method: "post",
+ data
+ });
+}
+// 鍒犻櫎閿�鍞彴璐�
+export function deleteSalespersonManagement(data) {
+ return request({
+ url: "/salespersonManagement/delete",
+ method: "delete",
+ data
+ });
+}
\ No newline at end of file
diff --git a/src/api/salesManagement/strategyControl.js b/src/api/salesManagement/strategyControl.js
new file mode 100644
index 0000000..d86864e
--- /dev/null
+++ b/src/api/salesManagement/strategyControl.js
@@ -0,0 +1,202 @@
+// 绛栫暐绠℃帶椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// ========== 浠锋牸绛栫暐閰嶇疆 ==========
+
+// 鍒嗛〉鏌ヨ浠锋牸绛栫暐鍒楄〃
+export function getPriceStrategyList(query) {
+ return request({
+ url: "/sales/priceStrategy/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏌ヨ浠锋牸绛栫暐璇︽儏
+export function getPriceStrategyDetail(id) {
+ return request({
+ url: "/sales/priceStrategy/detail",
+ method: "get",
+ params: { id },
+ });
+}
+
+// 鏂板浠锋牸绛栫暐
+export function addPriceStrategy(data) {
+ return request({
+ url: "/sales/priceStrategy/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼浠锋牸绛栫暐
+export function updatePriceStrategy(data) {
+ return request({
+ url: "/sales/priceStrategy/update",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鍒犻櫎浠锋牸绛栫暐
+export function deletePriceStrategy(id) {
+ return request({
+ url: "/sales/priceStrategy/delete",
+ method: "delete",
+ params: { id },
+ });
+}
+
+// 鍚敤/绂佺敤浠锋牸绛栫暐
+export function togglePriceStrategy(data) {
+ return request({
+ url: "/sales/priceStrategy/toggle",
+ method: "post",
+ data: data,
+ });
+}
+
+// ========== 鍚堝悓鎵ц鐩戞帶 ==========
+
+// 鑾峰彇鍚堝悓鎵ц缁熻鏁版嵁
+export function getContractStats(query) {
+ return request({
+ url: "/sales/contract/stats",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鍒嗛〉鏌ヨ鍚堝悓鎵ц鍒楄〃
+export function getContractExecutionList(query) {
+ return request({
+ url: "/sales/contract/executionList",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏌ヨ鍚堝悓鎵ц璇︽儏
+export function getContractExecutionDetail(contractNo) {
+ return request({
+ url: "/sales/contract/executionDetail",
+ method: "get",
+ params: { contractNo },
+ });
+}
+
+// 鏇存柊鍚堝悓鎵ц杩涘害
+export function updateContractProgress(data) {
+ return request({
+ url: "/sales/contract/updateProgress",
+ method: "post",
+ data: data,
+ });
+}
+
+// ========== 鍘嗗彶姣斾环鍒嗘瀽 ==========
+
+// 鏌ヨ鍘嗗彶浠锋牸瀵规瘮鏁版嵁
+export function getPriceComparisonList(query) {
+ return request({
+ url: "/sales/priceComparison/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇浠锋牸瓒嬪娍鍥捐〃鏁版嵁
+export function getPriceTrendChart(query) {
+ return request({
+ url: "/sales/priceComparison/trendChart",
+ method: "get",
+ params: query,
+ });
+}
+
+// 瀵煎嚭鍘嗗彶姣斾环鏁版嵁
+export function exportPriceComparison(query) {
+ return request({
+ url: "/sales/priceComparison/export",
+ method: "get",
+ params: query,
+ responseType: "blob",
+ });
+}
+
+// ========== 鍒╂鼎鍒嗘瀽 ==========
+
+// 鑾峰彇鍒╂鼎缁熻鏁版嵁
+export function getProfitStats(query) {
+ return request({
+ url: "/sales/profit/stats",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鍒嗛〉鏌ヨ鍒╂鼎鍒嗘瀽鍒楄〃
+export function getProfitAnalysisList(query) {
+ return request({
+ url: "/sales/profit/analysisList",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇鍒╂鼎瓒嬪娍鍥捐〃鏁版嵁
+export function getProfitTrendChart(query) {
+ return request({
+ url: "/sales/profit/trendChart",
+ method: "get",
+ params: query,
+ });
+}
+
+// 璁$畻姣涘埄鐜�
+export function calculateGrossProfit(data) {
+ return request({
+ url: "/sales/profit/calculate",
+ method: "post",
+ data: data,
+ });
+}
+
+// 瀵煎嚭鍒╂鼎鍒嗘瀽鎶ヨ〃
+export function exportProfitAnalysis(query) {
+ return request({
+ url: "/sales/profit/export",
+ method: "get",
+ params: query,
+ responseType: "blob",
+ });
+}
+
+// ========== 鍏叡鎺ュ彛 ==========
+
+// 鏌ヨ瀹㈡埛鍒楄〃锛堢敤浜庝笅鎷夐�夋嫨锛�
+export function getCustomerOptions() {
+ return request({
+ url: "/basic/customer/options",
+ method: "get",
+ });
+}
+
+// 鏌ヨ浜у搧鍒楄〃锛堢敤浜庝笅鎷夐�夋嫨锛�
+export function getProductOptions(query) {
+ return request({
+ url: "/basic/product/options",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏌ヨ閿�鍞尯鍩熷垪琛�
+export function getRegionOptions() {
+ return request({
+ url: "/basic/region/options",
+ method: "get",
+ });
+}
+
diff --git a/src/api/system/message.js b/src/api/system/message.js
new file mode 100644
index 0000000..dd81cc5
--- /dev/null
+++ b/src/api/system/message.js
@@ -0,0 +1,45 @@
+import request from "@/utils/request";
+
+// 鏌ヨ娑堟伅閫氱煡鍒楄〃
+export function listMessage(query) {
+ return request({
+ url: "/system/notice/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏌ヨ鏈娑堟伅鏁伴噺
+export function getUnreadCount(consigneeId) {
+ return request({
+ url: "/system/notice/getCount",
+ method: "get",
+ params: { consigneeId },
+ });
+}
+
+// 鏍囪娑堟伅涓哄凡璇�
+export function markAsRead(noticeId, status) {
+ return request({
+ url: "/system/notice",
+ method: "put",
+ data: { noticeId, status },
+ });
+}
+
+// 涓�閿爣璁版墍鏈夋秷鎭负宸茶
+export function markAllAsRead() {
+ return request({
+ url: "/system/notice/readAll",
+ method: "post",
+ });
+}
+
+// 纭娑堟伅
+export function confirmMessage(noticeId, status) {
+ return request({
+ url: "/system/notice",
+ method: "put",
+ data: { noticeId, status },
+ });
+}
diff --git a/src/api/system/post.js b/src/api/system/post.js
index 8faa266..fcb5bba 100644
--- a/src/api/system/post.js
+++ b/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({
diff --git a/src/api/viewIndex.js b/src/api/viewIndex.js
index 2d85171..9abd3cc 100644
--- a/src/api/viewIndex.js
+++ b/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'
+ })
}
\ No newline at end of file
diff --git a/src/assets/BI/backImage@2x.png b/src/assets/BI/backImage@2x.png
new file mode 100644
index 0000000..5c62ec6
--- /dev/null
+++ b/src/assets/BI/backImage@2x.png
Binary files differ
diff --git a/src/assets/BI/biaoti.png b/src/assets/BI/biaoti.png
new file mode 100644
index 0000000..3c5ccb9
--- /dev/null
+++ b/src/assets/BI/biaoti.png
Binary files differ
diff --git a/src/assets/BI/border@2x.png b/src/assets/BI/border@2x.png
new file mode 100644
index 0000000..07555cb
--- /dev/null
+++ b/src/assets/BI/border@2x.png
Binary files differ
diff --git a/src/assets/BI/caiwufenxiback@2x.png b/src/assets/BI/caiwufenxiback@2x.png
new file mode 100644
index 0000000..3f242d4
--- /dev/null
+++ b/src/assets/BI/caiwufenxiback@2x.png
Binary files differ
diff --git a/src/assets/BI/chuchangyijianicon@2x.png b/src/assets/BI/chuchangyijianicon@2x.png
new file mode 100644
index 0000000..bc3677a
--- /dev/null
+++ b/src/assets/BI/chuchangyijianicon@2x.png
Binary files differ
diff --git a/src/assets/BI/guochengyijianicon@2x.png b/src/assets/BI/guochengyijianicon@2x.png
new file mode 100644
index 0000000..0c64bdc
--- /dev/null
+++ b/src/assets/BI/guochengyijianicon@2x.png
Binary files differ
diff --git a/src/assets/BI/hetongicon.png b/src/assets/BI/hetongicon.png
new file mode 100644
index 0000000..60c2f0e
--- /dev/null
+++ b/src/assets/BI/hetongicon.png
Binary files differ
diff --git a/src/assets/BI/hetongjineback@2x.png b/src/assets/BI/hetongjineback@2x.png
new file mode 100644
index 0000000..c15ed6a
--- /dev/null
+++ b/src/assets/BI/hetongjineback@2x.png
Binary files differ
diff --git a/src/assets/BI/hetongjineicon1@2x.png b/src/assets/BI/hetongjineicon1@2x.png
new file mode 100644
index 0000000..38c86da
--- /dev/null
+++ b/src/assets/BI/hetongjineicon1@2x.png
Binary files differ
diff --git a/src/assets/BI/hetongjineicon@2x.png b/src/assets/BI/hetongjineicon@2x.png
new file mode 100644
index 0000000..60c2f0e
--- /dev/null
+++ b/src/assets/BI/hetongjineicon@2x.png
Binary files differ
diff --git a/src/assets/BI/hetongtitleback@2x.png b/src/assets/BI/hetongtitleback@2x.png
new file mode 100644
index 0000000..c15ed6a
--- /dev/null
+++ b/src/assets/BI/hetongtitleback@2x.png
Binary files differ
diff --git a/src/assets/BI/icon@2x.png b/src/assets/BI/icon@2x.png
new file mode 100644
index 0000000..267a738
--- /dev/null
+++ b/src/assets/BI/icon@2x.png
Binary files differ
diff --git a/src/assets/BI/jiantou@2x.png b/src/assets/BI/jiantou@2x.png
new file mode 100644
index 0000000..38c86da
--- /dev/null
+++ b/src/assets/BI/jiantou@2x.png
Binary files differ
diff --git a/src/assets/BI/kehuhetongback@2x.png b/src/assets/BI/kehuhetongback@2x.png
new file mode 100644
index 0000000..22a7ead
--- /dev/null
+++ b/src/assets/BI/kehuhetongback@2x.png
Binary files differ
diff --git a/src/assets/BI/pieback@2x.png b/src/assets/BI/pieback@2x.png
new file mode 100644
index 0000000..c8930cc
--- /dev/null
+++ b/src/assets/BI/pieback@2x.png
Binary files differ
diff --git a/src/assets/BI/shijianmingchengbeijing@2x.png b/src/assets/BI/shijianmingchengbeijing@2x.png
new file mode 100644
index 0000000..0ed2813
--- /dev/null
+++ b/src/assets/BI/shijianmingchengbeijing@2x.png
Binary files differ
diff --git a/src/assets/BI/shijianmingxiicon@2x.png b/src/assets/BI/shijianmingxiicon@2x.png
new file mode 100644
index 0000000..3c3a7b7
--- /dev/null
+++ b/src/assets/BI/shijianmingxiicon@2x.png
Binary files differ
diff --git a/src/assets/BI/shujutongji@2x.png b/src/assets/BI/shujutongji@2x.png
new file mode 100644
index 0000000..69b6834
--- /dev/null
+++ b/src/assets/BI/shujutongji@2x.png
Binary files differ
diff --git a/src/assets/BI/shujutongjiicon@2x.png b/src/assets/BI/shujutongjiicon@2x.png
new file mode 100644
index 0000000..780fc22
--- /dev/null
+++ b/src/assets/BI/shujutongjiicon@2x.png
Binary files differ
diff --git a/src/assets/BI/yuancailiaoyijianicon@2x.png b/src/assets/BI/yuancailiaoyijianicon@2x.png
new file mode 100644
index 0000000..dc4a72c
--- /dev/null
+++ b/src/assets/BI/yuancailiaoyijianicon@2x.png
Binary files differ
diff --git a/src/assets/BI/zonghetongbingtubiankuang@2x.png b/src/assets/BI/zonghetongbingtubiankuang@2x.png
new file mode 100644
index 0000000..a322aa5
--- /dev/null
+++ b/src/assets/BI/zonghetongbingtubiankuang@2x.png
Binary files differ
diff --git a/src/assets/icons/png/circleBlue@2x.png b/src/assets/icons/png/circleBlue@2x.png
new file mode 100644
index 0000000..55811dc
--- /dev/null
+++ b/src/assets/icons/png/circleBlue@2x.png
Binary files differ
diff --git a/src/assets/icons/png/circleGreen@2x.png b/src/assets/icons/png/circleGreen@2x.png
new file mode 100644
index 0000000..a1505a7
--- /dev/null
+++ b/src/assets/icons/png/circleGreen@2x.png
Binary files differ
diff --git a/src/assets/icons/png/circleOrange@2x.png b/src/assets/icons/png/circleOrange@2x.png
new file mode 100644
index 0000000..ba6807d
--- /dev/null
+++ b/src/assets/icons/png/circleOrange@2x.png
Binary files differ
diff --git a/src/assets/icons/png/circleRed@2x.png b/src/assets/icons/png/circleRed@2x.png
new file mode 100644
index 0000000..3d9a33c
--- /dev/null
+++ b/src/assets/icons/png/circleRed@2x.png
Binary files differ
diff --git a/src/assets/icons/png/circleYellow@2x.png b/src/assets/icons/png/circleYellow@2x.png
new file mode 100644
index 0000000..7b873a0
--- /dev/null
+++ b/src/assets/icons/png/circleYellow@2x.png
Binary files differ
diff --git a/src/assets/icons/png/walletBlue@2x.png b/src/assets/icons/png/walletBlue@2x.png
new file mode 100644
index 0000000..e0dc792
--- /dev/null
+++ b/src/assets/icons/png/walletBlue@2x.png
Binary files differ
diff --git a/src/assets/icons/png/walletGreen@2x.png b/src/assets/icons/png/walletGreen@2x.png
new file mode 100644
index 0000000..c68f20f
--- /dev/null
+++ b/src/assets/icons/png/walletGreen@2x.png
Binary files differ
diff --git a/src/assets/icons/png/walletOrange@2x.png b/src/assets/icons/png/walletOrange@2x.png
new file mode 100644
index 0000000..aacddc3
--- /dev/null
+++ b/src/assets/icons/png/walletOrange@2x.png
Binary files differ
diff --git a/src/assets/icons/png/walletRed@2x.png b/src/assets/icons/png/walletRed@2x.png
new file mode 100644
index 0000000..cd45c0b
--- /dev/null
+++ b/src/assets/icons/png/walletRed@2x.png
Binary files differ
diff --git a/src/assets/icons/png/walletYellow@2x.png b/src/assets/icons/png/walletYellow@2x.png
new file mode 100644
index 0000000..9d828cf
--- /dev/null
+++ b/src/assets/icons/png/walletYellow@2x.png
Binary files differ
diff --git a/src/assets/images/chartCard.svg b/src/assets/images/chartCard.svg
new file mode 100644
index 0000000..32d48b1
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/src/assets/images/chartCard2.svg b/src/assets/images/chartCard2.svg
new file mode 100644
index 0000000..ff67331
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/src/assets/images/chartCard3.svg b/src/assets/images/chartCard3.svg
new file mode 100644
index 0000000..0e8ce16
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/src/assets/images/video.png b/src/assets/images/video.png
new file mode 100644
index 0000000..7a90175
--- /dev/null
+++ b/src/assets/images/video.png
Binary files differ
diff --git a/src/assets/indexViews/LCLogo.png b/src/assets/indexViews/LCLogo.png
new file mode 100644
index 0000000..d18f9fd
--- /dev/null
+++ b/src/assets/indexViews/LCLogo.png
Binary files differ
diff --git a/src/assets/indexViews/login-background.png b/src/assets/indexViews/login-background.png
new file mode 100644
index 0000000..ace9d53
--- /dev/null
+++ b/src/assets/indexViews/login-background.png
Binary files differ
diff --git "a/src/assets/logo/\346\225\246\347\205\214\351\274\216\350\257\232.png" "b/src/assets/logo/\346\225\246\347\205\214\351\274\216\350\257\232.png"
new file mode 100644
index 0000000..139bdd1
--- /dev/null
+++ "b/src/assets/logo/\346\225\246\347\205\214\351\274\216\350\257\232.png"
Binary files differ
diff --git a/src/components/Dialog/FileListDialog.vue b/src/components/Dialog/FileListDialog.vue
new file mode 100644
index 0000000..0721a55
--- /dev/null
+++ b/src/components/Dialog/FileListDialog.vue
@@ -0,0 +1,309 @@
+<template>
+ <el-dialog
+ v-model="dialogVisible"
+ :title="title"
+ :width="width"
+ :before-close="handleClose"
+ >
+ <div class="file-list-toolbar" v-if="showToolbar">
+ <template v-if="useBuiltInUpload">
+ <el-upload
+ v-model:file-list="uploadFileList"
+ class="upload-demo"
+ :action="uploadAction"
+ :headers="uploadHeaders"
+ :show-file-list="false"
+ :on-success="handleDefaultUploadSuccess"
+ :on-error="handleDefaultUploadError"
+ >
+ <el-button
+ v-if="showUploadButton"
+ type="primary"
+ size="small"
+ >
+ 涓婁紶闄勪欢
+ </el-button>
+ </el-upload>
+ </template>
+ <template v-else>
+ <el-button
+ v-if="showUploadButton"
+ type="primary"
+ size="small"
+ @click="handleUpload"
+ >
+ 鏂板闄勪欢
+ </el-button>
+ </template>
+ </div>
+ <el-table :data="tableData" border :height="tableHeight">
+ <el-table-column
+ :label="nameColumnLabel"
+ :prop="nameColumnProp"
+ :min-width="nameColumnMinWidth"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ v-if="showActions"
+ fixed="right"
+ label="鎿嶄綔"
+ :width="actionColumnWidth"
+ align="center"
+ >
+ <template #default="scope">
+ <el-button
+ v-if="showDownload"
+ link
+ type="primary"
+ size="small"
+ @click="handleDownload(scope.row)"
+ >
+ 涓嬭浇
+ </el-button>
+ <el-button
+ v-if="showPreview"
+ link
+ type="primary"
+ size="small"
+ @click="handlePreview(scope.row)"
+ >
+ 棰勮
+ </el-button>
+ <el-button
+ v-if="showDeleteButton"
+ link
+ type="danger"
+ size="small"
+ @click="handleDelete(scope.row, scope.$index)"
+ >
+ 鍒犻櫎
+ </el-button>
+ <slot name="actions" :row="scope.row"></slot>
+ </template>
+ </el-table-column>
+ <slot name="columns"></slot>
+ </el-table>
+ </el-dialog>
+ <filePreview v-if="showPreview" ref="filePreviewRef" />
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance } from 'vue'
+import { ElMessage } from 'element-plus'
+import filePreview from '@/components/filePreview/index.vue'
+import { getToken } from '@/utils/auth'
+
+const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ title: {
+ type: String,
+ default: '闄勪欢'
+ },
+ width: {
+ type: String,
+ default: '40%'
+ },
+ tableHeight: {
+ type: String,
+ default: '40vh'
+ },
+ nameColumnLabel: {
+ type: String,
+ default: '闄勪欢鍚嶇О'
+ },
+ nameColumnProp: {
+ type: String,
+ default: 'name'
+ },
+ nameColumnMinWidth: {
+ type: [String, Number],
+ default: 400
+ },
+ actionColumnWidth: {
+ type: [String, Number],
+ default: 160
+ },
+ showActions: {
+ type: Boolean,
+ default: true
+ },
+ showDownload: {
+ type: Boolean,
+ default: true
+ },
+ showPreview: {
+ type: Boolean,
+ default: true
+ },
+ showUploadButton: {
+ type: Boolean,
+ default: false
+ },
+ showDeleteButton: {
+ type: Boolean,
+ default: false
+ },
+ urlField: {
+ type: String,
+ default: 'url'
+ },
+ downloadMethod: {
+ type: Function,
+ default: null
+ },
+ previewMethod: {
+ type: Function,
+ default: null
+ },
+ uploadMethod: {
+ type: Function,
+ default: null
+ },
+ deleteMethod: {
+ type: Function,
+ default: null
+ },
+ rulesRegulationsManagementId: {
+ type: [String, Number],
+ default: ''
+ },
+ uploadUrl: {
+ type: String,
+ default: `${import.meta.env.VITE_APP_BASE_API}/file/upload`
+ }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'download', 'preview', 'upload', 'delete'])
+
+const { proxy } = getCurrentInstance()
+const filePreviewRef = ref(null)
+const uploadFileList = ref([])
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit('update:modelValue', val)
+})
+
+const tableData = ref([])
+const showToolbar = computed(() => props.showUploadButton)
+const useBuiltInUpload = computed(() => !props.uploadMethod)
+const uploadAction = computed(() => props.uploadUrl)
+const uploadHeaders = computed(() => ({
+ Authorization: `Bearer ${getToken()}`
+}))
+
+const handleClose = () => {
+ emit('close')
+ dialogVisible.value = false
+}
+
+const handleDownload = (row) => {
+ if (props.downloadMethod) {
+ props.downloadMethod(row)
+ } else {
+ // 榛樿涓嬭浇鏂规硶
+ proxy.$download.name(row[props.urlField])
+ }
+ emit('download', row)
+}
+
+const handlePreview = (row) => {
+ if (props.previewMethod) {
+ props.previewMethod(row)
+ } else {
+ // 榛樿棰勮鏂规硶
+ if (filePreviewRef.value) {
+ filePreviewRef.value.open(row[props.urlField])
+ }
+ }
+ emit('preview', row)
+}
+
+const open = (list) => {
+ dialogVisible.value = true
+ tableData.value = list || []
+}
+
+const handleUpload = async () => {
+ if (props.uploadMethod) {
+ // 濡傛灉鎻愪緵浜嗚嚜瀹氫箟涓婁紶鏂规硶锛岀敱鐖剁粍浠惰礋璐f洿鏂板垪琛紙閫氳繃 setList锛�
+ // 杩欓噷涓嶅啀鑷姩娣诲姞锛岄伩鍏嶄笌鐖剁粍浠剁殑 setList 閲嶅
+ await props.uploadMethod()
+ }
+ emit('upload')
+}
+
+const handleDelete = async (row, index) => {
+ if (props.deleteMethod) {
+ const result = await props.deleteMethod(row, index)
+ if (result === false) {
+ return
+ }
+ // 濡傛灉鎻愪緵浜� deleteMethod锛岀敱鐖剁粍浠惰礋璐e埛鏂板垪琛紝涓嶅湪杩欓噷鍒犻櫎
+ } else {
+ // 濡傛灉娌℃湁鎻愪緵 deleteMethod锛屾墠鍦ㄧ粍浠跺唴閮ㄥ垹闄�
+ removeAttachment(index)
+ }
+ emit('delete', row)
+}
+
+const addAttachment = (item) => {
+ tableData.value = [...tableData.value, item]
+}
+
+const handleDefaultUploadSuccess = async (res, file) => {
+ if (res?.code !== 200) {
+ ElMessage.error(res?.msg || '鏂囦欢涓婁紶澶辫触')
+ return
+ }
+ if (!props.rulesRegulationsManagementId) {
+ ElMessage.error('缂哄皯瑙勭珷鍒跺害ID锛屾棤娉曚繚瀛橀檮浠�')
+ return
+ }
+ const fileName = res?.data?.originalName || file?.name
+ const fileUrl = res?.data?.tempPath || res?.data?.url
+ const payload = {
+ fileName,
+ fileUrl,
+ rulesRegulationsManagementId: props.rulesRegulationsManagementId,
+ raw: res?.data || {}
+ }
+ emit('upload', payload)
+}
+
+const handleDefaultUploadError = () => {
+ ElMessage.error('鏂囦欢涓婁紶澶辫触')
+}
+
+const removeAttachment = (index) => {
+ if (index > -1 && index < tableData.value.length) {
+ const newList = [...tableData.value]
+ newList.splice(index, 1)
+ tableData.value = newList
+ }
+}
+
+const setList = (list) => {
+ tableData.value = list || []
+}
+
+defineExpose({
+ open,
+ addAttachment,
+ removeAttachment,
+ setList,
+ handleUpload,
+ handleDelete
+})
+</script>
+
+<style scoped>
+.file-list-toolbar {
+ margin-bottom: 8px;
+ text-align: right;
+}
+</style>
+
diff --git a/src/components/Dialog/FormDialog.vue b/src/components/Dialog/FormDialog.vue
new file mode 100644
index 0000000..5e21b1d
--- /dev/null
+++ b/src/components/Dialog/FormDialog.vue
@@ -0,0 +1,73 @@
+<template>
+ <el-dialog
+ v-model="dialogVisible"
+ :title="computedTitle"
+ :width="width"
+ @close="handleClose"
+ >
+ <slot></slot>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleConfirm">纭</el-button>
+ <el-button @click="handleCancel">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ title: {
+ type: [String, Function],
+ default: ''
+ },
+ operationType: {
+ type: String,
+ default: ''
+ },
+ width: {
+ type: String,
+ default: '70%'
+ }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel'])
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit('update:modelValue', val)
+})
+
+const computedTitle = computed(() => {
+ if (typeof props.title === 'function') {
+ return props.title(props.operationType)
+ }
+ return props.title
+})
+
+const handleClose = () => {
+ emit('close')
+}
+
+const handleConfirm = () => {
+ emit('confirm')
+}
+
+const handleCancel = () => {
+ emit('cancel')
+ dialogVisible.value = false
+}
+</script>
+
+<style scoped>
+.dialog-footer {
+ text-align: center;
+}
+</style>
+
diff --git a/src/components/Dialog/ImportDialog.vue b/src/components/Dialog/ImportDialog.vue
new file mode 100644
index 0000000..5b126dc
--- /dev/null
+++ b/src/components/Dialog/ImportDialog.vue
@@ -0,0 +1,172 @@
+<template>
+ <el-dialog
+ :title="title"
+ v-model="dialogVisible"
+ :width="width"
+ :append-to-body="appendToBody"
+ @close="handleClose"
+ >
+ <el-upload
+ ref="uploadRef"
+ :limit="limit"
+ :accept="accept"
+ :headers="headers"
+ :action="action"
+ :disabled="disabled"
+ :before-upload="beforeUpload"
+ :on-progress="onProgress"
+ :on-success="onSuccess"
+ :on-error="onError"
+ :on-change="onChange"
+ :auto-upload="autoUpload"
+ drag
+ >
+ <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
+ <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <template #tip>
+ <div class="el-upload__tip text-center">
+ <span>{{ tipText }}</span>
+ <el-link
+ v-if="showDownloadTemplate"
+ type="primary"
+ :underline="false"
+ style="font-size: 12px; vertical-align: baseline; margin-left: 5px;"
+ @click="handleDownloadTemplate"
+ >涓嬭浇妯℃澘</el-link
+ >
+ </div>
+ </template>
+ </el-upload>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleConfirm">纭� 瀹�</el-button>
+ <el-button @click="handleCancel">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import { UploadFilled } from '@element-plus/icons-vue'
+
+const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ title: {
+ type: String,
+ default: '瀵煎叆'
+ },
+ width: {
+ type: String,
+ default: '400px'
+ },
+ appendToBody: {
+ type: Boolean,
+ default: true
+ },
+ limit: {
+ type: Number,
+ default: 1
+ },
+ accept: {
+ type: String,
+ default: '.xlsx, .xls'
+ },
+ headers: {
+ type: Object,
+ default: () => ({})
+ },
+ action: {
+ type: String,
+ required: true
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ autoUpload: {
+ type: Boolean,
+ default: false
+ },
+ tipText: {
+ type: String,
+ default: '浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�'
+ },
+ showDownloadTemplate: {
+ type: Boolean,
+ default: true
+ },
+ beforeUpload: {
+ type: Function,
+ default: null
+ },
+ onProgress: {
+ type: Function,
+ default: null
+ },
+ onSuccess: {
+ type: Function,
+ default: null
+ },
+ onError: {
+ type: Function,
+ default: null
+ },
+ onChange: {
+ type: Function,
+ default: null
+ }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel', 'download-template'])
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit('update:modelValue', val)
+})
+
+const uploadRef = ref(null)
+
+const handleClose = () => {
+ emit('close')
+}
+
+const handleConfirm = () => {
+ emit('confirm')
+}
+
+const submit = () => {
+ if (uploadRef.value) {
+ uploadRef.value.submit()
+ }
+}
+
+const handleCancel = () => {
+ emit('cancel')
+ dialogVisible.value = false
+}
+
+const handleDownloadTemplate = () => {
+ emit('download-template')
+}
+
+defineExpose({
+ uploadRef,
+ submit,
+ clearFiles: () => {
+ if (uploadRef.value) {
+ uploadRef.value.clearFiles()
+ }
+ }
+})
+</script>
+
+<style scoped>
+.dialog-footer {
+ text-align: center;
+}
+</style>
+
diff --git a/src/components/DynamicTable/index.vue b/src/components/DynamicTable/index.vue
new file mode 100644
index 0000000..9da9a3c
--- /dev/null
+++ b/src/components/DynamicTable/index.vue
@@ -0,0 +1,402 @@
+<template>
+ <div class="dynamic-table-container">
+ <el-table
+ ref="tableRef"
+ v-loading="loading"
+ :data="tableData"
+ :border="border"
+ :height="height"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ style="width: 100%"
+ @selection-change="handleSelectionChange"
+ @row-click="handleRowClick"
+ >
+ <!-- 閫夋嫨鍒� -->
+ <el-table-column
+ v-if="showSelection"
+ align="center"
+ type="selection"
+ width="55"
+ />
+
+ <!-- 搴忓彿鍒� -->
+ <el-table-column
+ v-if="showIndex"
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+
+ <!-- 鍥哄畾鍒楋細閮ㄩ棬 -->
+ <el-table-column
+ label="閮ㄩ棬"
+ prop="department"
+ width="120"
+ show-overflow-tooltip
+ align="center"
+ />
+
+ <!-- 鍥哄畾鍒楋細濮撳悕 -->
+ <el-table-column
+ label="濮撳悕"
+ prop="name"
+ width="100"
+ show-overflow-tooltip
+ align="center"
+ />
+
+ <!-- 鍥哄畾鍒楋細宸ュ彿 -->
+ <el-table-column
+ label="宸ュ彿"
+ prop="employeeId"
+ width="100"
+ show-overflow-tooltip
+ align="center"
+ />
+
+ <!-- 鍔ㄦ�佸垪锛氭牴鎹瓧鍏告覆鏌� -->
+ <el-table-column
+ v-for="(dictItem, index) in dynamicColumns"
+ :key="dictItem.value"
+ :label="dictItem.label"
+ :prop="dictItem.value"
+ :width="dictItem.width || 120"
+ show-overflow-tooltip
+ align="center"
+ >
+ <template #default="scope">
+ <!-- 鏍规嵁瀛楀吀绫诲瀷娓叉煋涓嶅悓鐨勬樉绀烘柟寮� -->
+ <template v-if="dictItem.renderType === 'tag'">
+ <el-tag
+ :type="getTagType(scope.row[dictItem.value])"
+ size="small"
+ >
+ {{ getDictValueLabel(dictItem.dictType, scope.row[dictItem.value]) }}
+ </el-tag>
+ </template>
+ <template v-else-if="dictItem.renderType === 'select'">
+ <el-select
+ v-model="scope.row[dictItem.value]"
+ placeholder="璇烽�夋嫨"
+ size="small"
+ @change="handleSelectChange(scope.row, dictItem.value, $event)"
+ >
+ <el-option
+ v-for="option in dictItem.options"
+ :key="option.value"
+ :label="option.label"
+ :value="option.value"
+ />
+ </el-select>
+ </template>
+ <template v-else-if="dictItem.renderType === 'input'">
+ <el-input
+ v-model="scope.row[dictItem.value]"
+ size="small"
+ placeholder="璇疯緭鍏�"
+ @blur="handleInputChange(scope.row, dictItem.value, $event)"
+ />
+ </template>
+ <template v-else>
+ <span>{{ getDictValueLabel(dictItem.dictType, scope.row[dictItem.value]) }}</span>
+ </template>
+ </template>
+ </el-table-column>
+
+ <!-- 鎿嶄綔鍒� -->
+ <el-table-column
+ v-if="showActions"
+ label="鎿嶄綔"
+ width="150"
+ align="center"
+ fixed="right"
+ >
+ <template #default="scope">
+ <el-button
+ type="primary"
+ link
+ size="small"
+ @click="handleEdit(scope.row, scope.$index)"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ type="danger"
+ link
+ size="small"
+ @click="handleDelete(scope.row, scope.$index)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉缁勪欢 -->
+ <div v-if="showPagination" class="pagination-container">
+ <el-pagination
+ v-model:current-page="pagination.current"
+ v-model:page-size="pagination.size"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, watch } from 'vue'
+import { useDict } from '@/utils/dict'
+
+// 瀹氫箟缁勪欢灞炴��
+const props = defineProps({
+ // 琛ㄦ牸鏁版嵁
+ data: {
+ type: Array,
+ default: () => []
+ },
+ // 瀛楀吀绫诲瀷鏁扮粍锛岀敤浜庡姩鎬佺敓鎴愬垪
+ dictTypes: {
+ type: Array,
+ default: () => []
+ },
+ // 鏄惁鏄剧ず閫夋嫨鍒�
+ showSelection: {
+ type: Boolean,
+ default: false
+ },
+ // 鏄惁鏄剧ず搴忓彿鍒�
+ showIndex: {
+ type: Boolean,
+ default: true
+ },
+ // 鏄惁鏄剧ず鎿嶄綔鍒�
+ showActions: {
+ type: Boolean,
+ default: false
+ },
+ // 鏄惁鏄剧ず鍒嗛〉
+ showPagination: {
+ type: Boolean,
+ default: false
+ },
+ // 琛ㄦ牸楂樺害
+ height: {
+ type: [String, Number],
+ default: 'auto'
+ },
+ // 鏄惁鏄剧ず杈规
+ border: {
+ type: Boolean,
+ default: true
+ },
+ // 鍔犺浇鐘舵��
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ // 鍒嗛〉閰嶇疆
+ pagination: {
+ type: Object,
+ default: () => ({
+ current: 1,
+ size: 10,
+ total: 0
+ })
+ }
+})
+
+// 瀹氫箟浜嬩欢
+const emit = defineEmits([
+ 'selection-change',
+ 'row-click',
+ 'edit',
+ 'delete',
+ 'select-change',
+ 'input-change',
+ 'size-change',
+ 'current-change'
+])
+
+// 鍝嶅簲寮忔暟鎹�
+const tableRef = ref(null)
+const tableData = ref([])
+
+// 鑾峰彇瀛楀吀鏁版嵁
+const dictData = ref({})
+
+// 鍔ㄦ�佸垪閰嶇疆
+const dynamicColumns = computed(() => {
+ const columns = []
+
+ props.dictTypes.forEach(dictType => {
+ const dictItems = dictData.value[dictType] || []
+ // 涓烘瘡涓瓧鍏哥被鍨嬪垱寤轰竴涓垪锛岃�屼笉鏄负姣忎釜瀛楀吀椤瑰垱寤哄垪
+ if (dictItems.length > 0) {
+ columns.push({
+ label: getDictLabel(dictType), // 鑾峰彇瀛楀吀绫诲瀷鐨勬樉绀哄悕绉�
+ value: dictType, // 浣跨敤瀛楀吀绫诲瀷浣滀负瀛楁鍚�
+ width: 120,
+ renderType: 'tag', // 榛樿浣跨敤鏍囩鏄剧ず
+ options: dictItems, // 鎻愪緵閫夐」
+ dictType: dictType
+ })
+ }
+ })
+
+ return columns
+})
+
+// 鑾峰彇瀛楀吀绫诲瀷鐨勬樉绀哄悕绉�
+const getDictLabel = (dictType) => {
+ const labelMap = {
+ 'sys_normal_disable': '鐘舵��',
+ 'sys_user_level': '绾у埆',
+ 'sys_user_position': '鑱屼綅',
+ 'sys_yes_no': '鏄惁',
+ 'sys_user_sex': '鎬у埆',
+ 'sys_lavor_issue': '鍔冲姟闂' // 娣诲姞鍔冲姟闂瀛楀吀
+ }
+ return labelMap[dictType] || dictType
+}
+
+// 鑾峰彇瀛楀吀鏁版嵁
+const loadDictData = async () => {
+ try {
+ const dictPromises = props.dictTypes.map(async (dictType) => {
+ const { getDicts } = await import('@/api/system/dict/data')
+ const response = await getDicts(dictType)
+ return {
+ type: dictType,
+ data: response.data.map(item => ({
+ label: item.dictLabel,
+ value: item.dictValue,
+ elTagType: item.listClass,
+ elTagClass: item.cssClass
+ }))
+ }
+ })
+
+ const results = await Promise.all(dictPromises)
+ results.forEach(result => {
+ dictData.value[result.type] = result.data
+ })
+ } catch (error) {
+ console.error('鍔犺浇瀛楀吀鏁版嵁澶辫触:', error)
+ // 濡傛灉瀛楀吀鍔犺浇澶辫触锛屼娇鐢ㄩ粯璁ゆ暟鎹�
+ props.dictTypes.forEach(dictType => {
+ if (!dictData.value[dictType]) {
+ dictData.value[dictType] = []
+ }
+ })
+ }
+}
+
+// 鑾峰彇鏍囩绫诲瀷
+const getTagType = (value) => {
+ // 鏍规嵁鍊艰繑鍥炰笉鍚岀殑鏍囩绫诲瀷
+ if (value === '1' || value === 'true' || value === '鏄�') return 'success'
+ if (value === '0' || value === 'false' || value === '鍚�') return 'danger'
+ if (value === '2' || value === 'warning') return 'warning'
+ return 'info'
+}
+
+// 鑾峰彇瀛楀吀鍊肩殑鏍囩
+const getDictValueLabel = (dictType, value) => {
+ if (!value) return '-'
+ const dictItems = dictData.value[dictType] || []
+ const item = dictItems.find(item => item.value === value)
+ return item ? item.label : value
+}
+
+// 浜嬩欢澶勭悊鍑芥暟
+const handleSelectionChange = (selection) => {
+ emit('selection-change', selection)
+}
+
+const handleRowClick = (row, column, event) => {
+ emit('row-click', row, column, event)
+}
+
+const handleEdit = (row, index) => {
+ emit('edit', row, index)
+}
+
+const handleDelete = (row, index) => {
+ emit('delete', row, index)
+}
+
+const handleSelectChange = (row, prop, value) => {
+ emit('select-change', row, prop, value)
+}
+
+const handleInputChange = (row, prop, event) => {
+ emit('input-change', row, prop, event.target.value)
+}
+
+const handleSizeChange = (size) => {
+ emit('size-change', size)
+}
+
+const handleCurrentChange = (current) => {
+ emit('current-change', current)
+}
+
+// 鐩戝惉鏁版嵁鍙樺寲
+watch(() => props.data, (newData) => {
+ tableData.value = newData
+}, { immediate: true })
+
+// 鐩戝惉瀛楀吀绫诲瀷鍙樺寲
+watch(() => props.dictTypes, () => {
+ loadDictData()
+}, { immediate: true })
+
+// 缁勪欢鎸傝浇鏃跺姞杞藉瓧鍏告暟鎹�
+onMounted(() => {
+ loadDictData()
+})
+
+// 鏆撮湶鏂规硶缁欑埗缁勪欢
+defineExpose({
+ tableRef,
+ getSelection: () => tableRef.value?.getSelectionRows() || [],
+ clearSelection: () => tableRef.value?.clearSelection(),
+ toggleRowSelection: (row, selected) => tableRef.value?.toggleRowSelection(row, selected),
+ setCurrentRow: (row) => tableRef.value?.setCurrentRow(row)
+})
+</script>
+
+<style scoped>
+.dynamic-table-container {
+ width: 100%;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+ background-color: #F0F1F5 !important;
+ color: #333333;
+ font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+ padding: 8px 0;
+}
+
+:deep(.el-select) {
+ width: 100%;
+}
+
+:deep(.el-input) {
+ width: 100%;
+}
+</style>
diff --git a/src/components/Echarts/echarts.vue b/src/components/Echarts/echarts.vue
index d8264ad..a386bb7 100644
--- a/src/components/Echarts/echarts.vue
+++ b/src/components/Echarts/echarts.vue
@@ -76,6 +76,10 @@
type: Array,
default: () => []
},
+ visualMap: {
+ type: Object,
+ default: () => ({})
+ },
option: {
type: Object,
default: () => ({})
@@ -113,6 +117,7 @@
const option = {
color: props.color.length ? props.color : undefined,
backgroundColor: props.options.backgroundColor || '#fff',
+ textStyle: props.options.textStyle || { color: '#333' },
xAxis: props.xAxis,
yAxis: props.yAxis,
dataset: props.dataset,
@@ -120,6 +125,7 @@
grid: props.grid,
legend: props.legend,
tooltip: props.tooltip,
+ visualMap: Object.keys(props.visualMap).length ? props.visualMap : undefined,
}
chartInstance.clear()
@@ -148,7 +154,7 @@
// Watch all reactive props that affect the chart
watch(
- () => [props.xAxis, props.series, props.legend, props.tooltip],
+ () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap],
() => {
if (chartInstance) {
renderChart()
diff --git a/src/components/PIMTable/PIMTable.vue b/src/components/PIMTable/PIMTable.vue
index a89aa96..52becd6 100644
--- a/src/components/PIMTable/PIMTable.vue
+++ b/src/components/PIMTable/PIMTable.vue
@@ -10,7 +10,7 @@
:row-class-name="rowClassName"
:row-style="rowStyle"
:row-key="rowKey"
- style="width: 100%"
+ :style="tableStyle"
tooltip-effect="dark"
:expand-row-keys="expandRowKeys"
:show-summary="isShowSummary"
@@ -41,12 +41,22 @@
:fixed="item.fixed"
:label="item.label"
:prop="item.prop"
- show-overflow-tooltip
+ :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
:align="item.align"
:sortable="!!item.sortable"
:type="item.type"
:width="item.width"
>
+ <template #header="scope">
+ <div class="pim-table-header-cell">
+ <div class="pim-table-header-title">
+ {{ item.label }}
+ </div>
+ <div v-if="item.headerSlot" class="pim-table-header-extra">
+ <slot :name="item.headerSlot" :column="scope.column" />
+ </div>
+ </div>
+ </template>
<template
v-if="item.hasOwnProperty('colunmTemplate')"
#[item.colunmTemplate]="scope"
@@ -121,11 +131,10 @@
</div>
<!-- 鎸夐挳 -->
- <div v-else-if="item.dataType == 'action'">
+ <div v-else-if="item.dataType == 'action'" @click.stop>
<template v-for="(o, key) in item.operation" :key="key">
<el-button
v-show="o.type != 'upload'"
- size="small"
v-if="o.showHide ? o.showHide(scope.row) : true"
:disabled="o.disabled ? o.disabled(scope.row) : false"
:plain="o.plain"
@@ -137,7 +146,7 @@
: o.color,
}"
link
- @click="o.clickFun(scope.row)"
+ @click.stop="o.clickFun(scope.row)"
:key="key"
>
{{ o.name }}
@@ -150,7 +159,6 @@
(o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
"
ref="uploadRef"
- size="small"
:multiple="o.multiple ? o.multiple : false"
:limit="1"
:disabled="o.disabled ? o.disabled(scope.row) : false"
@@ -179,7 +187,6 @@
:show-file-list="false"
>
<el-button
- :size="o.size ? o.size : 'small'"
link
type="primary"
:disabled="o.disabled ? o.disabled(scope.row) : false"
@@ -208,7 +215,7 @@
</el-table-column>
</el-table>
<pagination
- v-if="page.total > 0"
+ v-if="isShowPagination"
:total="page.total"
:layout="page.layout"
:page="page.current"
@@ -272,6 +279,10 @@
type: Boolean,
default: false,
},
+ isShowPagination: {
+ type: Boolean,
+ default: true,
+ },
isShowSummary: {
type: Boolean,
default: false,
@@ -316,6 +327,10 @@
total: {
type: Number,
default: 0,
+ },
+ tableStyle: {
+ type: [String, Object],
+ default: () => ({ width: "100%" }),
},
});
@@ -430,4 +445,9 @@
padding-right: 0 !important;
padding-left: 0 !important;
}
+
+.pim-table-header-extra :deep(.el-input),
+.pim-table-header-extra :deep(.el-select) {
+ width: 100%;
+}
</style>
diff --git a/src/components/PageHeader/index.vue b/src/components/PageHeader/index.vue
new file mode 100644
index 0000000..d8fc6fa
--- /dev/null
+++ b/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>
diff --git a/src/components/QRCodeGenerator/index.vue b/src/components/QRCodeGenerator/index.vue
new file mode 100644
index 0000000..fd44f44
--- /dev/null
+++ b/src/components/QRCodeGenerator/index.vue
@@ -0,0 +1,566 @@
+<template>
+ <div class="qr-code-generator">
+ <!-- 浜岀淮鐮佺敓鎴愯〃鍗� -->
+ <el-form :model="form"
+ :rules="rules"
+ ref="formRef"
+ label-width="120px"
+ class="qr-form">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏍囪瘑绫诲瀷"
+ prop="type">
+ <el-select v-model="form.type"
+ placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷"
+ style="width: 100%">
+ <el-option label="浜岀淮鐮�"
+ value="qrcode"></el-option>
+ <el-option label="闃蹭吉鐮�"
+ value="security"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍐呭"
+ prop="content">
+ <el-input v-model="form.content"
+ placeholder="璇疯緭鍏ヨ缂栫爜鐨勫唴瀹�"
+ :type="form.type === 'security' ? 'textarea' : 'text'"
+ :rows="form.type === 'security' ? 3 : 1"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="灏哄"
+ prop="size">
+ <el-input-number v-model="form.size"
+ :min="100"
+ :max="500"
+ :step="50"
+ style="width: 100%"></el-input-number>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="杈硅窛"
+ prop="margin">
+ <el-input-number v-model="form.margin"
+ :min="0"
+ :max="10"
+ :step="1"
+ style="width: 100%"></el-input-number>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍓嶆櫙鑹�"
+ prop="foregroundColor">
+ <el-color-picker v-model="form.foregroundColor"
+ style="width: 100%"></el-color-picker>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑳屾櫙鑹�"
+ prop="backgroundColor">
+ <el-color-picker v-model="form.backgroundColor"
+ style="width: 100%"></el-color-picker>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item>
+ <el-button type="primary"
+ @click="generateCode"
+ :loading="generating">
+ 鐢熸垚{{ form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�' }}
+ </el-button>
+ <el-button @click="resetForm">閲嶇疆</el-button>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <!-- 鐢熸垚鐨勭爜鏄剧ず鍖哄煙 -->
+ <div v-if="generatedCodeUrl"
+ class="code-display">
+ <el-divider content-position="center">
+ {{ form.type === 'qrcode' ? '鐢熸垚鐨勪簩缁寸爜' : '鐢熸垚鐨勯槻浼爜' }}
+ </el-divider>
+ <div class="code-container">
+ <div class="code-image">
+ <img :src="generatedCodeUrl"
+ :alt="form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'" />
+ </div>
+ <div class="code-info">
+ <p><strong>鍐呭锛�</strong>{{ form.content }}</p>
+ <p><strong>绫诲瀷锛�</strong>{{ form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�' }}</p>
+ <p><strong>灏哄锛�</strong>{{ form.size }}x{{ form.size }}px</p>
+ <p><strong>鐢熸垚鏃堕棿锛�</strong>{{ generateTime }}</p>
+ </div>
+ </div>
+ <div class="code-actions">
+ <el-button type="success"
+ @click="downloadCode"
+ icon="Download">
+ 涓嬭浇鍥剧墖
+ </el-button>
+ <el-button type="primary"
+ @click="copyToClipboard"
+ icon="CopyDocument">
+ 澶嶅埗鍐呭
+ </el-button>
+ <el-button @click="printCode"
+ icon="Printer">
+ 鎵撳嵃
+ </el-button>
+ </div>
+ </div>
+ <!-- 鎵归噺鐢熸垚瀵硅瘽妗� -->
+ <el-dialog v-model="batchDialogVisible"
+ title="鎵归噺鐢熸垚"
+ width="600px">
+ <el-form :model="batchForm"
+ label-width="120px">
+ <el-form-item label="鐢熸垚鏁伴噺">
+ <el-input-number v-model="batchForm.quantity"
+ :min="1"
+ :max="100"
+ style="width: 100%"></el-input-number>
+ </el-form-item>
+ <el-form-item label="鍓嶇紑">
+ <el-input v-model="batchForm.prefix"
+ placeholder="璇疯緭鍏ュ墠缂�锛屽锛歅ROD_"></el-input>
+ </el-form-item>
+ <el-form-item label="璧峰缂栧彿">
+ <el-input-number v-model="batchForm.startNumber"
+ :min="1"
+ style="width: 100%"></el-input-number>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="generateBatchCodes">寮�濮嬬敓鎴�</el-button>
+ <el-button @click="batchDialogVisible = false">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 鎵归噺鐢熸垚缁撴灉 -->
+ <div v-if="batchCodes.length > 0"
+ class="batch-results">
+ <el-divider content-position="center">鎵归噺鐢熸垚缁撴灉</el-divider>
+ <div class="batch-grid">
+ <div v-for="(code, index) in batchCodes"
+ :key="index"
+ class="batch-item">
+ <img :src="code.url"
+ :alt="code.content" />
+ <p class="batch-content">{{ code.content }}</p>
+ <el-button size="small"
+ @click="downloadSingleCode(code)">涓嬭浇</el-button>
+ </div>
+ </div>
+ <div class="batch-actions">
+ <el-button type="success"
+ @click="downloadAllCodes">涓嬭浇鍏ㄩ儴</el-button>
+ <el-button @click="clearBatchCodes">娓呯┖缁撴灉</el-button>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+ import { ref, reactive, computed, onMounted } from "vue";
+ import QRCode from "qrcode";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import { Download, CopyDocument, Printer } from "@element-plus/icons-vue";
+
+ // 瀹氫箟缁勪欢鍚嶇О
+ defineOptions({
+ name: "QRCodeGenerator",
+ });
+
+ // 琛ㄥ崟鏁版嵁
+ const form = reactive({
+ type: "qrcode",
+ content: "",
+ size: 200,
+ margin: 2,
+ foregroundColor: "#000000",
+ backgroundColor: "#FFFFFF",
+ });
+
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ const rules = {
+ type: [{ required: true, message: "璇烽�夋嫨鏍囪瘑绫诲瀷", trigger: "change" }],
+ content: [{ required: true, message: "璇疯緭鍏ュ唴瀹�", trigger: "blur" }],
+ };
+
+ // 鍝嶅簲寮忔暟鎹�
+ const formRef = ref();
+ const generating = ref(false);
+ const generatedCodeUrl = ref("");
+ const generateTime = ref("");
+ const batchDialogVisible = ref(false);
+ const batchForm = reactive({
+ quantity: 10,
+ prefix: "",
+ startNumber: 1,
+ });
+ const batchCodes = ref([]);
+
+ // 鐢熸垚浜岀淮鐮佹垨闃蹭吉鐮�
+ const generateCode = async () => {
+ try {
+ await formRef.value.validate();
+
+ if (!form.content.trim()) {
+ ElMessage.warning("璇疯緭鍏ヨ缂栫爜鐨勫唴瀹�");
+ return;
+ }
+
+ generating.value = true;
+
+ if (form.type === "qrcode") {
+ // 鐢熸垚浜岀淮鐮�
+ generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
+ width: form.size,
+ margin: form.margin,
+ color: {
+ dark: form.foregroundColor,
+ light: form.backgroundColor,
+ },
+ errorCorrectionLevel: "M",
+ });
+ } else {
+ // 鐢熸垚闃蹭吉鐮侊紙浣跨敤浜岀淮鐮佹妧鏈紝浣嗗唴瀹规牸寮忎笉鍚岋級
+ const securityContent = generateSecurityCode(form.content);
+ generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
+ width: form.size,
+ margin: form.margin,
+ color: {
+ dark: form.foregroundColor,
+ light: form.backgroundColor,
+ },
+ errorCorrectionLevel: "H", // 闃蹭吉鐮佷娇鐢ㄦ渶楂樼籂閿欑骇鍒�
+ });
+ }
+
+ generateTime.value = new Date().toLocaleString();
+ ElMessage.success("鐢熸垚鎴愬姛锛�");
+ } catch (error) {
+ console.error("鐢熸垚澶辫触:", error);
+ ElMessage.error("鐢熸垚澶辫触锛�" + error.message);
+ } finally {
+ generating.value = false;
+ }
+ };
+
+ // 鐢熸垚闃蹭吉鐮佸唴瀹�
+ const generateSecurityCode = content => {
+ const timestamp = Date.now();
+ const random = Math.random().toString(36).substr(2, 8);
+ return `SEC_${content}_${timestamp}_${random}`;
+ };
+
+ // 涓嬭浇鐢熸垚鐨勭爜
+ const downloadCode = () => {
+ if (!generatedCodeUrl.value) {
+ ElMessage.warning("璇峰厛鐢熸垚鐮�");
+ return;
+ }
+
+ const a = document.createElement("a");
+ a.href = generatedCodeUrl.value;
+ a.download = `${
+ form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"
+ }_${new Date().getTime()}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ ElMessage.success("涓嬭浇鎴愬姛锛�");
+ };
+
+ // 澶嶅埗鍐呭鍒板壀璐存澘
+ const copyToClipboard = async () => {
+ try {
+ await navigator.clipboard.writeText(form.content);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ } catch (error) {
+ // 闄嶇骇鏂规
+ const textArea = document.createElement("textarea");
+ textArea.value = form.content;
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textArea);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ }
+ };
+
+ // 鎵撳嵃鐮�
+ const printCode = () => {
+ if (!generatedCodeUrl.value) {
+ ElMessage.warning("璇峰厛鐢熸垚鐮�");
+ return;
+ }
+
+ const printWindow = window.open("", "_blank");
+ printWindow.document.write(`
+ <html>
+ <head>
+ <title>鎵撳嵃${form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"}</title>
+ <style>
+ body { text-align: center; padding: 20px; }
+ img { max-width: 100%; height: auto; }
+ .info { margin: 20px 0; }
+ </style>
+ </head>
+ <body>
+ <h2>${form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"}</h2>
+ <img src="${generatedCodeUrl.value}" alt="${
+ form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"
+ }" />
+ <div class="info">
+ <p><strong>鍐呭锛�</strong>${form.content}</p>
+ <p><strong>鐢熸垚鏃堕棿锛�</strong>${generateTime.value}</p>
+ </div>
+ </body>
+ </html>
+ `);
+ printWindow.document.close();
+ printWindow.print();
+ };
+
+ // 閲嶇疆琛ㄥ崟
+ const resetForm = () => {
+ formRef.value.resetFields();
+ generatedCodeUrl.value = "";
+ generateTime.value = "";
+ batchCodes.value = [];
+ };
+
+ // 鎵归噺鐢熸垚
+ const generateBatchCodes = async () => {
+ if (!batchForm.prefix.trim()) {
+ ElMessage.warning("璇疯緭鍏ュ墠缂�");
+ return;
+ }
+
+ batchCodes.value = [];
+ generating.value = true;
+
+ try {
+ for (let i = 0; i < batchForm.quantity; i++) {
+ const number = batchForm.startNumber + i;
+ const content = `${batchForm.prefix}${number
+ .toString()
+ .padStart(6, "0")}`;
+
+ let codeUrl;
+ if (form.type === "qrcode") {
+ codeUrl = await QRCode.toDataURL(content, {
+ width: form.size,
+ margin: form.margin,
+ color: {
+ dark: form.foregroundColor,
+ light: form.backgroundColor,
+ },
+ });
+ } else {
+ const securityContent = generateSecurityCode(content);
+ codeUrl = await QRCode.toDataURL(securityContent, {
+ width: form.size,
+ margin: form.margin,
+ color: {
+ dark: form.foregroundColor,
+ light: form.backgroundColor,
+ },
+ });
+ }
+
+ batchCodes.value.push({
+ content,
+ url: codeUrl,
+ });
+ }
+
+ ElMessage.success(`鎵归噺鐢熸垚瀹屾垚锛屽叡鐢熸垚 ${batchForm.quantity} 涓爜`);
+ batchDialogVisible.value = false;
+ } catch (error) {
+ console.error("鎵归噺鐢熸垚澶辫触:", error);
+ ElMessage.error("鎵归噺鐢熸垚澶辫触锛�" + error.message);
+ } finally {
+ generating.value = false;
+ }
+ };
+
+ // 涓嬭浇鍗曚釜鎵归噺鐢熸垚鐨勭爜
+ const downloadSingleCode = code => {
+ const a = document.createElement("a");
+ a.href = code.url;
+ a.download = `${code.content}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ };
+
+ // 涓嬭浇鎵�鏈夋壒閲忕敓鎴愮殑鐮�
+ const downloadAllCodes = async () => {
+ if (batchCodes.value.length === 0) {
+ ElMessage.warning("娌℃湁鍙笅杞界殑鐮�");
+ return;
+ }
+
+ try {
+ // 浣跨敤JSZip鎵撳寘涓嬭浇
+ const JSZip = await import("jszip");
+ const zip = new JSZip.default();
+
+ batchCodes.value.forEach((code, index) => {
+ // 灏哹ase64杞崲涓篵lob
+ const base64Data = code.url.split(",")[1];
+ const byteCharacters = atob(base64Data);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+
+ zip.file(`${code.content}.png`, byteArray);
+ });
+
+ const content = await zip.generateAsync({ type: "blob" });
+ const a = document.createElement("a");
+ a.href = URL.createObjectURL(content);
+ a.download = `鎵归噺${
+ form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"
+ }_${new Date().getTime()}.zip`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(a.href);
+
+ ElMessage.success("鎵归噺涓嬭浇瀹屾垚锛�");
+ } catch (error) {
+ console.error("鎵归噺涓嬭浇澶辫触:", error);
+ ElMessage.error("鎵归噺涓嬭浇澶辫触锛岃閫愪釜涓嬭浇");
+ }
+ };
+
+ // 娓呯┖鎵归噺鐢熸垚缁撴灉
+ const clearBatchCodes = () => {
+ batchCodes.value = [];
+ };
+
+ // 鏆撮湶鏂规硶缁欑埗缁勪欢
+ defineExpose({
+ generateCode,
+ downloadCode,
+ resetForm,
+ form,
+ });
+</script>
+
+<style scoped>
+ .qr-code-generator {
+ padding: 20px;
+ }
+
+ .qr-form {
+ background: #f8f9fa;
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ }
+
+ .code-display {
+ margin-top: 30px;
+ }
+
+ .code-container {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ gap: 40px;
+ margin: 20px 0;
+ }
+
+ .code-image img {
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ .code-info {
+ text-align: left;
+ min-width: 200px;
+ }
+
+ .code-info p {
+ margin: 8px 0;
+ color: #666;
+ }
+
+ .code-actions {
+ text-align: center;
+ margin: 20px 0;
+ }
+
+ .code-actions .el-button {
+ margin: 0 10px;
+ }
+
+ .batch-results {
+ margin-top: 30px;
+ }
+
+ .batch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: 20px;
+ margin: 20px 0;
+ }
+
+ .batch-item {
+ text-align: center;
+ padding: 15px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #fff;
+ }
+
+ .batch-item img {
+ width: 100px;
+ height: 100px;
+ margin-bottom: 10px;
+ }
+
+ .batch-content {
+ font-size: 12px;
+ color: #666;
+ margin: 10px 0;
+ word-break: break-all;
+ }
+
+ .batch-actions {
+ text-align: center;
+ margin: 20px 0;
+ }
+
+ .batch-actions .el-button {
+ margin: 0 10px;
+ }
+
+ @media (max-width: 768px) {
+ .code-container {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .batch-grid {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ }
+ }
+</style>
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
index a87ef7d..1e7a78b 100644
--- a/src/layout/components/AppMain.vue
+++ b/src/layout/components/AppMain.vue
@@ -2,9 +2,12 @@
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="fade-transform" mode="out-in">
- <keep-alive :include="tagsViewStore.cachedViews">
- <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
- </keep-alive>
+ <div v-if="!route.meta.link" class="route-view-wrapper">
+ <keep-alive :include="tagsViewStore.cachedViews">
+ <component :is="Component" :key="route.path"/>
+ </keep-alive>
+ </div>
+ <div v-else class="route-view-wrapper"></div>
</transition>
</router-view>
<iframe-toggle />
@@ -43,6 +46,11 @@
background: #F5F7FB;
}
+.route-view-wrapper {
+ width: 100%;
+ height: 100%;
+}
+
.fixed-header + .app-main {
padding-top: 50px;
}
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
index 1f0d385..d4e938d 100644
--- a/src/layout/components/Navbar.vue
+++ b/src/layout/components/Navbar.vue
@@ -6,24 +6,30 @@
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
</div>
<!-- <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
- <div class="center-menu">
- <span class="label">{{ userStore.currentFactoryName }}</span>
- <el-dropdown @command="handleFactoryChange" class="right-menu-item hover-effect" trigger="click">
- <div>
- <el-icon size="20">
- <Switch />
- </el-icon>
- </div>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item v-for="item in factoryList" :key="item.deptId" :command="item">
- {{ item.deptName }}
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </div>
<div class="right-menu">
+ <!-- 娑堟伅閫氱煡 -->
+ <el-popover
+ v-model:visible="notificationVisible"
+ :width="500"
+ placement="bottom-end"
+ trigger="click"
+ :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [0, 10] } }] }"
+ popper-class="notification-popover"
+ >
+ <template #reference>
+ <div class="notification-container right-menu-item hover-effect">
+ <el-badge :value="unreadCount" :hidden="unreadCount === 0" class="notification-badge">
+ <el-icon :size="20" style="cursor: pointer;">
+ <Bell />
+ </el-icon>
+ </el-badge>
+ </div>
+ </template>
+ <NotificationCenter
+ @unreadCountChange="handleUnreadCountChange"
+ ref="notificationCenterRef"
+ />
+ </el-popover>
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
@@ -51,6 +57,7 @@
<script setup>
import { ElMessageBox } from 'element-plus'
+import { Bell } from '@element-plus/icons-vue'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
@@ -59,17 +66,17 @@
import HeaderSearch from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
+import NotificationCenter from './NotificationCenter/index.vue'
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
-import { userLoginFacotryList } from "@/api/system/user.js"
-import Cookies from "js-cookie";
-import { decrypt } from "@/utils/jsencrypt"
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
-const factoryList = ref([])
+const notificationVisible = ref(false)
+const notificationCenterRef = ref(null)
+const unreadCount = ref(0)
function toggleSideBar() {
appStore.toggleSideBar()
}
@@ -112,41 +119,42 @@
settingsStore.toggleTheme()
}
-function getUserLoginFacotryList() {
- if (userStore.id) {
- userLoginFacotryList({ userId: userStore.id }).then(res => {
- console.log('res', res)
- factoryList.value = res.data
- })
- } else {
- factoryList.value = []
- }
+// 娑堟伅閫氱煡鐩稿叧
+function handleUnreadCountChange(count) {
+ unreadCount.value = count
}
-function handleFactoryChange(command) {
- console.log('command', command)
- handleLogin(command.deptId);
-}
-
-function handleLogin(currentFatoryId) {
- const loginForm = {
- username: Cookies.get("username"),
- password: Cookies.get("password") === undefined ? null : decrypt(Cookies.get("password")),
- currentFatoryId: currentFatoryId
- }
- userStore.loginCheckFactory(loginForm).then(res => {
- forceReload();
- }).catch((err) => {
- console.log(err)
+// 缁勪欢鎸傝浇鏃跺姞杞芥湭璇绘暟閲忓拰瀹氭椂鍒锋柊
+let unreadCountTimer = null
+onMounted(() => {
+ // 寤惰繜鍔犺浇锛岀‘淇濈粍浠跺凡娓叉煋
+ nextTick(() => {
+ if (notificationCenterRef.value) {
+ notificationCenterRef.value.loadUnreadCount()
+ }
})
-}
-function forceReload() {
- const currentUrl = window.location.origin + window.location.pathname;
- const timestamp = new Date().getTime();
- window.location.href = `${currentUrl}?reload=${timestamp}`;
-}
+ // 瀹氭椂鍒锋柊鏈鏁伴噺锛堟瘡30绉掞級
+ unreadCountTimer = setInterval(() => {
+ if (notificationCenterRef.value) {
+ notificationCenterRef.value.loadUnreadCount()
+ }
+ }, 30000)
+})
-getUserLoginFacotryList();
+// 鐩戝惉 popover 鏄剧ず鐘舵�侊紝鎵撳紑鏃跺姞杞芥秷鎭垪琛�
+watch(notificationVisible, (val) => {
+ if (val && notificationCenterRef.value) {
+ nextTick(() => {
+ notificationCenterRef.value.loadMessages()
+ })
+ }
+})
+
+onUnmounted(() => {
+ if (unreadCountTimer) {
+ clearInterval(unreadCountTimer)
+ }
+})
</script>
<style lang='scss' scoped>
@@ -156,22 +164,6 @@
position: relative;
background: var(--navbar-bg);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-
- .center-menu {
- line-height: 50px;
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- display: flex;
- align-items: center;
-
- .label {
- font-weight: bold;
- font-size: 18px;
- color: #333333;
- margin-right: 10px;
- }
- }
.hamburger-container {
line-height: 46px;
@@ -241,6 +233,19 @@
}
}
+ .notification-container {
+ margin-right: 20px;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+
+ .notification-badge {
+ :deep(.el-badge__content) {
+ border: none;
+ }
+ }
+ }
+
.avatar-container {
margin-right: 40px;
@@ -266,4 +271,22 @@
}
}
}
+
+</style>
+
+<style lang="scss">
+.notification-popover {
+ padding: 0 !important;
+
+ .el-popover__title {
+ display: none;
+ }
+
+ .el-popover__body {
+ padding: 0 !important;
+ }
+}
+.el-badge__content.is-fixed{
+ top: 12px;
+}
</style>
diff --git a/src/layout/components/NotificationCenter/index.vue b/src/layout/components/NotificationCenter/index.vue
new file mode 100644
index 0000000..6098153
--- /dev/null
+++ b/src/layout/components/NotificationCenter/index.vue
@@ -0,0 +1,372 @@
+<template>
+ <div class="notification-popover-content">
+ <div class="popover-header">
+ <span class="popover-title">娑堟伅閫氱煡</span>
+ <el-button type="primary" size="small" @click="handleMarkAllAsRead" :disabled="unreadCount === 0">
+ 涓�閿凡璇�
+ </el-button>
+ </div>
+
+ <div class="notification-content">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane :label="`鏈(${unreadCount})`" name="unread">
+ <div v-if="unreadList.length === 0" class="empty-state">
+ <el-empty description="鏆傛棤鏈娑堟伅" />
+ </div>
+ <div v-else class="notification-list">
+ <div
+ v-for="item in unreadList"
+ :key="item.id"
+ class="notification-item"
+ >
+ <div class="notification-icon">
+ <el-icon :size="24" color="#67C23A">
+ <Bell />
+ </el-icon>
+ </div>
+ <div class="notification-content-wrapper">
+ <div class="notification-title">{{ item.noticeTitle }}</div>
+ <div class="notification-detail">{{ item.noticeContent }}</div>
+ <div class="notification-time">{{ item.createTime }}</div>
+ </div>
+ <div class="notification-action">
+ <el-button type="primary" size="small" @click="handleConfirm(item)">
+ 纭
+ </el-button>
+ </div>
+ </div>
+ </div>
+ </el-tab-pane>
+ <el-tab-pane label="宸茶" name="read">
+ <div v-if="readList.length === 0" class="empty-state">
+ <el-empty description="鏆傛棤宸茶娑堟伅" />
+ </div>
+ <div v-else class="notification-list">
+ <div
+ v-for="item in readList"
+ :key="item.id"
+ class="notification-item read"
+ >
+ <div class="notification-icon">
+ <el-icon :size="24" color="#909399">
+ <Bell />
+ </el-icon>
+ </div>
+ <div class="notification-content-wrapper">
+ <div class="notification-title">{{ item.noticeTitle }}</div>
+ <div class="notification-detail">{{ item.noticeContent }}</div>
+ <div class="notification-time">{{ item.createTime }}</div>
+ </div>
+ </div>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-wrapper" v-if="total > 0">
+ <el-pagination
+ v-model:current-page="pageNum"
+ v-model:page-size="pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="prev, pager, next, sizes"
+ @size-change="handleSizeChange"
+ @current-change="handlePageChange"
+ />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { Bell } from '@element-plus/icons-vue'
+import { listMessage, markAsRead, markAllAsRead, confirmMessage, getUnreadCount } from '@/api/system/message'
+import { ElMessage } from 'element-plus'
+import useUserStore from '@/store/modules/user'
+import { useRouter } from 'vue-router'
+
+const userStore = useUserStore()
+const router = useRouter()
+const emit = defineEmits(['unreadCountChange'])
+
+const activeTab = ref('unread')
+const unreadList = ref([])
+const readList = ref([])
+const unreadCount = ref(0)
+const total = ref(0)
+const pageNum = ref(1)
+const pageSize = ref(10)
+
+// 鍔犺浇娑堟伅鍒楄〃
+const loadMessages = async () => {
+ try {
+ const consigneeId = userStore.id
+ if (!consigneeId) {
+ console.warn('鏈幏鍙栧埌褰撳墠鐧诲綍鐢ㄦ埛ID')
+ return
+ }
+ const params = {
+ consigneeId: consigneeId,
+ current: pageNum.value,
+ size: pageSize.value,
+ status: activeTab.value === 'read' ? 1 : 0
+ }
+ const res = await listMessage(params)
+ if (res.code === 200) {
+ if (activeTab.value === 'unread') {
+ unreadList.value = res.data.records || []
+ } else {
+ readList.value = res.data.records || []
+ }
+ total.value = res.data.total || 0
+ }
+ } catch (error) {
+ console.error('鍔犺浇娑堟伅鍒楄〃澶辫触:', error)
+ }
+}
+
+// 鍔犺浇鏈鏁伴噺
+const loadUnreadCount = async () => {
+ try {
+ const consigneeId = userStore.id
+ if (!consigneeId) {
+ console.warn('鏈幏鍙栧埌褰撳墠鐧诲綍鐢ㄦ埛ID')
+ return
+ }
+ const res = await getUnreadCount(consigneeId)
+ if (res.code === 200) {
+ unreadCount.value = res.data || 0
+ emit('unreadCountChange', unreadCount.value)
+ }
+ } catch (error) {
+ console.error('鍔犺浇鏈鏁伴噺澶辫触:', error)
+ }
+}
+
+// 鏍囩椤靛垏鎹�
+const handleTabChange = (tab) => {
+ pageNum.value = 1
+ loadMessages()
+}
+
+// 纭娑堟伅
+const handleConfirm = async (item) => {
+ try {
+ console.log('item', item)
+ const res = await confirmMessage(item.noticeId, 1)
+ if (res.code === 200) {
+ ElMessage.success('纭鎴愬姛')
+ // 閲嶆柊鍔犺浇鏁版嵁
+ loadMessages()
+ loadUnreadCount()
+
+ // 鏍规嵁 jumpPath 杩涜椤甸潰璺宠浆
+ if (item.jumpPath) {
+ try {
+ // 瑙f瀽 jumpPath锛屽垎绂昏矾寰勫拰鏌ヨ鍙傛暟
+ const [path, queryString] = item.jumpPath.split('?')
+ let query = {}
+
+ if (queryString) {
+ // 瑙f瀽鏌ヨ鍙傛暟
+ queryString.split('&').forEach(param => {
+ const [key, value] = param.split('=')
+ if (key && value) {
+ query[key] = decodeURIComponent(value)
+ }
+ })
+ }
+
+ // 璺宠浆鍒版寚瀹氶〉闈�
+ router.push({
+ path: path,
+ query: query
+ })
+ } catch (error) {
+ console.error('椤甸潰璺宠浆澶辫触:', error)
+ }
+ }
+ }
+ } catch (error) {
+ console.error('纭娑堟伅澶辫触:', error)
+ ElMessage.error('纭澶辫触')
+ }
+}
+
+// 涓�閿凡璇�
+const handleMarkAllAsRead = async () => {
+ try {
+ const res = await markAllAsRead()
+ if (res.code === 200) {
+ ElMessage.success('宸插叏閮ㄦ爣璁颁负宸茶')
+ loadMessages()
+ loadUnreadCount()
+ }
+ } catch (error) {
+ console.error('涓�閿凡璇诲け璐�:', error)
+ ElMessage.error('鎿嶄綔澶辫触')
+ }
+}
+
+// 鍒嗛〉澶у皬鏀瑰彉
+const handleSizeChange = (size) => {
+ pageSize.value = size
+ pageNum.value = 1
+ loadMessages()
+}
+
+// 椤电爜鏀瑰彉
+const handlePageChange = (page) => {
+ pageNum.value = page
+ loadMessages()
+}
+
+// 缁勪欢鎸傝浇鏃跺姞杞芥湭璇绘暟閲�
+onMounted(() => {
+ loadUnreadCount()
+})
+
+// 鐩戝惉鐖剁粍浠朵紶閫掔殑 visible 鐘舵�侊紙閫氳繃 watch 鍦� Navbar 涓鐞嗭級
+// 杩欓噷鍙礋璐f暟鎹姞杞斤紝涓嶆帶鍒舵樉绀�
+
+// 鏆撮湶鏂规硶渚涘閮ㄨ皟鐢�
+defineExpose({
+ loadUnreadCount,
+ loadMessages
+})
+</script>
+
+<style lang="scss" scoped>
+.notification-popover-content {
+ display: flex;
+ flex-direction: column;
+ width: 500px;
+ padding: 16px;
+}
+
+.popover-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ margin-bottom: 16px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f0f0;
+
+ .popover-title {
+ font-size: 18px;
+ font-weight: 500;
+ color: #303133;
+ }
+}
+
+.notification-content {
+ max-height: 60vh;
+ display: flex;
+ flex-direction: column;
+
+ :deep(.el-tabs) {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+
+ .el-tabs__header {
+ margin-bottom: 0;
+ flex-shrink: 0;
+ padding: 0;
+ }
+
+ .el-tabs__content {
+ flex: 1;
+ overflow-y: auto;
+ min-height: 0;
+ padding-top: 16px;
+ }
+
+ .el-tab-pane {
+ height: 100%;
+ }
+ }
+}
+
+.empty-state {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 300px;
+ padding: 40px 0;
+}
+
+.notification-list {
+ .notification-item {
+ display: flex;
+ padding: 12px 0;
+ border-bottom: 1px solid #f0f0f0;
+ transition: background-color 0.3s;
+
+ &:hover {
+ background-color: #f5f7fa;
+ }
+
+ &.read {
+ opacity: 0.7;
+ }
+
+ .notification-icon {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f0f9ff;
+ border-radius: 50%;
+ margin-right: 12px;
+ }
+
+ .notification-content-wrapper {
+ flex: 1;
+ min-width: 0;
+
+ .notification-title {
+ font-size: 14px;
+ font-weight: 500;
+ color: #303133;
+ margin-bottom: 8px;
+ }
+
+ .notification-detail {
+ font-size: 13px;
+ color: #606266;
+ line-height: 1.5;
+ margin-bottom: 8px;
+ word-break: break-all;
+ }
+
+ .notification-time {
+ font-size: 12px;
+ color: #909399;
+ }
+ }
+
+ .notification-action {
+ flex-shrink: 0;
+ margin-left: 12px;
+ display: flex;
+ align-items: center;
+ }
+ }
+}
+
+.pagination-wrapper {
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid #f0f0f0;
+ display: flex;
+ justify-content: center;
+ padding-left: 0;
+ padding-right: 0;
+}
+</style>
+
diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue
index 898085b..f8f1da3 100644
--- a/src/layout/components/Sidebar/Logo.vue
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -3,11 +3,11 @@
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="鍏徃Logo" />
- <h1 v-else class="sidebar-title">{{ title }}</h1>
+ <h1 class="sidebar-title">{{ title }}</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="鍏徃Logo" />
- <h1 v-else class="sidebar-title">{{ title }}</h1>
+ <h1 class="sidebar-title">{{ title }}</h1>
</router-link>
</transition>
</div>
@@ -126,4 +126,4 @@
}
}
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
index 33b2e85..9044945 100644
--- a/src/layout/components/Sidebar/index.vue
+++ b/src/layout/components/Sidebar/index.vue
@@ -30,7 +30,6 @@
const sideTheme = computed(() => settingsStore.sideTheme)
const theme = computed(() => settingsStore.theme)
const isCollapse = computed(() => !appStore.sidebar.opened)
-console.log(44444, settingsStore.isDark, sideTheme.value)
// 鑾峰彇鑿滃崟鑳屾櫙鑹�
const getMenuBackground = computed(() => {
diff --git a/src/layout/components/index.js b/src/layout/components/index.js
index d1308ce..630b9fe 100644
--- a/src/layout/components/index.js
+++ b/src/layout/components/index.js
@@ -2,3 +2,4 @@
export { default as Navbar } from './Navbar'
export { default as Settings } from './Settings'
export { default as TagsView } from './TagsView/index.vue'
+export { default as NotificationCenter } from './NotificationCenter/index.vue'
diff --git a/src/main.js b/src/main.js
index ef6b2a4..0b3f714 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,113 +1,116 @@
-import { createApp } from "vue";
-
-import Cookies from "js-cookie";
-
-import ElementPlus from "element-plus";
-import "element-plus/dist/index.css";
-import "element-plus/theme-chalk/dark/css-vars.css";
-import locale from "element-plus/es/locale/lang/zh-cn";
-
-import "@/assets/styles/index.scss"; // global css
-
-import App from "./App";
-import store from "./store";
-import router from "./router";
-import directive from "./directive"; // directive
-
-// 娉ㄥ唽鎸囦护
-import plugins from "./plugins"; // plugins
-import { download } from "@/utils/request";
-
-// svg鍥炬爣
-import "virtual:svg-icons-register";
-import SvgIcon from "@/components/SvgIcon";
-import elementIcons from "@/components/SvgIcon/svgicon";
-import "./assets/fonts/font.css";
-
-import "./permission"; // permission control
-
-import { useDict } from "@/utils/dict";
-import {
- parseTime,
- resetForm,
- addDateRange,
- handleTree,
- selectDictLabel,
- selectDictLabels,
-} from "@/utils/ruoyi";
-
-// 鍒嗛〉缁勪欢
-import Pagination from "@/components/Pagination";
-// 鑷畾涔夎〃鏍煎伐鍏风粍浠�
-import RightToolbar from "@/components/RightToolbar";
-// 瀵屾枃鏈粍浠�
-import Editor from "@/components/Editor";
-// 鏂囦欢涓婁紶缁勪欢
-import FileUpload from "@/components/FileUpload";
-// 鍥剧墖涓婁紶缁勪欢
-import ImageUpload from "@/components/ImageUpload";
-// 鍥剧墖棰勮缁勪欢
-import ImagePreview from "@/components/ImagePreview";
-// 瀛楀吀鏍囩缁勪欢
-import DictTag from "@/components/DictTag";
-// 琛ㄦ牸缁勪欢
-import PIMTable from "@/components/PIMTable/PIMTable.vue";
-
-import { getToken } from "@/utils/auth";
-import {
- calculateTaxExclusiveTotalPrice,
- summarizeTable,
- calculateTaxIncludeTotalPrice,
-} from "@/utils/summarizeTable.js";
-
-const app = createApp(App);
-
-// 鍏ㄥ眬鏂规硶鎸傝浇
-app.config.globalProperties.useDict = useDict;
-app.config.globalProperties.download = download;
-app.config.globalProperties.parseTime = parseTime;
-app.config.globalProperties.resetForm = resetForm;
-app.config.globalProperties.summarizeTable = summarizeTable;
-app.config.globalProperties.calculateTaxExclusiveTotalPrice =
- calculateTaxExclusiveTotalPrice;
-app.config.globalProperties.calculateTaxIncludeTotalPrice =
- calculateTaxIncludeTotalPrice;
-app.config.globalProperties.handleTree = handleTree;
-app.config.globalProperties.addDateRange = addDateRange;
-app.config.globalProperties.selectDictLabel = selectDictLabel;
-app.config.globalProperties.selectDictLabels = selectDictLabels;
-app.config.globalProperties.javaApi = "http://114.132.189.42:7004";
-app.config.globalProperties.HaveJson = (val) => {
- return JSON.parse(JSON.stringify(val));
-};
-app.config.globalProperties.uploadHeader = {
- Authorization: "Bearer " + getToken(),
-};
-
-// 鍏ㄥ眬缁勪欢鎸傝浇
-app.component("DictTag", DictTag);
-app.component("Pagination", Pagination);
-app.component("FileUpload", FileUpload);
-app.component("ImageUpload", ImageUpload);
-app.component("ImagePreview", ImagePreview);
-app.component("RightToolbar", RightToolbar);
-app.component("Editor", Editor);
-app.component("PIMTable", PIMTable);
-
-app.use(router);
-app.use(store);
-app.use(plugins);
-app.use(elementIcons);
-app.component("svg-icon", SvgIcon);
-
-directive(app);
-
-// 浣跨敤element-plus 骞朵笖璁剧疆鍏ㄥ眬鐨勫ぇ灏�
-app.use(ElementPlus, {
- locale: locale,
- // 鏀寔 large銆乨efault銆乻mall
- size: Cookies.get("size") || "default",
-});
-app._context.components.ElDialog.props.closeOnClickModal.default = false;
-
-app.mount("#app");
+import { createApp } from "vue";
+
+import Cookies from "js-cookie";
+
+import ElementPlus from "element-plus";
+import "element-plus/dist/index.css";
+import "element-plus/theme-chalk/dark/css-vars.css";
+import locale from "element-plus/es/locale/lang/zh-cn";
+
+import "@/assets/styles/index.scss"; // global css
+
+import App from "./App";
+import store from "./store";
+import router from "./router";
+import directive from "./directive"; // directive
+
+// 娉ㄥ唽鎸囦护
+import plugins from "./plugins"; // plugins
+import { download } from "@/utils/request";
+
+// svg鍥炬爣
+import "virtual:svg-icons-register";
+import SvgIcon from "@/components/SvgIcon";
+import elementIcons from "@/components/SvgIcon/svgicon";
+import "./assets/fonts/font.css";
+
+import "./permission"; // permission control
+
+import { useDict } from "@/utils/dict";
+import {
+ parseTime,
+ resetForm,
+ addDateRange,
+ handleTree,
+ selectDictLabel,
+ selectDictLabels,
+} from "@/utils/ruoyi";
+
+// 鍒嗛〉缁勪欢
+import Pagination from "@/components/Pagination";
+// 鑷畾涔夎〃鏍煎伐鍏风粍浠�
+import RightToolbar from "@/components/RightToolbar";
+// 瀵屾枃鏈粍浠�
+import Editor from "@/components/Editor";
+// 鏂囦欢涓婁紶缁勪欢
+import FileUpload from "@/components/FileUpload";
+// 鍥剧墖涓婁紶缁勪欢
+import ImageUpload from "@/components/ImageUpload";
+// 鍥剧墖棰勮缁勪欢
+import ImagePreview from "@/components/ImagePreview";
+// 瀛楀吀鏍囩缁勪欢
+import DictTag from "@/components/DictTag";
+// 琛ㄦ牸缁勪欢
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
+// 椤甸潰澶撮儴缁勪欢
+import PageHeader from "@/components/PageHeader/index.vue";
+
+import { getToken } from "@/utils/auth";
+import {
+ calculateTaxExclusiveTotalPrice,
+ summarizeTable,
+ calculateTaxIncludeTotalPrice,
+} from "@/utils/summarizeTable.js";
+
+const app = createApp(App);
+
+// 鍏ㄥ眬鏂规硶鎸傝浇
+app.config.globalProperties.useDict = useDict;
+app.config.globalProperties.download = download;
+app.config.globalProperties.parseTime = parseTime;
+app.config.globalProperties.resetForm = resetForm;
+app.config.globalProperties.summarizeTable = summarizeTable;
+app.config.globalProperties.calculateTaxExclusiveTotalPrice =
+ calculateTaxExclusiveTotalPrice;
+app.config.globalProperties.calculateTaxIncludeTotalPrice =
+ calculateTaxIncludeTotalPrice;
+app.config.globalProperties.handleTree = handleTree;
+app.config.globalProperties.addDateRange = addDateRange;
+app.config.globalProperties.selectDictLabel = selectDictLabel;
+app.config.globalProperties.selectDictLabels = selectDictLabels;
+app.config.globalProperties.javaApi = __BASE_API__;
+app.config.globalProperties.HaveJson = (val) => {
+ return JSON.parse(JSON.stringify(val));
+};
+app.config.globalProperties.uploadHeader = {
+ Authorization: "Bearer " + getToken(),
+};
+
+// 鍏ㄥ眬缁勪欢鎸傝浇
+app.component("DictTag", DictTag);
+app.component("Pagination", Pagination);
+app.component("FileUpload", FileUpload);
+app.component("ImageUpload", ImageUpload);
+app.component("ImagePreview", ImagePreview);
+app.component("RightToolbar", RightToolbar);
+app.component("Editor", Editor);
+app.component("PIMTable", PIMTable);
+app.component("PageHeader", PageHeader);
+
+app.use(router);
+app.use(store);
+app.use(plugins);
+app.use(elementIcons);
+app.component("svg-icon", SvgIcon);
+
+directive(app);
+
+// 浣跨敤element-plus 骞朵笖璁剧疆鍏ㄥ眬鐨勫ぇ灏�
+app.use(ElementPlus, {
+ locale: locale,
+ // 鏀寔 large銆乨efault銆乻mall
+ size: Cookies.get("size") || "default",
+});
+app._context.components.ElDialog.props.closeOnClickModal.default = false;
+
+app.mount("#app");
diff --git a/src/permission.js b/src/permission.js
index 809df37..9bf4a91 100644
--- a/src/permission.js
+++ b/src/permission.js
@@ -11,7 +11,7 @@
NProgress.configure({ showSpinner: false })
-const whiteList = ['/login', '/register', '/callbacklccpn']
+const whiteList = ['/login', '/register', '/callbacklccpn','/device-info']
const isWhiteList = (path) => {
return whiteList.some(pattern => isPathMatch(pattern, path))
diff --git a/src/router/index.js b/src/router/index.js
index d190347..f5920d7 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,6 +1,6 @@
-import { createWebHistory, createRouter } from 'vue-router'
+import { createWebHistory, createRouter } from "vue-router";
/* Layout */
-import Layout from '@/layout'
+import Layout from "@/layout";
/**
* Note: 璺敱閰嶇疆椤�
@@ -16,180 +16,181 @@
* roles: ['admin', 'common'] // 璁块棶璺敱鐨勮鑹叉潈闄�
* permissions: ['a:a:a', 'b:b:b'] // 璁块棶璺敱鐨勮彍鍗曟潈闄�
* meta : {
- noCache: true // 濡傛灉璁剧疆涓簍rue锛屽垯涓嶄細琚� <keep-alive> 缂撳瓨(榛樿 false)
- title: 'title' // 璁剧疆璇ヨ矾鐢卞湪渚ц竟鏍忓拰闈㈠寘灞戜腑灞曠ず鐨勫悕瀛�
- icon: 'svg-name' // 璁剧疆璇ヨ矾鐢辩殑鍥炬爣锛屽搴旇矾寰剆rc/assets/icons/svg
- breadcrumb: false // 濡傛灉璁剧疆涓篺alse锛屽垯涓嶄細鍦╞readcrumb闈㈠寘灞戜腑鏄剧ず
- activeMenu: '/system/user' // 褰撹矾鐢辫缃簡璇ュ睘鎬э紝鍒欎細楂樹寒鐩稿搴旂殑渚ц竟鏍忋��
- }
+ noCache: true // 濡傛灉璁剧疆涓簍rue锛屽垯涓嶄細琚� <keep-alive> 缂撳瓨(榛樿 false)
+ title: 'title' // 璁剧疆璇ヨ矾鐢卞湪渚ц竟鏍忓拰闈㈠寘灞戜腑灞曠ず鐨勫悕瀛�
+ icon: 'svg-name' // 璁剧疆璇ヨ矾鐢辩殑鍥炬爣锛屽搴旇矾寰剆rc/assets/icons/svg
+ breadcrumb: false // 濡傛灉璁剧疆涓篺alse锛屽垯涓嶄細鍦╞readcrumb闈㈠寘灞戜腑鏄剧ず
+ activeMenu: '/system/user' // 褰撹矾鐢辫缃簡璇ュ睘鎬э紝鍒欎細楂樹寒鐩稿搴旂殑渚ц竟鏍忋��
+ }
*/
// 鍏叡璺敱
export const constantRoutes = [
{
- path: '/redirect',
+ path: "/redirect",
component: Layout,
hidden: true,
children: [
{
- path: '/redirect/:path(.*)',
- component: () => import('@/views/redirect/index.vue')
- }
- ]
+ path: "/redirect/:path(.*)",
+ component: () => import("@/views/redirect/index.vue"),
+ },
+ ],
},
{
- path: '/login',
- component: () => import('@/views/login'),
- hidden: true
- },
- {
- path: "/callbacklccpn",
- component: () => import("@/views/tideLogin.vue"),
+ path: "/login",
+ component: () => import("@/views/login"),
hidden: true,
},
{
- path: '/register',
- component: () => import('@/views/register'),
- hidden: true
+ path: "/register",
+ component: () => import("@/views/register"),
+ hidden: true,
},
{
path: "/:pathMatch(.*)*",
- component: () => import('@/views/error/404'),
- hidden: true
+ component: () => import("@/views/error/404"),
+ hidden: true,
},
{
- path: '/401',
- component: () => import('@/views/error/401'),
- hidden: true
+ path: "/401",
+ component: () => import("@/views/error/401"),
+ hidden: true,
},
{
- path: '',
+ path: "",
component: Layout,
- redirect: '/index',
+ redirect: "/index",
children: [
{
- path: '/index',
- component: () => import('@/views/index'),
- name: 'Index',
- meta: { title: '棣栭〉', icon: 'dashboard', affix: true }
- }
- ]
+ path: "/index",
+ component: () => import("@/views/index"),
+ name: "Index",
+ meta: { title: "棣栭〉", icon: "dashboard", affix: true },
+ },
+ ],
},
{
- path: '/user',
+ path: "/user",
component: Layout,
hidden: true,
- redirect: 'noredirect',
+ redirect: "noredirect",
children: [
{
- path: 'profile',
- component: () => import('@/views/system/user/profile/index'),
- name: 'Profile',
- meta: { title: '涓汉涓績', icon: 'user' }
- }
- ]
- }
-]
+ path: "profile",
+ component: () => import("@/views/system/user/profile/index"),
+ name: "Profile",
+ meta: { title: "涓汉涓績", icon: "user" },
+ },
+ ],
+ },
+ {
+ path: "/device-info",
+ component: () => import("@/views/equipmentManagement/deviceInfo/index.vue"),
+ hidden: true,
+ name: "DeviceInfo",
+ meta: { title: "璁惧淇℃伅", icon: "monitor" },
+ },
+ // 娣诲姞椤圭洰璇︽儏椤甸潰璺敱閰嶇疆
+ {
+ path: "/oaSystem/projectManagement/projectDetail",
+ component: Layout,
+ hidden: true,
+ children: [
+ {
+ path: ":projectId",
+ component: () => import("@/views/oaSystem/projectManagement/projectDetail.vue"),
+ name: "ProjectDetail",
+ meta: { title: "椤圭洰璇︽儏", activeMenu: "/oaSystem/projectManagement" },
+ },
+ ],
+ },
+];
// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇
export const dynamicRoutes = [
{
- path: '/system/user-auth',
+ path: "/system/user-auth",
component: Layout,
hidden: true,
- permissions: ['system:user:edit'],
+ permissions: ["system:user:edit"],
children: [
{
- path: 'role/:userId(\\d+)',
- component: () => import('@/views/system/user/authRole'),
- name: 'AuthRole',
- meta: { title: '鍒嗛厤瑙掕壊', activeMenu: '/system/user' }
- }
- ]
+ path: "role/:userId(\\d+)",
+ component: () => import("@/views/system/user/authRole"),
+ name: "AuthRole",
+ meta: { title: "鍒嗛厤瑙掕壊", activeMenu: "/system/user" },
+ },
+ ],
},
{
- path: '/main/MobileChat',
+ path: "/system/role-auth",
component: Layout,
- redirect: '',
hidden: true,
- permissions: ['MobileChat:edit'],
+ permissions: ["system:role:edit"],
children: [
{
- path: '',
- component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
- name: 'MobileChat',
- meta: { title: 'AI瀵硅瘽', activeMenu: '/chatHome/chatHomeIndex'}
- }
- ]
+ path: "user/:roleId(\\d+)",
+ component: () => import("@/views/system/role/authUser"),
+ name: "AuthUser",
+ meta: { title: "鍒嗛厤鐢ㄦ埛", activeMenu: "/system/role" },
+ },
+ ],
},
{
- path: '/system/role-auth',
+ path: "/system/dict-data",
component: Layout,
hidden: true,
- permissions: ['system:role:edit'],
+ permissions: ["system:dict:list"],
children: [
{
- path: 'user/:roleId(\\d+)',
- component: () => import('@/views/system/role/authUser'),
- name: 'AuthUser',
- meta: { title: '鍒嗛厤鐢ㄦ埛', activeMenu: '/system/role' }
- }
- ]
+ path: "index/:dictId(\\d+)",
+ component: () => import("@/views/system/dict/data"),
+ name: "Data",
+ meta: { title: "瀛楀吀鏁版嵁", activeMenu: "/system/dict" },
+ },
+ ],
},
{
- path: '/system/dict-data',
+ path: "/monitor/job-log",
component: Layout,
hidden: true,
- permissions: ['system:dict:list'],
+ permissions: ["monitor:job:list"],
children: [
{
- path: 'index/:dictId(\\d+)',
- component: () => import('@/views/system/dict/data'),
- name: 'Data',
- meta: { title: '瀛楀吀鏁版嵁', activeMenu: '/system/dict' }
- }
- ]
+ path: "index/:jobId(\\d+)",
+ component: () => import("@/views/monitor/job/log"),
+ name: "JobLog",
+ meta: { title: "璋冨害鏃ュ織", activeMenu: "/monitor/job" },
+ },
+ ],
},
{
- path: '/monitor/job-log',
+ path: "/tool/gen-edit",
component: Layout,
hidden: true,
- permissions: ['monitor:job:list'],
+ permissions: ["tool:gen:edit"],
children: [
{
- path: 'index/:jobId(\\d+)',
- component: () => import('@/views/monitor/job/log'),
- name: 'JobLog',
- meta: { title: '璋冨害鏃ュ織', activeMenu: '/monitor/job' }
- }
- ]
+ path: "index/:tableId(\\d+)",
+ component: () => import("@/views/tool/gen/editTable"),
+ name: "GenEdit",
+ meta: { title: "淇敼鐢熸垚閰嶇疆", activeMenu: "/tool/gen" },
+ },
+ ],
},
- {
- path: '/tool/gen-edit',
- component: Layout,
- hidden: true,
- permissions: ['tool:gen:edit'],
- children: [
- {
- path: 'index/:tableId(\\d+)',
- component: () => import('@/views/tool/gen/editTable'),
- name: 'GenEdit',
- meta: { title: '淇敼鐢熸垚閰嶇疆', activeMenu: '/tool/gen' }
- }
- ]
- }
-]
+];
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
- return savedPosition
+ return savedPosition;
}
- return { top: 0 }
+ return { top: 0 };
},
-})
+});
-export default router
+export default router;
diff --git a/src/utils/request.js b/src/utils/request.js
index a8a6b67..9cfcf5b 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -17,7 +17,7 @@
// axios涓姹傞厤缃湁baseURL閫夐」锛岃〃绀鸿姹俇RL鍏叡閮ㄥ垎
baseURL: import.meta.env.VITE_APP_BASE_API,
// 瓒呮椂
- timeout: 60000
+ timeout: 160000
})
// request鎷︽埅鍣�
diff --git a/src/utils/util.js b/src/utils/util.js
index 78846dc..be08cc1 100644
--- a/src/utils/util.js
+++ b/src/utils/util.js
@@ -1,4 +1,6 @@
//闃叉姈
+import dayjs from "dayjs";
+
export function debounce(fn) {
console.log(1)
let t = null //鍙細鎵ц涓�娆�
@@ -86,7 +88,34 @@
'aplication/zip': 'zpi',
}
}
-
+ export const deepCopySameProperties = (source, target) =>{
+ for (const key in source) {
+ if (target.hasOwnProperty(key)) {
+ if (typeof source[key] === 'object' && source[key] !== null &&
+ typeof target[key] === 'object' && target[key] !== null) {
+ // 閫掑綊澶勭悊瀵硅薄
+ deepCopySameProperties(source[key], target[key]);
+ } else {
+ // 鍩烘湰绫诲瀷鐩存帴璧嬪��
+ target[key] = source[key];
+ }
+ }
+ }
+ return target;
+}
export function filterArr(arr) {
return arr.filter(item => item.flag !== false);
- }
\ No newline at end of file
+ }
+
+ export function getCurrentMonth () {
+ let month = dayjs().month() + 1
+ if (month <= 3) {
+ return '1';
+ } else if (month <= 6) {
+ return '2';
+ } else if (month <= 9) {
+ return '3';
+ } else if (month <= 12) {
+ return '4';
+ }
+}
\ No newline at end of file
diff --git a/src/views/basicData/customerFile/index.vue b/src/views/basicData/customerFile/index.vue
index 8043d1a..0f39e25 100644
--- a/src/views/basicData/customerFile/index.vue
+++ b/src/views/basicData/customerFile/index.vue
@@ -5,12 +5,23 @@
<span class="search_title">瀹㈡埛鍚嶇О锛�</span>
<el-input
v-model="searchForm.customerName"
- style="width: 240px"
+ style="width: 240px;margin-right: 10px"
placeholder="璇疯緭鍏�"
@change="handleQuery"
clearable
:prefix-icon="Search"
/>
+ <span class="search_title">瀹㈡埛鍒嗙被锛�</span>
+ <el-select
+ v-model="searchForm.customerType"
+ placeholder="璇烽�夋嫨"
+ style="width: 240px"
+ clearable
+ @change="handleQuery"
+ >
+ <el-option label="闆跺敭瀹㈡埛" value="闆跺敭瀹㈡埛" />
+ <el-option label="杩涢攢鍟嗗鎴�" value="杩涢攢鍟嗗鎴�" />
+ </el-select>
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
@@ -122,6 +133,14 @@
/>
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍒嗙被锛�" prop="customerType">
+ <el-select v-model="form.customerType" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="闆跺敭瀹㈡埛" value="闆跺敭瀹㈡埛" />
+ <el-option label="杩涢攢鍟嗗鎴�" value="杩涢攢鍟嗗鎴�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
</el-row>
<el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index">
<el-col :span="12">
@@ -169,7 +188,6 @@
type="date"
placeholder="璇烽�夋嫨"
clearable
- disabled
/>
</el-form-item>
</el-col>
@@ -209,13 +227,13 @@
<template #tip>
<div class="el-upload__tip text-center">
<span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
-<!-- <el-link-->
-<!-- type="primary"-->
-<!-- :underline="false"-->
-<!-- style="font-size: 12px; vertical-align: baseline"-->
-<!-- @click="importTemplate"-->
-<!-- >涓嬭浇妯℃澘</el-link-->
-<!-- >-->
+ <el-link
+ type="primary"
+ :underline="false"
+ style="font-size: 12px; vertical-align: baseline"
+ @click="importTemplate"
+ >涓嬭浇妯℃澘</el-link
+ >
</div>
</template>
</el-upload>
@@ -247,6 +265,11 @@
const userStore = useUserStore();
const tableColumn = ref([
+ {
+ label: "瀹㈡埛鍒嗙被",
+ prop: "customerType",
+ width: 120,
+ },
{
label: "瀹㈡埛鍚嶇О",
prop: "customerName",
@@ -307,9 +330,6 @@
clickFun: (row) => {
openForm("edit", row);
},
- disabled: (row) => {
- return row.maintainer !== userStore.nickName
- }
},
],
},
@@ -339,6 +359,7 @@
const data = reactive({
searchForm: {
customerName: "",
+ customerType: "",
},
form: {
customerName: "",
@@ -352,6 +373,7 @@
basicBankAccount: "",
bankAccount: "",
bankCode: "",
+ customerType: "",
},
rules: {
customerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
@@ -369,6 +391,7 @@
basicBankAccount: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
bankAccount: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
bankCode: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ customerType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
},
});
const upload = reactive({
@@ -399,8 +422,12 @@
// 鏂囦欢涓婁紶鎴愬姛鏃剁殑鍥炶皟
onSuccess: (response, file, fileList) => {
console.log('涓婁紶鎴愬姛', response, file, fileList);
+ upload.isUploading = false;
if(response.code === 200){
proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
+ upload.open = false;
+ proxy.$refs["uploadRef"].clearFiles();
+ getList();
}else if(response.code === 500){
proxy.$modal.msgError(response.msg);
}else{
@@ -410,6 +437,7 @@
// 鏂囦欢涓婁紶澶辫触鏃剁殑鍥炶皟
onError: (error, file, fileList) => {
console.error('涓婁紶澶辫触', error, file, fileList);
+ upload.isUploading = false;
proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
},
// 鏂囦欢涓婁紶杩涘害鍥炶皟
@@ -455,6 +483,7 @@
};
/** 鎻愪氦涓婁紶鏂囦欢 */
function submitFileForm() {
+ upload.isUploading = true;
proxy.$refs["uploadRef"].submit();
}
/** 瀵煎叆鎸夐挳鎿嶄綔 */
@@ -462,6 +491,10 @@
upload.title = "瀹㈡埛瀵煎叆";
upload.open = true;
}
+/** 涓嬭浇妯℃澘 */
+function importTemplate() {
+ proxy.download("/basic/customer/downloadTemplate", {}, "瀹㈡埛瀵煎叆妯℃澘.xlsx");
+}
// 鎵撳紑寮规
const openForm = (type, row) => {
operationType.value = type;
diff --git a/src/views/basicData/product/ProductSelectDialog.vue b/src/views/basicData/product/ProductSelectDialog.vue
new file mode 100644
index 0000000..70dbb16
--- /dev/null
+++ b/src/views/basicData/product/ProductSelectDialog.vue
@@ -0,0 +1,180 @@
+<template>
+ <el-dialog v-model="visible" title="閫夋嫨浜у搧" width="900px" destroy-on-close :close-on-click-modal="false">
+ <el-form :inline="true" :model="query" class="mb-2">
+ <el-form-item label="浜у搧澶х被">
+ <el-input v-model="query.productName" placeholder="杈撳叆浜у搧澶х被" clearable @keyup.enter="onSearch" />
+ </el-form-item>
+
+ <el-form-item label="鍨嬪彿鍚嶇О">
+ <el-input v-model="query.model" placeholder="杈撳叆鍨嬪彿鍚嶇О" clearable @keyup.enter="onSearch" />
+ </el-form-item>
+
+ <el-form-item>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
+ <el-button @click="onReset">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 鍒楄〃 -->
+ <el-table ref="tableRef" v-loading="loading" :data="tableData" height="420" highlight-current-row row-key="id"
+ @selection-change="handleSelectionChange" @select="handleSelect">
+ <el-table-column type="selection" width="55" />
+ <el-table-column type="index" label="搴忓彿" width="60" />
+ <el-table-column prop="productName" label="浜у搧澶х被" min-width="160" />
+ <el-table-column prop="model" label="鍨嬪彿鍚嶇О" min-width="200" />
+ <el-table-column prop="unit" label="鍗曚綅" min-width="160" />
+ </el-table>
+
+ <div class="mt-3 flex justify-end">
+ <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
+ v-model:page-size="page.pageSize" v-model:current-page="page.pageNum" :page-sizes="[10, 20, 50, 100]"
+ @size-change="onPageChange" @current-change="onPageChange" />
+ </div>
+
+ <template #footer>
+ <el-button @click="close()">鍙栨秷</el-button>
+ <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm">
+ 纭畾
+ </el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+import { productModelList } from '@/api/basicData/productModel'
+
+export type ProductRow = {
+ id: number;
+ productName: string;
+ model: string;
+ unit?: string;
+};
+
+const props = defineProps<{
+ modelValue: boolean;
+ single?: boolean; // 鏄惁鍙兘閫夋嫨涓�涓紝榛樿false锛堝彲閫夋嫨澶氫釜锛�
+}>();
+
+const emit = defineEmits(['update:modelValue', 'confirm']);
+
+const visible = computed({
+ get: () => props.modelValue,
+ set: (v) => emit("update:modelValue", v),
+});
+
+const query = reactive({
+ productName: "",
+ model: "",
+});
+
+const page = reactive({
+ pageNum: 1,
+ pageSize: 10,
+});
+
+const loading = ref(false);
+const tableData = ref<ProductRow[]>([]);
+const total = ref(0);
+const multipleSelection = ref<ProductRow[]>([]);
+const tableRef = ref();
+
+function close() {
+ visible.value = false;
+}
+
+const handleSelectionChange = (val: ProductRow[]) => {
+ if (props.single && val.length > 1) {
+ // 濡傛灉闄愬埗涓哄崟涓�夋嫨锛屽彧淇濈暀鏈�鍚庝竴涓�変腑鐨�
+ const lastSelected = val[val.length - 1];
+ multipleSelection.value = [lastSelected];
+ // 娓呯┖琛ㄦ牸閫変腑鐘舵�侊紝鐒跺悗閲嶆柊閫変腑鏈�鍚庝竴涓�
+ nextTick(() => {
+ if (tableRef.value) {
+ tableRef.value.clearSelection();
+ tableRef.value.toggleRowSelection(lastSelected, true);
+ }
+ });
+ } else {
+ multipleSelection.value = val;
+ }
+}
+
+// 澶勭悊鍗曚釜閫夋嫨
+const handleSelect = (selection: ProductRow[], row: ProductRow) => {
+ if (props.single) {
+ // 濡傛灉闄愬埗涓哄崟涓紝娓呯┖鍏朵粬閫夋嫨锛屽彧淇濈暀褰撳墠琛�
+ if (selection.includes(row)) {
+ // 閫変腑褰撳墠琛屾椂锛屾竻绌哄叾浠栭�変腑
+ multipleSelection.value = [row];
+ nextTick(() => {
+ if (tableRef.value) {
+ tableData.value.forEach((item) => {
+ if (item.id !== row.id) {
+ tableRef.value.toggleRowSelection(item, false);
+ }
+ });
+ }
+ });
+ }
+ }
+}
+
+function onSearch() {
+ page.pageNum = 1;
+ loadData();
+}
+
+function onReset() {
+ query.productName = "";
+ query.model = "";
+ page.pageNum = 1;
+ loadData();
+}
+
+function onPageChange() {
+ loadData();
+}
+
+function onConfirm() {
+ if (multipleSelection.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨涓�鏉′骇鍝�");
+ return;
+ }
+ if (props.single && multipleSelection.value.length > 1) {
+ ElMessage.warning("鍙兘閫夋嫨涓�涓骇鍝�");
+ return;
+ }
+ emit("confirm", props.single ? [multipleSelection.value[0]] : multipleSelection.value);
+ close();
+}
+
+async function loadData() {
+ loading.value = true;
+ try {
+ multipleSelection.value = []; // 缈婚〉/鎼滅储鍚庢竻绌洪�夋嫨鏇寸鍚堥鏈�
+ const res: any = await productModelList({
+ productName: query.productName.trim(),
+ model: query.model.trim(),
+ current: page.pageNum,
+ size: page.pageSize,
+ });
+ tableData.value = res.records;
+ total.value = res.total;
+ } finally {
+ loading.value = false;
+ }
+}
+
+// 鐩戝惉寮圭獥鎵撳紑锛岄噸缃�夋嫨
+watch(() => props.modelValue, (visible) => {
+ if (visible) {
+ multipleSelection.value = [];
+ }
+});
+
+onMounted(() => {
+ loadData()
+})
+</script>
diff --git a/src/views/basicData/product/index.vue b/src/views/basicData/product/index.vue
index b88d678..3f0921a 100644
--- a/src/views/basicData/product/index.vue
+++ b/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
diff --git a/src/views/basicData/supplierManage/components/BlacklistTab.vue b/src/views/basicData/supplierManage/components/BlacklistTab.vue
new file mode 100644
index 0000000..894a286
--- /dev/null
+++ b/src/views/basicData/supplierManage/components/BlacklistTab.vue
@@ -0,0 +1,564 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">渚涘簲鍟嗘。妗堬細</span>
+ <el-input
+ v-model="searchForm.supplierName"
+ style="width: 240px"
+ placeholder="杈撳叆渚涘簲鍟嗗悕绉版悳绱�"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ </div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ ></PIMTable>
+ </div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ :title="operationType === 'add' ? '鏂板渚涘簲鍟嗕俊鎭�' : '缂栬緫渚涘簲鍟嗕俊鎭�'"
+ width="70%"
+ @close="closeDia"
+ >
+ <el-form
+ :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierName">
+ <el-input
+ v-model="form.supplierName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item
+ label="绾崇◣浜鸿瘑鍒彿锛�"
+ prop="taxpayerIdentificationNum"
+ >
+ <el-input
+ v-model="form.taxpayerIdentificationNum"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍏徃鍦板潃锛�" prop="companyAddress">
+ <el-input
+ v-model="form.companyAddress"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏徃鐢佃瘽锛�" prop="companyPhone">
+ <el-input
+ v-model="form.companyPhone"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯锛�" prop="bankAccountName">
+ <el-input
+ v-model="form.bankAccountName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璐﹀彿锛�" prop="bankAccountNum">
+ <el-input
+ v-model="form.bankAccountNum"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴浜猴細" prop="contactUserName">
+ <el-input
+ v-model="form.contactUserName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴鐢佃瘽锛�" prop="contactUserPhone">
+ <el-input
+ v-model="form.contactUserPhone"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="缁存姢浜猴細" prop="maintainUserId">
+ <el-select
+ v-model="form.maintainUserId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ disabled
+ >
+ <el-option
+ v-for="item in userList"
+ :key="item.nickName"
+ :label="item.nickName"
+ :value="item.userId"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁存姢鏃堕棿锛�" prop="maintainTime">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.maintainTime"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鏄惁鐧藉悕鍗曪細" prop="isWhite">
+ <el-select v-model="form.isWhite" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="鏄�" :value="0" />
+ <el-option label="鍚�" :value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 渚涘簲鍟嗗鍏ュ璇濇 -->
+ <el-dialog
+ :title="upload.title"
+ v-model="upload.open"
+ width="400px"
+ append-to-body
+ >
+ <el-upload
+ ref="uploadRef"
+ :limit="1"
+ accept=".xlsx, .xls"
+ :headers="upload.headers"
+ :action="upload.url + '?updateSupport=' + upload.updateSupport"
+ :disabled="upload.isUploading"
+ :on-progress="handleFileUploadProgress"
+ :on-success="handleFileSuccess"
+ :on-error="handleFileError"
+ :auto-upload="false"
+ drag
+ >
+ <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+ <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <template #tip>
+ <div class="el-upload__tip text-center">
+ <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
+ <el-link
+ type="primary"
+ :underline="false"
+ style="font-size: 12px; vertical-align: baseline"
+ @click="importTemplate"
+ >涓嬭浇妯℃澘</el-link
+ >
+ </div>
+ </template>
+ </el-upload>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+ <el-button @click="upload.open = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <files-dia ref="filesDia"></files-dia>
+ </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import { delSupplier } from "@/api/basicData/supplierManageFile.js";
+import { ElMessageBox } from "element-plus";
+import { userListNoPage } from "@/api/system/user.js";
+import {
+ addSupplier,
+ getSupplier,
+ listSupplier,
+ updateSupplier,
+} from "@/api/basicData/supplierManageFile.js";
+import useUserStore from "@/store/modules/user";
+import { getToken } from "@/utils/auth.js";
+import FilesDia from "../filesDia.vue";
+const { proxy } = getCurrentInstance();
+const userStore = useUserStore();
+
+const tableColumn = ref([
+ {
+ label: "渚涘簲鍟嗗悕绉�",
+ prop: "supplierName",
+ width: 250,
+ },
+ {
+ label: "绾崇◣浜鸿瘑鍒彿",
+ prop: "taxpayerIdentificationNum",
+ width: 230,
+ },
+ {
+ label: "鍏徃鍦板潃",
+ prop: "companyAddress",
+ width: 220,
+ },
+ {
+ label: "鑱旂郴鏂瑰紡",
+ prop: "companyPhone",
+ width:150
+ },
+ {
+ label: "寮�鎴疯",
+ prop: "bankAccountName",
+ width: 220,
+ },
+ {
+ label: "璐﹀彿",
+ prop: "bankAccountNum",
+ width: 220,
+ },
+ {
+ label: "鑱旂郴浜�",
+ prop: "contactUserName",
+ },
+ {
+ label: "鑱旂郴鐢佃瘽",
+ prop: "contactUserPhone",
+ width: 150,
+ },
+ {
+ label: "缁存姢浜�",
+ prop: "maintainUserName",
+ },
+
+ {
+ label: "缁存姢鏃堕棿",
+ prop: "maintainTime",
+ width:100
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: 'right',
+ width: 150,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ openForm("edit", row);
+ },
+ },
+ {
+ //璧勮川闄勪欢
+ name: "璧勮川鏂囦欢",
+ type: "text",
+ clickFun: (row) => {
+ openFilesFormDia(row)
+ }
+ }
+ ],
+ },
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const userList = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const filesDia = ref()
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref("");
+const dialogFormVisible = ref(false);
+const data = reactive({
+ searchForm: {
+ supplierName: "",
+ },
+ form: {
+ supplierName: "",
+ taxpayerIdentificationNum: "",
+ companyAddress: "",
+ companyPhone: "",
+ bankAccountName: "",
+ bankAccountNum: "",
+ contactUserName: "",
+ contactUserPhone: "",
+ maintainUserId: "",
+ maintainTime: "",
+ isWhite: "",
+ },
+ rules: {
+ supplierName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxpayerIdentificationNum: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ companyAddress: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ companyPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ bankAccountName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ bankAccountNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ contactUserName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ contactUserPhone: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ maintainUserId: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
+ maintainTime: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+});
+const { searchForm, form, rules } = toRefs(data);
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+/** 鎻愪氦涓婁紶鏂囦欢 */
+function submitFileForm() {
+ upload.isUploading = true;
+ proxy.$refs["uploadRef"].submit();
+}
+const getList = () => {
+ tableLoading.value = true;
+ listSupplier({ ...searchForm.value, ...page, isWhite: 1 }).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ });
+};
+const upload = reactive({
+ // 鏄惁鏄剧ず寮瑰嚭灞傦紙渚涘簲鍟嗗鍏ワ級
+ open: false,
+ // 寮瑰嚭灞傛爣棰橈紙渚涘簲鍟嗗鍏ワ級
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
+ updateSupport: 1,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
+});
+/** 瀵煎叆鎸夐挳鎿嶄綔 */
+function handleImport() {
+ upload.title = "渚涘簲鍟嗗鍏�";
+ upload.open = true;
+}
+/** 涓嬭浇妯℃澘 */
+function importTemplate() {
+ proxy.download("/system/supplier/downloadTemplate", {}, "渚涘簲鍟嗗鍏ユā鏉�.xlsx");
+}
+
+/**鏂囦欢涓婁紶涓鐞� */
+const handleFileUploadProgress = (event, file, fileList) => {
+ upload.isUploading = true;
+};
+
+/** 鏂囦欢涓婁紶鎴愬姛澶勭悊 */
+const handleFileSuccess = (response, file, fileList) => {
+ upload.isUploading = false;
+ if(response.code === 200){
+ proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
+ upload.open = false;
+ proxy.$refs["uploadRef"].clearFiles();
+ getList();
+ }else if(response.code === 500){
+ proxy.$modal.msgError(response.msg);
+ }else{
+ proxy.$modal.msgWarning(response.msg);
+ }
+};
+
+/** 鏂囦欢涓婁紶澶辫触澶勭悊 */
+const handleFileError = (error, file, fileList) => {
+ upload.isUploading = false;
+ proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ operationType.value = type;
+ form.value = {};
+ form.value.maintainUserId = userStore.id;
+ form.value.maintainTime = getCurrentDate();
+ userListNoPage().then((res) => {
+ userList.value = res.data;
+ });
+ if (type === "edit") {
+ getSupplier(row.id).then((res) => {
+ form.value = { ...res.data };
+ });
+ }
+ dialogFormVisible.value = true;
+};
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitEdit();
+ } else {
+ submitAdd();
+ }
+ }
+ });
+};
+// 鎻愪氦鏂板
+const submitAdd = () => {
+ addSupplier(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+};
+// 鎻愪氦淇敼
+const submitEdit = () => {
+ updateSupplier(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+};
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/system/supplier/export", {}, "渚涘簲鍟嗘。妗�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
+ const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
+ if (unauthorizedData.length > 0) {
+ proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
+ return;
+ }
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ delSupplier(ids)
+ .then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
+function getCurrentDate() {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
+ const day = String(today.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+}
+// 鎵撳紑闄勪欢寮规
+const openFilesFormDia = (row) => {
+ nextTick(() => {
+ filesDia.value?.openDialog(row)
+ })
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+
diff --git a/src/views/basicData/supplierManage/components/HomeTab.vue b/src/views/basicData/supplierManage/components/HomeTab.vue
new file mode 100644
index 0000000..67e3646
--- /dev/null
+++ b/src/views/basicData/supplierManage/components/HomeTab.vue
@@ -0,0 +1,569 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">渚涘簲鍟嗘。妗堬細</span>
+ <el-input
+ v-model="searchForm.supplierName"
+ style="width: 240px"
+ placeholder="杈撳叆渚涘簲鍟嗗悕绉版悳绱�"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ </div>
+ <div>
+ <el-button type="primary" @click="openForm('add')"
+ >鏂板渚涘簲鍟�</el-button
+ >
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="info" plain icon="Upload" @click="handleImport"
+ >瀵煎叆</el-button
+ >
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ ></PIMTable>
+ </div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ :title="operationType === 'add' ? '鏂板渚涘簲鍟嗕俊鎭�' : '缂栬緫渚涘簲鍟嗕俊鎭�'"
+ width="70%"
+ @close="closeDia"
+ >
+ <el-form
+ :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierName">
+ <el-input
+ v-model="form.supplierName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item
+ label="绾崇◣浜鸿瘑鍒彿锛�"
+ prop="taxpayerIdentificationNum"
+ >
+ <el-input
+ v-model="form.taxpayerIdentificationNum"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍏徃鍦板潃锛�" prop="companyAddress">
+ <el-input
+ v-model="form.companyAddress"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏徃鐢佃瘽锛�" prop="companyPhone">
+ <el-input
+ v-model="form.companyPhone"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯锛�" prop="bankAccountName">
+ <el-input
+ v-model="form.bankAccountName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璐﹀彿锛�" prop="bankAccountNum">
+ <el-input
+ v-model="form.bankAccountNum"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴浜猴細" prop="contactUserName">
+ <el-input
+ v-model="form.contactUserName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴鐢佃瘽锛�" prop="contactUserPhone">
+ <el-input
+ v-model="form.contactUserPhone"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="缁存姢浜猴細" prop="maintainUserId">
+ <el-select
+ v-model="form.maintainUserId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ disabled
+ >
+ <el-option
+ v-for="item in userList"
+ :key="item.nickName"
+ :label="item.nickName"
+ :value="item.userId"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁存姢鏃堕棿锛�" prop="maintainTime">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.maintainTime"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鏄惁鐧藉悕鍗曪細" prop="isWhite">
+ <el-select v-model="form.isWhite" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="鏄�" :value="0" />
+ <el-option label="鍚�" :value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 渚涘簲鍟嗗鍏ュ璇濇 -->
+ <el-dialog
+ :title="upload.title"
+ v-model="upload.open"
+ width="400px"
+ append-to-body
+ >
+ <el-upload
+ ref="uploadRef"
+ :limit="1"
+ accept=".xlsx, .xls"
+ :headers="upload.headers"
+ :action="upload.url + '?updateSupport=' + upload.updateSupport"
+ :disabled="upload.isUploading"
+ :on-progress="handleFileUploadProgress"
+ :on-success="handleFileSuccess"
+ :on-error="handleFileError"
+ :auto-upload="false"
+ drag
+ >
+ <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+ <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <template #tip>
+ <div class="el-upload__tip text-center">
+ <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
+ <el-link
+ type="primary"
+ :underline="false"
+ style="font-size: 12px; vertical-align: baseline"
+ @click="importTemplate"
+ >涓嬭浇妯℃澘</el-link
+ >
+ </div>
+ </template>
+ </el-upload>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+ <el-button @click="upload.open = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <files-dia ref="filesDia"></files-dia>
+ </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import { delSupplier } from "@/api/basicData/supplierManageFile.js";
+import { ElMessageBox } from "element-plus";
+import { userListNoPage } from "@/api/system/user.js";
+import {
+ addSupplier,
+ getSupplier,
+ listSupplier,
+ updateSupplier,
+} from "@/api/basicData/supplierManageFile.js";
+import useUserStore from "@/store/modules/user";
+import { getToken } from "@/utils/auth.js";
+import FilesDia from "../filesDia.vue";
+const { proxy } = getCurrentInstance();
+const userStore = useUserStore();
+
+const tableColumn = ref([
+ {
+ label: "渚涘簲鍟嗗悕绉�",
+ prop: "supplierName",
+ width: 250,
+ },
+ {
+ label: "绾崇◣浜鸿瘑鍒彿",
+ prop: "taxpayerIdentificationNum",
+ width: 230,
+ },
+ {
+ label: "鍏徃鍦板潃",
+ prop: "companyAddress",
+ width: 220,
+ },
+ {
+ label: "鑱旂郴鏂瑰紡",
+ prop: "companyPhone",
+ width:150
+ },
+ {
+ label: "寮�鎴疯",
+ prop: "bankAccountName",
+ width: 220,
+ },
+ {
+ label: "璐﹀彿",
+ prop: "bankAccountNum",
+ width: 220,
+ },
+ {
+ label: "鑱旂郴浜�",
+ prop: "contactUserName",
+ },
+ {
+ label: "鑱旂郴鐢佃瘽",
+ prop: "contactUserPhone",
+ width: 150,
+ },
+ {
+ label: "缁存姢浜�",
+ prop: "maintainUserName",
+ },
+
+ {
+ label: "缁存姢鏃堕棿",
+ prop: "maintainTime",
+ width:100
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: 'right',
+ width: 150,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ openForm("edit", row);
+ },
+ },
+ {
+ //璧勮川闄勪欢
+ name: "璧勮川鏂囦欢",
+ type: "text",
+ clickFun: (row) => {
+ openFilesFormDia(row)
+ }
+ }
+ ],
+ },
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const userList = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const filesDia = ref()
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref("");
+const dialogFormVisible = ref(false);
+const data = reactive({
+ searchForm: {
+ supplierName: "",
+ },
+ form: {
+ supplierName: "",
+ taxpayerIdentificationNum: "",
+ companyAddress: "",
+ companyPhone: "",
+ bankAccountName: "",
+ bankAccountNum: "",
+ contactUserName: "",
+ contactUserPhone: "",
+ maintainUserId: "",
+ maintainTime: "",
+ isWhite: "",
+ },
+ rules: {
+ supplierName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxpayerIdentificationNum: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ companyAddress: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ companyPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ bankAccountName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ bankAccountNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ contactUserName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ contactUserPhone: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ maintainUserId: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
+ maintainTime: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+});
+const { searchForm, form, rules } = toRefs(data);
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+/** 鎻愪氦涓婁紶鏂囦欢 */
+function submitFileForm() {
+ upload.isUploading = true;
+ proxy.$refs["uploadRef"].submit();
+}
+const getList = () => {
+ tableLoading.value = true;
+ listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ });
+};
+const upload = reactive({
+ // 鏄惁鏄剧ず寮瑰嚭灞傦紙渚涘簲鍟嗗鍏ワ級
+ open: false,
+ // 寮瑰嚭灞傛爣棰橈紙渚涘簲鍟嗗鍏ワ級
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
+ updateSupport: 1,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
+});
+/** 瀵煎叆鎸夐挳鎿嶄綔 */
+function handleImport() {
+ upload.title = "渚涘簲鍟嗗鍏�";
+ upload.open = true;
+}
+/** 涓嬭浇妯℃澘 */
+function importTemplate() {
+ proxy.download("/system/supplier/downloadTemplate", {}, "渚涘簲鍟嗗鍏ユā鏉�.xlsx");
+}
+
+/**鏂囦欢涓婁紶涓鐞� */
+const handleFileUploadProgress = (event, file, fileList) => {
+ upload.isUploading = true;
+};
+
+/** 鏂囦欢涓婁紶鎴愬姛澶勭悊 */
+const handleFileSuccess = (response, file, fileList) => {
+ upload.isUploading = false;
+ if(response.code === 200){
+ proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
+ upload.open = false;
+ proxy.$refs["uploadRef"].clearFiles();
+ getList();
+ }else if(response.code === 500){
+ proxy.$modal.msgError(response.msg);
+ }else{
+ proxy.$modal.msgWarning(response.msg);
+ }
+};
+
+/** 鏂囦欢涓婁紶澶辫触澶勭悊 */
+const handleFileError = (error, file, fileList) => {
+ upload.isUploading = false;
+ proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ operationType.value = type;
+ form.value = {};
+ form.value.maintainUserId = userStore.id;
+ form.value.maintainTime = getCurrentDate();
+ userListNoPage().then((res) => {
+ userList.value = res.data;
+ });
+ if (type === "edit") {
+ getSupplier(row.id).then((res) => {
+ form.value = { ...res.data };
+ });
+ }
+ dialogFormVisible.value = true;
+};
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitEdit();
+ } else {
+ submitAdd();
+ }
+ }
+ });
+};
+// 鎻愪氦鏂板
+const submitAdd = () => {
+ addSupplier(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+};
+// 鎻愪氦淇敼
+const submitEdit = () => {
+ updateSupplier(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+};
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/system/supplier/export", {}, "渚涘簲鍟嗘。妗�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
+ const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
+ if (unauthorizedData.length > 0) {
+ proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
+ return;
+ }
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ delSupplier(ids)
+ .then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
+function getCurrentDate() {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
+ const day = String(today.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+}
+// 鎵撳紑闄勪欢寮规
+const openFilesFormDia = (row) => {
+ nextTick(() => {
+ filesDia.value?.openDialog(row)
+ })
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
+
diff --git a/src/views/basicData/supplierManage/filesDia.vue b/src/views/basicData/supplierManage/filesDia.vue
new file mode 100644
index 0000000..7943185
--- /dev/null
+++ b/src/views/basicData/supplierManage/filesDia.vue
@@ -0,0 +1,203 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ title="涓婁紶闄勪欢"
+ width="50%"
+ @close="closeDia"
+ >
+ <div style="margin-bottom: 10px;text-align: right">
+ <el-upload
+ v-model:file-list="fileList"
+ class="upload-demo"
+ :action="uploadUrl"
+ :on-success="handleUploadSuccess"
+ :on-error="handleUploadError"
+ name="file"
+ :show-file-list="false"
+ :headers="headers"
+ style="display: inline;margin-right: 10px"
+ >
+ <el-button type="primary">涓婁紶闄勪欢</el-button>
+ </el-upload>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :tableLoading="tableLoading"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ height="500"
+ @pagination-change="paginationSearch"
+ :total="total"
+ :page="page.current"
+ :limit="page.size"
+ >
+ </PIMTable>
+ <pagination
+ style="margin: 10px 0"
+ v-show="total > 0"
+ @pagination="paginationSearch"
+ :total="total"
+ :page="page.current"
+ :limit="page.size"
+ />
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <filePreview ref="filePreviewRef" />
+ </div>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {ElMessageBox} from "element-plus";
+import {getToken} from "@/utils/auth.js";
+import filePreview from '@/components/filePreview/index.vue'
+import {
+ fileAdd,
+ fileDel,
+ fileListPage
+} from "@/api/basicData/supplierManageFile.js";
+import Pagination from "@/components/PIMTable/Pagination.vue";
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false);
+const currentId = ref('')
+const selectedRows = ref([]);
+const filePreviewRef = ref()
+const tableColumn = ref([
+ {
+ label: "鏂囦欢鍚嶇О",
+ prop: "name",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ operation: [
+ {
+ name: "涓嬭浇",
+ type: "text",
+ clickFun: (row) => {
+ downLoadFile(row);
+ },
+ },
+ {
+ name: "棰勮",
+ type: "text",
+ clickFun: (row) => {
+ lookFile(row);
+ },
+ }
+ ],
+ },
+]);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+const tableData = ref([]);
+const fileList = ref([]);
+const tableLoading = ref(false);
+const headers = ref({
+ Authorization: "Bearer " + getToken(),
+});
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 涓婁紶鐨勫浘鐗囨湇鍔″櫒鍦板潃
+
+// 鎵撳紑寮规
+const openDialog = (row) => {
+ dialogFormVisible.value = true;
+ currentId.value = row.id;
+ getList()
+}
+const paginationSearch = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ fileListPage({supplierId: currentId.value, ...page}).then(res => {
+ tableData.value = res.data.records;
+ total.value = res.data.total;
+ })
+}
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ dialogFormVisible.value = false;
+ emit('close')
+};
+// 涓婁紶鎴愬姛澶勭悊
+function handleUploadSuccess(res, file) {
+ // 濡傛灉涓婁紶鎴愬姛
+ if (res.code == 200) {
+ const fileRow = {}
+ fileRow.name = res.data.originalName
+ fileRow.url = res.data.tempPath
+ uploadFile(fileRow)
+ } else {
+ proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+ }
+}
+function uploadFile(file) {
+ file.supplierId = currentId.value;
+ fileAdd(file).then(res => {
+ proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
+ getList()
+ })
+}
+// 涓婁紶澶辫触澶勭悊
+function handleUploadError() {
+ proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+}
+// 涓嬭浇闄勪欢
+const downLoadFile = (row) => {
+ proxy.$download.name(row.url);
+}
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ fileDel(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 棰勮闄勪欢
+const lookFile = (row) => {
+ filePreviewRef.value.open(row.url)
+}
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/basicData/supplierManage/index.vue b/src/views/basicData/supplierManage/index.vue
index 68c51d3..bb83fcf 100644
--- a/src/views/basicData/supplierManage/index.vue
+++ b/src/views/basicData/supplierManage/index.vue
@@ -1,528 +1,47 @@
+<!-- 鍦ㄤ綘鐨勪富椤甸潰涓� -->
<template>
<div class="app-container">
- <div class="search_form">
- <div>
- <span class="search_title">渚涘簲鍟嗘。妗堬細</span>
- <el-input
- v-model="searchForm.supplierName"
- style="width: 240px"
- placeholder="杈撳叆渚涘簲鍟嗗悕绉版悳绱�"
- @change="handleQuery"
- clearable
- :prefix-icon="Search"
- />
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
- >鎼滅储</el-button
- >
- </div>
- <div>
- <el-button type="primary" @click="openForm('add')"
- >鏂板渚涘簲鍟�</el-button
- >
- <el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button type="info" plain icon="Upload" @click="handleImport"
- >瀵煎叆</el-button
- >
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
- </div>
- </div>
- <div class="table_list">
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- ></PIMTable>
- </div>
- <el-dialog
- v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板渚涘簲鍟嗕俊鎭�' : '缂栬緫渚涘簲鍟嗕俊鎭�'"
- width="70%"
- @close="closeDia"
- >
- <el-form
- :model="form"
- label-width="140px"
- label-position="top"
- :rules="rules"
- ref="formRef"
- >
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierName">
- <el-input
- v-model="form.supplierName"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item
- label="绾崇◣浜鸿瘑鍒彿锛�"
- prop="taxpayerIdentificationNum"
- >
- <el-input
- v-model="form.taxpayerIdentificationNum"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鍏徃鍦板潃锛�" prop="companyAddress">
- <el-input
- v-model="form.companyAddress"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍏徃鐢佃瘽锛�" prop="companyPhone">
- <el-input
- v-model="form.companyPhone"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="寮�鎴疯锛�" prop="bankAccountName">
- <el-input
- v-model="form.bankAccountName"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="璐﹀彿锛�" prop="bankAccountNum">
- <el-input
- v-model="form.bankAccountNum"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鑱旂郴浜猴細" prop="contactUserName">
- <el-input
- v-model="form.contactUserName"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鑱旂郴鐢佃瘽锛�" prop="contactUserPhone">
- <el-input
- v-model="form.contactUserPhone"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="缁存姢浜猴細" prop="maintainUserId">
- <el-select
- v-model="form.maintainUserId"
- placeholder="璇烽�夋嫨"
- clearable
- disabled
- >
- <el-option
- v-for="item in userList"
- :key="item.nickName"
- :label="item.nickName"
- :value="item.userId"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="缁存姢鏃堕棿锛�" prop="maintainTime">
- <el-date-picker
- style="width: 100%"
- v-model="form.maintainTime"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- type="date"
- placeholder="璇烽�夋嫨"
- clearable
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
-
- <!-- 渚涘簲鍟嗗鍏ュ璇濇 -->
- <el-dialog
- :title="upload.title"
- v-model="upload.open"
- width="400px"
- append-to-body
- >
- <el-upload
- ref="uploadRef"
- :limit="1"
- accept=".xlsx, .xls"
- :headers="upload.headers"
- :action="upload.url + '?updateSupport=' + upload.updateSupport"
- :disabled="upload.isUploading"
- :on-progress="handleFileUploadProgress"
- :on-success="handleFileSuccess"
- :auto-upload="false"
- drag
- >
- <el-icon class="el-icon--upload"><upload-filled /></el-icon>
- <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
- <template #tip>
- <div class="el-upload__tip text-center">
- <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
- <!-- <el-link
- type="primary"
- :underline="false"
- style="font-size: 12px; vertical-align: baseline"
- @click="importTemplate"
- >涓嬭浇妯℃澘</el-link
- > -->
- </div>
- </template>
- </el-upload>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
- <el-button @click="upload.open = false">鍙� 娑�</el-button>
- </div>
- </template>
- </el-dialog>
+ <el-tabs v-model="activeTab" type="card">
+ <el-tab-pane label="姝e父渚涘簲鍟�" name="home">
+ <HomeTab />
+ </el-tab-pane>
+ <el-tab-pane label="榛戝悕鍗�" name="blacklist">
+ <BlacklistTab />
+ </el-tab-pane>
+ </el-tabs>
</div>
</template>
-<script setup>
-import { onMounted, ref } from "vue";
-import { Search } from "@element-plus/icons-vue";
-import { delSupplier } from "@/api/basicData/supplierManageFile.js";
-import { ElMessageBox } from "element-plus";
-import { userListNoPage } from "@/api/system/user.js";
-import {
- addSupplier,
- getSupplier,
- listSupplier,
- updateSupplier,
-} from "@/api/basicData/supplierManageFile.js";
-import useUserStore from "@/store/modules/user";
-import { getToken } from "@/utils/auth.js";
-const { proxy } = getCurrentInstance();
-const userStore = useUserStore();
+<script>
+import HomeTab from './components/HomeTab.vue'
+import BlacklistTab from './components/BlacklistTab.vue'
-const tableColumn = ref([
- {
- label: "渚涘簲鍟嗗悕绉�",
- prop: "supplierName",
- width: 250,
+export default {
+ name: 'MainPage',
+ components: {
+ HomeTab,
+ BlacklistTab
},
- {
- label: "绾崇◣浜鸿瘑鍒彿",
- prop: "taxpayerIdentificationNum",
- width: 230,
+ data() {
+ return {
+ activeTab: 'home'
+ }
},
- {
- label: "鍏徃鍦板潃",
- prop: "companyAddress",
- width: 220,
- },
- {
- label: "鑱旂郴鏂瑰紡",
- prop: "companyPhone",
- width:150
- },
- {
- label: "寮�鎴疯",
- prop: "bankAccountName",
- width: 220,
- },
- {
- label: "璐﹀彿",
- prop: "bankAccountNum",
- width: 220,
- },
- {
- label: "鑱旂郴浜�",
- prop: "contactUserName",
- },
- {
- label: "鑱旂郴鐢佃瘽",
- prop: "contactUserPhone",
- width: 150,
- },
- {
- label: "缁存姢浜�",
- prop: "maintainUserName",
- },
-
- {
- label: "缁存姢鏃堕棿",
- prop: "maintainTime",
- width:100
- },
- {
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
- fixed: 'right',
- operation: [
- {
- name: "缂栬緫",
- type: "text",
- clickFun: (row) => {
- openForm("edit", row);
- },
- disabled: (row) => {
- return row.maintainUserName !== userStore.nickName
- }
- },
- ],
- },
-]);
-const tableData = ref([]);
-const selectedRows = ref([]);
-const userList = ref([]);
-const tableLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
- total: 0,
-});
-
-// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
-const operationType = ref("");
-const dialogFormVisible = ref(false);
-const data = reactive({
- searchForm: {
- supplierName: "",
- },
- form: {
- supplierName: "",
- taxpayerIdentificationNum: "",
- companyAddress: "",
- companyPhone: "",
- bankAccountName: "",
- bankAccountNum: "",
- contactUserName: "",
- contactUserPhone: "",
- maintainUserId: "",
- maintainTime: "",
- },
- rules: {
- supplierName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- taxpayerIdentificationNum: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- companyAddress: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- companyPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- bankAccountName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- bankAccountNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- contactUserName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
- contactUserPhone: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
- maintainUserId: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
- maintainTime: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
- },
-});
-const { searchForm, form, rules } = toRefs(data);
-
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-/** 鎻愪氦涓婁紶鏂囦欢 */
-function submitFileForm() {
- console.log(upload.url + '?updateSupport=' + upload.updateSupport)
- proxy.$refs["uploadRef"].submit();
-}
-const getList = () => {
- tableLoading.value = true;
- listSupplier({ ...searchForm.value, ...page }).then((res) => {
- tableLoading.value = false;
- tableData.value = res.data.records;
- page.total = res.data.total;
- });
-};
-const upload = reactive({
- // 鏄惁鏄剧ず寮瑰嚭灞傦紙渚涘簲鍟嗗鍏ワ級
- open: false,
- // 寮瑰嚭灞傛爣棰橈紙渚涘簲鍟嗗鍏ワ級
- title: "",
- // 鏄惁绂佺敤涓婁紶
- isUploading: false,
- // 鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
- updateSupport: 1,
- // 璁剧疆涓婁紶鐨勮姹傚ご閮�
- headers: { Authorization: "Bearer " + getToken() },
- // 涓婁紶鐨勫湴鍧�
- url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
-});
-/** 瀵煎叆鎸夐挳鎿嶄綔 */
-function handleImport() {
- upload.title = "渚涘簲鍟嗗鍏�";
- upload.open = true;
-}
-
-/**鏂囦欢涓婁紶涓鐞� */
-const handleFileUploadProgress = (event, file, fileList) => {
- upload.isUploading = true;
-};
-
-/** 鏂囦欢涓婁紶鎴愬姛澶勭悊 */
-const handleFileSuccess = (response, file, fileList) => {
- upload.open = false;
- upload.isUploading = false;
- proxy.$refs["uploadRef"].handleRemove(file);
- getList();
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-// 鎵撳紑寮规
-const openForm = (type, row) => {
- operationType.value = type;
- form.value = {};
- form.value.maintainUserId = userStore.id;
- form.value.maintainTime = getCurrentDate();
- userListNoPage().then((res) => {
- userList.value = res.data;
- });
- if (type === "edit") {
- getSupplier(row.id).then((res) => {
- form.value = { ...res.data };
- });
- }
- dialogFormVisible.value = true;
-};
-// 鎻愪氦琛ㄥ崟
-const submitForm = () => {
- proxy.$refs["formRef"].validate((valid) => {
- if (valid) {
- if (operationType.value === "edit") {
- submitEdit();
- } else {
- submitAdd();
+ watch: {
+ activeTab(newVal) {
+ if (newVal === 'home') {
+ this.$refs.homeTab && this.$refs.homeTab.getList()
+ } else if (newVal === 'blacklist') {
+ this.$refs.blacklistTab && this.$refs.blacklistTab.getList()
}
}
- });
-};
-// 鎻愪氦鏂板
-const submitAdd = () => {
- addSupplier(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
-};
-// 鎻愪氦淇敼
-const submitEdit = () => {
- updateSupplier(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
-};
-// 鍏抽棴寮规
-const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
-};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/system/supplier/export", {}, "渚涘簲鍟嗘。妗�.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-// 鍒犻櫎
-const handleDelete = () => {
- let ids = [];
- if (selectedRows.value.length > 0) {
- // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
- const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
- if (unauthorizedData.length > 0) {
- proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
- return;
- }
- ids = selectedRows.value.map((item) => item.id);
- } else {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
}
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- tableLoading.value = true;
- delSupplier(ids)
- .then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- })
- .finally(() => {
- tableLoading.value = false;
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
+}
+</script>
+<style>
+.main-container :deep(.el-tabs__item.is-active) {
+ color: #1883f6 !important;
+ border-bottom: 2px solid #409EFF;
}
-onMounted(() => {
- getList();
-});
-</script>
-
-<style scoped lang="scss"></style>
+</style>
diff --git a/src/views/chatHome/chatHomeIndex/MobileChat.vue b/src/views/chatHome/chatHomeIndex/MobileChat.vue
index 5b06e76..adeb5e7 100644
--- a/src/views/chatHome/chatHomeIndex/MobileChat.vue
+++ b/src/views/chatHome/chatHomeIndex/MobileChat.vue
@@ -14,7 +14,7 @@
<span class="flash_cursor"></span>
</template>
<template v-else>
- <pre>{{ item.msg }}</pre>
+ <pre style="font-family: none;">{{ item.msg }}</pre>
</template>
</div>
<div class="chat-img" v-if="item.chatType == 1">
@@ -140,7 +140,7 @@
}
chatList.value.push(chatMsg)
let chatGPT = {
- headImg: headPortrait,
+ headImg: chatGPTHeadImg,
name: '灏忔櫤',
time: new Date().toLocaleTimeString(),
msg: "",
@@ -185,7 +185,7 @@
uid: '1002'
})
chatList.value.push({
- headImg: chatGPTHeadImg,
+ headImg: headPortrait,
name: '鍗ч緳',
time: new Date().toLocaleTimeString(),
msg: route.query.keyWord,
diff --git a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
index c38ee89..167a921 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -178,7 +178,7 @@
</template>
<script setup>
-import { computed, getCurrentInstance, reactive, ref, toRefs } from "vue";
+import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue";
import {
approveProcessDetails,
getDept,
@@ -255,7 +255,31 @@
userList.value = res.data;
});
form.value = {...row}
- getProductOptions()
+ // 绔嬪嵆娓呴櫎琛ㄥ崟楠岃瘉鐘舵�侊紙鍥犱负瀛楁鏄痙isabled鐨勶紝涓嶉渶瑕侀獙璇侊級
+ nextTick(() => {
+ if (formRef.value) {
+ formRef.value.clearValidate();
+ }
+ });
+ // 纭繚閫夐」鍔犺浇瀹屾垚鍚庡啀鍖归厤鍊肩被鍨�
+ getProductOptions().then(() => {
+ // 纭繚鍊肩被鍨嬪尮閰嶏紙濡傛灉閫夐」宸插姞杞斤級
+ if (productOptions.value.length > 0 && form.value.approveDeptId) {
+ const matchedOption = productOptions.value.find(opt =>
+ opt.deptId == form.value.approveDeptId ||
+ String(opt.deptId) === String(form.value.approveDeptId)
+ );
+ if (matchedOption) {
+ form.value.approveDeptId = matchedOption.deptId;
+ }
+ }
+ // 鍐嶆娓呴櫎楠岃瘉锛岀‘淇濋�夐」鍔犺浇鍚庡�煎尮閰嶆纭�
+ nextTick(() => {
+ if (formRef.value) {
+ formRef.value.clearValidate();
+ }
+ });
+ });
// 鎶ヤ环瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨勨�滄姤浠峰崟鍙封�濆幓鏌ユ姤浠峰垪琛�
if (isQuotationApproval.value) {
@@ -291,17 +315,26 @@
})
}
const getProductOptions = () => {
- getDept().then((res) => {
+ return getDept().then((res) => {
productOptions.value = res.data;
});
};
// 鎻愪氦瀹℃壒
const submitForm = (status) => {
const filteredActivities = activities.value.filter(activity => activity.isShen);
- filteredActivities[0].approveNodeStatus = status;
+ if (!filteredActivities || filteredActivities.length === 0) {
+ proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
+ return;
+ }
+ const currentActivity = filteredActivities[0];
+ if (!currentActivity) {
+ proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
+ return;
+ }
+ currentActivity.approveNodeStatus = status;
// 鍒ゆ柇鏄惁涓烘渶鍚庝竴姝�
const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1;
- updateApproveNode({ ...filteredActivities[0], isLast }).then(() => {
+ updateApproveNode({ ...currentActivity, isLast }).then(() => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
});
@@ -350,4 +383,4 @@
width: 200px;
height: 60px;
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
index 4c9fce3..67a635c 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -16,8 +16,7 @@
</el-row>
<el-row>
<el-col :span="24">
- <!-- 鐢宠閮ㄩ棬锛氭牎楠屼娇鐢ㄩ儴闂↖D锛屼究浜庝笅鎷夐�夋嫨鍚庣珛鍗抽�氳繃鏍¢獙 -->
- <el-form-item label="鐢宠閮ㄩ棬锛�" prop="approveDeptId">
+ <el-form-item label="鐢宠閮ㄩ棬锛�" prop="approveDeptName">
<!-- <el-input v-model="form.approveDeptName" placeholder="璇疯緭鍏�" clearable/>-->
<el-select
v-model="form.approveDeptId"
@@ -89,10 +88,10 @@
<!-- 鍑哄樊鍦扮偣锛堜粎褰� approveType 涓� 3 鏃舵樉绀猴級 -->
<el-row v-if="props.approveType == 3">
<el-col :span="24">
- <el-form-item label="澶囨敞锛�" prop="location">
+ <el-form-item label="鍑哄樊鍦扮偣锛�" prop="location">
<el-input
v-model="form.location"
- placeholder="璇疯緭鍏ュ娉�"
+ placeholder="璇疯緭鍏ュ嚭宸湴鐐�"
clearable
/>
</el-form-item>
@@ -235,7 +234,7 @@
approveTime: "",
approveId: "",
approveUser: "",
- approveDeptId: "",
+ approveDeptId: "",
approveDeptName: "",
approveReason: "",
checkResult: "",
@@ -247,11 +246,10 @@
location: "" // 鍑哄樊鍦扮偣
},
rules: {
- approveTime: [{ required: false, message: "璇疯緭鍏�", trigger: "change" }],
+ approveTime: [{ required: false, message: "璇疯緭鍏�", trigger: "change" },],
approveId: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
approveUser: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
- // 浣跨敤閮ㄩ棬ID鍋氬繀濉牎楠岋紝閬垮厤鍚嶇О鏈悓姝ュ鑷磋鎶�
- approveDeptId: [{ required: true, message: "璇烽�夋嫨鐢宠閮ㄩ棬", trigger: "change" }],
+ approveDeptName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
approveReason: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
checkResult: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
startDate: [{ required: true, message: "璇烽�夋嫨璇峰亣寮�濮嬫椂闂�", trigger: "change" }],
@@ -300,7 +298,6 @@
userListNoPageByTenantId().then((res) => {
userList.value = res.data;
});
- getProductOptions();
form.value = {}
approverNodes.value = [
{ id: 1, userId: null }
@@ -310,6 +307,9 @@
// 鑾峰彇褰撳墠鐢ㄦ埛淇℃伅骞惰缃儴闂↖D
form.value.approveDeptId = userStore.currentDeptId
+
+ // 鍔犺浇閮ㄩ棬閫夐」锛屽苟鍦ㄥ姞杞藉畬鎴愬悗璁剧疆閮ㄩ棬鍚嶇О
+ getProductOptions();
if (operationType.value === 'edit') {
fileList.value = row.commonFileList
form.value.tempFileIds = fileList.value.map(file => file.id)
@@ -332,8 +332,18 @@
}
}
const getProductOptions = () => {
- getDept().then((res) => {
+ return getDept().then((res) => {
productOptions.value = res.data;
+ // 濡傛灉宸叉湁閮ㄩ棬ID锛岃嚜鍔ㄨ缃儴闂ㄥ悕绉帮紙鐢ㄤ簬楠岃瘉锛�
+ if (form.value.approveDeptId && productOptions.value.length > 0) {
+ const matchedDept = productOptions.value.find(dept =>
+ dept.deptId == form.value.approveDeptId ||
+ String(dept.deptId) === String(form.value.approveDeptId)
+ );
+ if (matchedDept) {
+ form.value.approveDeptName = matchedDept.deptName;
+ }
+ }
});
};
function convertIdToValue(data) {
diff --git a/src/views/collaborativeApproval/approvalProcess/index.vue b/src/views/collaborativeApproval/approvalProcess/index.vue
index afc2529..00e59c9 100644
--- a/src/views/collaborativeApproval/approvalProcess/index.vue
+++ b/src/views/collaborativeApproval/approvalProcess/index.vue
@@ -2,13 +2,13 @@
<div class="app-container">
<!-- 鏍囩椤靛垏鎹笉鍚岀殑瀹℃壒绫诲瀷 -->
<el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs">
- <el-tab-pane label="鍗忓悓瀹℃壒" name="1"></el-tab-pane>
+ <el-tab-pane label="鍏嚭绠$悊" name="1"></el-tab-pane>
<el-tab-pane label="璇峰亣绠$悊" name="2"></el-tab-pane>
- <el-tab-pane label="閿�鍞鎵�" name="3"></el-tab-pane>
- <el-tab-pane label="鎶ラ攢瀹℃壒" name="4"></el-tab-pane>
+ <el-tab-pane label="鍑哄樊绠$悊" name="3"></el-tab-pane>
+ <el-tab-pane label="鎶ラ攢绠$悊" name="4"></el-tab-pane>
<el-tab-pane label="閲囪喘瀹℃壒" name="5"></el-tab-pane>
<el-tab-pane label="鎶ヤ环瀹℃壒" name="6"></el-tab-pane>
- <el-tab-pane label="鍑哄簱瀹℃壒" name="7"></el-tab-pane>
+ <el-tab-pane label="鍙戣揣瀹℃壒" name="7"></el-tab-pane>
</el-tabs>
<div class="search_form">
@@ -35,9 +35,18 @@
>
</div>
<div>
- <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 6">鏂板</el-button>
+ <el-button
+ type="primary"
+ @click="openForm('add')"
+ v-if="currentApproveType !== 6 && currentApproveType !== 7"
+ >鏂板</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ <el-button
+ type="danger"
+ plain
+ @click="handleDelete"
+ v-if="currentApproveType !== 7"
+ >鍒犻櫎</el-button>
</div>
</div>
<div class="table_list">
@@ -205,7 +214,12 @@
clickFun: (row) => {
openForm("edit", row);
},
- disabled: (row) => currentApproveType.value === 6 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
+ disabled: (row) =>
+ currentApproveType.value === 6 ||
+ currentApproveType.value === 7 ||
+ row.approveStatus == 2 ||
+ row.approveStatus == 1 ||
+ row.approveStatus == 4
},
{
name: "瀹℃牳",
@@ -294,7 +308,7 @@
4: "鎶ラ攢绠$悊瀹℃壒琛�",
5: "閲囪喘鐢宠瀹℃壒琛�",
6: "鎶ヤ环瀹℃壒琛�",
- 7: "鍑哄簱瀹℃壒琛�",
+ 7: "鍙戣揣瀹℃壒琛�",
}
const fileName = nameMap[type] || nameMap[0]
proxy.download(url, {}, `${fileName}.xlsx`)
diff --git a/src/views/collaborativeApproval/enterpriseBook/index.vue b/src/views/collaborativeApproval/enterpriseBook/index.vue
index 0b33a32..fa9fa5c 100644
--- a/src/views/collaborativeApproval/enterpriseBook/index.vue
+++ b/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 => {})
diff --git a/src/views/collaborativeApproval/noticeManagement/index.vue b/src/views/collaborativeApproval/noticeManagement/index.vue
index d92adea..0957882 100644
--- a/src/views/collaborativeApproval/noticeManagement/index.vue
+++ b/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) => {
diff --git a/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue b/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
index 7781593..ed2dafa 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
+++ b/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>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue b/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
index 83747a0..157b6b5 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
+++ b/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()
})
diff --git a/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue b/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
index 2351293..a85de7f 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
+++ b/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()
})
diff --git a/src/views/collaborativeApproval/notificationManagement/summary/index.vue b/src/views/collaborativeApproval/notificationManagement/summary/index.vue
index a00316d..f94e548 100644
--- a/src/views/collaborativeApproval/notificationManagement/summary/index.vue
+++ b/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()
})
diff --git a/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue b/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
index b0f8a90..1c71cdc 100644
--- a/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
+++ b/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -1,100 +1,166 @@
<template>
<div class="app-container">
-
- <!-- 瑙勭珷鍒跺害绠$悊-->
- <el-card class="box-card">
- <template #header>
- <div class="card-header">
- <span>瑙勭珷鍒跺害鍙戝竷</span>
- </div>
+ <!-- 瑙勭珷鍒跺害绠$悊-->
+ <el-card class="box-card">
+ <template #header>
+ <div class="card-header">
+ <span>瑙勭珷鍒跺害鍙戝竷</span>
+ </div>
+ </template>
+ <div class="tab-content">
+ <el-row :gutter="20"
+ class="mb-20">
+ <span class="ml-10">鍒跺害鏍囬锛�</span>
+ <el-col :span="6">
+ <el-input v-model="regulationSearchForm.title"
+ placeholder="璇疯緭鍏ュ埗搴︽爣棰�"
+ clearable />
+ </el-col>
+ <span class="search_title">鍒跺害鍒嗙被锛�</span>
+ <el-col :span="4">
+ <el-select v-model="regulationSearchForm.category"
+ placeholder="鍒跺害鍒嗙被"
+ clearable>
+ <el-option label="浜轰簨鍒跺害"
+ value="hr" />
+ <el-option label="璐㈠姟鍒跺害"
+ value="finance" />
+ <el-option label="瀹夊叏鍒跺害"
+ value="safety" />
+ <el-option label="鎶�鏈埗搴�"
+ value="tech" />
+ </el-select>
+ </el-col>
+ <el-col :span="8">
+ <el-button type="primary"
+ @click="searchRegulations">鎼滅储</el-button>
+ <el-button @click="resetRegulationSearch">閲嶇疆</el-button>
+ <el-button @click="handleExport">瀵煎嚭</el-button>
+ <el-button type="success"
+ @click="handleAdd">
+ 鍙戝竷鍒跺害
+ </el-button>
+ </el-col>
+ </el-row>
+ <el-table :data="regulations"
+ border
+ v-loading="tableLoading"
+ style="width: 100%">
+ <el-table-column prop="regulationNum"
+ label="鍒跺害缂栧彿"
+ width="120" />
+ <el-table-column prop="title"
+ label="鍒跺害鏍囬"
+ min-width="150" />
+ <el-table-column prop="category"
+ label="鍒嗙被"
+ width="120">
+ <template #default="scope">
+ <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
</template>
- <div class="tab-content">
- <el-row :gutter="20" class="mb-20">
- <span class="ml-10">鍒跺害鏍囬锛�</span>
- <el-col :span="6">
- <el-input v-model="regulationSearchForm.title" placeholder="璇疯緭鍏ュ埗搴︽爣棰�" clearable />
- </el-col>
- <span class="search_title">鍒跺害鍒嗙被锛�</span>
- <el-col :span="4">
- <el-select v-model="regulationSearchForm.category" placeholder="鍒跺害鍒嗙被" clearable>
- <el-option label="浜轰簨鍒跺害" value="hr" />
- <el-option label="璐㈠姟鍒跺害" value="finance" />
- <el-option label="瀹夊叏鍒跺害" value="safety" />
- <el-option label="鎶�鏈埗搴�" value="tech" />
- </el-select>
- </el-col>
- <el-col :span="8">
- <el-button type="primary" @click="searchRegulations">鎼滅储</el-button>
- <el-button @click="resetRegulationSearch">閲嶇疆</el-button>
- <el-button @click="handleExport">瀵煎嚭</el-button>
- <el-button type="success" @click="handleAdd">
- 鍙戝竷鍒跺害
- </el-button>
- </el-col>
- </el-row>
-
- <el-table :data="regulations" border v-loading="tableLoading" style="width: 100%">
- <el-table-column prop="regulationNum" label="鍒跺害缂栧彿" width="120" />
- <el-table-column prop="title" label="鍒跺害鏍囬" min-width="150" />
- <el-table-column prop="category" label="鍒嗙被" width="120">
- <template #default="scope">
- <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="version" label="鐗堟湰" width="120" />
- <el-table-column prop="createUserName" label="鍙戝竷浜�" width="120" />
- <el-table-column prop="createTime" label="鍙戝竷鏃堕棿" width="180" />
- <el-table-column prop="status" label="鐘舵��" width="100">
- <template #default="scope">
- <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
- {{ scope.row.status === 'active' ? '鐢熸晥涓�' : '宸插簾姝�' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="readCount" label="宸茶浜烘暟" width="100" />
- <el-table-column label="鎿嶄綔" width="320" fixed="right">
- <template #default="scope">
- <el-button link @click="viewRegulation(scope.row)">鏌ョ湅</el-button>
- <el-button link type="primary" @click="handleEdit(scope.row)">缂栬緫</el-button>
- <el-button link type="danger" @click="repealEdit(scope.row)">搴熷純</el-button>
- <el-button link type="success" @click="viewVersionHistory(scope.row)">鐗堟湰鍘嗗彶</el-button>
- <el-button link type="warning" @click="viewReadStatus(scope.row)">闃呰鐘舵��</el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </el-card>
-
+ </el-table-column>
+ <el-table-column prop="version"
+ label="鐗堟湰"
+ width="120" />
+ <el-table-column prop="createUserName"
+ label="鍙戝竷浜�"
+ width="120" />
+ <el-table-column prop="createTime"
+ label="鍙戝竷鏃堕棿"
+ width="180" />
+ <el-table-column prop="status"
+ label="鐘舵��"
+ width="100">
+ <template #default="scope">
+ <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
+ {{ scope.row.status === 'active' ? '鐢熸晥涓�' : '宸插簾姝�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="readCount"
+ label="宸茶浜烘暟"
+ width="100" />
+ <el-table-column label="鎿嶄綔"
+ width="320"
+ fixed="right">
+ <template #default="scope">
+ <el-button link
+ @click="viewRegulation(scope.row)">鏌ョ湅</el-button>
+ <el-button link
+ type="primary"
+ @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button link
+ type="danger"
+ @click="repealEdit(scope.row)">搴熷純</el-button>
+ <el-button link
+ type="success"
+ @click="viewVersionHistory(scope.row)">鐗堟湰鍘嗗彶</el-button>
+ <!-- <el-button link type="warning" @click="viewReadStatus(scope.row)">闃呰鐘舵��</el-button> -->
+ <el-button link
+ type="primary"
+ @click="openFileDialog(scope.row)">闄勪欢</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </el-card>
<!-- 鐢ㄥ嵃鐢宠瀵硅瘽妗嗭紙宸茬Щ闄わ級 -->
-
<!-- 瑙勭珷鍒跺害鍙戝竷瀵硅瘽妗� -->
- <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '鍙戝竷鍒跺害' : '缂栬緫鍒跺害'" width="800px">
- <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px">
- <el-form-item label="鍒跺害缂栧彿" prop="regulationNum">
- <el-input v-model="regulationForm.regulationNum" placeholder="璇疯緭鍏ュ埗搴︾紪鍙�" />
+ <el-dialog v-model="showRegulationDialog"
+ :title="operationType === 'add' ? '鍙戝竷鍒跺害' : '缂栬緫鍒跺害'"
+ width="800px">
+ <el-form :model="regulationForm"
+ :rules="regulationRules"
+ ref="regulationFormRef"
+ label-width="100px">
+ <el-form-item label="鍒跺害缂栧彿"
+ prop="regulationNum">
+ <el-input v-model="regulationForm.regulationNum"
+ placeholder="璇疯緭鍏ュ埗搴︾紪鍙�" />
</el-form-item>
- <el-form-item label="鍒跺害鏍囬" prop="title">
- <el-input v-model="regulationForm.title" placeholder="璇疯緭鍏ュ埗搴︽爣棰�" />
+ <el-form-item label="鍒跺害鏍囬"
+ prop="title">
+ <el-input v-model="regulationForm.title"
+ placeholder="璇疯緭鍏ュ埗搴︽爣棰�" />
</el-form-item>
- <el-form-item label="鍒跺害鍒嗙被" prop="category">
- <el-select v-model="regulationForm.category" placeholder="璇烽�夋嫨鍒跺害鍒嗙被" style="width: 100%">
- <el-option label="浜轰簨鍒跺害" value="hr" />
- <el-option label="璐㈠姟鍒跺害" value="finance" />
- <el-option label="瀹夊叏鍒跺害" value="safety" />
- <el-option label="鎶�鏈埗搴�" value="tech" />
+ <el-form-item label="鍒跺害鍒嗙被"
+ prop="category">
+ <el-select v-model="regulationForm.category"
+ placeholder="璇烽�夋嫨鍒跺害鍒嗙被"
+ style="width: 100%">
+ <el-option label="浜轰簨鍒跺害"
+ value="hr" />
+ <el-option label="璐㈠姟鍒跺害"
+ value="finance" />
+ <el-option label="瀹夊叏鍒跺害"
+ value="safety" />
+ <el-option label="鎶�鏈埗搴�"
+ value="tech" />
</el-select>
</el-form-item>
- <el-form-item label="鍒跺害鍐呭" prop="content">
- <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="璇疯緭鍏ュ埗搴﹁缁嗗唴瀹�" />
+ <el-form-item label="鍒跺害鍐呭"
+ prop="content">
+ <el-input v-model="regulationForm.content"
+ type="textarea"
+ :rows="10"
+ placeholder="璇疯緭鍏ュ埗搴﹁缁嗗唴瀹�" />
</el-form-item>
- <el-form-item label="鍒跺害鐗堟湰" prop="version">
- <el-input v-model="regulationForm.version" placeholder="璇疯緭鍏ュ埗搴︾増鏈�" />
+ <el-form-item label="鍒跺害鐗堟湰"
+ prop="version">
+ <el-input v-model="regulationForm.version"
+ placeholder="璇疯緭鍏ュ埗搴︾増鏈�" />
</el-form-item>
- <el-form-item label="鐢熸晥鏃堕棿" prop="effectiveTime">
- <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss" placeholder="閫夋嫨鐢熸晥鏃堕棿" style="width: 100%" />
+ <el-form-item label="鐢熸晥鏃堕棿"
+ prop="effectiveTime">
+ <el-date-picker v-model="regulationForm.effectiveTime"
+ type="datetime"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ placeholder="閫夋嫨鐢熸晥鏃堕棿"
+ style="width: 100%" />
</el-form-item>
- <el-form-item label="閫傜敤鑼冨洿" prop="scope">
+ <el-form-item label="閫傜敤鑼冨洿"
+ prop="scope">
<el-checkbox-group v-model="regulationForm.scope">
<el-checkbox label="all">鍏ㄤ綋鍛樺伐</el-checkbox>
<el-checkbox label="manager">绠$悊灞�</el-checkbox>
@@ -103,7 +169,8 @@
<el-checkbox label="tech">鎶�鏈儴闂�</el-checkbox>
</el-checkbox-group>
</el-form-item>
- <el-form-item label="鏄惁闇�瑕佺‘璁�" prop="requireConfirm">
+ <el-form-item label="鏄惁闇�瑕佺‘璁�"
+ prop="requireConfirm">
<el-switch v-model="regulationForm.requireConfirm" />
<span class="ml-10">寮�鍚悗鍛樺伐闇�瑕侀槄璇荤‘璁�</span>
</el-form-item>
@@ -111,17 +178,19 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="showRegulationDialog = false">鍙栨秷</el-button>
- <el-button type="primary" @click="submitRegulation">鍙戝竷鍒跺害</el-button>
+ <el-button type="primary"
+ @click="submitRegulation">鍙戝竷鍒跺害</el-button>
</span>
</template>
</el-dialog>
-
<!-- 鐢ㄥ嵃璇︽儏瀵硅瘽妗嗭紙宸茬Щ闄わ級 -->
-
<!-- 瑙勭珷鍒跺害璇︽儏瀵硅瘽妗� -->
- <el-dialog v-model="showRegulationDetailDialog" title="瑙勭珷鍒跺害璇︽儏" width="800px">
+ <el-dialog v-model="showRegulationDetailDialog"
+ title="瑙勭珷鍒跺害璇︽儏"
+ width="800px">
<div v-if="currentRegulationDetail">
- <el-descriptions :column="2" border>
+ <el-descriptions :column="2"
+ border>
<el-descriptions-item label="鍒跺害缂栧彿">{{ currentRegulationDetail.id }}</el-descriptions-item>
<el-descriptions-item label="鍒跺害鏍囬">{{ currentRegulationDetail.title }}</el-descriptions-item>
<el-descriptions-item label="鍒嗙被">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item>
@@ -134,19 +203,30 @@
<div class="regulation-content">{{ currentRegulationDetail.content }}</div>
</div>
<!-- 濡傛灉tableData>0 鏄剧ず -->
- <div style="margin: 10px 0;" v-if="tableData && tableData.length > 0" >
- <el-button type="success" @click="resetForm(currentRegulationDetail)">纭鏌ョ湅</el-button>
+ <div style="margin: 10px 0;"
+ v-if="tableData && tableData.length > 0">
+ <el-button type="success"
+ @click="resetForm(currentRegulationDetail)">纭鏌ョ湅</el-button>
</div>
</div>
</el-dialog>
-
<!-- 鐗堟湰鍘嗗彶瀵硅瘽妗� -->
- <el-dialog v-model="showVersionHistoryDialog" title="鐗堟湰鍘嗗彶" width="800px">
- <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px">
- <el-table-column prop="version" label="鐗堟湰鍙�" width="100" />
- <el-table-column prop="updateTime" label="鏇存柊鏃堕棿" width="180" />
- <el-table-column prop="createUserName" label="鏇存柊浜�" width="120" />
- <el-table-column prop="changeLog" label="鍙樻洿璇存槑">
+ <el-dialog v-model="showVersionHistoryDialog"
+ title="鐗堟湰鍘嗗彶"
+ width="800px">
+ <el-table :data="versionHistory"
+ style="width: 100%;margin-bottom: 10px">
+ <el-table-column prop="version"
+ label="鐗堟湰鍙�"
+ width="100" />
+ <el-table-column prop="updateTime"
+ label="鏇存柊鏃堕棿"
+ width="180" />
+ <el-table-column prop="createUserName"
+ label="鏇存柊浜�"
+ width="120" />
+ <el-table-column prop="changeLog"
+ label="鍙樻洿璇存槑">
<template #default="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
{{ scope.row.status === 'active' ? '鐢熸晥涓�' : '宸插簾姝�' }}
@@ -155,15 +235,27 @@
</el-table-column>
</el-table>
</el-dialog>
-
<!-- 闃呰鐘舵�佸璇濇 -->
- <el-dialog v-model="showReadStatusDialog" title="闃呰鐘舵��" width="800px">
- <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px">
- <el-table-column prop="employee" label="鍛樺伐濮撳悕" width="120" />
- <el-table-column prop="department" label="鎵�灞為儴闂�" width="150" />
- <el-table-column prop="createTime" label="闃呰鏃堕棿" width="180" />
- <el-table-column prop="confirmTime" label="纭鏃堕棿" width="180" />
- <el-table-column prop="status" label="鐘舵��" width="100">
+ <el-dialog v-model="showReadStatusDialog"
+ title="闃呰鐘舵��"
+ width="800px">
+ <el-table :data="readStatusList"
+ style="width: 100%;margin-bottom: 10px">
+ <el-table-column prop="employee"
+ label="鍛樺伐濮撳悕"
+ width="120" />
+ <el-table-column prop="department"
+ label="鎵�灞為儴闂�"
+ width="150" />
+ <el-table-column prop="createTime"
+ label="闃呰鏃堕棿"
+ width="180" />
+ <el-table-column prop="confirmTime"
+ label="纭鏃堕棿"
+ width="180" />
+ <el-table-column prop="status"
+ label="鐘舵��"
+ width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'">
{{ scope.row.status === 'confirmed' ? '宸茬‘璁�' : '鏈‘璁�' }}
@@ -172,333 +264,437 @@
</el-table-column>
</el-table>
</el-dialog>
-
+ <FileListDialog ref="fileListDialogRef"
+ v-model="fileDialogVisible"
+ :show-upload-button="true"
+ :show-delete-button="true"
+ :delete-method="handleAttachmentDelete"
+ :rules-regulations-management-id="currentFileRuleId"
+ :name-column-label="'闄勪欢鍚嶇О'"
+ @upload="handleAttachmentUpload" />
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,addReadingStatus,updateReadingStatus } from '@/api/collaborativeApproval/sealManagement.js'
+ import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import {
+ listRuleManagement,
+ addRuleManagement,
+ updateRuleManagement,
+ delRuleManagement,
+ getReadingStatusByRuleId,
+ addReadingStatus,
+ updateReadingStatus,
+ } from "@/api/collaborativeApproval/sealManagement.js";
+ import FileListDialog from "@/components/Dialog/FileListDialog.vue";
+ import {
+ listRuleFiles,
+ delRuleFile,
+ addRuleFile,
+ } from "@/api/collaborativeApproval/rulesRegulationsManagementFile.js";
-// 鍝嶅簲寮忔暟鎹�
-const operationType = ref('add')
-const tableData = ref([])
-const tableLoading = ref(false)
-// 鍒嗛〉鍙傛暟
-const page = reactive({
- current: 1,
- size: 10,
- total: 0
-})
-// 瑙勭珷鍒跺害鐩稿叧
-const showRegulationDialog = ref(false)
-const showRegulationDetailDialog = ref(false)
-const showVersionHistoryDialog = ref(false)
-const showReadStatusDialog = ref(false)
-const currentRegulationDetail = ref(null)
-const regulationFormRef = ref()
-const regulationForm = reactive({
- id: '',
- regulationNum: '',
- title: '',
- category: '',
- content: '',
- version: '',
- status: 'active',
- readCount: 0,
- effectiveTime: '',
- scope: [],
- requireConfirm: false
-})
+ // 鍝嶅簲寮忔暟鎹�
+ const operationType = ref("add");
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ // 鍒嗛〉鍙傛暟
+ const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+ });
+ // 闄勪欢寮圭獥
+ const fileDialogVisible = ref(false);
+ const fileListDialogRef = ref(null);
+ const currentFileRuleId = ref(null);
+ const filePage = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+ });
+ // 瑙勭珷鍒跺害鐩稿叧
+ const showRegulationDialog = ref(false);
+ const showRegulationDetailDialog = ref(false);
+ const showVersionHistoryDialog = ref(false);
+ const showReadStatusDialog = ref(false);
+ const currentRegulationDetail = ref(null);
+ const regulationFormRef = ref();
+ const regulationForm = reactive({
+ id: "",
+ regulationNum: "",
+ title: "",
+ category: "",
+ content: "",
+ version: "",
+ status: "active",
+ readCount: 0,
+ effectiveTime: "",
+ scope: [],
+ requireConfirm: false,
+ });
-const readStatus = ref({
- id: '',
- ruleId: '',
- employee: '',
- department: '',
- createTime: '',
- confirmTime: '',
- status: 'unconfirmed'
-})
+ const readStatus = ref({
+ id: "",
+ ruleId: "",
+ employee: "",
+ department: "",
+ createTime: "",
+ confirmTime: "",
+ status: "unconfirmed",
+ });
-const regulationRules = {
- title: [{ required: true, message: '璇疯緭鍏ュ埗搴︽爣棰�', trigger: 'blur' }],
- category: [{ required: true, message: '璇烽�夋嫨鍒跺害鍒嗙被', trigger: 'change' }],
- content: [{ required: true, message: '璇疯緭鍏ュ埗搴﹀唴瀹�', trigger: 'blur' }],
- effectiveTime: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃堕棿', trigger: 'change' }],
- scope: [{ required: true, message: '璇烽�夋嫨閫傜敤鑼冨洿', trigger: 'change' }]
-}
+ const regulationRules = {
+ title: [{ required: true, message: "璇疯緭鍏ュ埗搴︽爣棰�", trigger: "blur" }],
+ category: [{ required: true, message: "璇烽�夋嫨鍒跺害鍒嗙被", trigger: "change" }],
+ content: [{ required: true, message: "璇疯緭鍏ュ埗搴﹀唴瀹�", trigger: "blur" }],
+ effectiveTime: [
+ { required: true, message: "璇烽�夋嫨鐢熸晥鏃堕棿", trigger: "change" },
+ ],
+ scope: [{ required: true, message: "璇烽�夋嫨閫傜敤鑼冨洿", trigger: "change" }],
+ };
-const regulationSearchForm = reactive({
- title: '',
- category: ''
-})
+ const regulationSearchForm = reactive({
+ title: "",
+ category: "",
+ });
-const regulations = ref([])
+ const regulations = ref([]);
-const versionHistory = ref([])
+ const versionHistory = ref([]);
-const readStatusList = ref([])
+ const readStatusList = ref([]);
// { employee: '闄堝織寮�', department: '閿�鍞儴', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' },
// { employee: '鍒橀泤濠�', department: '鎶�鏈儴', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' },
// { employee: '鐜嬪缓鍥�', department: '璐㈠姟閮�', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' }
-// 鍒跺害鍒嗙被
-const getCategoryText = (category) => {
- const categoryMap = {
- hr: '浜轰簨鍒跺害',
- finance: '璐㈠姟鍒跺害',
- safety: '瀹夊叏鍒跺害',
- tech: '鎶�鏈埗搴�'
- }
- return categoryMap[category] || '鏈煡'
-}
-// 鎼滅储鍒跺害
-const searchRegulations = () => {
- page.current=1
- getRegulationList()
-}
-// 閲嶇疆鍒跺害鎼滅储
-const resetRegulationSearch = () => {
- regulationSearchForm.title = ''
- regulationSearchForm.category = ''
- searchRegulations()
-}
-// 鏂板
-const handleAdd = () => {
- operationType.value = 'add'
- resetRegulationForm()
- showRegulationDialog.value = true
-}
+ // 鍒跺害鍒嗙被
+ const getCategoryText = category => {
+ const categoryMap = {
+ hr: "浜轰簨鍒跺害",
+ finance: "璐㈠姟鍒跺害",
+ safety: "瀹夊叏鍒跺害",
+ tech: "鎶�鏈埗搴�",
+ };
+ return categoryMap[category] || "鏈煡";
+ };
+ // 鎼滅储鍒跺害
+ const searchRegulations = () => {
+ page.current = 1;
+ getRegulationList();
+ };
+ // 閲嶇疆鍒跺害鎼滅储
+ const resetRegulationSearch = () => {
+ regulationSearchForm.title = "";
+ regulationSearchForm.category = "";
+ searchRegulations();
+ };
+ // 鏂板
+ const handleAdd = () => {
+ operationType.value = "add";
+ resetRegulationForm();
+ showRegulationDialog.value = true;
+ };
-// 缂栬緫
-const handleEdit = (row) => {
- operationType.value = 'edit'
- Object.assign(regulationForm, row)
- showRegulationDialog.value = true
-}
-// 搴熷純
-const repealEdit = (row) => {
- operationType.value = 'edit'
- Object.assign(regulationForm, row)
- regulationForm.status = 'repealed'
- ElMessageBox.confirm('纭搴熷純璇ュ埗搴︼紵', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).then(() => {
- updateRuleManagement(regulationForm).then(res => {
- if(res.code == 200){
- ElMessage.success('鍒跺害搴熷純鎴愬姛')
- // showRegulationDialog.value = false
- getRegulationList()
- resetRegulationForm()
- }
+ // 缂栬緫
+ const handleEdit = row => {
+ operationType.value = "edit";
+ Object.assign(regulationForm, row);
+ showRegulationDialog.value = true;
+ };
+ // 搴熷純
+ const repealEdit = row => {
+ operationType.value = "edit";
+ Object.assign(regulationForm, row);
+ regulationForm.status = "repealed";
+ ElMessageBox.confirm("纭搴熷純璇ュ埗搴︼紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
})
- }).catch(() => {
- ElMessage({
- type: 'info',
- message: '宸插彇娑堝簾寮�'
- })
- })
-}
-// 鍙戝竷鍒跺害
-const submitRegulation = async () => {
- try {
- await regulationFormRef.value.validate()
- if(operationType.value == 'add'){
- addRuleManagement(regulationForm).then(res => {
- if(res.code == 200){
- ElMessage.success('鍒跺害鍙戝竷鎴愬姛')
- showRegulationDialog.value = false
- getRegulationList()
- resetRegulationForm()
- }
+ .then(() => {
+ updateRuleManagement(regulationForm).then(res => {
+ if (res.code == 200) {
+ ElMessage.success("鍒跺害搴熷純鎴愬姛");
+ // showRegulationDialog.value = false
+ getRegulationList();
+ resetRegulationForm();
+ }
+ });
})
- }else{
- updateRuleManagement(regulationForm).then(res => {
- if(res.code == 200){
- ElMessage.success('鍒跺害缂栬緫鎴愬姛')
- showRegulationDialog.value = false
- resetRegulationForm()
- getRegulationList()
- }})}
- }catch(err){
- ElMessage.error(err.msg)
- }
-}
-//閲嶇疆鍒跺害琛ㄥ崟
-const resetRegulationForm = () => {
- Object.assign(regulationForm, {
- id: '',
- regulationNum: '',
- title: '',
- category: '',
- content: '',
- version: '',
- status: 'active',
- readCount: 0,
- effectiveTime: '',
- scope: [],
- requireConfirm: false
-})
-}
-
-
-// 鏌ョ湅鍒跺害鐗堟湰鍘嗗彶
-const viewVersionHistory = (row) => {
- showVersionHistoryDialog.value = true
- const params = {
-
- category: row.category
- }
- listRuleManagement(page,params).then(res => {
- if(res.code == 200){
- versionHistory.value = res.data.records
+ .catch(() => {
+ ElMessage({
+ type: "info",
+ message: "宸插彇娑堝簾寮�",
+ });
+ });
+ };
+ // 鍙戝竷鍒跺害
+ const submitRegulation = async () => {
+ try {
+ await regulationFormRef.value.validate();
+ if (operationType.value == "add") {
+ addRuleManagement(regulationForm).then(res => {
+ if (res.code == 200) {
+ ElMessage.success("鍒跺害鍙戝竷鎴愬姛");
+ showRegulationDialog.value = false;
+ getRegulationList();
+ resetRegulationForm();
+ }
+ });
+ } else {
+ updateRuleManagement(regulationForm).then(res => {
+ if (res.code == 200) {
+ ElMessage.success("鍒跺害缂栬緫鎴愬姛");
+ showRegulationDialog.value = false;
+ resetRegulationForm();
+ getRegulationList();
+ }
+ });
+ }
+ } catch (err) {
+ ElMessage.error(err.msg);
}
- })
-}
-// 鏌ョ湅鍒跺害璇︽儏
-const viewRegulation = (row) => {
- currentRegulationDetail.value = row
- showRegulationDetailDialog.value = true
- getReadingStatusByRuleId(row.id).then(res => {
- if(res.code == 200){
- readStatusList.value = res.data
- if(readStatusList.value.length==0 && tableData.value.length>0){
+ };
+ //閲嶇疆鍒跺害琛ㄥ崟
+ const resetRegulationForm = () => {
+ Object.assign(regulationForm, {
+ id: "",
+ regulationNum: "",
+ title: "",
+ category: "",
+ content: "",
+ version: "",
+ status: "active",
+ readCount: 0,
+ effectiveTime: "",
+ scope: [],
+ requireConfirm: false,
+ });
+ };
+
+ // 鏌ョ湅鍒跺害鐗堟湰鍘嗗彶
+ const viewVersionHistory = row => {
+ showVersionHistoryDialog.value = true;
+ const params = {
+ category: row.category,
+ };
+ listRuleManagement(page, params).then(res => {
+ if (res.code == 200) {
+ versionHistory.value = res.data.records;
+ }
+ });
+ };
+ // 鏌ョ湅鍒跺害璇︽儏
+ const viewRegulation = row => {
+ currentRegulationDetail.value = row;
+ showRegulationDetailDialog.value = true;
+ getReadingStatusByRuleId(row.id).then(res => {
+ if (res.code == 200) {
+ readStatusList.value = res.data;
+ if (readStatusList.value.length == 0 && tableData.value.length > 0) {
const params = {
- ruleId: row.id,
- employee: tableData.value[0].staffName,
- department: tableData.value[0].postJob,
- status: 'unconfirmed'
+ ruleId: row.id,
+ employee: tableData.value[0].staffName,
+ department: tableData.value[0].postJob,
+ status: "unconfirmed",
+ };
+ addReadingStatus(params).then(res => {
+ if (res.code == 200) {
+ ElMessage.success("鍒跺害闃呰鎴愬姛");
+ }
+ });
}
- addReadingStatus(params).then(res => {
- if(res.code == 200){
- ElMessage.success('鍒跺害闃呰鎴愬姛')
- }
- })
}
- }
- })
-
-}
-// 鏌ョ湅鍒跺害闃呰鐘舵��
-const viewReadStatus = (row) => {
- showReadStatusDialog.value = true
- //鏌ョ湅闃呰鐘舵�佸垪琛�
- getReadingStatusByRuleId(row.id).then(res => {
- if(res.code == 200){
- readStatusList.value = res.data
- }
- })
-}
-
-//纭鏌ョ湅
-const resetForm = (row) => {
- console.log("row",row)
- row.readCount = row.readCount + 1
-
- updateRuleManagement(row).then(res => {
- if(res.code == 200){
- ElMessage.success('鏌ョ湅鏁伴噺淇敼鎴愬姛')
- //淇敼闃呰鐘舵��
- //鏍规嵁鍒跺害id鍜屽綋鍓嶇櫥褰曠殑鍛樺伐寰楀埌闃呰鐘舵��
- // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName )
- // if(item.length>0){
- // item[0].status = 'confirmed',
- // item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
- // }
- // 绛涢�夊綋鍓嶅憳宸ュ搴旇鍒跺害鐨勯槄璇荤姸鎬佽褰�
- let statusItem = readStatusList.value.find(item => item.employee === tableData.value[0].staffName && item.ruleId === row.id);
-
- if (statusItem) {
- // 濡傛灉鎵惧埌璁板綍锛屾洿鏂扮姸鎬佸拰纭鏃堕棿
- statusItem.status = 'confirmed';
- // 鏍煎紡鍖栨椂闂翠负"YYYY-MM-DD HH:mm:ss"鏍煎紡
- const now = new Date();
- statusItem.confirmTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
- // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
-
- updateReadingStatus(statusItem).then(res => {
- if(res.code == 200){
- ElMessage.success('鍒跺害闃呰鐘舵�佷慨鏀规垚鍔�')
- }
- })
+ });
+ };
+ // 鏌ョ湅鍒跺害闃呰鐘舵��
+ const viewReadStatus = row => {
+ showReadStatusDialog.value = true;
+ //鏌ョ湅闃呰鐘舵�佸垪琛�
+ getReadingStatusByRuleId(row.id).then(res => {
+ if (res.code == 200) {
+ readStatusList.value = res.data;
}
+ });
+ };
+ //纭鏌ョ湅
+ const resetForm = row => {
+ console.log("row", row);
+ row.readCount = row.readCount + 1;
+
+ updateRuleManagement(row).then(res => {
+ if (res.code == 200) {
+ ElMessage.success("鏌ョ湅鏁伴噺淇敼鎴愬姛");
+ //淇敼闃呰鐘舵��
+ //鏍规嵁鍒跺害id鍜屽綋鍓嶇櫥褰曠殑鍛樺伐寰楀埌闃呰鐘舵��
+ // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName )
+ // if(item.length>0){
+ // item[0].status = 'confirmed',
+ // item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
+ // }
+ // 绛涢�夊綋鍓嶅憳宸ュ搴旇鍒跺害鐨勯槄璇荤姸鎬佽褰�
+ let statusItem = readStatusList.value.find(
+ item =>
+ item.employee === tableData.value[0].staffName &&
+ item.ruleId === row.id
+ );
+
+ if (statusItem) {
+ // 濡傛灉鎵惧埌璁板綍锛屾洿鏂扮姸鎬佸拰纭鏃堕棿
+ statusItem.status = "confirmed";
+ // 鏍煎紡鍖栨椂闂翠负"YYYY-MM-DD HH:mm:ss"鏍煎紡
+ const now = new Date();
+ statusItem.confirmTime = `${now.getFullYear()}-${String(
+ now.getMonth() + 1
+ ).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(
+ now.getHours()
+ ).padStart(2, "0")}:${String(now.getMinutes()).padStart(
+ 2,
+ "0"
+ )}:${String(now.getSeconds()).padStart(2, "0")}`;
+ // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
+
+ updateReadingStatus(statusItem).then(res => {
+ if (res.code == 200) {
+ ElMessage.success("鍒跺害闃呰鐘舵�佷慨鏀规垚鍔�");
+ }
+ });
+ }
+ }
+ });
+ };
+
+ // 瀵煎嚭瑙勭珷鍒跺害
+ const { proxy } = getCurrentInstance();
+ const handleExport = () => {
+ proxy.download(
+ "/rulesRegulationsManagement/export",
+ { ...regulationSearchForm },
+ "瑙勭珷鍒跺害.xlsx"
+ );
+ };
+
+ // 闄勪欢锛氭煡璇�
+ const fetchRuleFiles = async rulesRegulationsManagementId => {
+ const params = {
+ current: filePage.current,
+ size: filePage.size,
+ rulesRegulationsManagementId,
+ };
+ const res = await listRuleFiles(params);
+ const records = res?.data?.records || [];
+ filePage.total = res?.data?.total || records.length;
+ const mapped = records.map(item => ({
+ id: item.id,
+ name: item.fileName || item.name,
+ url: item.fileUrl || item.url,
+ raw: item,
+ }));
+ fileListDialogRef.value?.setList(mapped);
+ };
+
+ // 鎵撳紑闄勪欢寮圭獥
+ const openFileDialog = async row => {
+ currentFileRuleId.value = row.id;
+ fileDialogVisible.value = true;
+ await fetchRuleFiles(row.id);
+ };
+
+ // 鍒锋柊闄勪欢鍒楄〃
+ const refreshFileList = async () => {
+ if (!currentFileRuleId.value) return;
+ await fetchRuleFiles(currentFileRuleId.value);
+ };
+
+ // 涓婁紶闄勪欢锛堢敱瀛愮粍浠惰Е鍙戯級
+ const handleAttachmentUpload = async filePayload => {
+ if (!currentFileRuleId.value) return;
+ const payload = {
+ name: filePayload?.fileName || filePayload?.name,
+ url: filePayload?.fileUrl || filePayload?.url,
+ rulesRegulationsManagementId: currentFileRuleId.value,
+ };
+ await addRuleFile(payload);
+ ElMessage.success("鏂囦欢涓婁紶鎴愬姛");
+ await refreshFileList();
+ };
+
+ // 鍒犻櫎闄勪欢
+ const handleAttachmentDelete = async row => {
+ if (!row?.id) return false;
+ try {
+ await ElMessageBox.confirm("纭鍒犻櫎璇ラ檮浠讹紵", "鎻愮ず", { type: "warning" });
+ } catch {
+ return false;
}
- })
-}
+ await delRuleFile([row.id]);
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ await refreshFileList();
+ };
-// 瀵煎嚭瑙勭珷鍒跺害
-const { proxy } = getCurrentInstance()
-const handleExport = () => {
- proxy.download('/rulesRegulationsManagement/export', { ...regulationSearchForm }, '瑙勭珷鍒跺害.xlsx')
-}
+ // 鑾峰彇瑙勭珷鍒跺害鍒楄〃鏁版嵁
+ const getRegulationList = async () => {
+ tableLoading.value = true;
+ listRuleManagement(page, regulationSearchForm)
+ .then(res => {
+ regulations.value = res.data.records;
+ // 杩囨护鎺夊凡搴熷純鐨勫埗搴�
+ // regulations.value = res.data.records.filter(item => item.status !== 'repealed')
+ page.value.total = res.data.total;
+ tableLoading.value = false;
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ });
+ };
-// 鑾峰彇瑙勭珷鍒跺害鍒楄〃鏁版嵁
-const getRegulationList = async () => {
- tableLoading.value = true
- listRuleManagement(page,regulationSearchForm)
- .then(res => {
-
- regulations.value = res.data.records
- // 杩囨护鎺夊凡搴熷純鐨勫埗搴�
- // regulations.value = res.data.records.filter(item => item.status !== 'repealed')
- page.value.total = res.data.total;
- tableLoading.value = false;
-
- }).catch(err => {
- tableLoading.value = false;
- })
-}
-
-onMounted(() => {
- // 鍒濆鍖�
- getRegulationList()
-})
+ onMounted(() => {
+ // 鍒濆鍖�
+ getRegulationList();
+ });
</script>
<style scoped>
-.app-container {
- padding: 20px;
-}
+ .app-container {
+ padding: 20px;
+ }
-.card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
+ .card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
-.tab-content {
- padding: 20px 0;
-}
+ .tab-content {
+ padding: 20px 0;
+ }
-.mb-20 {
- margin-bottom: 20px;
-}
+ .mb-20 {
+ margin-bottom: 20px;
+ }
-.mt-20 {
- margin-top: 20px;
-}
+ .mt-20 {
+ margin-top: 20px;
+ }
-.ml-10 {
- margin-left: 10px;
-}
+ .ml-10 {
+ margin-left: 10px;
+ }
-.regulation-content {
- background-color: #f5f5f5;
- padding: 15px;
- border-radius: 4px;
- line-height: 1.6;
- white-space: pre-wrap;
- height: 200px;
-}
+ .regulation-content {
+ background-color: #f5f5f5;
+ padding: 15px;
+ border-radius: 4px;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ height: 200px;
+ }
-.dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
-}
+ .dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ }
</style>
diff --git a/src/views/collaborativeApproval/sealManagement/index.vue b/src/views/collaborativeApproval/sealManagement/index.vue
index 8e0d054..04ecfc4 100644
--- a/src/views/collaborativeApproval/sealManagement/index.vue
+++ b/src/views/collaborativeApproval/sealManagement/index.vue
@@ -27,7 +27,7 @@
<el-option label="宸叉嫆缁�" value="rejected" />
</el-select>
</el-col>
- <el-col :span="8">
+ <el-col :span="6">
<el-button type="primary" @click="searchSealApplications">鎼滅储</el-button>
<el-button @click="resetSealSearch">閲嶇疆</el-button>
<el-button @click="handleExport">瀵煎嚭</el-button>
@@ -261,9 +261,9 @@
import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus } from '@/api/collaborativeApproval/sealManagement.js'
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile, userListNoPageByTenantId } from '@/api/system/user.js'
-import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
+import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// 鍝嶅簲寮忔暟鎹�
const currentUser = ref(null)
@@ -583,7 +583,7 @@
currentUser.value = res.data.userName
}
})
- staffJoinListPage({staffState: 1, ...page}).then(res => {
+ staffOnJobListPage({staffState: 1, ...page}).then(res => {
tableLoading.value = false;
// tableData.value = res.data.records
// //绛涢�夊嚭鍜宑urrentUser鍚屽悕鐨勪汉鍛�
diff --git a/src/views/collaborativeApproval/shipmentReview/fileList.vue b/src/views/collaborativeApproval/shipmentReview/fileList.vue
new file mode 100644
index 0000000..da37db2
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/src/views/collaborativeApproval/shipmentReview/index.vue b/src/views/collaborativeApproval/shipmentReview/index.vue
new file mode 100644
index 0000000..ac2b790
--- /dev/null
+++ b/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";
+
+// 瀹氫箟缁勪欢鎺ユ敹鐨刾rops
+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>
diff --git a/src/views/customerService/afterSalesHandling/components/formDia.vue b/src/views/customerService/afterSalesHandling/components/formDia.vue
index cfdb01c..daccaca 100644
--- a/src/views/customerService/afterSalesHandling/components/formDia.vue
+++ b/src/views/customerService/afterSalesHandling/components/formDia.vue
@@ -131,6 +131,7 @@
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceDispose, afterSalesServiceUpdate} from "@/api/customerService/index.js";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -194,14 +195,6 @@
dialogFormVisible.value = false;
emit('close')
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
defineExpose({
openDialog,
});
diff --git a/src/views/customerService/afterSalesHandling/index.vue b/src/views/customerService/afterSalesHandling/index.vue
index 818270d..65a7551 100644
--- a/src/views/customerService/afterSalesHandling/index.vue
+++ b/src/views/customerService/afterSalesHandling/index.vue
@@ -30,6 +30,7 @@
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
+ <el-button @click="handleOut" style="margin-left: 10px">瀵煎嚭</el-button>
</div>
</div>
<div class="table_list">
@@ -50,7 +51,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
-import {onMounted, ref} from "vue";
+import {onMounted, ref, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/customerService/afterSalesHandling/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {afterSalesServiceDelete, afterSalesServiceListPage} from "@/api/customerService/index.js";
@@ -225,6 +226,22 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/afterSalesService/exportTwo", {}, "鍞悗澶勭悊.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/customerService/expiryAfterSales/components/formDia.vue b/src/views/customerService/expiryAfterSales/components/formDia.vue
new file mode 100644
index 0000000..e8293d6
--- /dev/null
+++ b/src/views/customerService/expiryAfterSales/components/formDia.vue
@@ -0,0 +1,283 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ :title="dialogTitle"
+ width="70%"
+ @close="closeDia"
+ >
+ <el-form
+ :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="涓存湡浜у搧鍚嶇О锛�" prop="productName">
+ <el-input
+ v-model="form.productName"
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+ clearable
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浜у搧鎵瑰彿锛�" prop="batchNumber">
+ <el-input
+ v-model="form.batchNumber"
+ placeholder="璇疯緭鍏ヤ骇鍝佹壒鍙�"
+ clearable
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="涓存湡鏃ユ湡锛�" prop="expiryDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.expiryDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨涓存湡鏃ユ湡"
+ clearable
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="搴撳瓨鏁伴噺锛�" prop="stockQuantity">
+ <el-input-number
+ v-model="form.stockQuantity"
+ :min="0"
+ placeholder="璇疯緭鍏ュ簱瀛樻暟閲�"
+ style="width: 100%"
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
+ <el-input
+ v-model="form.customerName"
+ placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+ clearable
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴鐢佃瘽锛�" prop="contactPhone">
+ <el-input
+ v-model="form.contactPhone"
+ placeholder="璇疯緭鍏ヨ仈绯荤數璇�"
+ clearable
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="闂鎻忚堪锛�" prop="problemDesc">
+ <el-input
+ v-model="form.problemDesc"
+ placeholder="璇疯緭鍏ラ棶棰樻弿杩�"
+ clearable
+ :disabled="operationType === 'view'"
+ type="textarea"
+ :rows="3"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30" v-if="operationType !== 'add'">
+ <el-col :span="12">
+ <el-form-item label="澶勭悊浜猴細" prop="handlerId">
+ <el-select
+ v-model="form.handlerId"
+ placeholder="璇烽�夋嫨澶勭悊浜�"
+ clearable
+ :disabled="operationType === 'view'"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in userList"
+ :key="item.userId"
+ :label="item.nickName"
+ :value="item.userId"
+ ></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="澶勭悊鏃ユ湡锛�" prop="handleDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.handleDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨澶勭悊鏃ユ湡"
+ clearable
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30" v-if="operationType !== 'add'">
+ <el-col :span="24">
+ <el-form-item label="澶勭悊缁撴灉锛�" prop="handleResult">
+ <el-input
+ v-model="form.handleResult"
+ placeholder="璇疯緭鍏ュ鐞嗙粨鏋�"
+ clearable
+ :disabled="operationType === 'view'"
+ type="textarea"
+ :rows="3"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">纭</el-button>
+ <el-button @click="closeDia">{{ operationType === 'view' ? '鍏抽棴' : '鍙栨秷' }}</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed} from "vue";
+import useUserStore from "@/store/modules/user.js";
+import { getCurrentDate } from "@/utils/index.js";
+// import {userListNoPageByTenantId} from "@/api/system/user.js"; // 鏆傛椂娉ㄩ噴鎺夛紝浣跨敤鍋囨暟鎹�
+// import {expiryAfterSalesAdd, expiryAfterSalesUpdate} from "@/api/customerService/index.js"; // 鏆傛椂娉ㄩ噴鎺夛紝浣跨敤鍋囨暟鎹�
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+const userStore = useUserStore();
+
+const dialogTitle = computed(() => {
+ switch (operationType.value) {
+ case 'add':
+ return '鏂板涓存湡鍞悗';
+ case 'edit':
+ return '缂栬緫涓存湡鍞悗';
+ case 'view':
+ return '鏌ョ湅涓存湡鍞悗';
+ default:
+ return '涓存湡鍞悗绠$悊';
+ }
+});
+
+const data = reactive({
+ form: {
+ id: "",
+ productName: "",
+ batchNumber: "",
+ expiryDate: "",
+ stockQuantity: 0,
+ customerName: "",
+ contactPhone: "",
+ problemDesc: "",
+ handlerId: "",
+ handleDate: "",
+ handleResult: "",
+ status: 1
+ },
+ rules: {
+ productName: [{required: true, message: "璇疯緭鍏ヤ骇鍝佸悕绉�", trigger: "blur"}],
+ batchNumber: [{required: true, message: "璇疯緭鍏ヤ骇鍝佹壒鍙�", trigger: "blur"}],
+ expiryDate: [{required: true, message: "璇烽�夋嫨涓存湡鏃ユ湡", trigger: "change"}],
+ stockQuantity: [{required: true, message: "璇疯緭鍏ュ簱瀛樻暟閲�", trigger: "blur"}],
+ customerName: [{required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur"}],
+ contactPhone: [
+ {required: true, message: "璇疯緭鍏ヨ仈绯荤數璇�", trigger: "blur"},
+ {pattern: /^1[3-9]\d{9}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur"}
+ ],
+ problemDesc: [{required: true, message: "璇疯緭鍏ラ棶棰樻弿杩�", trigger: "blur"}],
+ }
+})
+const { form, rules } = toRefs(data);
+const userList = ref([])
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+
+ // 妯℃嫙鑾峰彇鐢ㄦ埛鍒楄〃
+ userList.value = [
+ { userId: 1, nickName: "寮犱笁" },
+ { userId: 2, nickName: "鏉庡洓" },
+ { userId: 3, nickName: "鐜嬩簲" },
+ { userId: 4, nickName: "璧靛叚" },
+ { userId: 5, nickName: "瀛欏叓" }
+ ];
+
+ if (type === 'add') {
+ // 鏂板鏃堕噸缃〃鍗�
+ form.value = {
+ id: "",
+ productName: "",
+ batchNumber: "",
+ expiryDate: "",
+ stockQuantity: 0,
+ customerName: "",
+ contactPhone: "",
+ problemDesc: "",
+ handlerId: "",
+ handleDate: "",
+ handleResult: "",
+ status: 1
+ };
+ } else {
+ // 缂栬緫鎴栨煡鐪嬫椂濉厖鏁版嵁
+ form.value = { ...row };
+ if (type === 'edit' && !form.value.handlerId) {
+ form.value.handlerId = userStore.id;
+ form.value.handleDate = getCurrentDate();
+ }
+ }
+}
+
+const submitForm = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ // 妯℃嫙鎻愪氦鎿嶄綔
+ setTimeout(() => {
+ console.log("妯℃嫙鎻愪氦鐨勬暟鎹�:", form.value);
+ proxy.$modal.msgSuccess(operationType.value === 'add' ? "鏂板鎴愬姛" : "鏇存柊鎴愬姛");
+ closeDia();
+ }, 300);
+ }
+ });
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ emit('close')
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/views/customerService/expiryAfterSales/index.vue b/src/views/customerService/expiryAfterSales/index.vue
new file mode 100644
index 0000000..df44504
--- /dev/null
+++ b/src/views/customerService/expiryAfterSales/index.vue
@@ -0,0 +1,258 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">涓存湡鏃ユ湡锛�</span>
+ <el-date-picker
+ v-model="searchForm.expiryDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="handleQuery"
+ />
+ <span class="search_title ml10">澶勭悊鏃ユ湡锛�</span>
+ <el-date-picker
+ v-model="searchForm.handleDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="handleQuery"
+ />
+ <span style = "margin-left: 10px;" class="search_title">澶勭悊鐘舵�侊細</span>
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" @change="handleQuery" style="width: 140px" clearable>
+ <el-option label="寰呭鐞�" :value="1"></el-option>
+ <el-option label="宸插鐞�" :value="2"></el-option>
+ </el-select>
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ <el-button @click="resetQuery" style="margin-left: 10px"
+ >閲嶇疆</el-button
+ >
+ </div>
+ </div>
+ <div class="table_actions" style="margin-bottom: 10px;">
+ <el-button type="primary" @click="openForm('add')">鏂板</el-button>
+ <el-button type="danger" @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ >
+ <!-- 琛ㄦ牸鎻掓Ы -->
+ <template #status="{ row }">
+ <el-tag :type="row.status === 1 ? 'warning' : 'success'">
+ {{ row.status === 1 ? '寰呭鐞�' : '宸插鐞�' }}
+ </el-tag>
+ </template>
+
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="openForm('view', row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="openForm('edit', row)" v-if="row.status === 1">缂栬緫</el-button>
+ </template>
+ </PIMTable>
+ </div>
+ <form-dia ref="formDia" @close="handleQuery"></form-dia>
+ </div>
+</template>
+
+<script setup>
+import {Search} from "@element-plus/icons-vue";
+import {onMounted, ref} from "vue";
+import FormDia from "@/views/customerService/expiryAfterSales/components/formDia.vue";
+import {ElMessageBox} from "element-plus";
+// import {expiryAfterSalesDelete, expiryAfterSalesListPage} from "@/api/customerService/index.js"; // 鏆傛椂娉ㄩ噴鎺夛紝浣跨敤鍋囨暟鎹�
+import useUserStore from "@/store/modules/user.js";
+const { proxy } = getCurrentInstance();
+const userStore = useUserStore()
+
+const data = reactive({
+ searchForm: {
+ expiryDate: "",
+ handleDate: "",
+ status: ""
+ },
+ tableData: [],
+ page: {
+ current: 1,
+ size: 10,
+ total: 0,
+ },
+ selectedRows: [],
+ tableLoading: false,
+ formDia: null,
+ tableColumn: [
+ {
+ label: "涓存湡浜у搧鍚嶇О",
+ prop: "productName",
+ width: "",
+ },
+ {
+ label: "浜у搧鎵瑰彿",
+ prop: "batchNumber",
+ width: "",
+ },
+ {
+ label: "涓存湡鏃ユ湡",
+ prop: "expiryDate",
+ width: "",
+ },
+ {
+ label: "搴撳瓨鏁伴噺",
+ prop: "stockQuantity",
+ width: "",
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: "",
+ },
+ {
+ label: "闂鎻忚堪",
+ prop: "problemDesc",
+ width: "",
+ },
+ {
+ label: "澶勭悊鐘舵��",
+ prop: "status",
+ width: "",
+ slot: true,
+ },
+ {
+ label: "澶勭悊浜�",
+ prop: "handlerName",
+ width: "",
+ },
+ {
+ label: "澶勭悊鏃ユ湡",
+ prop: "handleDate",
+ width: "",
+ },
+ {
+ label: "鎿嶄綔",
+ prop: "operation",
+ slot: true,
+ width: "200",
+ },
+ ],
+});
+
+const {
+ searchForm,
+ tableData,
+ page,
+ selectedRows,
+ tableLoading,
+ formDia,
+ tableColumn,
+} = toRefs(data);
+
+// 鏌ヨ
+const handleQuery = () => {
+ page.value.current = 1;
+ getList();
+};
+
+// 閫夋嫨
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 閲嶇疆
+const resetQuery = () => {
+ proxy.resetForm("queryRef");
+ searchForm.value = {
+ expiryDate: "",
+ handleDate: "",
+ status: ""
+ };
+ handleQuery();
+};
+
+// 鍒嗛〉
+const pagination = (obj) => {
+ page.value.current = obj.page;
+ page.value.size = obj.limit;
+ getList();
+};
+
+// 鑾峰彇鍒楄〃鏁版嵁
+const getList = () => {
+ tableLoading.value = true;
+ // 鍙栨秷娉ㄩ噴骞朵娇鐢ㄧ湡瀹濧PI
+ // expiryAfterSalesListPage({
+ // ...searchForm.value,
+ // current: page.value.current,
+ // size: page.value.size
+ // }).then(res => {
+ // tableData.value = res.data.records;
+ // page.value.total = res.data.total;
+ // tableLoading.value = false;
+ // });
+
+ // 鏆傛椂杩斿洖绌烘暟鎹�
+ tableData.value = [];
+ page.value.total = 0;
+ tableLoading.value = false;
+};
+
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ nextTick(() => {
+ formDia.value?.openDialog(type, row)
+ })
+};
+
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ // 鍙栨秷娉ㄩ噴骞朵娇鐢ㄧ湡瀹濧PI
+ // expiryAfterSalesDelete(ids).then(() => {
+ // proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ // getList();
+ // }).finally(() => {
+ // tableLoading.value = false;
+ // });
+
+ // 鏆傛椂妯℃嫙鍒犻櫎鎴愬姛
+ tableLoading.value = false;
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/views/customerService/feedbackRegistration/components/formDia.vue b/src/views/customerService/feedbackRegistration/components/formDia.vue
index a37002b..41c8ac6 100644
--- a/src/views/customerService/feedbackRegistration/components/formDia.vue
+++ b/src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -81,6 +81,7 @@
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -149,14 +150,6 @@
dialogFormVisible.value = false;
emit('close')
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
defineExpose({
openDialog,
});
diff --git a/src/views/customerService/feedbackRegistration/index.vue b/src/views/customerService/feedbackRegistration/index.vue
index 780de93..3d97ef8 100644
--- a/src/views/customerService/feedbackRegistration/index.vue
+++ b/src/views/customerService/feedbackRegistration/index.vue
@@ -23,6 +23,7 @@
</div>
<div>
<el-button type="primary" @click="openForm('add')">鏂板</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
@@ -44,7 +45,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
-import {onMounted, ref} from "vue";
+import {onMounted, ref, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/customerService/feedbackRegistration/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {afterSalesServiceDelete, afterSalesServiceListPage} from "@/api/customerService/index.js";
@@ -120,7 +121,7 @@
openForm("edit", row);
},
disabled: (row) => {
- return row.checkUserId !== userStore.id || row.status !== 1
+ return row.status !== 1
}
},
],
@@ -202,6 +203,22 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/afterSalesService/export", {}, "鍙嶉鐧昏.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/energyManagement/carbonManagement/index.vue b/src/views/energyManagement/carbonManagement/index.vue
new file mode 100644
index 0000000..5a4be25
--- /dev/null
+++ b/src/views/energyManagement/carbonManagement/index.vue
@@ -0,0 +1,1553 @@
+<template>
+ <div class="carbon-management">
+ <!-- 椤甸潰澶撮儴 -->
+ <div class="page-header">
+ <div class="header-content">
+ <h1 class="page-title">纰虫帓鏀剧鐞嗙郴缁�</h1>
+ <p class="page-subtitle">鍩轰簬ISO 14064鏍囧噯 路 GHG Protocol鏍哥畻鏍囧噯</p>
+ </div>
+ <div class="header-stats">
+ <div class="stat-item">
+ <span class="stat-label">鎬荤⒊鎺掓斁閲�</span>
+ <span class="stat-value">{{totalEmissions}} tCO鈧俥</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">鏈湀鍑忔帓</span>
+ <span class="stat-value reduction">-{{monthlyReduction}}%</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">纰充腑鍜岃繘搴�</span>
+ <span class="stat-value">{{neutralProgress}}%</span>
+ </div>
+ </div>
+ </div>
+
+ <!-- 涓昏鍐呭鍖哄煙 -->
+ <div class="dashboard-content">
+ <!-- 椤堕儴鏁版嵁闈㈡澘 -->
+ <div class="top-panels">
+ <div class="data-panel top-left">
+ <div class="panel-title">褰撳墠纰虫帓鏀�</div>
+ <div class="panel-value">{{carbonData.scope1}} <span class="unit">tCO鈧俥</span></div>
+ <div class="panel-subtitle">鑼冨洿1鐩存帴鎺掓斁</div>
+ </div>
+ <div class="data-panel top-center">
+ <div class="panel-title">鑳借�楃洃娴�</div>
+ <div class="panel-value">{{carbonData.scope2}} <span class="unit">tCO鈧俥</span></div>
+ <div class="panel-subtitle">鑼冨洿2闂存帴鎺掓斁</div>
+ </div>
+ <div class="data-panel top-right">
+ <div class="panel-title">渚涘簲閾炬帓鏀�</div>
+ <div class="panel-value">{{carbonData.scope3}} <span class="unit">tCO鈧俥</span></div>
+ <div class="panel-subtitle">鑼冨洿3渚涘簲閾炬帓鏀�</div>
+ </div>
+ <div class="data-panel top-far-right">
+ <div class="panel-title">鍑忔帓杩涘害</div>
+ <div class="panel-value">{{neutralProgress}} <span class="unit">%</span></div>
+ <div class="panel-subtitle">纰充腑鍜岀洰鏍�</div>
+ </div>
+ </div>
+
+ <!-- 涓績涓昏鍥惧尯鍩� -->
+ <div class="center-main-view">
+ <!-- 宸︿晶鎺у埗闈㈡澘 -->
+ <div class="left-control-panel">
+ <div class="control-section">
+ <div class="section-title">纰虫帓鏀捐寖鍥�</div>
+ <el-radio-group v-model="selectedScope" @change="updateScopeData" class="vertical-radio">
+ <el-radio-button :value="'all'">鍏ㄩ儴鑼冨洿</el-radio-button>
+ <el-radio-button :value="'scope1'">鑼冨洿1</el-radio-button>
+ <el-radio-button :value="'scope2'">鑼冨洿2</el-radio-button>
+ <el-radio-button :value="'scope3'">鑼冨洿3</el-radio-button>
+ </el-radio-group>
+ </div>
+ <div class="control-section">
+ <div class="section-title">鐩戞祴灞傜骇</div>
+ <el-radio-group v-model="heatmapLevel" @change="updateHeatmapLevel" class="vertical-radio">
+ <el-radio-button :value="'device'">璁惧绾�</el-radio-button>
+ <el-radio-button :value="'line'">浜х嚎绾�</el-radio-button>
+ <el-radio-button :value="'enterprise'">浼佷笟绾�</el-radio-button>
+ </el-radio-group>
+ </div>
+ </div>
+
+ <!-- 涓績鐑姏鍥� -->
+ <div class="main-heatmap">
+ <div class="heatmap-header">
+ <h2 class="main-title">纰宠冻杩圭儹鍔涘浘鍒嗘瀽</h2>
+ <div class="date-selector">
+ <el-date-picker
+ v-model="selectedDate"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ size="small"
+ @change="updateHeatmapData"
+ />
+ </div>
+ </div>
+ <div class="heatmap-view">
+ <Echarts ref="heatmapChart"
+ :series="heatmapSeries"
+ :xAxis="heatmapXAxis"
+ :yAxis="heatmapYAxis"
+ :tooltip="heatmapTooltip"
+ :visualMap="heatmapVisualMap"
+ :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
+ style="height: 450px"></Echarts>
+ </div>
+ </div>
+
+ <!-- 鍙充晶鏁版嵁闈㈡澘 -->
+ <div class="right-data-panel">
+ <div class="data-section">
+ <div class="section-title">瀹炴椂鐩戞帶</div>
+ <div class="mini-chart">
+ <Echarts ref="realtimeChart"
+ :series="realtimeSeries"
+ :xAxis="realtimeXAxis"
+ :chartStyle="chartStyle"
+ :yAxis="realtimeYAxis"
+ :tooltip="realtimeTooltip"
+ :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
+ style="height: 300px"></Echarts>
+ </div>
+ </div>
+ <div class="data-section">
+ <div class="section-title">瓒嬪娍鍒嗘瀽</div>
+ <div class="trend-controls">
+ <el-radio-group v-model="trendPeriod" size="small" @change="updateTrendData">
+ <el-radio-button :value="'week'">鍛�</el-radio-button>
+ <el-radio-button :value="'month'">鏈�</el-radio-button>
+ <el-radio-button :value="'year'">骞�</el-radio-button>
+ </el-radio-group>
+ </div>
+ <div class="mini-chart">
+ <Echarts ref="trendChart"
+ :series="trendSeries"
+ :xAxis="trendXAxis"
+ :yAxis="trendYAxis"
+ :tooltip="trendTooltip"
+ :chartStyle="chartStyle"
+ :legend="trendLegend"
+ :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
+ style="height: 200px"></Echarts>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 搴曢儴杩涘害闈㈡澘 -->
+ <div class="bottom-progress-panel">
+ <div class="progress-section">
+ <div class="progress-title">2024骞村噺鎺掔洰鏍�</div>
+ <div class="progress-data">
+ <span class="current">{{reductionTarget.current}}</span>
+ <span class="separator">/</span>
+ <span class="target">{{reductionTarget.target}} tCO鈧俥</span>
+ </div>
+ <el-progress :percentage="reductionTarget.percentage" :stroke-width="6" color="#00E676"/>
+ </div>
+ <div class="progress-section">
+ <div class="progress-title">纰充腑鍜岃繘搴�</div>
+ <div class="progress-data">
+ <span class="current">{{neutralTarget.current}}</span>
+ <span class="separator">/</span>
+ <span class="target">{{neutralTarget.target}} tCO鈧俥</span>
+ </div>
+ <el-progress :percentage="neutralTarget.percentage" :stroke-width="6" color="#00D4FF"/>
+ </div>
+ </div>
+
+ <!-- 搴曢儴鏁版嵁琛ㄦ牸 -->
+ <div class="bottom-data-table">
+ <div class="table-panel">
+ <div class="table-header">
+ <h3 class="table-title">纰虫帓鏀捐缁嗘暟鎹�</h3>
+ <div class="table-controls">
+ <el-input
+ v-model="searchKeyword"
+ placeholder="鎼滅储璁惧鎴栦骇绾�"
+ size="small"
+ style="width: 200px; margin-right: 10px;"
+ />
+ <el-button type="primary" size="small" @click="exportData">瀵煎嚭鏁版嵁</el-button>
+ </div>
+ </div>
+ <el-table :data="filteredTableData" style="width: 100%" height="180">
+ <el-table-column prop="name" label="璁惧/浜х嚎" width="150"/>
+ <el-table-column prop="type" label="绫诲瀷" width="100"/>
+ <el-table-column prop="scope1" label="鑼冨洿1鎺掓斁" width="120"/>
+ <el-table-column prop="scope2" label="鑼冨洿2鎺掓斁" width="120"/>
+ <el-table-column prop="scope3" label="鑼冨洿3鎺掓斁" width="120"/>
+ <el-table-column prop="total" label="鎬绘帓鏀鹃噺" width="120"/>
+ <el-table-column prop="efficiency" label="纰虫晥鐜�" width="100"/>
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">{{scope.row.status}}</el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'
+import * as echarts from 'echarts'
+import Echarts from '@/components/Echarts/echarts.vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const selectedScope = ref('all')
+const heatmapLevel = ref('device')
+const selectedDate = ref(new Date())
+const trendPeriod = ref('week')
+const searchKeyword = ref('')
+
+// 纰虫帓鏀炬暟鎹�
+const carbonData = ref({
+ scope1: 125.6,
+ scope2: 89.3,
+ scope3: 234.7
+})
+const chartStyle = {
+ width: '96%',
+ height: '110%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+}
+// 璁$畻灞炴��
+const totalEmissions = computed(() => {
+ return (carbonData.value.scope1 + carbonData.value.scope2 + carbonData.value.scope3).toFixed(1)
+})
+
+const monthlyReduction = ref(8.5)
+
+// 璁$畻纰充腑鍜岃繘搴︾櫨鍒嗘瘮
+const neutralProgress = computed(() => {
+ return Math.round(neutralTarget.value.percentage)
+})
+
+// 鍑忔帓鐩爣鏁版嵁
+const reductionTarget = ref({
+ current: 320.5,
+ target: 500,
+ percentage: 64.1
+})
+
+const neutralTarget = ref({
+ current: 1250,
+ target: 3800,
+ percentage: 32.9
+})
+
+// 瀹炴椂鐩戞帶鍥捐〃閰嶇疆
+const realtimeSeries = ref([
+ {
+ name: '瀹炴椂纰虫帓鏀�',
+ type: 'line',
+ smooth: true,
+ data: generateRealtimeData(),
+ itemStyle: {
+ color: '#FF6B6B'
+ },
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: 'rgba(255, 107, 107, 0.3)' },
+ { offset: 1, color: 'rgba(255, 107, 107, 0.1)' }
+ ])
+ }
+ }
+])
+
+const realtimeXAxis = [{
+ type: 'category',
+ data: Array.from({length: 24}, (_, i) => `${i.toString().padStart(2, '0')}:00`),
+ axisLabel: { color: '#B8C8E0' }
+}]
+
+const realtimeYAxis = [{
+ type: 'value',
+ name: 'tCO鈧俥/h',
+ axisLabel: { color: '#B8C8E0' },
+ nameTextStyle: { color: '#B8C8E0' }
+}]
+
+const realtimeTooltip = {
+ trigger: 'axis',
+ formatter: '{b}: {c} tCO鈧俥/h'
+}
+
+// 鐑姏鍥鹃厤缃�
+const heatmapSeries = ref([
+ {
+ name: '纰虫帓鏀鹃噺',
+ type: 'heatmap',
+ data: generateHeatmapData(),
+ label: {
+ show: false
+ },
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
+ }
+ }
+ }
+])
+
+const heatmapXAxis = [{
+ type: 'category',
+ data: Array.from({length: 24}, (_, i) => `${i}:00`),
+ splitArea: { show: true },
+ axisLabel: { color: '#B8C8E0' }
+}]
+
+const heatmapYAxis = [{
+ type: 'category',
+ data: ['璁惧A', '璁惧B', '璁惧C', '璁惧D', '璁惧E', '璁惧F', '璁惧G'],
+ splitArea: { show: true },
+ axisLabel: { color: '#B8C8E0' }
+}]
+
+const heatmapTooltip = {
+ trigger: 'item',
+ formatter: function (params) {
+ const [hour, device] = params.data
+ const value = params.value[2]
+ return `璁惧: ${heatmapYAxis[0].data[device]}<br/>鏃堕棿: ${hour}:00<br/>纰虫帓鏀鹃噺: ${value} tCO鈧俥`
+ }
+}
+
+const heatmapVisualMap = ref({
+ min: 0,
+ max: 50,
+ calculable: true,
+ orient: 'horizontal',
+ left: 'center',
+ bottom: '5%',
+ inRange: {
+ color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
+ },
+ textStyle: { color: '#B8C8E0' }
+})
+
+// 瓒嬪娍鍒嗘瀽鍥捐〃閰嶇疆
+const trendSeries = ref([
+ {
+ name: '鑼冨洿1',
+ type: 'line',
+ data: [120, 132, 101, 134, 90, 230, 210],
+ itemStyle: { color: '#FF6B6B' }
+ },
+ {
+ name: '鑼冨洿2',
+ type: 'line',
+ data: [220, 182, 191, 234, 290, 330, 310],
+ itemStyle: { color: '#4ECDC4' }
+ },
+ {
+ name: '鑼冨洿3',
+ type: 'line',
+ data: [150, 232, 201, 154, 190, 330, 410],
+ itemStyle: { color: '#45B7D1' }
+ }
+])
+
+const trendXAxis = [{
+ type: 'category',
+ data: ['鍛ㄤ竴', '鍛ㄤ簩', '鍛ㄤ笁', '鍛ㄥ洓', '鍛ㄤ簲', '鍛ㄥ叚', '鍛ㄦ棩'],
+ axisLabel: { color: '#B8C8E0' }
+}]
+
+const trendYAxis = [{
+ type: 'value',
+ name: 'tCO鈧俥',
+ axisLabel: { color: '#B8C8E0' },
+ nameTextStyle: { color: '#B8C8E0' }
+}]
+
+const trendTooltip = {
+ trigger: 'axis'
+}
+
+const trendLegend = {
+ data: ['鑼冨洿1', '鑼冨洿2', '鑼冨洿3'],
+ textStyle: { color: '#B8C8E0' }
+}
+
+// 琛ㄦ牸鏁版嵁
+const carbonTableData = ref([
+ { name: '鐢熶骇绾緼', type: '浜х嚎', scope1: 45.2, scope2: 32.1, scope3: 18.7, total: 96.0, efficiency: '鑹ソ', status: '姝e父' },
+ { name: '璁惧B-01', type: '璁惧', scope1: 12.5, scope2: 8.3, scope3: 5.2, total: 26.0, efficiency: '浼樼', status: '姝e父' },
+ { name: '鐢熶骇绾緾', type: '浜х嚎', scope1: 38.7, scope2: 28.9, scope3: 15.4, total: 83.0, efficiency: '鑹ソ', status: '鍛婅' },
+ { name: '璁惧D-02', type: '璁惧', scope1: 15.8, scope2: 11.2, scope3: 7.1, total: 34.1, efficiency: '涓�鑸�', status: '姝e父' },
+ { name: '鐢熶骇绾縀', type: '浜х嚎', scope1: 52.3, scope2: 39.6, scope3: 22.8, total: 114.7, efficiency: '寰呬紭鍖�', status: '鍛婅' }
+])
+
+// 鐢熸垚瀹炴椂鏁版嵁
+function generateRealtimeData() {
+ return Array.from({length: 24}, () => (Math.random() * 20 + 10).toFixed(1))
+}
+
+// 鐢熸垚鐑姏鍥炬暟鎹�
+function generateHeatmapData() {
+ const data = []
+ let yAxisLength = 7 // 榛樿璁惧绾�
+ let baseMultiplier = 1 // 鍩虹鍊嶆暟
+
+ // 鏍规嵁灞傜骇纭畾Y杞撮暱搴﹀拰鏁版嵁鑼冨洿
+ if (heatmapLevel.value === 'line') {
+ yAxisLength = 5
+ baseMultiplier = 2 // 浜х嚎绾ф暟鎹洿澶�
+ } else if (heatmapLevel.value === 'enterprise') {
+ yAxisLength = 3
+ baseMultiplier = 4 // 浼佷笟绾ф暟鎹渶澶�
+ }
+
+ for (let i = 0; i < yAxisLength; i++) {
+ for (let j = 0; j < 24; j++) {
+ let value
+ // 绠�鍖栫殑鏃堕棿娈甸�昏緫
+ if (j >= 8 && j <= 18) {
+ // 宸ヤ綔鏃堕棿鎺掓斁閲忚緝楂�
+ value = Math.random() * 30 + 20
+ } else if (j >= 19 && j <= 22) {
+ // 鏅氶棿鎺掓斁閲忎腑绛�
+ value = Math.random() * 20 + 10
+ } else {
+ // 娣卞鍜屽噷鏅ㄦ帓鏀鹃噺杈冧綆
+ value = Math.random() * 10 + 2
+ }
+
+ // 娣诲姞璁惧宸紓鍜屽眰绾у�嶆暟
+ value *= (0.8 + i * 0.1) * baseMultiplier
+
+ data.push([j, i, Math.round(value * 10) / 10])
+ }
+ }
+ return data
+}
+
+// 鏇存柊鑼冨洿鏁版嵁
+function updateScopeData() {
+ // 鏍规嵁閫夋嫨鐨勮寖鍥存洿鏂版墍鏈夌浉鍏冲浘琛ㄦ暟鎹�
+ const scopeMultiplier = {
+ 'all': 1,
+ 'scope1': 0.3,
+ 'scope2': 0.4,
+ 'scope3': 0.3
+ }
+
+ const multiplier = scopeMultiplier[selectedScope.value] || 1
+
+ // 鏇存柊纰虫帓鏀炬暟鎹樉绀�
+ if (selectedScope.value === 'all') {
+ carbonData.value = {
+ scope1: 125.6,
+ scope2: 89.3,
+ scope3: 234.7
+ }
+ } else {
+ const baseTotal = 125.6 + 89.3 + 234.7
+ carbonData.value = {
+ scope1: selectedScope.value === 'scope1' ? 125.6 : 0,
+ scope2: selectedScope.value === 'scope2' ? 89.3 : 0,
+ scope3: selectedScope.value === 'scope3' ? 234.7 : 0
+ }
+ }
+
+ // 鏇存柊鐑姏鍥炬暟鎹�
+ heatmapSeries.value[0].data = generateHeatmapData().map(item => [
+ item[0], item[1], Math.round(item[2] * multiplier * 10) / 10
+ ])
+
+ // 鏇存柊瀹炴椂鐩戞帶鏁版嵁
+ realtimeSeries.value[0].data = generateRealtimeData().map(val =>
+ Math.round(parseFloat(val) * multiplier * 10) / 10
+ )
+}
+
+// 鏇存柊鐑姏鍥惧眰绾�
+function updateHeatmapLevel() {
+ // 鏍规嵁灞傜骇鏇存柊Y杞存暟鎹拰visualMap鑼冨洿
+ if (heatmapLevel.value === 'device') {
+ heatmapYAxis[0].data = ['閿呯倝A', '鍘嬬缉鏈築', '鍐峰嵈濉擟', '椋庢満D', '娉礒', '鍙樺帇鍣‵', '鐢垫満G']
+ heatmapVisualMap.value.max = 50
+ } else if (heatmapLevel.value === 'line') {
+ heatmapYAxis[0].data = ['鐢熶骇绾�1', '鐢熶骇绾�2', '鐢熶骇绾�3', '鐢熶骇绾�4', '鐢熶骇绾�5']
+ heatmapVisualMap.value.max = 100
+ } else {
+ heatmapYAxis[0].data = ['鍘傚尯A', '鍘傚尯B', '鍘傚尯C']
+ heatmapVisualMap.value.max = 200
+ }
+
+ // 鏇存柊鐑姏鍥炬暟鎹�
+ heatmapSeries.value[0].data = generateHeatmapData()
+
+ // 鏇存柊琛ㄦ牸鏁版嵁浠ュ尮閰嶅綋鍓嶅眰绾�
+ updateTableDataForLevel()
+}
+
+// 鏍规嵁灞傜骇鏇存柊琛ㄦ牸鏁版嵁
+function updateTableDataForLevel() {
+ const levelConfigs = {
+ device: [
+ { name: '閿呯倝A', type: '璁惧', scope1: 45.2, scope2: 32.1, scope3: 18.7, total: 96.0, efficiency: '鑹ソ', status: '姝e父' },
+ { name: '鍘嬬缉鏈築', type: '璁惧', scope1: 38.5, scope2: 28.3, scope3: 15.2, total: 82.0, efficiency: '浼樼', status: '姝e父' },
+ { name: '鍐峰嵈濉擟', type: '璁惧', scope1: 22.8, scope2: 18.9, scope3: 12.3, total: 54.0, efficiency: '鑹ソ', status: '鍛婅' },
+ { name: '椋庢満D', type: '璁惧', scope1: 15.6, scope2: 12.4, scope3: 8.1, total: 36.1, efficiency: '涓�鑸�', status: '姝e父' },
+ { name: '娉礒', type: '璁惧', scope1: 12.3, scope2: 9.8, scope3: 6.4, total: 28.5, efficiency: '浼樼', status: '姝e父' }
+ ],
+ line: [
+ { name: '鐢熶骇绾�1', type: '浜х嚎', scope1: 125.6, scope2: 89.3, scope3: 56.8, total: 271.7, efficiency: '鑹ソ', status: '姝e父' },
+ { name: '鐢熶骇绾�2', type: '浜х嚎', scope1: 98.4, scope2: 72.1, scope3: 45.2, total: 215.7, efficiency: '浼樼', status: '姝e父' },
+ { name: '鐢熶骇绾�3', type: '浜х嚎', scope1: 87.2, scope2: 65.8, scope3: 41.6, total: 194.6, efficiency: '鑹ソ', status: '鍛婅' },
+ { name: '鐢熶骇绾�4', type: '浜х嚎', scope1: 76.9, scope2: 58.3, scope3: 37.1, total: 172.3, efficiency: '涓�鑸�', status: '姝e父' },
+ { name: '鐢熶骇绾�5', type: '浜х嚎', scope1: 65.7, scope2: 49.2, scope3: 31.8, total: 146.7, efficiency: '寰呬紭鍖�', status: '鍛婅' }
+ ],
+ enterprise: [
+ { name: '鍘傚尯A', type: '鍘傚尯', scope1: 456.8, scope2: 334.7, scope3: 212.5, total: 1004.0, efficiency: '鑹ソ', status: '姝e父' },
+ { name: '鍘傚尯B', type: '鍘傚尯', scope1: 387.2, scope2: 289.6, scope3: 184.3, total: 861.1, efficiency: '浼樼', status: '姝e父' },
+ { name: '鍘傚尯C', type: '鍘傚尯', scope1: 298.5, scope2: 223.8, scope3: 142.7, total: 665.0, efficiency: '鑹ソ', status: '鍛婅' }
+ ]
+ }
+
+ carbonTableData.value = levelConfigs[heatmapLevel.value] || levelConfigs.device
+}
+
+// 鏇存柊鐑姏鍥炬暟鎹紙鏃ユ湡鍙樺寲鏃讹級
+function updateHeatmapData() {
+ heatmapSeries.value[0].data = generateHeatmapData()
+
+ // 鍚屾椂鏇存柊鍏朵粬鐩稿叧鏁版嵁
+ updateScopeData()
+}
+
+// 鏇存柊瓒嬪娍鏁版嵁
+function updateTrendData() {
+ const trendDataConfigs = {
+ week: {
+ xAxisData: ['鍛ㄤ竴', '鍛ㄤ簩', '鍛ㄤ笁', '鍛ㄥ洓', '鍛ㄤ簲', '鍛ㄥ叚', '鍛ㄦ棩'],
+ scope1Data: [120, 132, 101, 134, 90, 80, 75],
+ scope2Data: [220, 182, 191, 234, 190, 150, 140],
+ scope3Data: [150, 232, 201, 154, 190, 120, 110]
+ },
+ month: {
+ xAxisData: ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�'],
+ scope1Data: [1200, 1150, 1300, 1250, 1180, 1320, 1280, 1350, 1220, 1290, 1160, 1100],
+ scope2Data: [2200, 2100, 2350, 2280, 2150, 2400, 2320, 2450, 2180, 2380, 2120, 2050],
+ scope3Data: [1800, 1750, 1950, 1880, 1820, 2000, 1920, 2100, 1850, 1980, 1780, 1720]
+ },
+ year: {
+ xAxisData: ['2019', '2020', '2021', '2022', '2023', '2024'],
+ scope1Data: [14500, 14200, 13800, 13500, 13100, 12800],
+ scope2Data: [26800, 26200, 25600, 25000, 24400, 23800],
+ scope3Data: [22400, 21800, 21200, 20600, 20000, 19400]
+ }
+ }
+
+ const config = trendDataConfigs[trendPeriod.value] || trendDataConfigs.week
+
+ // 鏇存柊X杞存暟鎹�
+ trendXAxis[0].data = config.xAxisData
+
+ // 鏇存柊绯诲垪鏁版嵁
+ trendSeries.value = [
+ {
+ name: '鑼冨洿1',
+ type: 'line',
+ data: config.scope1Data,
+ itemStyle: { color: '#FF6B6B' },
+ smooth: true
+ },
+ {
+ name: '鑼冨洿2',
+ type: 'line',
+ data: config.scope2Data,
+ itemStyle: { color: '#4ECDC4' },
+ smooth: true
+ },
+ {
+ name: '鑼冨洿3',
+ type: 'line',
+ data: config.scope3Data,
+ itemStyle: { color: '#45B7D1' },
+ smooth: true
+ }
+ ]
+}
+
+// 鑾峰彇鐘舵�佺被鍨�
+function getStatusType(status) {
+ switch (status) {
+ case '姝e父': return 'success'
+ case '鍛婅': return 'warning'
+ case '寮傚父': return 'danger'
+ default: return 'info'
+ }
+}
+
+// 瀵煎嚭鏁版嵁
+function exportData() {
+ // 鍑嗗瀵煎嚭鏁版嵁
+ const exportDataSet = {
+ 鍩烘湰淇℃伅: {
+ 瀵煎嚭鏃堕棿: new Date().toLocaleString('zh-CN'),
+ 鏁版嵁灞傜骇: heatmapLevel.value === 'device' ? '璁惧绾�' : heatmapLevel.value === 'line' ? '浜х嚎绾�' : '浼佷笟绾�',
+ 閫夋嫨鑼冨洿: selectedScope.value === 'all' ? '鍏ㄩ儴鑼冨洿' : `鑼冨洿${selectedScope.value.slice(-1)}`,
+ 閫夋嫨鏃ユ湡: selectedDate.value ? selectedDate.value.toLocaleDateString('zh-CN') : '浠婃棩'
+ },
+ 纰虫帓鏀剧粺璁�: {
+ 鑼冨洿1鐩存帴鎺掓斁: carbonData.value.scope1 + ' tCO鈧俥',
+ 鑼冨洿2闂存帴鎺掓斁: carbonData.value.scope2 + ' tCO鈧俥',
+ 鑼冨洿3渚涘簲閾炬帓鏀�: carbonData.value.scope3 + ' tCO鈧俥',
+ 鎬绘帓鏀鹃噺: totalEmissions.value + ' tCO鈧俥'
+ },
+ 璇︾粏鏁版嵁: carbonTableData.value,
+ 鐑姏鍥炬暟鎹�: heatmapSeries.value[0].data.map(item => ({
+ 鏃堕棿: `${item[0]}:00`,
+ 璁惧搴忓彿: item[1],
+ 璁惧鍚嶇О: heatmapYAxis.data[item[1]],
+ 纰虫帓鏀鹃噺: item[2] + ' tCO鈧俥'
+ }))
+ }
+
+ // 鍒涘缓CSV鍐呭
+ let csvContent = '\uFEFF' // BOM for UTF-8
+
+ // 鍩烘湰淇℃伅
+ csvContent += '鍩烘湰淇℃伅\n'
+ Object.entries(exportDataSet.鍩烘湰淇℃伅).forEach(([key, value]) => {
+ csvContent += `${key},${value}\n`
+ })
+ csvContent += '\n'
+
+ // 纰虫帓鏀剧粺璁�
+ csvContent += '纰虫帓鏀剧粺璁n'
+ Object.entries(exportDataSet.纰虫帓鏀剧粺璁�).forEach(([key, value]) => {
+ csvContent += `${key},${value}\n`
+ })
+ csvContent += '\n'
+
+ // 璇︾粏鏁版嵁琛ㄦ牸
+ csvContent += '璇︾粏鏁版嵁\n'
+ csvContent += '鍚嶇О,绫诲瀷,鑼冨洿1鎺掓斁,鑼冨洿2鎺掓斁,鑼冨洿3鎺掓斁,鎬绘帓鏀鹃噺,纰虫晥鐜�,鐘舵�乗n'
+ exportDataSet.璇︾粏鏁版嵁.forEach(row => {
+ csvContent += `${row.name},${row.type},${row.scope1},${row.scope2},${row.scope3},${row.total},${row.efficiency},${row.status}\n`
+ })
+ csvContent += '\n'
+
+ // 鐑姏鍥炬暟鎹紙鍓�50鏉★級
+ csvContent += '鐑姏鍥炬暟鎹紙鍓�50鏉★級\n'
+ csvContent += '鏃堕棿,璁惧鍚嶇О,纰虫帓鏀鹃噺\n'
+ exportDataSet.鐑姏鍥炬暟鎹�.slice(0, 50).forEach(row => {
+ csvContent += `${row.鏃堕棿},${row.璁惧鍚嶇О},${row.纰虫帓鏀鹃噺}\n`
+ })
+
+ // 鍒涘缓涓嬭浇閾炬帴
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
+ const link = document.createElement('a')
+ const url = URL.createObjectURL(blob)
+ link.setAttribute('href', url)
+ link.setAttribute('download', `纰虫帓鏀炬暟鎹甠${new Date().toISOString().slice(0, 10)}.csv`)
+ link.style.visibility = 'hidden'
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+
+ // 鏄剧ず鎴愬姛娑堟伅
+ console.log('纰虫帓鏀炬暟鎹鍑烘垚鍔�')
+}
+
+// 鎼滅储杩囨护鍔熻兘
+const filteredTableData = computed(() => {
+ if (!searchKeyword.value) {
+ return carbonTableData.value
+ }
+ return carbonTableData.value.filter(item =>
+ item.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
+ item.type.toLowerCase().includes(searchKeyword.value.toLowerCase())
+ )
+})
+
+// 鐑姏鍥剧偣鍑讳簨浠跺鐞�
+function handleHeatmapClick(params) {
+ if (params.componentType === 'series') {
+ const [hour, deviceIndex, value] = params.data
+ const deviceName = heatmapYAxis.data[deviceIndex]
+ console.log(`鐐瑰嚮浜嗚澶�: ${deviceName}, 鏃堕棿: ${hour}:00, 鎺掓斁閲�: ${value} tCO鈧俥`)
+
+ // 鍙互鍦ㄨ繖閲屾坊鍔犺缁嗕俊鎭脊绐楁垨璺宠浆鍒拌缁嗛〉闈�
+ }
+}
+
+onMounted(() => {
+ // 椤甸潰鍔犺浇瀹屾垚鍚庣殑鍒濆鍖栨搷浣�
+ console.log('纰崇鐞嗛〉闈㈠凡鍔犺浇')
+
+ // 鍒濆鍖栫儹鍔涘浘鏁版嵁
+ updateHeatmapLevel()
+
+ // 鍒濆鍖栬秼鍔挎暟鎹�
+ updateTrendData()
+
+ // 鍒濆鍖栬寖鍥存暟鎹�
+ updateScopeData()
+
+ // 璁剧疆瀹氭椂鍣紝姣�30绉掓洿鏂颁竴娆″疄鏃舵暟鎹�
+ const timer = setInterval(() => {
+ realtimeSeries.value[0].data = generateRealtimeData()
+ }, 30000)
+
+ // 娓呯悊瀹氭椂鍣�
+ onBeforeUnmount(() => {
+ clearInterval(timer)
+ })
+})
+
+// 娣诲姞鐑姏鍥剧偣鍑讳簨浠剁粦瀹�
+function bindHeatmapEvents() {
+ // 杩欎釜鍑芥暟鍙互鐢ㄦ潵缁戝畾鐑姏鍥剧殑鐐瑰嚮浜嬩欢
+ // 鍦ㄥ疄闄呬娇鐢ㄤ腑锛屽彲浠ラ�氳繃ECharts鐨勪簨浠剁郴缁熸潵瀹炵幇
+}
+</script>
+
+<style scoped>
+.carbon-management {
+ min-height: 100vh;
+ background:
+ radial-gradient(ellipse at top, rgba(29, 78, 216, 0.15), transparent 50%),
+ radial-gradient(ellipse at bottom, rgba(139, 92, 246, 0.15), transparent 50%),
+ linear-gradient(135deg, #0a0f1c 0%, #1e293b 25%, #0f172a 50%, #1e293b 75%, #0a0f1c 100%);
+ padding: 20px;
+ font-family: 'Inter', 'Microsoft YaHei', sans-serif;
+ overflow: hidden;
+ position: relative;
+}
+
+.carbon-management::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
+ radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.1) 0%, transparent 50%),
+ radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.05) 0%, transparent 50%);
+ pointer-events: none;
+
+}
+
+
+
+.page-header {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.95) 0%, rgba(30, 41, 59, 0.9) 100%),
+ radial-gradient(circle at top right, rgba(59, 130, 246, 0.1), transparent 50%);
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ border-radius: 20px;
+ padding: 40px;
+ margin-bottom: 30px;
+ box-shadow:
+ 0 25px 50px -12px rgba(0, 0, 0, 0.4),
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+ backdrop-filter: blur(20px);
+
+}
+
+.page-header:hover {
+ transform: translateY(-2px);
+ box-shadow:
+ 0 32px 64px -12px rgba(0, 0, 0, 0.5),
+ 0 0 0 1px rgba(255, 255, 255, 0.1),
+ inset 0 1px 0 rgba(255, 255, 255, 0.15);
+}
+
+.page-header::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ linear-gradient(45deg, rgba(59, 130, 246, 0.08) 0%, rgba(147, 51, 234, 0.08) 50%, rgba(236, 72, 153, 0.08) 100%);
+ pointer-events: none;
+
+}
+
+
+
+.header-content {
+ flex: 1;
+ position: relative;
+ z-index: 1;
+}
+
+.page-title {
+ font-size: 28px;
+ font-weight: bold;
+ color: #ffffff;
+ margin: 0 0 8px 0;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+.page-subtitle {
+ font-size: 14px;
+ color: #B8C8E0;
+ margin: 0;
+}
+
+.header-stats {
+ display: flex;
+ gap: 40px;
+ position: relative;
+ z-index: 1;
+}
+
+.stat-item {
+ text-align: center;
+ padding: 20px;
+ background:
+ linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(147, 51, 234, 0.15) 100%),
+ radial-gradient(circle at center, rgba(255, 255, 255, 0.05), transparent 70%);
+ border-radius: 12px;
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ position: relative;
+ overflow: hidden;
+
+ backdrop-filter: blur(10px);
+}
+
+.stat-item:hover {
+ transform: translateY(-2px) scale(1.05);
+ box-shadow:
+ 0 20px 25px -5px rgba(59, 130, 246, 0.3),
+ 0 10px 10px -5px rgba(59, 130, 246, 0.2);
+}
+
+.stat-item::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
+
+}
+
+.stat-item:hover::before {
+ left: 100%;
+}
+
+.stat-label {
+ display: block;
+ font-size: 12px;
+ color: #94A3B8;
+ margin-bottom: 8px;
+ font-weight: 500;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+}
+
+.stat-value {
+ display: block;
+ font-size: 28px;
+ font-weight: 700;
+ color: #00D4FF;
+ text-shadow:
+ 0 0 20px rgba(0, 212, 255, 0.6),
+ 0 0 40px rgba(0, 212, 255, 0.3);
+ position: relative;
+ z-index: 1;
+}
+
+.stat-value.reduction {
+ color: #00E676;
+ text-shadow:
+ 0 0 20px rgba(0, 230, 118, 0.6),
+ 0 0 40px rgba(0, 230, 118, 0.3);
+}
+
+.dashboard-content {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ min-height: calc(100vh - 200px);
+}
+
+.top-panels {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ gap: 20px;
+ height: 120px;
+}
+
+.data-panel {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
+ radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ border-radius: 16px;
+ padding: 20px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.2),
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(16px);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+}
+
+.panel-title {
+ font-size: 12px;
+ color: #94A3B8;
+ margin-bottom: 8px;
+ font-weight: 500;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+}
+
+.panel-value {
+ font-size: 24px;
+ font-weight: 700;
+ color: #00D4FF;
+ text-shadow:
+ 0 0 20px rgba(0, 212, 255, 0.6),
+ 0 0 40px rgba(0, 212, 255, 0.3);
+ margin-bottom: 4px;
+}
+
+.panel-subtitle {
+ font-size: 11px;
+ color: #B8C8E0;
+ font-weight: 400;
+}
+
+.unit {
+ font-size: 16px;
+ color: #94A3B8;
+}
+
+.center-main-view {
+ display: grid;
+ grid-template-columns: 200px 1fr 300px;
+ gap: 20px;
+ flex: 1;
+}
+
+.left-control-panel {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ border-radius: 16px;
+ padding: 20px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(16px);
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.control-section {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.section-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #ffffff;
+ margin-bottom: 8px;
+}
+
+.vertical-radio {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.main-heatmap {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ border-radius: 16px;
+ padding: 20px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(16px);
+ display: flex;
+ flex-direction: column;
+}
+
+.heatmap-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid rgba(148, 163, 184, 0.2);
+}
+
+.main-title {
+ font-size: 18px;
+ font-weight: bold;
+ color: #ffffff;
+ margin: 0;
+}
+
+.date-selector {
+ display: flex;
+ align-items: center;
+}
+
+.heatmap-view {
+ flex: 1;
+}
+
+.right-data-panel {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ border-radius: 16px;
+ padding: 20px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(16px);
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.data-section {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.mini-chart {
+ width: 100%;
+}
+
+.trend-controls {
+ margin-bottom: 10px;
+}
+
+.bottom-progress-panel {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 40px;
+ height: 100px;
+}
+
+.progress-section {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ border-radius: 16px;
+ padding: 20px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(16px);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.progress-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #ffffff;
+ margin-bottom: 8px;
+}
+
+.progress-data {
+ display: flex;
+ align-items: baseline;
+ gap: 4px;
+ margin-bottom: 12px;
+}
+
+.progress-data .current {
+ font-size: 20px;
+ font-weight: 700;
+ color: #00D4FF;
+}
+
+.progress-data .separator {
+ font-size: 16px;
+ color: #94A3B8;
+}
+
+.progress-data .target {
+ font-size: 14px;
+ color: #B8C8E0;
+}
+
+.bottom-data-table {
+ margin-top: 20px;
+}
+
+.table-panel {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
+ radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ border-radius: 16px;
+ padding: 20px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.2),
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(16px);
+}
+
+.table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid rgba(148, 163, 184, 0.2);
+}
+
+.table-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #ffffff;
+ margin: 0;
+}
+
+.table-controls {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.panel-card {
+ background:
+ linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
+ radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ border-radius: 16px;
+ padding: 24px;
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.3),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.2),
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ position: relative;
+ overflow: hidden;
+ backdrop-filter: blur(16px);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+
+
+.heatmap-card {
+ height: 500px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid rgba(81, 129, 219, 0.3);
+ position: relative;
+ z-index: 1;
+}
+
+.card-title {
+ font-size: 18px;
+ font-weight: bold;
+ color: #ffffff;
+ margin: 0;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+.heatmap-controls {
+ display: flex;
+ align-items: center;
+}
+
+.scope-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ position: relative;
+ z-index: 1;
+}
+
+.scope-item {
+ display: flex;
+ align-items: center;
+ padding: 20px;
+ border-radius: 12px;
+ background:
+ linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(147, 51, 234, 0.12) 100%),
+ radial-gradient(circle at top, rgba(255, 255, 255, 0.05), transparent 60%);
+ border-left: 4px solid #00D4FF;
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ position: relative;
+ overflow: hidden;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ backdrop-filter: blur(8px);
+}
+
+.scope-item:hover {
+ transform: translateY(-3px);
+ box-shadow:
+ 0 15px 30px -5px rgba(59, 130, 246, 0.25),
+ 0 0 0 1px rgba(255, 255, 255, 0.1);
+}
+
+.scope-item::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, #3B82F6, #8B5CF6, #EC4899);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.scope-item:hover::after {
+ opacity: 1;
+}
+
+/* 纰虫帓鏀剧粺璁℃牱寮� */
+.carbon-stats {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 20px;
+ gap: 15px;
+}
+
+.carbon-stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ padding: 20px;
+ background:
+ linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(147, 51, 234, 0.12) 100%),
+ radial-gradient(circle at top, rgba(255, 255, 255, 0.05), transparent 60%);
+ border-radius: 12px;
+ border: 1px solid rgba(148, 163, 184, 0.15);
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ backdrop-filter: blur(8px);
+}
+
+.carbon-stat-item:hover {
+ transform: translateY(-3px);
+ box-shadow:
+ 0 15px 30px -5px rgba(59, 130, 246, 0.25),
+ 0 0 0 1px rgba(255, 255, 255, 0.1);
+}
+
+.carbon-stat-item::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, #3B82F6, #8B5CF6, #EC4899);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.carbon-stat-item:hover::after {
+ opacity: 1;
+}
+
+.carbon-label {
+ color: #94A3B8;
+ font-size: 11px;
+ text-align: center;
+ font-weight: 500;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+}
+
+.carbon-value {
+ color: #00D4FF;
+ font-size: 18px;
+ font-weight: 700;
+ text-shadow:
+ 0 0 15px rgba(0, 212, 255, 0.6),
+ 0 0 30px rgba(0, 212, 255, 0.3);
+ position: relative;
+}
+
+.scope-item.scope1 {
+ border-left-color: #FF6B6B;
+}
+
+.scope-item.scope2 {
+ border-left-color: #FFD93D;
+}
+
+.scope-item.scope3 {
+ border-left-color: #6BCF7F;
+}
+
+.scope-icon {
+ font-size: 24px;
+ margin-right: 15px;
+}
+
+.scope-info {
+ flex: 1;
+}
+
+.scope-name {
+ display: block;
+ font-weight: bold;
+ color: #ffffff;
+ margin-bottom: 5px;
+}
+
+.scope-value {
+ display: block;
+ font-size: 20px;
+ font-weight: bold;
+ color: #00D4FF;
+ margin-bottom: 3px;
+ text-shadow: 0 0 8px rgba(0, 212, 255, 0.5);
+}
+
+.scope-desc {
+ display: block;
+ font-size: 12px;
+ color: #B8C8E0;
+}
+
+.target-progress {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ position: relative;
+ z-index: 1;
+}
+
+.progress-item {
+ padding: 15px;
+ background: rgba(81, 129, 219, 0.1);
+ border-radius: 8px;
+ border: 1px solid rgba(81, 129, 219, 0.2);
+}
+
+.progress-info {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.progress-label {
+ font-weight: bold;
+ color: #ffffff;
+}
+
+.progress-value {
+ color: #B8C8E0;
+ font-size: 14px;
+}
+
+.bottom-panel {
+ margin-top: 20px;
+}
+
+.table-controls {
+ display: flex;
+ align-items: center;
+}
+
+/* Element Plus 缁勪欢娣辫壊涓婚鏍峰紡 */
+:deep(.el-table) {
+ background: transparent !important;
+ color: #ffffff !important;
+}
+
+:deep(.el-table th) {
+ background: rgba(81, 129, 219, 0.2) !important;
+ color: #ffffff !important;
+ border-bottom: 1px solid rgba(81, 129, 219, 0.3) !important;
+}
+
+:deep(.el-table td) {
+ background: transparent !important;
+ color: #B8C8E0 !important;
+ border-bottom: 1px solid rgba(81, 129, 219, 0.1) !important;
+}
+
+:deep(.el-table tr:hover > td) {
+ background: rgba(81, 129, 219, 0.1) !important;
+}
+
+:deep(.el-input__wrapper) {
+ background: rgba(15, 27, 46, 0.8) !important;
+ border: 1px solid rgba(81, 129, 219, 0.3) !important;
+ color: #ffffff !important;
+}
+
+:deep(.el-input__inner) {
+ color: #ffffff !important;
+}
+
+:deep(.el-button--primary) {
+ background: linear-gradient(135deg, #5181DB, #D369E0) !important;
+ border: none !important;
+ box-shadow: 0 0 10px rgba(81, 129, 219, 0.5) !important;
+}
+
+/* 鍨傜洿鍗曢�夋寜閽粍鏍峰紡 */
+:deep(.vertical-radio) {
+ display: flex !important;
+ flex-direction: column !important;
+ gap: 6px !important;
+}
+
+:deep(.vertical-radio .el-radio-button) {
+ margin: 0 !important;
+ width: 100% !important;
+}
+
+:deep(.vertical-radio .el-radio-button__inner) {
+ background: rgba(59, 130, 246, 0.1) !important;
+ border: 1px solid rgba(148, 163, 184, 0.2) !important;
+ color: #B8C8E0 !important;
+ border-radius: 8px !important;
+ padding: 10px 16px !important;
+ width: 100% !important;
+ text-align: center !important;
+ font-size: 12px !important;
+ font-weight: 500 !important;
+}
+
+:deep(.vertical-radio .el-radio-button__inner:hover) {
+ background: rgba(59, 130, 246, 0.2) !important;
+ border-color: rgba(59, 130, 246, 0.4) !important;
+ color: #ffffff !important;
+}
+
+:deep(.vertical-radio .el-radio-button.is-active .el-radio-button__inner) {
+ background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important;
+ border-color: #3B82F6 !important;
+ color: #ffffff !important;
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
+}
+
+:deep(.vertical-radio .el-radio-button:first-child .el-radio-button__inner) {
+ border-left: 1px solid rgba(148, 163, 184, 0.2) !important;
+}
+
+:deep(.el-radio-group .el-radio-button__inner) {
+ background: rgba(59, 130, 246, 0.1) !important;
+ border: 1px solid rgba(148, 163, 184, 0.2) !important;
+ color: #B8C8E0 !important;
+ border-radius: 6px !important;
+ padding: 6px 12px !important;
+ margin: 0 2px !important;
+ font-size: 12px !important;
+}
+
+:deep(.el-radio-group .el-radio-button__inner:hover) {
+ background: rgba(59, 130, 246, 0.2) !important;
+ border-color: rgba(59, 130, 246, 0.4) !important;
+ color: #ffffff !important;
+}
+
+:deep(.el-radio-group .el-radio-button.is-active .el-radio-button__inner) {
+ background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important;
+ border-color: #3B82F6 !important;
+ color: #ffffff !important;
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
+}
+
+:deep(.el-date-editor .el-input__wrapper) {
+ background: rgba(15, 27, 46, 0.8) !important;
+ border: 1px solid rgba(81, 129, 219, 0.3) !important;
+}
+
+:deep(.el-progress-bar__outer) {
+ background: rgba(81, 129, 219, 0.2) !important;
+}
+
+:deep(.el-tag) {
+ background: rgba(81, 129, 219, 0.2) !important;
+ border: 1px solid rgba(81, 129, 219, 0.3) !important;
+ color: #ffffff !important;
+}
+
+:deep(.el-tag.el-tag--success) {
+ background: rgba(0, 230, 118, 0.2) !important;
+ border-color: rgba(0, 230, 118, 0.3) !important;
+ color: #00E676 !important;
+}
+
+:deep(.el-tag.el-tag--warning) {
+ background: rgba(255, 193, 7, 0.2) !important;
+ border-color: rgba(255, 193, 7, 0.3) !important;
+ color: #FFC107 !important;
+}
+
+:deep(.el-tag.el-tag--danger) {
+ background: rgba(244, 67, 54, 0.2) !important;
+ border-color: rgba(244, 67, 54, 0.3) !important;
+ color: #F44336 !important;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 1200px) {
+ .main-content {
+ flex-direction: column;
+ }
+
+ .header-stats {
+ gap: 20px;
+ }
+}
+
+@media (max-width: 768px) {
+ .page-header {
+ flex-direction: column;
+ text-align: center;
+ gap: 20px;
+ }
+
+ .header-stats {
+ justify-content: center;
+ }
+
+ .carbon-management {
+ padding: 10px;
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/energyManagement/dynamicEnergySaving/index.vue b/src/views/energyManagement/dynamicEnergySaving/index.vue
index 2666e0f..30b5f0f 100644
--- a/src/views/energyManagement/dynamicEnergySaving/index.vue
+++ b/src/views/energyManagement/dynamicEnergySaving/index.vue
@@ -237,21 +237,21 @@
version: 'v2.1.0',
status: 'active',
accuracy: '94.2%',
- lastUpdate: '2024-01-15 14:30:00'
+ lastUpdate: '2025-01-15 14:30:00'
},
{
modelName: '鍦板眰鍘嬪姏棰勬祴妯″瀷',
version: 'v1.8.5',
status: 'active',
accuracy: '91.7%',
- lastUpdate: '2024-01-14 09:15:00'
+ lastUpdate: '2025-01-14 09:15:00'
},
{
modelName: '鑳借�楀垎鏋愭ā鍨�',
version: 'v2.0.3',
status: 'standby',
accuracy: '89.3%',
- lastUpdate: '2024-01-13 16:45:00'
+ lastUpdate: '2025-01-13 16:45:00'
}
])
diff --git a/src/views/energyManagement/energyPeriodTime/index.vue b/src/views/energyManagement/energyPeriodTime/index.vue
index 0c94322..49bb226 100644
--- a/src/views/energyManagement/energyPeriodTime/index.vue
+++ b/src/views/energyManagement/energyPeriodTime/index.vue
@@ -16,7 +16,8 @@
v-model="searchForm.date"
type="date"
placeholder="璇烽�夋嫨鏃ユ湡"
- :size="size"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
/>
<!-- <el-time-picker
v-model="searchForm.timeRange"
@@ -41,6 +42,7 @@
</div>
<div>
<el-button type="primary" @click="openForm('add')">鏂板</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
@@ -160,7 +162,7 @@
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
-import {onMounted, ref} from "vue";
+import {onMounted, ref, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import {periodListPage,periodDelete,periodAdd,periodUpdate} from "@/api/energyManagement/index.js";
@@ -173,7 +175,7 @@
},
form: {
date: "",
- price: "",
+ price: "",
peak: "",
valley: "",
flat: "",
@@ -183,7 +185,7 @@
const { searchForm,form } = toRefs(data);
const page = ref({
current: 1,
- size: 10,
+ size: 100,
total: 0
});
const dialogFormVisible = ref(false);
@@ -316,14 +318,14 @@
};
const getList = () => {
tableLoading.value = true;
- periodListPage({ ...searchForm, ...page.value }).then((res) => {
+ periodListPage({ ...searchForm.value, ...page.value }).then((res) => {
tableLoading.value = false;
if (res && res.data) {
tableData.value = res.data.records || [];
- page.total = res.data.total || 0;
+ page.value.total = res.data.total || 0;
} else {
tableData.value = [];
- page.total = 0;
+ page.value.total = 0;
ElMessageBox.warning('鏈幏鍙栧埌鏁版嵁');
}
})
@@ -434,6 +436,22 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/energyPeriod/export", {}, "鐢ㄧ數鏃舵绠$悊.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/energyManagement/energyPower/components/formDia.vue b/src/views/energyManagement/energyPower/components/formDia.vue
index 518a254..3cb4455 100644
--- a/src/views/energyManagement/energyPower/components/formDia.vue
+++ b/src/views/energyManagement/energyPower/components/formDia.vue
@@ -122,6 +122,7 @@
import {ref} from "vue";
import useUserStore from "@/store/modules/user.js";
import {deviceList, equipmentEnergyAdd, equipmentEnergyUpdate, areaListTree} from "@/api/energyManagement/index.js";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -217,14 +218,6 @@
dialogFormVisible.value = false;
emit('close')
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
defineExpose({
openDialog,
});
diff --git a/src/views/energyManagement/energyPower/index.vue b/src/views/energyManagement/energyPower/index.vue
index 6d7ba83..6311019 100644
--- a/src/views/energyManagement/energyPower/index.vue
+++ b/src/views/energyManagement/energyPower/index.vue
@@ -18,6 +18,7 @@
<div>
<el-button type="primary" @click="openForm('add')">鏂板</el-button>
<el-button type="info" plain icon="Upload" @click="handleImport">瀵煎叆</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
@@ -83,7 +84,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
-import {onMounted, ref} from "vue";
+import {onMounted, ref, getCurrentInstance} from "vue";
import FormDia from "@/views/energyManagement/energyPower/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
@@ -295,6 +296,22 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/equipmentEnergyConsumption/export", {}, "鑳芥簮鍔熺巼.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/energyManagement/energyTrends/index.vue b/src/views/energyManagement/energyTrends/index.vue
index 0e7bcbf..7f67be7 100644
--- a/src/views/energyManagement/energyTrends/index.vue
+++ b/src/views/energyManagement/energyTrends/index.vue
@@ -14,6 +14,7 @@
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
+ <el-button @click="handleOut" style="margin-left: 10px">瀵煎嚭</el-button>
</div>
</div>
<div class="table_list">
@@ -34,8 +35,11 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
-import {onMounted, ref} from "vue";
+import {onMounted, ref, getCurrentInstance} from "vue";
import {listPageByTrend} from "@/api/energyManagement/index.js";
+import { ElMessageBox } from "element-plus";
+
+const { proxy } = getCurrentInstance();
const data = reactive({
searchForm: {
@@ -76,6 +80,7 @@
},
]);
const tableData = ref([]);
+const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
current: 1,
@@ -106,6 +111,22 @@
page.total = res.data.total;
});
};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/equipmentEnergyConsumption/exportTwo", {}, "鑳芥簮瓒嬪娍.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/energyManagement/gasManagement/index.vue b/src/views/energyManagement/gasManagement/index.vue
index 59ca9f7..f067f7c 100644
--- a/src/views/energyManagement/gasManagement/index.vue
+++ b/src/views/energyManagement/gasManagement/index.vue
@@ -295,7 +295,7 @@
// 妯℃嫙鏁版嵁鐢熸垚
const generateMockData = () => {
// 鏇存柊缁熻鏁版嵁
- totalDevices.value = Math.floor(Math.random() * 10) + 15 // 15-25鍙拌澶�
+ totalDevices.value = Math.floor(Math.random() * 8) // 0-7鍙拌澶�
dailyConsumption.value = Math.floor(Math.random() * 100) + 200 // 200-300 m鲁
monthlyConsumption.value = Math.floor(Math.random() * 2000) + 5000 // 5000-7000 m鲁
gasUnitPrice.value = (Math.random() * 2 + 3).toFixed(2) // 3-5鍏�/m鲁
diff --git a/src/views/energyManagement/meterCollection/index.vue b/src/views/energyManagement/meterCollection/index.vue
index 8b2636b..dfa5617 100644
--- a/src/views/energyManagement/meterCollection/index.vue
+++ b/src/views/energyManagement/meterCollection/index.vue
@@ -227,7 +227,7 @@
power: '75.5',
powerFactor: '0.85',
status: '姝e父',
- lastUpdateTime: '2024-01-15 10:30:00'
+ lastUpdateTime: '2025-01-15 10:30:00'
},
{
id: 2,
@@ -241,7 +241,7 @@
power: '45.2',
powerFactor: '0.92',
status: '姝e父',
- lastUpdateTime: '2024-01-15 10:25:00'
+ lastUpdateTime: '2025-01-15 10:25:00'
}
]
this.pagination.total = this.meterList.length
@@ -408,7 +408,7 @@
power: '50.0',
powerFactor: '0.85',
status: '姝e父',
- lastUpdateTime: '2024-01-15 12:00:00'
+ lastUpdateTime: '2025-01-15 12:00:00'
}
this.detailDialogVisible = true
},
diff --git a/src/views/energyManagement/waterManagement/components/formDia.vue b/src/views/energyManagement/waterManagement/components/formDia.vue
index 2e58ea0..bf605ca 100644
--- a/src/views/energyManagement/waterManagement/components/formDia.vue
+++ b/src/views/energyManagement/waterManagement/components/formDia.vue
@@ -128,6 +128,7 @@
import {ref, reactive, nextTick} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterEquipmentAdd, waterEquipmentUpdate} from "@/api/energyManagement/waterManagement.js";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -203,14 +204,6 @@
dialogFormVisible.value = false;
emit('close')
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
defineExpose({
openDialog,
});
diff --git a/src/views/energyManagement/waterManagement/components/waterBillForm.vue b/src/views/energyManagement/waterManagement/components/waterBillForm.vue
index a132041..667fe10 100644
--- a/src/views/energyManagement/waterManagement/components/waterBillForm.vue
+++ b/src/views/energyManagement/waterManagement/components/waterBillForm.vue
@@ -109,6 +109,7 @@
import {ref, reactive, nextTick, watch} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterBillAdd, waterBillUpdate} from "@/api/energyManagement/waterManagement.js";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -192,14 +193,6 @@
dialogFormVisible.value = false;
emit('close')
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
defineExpose({
openDialog,
});
diff --git a/src/views/energyManagement/waterManagement/index.vue b/src/views/energyManagement/waterManagement/index.vue
index 181ae88..4ada029 100644
--- a/src/views/energyManagement/waterManagement/index.vue
+++ b/src/views/energyManagement/waterManagement/index.vue
@@ -18,6 +18,7 @@
<div>
<el-button type="primary" @click="openForm('add')">鏂板</el-button>
<el-button type="info" plain icon="Upload" @click="handleImport">瀵煎叆</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
@@ -83,7 +84,7 @@
<script setup>
import {Search} from "@element-plus/icons-vue";
-import {onMounted, ref, reactive, nextTick} from "vue";
+import {onMounted, ref, reactive, nextTick, getCurrentInstance} from "vue";
import FormDia from "@/views/energyManagement/waterManagement/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
@@ -302,6 +303,22 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/waterRecord/export", {}, "鐢ㄦ按绠$悊.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/equipmentManagement/brand/index.vue b/src/views/equipmentManagement/brand/index.vue
new file mode 100644
index 0000000..6607cc8
--- /dev/null
+++ b/src/views/equipmentManagement/brand/index.vue
@@ -0,0 +1,217 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍝佺墝鍚嶇О/鍥藉">
+ <el-input
+ v-model="filters.name"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ュ叧閿瘝"
+ clearable
+ prefix-icon="Search"
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="openAdd" icon="Plus"> 鏂板 </el-button>
+ <el-button
+ type="danger"
+ icon="Delete"
+ :disabled="multipleSelection.length <= 0"
+ @click="handleBatchDelete"
+ >鎵归噺鍒犻櫎</el-button>
+ </div>
+ </div>
+
+ <PIMTable
+ rowKey="id"
+ isSelection
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="changePage"
+ >
+ </PIMTable>
+ </div>
+
+ <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close>
+ <el-form :model="form" ref="formRef" :rules="rules" label-width="90px">
+ <el-form-item label="鍝佺墝鍚嶇О" prop="name">
+ <el-input v-model="form.name" placeholder="璇疯緭鍏ュ搧鐗屽悕绉�" />
+ </el-form-item>
+ <el-form-item label="鎵�灞炲浗瀹�" prop="country">
+ <el-input v-model="form.country" placeholder="璇疯緭鍏ュ浗瀹�/鍦板尯" />
+ </el-form-item>
+ <el-form-item label="鎻忚堪" prop="description">
+ <el-input v-model="form.description" type="textarea" :rows="3" placeholder="鍙~鍐欏搧鐗岀畝浠�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="visible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+
+</template>
+
+<script setup>
+import { ref, getCurrentInstance, onMounted } from 'vue'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import { usePaginationApi } from '@/hooks/usePaginationApi'
+import { getBrandPage, addBrand, editBrand, delBrand } from '@/api/equipmentManagement/brand'
+
+defineOptions({ name: '璁惧鍝佺墝绠$悊' })
+
+const { proxy } = getCurrentInstance()
+
+const multipleSelection = ref([])
+const formRef = ref()
+const visible = ref(false)
+const dialogTitle = ref('鏂板鍝佺墝')
+const form = ref({ id: undefined, name: '', country: '', description: '' })
+
+const rules = {
+ name: [{ required: true, message: '璇疯緭鍏ュ搧鐗屽悕绉�', trigger: 'blur' }],
+ country: [{ required: true, message: '璇疯緭鍏ユ墍灞炲浗瀹�', trigger: 'blur' }]
+}
+
+const {
+ filters,
+ columns,
+ dataList,
+ pagination,
+ getTableData,
+ resetFilters,
+ onCurrentChange,
+} = usePaginationApi(
+ getBrandPage,
+ { name: undefined },
+ [
+ { label: '鍝佺墝鍚嶇О', align: 'center', prop: 'name' },
+ { label: '鎵�灞炲浗瀹�', align: 'center', prop: 'country' },
+ { label: '鎻忚堪', align: 'center', prop: 'description' },
+ { label: '鍒涘缓鏃堕棿', align: 'center', prop: 'createdAt' },
+ {
+ dataType: 'action',
+ label: '鎿嶄綔',
+ align: 'center',
+ fixed: 'right',
+ width: 140,
+ operation: [
+ {
+ name: '缂栬緫',
+ type: 'text',
+ clickFun: (row) => openEdit(row),
+ },
+ {
+ name: '鍒犻櫎',
+ type: 'text',
+ clickFun: (row) => handleDelete(row.id),
+ }
+ ]
+ }
+ ]
+)
+
+const handleSelectionChange = (list) => {
+ multipleSelection.value = list
+}
+
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page
+ pagination.pageSize = limit
+ onCurrentChange(page)
+}
+
+function resetForm() {
+ form.value = { id: undefined, name: '', country: '', description: '' }
+}
+
+function openAdd() {
+ resetForm()
+ dialogTitle.value = '鏂板鍝佺墝'
+ visible.value = true
+}
+
+function openEdit(row) {
+ form.value = { id: row.id, name: row.name, country: row.country, description: row.description }
+ dialogTitle.value = '缂栬緫鍝佺墝'
+ visible.value = true
+}
+
+function handleSubmit() {
+ formRef.value.validate(async (valid) => {
+ if (!valid) return
+ const isEdit = Boolean(form.value.id)
+ const api = isEdit ? editBrand : addBrand
+ const { code, msg } = await api({ ...form.value })
+ if (code === 200) {
+ ElMessage.success(isEdit ? '淇敼鎴愬姛' : '鏂板鎴愬姛')
+ visible.value = false
+ getTableData()
+ } else {
+ ElMessage.error(msg || '鎿嶄綔澶辫触')
+ }
+ })
+}
+
+function handleDelete(id) {
+ ElMessageBox.confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ュ搧鐗�, 鏄惁缁х画?', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }).then(async () => {
+ const { code } = await delBrand(id)
+ if (code === 200) {
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getTableData()
+ }
+ })
+}
+
+function handleBatchDelete() {
+ if (multipleSelection.value.length === 0) return
+ ElMessageBox.confirm('灏嗗垹闄ら�変腑鐨勫搧鐗岋紝鏄惁缁х画锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }).then(async () => {
+ const ids = multipleSelection.value.map((i) => i.id)
+ const { code } = await delBrand(ids)
+ if (code === 200) {
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getTableData()
+ }
+ })
+}
+
+onMounted(() => {
+ getTableData()
+})
+
+</script>
+
+<style scoped lang="scss">
+.table_list { margin-top: unset; }
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+</style>
+
+
diff --git a/src/views/equipmentManagement/calibration/index.vue b/src/views/equipmentManagement/calibration/index.vue
index 7dfd405..e3eaef6 100644
--- a/src/views/equipmentManagement/calibration/index.vue
+++ b/src/views/equipmentManagement/calibration/index.vue
@@ -34,6 +34,7 @@
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
+ <el-button @click="handleReset" style="margin-left: 10px">閲嶇疆</el-button>
</div>
<div>
<el-button @click="handleOut">瀵煎嚭</el-button>
@@ -56,11 +57,11 @@
</template>
<script setup>
-import {onMounted, ref} from "vue";
-import {ElMessageBox} from "element-plus";
+import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
+import {ElMessageBox, ElMessage} from "element-plus";
import useUserStore from "@/store/modules/user.js";
import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue";
-import {ledgerRecordListPage} from "@/api/equipmentManagement/calibration.js";
+import {ledgerRecordListPage, ledgerRecordDelete} from "@/api/equipmentManagement/calibration.js";
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -134,6 +135,7 @@
{
dataType: "action",
label: "鎿嶄綔",
+ width: 140,
align: "center",
fixed: 'right',
operation: [
@@ -143,9 +145,16 @@
clickFun: (row) => {
openCalibrationDia("edit", row);
},
- disabled: (row) => {
- return row.userId !== userStore.id
- }
+ },
+ {
+ name: "鍒犻櫎",
+ type: "text",
+ style: {
+ color: "#F56C6C"
+ },
+ clickFun: (row) => {
+ handleDelete(row);
+ },
},
],
},
@@ -171,6 +180,15 @@
page.current = 1;
getList();
};
+
+// 閲嶇疆鎼滅储鏉′欢
+const handleReset = () => {
+ searchForm.value.recordDate = "";
+ searchForm.value.entryDate = "";
+ searchForm.value.code = "";
+ page.current = 1;
+ getList();
+};
const pagination = (obj) => {
page.current = obj.page;
page.size = obj.limit;
@@ -194,6 +212,26 @@
})
}
+// 鍒犻櫎璁板綍
+const handleDelete = (row) => {
+ ElMessageBox.confirm(`纭鍒犻櫎璁¢噺鍣ㄥ叿缂栧彿涓�"${row.code}"鐨勬瀹氳褰曞悧锛焋, "鍒犻櫎纭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ ledgerRecordDelete([row.id]).then(() => {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getList();
+ }).catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑堝垹闄�");
+ });
+};
+
// 瀵煎嚭
const handleOut = () => {
ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
diff --git a/src/views/equipmentManagement/defectManagement/index.vue b/src/views/equipmentManagement/defectManagement/index.vue
new file mode 100644
index 0000000..f35454f
--- /dev/null
+++ b/src/views/equipmentManagement/defectManagement/index.vue
@@ -0,0 +1,221 @@
+<template>
+ <div class="defect-management">
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div class="actions">
+ <el-button type="primary" @click="showRegisterDialog = true">鐧昏缂洪櫡</el-button>
+ </div>
+
+ <!-- 缂洪櫡鍒楄〃 -->
+ <el-table :data="defectList" style="width: 100%; margin-top: 10px;" border>
+ <el-table-column prop="deviceName" label="璁惧鍚嶇О" width="180"></el-table-column>
+ <el-table-column prop="defectDescription" label="缂洪櫡鎻忚堪" win-width="300"></el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="220">
+ <template #default="{ row }">
+ <el-tag :type="row.status === '涓ラ噸缂洪櫡' ? 'danger' : 'success'">
+ {{ row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="220">
+ <template #default="{ row }">
+ <el-button
+ v-if="row.status === '涓ラ噸缂洪櫡' || row.status === '涓�鑸己闄�'"
+ type="text"
+ @click="eliminateDefect(row)"
+ >
+ 娑堥櫎缂洪櫡
+ </el-button>
+ <!-- <el-button
+ v-if="row.status === '涓ラ噸缂洪櫡'"
+ type="text"
+ @click="transferToRepairOrder(row.id)"
+ >
+ 杞淮淇崟
+ </el-button> -->
+ <el-button type="text" @click="getLedger(row.deviceLedgerId)">
+ 鏌ョ湅鍙拌处
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 缂洪櫡鐧昏瀵硅瘽妗� -->
+ <el-dialog title="鐧昏璁惧缂洪櫡" v-model="showRegisterDialog" width="50%">
+ <el-form :model="defectForm" :rules="defectRules" ref="defectFormRef" label-width="100px">
+ <el-form-item label="璁惧鍚嶇О" prop="deviceName">
+ <el-select v-model="defectForm.deviceLedgerId" @change="setDeviceModel">
+ <el-option
+ v-for="(item, index) in deviceOptions"
+ :key="index"
+ :label="item.deviceName"
+ :value="item.id"
+ ></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="缂洪櫡鎻忚堪" prop="defectDescription">
+ <el-input type="textarea" v-model="defectForm.defectDescription"></el-input>
+ </el-form-item>
+ <el-form-item label="璁惧鐘舵��" prop="status">
+ <el-radio-group v-model="defectForm.status">
+ <el-radio label="姝e父">姝e父</el-radio>
+ <el-radio label="涓�鑸己闄�">涓�鑸己闄�</el-radio>
+ <el-radio label="涓ラ噸缂洪櫡">涓ラ噸缂洪櫡</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="showRegisterDialog = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitDefectForm">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
+
+ <!-- 缂洪櫡璁惧鍙拌处瀵硅瘽妗� -->
+ <el-dialog title="缂洪櫡璁惧鍙拌处" v-model="showLedgerDialog" width="80%">
+ <el-table :data="ledgerList" style="width: 100%; margin-top: 10px;" border>
+ <el-table-column prop="deviceName" label="璁惧鍚嶇О"></el-table-column>
+ <el-table-column prop="defectDescription" label="缂洪櫡鎻忚堪"></el-table-column>
+ <el-table-column prop="status" label="鐘舵��"></el-table-column>
+ <el-table-column prop="eliminateTime" label="娑堢己鏃堕棿"></el-table-column>
+ </el-table>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+// 鍋囪浠ヤ笅鏄悗绔帴鍙�
+import {
+ registerDefect,
+ getDefectList,
+ eliminateDefect as apiEliminateDefect,
+ getDefectLedger,
+ deleteDefect
+} from '@/api/equipmentManagement/defectManagement';
+
+// 缂洪櫡鍒楄〃
+const defectList = ref([]);
+// 鐧昏瀵硅瘽妗嗘樉绀虹姸鎬�
+const showRegisterDialog = ref(false);
+// 鍙拌处瀵硅瘽妗嗘樉绀虹姸鎬�
+const showLedgerDialog = ref(false);
+// 缂洪櫡琛ㄥ崟
+const defectForm = reactive({
+ deviceLedgerId: '',
+ defectDescription: '',
+ status: '',
+});
+const deviceOptions = ref([]);
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const defectRules = reactive({
+ deviceLedgerId: [{ required: true, message: '璇疯緭鍏ヨ澶囧悕绉�', trigger: 'blur' }],
+ defectDescription: [{ required: true, message: '璇疯緭鍏ョ己闄锋弿杩�', trigger: 'blur' }]
+});
+// 琛ㄥ崟寮曠敤
+const defectFormRef = ref(null);
+// 鍙拌处鍒楄〃
+const ledgerList = ref([]);
+
+const loadDeviceName = async () => {
+ const { data } = await getDeviceLedger();
+ // console.log(data);
+ deviceOptions.value = data;
+};
+
+// 鑾峰彇缂洪櫡鍒楄〃
+const fetchDefectList = async () => {
+ try {
+ const res = await getDefectList();
+ if (res.code === 200) {
+ defectList.value = res.data.records;
+ } else {
+ ElMessage.error(res.message || '鑾峰彇缂洪櫡鍒楄〃澶辫触');
+ }
+ } catch (error) {
+ ElMessage.error('鑾峰彇缂洪櫡鍒楄〃澶辫触');
+ }
+};
+
+// 鎻愪氦缂洪櫡鐧昏琛ㄥ崟
+const submitDefectForm = async () => {
+ if (!defectFormRef.value) return;
+ try {
+ await defectFormRef.value.validate();
+ const res = await registerDefect(defectForm);
+ if (res.code === 200) {
+ ElMessage.success('缂洪櫡鐧昏鎴愬姛');
+ showRegisterDialog.value = false;
+ fetchDefectList();
+ } else {
+ ElMessage.error(res.message || '缂洪櫡鐧昏澶辫触');
+ }
+ } catch (error) {
+ ElMessage.error('璇峰~鍐欏畬鏁磋〃鍗曚俊鎭�');
+ }
+};
+
+// 娑堥櫎缂洪櫡
+const eliminateDefect = async (row) => {
+
+ try {
+ const res = await apiEliminateDefect(row);
+ if (res.code === 200) {
+ ElMessage.success('缂洪櫡娑堥櫎鎴愬姛');
+ fetchDefectList();
+ } else {
+ ElMessage.error(res.message || '缂洪櫡娑堥櫎澶辫触');
+ }
+ } catch (error) {
+ ElMessage.error('缂洪櫡娑堥櫎澶辫触');
+ }
+};
+
+// // 杞淮淇伐鍗�
+// const transferToRepairOrder = async (id) => {
+// try {
+// const res = await transferToRepair(id);
+// if (res.code === 200) {
+// ElMessage.success('杞淮淇伐鍗曟垚鍔�');
+// } else {
+// ElMessage.error(res.message || '杞淮淇伐鍗曞け璐�');
+// }
+// } catch (error) {
+// ElMessage.error('杞淮淇伐鍗曞け璐�');
+// }
+// };
+
+// 鑾峰彇缂洪櫡璁惧鍙拌处
+const getLedger = async (deviceLedgerId) => {
+ try {
+ const res = await getDefectLedger(deviceLedgerId);
+ if (res.code === 200) {
+ ledgerList.value = res.data.records;
+ showLedgerDialog.value = true;
+ } else {
+ ElMessage.error(res.message || '鑾峰彇缂洪櫡璁惧鍙拌处澶辫触');
+ }
+ } catch (error) {
+ ElMessage.error('鑾峰彇缂洪櫡璁惧鍙拌处澶辫触');
+ }
+};
+
+// 缁勪欢鎸傝浇鏃惰幏鍙栫己闄峰垪琛�
+const onMounted = () => {
+ fetchDefectList();
+ loadDeviceName();
+};
+onMounted();
+</script>
+
+<style scoped>
+.defect-management {
+ padding: 20px;
+}
+
+.actions {
+ margin-bottom: 10px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/deviceInfo/index.vue b/src/views/equipmentManagement/deviceInfo/index.vue
new file mode 100644
index 0000000..de162cc
--- /dev/null
+++ b/src/views/equipmentManagement/deviceInfo/index.vue
@@ -0,0 +1,190 @@
+<template>
+ <div class="device-info-container">
+ <div class="page-header">
+ <h1>璁惧淇℃伅</h1>
+ <div class="device-status" :class="deviceStatusClass">
+ {{ deviceStatusText }}
+ </div>
+ </div>
+
+ <div class="info-card">
+ <div class="card-header">鍩烘湰淇℃伅</div>
+ <div class="card-content">
+ <div class="info-row">
+ <span class="label">璁惧鍚嶇О锛�</span>
+ <span class="value">{{ deviceInfo.deviceName }}</span>
+ </div>
+ <div class="info-row">
+ <span class="label">瑙勬牸鍨嬪彿锛�</span>
+ <span class="value">{{ deviceInfo.deviceModel }}</span>
+ </div>
+ <div class="info-row">
+ <span class="label">鐢熶骇鍘傚锛�</span>
+ <span class="value">{{ deviceInfo.supplierName }}</span>
+ </div>
+ <div class="info-row">
+ <span class="label">鍗曚綅锛�</span>
+ <span class="value">{{ deviceInfo.unit }}</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="info-card">
+ <div class="card-header">缁存姢淇℃伅</div>
+ <div class="card-content">
+ <div class="maintenance-info">
+ <div class="maintenance-item">
+ <span class="label">鏈�鍚庣淮鎶わ細</span>
+ <span class="value">{{ deviceInfo.updateTime }}</span>
+ </div>
+ <div class="maintenance-item">
+ <span class="label">涓嬫缁存姢锛�</span>
+ <span class="value">{{ deviceInfo.createTime }}</span>
+ </div>
+ <div class="maintenance-item">
+ <span class="label">缁存姢鐘舵�侊細</span>
+ <span class="value status-normal">{{ deviceInfo.statusText }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from 'vue'
+import { useRoute } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import {
+ getDeviceInfo,
+} from '@/api/equipmentManagement/deviceInfo'
+
+const route = useRoute()
+
+const deviceInfo = reactive({
+ deviceName: '',
+ deviceModel: '',
+ supplierName: '',
+ unit: '',
+ statusText:'姝e父',
+ updateTime:'',
+ createTime:''
+})
+
+const deviceStatusClass = computed(() => {
+ return 'status-normal'
+})
+
+const deviceStatusText = computed(() => {
+ return '姝e父'
+})
+
+const fetchDeviceInfo = async (deviceId) => {
+ try {
+ // 鑾峰彇璁惧淇℃伅
+ const deviceResponse = await getDeviceInfo({id:deviceId})
+ if (deviceResponse.code === 200) {
+ Object.assign(deviceInfo, deviceResponse.data)
+ }
+
+
+ } catch (error) {
+
+ ElMessage.warning('浣跨敤妯℃嫙鏁版嵁锛屽疄闄匒PI璋冪敤澶辫触')
+ }
+}
+
+onMounted(() => {
+ const deviceId = route.query.deviceId || route.params.deviceId || ''
+ fetchDeviceInfo(deviceId)
+})
+</script>
+
+<style scoped>
+.device-info-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ padding: 20px;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+.page-header {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 16px;
+ padding: 20px;
+ margin-bottom: 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.page-header h1 {
+ margin: 0;
+ color: #2c3e50;
+ font-size: 24px;
+}
+
+.device-status {
+ padding: 8px 16px;
+ border-radius: 20px;
+ font-size: 14px;
+ color: white;
+ background: #52c41a;
+}
+
+.info-card {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 16px;
+ margin-bottom: 20px;
+ overflow: hidden;
+}
+
+.card-header {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 16px 20px;
+ font-weight: 500;
+}
+
+.card-content {
+ padding: 20px;
+}
+
+.info-row, .maintenance-item {
+ display: flex;
+ margin-bottom: 12px;
+ align-items: center;
+}
+
+.label {
+ width: 100px;
+ color: #666;
+ font-size: 14px;
+}
+
+.value {
+ flex: 1;
+ color: #2c3e50;
+ font-weight: 500;
+}
+
+.status-normal {
+ color: #52c41a;
+}
+
+
+
+@media (max-width: 768px) {
+ .device-info-container {
+ padding: 16px;
+ }
+
+ .page-header h1 {
+ font-size: 20px;
+ }
+
+ .label {
+ width: 80px;
+ }
+}
+</style>
diff --git a/src/views/equipmentManagement/gasTank/simple.vue b/src/views/equipmentManagement/gasTank/simple.vue
index 92e88df..4cb2d76 100644
--- a/src/views/equipmentManagement/gasTank/simple.vue
+++ b/src/views/equipmentManagement/gasTank/simple.vue
@@ -233,7 +233,7 @@
const maintenanceRecords = ref([
{
id: 1,
- date: '2024-01-15',
+ date: '2025-01-15',
type: 'inspection',
title: '骞村害妫�楠�',
description: '鎸夌収TSG 21-2016鏍囧噯杩涜骞村害妫�楠岋紝璁惧鐘舵�佽壇濂�',
@@ -241,7 +241,7 @@
},
{
id: 2,
- date: '2024-02-20',
+ date: '2025-02-20',
type: 'maintenance',
title: '瀹夊叏闃�缁存姢',
description: '鏇存崲瀹夊叏闃�瀵嗗皝鍦堬紝鏍″噯鍘嬪姏璁惧畾鍊�',
@@ -249,7 +249,7 @@
},
{
id: 3,
- date: '2024-03-10',
+ date: '2025-03-10',
type: 'inspection',
title: '鍘嬪姏娴嬭瘯',
description: '杩涜鍘嬪姏瀹瑰櫒姘村帇璇曢獙锛岀鍚堣璁¤姹�',
diff --git a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
new file mode 100644
index 0000000..3202d32
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -0,0 +1,302 @@
+<template>
+ <div>
+ <el-dialog :title="operationType === 'add' ? '鏂板宸℃浠诲姟' : '缂栬緫宸℃浠诲姟'"
+ v-model="dialogVisitable" width="800px" @close="cancel">
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="璁惧鍚嶇О" prop="taskId">
+ <el-select v-model="form.taskId" @change="setDeviceModel" filterable>
+ <el-option
+ v-for="(item, index) in deviceOptions"
+ :key="index"
+ :label="item.deviceName"
+ :value="item.id"
+ ></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="宸℃浜�" prop="inspector">
+ <el-select v-model="form.inspector" filterable
+ default-first-option
+ :reserve-keyword="false" placeholder="璇烽�夋嫨" multiple clearable>
+ <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="澶囨敞" prop="remarks">
+ <el-input v-model="form.remarks" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐧昏鏃堕棿" prop="dateStr">
+ <el-date-picker
+ v-model="form.dateStr"
+ type="date"
+ placeholder="閫夋嫨鐧昏鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
+ <el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="姣忔棩" value="DAILY"/>
+ <el-option label="姣忓懆" value="WEEKLY"/>
+ <el-option label="姣忔湀" value="MONTHLY"/>
+ <!-- <el-option label="瀛e害" value="QUARTERLY"/> -->
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-time-picker v-model="form.frequencyDetail" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+ value-format="HH:mm" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
+ <el-option label="鍛ㄤ竴" value="MON"/>
+ <el-option label="鍛ㄤ簩" value="TUE"/>
+ <el-option label="鍛ㄤ笁" value="WED"/>
+ <el-option label="鍛ㄥ洓" value="THU"/>
+ <el-option label="鍛ㄤ簲" value="FRI"/>
+ <el-option label="鍛ㄥ叚" value="SAT"/>
+ <el-option label="鍛ㄦ棩" value="SUN"/>
+ </el-select>
+ <el-time-picker v-model="form.time" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+ value-format="HH:mm" style="width: 50%"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-date-picker
+ v-model="form.frequencyDetail"
+ type="datetime"
+ clearable
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ format="DD,HH:mm"
+ value-format="DD,HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-date-picker
+ v-model="form.frequencyDetail"
+ type="datetime"
+ clearable
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ format="MM,DD,HH:mm"
+ value-format="MM,DD,HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="cancel">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">淇濆瓨</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {reactive, ref, getCurrentInstance, toRefs} from "vue";
+import useUserStore from '@/store/modules/user'
+import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js";
+import {userListNoPageByTenantId} from "@/api/system/user.js";
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits()
+const userStore = useUserStore()
+const dialogVisitable = ref(false);
+const operationType = ref('add');
+const deviceOptions = ref([]);
+const data = reactive({
+ form: {
+ taskId: undefined,
+ taskName: undefined,
+ inspector: '',
+ inspectorIds: '',
+ remarks: '',
+ frequencyType: '',
+ frequencyDetail: '',
+ week: '',
+ time: '',
+ dateStr: ''
+ },
+ rules: {
+ taskId: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
+ inspector: [{ required: true, message: "璇疯緭鍏ュ贰妫�浜�", trigger: "blur" },],
+ dateStr: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }],
+ frequencyType: [{ required: true, message: "璇烽�夋嫨浠诲姟棰戠巼", trigger: "change" }],
+ frequencyDetail: [
+ {
+ required: true,
+ message: "璇烽�夋嫨鏃ユ湡",
+ trigger: "change",
+ validator: (rule, value, callback) => {
+ if (!form.value.frequencyType) {
+ callback()
+ return
+ }
+ if (form.value.frequencyType === 'WEEKLY') {
+ if (!form.value.week || !form.value.time) {
+ callback(new Error("璇烽�夋嫨鏃ユ湡鍜屾椂闂�"))
+ } else {
+ callback()
+ }
+ } else {
+ if (!value) {
+ callback(new Error("璇烽�夋嫨鏃ユ湡"))
+ } else {
+ callback()
+ }
+ }
+ }
+ }
+ ],
+ week: [
+ {
+ required: true,
+ message: "璇烽�夋嫨鏄熸湡",
+ trigger: "change",
+ validator: (rule, value, callback) => {
+ if (form.value.frequencyType === 'WEEKLY' && !value) {
+ callback(new Error("璇烽�夋嫨鏄熸湡"))
+ } else {
+ callback()
+ }
+ }
+ }
+ ],
+ time: [
+ {
+ required: true,
+ message: "璇烽�夋嫨鏃堕棿",
+ trigger: "change",
+ validator: (rule, value, callback) => {
+ if (form.value.frequencyType === 'WEEKLY' && !value) {
+ callback(new Error("璇烽�夋嫨鏃堕棿"))
+ } else {
+ callback()
+ }
+ }
+ }
+ ]
+ }
+})
+const { form, rules } = toRefs(data)
+const userList = ref([])
+
+const loadDeviceName = async () => {
+ const { data } = await getDeviceLedger();
+ deviceOptions.value = data;
+};
+
+const setDeviceModel = (id) => {
+ const option = deviceOptions.value.find((item) => item.id === id);
+ if (option) {
+ form.value.taskName = option.deviceName;
+ }
+}
+
+// 鎵撳紑寮规
+const openDialog = async (type, row) => {
+ dialogVisitable.value = true
+ operationType.value = type
+
+ // 閲嶇疆琛ㄥ崟
+ resetForm();
+
+ // 鍔犺浇鐢ㄦ埛鍒楄〃
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data;
+ });
+
+ // 鍔犺浇璁惧鍒楄〃
+ await loadDeviceName();
+
+ if (type === 'edit' && row) {
+ form.value = {...row}
+ form.value.inspector = form.value.inspectorIds.split(',').map(Number)
+
+ // 濡傛灉鏈夎澶嘔D锛岃嚜鍔ㄨ缃澶囦俊鎭�
+ if (form.value.taskId) {
+ setDeviceModel(form.value.taskId);
+ }
+ }
+}
+
+// 鍏抽棴瀵硅瘽妗�
+const cancel = () => {
+ resetForm()
+ dialogVisitable.value = false
+ emit('closeDia')
+}
+
+// 閲嶇疆琛ㄥ崟鍑芥暟
+const resetForm = () => {
+ if (proxy.$refs.formRef) {
+ proxy.$refs.formRef.resetFields()
+ }
+ // 閲嶇疆琛ㄥ崟鏁版嵁纭繚璁惧淇℃伅姝g‘閲嶇疆
+ form.value = {
+ taskId: undefined,
+ taskName: undefined,
+ inspector: '',
+ inspectorIds: '',
+ remarks: '',
+ frequencyType: '',
+ frequencyDetail: '',
+ week: '',
+ time: ''
+ }
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate(async valid => {
+ if (valid) {
+ try {
+ form.value.inspectorIds = form.value.inspector.join(',')
+ delete form.value.inspector
+
+ if (form.value.frequencyType === 'WEEKLY') {
+ let frequencyDetail = ''
+ frequencyDetail = form.value.week + ',' + form.value.time
+ form.value.frequencyDetail = frequencyDetail
+ }
+
+ let res = await userStore.getInfo()
+ form.value.registrantId = res.user.userId
+
+ await addOrEditTimingTask(form.value)
+ cancel()
+ proxy.$modal.msgSuccess('鎻愪氦鎴愬姛')
+ } catch (error) {
+ proxy.$modal.msgError('鎻愪氦澶辫触锛岃閲嶈瘯')
+ }
+ }
+ })
+}
+defineExpose({ openDialog })
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/inspectionManagement/components/qrCodeDia.vue b/src/views/equipmentManagement/inspectionManagement/components/qrCodeDia.vue
new file mode 100644
index 0000000..136c18c
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/qrCodeDia.vue
@@ -0,0 +1,132 @@
+<template>
+ <div>
+ <el-dialog :title="operationType === 'add' ? '鏂板浜岀淮鐮�' : '缂栬緫浜岀淮鐮�'"
+ v-model="dialogVisitable" width="500px" @close="cancel">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="璁惧鍚嶇О" prop="deviceName">
+ <el-input v-model="form.deviceName" placeholder="璇疯緭鍏ヨ澶囧悕绉�" maxlength="30" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鎵�鍦ㄤ綅缃弿杩�" prop="location">
+ <el-input v-model="form.location" placeholder="璇疯緭鍏ユ墍鍦ㄤ綅缃弿杩�" maxlength="30"/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <div>
+ <el-button type="primary" @click="submitForm">鐢熸垚骞舵墦鍗颁簩缁寸爜</el-button>
+ </div>
+ <div v-if="isShowQrCode" class="print-section" ref="qrCodeContainer" id="qrCodeContainer">
+ <vue-qrcode :value="qrCodeValue" :width="qrCodeSize"></vue-qrcode>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import useUserStore from "@/store/modules/user.js";
+import {reactive, ref} from "vue";
+import printJS from 'print-js';
+import {addOrEditQrCode} from "@/api/inspectionUpload/index.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits()
+const userStore = useUserStore()
+const dialogVisitable = ref(false);
+const isShowQrCode = ref(false);
+const operationType = ref('add');
+
+const qrCodeValue = ref('');
+const qrCodeSize = ref(100);
+const data = reactive({
+ form: {
+ deviceName: '',
+ location: '',
+ qrCodeId: '',
+ id: ''
+ },
+ rules: {
+ deviceName: [{ required: true, message: '璇疯緭鍏ヨ澶囧悕绉�', trigger: 'blur' }],
+ location: [{ required: true, message: '璇疯緭鍏ュ湴鐐�', trigger: 'blur' }]
+ }
+})
+const { form, rules } = toRefs(data)
+
+
+// 鎵撳紑寮规
+const openDialog = async (type, row) => {
+ dialogVisitable.value = true
+ qrCodeValue.value = ''
+ isShowQrCode.value = false;
+ if (type === 'edit') {
+ form.value.id = row.id
+ form.value.qrCodeId = row.id
+ form.value.deviceName = row.deviceName
+ form.value.location = row.location
+ // 灏嗚〃鍗曟暟鎹浆涓� JSON 瀛楃涓蹭綔涓轰簩缁寸爜鍐呭
+ qrCodeValue.value = JSON.stringify(form.value);
+ isShowQrCode.value = true;
+ }
+}
+// 鎻愪氦鍚堝苟琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ addOrEditQrCode(form.value).then((res) => {
+ form.value.qrCodeId = res.data
+ })
+ // 灏嗚〃鍗曟暟鎹浆涓� JSON 瀛楃涓蹭綔涓轰簩缁寸爜鍐呭
+ qrCodeValue.value = JSON.stringify(form.value);
+ isShowQrCode.value = true;
+ showQrCode()
+ }
+ })
+}
+const showQrCode = () => {
+ // 寤惰繜鎵ц鎵撳嵃锛岄伩鍏� DOM 鏇存柊鍓嶅氨璋冪敤鎵撳嵃
+ setTimeout(() => {
+ printJS({
+ printable: 'qrCodeContainer',//椤甸潰
+ type: "html",//鏂囨。绫诲瀷
+ maxWidth: 360,
+ style: `@page {
+ margin:0;
+ size: 400px 75px collapse;
+ margin-top:3px;
+ &:first-of-type{
+ margin-top:0 !important;
+ }
+ }
+ html{
+ zoom:100%;
+ }
+ @media print{
+ width: 400px;
+ height: 75px;
+ margin:0;
+ }`,
+ targetStyles: ["*"], // 浣跨敤dom鐨勬墍鏈夋牱寮忥紝寰堥噸瑕�
+ font_size: '0.20cm',
+ });
+ }, 300);
+}
+// 鍏抽棴鍚堝苟琛ㄥ崟
+const cancel = () => {
+ proxy.resetForm("formRef")
+ dialogVisitable.value = false
+ emit('closeDia')
+}
+defineExpose({ openDialog })
+</script>
+
+<style scoped>
+.print-section {
+ text-align: center;
+ margin-top: 30px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
new file mode 100644
index 0000000..f0deddb
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -0,0 +1,246 @@
+<template>
+ <div>
+ <el-dialog title="鏌ョ湅闄勪欢"
+ v-model="dialogVisitable" width="800px" @close="cancel">
+ <div class="upload-container">
+ <!-- 鐢熶骇鍓� -->
+ <div class="form-container">
+ <div class="title">鐢熶骇鍓�</div>
+
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in beforeProductionImgs" :key="index"
+ @click="showMedia(beforeProductionImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in beforeProductionVideos"
+ :key="index"
+ @click="showMedia(beforeProductionVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鐢熶骇鍚� -->
+ <div class="form-container">
+ <div class="title">鐢熶骇涓�</div>
+
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in afterProductionImgs" :key="index"
+ @click="showMedia(afterProductionImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in afterProductionVideos"
+ :key="index"
+ @click="showMedia(afterProductionVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鐢熶骇闂 -->
+ <div class="form-container">
+ <div class="title">鐢熶骇鍚�</div>
+
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in productionIssuesImgs" :key="index"
+ @click="showMedia(productionIssuesImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in productionIssuesVideos"
+ :key="index"
+ @click="showMedia(productionIssuesVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+
+ <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+ <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
+ <div class="media-viewer-content" @click.stop>
+ <!-- 鍥剧墖 -->
+ <vue-easy-lightbox
+ v-if="mediaType === 'image'"
+ :visible="isMediaViewerVisible"
+ :imgs="mediaList"
+ :index="currentMediaIndex"
+ @hide="closeMediaViewer"
+ ></vue-easy-lightbox>
+
+ <!-- 瑙嗛 -->
+ <div v-else-if="mediaType === 'video'" style="position: relative;">
+ <Video
+ :src="mediaList[currentMediaIndex]"
+ autoplay
+ controls
+ style="max-width: 90vw; max-height: 80vh;"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+<script setup>
+import { ref } from 'vue';
+import VueEasyLightbox from 'vue-easy-lightbox';
+
+// 鎺у埗寮圭獥鏄剧ず
+const dialogVisitable = ref(false);
+
+// 鍥剧墖鏁扮粍
+const beforeProductionImgs = ref([]);
+const afterProductionImgs = ref([]);
+const productionIssuesImgs = ref([]);
+
+// 瑙嗛鏁扮粍
+const beforeProductionVideos = ref([]);
+const afterProductionVideos = ref([]);
+const productionIssuesVideos = ref([]);
+
+// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
+const isMediaViewerVisible = ref(false);
+const currentMediaIndex = ref(0);
+const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
+const mediaType = ref('image'); // image | video
+
+// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
+function processItems(items) {
+ const images = [];
+ const videos = [];
+ items.forEach(item => {
+ if (item.contentType?.startsWith('image/')) {
+ images.push(item.url);
+ } else if (item.contentType?.startsWith('video/')) {
+ videos.push(item.url);
+ }
+ });
+ return { images, videos };
+}
+
+// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
+const openDialog = async (row) => {
+ const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
+ const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
+ const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues);
+
+ beforeProductionImgs.value = beforeImgs;
+ beforeProductionVideos.value = beforeVids;
+
+ afterProductionImgs.value = afterImgs;
+ afterProductionVideos.value = afterVids;
+
+ productionIssuesImgs.value = issueImgs;
+ productionIssuesVideos.value = issueVids;
+
+ dialogVisitable.value = true;
+};
+
+// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
+function showMedia(mediaArray, index, type) {
+ mediaList.value = mediaArray;
+ currentMediaIndex.value = index;
+ mediaType.value = type;
+ isMediaViewerVisible.value = true;
+}
+
+// 鍏抽棴濯掍綋鏌ョ湅鍣�
+function closeMediaViewer() {
+ isMediaViewerVisible.value = false;
+ mediaList.value = [];
+ mediaType.value = 'image';
+}
+
+// 琛ㄥ崟鍏抽棴鏂规硶
+const cancel = () => {
+ dialogVisitable.value = false;
+};
+
+defineExpose({ openDialog });
+</script>
+<style scoped lang="scss">
+.upload-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ border: 1px solid #dcdfe6;
+ box-sizing: border-box;
+
+ .form-container {
+ flex: 1;
+ width: 100%;
+ margin-bottom: 20px;
+ }
+}
+
+.title {
+ font-size: 14px;
+ color: #165dff;
+ line-height: 20px;
+ font-weight: 600;
+ padding-left: 10px;
+ position: relative;
+ margin: 6px 0;
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 3px;
+ width: 4px;
+ height: 14px;
+ background-color: #165dff;
+ }
+}
+
+.media-viewer-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.8);
+ z-index: 9999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.media-viewer-content {
+ position: relative;
+ max-width: 90vw;
+ max-height: 90vh;
+ overflow: hidden;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/inspectionManagement/components/viewQrCodeFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/viewQrCodeFiles.vue
new file mode 100644
index 0000000..f8e923a
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/viewQrCodeFiles.vue
@@ -0,0 +1,169 @@
+<template>
+ <div>
+ <el-dialog title="鏌ョ湅闄勪欢"
+ v-model="dialogVisitable" width="800px" @close="cancel">
+ <div class="upload-container">
+ <div class="form-container">
+ <div class="title">宸℃闄勪欢</div>
+ <!-- 鍥剧墖鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <img v-for="(item, index) in beforeProductionImgs" :key="index"
+ @click="showMedia(beforeProductionImgs, index, 'image')"
+ :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+ </div>
+
+ <!-- 瑙嗛鍒楄〃 -->
+ <div style="display: flex; flex-wrap: wrap;">
+ <div
+ v-for="(videoUrl, index) in beforeProductionVideos"
+ :key="index"
+ @click="showMedia(beforeProductionVideos, index, 'video')"
+ style="position: relative; margin: 10px; cursor: pointer;"
+ >
+ <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+ <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ </div>
+ <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+ <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+ <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
+ <div class="media-viewer-content" @click.stop>
+ <!-- 鍥剧墖 -->
+ <vue-easy-lightbox
+ v-if="mediaType === 'image'"
+ :visible="isMediaViewerVisible"
+ :imgs="mediaList"
+ :index="currentMediaIndex"
+ @hide="closeMediaViewer"
+ ></vue-easy-lightbox>
+
+ <!-- 瑙嗛 -->
+ <div v-else-if="mediaType === 'video'" style="position: relative;">
+ <Video
+ :src="mediaList[currentMediaIndex]"
+ autoplay
+ controls
+ style="max-width: 90vw; max-height: 80vh;"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+// 鎺у埗寮圭獥鏄剧ず
+import VueEasyLightbox from "vue-easy-lightbox";
+
+const dialogVisitable = ref(false);
+// 鍥剧墖鏁扮粍
+const beforeProductionImgs = ref([]);
+// 瑙嗛鏁扮粍
+const beforeProductionVideos = ref([]);
+// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
+const isMediaViewerVisible = ref(false);
+const currentMediaIndex = ref(0);
+const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
+const mediaType = ref('image'); // image | video
+
+// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
+const openDialog = async (row) => {
+ const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO);
+
+ beforeProductionImgs.value = beforeImgs;
+ beforeProductionVideos.value = beforeVids;
+ dialogVisitable.value = true;
+};
+// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
+function showMedia(mediaArray, index, type) {
+ mediaList.value = mediaArray;
+ currentMediaIndex.value = index;
+ mediaType.value = type;
+ isMediaViewerVisible.value = true;
+}
+// 鍏抽棴濯掍綋鏌ョ湅鍣�
+function closeMediaViewer() {
+ isMediaViewerVisible.value = false;
+ mediaList.value = [];
+ mediaType.value = 'image';
+}
+// 琛ㄥ崟鍏抽棴鏂规硶
+const cancel = () => {
+ dialogVisitable.value = false;
+};
+// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
+function processItems(items) {
+ const images = [];
+ const videos = [];
+ items.forEach(item => {
+ if (item.contentType?.startsWith('image/')) {
+ images.push(item.url);
+ } else if (item.contentType?.startsWith('video/')) {
+ videos.push(item.url);
+ }
+ });
+ return { images, videos };
+}
+defineExpose({ openDialog });
+</script>
+
+<style scoped lang="scss">
+.upload-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ border: 1px solid #dcdfe6;
+ box-sizing: border-box;
+
+ .form-container {
+ flex: 1;
+ width: 100%;
+ margin-bottom: 20px;
+ }
+}
+
+.title {
+ font-size: 14px;
+ color: #165dff;
+ line-height: 20px;
+ font-weight: 600;
+ padding-left: 10px;
+ position: relative;
+ margin: 6px 0;
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 3px;
+ width: 4px;
+ height: 14px;
+ background-color: #165dff;
+ }
+}
+
+.media-viewer-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.8);
+ z-index: 9999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.media-viewer-content {
+ position: relative;
+ max-width: 90vw;
+ max-height: 90vh;
+ overflow: hidden;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
new file mode 100644
index 0000000..245f3d2
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -0,0 +1,354 @@
+<template>
+ <div class="app-container">
+ <el-form :inline="true" :model="queryParams" class="search-form">
+ <el-form-item label="宸℃浠诲姟鍚嶇О">
+ <el-input
+ v-model="queryParams.taskName"
+ placeholder="璇疯緭鍏ュ贰妫�浠诲姟鍚嶇О"
+ clearable
+ :style="{ width: '100%' }"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <el-card>
+ <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;">
+ <el-radio-group v-model="activeRadio" @change="radioChange">
+ <el-radio-button v-for="tab in radios"
+ :key="tab.name"
+ :label="tab.label"
+ :value="tab.name"/>
+ </el-radio-group>
+ <!-- 鎿嶄綔鎸夐挳鍖� -->
+ <el-space v-if="activeRadio !== 'task'">
+ <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">鏂板缓</el-button>
+ <el-button type="danger" :icon="Delete" @click="handleDelete">鍒犻櫎</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </el-space>
+ <el-space v-else>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </el-space>
+ </div>
+ <div>
+ <div>
+ <PIMTable :table-loading="tableLoading"
+ :table-data="tableData"
+ :column="tableColumns"
+ @selection-change="handleSelectionChange"
+ :is-selection="true"
+ :border="true"
+ :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }"
+ :page="{
+ current: pageNum,
+ size: pageSize,
+ total: total,
+ }"
+ @pagination="pagination"
+ >
+ <template #inspector="{ row }">
+ <div class="person-tags">
+ <!-- 璋冭瘯淇℃伅锛屼笂绾挎椂鍒犻櫎 -->
+ <!-- {{ console.log('inspector data:', row.inspector) }} -->
+ <template v-if="row.inspector && row.inspector.length > 0">
+ <el-tag
+ v-for="(person, index) in row.inspector"
+ :key="index"
+ size="small"
+ type="primary"
+ class="person-tag"
+ >
+ {{ person }}
+ </el-tag>
+ </template>
+ <span v-else class="no-data">--</span>
+ </div>
+ </template>
+ </PIMTable>
+ </div>
+ </div>
+ </el-card>
+ <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
+ <view-files ref="viewFiles"></view-files>
+ </div>
+</template>
+
+<script setup>
+import { Delete, Plus } from "@element-plus/icons-vue";
+import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue";
+import { ElMessageBox } from "element-plus";
+
+// 缁勪欢寮曞叆
+import Pagination from "@/components/Pagination/index.vue";
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
+import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
+import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
+
+// 鎺ュ彛寮曞叆
+import {
+ delTimingTask,
+ inspectionTaskList,
+ timingTaskList
+} from "@/api/inspectionManagement/index.js";
+
+// 鍏ㄥ眬鍙橀噺
+const { proxy } = getCurrentInstance();
+const formDia = ref();
+const viewFiles = ref();
+
+// 鏌ヨ鍙傛暟
+const queryParams = reactive({
+ taskName: "",
+});
+
+// 鍗曢�夋閰嶇疆
+const activeRadio = ref("taskManage");
+const radios = reactive([
+ { name: "taskManage", label: "瀹氭椂浠诲姟绠$悊" },
+ { name: "task", label: "瀹氭椂浠诲姟璁板綍" },
+]);
+
+// 琛ㄦ牸鏁版嵁
+const selectedRows = ref([]);
+const tableData = ref([]);
+const operationsArr = ref([]);
+const tableColumns = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const pageNum = ref(1);
+const pageSize = ref(10);
+
+// 鍒楅厤缃�
+const columns = ref([
+ { prop: "taskName", label: "宸℃浠诲姟鍚嶇О", minWidth: 160 },
+ { prop: "remarks", label: "澶囨敞", minWidth: 150 },
+ { prop: "inspector", label: "鎵ц宸℃浜�", minWidth: 150, slot: "inspector" },
+ {
+ prop: "frequencyType",
+ label: "棰戞",
+ minWidth: 150,
+ formatData: (cell) => ({
+ DAILY: "姣忔棩",
+ WEEKLY: "姣忓懆",
+ MONTHLY: "姣忔湀",
+ QUARTERLY: "瀛e害"
+ }[cell] || "")
+ },
+ {
+ prop: "frequencyDetail",
+ label: "寮�濮嬫棩鏈熶笌鏃堕棿",
+ minWidth: 150,
+ formatter: (row, column, cellValue) => {
+ // 鍏堝垽鏂槸鍚︽槸瀛楃涓�
+ if (typeof cellValue !== 'string') return '';
+ let val = cellValue;
+ const replacements = {
+ MON: '鍛ㄤ竴',
+ TUE: '鍛ㄤ簩',
+ WED: '鍛ㄤ笁',
+ THU: '鍛ㄥ洓',
+ FRI: '鍛ㄤ簲',
+ SAT: '鍛ㄥ叚',
+ SUN: '鍛ㄦ棩'
+ };
+ // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
+ return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
+ }
+ },
+ { prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
+ { prop: "dateStr", label: "鐧昏鏃ユ湡", minWidth: 100 },
+]);
+
+// 鎿嶄綔鍒楅厤缃�
+const getOperationColumn = (operations) => {
+ if (!operations || operations.length === 0) return null;
+
+ const operationConfig = {
+ label: "鎿嶄綔",
+ width: 130,
+ fixed: "right",
+ dataType: "action",
+ operation: operations.map(op => {
+ switch (op) {
+ case 'edit':
+ return {
+ name: "缂栬緫",
+ clickFun: handleAdd,
+ color: "#409EFF"
+ };
+ case 'viewFile':
+ return {
+ name: "鏌ョ湅闄勪欢",
+ clickFun: viewFile,
+ color: "#67C23A"
+ };
+ default:
+ return null;
+ }
+ }).filter(Boolean)
+ };
+
+ return operationConfig;
+};
+
+onMounted(() => {
+ radioChange('taskManage');
+});
+
+// 鍗曢�夊彉鍖�
+const radioChange = (value) => {
+ if (value === "taskManage") {
+ const operationColumn = getOperationColumn(['edit']);
+ tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
+ operationsArr.value = ['edit'];
+ } else if (value === "task") {
+ const operationColumn = getOperationColumn(['viewFile']);
+ tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
+ operationsArr.value = ['viewFile'];
+ }
+ pageNum.value = 1;
+ pageSize.value = 10;
+ getList();
+};
+
+// 鏌ヨ鎿嶄綔
+const handleQuery = () => {
+ pageNum.value = 1;
+ pageSize.value = 10;
+ getList();
+};
+const pagination = (obj) => {
+ pageNum.value = obj.page;
+ pageSize.value = obj.limit;
+ getList();
+};
+// 鑾峰彇鍒楄〃鏁版嵁
+const getList = () => {
+ tableLoading.value = true;
+
+ const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
+
+ let apiCall;
+ if (activeRadio.value === "task") {
+ apiCall = inspectionTaskList(params);
+ } else {
+ apiCall = timingTaskList(params);
+ }
+
+ apiCall.then(res => {
+ const rawData = res.data.records || [];
+ // 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
+ tableData.value = rawData.map(item => {
+ const processedItem = { ...item };
+
+ // 澶勭悊 inspector 瀛楁
+ if (processedItem.inspector) {
+ if (typeof processedItem.inspector === 'string') {
+ // 瀛楃涓叉寜閫楀彿鍒嗗壊
+ processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s);
+ } else if (!Array.isArray(processedItem.inspector)) {
+ // 闈炴暟缁勮浆涓烘暟缁�
+ processedItem.inspector = [processedItem.inspector];
+ }
+ } else {
+ // 绌哄�艰涓虹┖鏁扮粍
+ processedItem.inspector = [];
+ }
+
+ return processedItem;
+ });
+ total.value = res.data.total || 0;
+ }).finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+// 閲嶇疆鏌ヨ
+const resetQuery = () => {
+ for (const key in queryParams) {
+ if (!["pageNum", "pageSize"].includes(key)) {
+ queryParams[key] = "";
+ }
+ }
+ handleQuery();
+};
+
+// 鏂板 / 缂栬緫
+const handleAdd = (row) => {
+ const type = row ? 'edit' : 'add';
+ nextTick(() => {
+ formDia.value?.openDialog(type, row);
+ });
+};
+
+// 鏌ョ湅闄勪欢
+const viewFile = (row) => {
+ nextTick(() => {
+ viewFiles.value?.openDialog(row);
+ });
+};
+
+// 鍒犻櫎鎿嶄綔
+const handleDelete = () => {
+ if (!selectedRows.value.length) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+
+ const deleteIds = selectedRows.value.map(item => item.id);
+
+ proxy.$modal.confirm('鏄惁纭鍒犻櫎鎵�閫夋暟鎹」锛�').then(() => {
+ return delTimingTask(deleteIds);
+ }).then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ handleQuery();
+ }).catch(() => {});
+};
+
+// 澶氶�夊彉鏇�
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ // 鏍规嵁褰撳墠閫変腑鐨勬爣绛鹃〉璋冪敤涓嶅悓鐨勫鍑烘帴鍙�
+ if (activeRadio.value === "taskManage") {
+ // 瀹氭椂浠诲姟绠$悊
+ proxy.download("/timingTask/export", {}, "瀹氭椂浠诲姟绠$悊.xlsx");
+ } else if (activeRadio.value === "task") {
+ // 瀹氭椂浠诲姟璁板綍
+ proxy.download("/inspectionTask/export", {}, "瀹氭椂浠诲姟璁板綍.xlsx");
+ }
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+</script>
+
+<style scoped>
+.person-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.person-tag {
+ margin-right: 4px;
+ margin-bottom: 2px;
+}
+
+.no-data {
+ color: #909399;
+ font-size: 14px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/iotMonitor/index.vue b/src/views/equipmentManagement/iotMonitor/index.vue
index b0cd818..de62866 100644
--- a/src/views/equipmentManagement/iotMonitor/index.vue
+++ b/src/views/equipmentManagement/iotMonitor/index.vue
@@ -110,7 +110,7 @@
const devices = reactive([
{
id: 'water-pump',
- name: '娉ㄦ按娉�1',
+ name: '璁惧1',
type: '绉诲姩瑁呭',
baseline: { vibration: 9 },
initial: { temperature: 40, pressure: 0.70 },
@@ -124,7 +124,7 @@
},
{
id: 'fluid-supply-truck',
- name: '娉ㄦ按娉�2',
+ name: '璁惧2',
type: '绉诲姩瑁呭',
baseline: { vibration: 7 },
initial: { temperature: 30, pressure: 0.60 },
@@ -138,7 +138,7 @@
},
{
id: 'fracturing-truck',
- name: '娉ㄦ按娉�3',
+ name: '璁惧3',
type: '绉诲姩瑁呭',
baseline: { vibration: 12 },
initial: { temperature: 65, pressure: 1.40 },
@@ -152,7 +152,7 @@
},
{
id: 'oil-tank-truck',
- name: '娉ㄦ按娉�4',
+ name: '璁惧4',
type: '绉诲姩瑁呭',
baseline: { vibration: 6 },
initial: { temperature: 28, pressure: 0.50 },
diff --git a/src/views/equipmentManagement/iotMonitor/indexWD.vue b/src/views/equipmentManagement/iotMonitor/indexWD.vue
index 40a2eb0..d98afe2 100644
--- a/src/views/equipmentManagement/iotMonitor/indexWD.vue
+++ b/src/views/equipmentManagement/iotMonitor/indexWD.vue
@@ -110,7 +110,7 @@
const devices = reactive([
{
id: 'hydrocyclone-desander',
- name: '鏃嬫祦闄ょ爞鍣�',
+ name: '璁惧1',
type: '鍒嗙璁惧',
baseline: { vibration: 8 },
initial: { temperature: 35, pressure: 0.85 },
@@ -124,7 +124,7 @@
},
{
id: 'high-pressure-separator',
- name: '楂樺帇鍒嗙鍣ㄦ挰',
+ name: '璁惧2',
type: '鍒嗙璁惧',
baseline: { vibration: 6 },
initial: { temperature: 45, pressure: 1.20 },
@@ -138,7 +138,7 @@
},
{
id: 'heating-throttle-pressure',
- name: '缁勫悎寮忓姞鐑妭娴佽皟鍘�',
+ name: '璁惧3',
type: '璋冨帇璁惧',
baseline: { vibration: 10 },
initial: { temperature: 75, pressure: 1.80 },
@@ -152,7 +152,7 @@
},
{
id: 'three-phase-separator',
- name: '涓夌浉鍒嗙鍣�',
+ name: '璁惧4',
type: '鍒嗙璁惧',
baseline: { vibration: 7 },
initial: { temperature: 38, pressure: 0.95 },
diff --git a/src/views/equipmentManagement/kplMonitor/index.vue b/src/views/equipmentManagement/kplMonitor/index.vue
new file mode 100644
index 0000000..178b658
--- /dev/null
+++ b/src/views/equipmentManagement/kplMonitor/index.vue
@@ -0,0 +1,714 @@
+<template>
+ <div class="kpl-monitor-container">
+ <!-- 椤甸潰澶撮儴 -->
+ <div class="page-header">
+ <div class="header-content">
+ <h1>KPL鐩戞帶鍒嗘瀽</h1>
+ <p>璁惧鍏抽敭鎬ц兘鎸囨爣鐩戞帶涓庣淮淇濈瓥鐣ヤ紭鍖�</p>
+ </div>
+ <div class="time-range">
+ <el-date-picker
+ v-model="timeRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ @change="fetchKPLData"
+ />
+ </div>
+ </div>
+
+ <!-- 鍏抽敭鎸囨爣姒傝 -->
+ <div class="metrics-overview">
+ <div class="metric-card mtbf-card">
+ <div class="metric-icon">鈴憋笍</div>
+ <div class="metric-content">
+ <div class="metric-title">MTBF</div>
+ <div class="metric-subtitle">骞冲潎鏃犳晠闅滄椂闂�</div>
+ <div class="metric-value">{{ currentMTBF }}<span class="unit">灏忔椂</span></div>
+ <div class="metric-trend" :class="mtbfTrendClass">
+ <span class="trend-icon">{{ mtbfTrendText }}</span>
+ <span class="trend-text">{{ Math.abs(mtbfChange) }}%</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="metric-card mttr-card">
+ <div class="metric-icon">馃敡</div>
+ <div class="metric-content">
+ <div class="metric-title">MTTR</div>
+ <div class="metric-subtitle">骞冲潎淇鏃堕棿</div>
+ <div class="metric-value">{{ currentMTTR }}<span class="unit">灏忔椂</span></div>
+ <div class="metric-trend" :class="mttrTrendClass">
+ <span class="trend-icon">{{ mttrTrendText }}</span>
+ <span class="trend-text">{{ Math.abs(mttrChange) }}%</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="metric-card availability-card">
+ <div class="metric-icon">馃搳</div>
+ <div class="metric-content">
+ <div class="metric-title">璁惧鍙敤鐜�</div>
+ <div class="metric-subtitle">杩愯鏁堢巼鎸囨爣</div>
+ <div class="metric-value">{{ currentAvailability }}<span class="unit">%</span></div>
+ <div class="metric-trend" :class="availabilityTrendClass">
+ <span class="trend-icon">{{ availabilityTrendText }}</span>
+ <span class="trend-text">{{ Math.abs(availabilityChange) }}%</span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 瓒嬪娍鍒嗘瀽鍥捐〃 -->
+ <div class="charts-section">
+ <div class="chart-container">
+ <div class="chart-header">
+ <h3>MTBF & MTTR 瓒嬪娍鍒嗘瀽</h3>
+ <div class="chart-legend">
+ <span class="legend-item mtbf-legend">
+ <span class="legend-color"></span>
+ MTBF (骞冲潎鏃犳晠闅滄椂闂�)
+ </span>
+ <span class="legend-item mttr-legend">
+ <span class="legend-color"></span>
+ MTTR (骞冲潎淇鏃堕棿)
+ </span>
+ </div>
+ </div>
+ <div class="chart-wrapper">
+ <div ref="trendChart" class="chart"></div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 缁翠繚绛栫暐寤鸿 -->
+ <div class="recommendations-section">
+ <div class="section-header">
+ <h3>缁翠繚绛栫暐浼樺寲寤鸿</h3>
+ <p>鍩轰簬褰撳墠鎸囨爣鍒嗘瀽锛屼负鎮ㄦ彁渚涢拡瀵规�х殑浼樺寲寤鸿</p>
+ </div>
+ <div class="recommendations-grid">
+ <div
+ v-for="(recommendation, index) in recommendations"
+ :key="index"
+ class="recommendation-card"
+ >
+ <div class="recommendation-icon">{{ recommendation.icon }}</div>
+ <div class="recommendation-content">
+ <h4>{{ recommendation.title }}</h4>
+ <p>{{ recommendation.description }}</p>
+ <div class="recommendation-priority" :class="recommendation.priority">
+ {{ recommendation.priorityText }}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed, nextTick } from 'vue'
+import * as echarts from 'echarts'
+
+// 鐢熸垚妯℃嫙鏁版嵁
+const generateMockData = () => {
+ const months = ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�']
+ const mtbfData = months.map(() => Math.floor(Math.random() * 200 + 300)) // 300-500灏忔椂
+ const mttrData = months.map(() => Math.floor(Math.random() * 8 + 4)) // 4-12灏忔椂
+
+ return {
+ months,
+ mtbfData,
+ mttrData
+ }
+}
+
+const timeRange = ref([
+ new Date(new Date().getFullYear(), 0, 1).toISOString().split('T')[0],
+ new Date().toISOString().split('T')[0]
+])
+
+const mockData = reactive(generateMockData())
+
+// 璁$畻褰撳墠鎸囨爣鍊�
+const currentMTBF = computed(() => mockData.mtbfData[mockData.mtbfData.length - 1])
+const currentMTTR = computed(() => mockData.mttrData[mockData.mttrData.length - 1])
+const currentAvailability = computed(() =>
+ Math.round((currentMTBF.value / (currentMTBF.value + currentMTTR.value)) * 100)
+)
+
+// 璁$畻鍙樺寲瓒嬪娍
+const mtbfChange = computed(() => {
+ const current = currentMTBF.value
+ const previous = mockData.mtbfData[mockData.mtbfData.length - 2] || current
+ return Math.round(((current - previous) / previous) * 100)
+})
+
+const mttrChange = computed(() => {
+ const current = currentMTTR.value
+ const previous = mockData.mttrData[mockData.mttrData.length - 2] || current
+ return Math.round(((current - previous) / previous) * 100)
+})
+
+const availabilityChange = computed(() => {
+ const current = currentAvailability.value
+ const previous = Math.round(
+ (mockData.mtbfData[mockData.mtbfData.length - 2] /
+ (mockData.mtbfData[mockData.mtbfData.length - 2] + mockData.mttrData[mockData.mttrData.length - 2])) * 100
+ ) || current
+ return Math.round(((current - previous) / previous) * 100)
+})
+
+// 瓒嬪娍鏍峰紡鍜屾枃鏈�
+const mtbfTrendClass = computed(() => mtbfChange.value >= 0 ? 'trend-up' : 'trend-down')
+const mttrTrendClass = computed(() => mttrChange.value <= 0 ? 'trend-up' : 'trend-down')
+const availabilityTrendClass = computed(() => availabilityChange.value >= 0 ? 'trend-up' : 'trend-down')
+
+const mtbfTrendText = computed(() => mtbfChange.value >= 0 ? '鈫�' : '鈫�')
+const mttrTrendText = computed(() => mttrChange.value <= 0 ? '鈫�' : '鈫�')
+const availabilityTrendText = computed(() => availabilityChange.value >= 0 ? '鈫�' : '鈫�')
+
+// 鏅鸿兘缁翠繚寤鸿
+const recommendations = computed(() => {
+ const suggestions = []
+
+ // MTBF鐩稿叧寤鸿
+ if (currentMTBF.value < 400) {
+ suggestions.push({
+ icon: '馃敡',
+ title: '鎻愬崌MTBF',
+ description: '褰撳墠MTBF杈冧綆锛屽缓璁姞寮洪闃叉�х淮鎶わ紝瀹氭湡妫�鏌ュ叧閿儴浠讹紝寤堕暱璁惧鏃犳晠闅滆繍琛屾椂闂�',
+ priority: 'high',
+ priorityText: '楂樹紭鍏堢骇'
+ })
+ }
+
+ // MTTR鐩稿叧寤鸿
+ if (currentMTTR.value > 8) {
+ suggestions.push({
+ icon: '鈿�',
+ title: '浼樺寲MTTR',
+ description: '褰撳墠MTTR杈冮珮锛屽缓璁紭鍖栫淮淇祦绋嬶紝鎻愰珮缁翠慨浜哄憳鎶�鑳斤紝缂╃煭鏁呴殰淇鏃堕棿',
+ priority: 'high',
+ priorityText: '楂樹紭鍏堢骇'
+ })
+ }
+
+ // 鍙敤鐜囩浉鍏冲缓璁�
+ if (currentAvailability.value < 95) {
+ suggestions.push({
+ icon: '馃搱',
+ title: '鎻愬崌鍙敤鐜�',
+ description: '璁惧鍙敤鐜囨湁寰呮彁鍗囷紝寤鸿浼樺寲缁翠繚璁″垝瀹夋帓锛屽噺灏戣鍒掑鍋滄満鏃堕棿',
+ priority: 'medium',
+ priorityText: '涓紭鍏堢骇'
+ })
+ }
+
+ // 缁煎悎寤鸿
+ if (currentMTBF.value >= 400 && currentMTTR.value <= 8 && currentAvailability.value >= 95) {
+ suggestions.push({
+ icon: '鉁�',
+ title: '杩愯鐘跺喌鑹ソ',
+ description: '褰撳墠璁惧杩愯鐘跺喌鑹ソ锛屽悇椤规寚鏍囧潎杈惧埌棰勬湡锛屽缓璁户缁繚鎸佺幇鏈夌淮淇濈瓥鐣�',
+ priority: 'low',
+ priorityText: '浣庝紭鍏堢骇'
+ })
+ }
+
+ // 棰勯槻鎬у缓璁�
+ suggestions.push({
+ icon: '馃搵',
+ title: '棰勯槻鎬х淮鎶�',
+ description: '寤鸿寤虹珛璁惧鍋ュ悍妗f锛屽畾鏈熷垎鏋愯澶囪繍琛屾暟鎹紝鎻愬墠璇嗗埆娼滃湪鏁呴殰椋庨櫓',
+ priority: 'medium',
+ priorityText: '涓紭鍏堢骇'
+ })
+
+ return suggestions
+})
+
+// 鍥捐〃瀹炰緥
+let trendChart = null
+
+const initChart = () => {
+ nextTick(() => {
+ trendChart = echarts.init(document.querySelector('.chart'))
+ trendChart.setOption({
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross'
+ }
+ },
+ legend: {
+ data: ['MTBF', 'MTTR'],
+ top: 10
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: mockData.months,
+ axisLine: {
+ lineStyle: {
+ color: '#e0e0e0'
+ }
+ }
+ },
+ yAxis: [
+ {
+ type: 'value',
+ name: 'MTBF (灏忔椂)',
+ position: 'left',
+ axisLine: {
+ lineStyle: {
+ color: '#1890ff'
+ }
+ },
+ axisLabel: {
+ formatter: '{value}h'
+ }
+ },
+ {
+ type: 'value',
+ name: 'MTTR (灏忔椂)',
+ position: 'right',
+ axisLine: {
+ lineStyle: {
+ color: '#52c41a'
+ }
+ },
+ axisLabel: {
+ formatter: '{value}h'
+ }
+ }
+ ],
+ series: [
+ {
+ name: 'MTBF',
+ type: 'line',
+ yAxisIndex: 0,
+ data: mockData.mtbfData,
+ smooth: true,
+ lineStyle: {
+ color: '#1890ff',
+ width: 3
+ },
+ itemStyle: {
+ color: '#1890ff'
+ },
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
+ { offset: 1, color: 'rgba(24, 144, 255, 0.1)' }
+ ])
+ }
+ },
+ {
+ name: 'MTTR',
+ type: 'line',
+ yAxisIndex: 1,
+ data: mockData.mttrData,
+ smooth: true,
+ lineStyle: {
+ color: '#52c41a',
+ width: 3
+ },
+ itemStyle: {
+ color: '#52c41a'
+ },
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: 'rgba(82, 196, 26, 0.3)' },
+ { offset: 1, color: 'rgba(82, 196, 26, 0.1)' }
+ ])
+ }
+ }
+ ]
+ })
+ })
+}
+
+const fetchKPLData = () => {
+ // 妯℃嫙鏁版嵁鍒锋柊
+ Object.assign(mockData, generateMockData())
+
+ // 閲嶆柊娓叉煋鍥捐〃
+ if (trendChart) {
+ trendChart.setOption({
+ xAxis: {
+ data: mockData.months
+ },
+ series: [
+ {
+ data: mockData.mtbfData
+ },
+ {
+ data: mockData.mttrData
+ }
+ ]
+ })
+ }
+}
+
+onMounted(() => {
+ initChart()
+
+ // 鐩戝惉绐楀彛澶у皬鍙樺寲锛岄噸鏂拌皟鏁村浘琛ㄥぇ灏�
+ window.addEventListener('resize', () => {
+ if (trendChart) trendChart.resize()
+ })
+})
+</script>
+
+<style scoped>
+.kpl-monitor-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+ padding: 24px;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+/* 椤甸潰澶撮儴 */
+.page-header {
+ background: white;
+ border-radius: 12px;
+ padding: 24px;
+ margin-bottom: 24px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.header-content h1 {
+ margin: 0 0 8px 0;
+ color: #1f2937;
+ font-size: 28px;
+ font-weight: 700;
+}
+
+.header-content p {
+ margin: 0;
+ color: #6b7280;
+ font-size: 16px;
+}
+
+.time-range {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+/* 鎸囨爣姒傝 */
+.metrics-overview {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 20px;
+ margin-bottom: 24px;
+}
+
+.metric-card {
+ background: white;
+ border-radius: 12px;
+ padding: 24px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.metric-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+}
+
+.metric-icon {
+ font-size: 32px;
+ width: 60px;
+ height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.mtbf-card .metric-icon {
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.mttr-card .metric-icon {
+ background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+}
+
+.availability-card .metric-icon {
+ background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
+}
+
+.metric-content {
+ flex: 1;
+}
+
+.metric-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #1f2937;
+ margin-bottom: 4px;
+}
+
+.metric-subtitle {
+ font-size: 14px;
+ color: #6b7280;
+ margin-bottom: 12px;
+}
+
+.metric-value {
+ font-size: 32px;
+ font-weight: 700;
+ color: #1f2937;
+ margin-bottom: 8px;
+}
+
+.unit {
+ font-size: 16px;
+ font-weight: 500;
+ color: #6b7280;
+ margin-left: 4px;
+}
+
+.metric-trend {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.trend-up {
+ color: #10b981;
+}
+
+.trend-down {
+ color: #ef4444;
+}
+
+.trend-icon {
+ font-size: 16px;
+}
+
+/* 鍥捐〃鍖哄煙 */
+.charts-section {
+ margin-bottom: 24px;
+}
+
+.chart-container {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+
+.chart-header {
+ padding: 20px 24px;
+ border-bottom: 1px solid #e5e7eb;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.chart-header h3 {
+ margin: 0;
+ color: #1f2937;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.chart-legend {
+ display: flex;
+ gap: 20px;
+}
+
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: #6b7280;
+}
+
+.legend-color {
+ width: 12px;
+ height: 12px;
+ border-radius: 2px;
+}
+
+.mtbf-legend .legend-color {
+ background: #1890ff;
+}
+
+.mttr-legend .legend-color {
+ background: #52c41a;
+}
+
+.chart-wrapper {
+ padding: 20px;
+ height: 400px;
+}
+
+.chart {
+ width: 100%;
+ height: 100%;
+}
+
+/* 寤鸿鍖哄煙 */
+.recommendations-section {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+
+.section-header {
+ padding: 24px;
+ border-bottom: 1px solid #e5e7eb;
+}
+
+.section-header h3 {
+ margin: 0 0 8px 0;
+ color: #1f2937;
+ font-size: 20px;
+ font-weight: 600;
+}
+
+.section-header p {
+ margin: 0;
+ color: #6b7280;
+ font-size: 14px;
+}
+
+.recommendations-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
+ gap: 20px;
+ padding: 24px;
+}
+
+.recommendation-card {
+ background: #f8fafc;
+ border-radius: 8px;
+ padding: 20px;
+ display: flex;
+ gap: 16px;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.recommendation-card:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.recommendation-icon {
+ font-size: 24px;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ background: white;
+ flex-shrink: 0;
+}
+
+.recommendation-content {
+ flex: 1;
+}
+
+.recommendation-content h4 {
+ margin: 0 0 8px 0;
+ color: #1f2937;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.recommendation-content p {
+ margin: 0 0 12px 0;
+ color: #6b7280;
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+.recommendation-priority {
+ display: inline-block;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: 500;
+}
+
+.priority.high {
+ background: #fef2f2;
+ color: #dc2626;
+}
+
+.priority.medium {
+ background: #fffbeb;
+ color: #d97706;
+}
+
+.priority.low {
+ background: #f0fdf4;
+ color: #16a34a;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .kpl-monitor-container {
+ padding: 16px;
+ }
+
+ .page-header {
+ flex-direction: column;
+ gap: 16px;
+ align-items: stretch;
+ }
+
+ .metrics-overview {
+ grid-template-columns: 1fr;
+ }
+
+ .recommendations-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .chart-wrapper {
+ height: 300px;
+ }
+
+ .chart-legend {
+ flex-direction: column;
+ gap: 8px;
+ }
+}
+
+@media (max-width: 480px) {
+ .metric-card {
+ flex-direction: column;
+ text-align: center;
+ }
+
+ .recommendation-card {
+ flex-direction: column;
+ text-align: center;
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/ledger/Form.vue b/src/views/equipmentManagement/ledger/Form.vue
index 0951fd8..f85a7fa 100644
--- a/src/views/equipmentManagement/ledger/Form.vue
+++ b/src/views/equipmentManagement/ledger/Form.vue
@@ -1,5 +1,5 @@
<template>
- <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
+ <el-form :model="form" label-width="120px" :rules="formRules" ref="formRef">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="璁惧鍚嶇О" prop="deviceName">
@@ -8,7 +8,33 @@
</el-col>
<el-col :span="12">
<el-form-item label="瑙勬牸鍨嬪彿" prop="deviceModel">
- <el-input v-model="form.deviceModel" :disabled="(form.deviceModel != null && operationType === 'edit')" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ <el-input v-model="form.deviceModel" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁惧鍝佺墝" prop="deviceBrand">
+ <el-input v-model="form.deviceBrand" placeholder="璇疯緭鍏ヨ澶囧搧鐗�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁惧绫诲瀷" prop="type">
+ <el-select
+ v-model="form.type"
+ placeholder="璇烽�夋嫨鎴栬緭鍏ヨ澶囩被鍨�"
+ clearable
+ filterable
+ allow-create
+ default-first-option
+ style="width: 100%"
+ @change="handleDeviceTypeChange"
+ >
+ <el-option
+ v-for="item in deviceTypeOptions"
+ :key="item"
+ :label="item"
+ :value="item"
+ />
+ </el-select>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -17,14 +43,36 @@
</el-form-item>
</el-col>
<el-col :span="12">
+ <el-form-item label="瀛樻斁浣嶇疆" prop="storageLocation">
+ <el-input v-model="form.storageLocation" placeholder="璇疯緭鍏ュ瓨鏀句綅缃�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
<el-form-item label="鍗曚綅" prop="unit">
<el-input v-model="form.unit" placeholder="璇疯緭鍏ュ崟浣�" />
</el-form-item>
</el-col>
<el-col :span="12">
+ <el-form-item label="鍚敤鎶樻棫" prop="isDepr">
+ <el-switch v-model="form.isDepr" :active-value="1" :inactive-value="2" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.isDepr === 1">
+ <el-form-item label="姣忓勾鎶樻棫閲戦" prop="annualDepreciationAmount">
+ <el-input-number
+ :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="form.annualDepreciationAmount"
+ placeholder="璇疯緭鍏ユ瘡骞存姌鏃ч噾棰�"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
<el-form-item label="鏁伴噺" prop="number">
- <el-input-number :step="0.01" :min="0" style="width: 100%"
+ <el-input-number :min="1" style="width: 100%"
v-model="form.number"
+ disabled
placeholder="璇疯緭鍏ユ暟閲�"
@change="mathNum"
/>
@@ -99,6 +147,19 @@
/>
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="棰勮杩愯鏃堕棿" prop="planRuntimeTime">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.planRuntimeTime"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨褰曞叆鏃ユ湡"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
</el-row>
</el-form>
</template>
@@ -120,28 +181,56 @@
});
const formRef = ref(null);
const operationType = ref('');
+// 璁惧绫诲瀷鍥哄畾閫夐」
+const deviceTypeOptions = ref([
+ '鐢熶骇璁惧',
+ '鍔炲叕璁惧',
+ '妫�娴嬭澶�',
+ '杩愯緭璁惧',
+ '鍏朵粬璁惧'
+]);
const formRules = {
deviceName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
deviceModel: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ type: [{ required: true, trigger: "change", message: "璇烽�夋嫨鎴栬緭鍏ヨ澶囩被鍨�" }],
supplierName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
unit: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
number: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
taxRate: [{ required: true, trigger: "change", message: "璇疯緭鍏�" }],
+ planRuntimeTime: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ annualDepreciationAmount: [
+ {
+ validator: (rule, value, callback) => {
+ if (form.isDepr === 1 && (value === undefined || value === null || value === '')) {
+ callback(new Error('鍚敤鎶樻棫鏃讹紝璇疯緭鍏ユ瘡骞存姌鏃ч噾棰�'));
+ } else {
+ callback();
+ }
+ },
+ trigger: "blur"
+ }
+ ],
}
const { form, resetForm } = useFormData({
deviceName: undefined, // 璁惧鍚嶇О
deviceModel: undefined, // 瑙勬牸鍨嬪彿
+ deviceBrand: undefined, // 璁惧鍝佺墝
+ type: undefined, // 璁惧绫诲瀷
supplierName: undefined, // 渚涘簲鍟�
+ storageLocation: undefined, // 瀛樻斁浣嶇疆
+ isDepr: 2, // 鏄惁鍚敤鎶樻棫 1-鏄� 2-鍚�
+ annualDepreciationAmount: undefined, // 姣忓勾鎶樻棫閲戦
unit: undefined, // 鍗曚綅
- number: undefined, // 鏁伴噺
+ number: 1, // 鏁伴噺
taxIncludingPriceUnit: undefined, // 鍚◣鍗曚环
taxIncludingPriceTotal: undefined, // 鍚◣鎬讳环
taxRate: undefined, // 绋庣巼
unTaxIncludingPriceTotal: undefined, // 涓嶅惈绋庢�讳环
// createUser: useUserStore().nickName, // 褰曞叆浜�
createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // 褰曞叆鏃ユ湡
+ planRuntimeTime: dayjs().format("YYYY-MM-DD"), // 褰曞叆鏃ユ湡
});
const loadForm = async (id) => {
@@ -152,9 +241,14 @@
if (code == 200) {
form.deviceName = data.deviceName;
form.deviceModel = data.deviceModel;
+ form.deviceBrand = data.deviceBrand;
+ form.type = data.type;
form.supplierName = data.supplierName;
+ form.storageLocation = data.storageLocation;
+ form.isDepr = data.isDepr;
+ form.annualDepreciationAmount = data.annualDepreciationAmount;
form.unit = data.unit;
- form.number = data.number;
+ form.number = 1;
form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
form.taxIncludingPriceTotal = data.taxIncludingPriceTotal;
form.taxRate = data.taxRate;
@@ -163,13 +257,16 @@
}
};
+const handleDeviceTypeChange = (value) => {
+ // 濡傛灉杈撳叆鐨勬柊鍊间笉鍦ㄥ浐瀹氶�夐」涓紝鍒欐坊鍔犲埌閫夐」鍒楄〃
+ if (value && !deviceTypeOptions.value.includes(value)) {
+ deviceTypeOptions.value.push(value);
+ }
+};
+
const mathNum = () => {
if (!form.taxIncludingPriceUnit) {
ElMessage.error("璇疯緭鍏ュ崟浠�");
- return;
- }
- if (!form.number) {
- ElMessage.error("璇疯緭鍏ユ暟閲�");
return;
}
form.taxIncludingPriceTotal = calculateTaxIncludeTotalPrice(
diff --git a/src/views/equipmentManagement/ledger/Modal.vue b/src/views/equipmentManagement/ledger/Modal.vue
index 0cea56c..16166c6 100644
--- a/src/views/equipmentManagement/ledger/Modal.vue
+++ b/src/views/equipmentManagement/ledger/Modal.vue
@@ -1,5 +1,5 @@
<template>
- <el-dialog :title="modalOptions.title" v-model="visible" @close="close">
+ <el-dialog :title="modalOptions.title" v-model="visible" @close="close" draggable>
<Form ref="formRef"></Form>
<template #footer>
<el-button type="primary" @click="sendForm" :loading="loading">
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index decd235..7ef5e8d 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -7,7 +7,6 @@
style="width: 240px"
placeholder="璇疯緭鍏ヨ澶囧悕绉�"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -17,7 +16,6 @@
style="width: 240px"
placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -27,17 +25,6 @@
style="width: 240px"
placeholder="璇疯緭鍏ヤ緵搴斿晢"
clearable
- :prefix-icon="Search"
- @change="getTableData"
- />
- </el-form-item>
- <el-form-item label="鍗曚綅">
- <el-input
- v-model="filters.unit"
- style="width: 240px"
- placeholder="璇疯緭鍏ュ崟浣�"
- clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -84,7 +71,7 @@
</PIMTable>
</div>
<Modal ref="modalRef" @success="getTableData"></Modal>
- <el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px">
+ <el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px" draggable>
<div style="text-align:center;">
<img :src="qrCodeUrl" alt="浜岀淮鐮�" style="width:200px;height:200px;" />
<div style="margin:10px 0;">
@@ -134,83 +121,69 @@
deviceName: undefined,
deviceModel: undefined,
supplierName: undefined,
- unit: undefined,
entryDateStart: undefined,
entryDateEnd: undefined,
},
[
{
label: "璁惧鍚嶇О",
- align: "center",
prop: "deviceName",
},
{
label: "瑙勬牸鍨嬪彿",
- align: "center",
prop: "deviceModel",
},
{
+ label: "璁惧鍝佺墝",
+ prop: "deviceBrand",
+ },
+ {
+ label: "璁惧绫诲瀷",
+ prop: "type",
+ },
+ {
label: "渚涘簲鍟�",
- align: "center",
prop: "supplierName",
},
{
- label: "鍗曚綅",
- align: "center",
- prop: "unit",
+ label: "瀛樻斁浣嶇疆",
+ prop: "storageLocation",
},
{
label: "鏁伴噺",
- align: "center",
prop: "number",
},
{
- label: "鍚◣鍗曚环",
- align: "center",
- prop: "taxIncludingPriceUnit",
- },
- {
- label: "鍚◣鎬讳环",
- align: "center",
- prop: "taxIncludingPriceTotal",
- },
- {
- label: "绋庣巼",
- align: "center",
- prop: "taxRate",
- },
- {
- label: "涓嶅惈绋庢�讳环",
- align: "center",
- prop: "unTaxIncludingPriceTotal",
- },
- {
label: "褰曞叆浜�",
- align: "center",
prop: "createUser",
},
{
label: "褰曞叆鏃ユ湡",
- align: "center",
prop: "createTime",
+ formatData: (v) => {
+ if (!v) return '';
+ // 濡傛灉鍖呭惈鏃跺垎绉掞紝鍙彇鏃ユ湡閮ㄥ垎
+ if (v.includes(' ')) {
+ return v.split(' ')[0];
+ }
+ return v;
+ },
},
{
dataType: "action",
label: "鎿嶄綔",
align: "center",
fixed: 'right',
- width: 140,
+ width: 150,
operation: [
{
name: "缂栬緫",
- type: "text",
clickFun: (row) => {
edit(row.id)
},
},
{
name: "鐢熸垚浜岀淮鐮�",
- type: "text",
clickFun: (row) => {
showQRCode(row)
},
@@ -293,8 +266,8 @@
};
const showQRCode = async (row) => {
- // 浣犲彲浠ヨ嚜瀹氫箟浜岀淮鐮佸唴瀹癸紝姣斿 row.id 鎴� row.deviceName
- const qrContent = JSON.stringify(row); // 鎴� `${row.id}`
+ // 鐩存帴浣跨敤URL锛屼笉瑕佺敤JSON.stringify鍖呰
+ const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
qrCodeUrl.value = await QRCode.toDataURL(qrContent);
qrRowData.value = row;
qrDialogVisible.value = true;
@@ -308,12 +281,6 @@
};
onMounted(() => {
- filters.entryDate = [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ]
- filters.entryDateStart = dayjs().format("YYYY-MM-DD")
- filters.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
getTableData();
});
</script>
diff --git a/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue b/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
index f9679a0..b5c1ea1 100644
--- a/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
+++ b/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
@@ -4,6 +4,7 @@
v-model="dialogFormVisible"
title="璁¢噺鍣ㄥ叿"
width="50%"
+ draggable
@close="closeDia"
>
<el-form
@@ -67,7 +68,9 @@
<el-select
v-model="form.userId"
placeholder="璇烽�夋嫨"
- disabled
+ filterable
+ default-first-option
+ :reserve-keyword="false"
clearable
>
<el-option
@@ -88,7 +91,6 @@
format="YYYY-MM-DD"
type="date"
placeholder="璇烽�夋嫨"
- disabled
clearable
/>
</el-form-item>
@@ -123,13 +125,14 @@
</template>
<script setup>
-import {ref} from "vue";
+import {ref, reactive, toRefs, getCurrentInstance} from "vue";
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import {getToken} from "@/utils/auth.js";
import {ledgerRecordUpdate, ledgerRecordVerifying} from "@/api/equipmentManagement/calibration.js";
import {delLedgerFile} from "@/api/salesManagement/salesLedger.js";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -185,6 +188,10 @@
if(type === "add"){
fileList.value = row.commonFiles;
}
+ if(type === "verifying"){
+ form.value.valid = row.valid;
+ form.value.recordDate = row.mostDate;
+ }
form.value.id = row.id;
form.value.code = row.code;
@@ -249,14 +256,6 @@
dialogFormVisible.value = false;
emit('close')
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
defineExpose({
openDialog,
});
diff --git a/src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue b/src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue
new file mode 100644
index 0000000..c6aa70e
--- /dev/null
+++ b/src/views/equipmentManagement/measurementEquipment/components/dialogForm.vue
@@ -0,0 +1,7 @@
+<template>
+
+</template>
+
+<script setup>
+
+</script>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/measurementEquipment/components/formDia.vue b/src/views/equipmentManagement/measurementEquipment/components/formDia.vue
index a9e08d1..9ac5c44 100644
--- a/src/views/equipmentManagement/measurementEquipment/components/formDia.vue
+++ b/src/views/equipmentManagement/measurementEquipment/components/formDia.vue
@@ -4,6 +4,7 @@
v-model="dialogFormVisible"
title="璁¢噺鍣ㄥ叿"
width="50%"
+ draggable
@close="closeDia"
>
<el-form
@@ -14,8 +15,8 @@
ref="formRef"
>
<el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="璁¢噺鍣ㄥ叿缂栧彿锛�" prop="code">
+ <el-col :span="24">
+ <el-form-item label="鍑哄巶缂栧彿锛�" prop="code">
<el-input
v-model="form.code"
placeholder="璇疯緭鍏�"
@@ -23,40 +24,73 @@
/>
</el-form-item>
</el-col>
- <el-col :span="12">
- <el-form-item label="璁¢噺鍣ㄥ叿鍚嶇О锛�" prop="name">
- <el-input
- v-model="form.name"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="model">
+ <el-form-item label="瀹夎浣嶇疆锛�" prop="instationLocation">
<el-input
- v-model="form.model"
+ v-model="form.installationLocation"
placeholder="璇疯緭鍏�"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="棰勮涓嬫妫�瀹氭棩鏈燂細" prop="nextDate">
- <el-date-picker
- style="width: 100%"
- v-model="form.nextDate"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- type="date"
- placeholder="璇烽�夋嫨"
- clearable
- />
+ <el-form-item label="妫�瀹氬崟浣嶏細" prop="unit">
+ <el-input
+ v-model="form.unit"
+ placeholder="璇疯緭鍏ユ瀹氬崟浣�"
+ clearable
+ />
</el-form-item>
</el-col>
</el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="璇佷功缂栧彿锛�" prop="model">
+ <el-input
+ v-model="form.model"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈�鏂伴壌瀹氭棩鏈燂細" prop="mostDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.mostDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鏈夋晥鏃ユ湡(澶�)锛�" prop="valid">
+ <el-input
+ v-model="form.valid"
+ placeholder="璇疯緭鍏ユ湁鏁堟湡澶╂暟"
+ clearable
+ >
+ <template #append>鏃�</template>
+ </el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="妫�瀹氬懆鏈燂細" prop="cycle">
+ <el-input
+ v-model="form.cycle"
+ placeholder="璇疯緭鍏ユ瀹氬懆鏈�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
<el-row :gutter="30">
<el-col :span="12">
<el-form-item label="褰曞叆浜猴細" prop="userId">
@@ -64,7 +98,9 @@
v-model="form.userId"
placeholder="璇烽�夋嫨"
clearable
- disabled
+ filterable
+ default-first-option
+ :reserve-keyword="false"
>
<el-option
v-for="item in userList"
@@ -81,9 +117,9 @@
style="width: 100%"
v-model="form.recordDate"
value-format="YYYY-MM-DD"
+ disabled
format="YYYY-MM-DD"
type="date"
- disabled
placeholder="璇烽�夋嫨"
clearable
/>
@@ -124,7 +160,8 @@
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js";
import {getToken} from "@/utils/auth.js";
-import {measuringInstrumentAdd, measuringInstrumentUpdate} from "@/api/equipmentManagement/measurementEquipment.js";
+import {addMeasuringInstrumentLedger, updateMeasuringInstrumentLedger} from "@/api/equipmentManagement/measurementEquipment.js";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -134,8 +171,10 @@
const data = reactive({
form: {
code: "",
- name: "",
+ installationLocation: "",
+ mostDate:"",
model: "",
+ cycle:"",
validDate: "",
nextDate: "",
userId: "",
@@ -144,12 +183,16 @@
},
rules: {
code: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
- name: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
model: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
validDate: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
nextDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
userId: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
recordDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
+ installationLocation: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
+ mostDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
+ cycle: [{required: true, message: "璇烽�夋嫨", trigger: "blur"}],
+ valid: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
+ unit: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
}
})
const { form, rules } = toRefs(data);
@@ -214,13 +257,13 @@
proxy.$refs["formRef"].validate(valid => {
if (valid) {
if (operationType.value === "add") {
- measuringInstrumentAdd(form.value).then(response => {
+ addMeasuringInstrumentLedger(form.value).then(response => {
proxy.$modal.msgSuccess("鏂板鎴愬姛")
form.value.tempFileIds = []
closeDia()
})
} else {
- measuringInstrumentUpdate(form.value).then(response => {
+ updateMeasuringInstrumentLedger(form.value).then(response => {
proxy.$modal.msgSuccess("淇敼鎴愬姛")
form.value.tempFileIds = []
closeDia()
@@ -235,14 +278,6 @@
dialogFormVisible.value = false;
emit('close')
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
-function getCurrentDate() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
- const day = String(today.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
defineExpose({
openDialog,
});
diff --git a/src/views/equipmentManagement/measurementEquipment/components/rowClickData.vue b/src/views/equipmentManagement/measurementEquipment/components/rowClickData.vue
new file mode 100644
index 0000000..6604587
--- /dev/null
+++ b/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>
diff --git a/src/views/equipmentManagement/measurementEquipment/filesDia.vue b/src/views/equipmentManagement/measurementEquipment/filesDia.vue
index f752496..ee9dc3a 100644
--- a/src/views/equipmentManagement/measurementEquipment/filesDia.vue
+++ b/src/views/equipmentManagement/measurementEquipment/filesDia.vue
@@ -26,20 +26,14 @@
rowKey="id"
:column="tableColumn"
:tableData="tableData"
+ :page="page"
:tableLoading="tableLoading"
:isSelection="true"
@selection-change="handleSelectionChange"
+ @pagination="paginationSearch"
height="500"
>
</PIMTable>
- <pagination
- style="margin: 10px 0"
- v-show="total > 0"
- @pagination="paginationSearch"
- :total="total"
- :page="page.current"
- :limit="page.size"
- />
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDia">鍙栨秷</el-button>
@@ -51,16 +45,16 @@
</template>
<script setup>
-import {ref} from "vue";
+import {ref, reactive, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import filePreview from '@/components/filePreview/index.vue'
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
import {
fileAdd,
fileDel,
fileListPage
} from "@/api/financialManagement/revenueManagement.js";
-import Pagination from "@/components/PIMTable/Pagination.vue";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -98,8 +92,8 @@
const page = reactive({
current: 1,
size: 100,
+ total: 0,
});
-const total = ref(0);
const tableData = ref([]);
const fileList = ref([]);
const tableLoading = ref(false);
@@ -124,7 +118,7 @@
const getList = () => {
fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => {
tableData.value = res.data.records;
- total.value = res.data.total;
+ page.total = res.data.total;
})
}
// 琛ㄦ牸閫夋嫨鏁版嵁
diff --git a/src/views/equipmentManagement/measurementEquipment/index.vue b/src/views/equipmentManagement/measurementEquipment/index.vue
index e983a99..4572f22 100644
--- a/src/views/equipmentManagement/measurementEquipment/index.vue
+++ b/src/views/equipmentManagement/measurementEquipment/index.vue
@@ -23,6 +23,7 @@
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
+ <el-button @click="handleReset" style="margin-left: 10px">閲嶇疆</el-button>
</div>
<div>
<el-button type="primary" @click="openForm('add')">鏂板璁¢噺鍣ㄥ叿</el-button>
@@ -40,25 +41,28 @@
@selection-change="handleSelectionChange"
:tableLoading="tableLoading"
@pagination="pagination"
+ :dbRowClick="dbRowClick"
></PIMTable>
</div>
<form-dia ref="formDia" @close="handleQuery"></form-dia>
<calibration-dia ref="calibrationDia" @close="handleQuery"></calibration-dia>
<files-dia ref="filesDia"></files-dia>
+ <rowClickDataForm ref="rowClickData"></rowClickDataForm>
</div>
</template>
<script setup>
-import {onMounted, ref} from "vue";
+import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/equipmentManagement/measurementEquipment/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import useUserStore from "@/store/modules/user.js";
import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue";
import {
- measuringInstrumentDelete,
- measuringInstrumentListPage
+ measuringInstrumentDelete,
+ measuringInstrumentListPage,
} from "@/api/equipmentManagement/measurementEquipment.js";
import FilesDia from "./filesDia.vue";
+import rowClickDataForm from "./components/rowClickData.vue"
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -73,67 +77,74 @@
const tableColumn = ref([
{
- label: "鐘舵��",
- prop: "status",
- dataType: "tag",
- formatData: (params) => {
- if (params == 1) {
- return "鏈夋晥";
- } else if (params == 2) {
- return "閫炬湡";
- } else {
- return null;
- }
- },
- formatType: (params) => {
- if (params == 1) {
- return "success";
- } else if (params == 2) {
- return "danger";
- } else {
- return null;
- }
- },
+ label: "鍑哄巶缂栧彿",
+ prop: "code",
+ minWidth:150,
+ align:"center"
},
{
- label: "鏈�杩戜竴娆℃瀹氭棩鏈�",
+ label: "瀹夎浣嶇疆",
+ prop: "instationLocation",
+ width: 150,
+ align:"center"
+ },
+ {
+ label: "妫�瀹氬崟浣�",
+ prop: "unit",
+ width: 200,
+ align:"center"
+ },
+ {
+ label: "璇佷功缂栧彿",
+ prop: "model",
+ width:200,
+ align:"center"
+ },
+ {
+ label: "鏈�鏂伴壌瀹氭棩鏈�",
prop: "mostDate",
width: 130,
- },
- {
- label: "璁¢噺鍣ㄥ叿缂栧彿",
- prop: "code",
- width: 150,
- },
- {
- label: "璁¢噺鍣ㄥ叿鍚嶇О",
- prop: "name",
- width: 200,
- },
- {
- label: "瑙勬牸鍨嬪彿",
- prop: "model",
- width:200
- },
- {
- label: "鏈夋晥鏈�",
- prop: "valid",
- width: 130,
- },
- {
- label: "棰勮涓嬫妫�瀹氭棩鏈�",
- prop: "nextDate",
- width: 130,
+ align:"center"
},
{
label: "褰曞叆浜�",
prop: "userName",
+ width: 130,
+ align:"center"
},
{
label: "褰曞叆鏃ユ湡",
prop: "recordDate",
- width: 130,
+ align:"center",
+ minWidth: 130
},
+ {
+ label: "鏈夋晥鏃ユ湡",
+ prop: "valid",
+ width: 130,
+ align:"center"
+ },
+ {
+ label: "妫�瀹氬懆鏈�(澶�)",
+ prop: "cycle",
+ width: 130,
+ align:"center"
+ },
+ {
+ label: "鐘舵��",
+ prop: "status",
+ width: 130,
+ align: "center",
+ formatData: (params) => {
+ if (params === 1) {
+ return "鏈夋晥";
+ } else if (params === 2) {
+ return "閫炬湡";
+ } else {
+ return null;
+ }
+ }
+ },
{
dataType: "action",
label: "鎿嶄綔",
@@ -141,18 +152,18 @@
width: '130',
fixed: 'right',
operation: [
+ {
+ name: "闄勪欢",
+ type: "text",
+ clickFun: (row) => {
+ openFilesFormDia(row);
+ },
+ },
{
- name: "妫�瀹氭牎鍑�",
+ name: "鏌ョ湅",
type: "text",
clickFun: (row) => {
openCalibrationDia("verifying", row);
- },
- },
- {
- name: "闄勪欢",
- type: "text",
- clickFun: (row) => {
- openFilesFormDia(row);
},
},
],
@@ -160,6 +171,7 @@
]);
const tableData = ref([]);
const tableLoading = ref(false);
+const rowClickData = ref([])
const filesDia = ref()
const page = reactive({
current: 1,
@@ -170,11 +182,12 @@
// 鎵撳紑闄勪欢寮规
const openFilesFormDia = (row) => {
- console.log(row)
- nextTick(() => {
- filesDia.value?.openDialog( row,'璁¢噺鍣ㄥ叿鍙拌处')
- })
+ filesDia.value?.openDialog(row,'璁¢噺鍣ㄥ叿鍙拌处')
};
+
+const dbRowClick = (row)=>{
+ rowClickData.value?.openDialog(row)
+}
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
@@ -186,6 +199,15 @@
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+// 閲嶇疆鎼滅储鏉′欢
+const handleReset = () => {
+ searchForm.value.recordDate = "";
+ searchForm.value.code = "";
+ searchForm.value.status = "";
page.current = 1;
getList();
};
@@ -221,12 +243,6 @@
const handleDelete = () => {
let ids = [];
if (selectedRows.value.length > 0) {
- // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
- const unauthorizedData = selectedRows.value.filter(item => item.userId !== userStore.id);
- if (unauthorizedData.length > 0) {
- proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
- return;
- }
ids = selectedRows.value.map((item) => item.id);
} else {
proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
diff --git a/src/views/equipmentManagement/operationManagement/index.vue b/src/views/equipmentManagement/operationManagement/index.vue
new file mode 100644
index 0000000..008cc2c
--- /dev/null
+++ b/src/views/equipmentManagement/operationManagement/index.vue
@@ -0,0 +1,370 @@
+<template>
+ <div class="app-container">
+
+ <!-- 绛涢�夋潯浠� -->
+ <div class="filter-section">
+ <el-select v-model="deviceFilter" placeholder="璁惧鐘舵�佺瓫閫�" clearable style="width: 200px; margin-right: 10px;">
+ <el-option label="鍏ㄩ儴" value="all" />
+ <el-option label="杩愯涓�" value="start" />
+ <el-option label="鍋滄杩愯" value="stop" />
+ </el-select>
+ </div>
+
+ <!-- 璁惧鍚仠璁板綍琛ㄦ牸 -->
+ <el-card class="table-card">
+ <template #header>
+ <span>璁惧杩愯璁板綍</span>
+ </template>
+ <el-table
+ :data="filteredDeviceRecords"
+ style="width: 100%"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ :row-class-name="getRowClassName"
+ v-loading="loading"
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column
+ label="璁惧鍚嶇О"
+ prop="deviceName"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="瑙勬牸鍨嬪彿"
+ prop="deviceModel"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="璁惧鐘舵��"
+ prop="status"
+ width="150"
+ align="center"
+ >
+ <template #default="scope">
+ <!-- 瓒呮椂鏈惎鍔ㄦ椂鏄剧ず璀﹀憡 -->
+ <el-tag
+ v-if="isOverdue(scope.row)"
+ type="warning"
+ size="small"
+ effect="dark"
+ >
+ <el-icon><Warning /></el-icon>
+ 瓒呮椂鏈惎鍔�
+ </el-tag>
+ <!-- 姝e父鐘舵�佹椂鏄剧ず璁惧鐘舵�� -->
+ <el-tag
+ v-else
+ :type="getDeviceStatusType(scope.row.status)"
+ size="small"
+ >
+ <el-icon v-if="scope.row.status === '杩愯涓�'"><VideoPlay /></el-icon>
+ <el-icon v-else><VideoPause /></el-icon>
+ {{ scope.row.status || '鏈煡' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="璁″垝杩愯鏃堕棿"
+ prop="planRuntimeTime"
+ width="150"
+ align="center"
+ >
+ <template #default="scope">
+ {{ scope.row.planRuntimeTime || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="寮�濮嬭繍琛屾椂闂�"
+ prop="startRuntimeTime"
+ width="180"
+ align="center"
+ >
+ <template #default="scope">
+ {{ scope.row.startRuntimeTime || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="缁撴潫杩愯鏃堕棿"
+ prop="endRuntimeTime"
+ width="180"
+ align="center"
+ >
+ <template #default="scope">
+ {{ scope.row.endRuntimeTime || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="杩愯鏃堕暱"
+ prop="runtimeDuration"
+ width="120"
+ align="center"
+ >
+ <template #default="scope">
+ {{ scope.row.runtimeDuration || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎿嶄綔"
+ width="120"
+ align="center"
+ >
+ <template #default="scope">
+ <!-- 瓒呮椂鏈惎鍔ㄦ椂鏄剧ず鍚姩鎸夐挳 -->
+ <el-button
+ v-if="isOverdue(scope.row)"
+ type="warning"
+ size="small"
+ @click="changeDeviceStatus(scope.row, '鍚姩杩愯')"
+ >
+ <el-icon><VideoPlay /></el-icon>
+ 绔嬪嵆鍚姩
+ </el-button>
+ <!-- 姝e父鐘舵�佹椂鏄剧ず瀵瑰簲鐨勬搷浣滄寜閽� -->
+ <template v-else>
+ <el-button
+ v-if="scope.row.status === '杩愯涓�'"
+ type="danger"
+ size="small"
+ @click="changeDeviceStatus(scope.row, '鍋滄杩愯')"
+ >
+ <el-icon><VideoPause /></el-icon>
+ 鍋滄杩愯
+ </el-button>
+ <el-button
+ v-else
+ type="success"
+ size="small"
+ @click="changeDeviceStatus(scope.row, '鍚姩杩愯')"
+ >
+ <el-icon><VideoPlay /></el-icon>
+ 鍚姩杩愯
+ </el-button>
+ </template>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+import {
+ VideoPlay,
+ VideoPause,
+ Warning
+} from '@element-plus/icons-vue'
+import {editLedger, getLedgerPage} from "@/api/equipmentManagement/ledger.js";
+
+// 鍝嶅簲寮忔暟鎹�
+const deviceFilter = ref('all')
+const loading = ref(false)
+const total = ref(0)
+const queryParams = ref({
+ current: -1,
+ size: -1
+})
+
+// 绉婚櫎姒傝鏁版嵁锛屽洜涓虹幇鍦ㄤ娇鐢ㄨ〃鏍煎睍绀�
+
+// 璁惧鍚仠璁板綍鏁版嵁
+const deviceRecords = ref([])
+const allDeviceRecords = ref([]) // 瀛樺偍鎵�鏈夊師濮嬫暟鎹�
+
+// 鏍规嵁绛涢�夋潯浠惰繃婊ゆ暟鎹�
+const filteredDeviceRecords = computed(() => {
+ let filtered = allDeviceRecords.value
+
+ // 鏍规嵁璁惧鐘舵�佺瓫閫�
+ if (deviceFilter.value !== 'all') {
+ if (deviceFilter.value === 'start') {
+ filtered = filtered.filter(device => device.status === '杩愯涓�')
+ } else if (deviceFilter.value === 'stop') {
+ filtered = filtered.filter(device => device.status === '鍋滄杩愯')
+ }
+ }
+
+ return filtered
+})
+
+// 妫�鏌ヨ澶囨槸鍚﹁秴鏃舵湭鍚姩
+const isOverdue = (device) => {
+ if (!device.planRuntimeTime || device.status === '杩愯涓�' || device.startRuntimeTime) {
+ return false
+ }
+
+ const planTime = new Date(device.planRuntimeTime)
+ const currentTime = new Date()
+
+ return currentTime > planTime
+}
+
+// 鏂规硶
+const getList = async () => {
+ loading.value = true
+ try {
+ const response = await getLedgerPage(queryParams.value)
+ if (response.code === 200) {
+ allDeviceRecords.value = response.data.records || []
+ total.value = response.data.total || 0
+ }
+ } catch (error) {
+ console.error('鑾峰彇璁惧鍒楄〃澶辫触:', error)
+ ElMessage.error('鑾峰彇璁惧鍒楄〃澶辫触')
+ } finally {
+ loading.value = false
+ }
+}
+
+const changeDeviceStatus = async (device, status) => {
+ try {
+ const currentTime = new Date().toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false
+ }).replace(/\//g, '-')
+
+ // 鏇存柊璁惧鐘舵�佸拰鐩稿叧鏃堕棿瀛楁
+ if (status === '鍚姩杩愯') {
+ device.status = '杩愯涓�'
+ device.startRuntimeTime = currentTime
+ device.endRuntimeTime = null // 娓呯┖缁撴潫鏃堕棿
+ device.runtimeDuration = null // 娓呯┖杩愯鏃堕暱
+ } else {
+ device.status = '鍋滄杩愯'
+ device.endRuntimeTime = currentTime
+ // 璁$畻杩愯鏃堕暱
+ if (device.startRuntimeTime) {
+ const startTime = new Date(device.startRuntimeTime)
+ const endTime = new Date(currentTime)
+ const duration = endTime - startTime
+ const hours = Math.floor(duration / (1000 * 60 * 60))
+ const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60))
+ device.runtimeDuration = `${hours}灏忔椂${minutes}鍒嗛挓`
+ }
+ }
+ const params = {
+ id: device.id,
+ status: device.status,
+ planRuntimeTime: device.planRuntimeTime,
+ startRuntimeTime: device.startRuntimeTime,
+ endRuntimeTime: device.endRuntimeTime,
+ runtimeDuration: device.runtimeDuration,
+ }
+ // 璋冪敤API鏇存柊璁惧鐘舵��
+ const response = await editLedger(params)
+ if (response.code === 200) {
+ ElMessage.success(`${device.deviceName} ${status}鎴愬姛`)
+ // 鍒锋柊鍒楄〃
+ await getList()
+ } else {
+ ElMessage.error(response.msg || '鎿嶄綔澶辫触')
+ }
+ } catch (error) {
+ console.error('鏇存柊璁惧鐘舵�佸け璐�:', error)
+ ElMessage.error('鎿嶄綔澶辫触')
+ }
+}
+
+const getDeviceStatusType = (status) => {
+ if (status === '杩愯涓�') {
+ return 'success'
+ } else if (status === '鍋滄杩愯') {
+ return 'danger'
+ } else {
+ return 'info'
+ }
+}
+
+// 鑾峰彇琛ㄦ牸琛岀殑绫诲悕
+const getRowClassName = ({ row }) => {
+ if (isOverdue(row)) {
+ return 'overdue-row'
+ }
+ return ''
+}
+
+
+
+// 缁勪欢鎸傝浇鏃跺垵濮嬪寲鏁版嵁
+onMounted(() => {
+ getList()
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+ background: #f5f7fa;
+ min-height: 100vh;
+}
+
+
+.filter-section {
+ margin-bottom: 20px;
+ padding: 15px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ display: flex;
+ justify-content: flex-start;
+}
+
+.table-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.el-card__header) {
+ background: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ font-weight: 500;
+ font-size: 16px;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+ background-color: #F0F1F5 !important;
+ color: #333333;
+ font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+ padding: 12px 0;
+}
+
+:deep(.el-select) {
+ width: 100%;
+}
+
+:deep(.el-tag) {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+}
+
+/* 瓒呮椂鏈惎鍔ㄨ鐨勬牱寮� */
+:deep(.overdue-row) {
+ background-color: #fef0f0 !important;
+ border-left: 4px solid #f56c6c;
+}
+
+:deep(.overdue-row:hover) {
+ background-color: #fde2e2 !important;
+}
+
+:deep(.overdue-row td) {
+ background-color: transparent !important;
+}
+</style>
diff --git a/src/views/equipmentManagement/repair/Form/MaintainForm.vue b/src/views/equipmentManagement/repair/Form/MaintainForm.vue
deleted file mode 100644
index bbb25c1..0000000
--- a/src/views/equipmentManagement/repair/Form/MaintainForm.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<template>
- <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-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>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import useUserStore from "@/store/modules/user";
-import dayjs from "dayjs";
-
-defineOptions({
- name: "璁惧缁翠慨琛ㄥ崟",
-});
-
-const userStore = useUserStore();
-const { form, resetForm } = useFormData({
- maintenanceName: undefined, // 缁翠慨鍚嶇О
- maintenanceResult: undefined, // 缁翠慨缁撴灉
- maintenanceTime: undefined, // 缁翠慨鏃ユ湡
-});
-
-const setForm = (data) => {
- form.maintenanceName = data.maintenanceName ?? userStore.nickName;
- form.maintenanceResult = data.maintenanceResult;
- form.maintenanceTime =
- dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss") ??
- dayjs().format("YYYY-MM-DD HH:mm:ss");
-};
-
-const getForm = () => {
- return form;
-};
-
-defineExpose({
- getForm,
- setForm,
- resetForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/repair/Form/RepairForm.vue b/src/views/equipmentManagement/repair/Form/RepairForm.vue
deleted file mode 100644
index 1fadde4..0000000
--- a/src/views/equipmentManagement/repair/Form/RepairForm.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<template>
- <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">
- <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-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>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
-import useUserStore from "@/store/modules/user";
-
-defineOptions({
- name: "璁惧鎶ヤ慨琛ㄥ崟",
-});
-
-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: undefined, // 鎶ヤ慨鏃ユ湡
- repairName: userStore.nickName, // 鎶ヤ慨浜�
- remark: undefined, // 鏁呴殰鐜拌薄
-});
-
-const setDeviceModel = (id) => {
- const option = deviceOptions.value.find((item) => item.id === id);
- form.deviceModel = option.deviceModel;
-};
-
-const getForm = () => {
- return form;
-};
-
-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;
-};
-
-// onMounted(() => {
-// loadDeviceName();
-// });
-
-defineExpose({
- loadDeviceName,
- resetForm,
- getForm,
- setForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
index 309be0e..496b072 100644
--- a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -1,53 +1,108 @@
<template>
- <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr">
- <MaintainForm ref="maintainFormRef" />
- <template #footer>
- <el-button type="primary" @click="sendForm" :loading="loading">
- {{ modalOptions.confirmText }}
- </el-button>
- <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
- </template>
- </el-dialog>
+ <FormDialog
+ v-model="visible"
+ :title="'璁惧缁翠慨'"
+ width="500px"
+ @confirm="sendForm"
+ @cancel="handleCancel"
+ @close="handleClose"
+ >
+ <el-form :model="form" label-width="80px">
+ <el-form-item label="缁翠慨浜�">
+ <el-input v-model="form.maintenanceName" placeholder="璇疯緭鍏ョ淮淇汉" />
+ </el-form-item>
+ <el-form-item label="缁翠慨缁撴灉">
+ <el-input v-model="form.maintenanceResult" placeholder="璇疯緭鍏ョ淮淇粨鏋�" />
+ </el-form-item>
+ <el-form-item label="缁翠慨鐘舵��">
+ <el-select v-model="form.status">
+ <el-option label="寰呮姤淇�" :value="0"></el-option>
+ <el-option label="瀹岀粨" :value="1"></el-option>
+ <el-option label="澶辫触" :value="2"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="缁翠慨鏃ユ湡">
+ <el-date-picker
+ v-model="form.maintenanceTime"
+ placeholder="璇烽�夋嫨缁翠慨鏃ユ湡"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ type="datetime"
+ clearable
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
</template>
<script setup>
-import { useModal } from "@/hooks/useModal";
-import MaintainForm from "../Form/MaintainForm.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
import { addMaintain } from "@/api/equipmentManagement/repair";
+import useFormData from "@/hooks/useFormData";
+import useUserStore from "@/store/modules/user";
+import dayjs from "dayjs";
+import { ElMessage } from "element-plus";
defineOptions({
name: "缁翠慨妯℃�佹",
});
-const maintainFormRef = ref();
const emits = defineEmits(["ok"]);
-const {
- id,
- visible,
- loading,
- openModal,
- modalOptions,
- handleConfirm,
- closeModal,
-} = useModal({ title: "璁惧缁翠慨" });
+// 淇濆瓨鎶ヤ慨璁板綍鐨刬d
+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; // 淇濆瓨鎶ヤ慨璁板綍鐨刬d
+ visible.value = true;
await nextTick();
- maintainFormRef.value.setForm(row);
+ setForm(row);
};
defineExpose({
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 8441da2..1aa82ec 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,93 @@
<template>
- <el-dialog v-model="visible" :title="modalOptions.title" @close="close">
- <RepairForm ref="repairFormRef" />
- <template #footer>
- <el-button type="primary" @click="sendForm" :loading="loading">
- {{ modalOptions.confirmText }}
- </el-button>
- <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
- </template>
- </el-dialog>
+ <FormDialog
+ v-model="visible"
+ :title="id ? '缂栬緫璁惧鎶ヤ慨' : '鏂板璁惧鎶ヤ慨'"
+ width="800px"
+ @confirm="sendForm"
+ @cancel="handleCancel"
+ @close="handleClose"
+ >
+ <el-form :model="form" label-width="100px">
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="璁惧鍚嶇О">
+ <el-select v-model="form.deviceLedgerId" @change="setDeviceModel" filterable>
+ <el-option
+ v-for="(item, index) in deviceOptions"
+ :key="index"
+ :label="item.deviceName"
+ :value="item.id"
+ ></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿">
+ <el-input
+ v-model="form.deviceModel"
+ placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎶ヤ慨鏃ユ湡">
+ <el-date-picker
+ v-model="form.repairTime"
+ placeholder="璇烽�夋嫨鎶ヤ慨鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ type="date"
+ clearable
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎶ヤ慨浜�">
+ <el-input v-model="form.repairName" placeholder="璇疯緭鍏ユ姤淇汉" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row v-if="id">
+ <el-col :span="12">
+ <el-form-item label="鎶ヤ慨鐘舵��">
+ <el-select v-model="form.status">
+ <el-option label="寰呯淮淇�" :value="0"></el-option>
+ <el-option label="瀹岀粨" :value="1"></el-option>
+ <el-option label="澶辫触" :value="2"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鏁呴殰鐜拌薄">
+ <el-input
+ v-model="form.remark"
+ :rows="2"
+ type="textarea"
+ placeholder="璇疯緭鍏ユ晠闅滅幇璞�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
</template>
<script setup>
-import { useModal } from "@/hooks/useModal";
-import RepairForm from "../Form/RepairForm.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
addRepair,
editRepair,
getRepairById,
} from "@/api/equipmentManagement/repair";
import { ElMessage } from "element-plus";
+import dayjs from "dayjs";
+import useFormData from "@/hooks/useFormData";
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import useUserStore from "@/store/modules/user";
defineOptions({
name: "璁惧鎶ヤ慨寮圭獥",
@@ -26,48 +95,83 @@
const emits = defineEmits(["ok"]);
-const repairFormRef = ref();
-const {
- id,
- visible,
- loading,
- openModal,
- modalOptions,
- handleConfirm,
- closeModal,
-} = useModal({ title: "璁惧鎶ヤ慨" });
+const id = ref();
+const visible = ref(false);
+const loading = ref(false);
+
+const userStore = useUserStore();
+const deviceOptions = ref([]);
+
+const loadDeviceName = async () => {
+ const { data } = await getDeviceLedger();
+ deviceOptions.value = data;
+};
+
+const { form, resetForm } = useFormData({
+ deviceLedgerId: undefined, // 璁惧Id
+ deviceName: undefined, // 璁惧鍚嶇О
+ deviceModel: undefined, // 瑙勬牸鍨嬪彿
+ repairTime: dayjs().format("YYYY-MM-DD"), // 鎶ヤ慨鏃ユ湡锛岄粯璁ゅ綋澶�
+ repairName: userStore.nickName, // 鎶ヤ慨浜�
+ remark: undefined, // 鏁呴殰鐜拌薄
+ status: 0, // 鎶ヤ慨鐘舵��
+});
+
+const setDeviceModel = (deviceId) => {
+ const option = deviceOptions.value.find((item) => item.id === deviceId);
+ form.deviceModel = option.deviceModel;
+};
+
+const setForm = (data) => {
+ form.deviceLedgerId = data.deviceLedgerId;
+ form.deviceName = data.deviceName;
+ form.deviceModel = data.deviceModel;
+ form.repairTime = data.repairTime;
+ form.repairName = data.repairName;
+ form.remark = data.remark;
+ form.status = data.status;
+};
const sendForm = async () => {
loading.value = true;
- const form = await repairFormRef.value.getForm();
- const { code } = id.value
- ? await editRepair({ id: unref(id), ...form })
- : await addRepair(form);
- if (code == 200) {
- ElMessage.success(`${id ? "缂栬緫" : "鏂板"}鎶ヤ慨鎴愬姛`);
- closeModal();
- emits("ok");
+ try {
+ const { code } = id.value
+ ? await editRepair({ id: unref(id), ...form })
+ : await addRepair(form);
+ if (code == 200) {
+ ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}鎶ヤ慨鎴愬姛`);
+ visible.value = false;
+ emits("ok");
+ }
+ } finally {
+ loading.value = false;
}
- loading.value = false;
+};
+
+const handleCancel = () => {
+ resetForm();
+ visible.value = false;
+};
+
+const handleClose = () => {
+ resetForm();
+ visible.value = false;
};
const openAdd = async () => {
- openModal();
+ id.value = undefined;
+ visible.value = true;
await nextTick();
- await repairFormRef.value.loadDeviceName();
+ await loadDeviceName();
};
-const openEdit = async (id) => {
- const { data } = await getRepairById(id);
- openModal(id);
+const openEdit = async (editId) => {
+ const { data } = await getRepairById(editId);
+ id.value = editId;
+ visible.value = true;
await nextTick();
- await repairFormRef.value.loadDeviceName();
- await repairFormRef.value.setForm(data);
-};
-
-const close = () => {
- repairFormRef.value.resetForm();
- closeModal();
+ await loadDeviceName();
+ setForm(data);
};
defineExpose({
@@ -75,3 +179,5 @@
openEdit,
});
</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index 3ef3692..1815111 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -68,21 +68,16 @@
<div class="actions">
<el-text class="mx-1" size="large">璁惧鎶ヤ慨</el-text>
<div>
- <el-button
- type="primary"
- icon="Plus"
- :disabled="multipleList.length !== 1"
- @click="addMaintain"
- >
- 鏂板缁翠慨
- </el-button>
<el-button type="success" icon="Van" @click="addRepair">
鏂板鎶ヤ慨
+ </el-button>
+ <el-button @click="handleOut">
+ 瀵煎嚭
</el-button>
<el-button
type="danger"
icon="Delete"
- :disabled="multipleList.length <= 0"
+ :disabled="multipleList.length <= 0 || hasFinishedStatus"
@click="delRepairByIds(multipleList.map((item) => item.id))"
>
鎵归噺鍒犻櫎
@@ -90,35 +85,44 @@
</div>
</div>
<PIMTable
- rowKey="id"
- isSelection
- :column="columns"
- :tableData="dataList"
- :page="{
+ rowKey="id"
+ isSelection
+ :column="columns"
+ :tableData="dataList"
+ :page="{
current: pagination.currentPage,
size: pagination.pageSize,
total: pagination.total,
}"
- @selection-change="handleSelectionChange"
- @pagination="changePage"
+ @selection-change="handleSelectionChange"
+ @pagination="changePage"
>
<template #statusRef="{ row }">
+ <el-tag v-if="row.status === 2" type="danger">澶辫触</el-tag>
<el-tag v-if="row.status === 1" type="success">瀹岀粨</el-tag>
- <el-tag v-if="row.status === 0" type="danger">寰呯淮淇�</el-tag>
+ <el-tag v-if="row.status === 0" type="warning">寰呯淮淇�</el-tag>
</template>
<template #operation="{ row }">
<el-button
type="primary"
- text
- icon="editPen"
+ link
+ :disabled="row.status === 1"
@click="editRepair(row.id)"
>
缂栬緫
</el-button>
<el-button
+ type="success"
+ link
+ :disabled="row.status === 1"
+ @click="addMaintain(row)"
+ >
+ 缁翠慨
+ </el-button>
+ <el-button
type="danger"
- text
- icon="delete"
+ link
+ :disabled="row.status === 1"
@click="delRepairByIds(row.id)"
>
鍒犻櫎
@@ -126,17 +130,17 @@
</template>
</PIMTable>
</div>
- <RepairModal ref="repairModalRef" @ok="getTableData" />
- <MaintainModal ref="maintainModalRef" @ok="getTableData" />
+ <RepairModal ref="repairModalRef" @ok="getTableData"/>
+ <MaintainModal ref="maintainModalRef" @ok="getTableData"/>
</div>
</template>
<script setup>
-import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair";
-import { onMounted } from "vue";
+import { onMounted, getCurrentInstance, computed } from "vue";
+import {usePaginationApi} from "@/hooks/usePaginationApi";
+import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
import RepairModal from "./Modal/RepairModal.vue";
-import { ElMessageBox, ElMessage } from "element-plus";
+import {ElMessageBox, ElMessage} from "element-plus";
import dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
import {Search} from "@element-plus/icons-vue";
@@ -144,6 +148,8 @@
defineOptions({
name: "璁惧鎶ヤ慨",
});
+
+const {proxy} = getCurrentInstance();
// 妯℃�佹瀹炰緥
const repairModalRef = ref();
@@ -162,85 +168,85 @@
resetFilters,
onCurrentChange,
} = usePaginationApi(
- getRepairPage,
- {
- deviceName: undefined,
- deviceModel: undefined,
- remark: undefined,
- maintenanceName: undefined,
- repairTimeStr: undefined,
- maintenanceTimeStr: undefined,
- },
- [
+ getRepairPage,
{
- label: "璁惧鍚嶇О",
- align: "center",
- prop: "deviceName",
+ deviceName: undefined,
+ deviceModel: undefined,
+ remark: undefined,
+ maintenanceName: undefined,
+ repairTimeStr: undefined,
+ maintenanceTimeStr: undefined,
},
- {
- label: "瑙勬牸鍨嬪彿",
- align: "center",
- prop: "deviceModel",
- },
- {
- label: "鎶ヤ慨鏃ユ湡",
- align: "center",
- prop: "repairTime",
- formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
- },
- {
- label: "鎶ヤ慨浜�",
- align: "center",
- prop: "repairName",
- },
- {
- label: "鏁呴殰鐜拌薄",
- align: "center",
- prop: "remark",
- },
- {
- label: "缁翠慨浜�",
- align: "center",
- prop: "maintenanceName",
- },
- {
- label: "缁翠慨缁撴灉",
- align: "center",
- prop: "maintenanceResult",
- },
- {
- label: "缁翠慨鏃ユ湡",
- align: "center",
- prop: "maintenanceTime",
- formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
- },
- {
- label: "鐘舵��",
- align: "center",
- prop: "status",
- dataType: "slot",
- slot: "statusRef",
- },
- {
- fixed: "right",
- label: "鎿嶄綔",
- dataType: "slot",
- slot: "operation",
- align: "center",
- width: "200px",
- },
- ]
+ [
+ {
+ label: "璁惧鍚嶇О",
+ align: "center",
+ prop: "deviceName",
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ align: "center",
+ prop: "deviceModel",
+ },
+ {
+ label: "鎶ヤ慨鏃ユ湡",
+ align: "center",
+ prop: "repairTime",
+ formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
+ },
+ {
+ label: "鎶ヤ慨浜�",
+ align: "center",
+ prop: "repairName",
+ },
+ {
+ label: "鏁呴殰鐜拌薄",
+ align: "center",
+ prop: "remark",
+ },
+ {
+ label: "缁翠慨浜�",
+ align: "center",
+ prop: "maintenanceName",
+ },
+ {
+ label: "缁翠慨缁撴灉",
+ align: "center",
+ prop: "maintenanceResult",
+ },
+ {
+ label: "缁翠慨鏃ユ湡",
+ align: "center",
+ prop: "maintenanceTime",
+ formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
+ },
+ {
+ label: "鐘舵��",
+ align: "center",
+ prop: "status",
+ dataType: "slot",
+ slot: "statusRef",
+ },
+ {
+ fixed: "right",
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ align: "center",
+ width: "300px",
+ },
+ ]
);
// type === 1 缁翠慨 2鎶ヤ慨闂�
-const handleDateChange = (value,type) => {
+const handleDateChange = (value, type) => {
filters.maintenanceTimeStr = null
filters.c = null
- if(type === 1){
+ if (type === 1) {
if (value) {
filters.maintenanceTimeStr = dayjs(value).format("YYYY-MM-DD");
}
- }else{
+ } else {
if (value) {
filters.repairTimeStr = dayjs(value).format("YYYY-MM-DD");
}
@@ -253,6 +259,11 @@
multipleList.value = selectionList;
};
+// 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
+const hasFinishedStatus = computed(() => {
+ return multipleList.value.some(item => item.status === 1)
+})
+
// 鏂板鎶ヤ慨
const addRepair = () => {
repairModalRef.value.openAdd();
@@ -264,30 +275,56 @@
};
// 鏂板缁翠慨
-const addMaintain = () => {
- const row = multipleList.value[0];
+const addMaintain = (row) => {
maintainModalRef.value.open(row.id, row);
};
-const changePage = ({ page, limit }) => {
- pagination.currentPage = page;
- pagination.pageSize = limit;
- onCurrentChange(page);
+const changePage = ({page, limit}) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ onCurrentChange(page);
};
// 鍗曡鍒犻櫎
const delRepairByIds = async (ids) => {
+ // 妫�鏌ユ槸鍚︽湁瀹岀粨鐘舵�佺殑璁板綍
+ const idsArray = Array.isArray(ids) ? ids : [ids];
+ const hasFinished = idsArray.some(id => {
+ const record = dataList.value.find(item => item.id === id);
+ return record && record.status === 1;
+ });
+
+ if (hasFinished) {
+ ElMessage.warning('涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�');
+ return;
+ }
+
ElMessageBox.confirm("纭鍒犻櫎鎶ヤ慨鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
}).then(async () => {
- const { code } = await delRepair(ids);
+ const {code} = await delRepair(ids);
if (code === 200) {
ElMessage.success("鍒犻櫎鎴愬姛");
getTableData();
}
});
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/device/repair/export", {}, "璁惧鎶ヤ慨.xlsx");
+ })
+ .catch(() => {
+ ElMessage.info("宸插彇娑�");
+ });
};
onMounted(() => {
@@ -299,6 +336,7 @@
.table_list {
margin-top: unset;
}
+
.actions {
display: flex;
justify-content: space-between;
diff --git a/src/views/equipmentManagement/spareParts/index.vue b/src/views/equipmentManagement/spareParts/index.vue
new file mode 100644
index 0000000..eb0bdd5
--- /dev/null
+++ b/src/views/equipmentManagement/spareParts/index.vue
@@ -0,0 +1,401 @@
+<template>
+ <div class="spare-part-category">
+ <div class="search_form">
+ <el-form :inline="true" :model="queryParams" class="search-form">
+ <el-form-item label="澶囦欢鍚嶇О">
+ <el-input
+ v-model="queryParams.name"
+ placeholder="璇疯緭鍏ュ浠跺悕绉�"
+ clearable
+ style="width: 240px"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div>
+ <el-button type="primary" @click="addCategory" >鏂板</el-button>
+ </div>
+ </div>
+
+ <div class="table_list">
+ <el-table
+ v-loading="loading"
+ :data="renderTableData"
+ style="width: 100%; margin-top: 10px;"
+ border
+ row-key="id"
+ >
+ <el-table-column prop="deviceNameStr" label="璁惧鍚嶇О" width="300"></el-table-column>
+ <el-table-column prop="name" label="澶囦欢鍚嶇О" width="200"></el-table-column>
+ <el-table-column prop="sparePartsNo" label="澶囦欢缂栧彿" width="200"></el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="{ row }">
+ <el-tag type="success" size="small">{{ row.status }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="price" label="浠锋牸" width="140"></el-table-column>
+ <el-table-column prop="quantity" label="鏁伴噺" width="140"></el-table-column>
+ <el-table-column prop="description" label="鎻忚堪" width="150"></el-table-column>
+ <el-table-column label="鎿嶄綔" width="150" fixed="right" align="center">
+ <template #default="{ row }">
+ <el-button
+ link
+ type="primary"
+ @click="() => editCategory(row)"
+ :disabled="loading"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ link
+ @click="() => deleteCategory(row.id)"
+ style="color: #f56c6c;"
+ :disabled="loading"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <el-dialog title="鍒嗙被绠$悊" v-model="dialogVisible" width="60%">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-form-item label="璁惧" prop="deviceLedgerIds">
+ <el-select
+ v-model="form.deviceLedgerIds"
+ placeholder="璇烽�夋嫨璁惧"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
+ multiple
+ style="width: 100%"
+ >
+ <el-option
+ v-for="(item, index) in deviceOptions"
+ :key="index"
+ :label="item.deviceName"
+ :value="item.id"
+ ></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囦欢鍚嶇О" prop="name">
+ <el-input v-model="form.name"></el-input>
+ </el-form-item>
+ <el-form-item label="澶囦欢缂栧彿" prop="sparePartsNo">
+ <el-input v-model="form.sparePartsNo"></el-input>
+ </el-form-item>
+ <el-form-item label="鏁伴噺" prop="quantity">
+ <el-input type="number" v-model="form.quantity"></el-input>
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��">
+ <el-option label="姝e父" value="姝e父"></el-option>
+ <el-option label="绂佺敤" value="绂佺敤"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎻忚堪" prop="description">
+ <el-input v-model="form.description"></el-input>
+ </el-form-item>
+ <el-form-item label="浠锋牸" prop="price">
+ <el-input-number
+ v-model="form.price"
+ placeholder="璇疯緭鍏ヤ环鏍�"
+ :min="0"
+ :step="0.01"
+ :precision="2"
+ style="width: 100%"
+ ></el-input-number>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="dialogVisible = false" :disabled="formLoading">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm" :loading="formLoading">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, reactive, watch } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts";
+import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+
+// 鍔犺浇鐘舵��
+const loading = ref(false);
+const formLoading = ref(false);
+// 瀵硅瘽妗嗘樉绀虹姸鎬�
+const dialogVisible = ref(false);
+// 缂栬緫 ID
+const editId = ref(null);
+// 琛ㄦ牸鏁版嵁
+const categories = ref([]);
+// 娓叉煋鐢ㄧ殑琛ㄦ牸鏁版嵁
+// const renderTableData = computed(() => buildTree(categories.value));
+const renderTableData = ref([]);
+const operationType = ref('add')
+// 璁惧閫夐」
+const deviceOptions = ref([]);
+// 琛ㄥ崟寮曠敤
+const formRef = ref(null);
+// 鏌ヨ鍙傛暟
+const queryParams = reactive({
+ name: ''
+});
+// 琛ㄥ崟鏁版嵁
+const form = reactive({
+ id:'',
+ name: '',
+ sparePartsNo: '',
+ status: '',
+ description: '',
+ deviceLedgerIds: [],
+ price: null
+});
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const rules = reactive({
+ name: [
+ { required: true, message: '璇疯緭鍏ュ浠跺悕绉�', trigger: 'blur' }
+ ],
+ sparePartsNo: [
+ { required: true, message: '璇疯緭鍏ュ浠剁紪鍙�', trigger: 'blur' }
+ ],
+ quantity:[
+ { required: true, message: '璇疯緭鍏ユ暟閲�', trigger: 'blur' }
+ ],
+ status: [
+ { required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }
+ ],
+ deviceLedgerIds: [
+ {
+ required: true,
+ message: '璇烽�夋嫨璁惧',
+ trigger: 'change',
+ validator: (rule, value, callback) => {
+ if (operationType.value === 'add' && (!value || value.length === 0)) {
+ callback(new Error('璇烽�夋嫨璁惧'));
+ } else {
+ callback();
+ }
+ }
+ }
+ ]
+});
+// 鑾峰彇缂╄繘閲�
+const getIndentation = (row) => {
+ // 杩欓噷绠�鍗曡繑鍥� 20锛屽彲鏍规嵁瀹為檯闇�姹傚疄鐜板眰绾х缉杩涢�昏緫
+ return 20;
+};
+// 瀹氫箟 buildTree 鍑芥暟
+const buildTree = (flatData) => {
+ const map = {};
+ const result = [];
+ if(flatData){
+ return result;
+ }
+ flatData.forEach(item => {
+ map[item.id] = { ...item, children: [] };
+ });
+ flatData.forEach(item => {
+ if (item.parentId === null || !map[item.parentId]) {
+ result.push(map[item.id]);
+ } else {
+ map[item.parentId].children.push(map[item.id]);
+ }
+ });
+ return result;
+};
+// 鑾峰彇鍒楄〃鏁版嵁
+const fetchListData = async () => {
+ loading.value = true;
+ try {
+ const params = {};
+ if (queryParams.name) {
+ params.name = queryParams.name;
+ }
+ const res = await getSparePartsList(params);
+ if (res.code === 200) {
+ renderTableData.value = res.data.records || [];
+ categories.value = res.data.records || [];
+ }
+ } catch (error) {
+ loading.value = false;
+ } finally {
+ loading.value = false;
+ }
+}
+
+// 鏌ヨ
+const handleQuery = () => {
+ fetchListData();
+}
+
+// 閲嶇疆鏌ヨ
+const resetQuery = () => {
+ queryParams.name = '';
+ fetchListData();
+}
+
+// 鍔犺浇璁惧鍒楄〃锛堝湪鎵撳紑寮规鏃惰皟鐢級
+const loadDeviceName = async () => {
+ try {
+ const { data } = await getDeviceLedger();
+ deviceOptions.value = data || [];
+ } catch (error) {
+ ElMessage.error('鑾峰彇璁惧鍒楄〃澶辫触');
+ }
+};
+
+// 鏂板鍒嗙被
+const addCategory = async () => {
+ await loadDeviceName();
+ form.id = '';
+ form.name = '';
+ form.sparePartsNo = '';
+ form.status = '';
+ form.description = '';
+ form.deviceLedgerIds = [];
+ form.price = null;
+ operationType.value = 'add'
+ dialogVisible.value = true;
+};
+
+// 缂栬緫鍒嗙被
+const editCategory = async (row) => {
+ await loadDeviceName();
+ Object.assign(form, row);
+ // 濡傛灉鍚庣杩斿洖鐨勬槸 deviceIds 瀛楃涓诧紝闇�瑕佽浆鎹负鏁扮粍
+ if (row.deviceIds && typeof row.deviceIds === 'string') {
+ // 纭繚ID绫诲瀷涓庤澶囬�夐」涓殑ID绫诲瀷涓�鑷�
+ const deviceIdsArray = row.deviceIds.split(',').map(id => id.trim()).filter(id => id);
+ // 濡傛灉璁惧閫夐」涓殑ID鏄暟瀛楃被鍨嬶紝鍒欒浆鎹负鏁板瓧
+ if (deviceOptions.value.length > 0 && typeof deviceOptions.value[0].id === 'number') {
+ form.deviceLedgerIds = deviceIdsArray.map(id => Number(id)).filter(id => !isNaN(id));
+ } else {
+ form.deviceLedgerIds = deviceIdsArray;
+ }
+ } else if (row.deviceIds && Array.isArray(row.deviceIds)) {
+ form.deviceLedgerIds = row.deviceIds;
+ } else {
+ form.deviceLedgerIds = [];
+ }
+ operationType.value = 'edit'
+ dialogVisible.value = true;
+};
+
+// 鍒犻櫎鍒嗙被
+const deleteCategory = async (id) => {
+ try {
+ await ElMessageBox.confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ュ垎绫伙紝鏄惁缁х画?', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ });
+ loading.value = true;
+ const res = await delSparePart(id);
+ if (res.code === 200) {
+ ElMessage.success('鍒犻櫎鎴愬姛');
+ fetchListData();
+ } else {
+ ElMessage.error(res.message || '鍒犻櫎澶辫触');
+ }
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error('鍒犻櫎澶辫触');
+ }
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = async () => {
+ if (!formRef.value) return;
+ try {
+ await formRef.value.validate();
+ formLoading.value = true;
+
+ // 鏋勫缓鎻愪氦鏁版嵁
+ const submitData = {
+ ...form,
+ deviceIds: form.deviceLedgerIds && form.deviceLedgerIds.length > 0
+ ? form.deviceLedgerIds.join(',')
+ : ''
+ };
+
+ // 鍒犻櫎涓嶉渶瑕佺殑瀛楁
+ delete submitData.deviceLedgerIds;
+
+ if (operationType.value === 'edit') {
+ let res = await editSparePart(submitData);
+ if (res.code === 200) {
+ ElMessage.success('缂栬緫鎴愬姛');
+ dialogVisible.value = false;
+ fetchListData();
+ }
+ } else {
+ let res = await addSparePart(submitData);
+ if (res.code === 200) {
+ ElMessage.success('鏂板鎴愬姛');
+ dialogVisible.value = false;
+ fetchListData();
+ }
+ }
+ } catch (error) {
+ ElMessage.error('璇峰~鍐欏畬鏁磋〃鍗曚俊鎭�');
+ } finally {
+ formLoading.value = false;
+ }
+};
+
+// 缁勪欢鎸傝浇鏃惰幏鍙栧垪琛ㄦ暟鎹�
+onMounted(() => {
+ fetchListData();
+});
+</script>
+
+<style scoped>
+.spare-part-category {
+ padding: 20px;
+}
+.search_form {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+}
+.table_list {
+ margin-top: unset;
+}
+
+.el-table__header-wrapper th {
+ background-color: #f5f7fa;
+ font-weight: 600;
+}
+
+.el-table__row:hover > td {
+ background-color: #fafafa;
+}
+
+/* 鎸夐挳缁勬牱寮� */
+.actions > div {
+ display: flex;
+ gap: 10px;
+}
+
+/* 纭繚琛ㄦ牸涓殑鎿嶄綔鎸夐挳涓嶄細琚埅鏂� */
+.el-table-column--fixed-right .el-button {
+ margin: 0 2px;
+}
+
+/* 鏍戝舰鑺傜偣鍐呭鏍峰紡 */
+.nested-tree .el-tree-node__expand-icon {
+ font-size: 12px;
+ margin-right: 4px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue b/src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
deleted file mode 100644
index bc3db70..0000000
--- a/src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
- <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.maintenanceResult" placeholder="璇烽�夋嫨淇濆吇缁撴灉">
- <el-option label="瀹屽ソ" :value="1"></el-option>
- <el-option label="缁翠慨" :value="0"></el-option>
- </el-select>
- </el-form-item>
- </el-form>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import dayjs from "dayjs";
-import useUserStore from "@/store/modules/user";
-
-defineOptions({
- name: "淇濆吇琛ㄥ崟",
-});
-
-const userStore = useUserStore();
-const { form, resetForm } = useFormData({
- maintenanceActuallyName: undefined, // 瀹為檯淇濆吇浜�
- maintenanceActuallyTime: undefined, // 瀹為檯淇濆吇鏃ユ湡
- maintenanceResult: undefined, // 淇濆吇缁撴灉
-});
-
-const setForm = (data) => {
- form.maintenanceActuallyName =
- data.maintenanceActuallyName ?? userStore.nickName;
- form.maintenanceActuallyTime =
- dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss") ??
- dayjs().format("YYYY-MM-DD HH:mm:ss");
- form.maintenanceResult = data.maintenanceResult;
-};
-
-const getForm = () => {
- return form;
-};
-
-defineExpose({
- getForm,
- setForm,
- resetForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
new file mode 100644
index 0000000..c660840
--- /dev/null
+++ b/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"]);
+
+// 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
+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; // 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
+ visible.value = true;
+ await nextTick();
+ setForm(row);
+};
+
+defineExpose({
+ open,
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanForm.vue b/src/views/equipmentManagement/upkeep/Form/PlanForm.vue
deleted file mode 100644
index 1d94b68..0000000
--- a/src/views/equipmentManagement/upkeep/Form/PlanForm.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<template>
- <el-form :model="form" label-width="100px">
- <el-form-item label="璁惧鍚嶇О">
- <el-select
- v-model="form.deviceLedgerId"
- @change="setDeviceModel"
- placeholder="璇烽�夋嫨璁惧"
- >
- <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-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>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
-import { onMounted } from "vue";
-import dayjs from "dayjs";
-
-defineOptions({
- name: "璁″垝琛ㄥ崟",
-});
-
-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, // 璁″垝淇濆吇鏃ユ湡
-});
-
-const setDeviceModel = (id) => {
- const option = deviceOptions.value.find((item) => item.id === id);
- form.deviceModel = option.deviceModel;
-};
-
-const getForm = () => {
- return form;
-};
-
-/**
- * @desc 璁剧疆琛ㄥ崟鍐呭
- * @param data 璁惧淇℃伅
- */
-const setForm = (data) => {
- form.deviceLedgerId = data.deviceLedgerId;
- form.deviceName = data.deviceName;
- form.deviceModel = data.deviceModel;
- form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
- "YYYY-MM-DD HH:mm:ss"
- );
-};
-
-const loadForm = () => {};
-
-onMounted(() => {
- loadDeviceName();
-});
-
-defineExpose({
- loadForm,
- resetForm,
- getForm,
- setForm,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
new file mode 100644
index 0000000..19095b9
--- /dev/null
+++ b/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>
diff --git a/src/views/equipmentManagement/upkeep/Form/formDia.vue b/src/views/equipmentManagement/upkeep/Form/formDia.vue
new file mode 100644
index 0000000..66bf067
--- /dev/null
+++ b/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="瀛e害" 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
+ }
+
+ // 濡傛灉鏈夎澶嘔D锛岃嚜鍔ㄨ缃澶囦俊鎭�
+ 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()
+ }
+ // 閲嶇疆琛ㄥ崟鏁版嵁纭繚璁惧淇℃伅姝g‘閲嶇疆
+ 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 }
+ // 涓嶅啀鍚戝悗绔紶淇濆吇浜哄瓧娈碉紝浠呬娇鐢ㄦ帴鍙h姹傜殑 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>
diff --git a/src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue b/src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
deleted file mode 100644
index 1b5a7d4..0000000
--- a/src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-<template>
- <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr">
- <MaintenanceForm ref="maintenanceFormRef" />
- <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>
-</template>
-
-<script setup>
-import MaintenanceForm from "../Form/MaintenanceForm.vue";
-import { useModal } from "@/hooks/useModal";
-import { addMaintenance } from "@/api/equipmentManagement/upkeep";
-
-defineOptions({
- name: "淇濆吇妯℃�佹",
-});
-
-const maintenanceFormRef = ref();
-const emits = defineEmits(["ok"]);
-
-const {
- id,
- visible,
- loading,
- openModal,
- modalOptions,
- handleConfirm,
- closeModal,
-} = useModal({ title: "璁惧缁翠慨" });
-
-/**
- * @desc 淇濆瓨淇濆吇
- */
-const sendForm = async () => {
- loading.value = true;
- const form = await maintenanceFormRef.value.getForm();
- const { code } = await addMaintenance({ id: id.value, ...form });
- if (code == 200) {
- emits("ok");
- maintenanceFormRef.value.resetForm();
- closeModal();
- }
- loading.value = false;
-};
-
-const open = async (id, row) => {
- openModal(id);
- await nextTick();
- maintenanceFormRef.value.setForm(row);
-};
-defineExpose({
- open,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Modal/PlanModal.vue b/src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
deleted file mode 100644
index d9cf246..0000000
--- a/src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
- <el-dialog
- v-model="visible"
- :title="modalOptions.title"
- width="30%"
- @close="close"
- >
- <PlanForm ref="planFormRef"></PlanForm>
- <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>
-</template>
-
-<script setup>
-import { useModal } from "@/hooks/useModal";
-import PlanForm from "../Form/PlanForm";
-import {
- addUpkeep,
- editUpkeep,
- getUpkeepById,
-} from "@/api/equipmentManagement/upkeep";
-import { ElMessage } from "element-plus";
-
-defineOptions({
- name: "璁惧淇濆吇鏂板璁″垝",
-});
-
-const emits = defineEmits(["ok"]);
-const planFormRef = ref();
-const {
- id,
- visible,
- loading,
- openModal,
- modalOptions,
- handleConfirm,
- closeModal,
-} = useModal({ title: "璁惧淇濆吇璁″垝" });
-
-const openEdit = async (id) => {
- const { data } = await getUpkeepById(id);
- openModal(id);
- await nextTick();
- await planFormRef.value.setForm(data);
-};
-
-const sendForm = async () => {
- loading.value = true;
- const form = await planFormRef.value.getForm();
- const { code } = id.value
- ? await editUpkeep({ id: unref(id), ...form })
- : await addUpkeep(form);
- if (code == 200) {
- ElMessage.success(`${id ? "缂栬緫" : "鏂板"}璁″垝鎴愬姛`);
- closeModal();
- emits("ok");
- }
- loading.value = false;
-};
-
-const close = () => {
- planFormRef.value.resetForm();
- closeModal();
-};
-
-defineExpose({
- openModal,
- openEdit,
-});
-</script>
-
-<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 484538c..046c0fa 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -1,75 +1,155 @@
<template>
<div class="app-container">
- <el-form :model="filters" :inline="true">
- <el-form-item label="璁惧鍚嶇О">
- <el-input
- v-model="filters.deviceName"
- style="width: 240px"
- placeholder="璇疯緭鍏ヨ澶囧悕绉�"
- clearable
- :prefix-icon="Search"
- @change="getTableData"
- />
- </el-form-item>
- <el-form-item label="璁″垝淇濆吇鏃ユ湡">
- <el-date-picker
- v-model="filters.maintenancePlanTime"
- type="date"
- placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡"
- size="default"
- @change="(date) => handleDateChange(date,2)"
- />
- </el-form-item>
- <el-form-item label="瀹為檯淇濆吇鏃ユ湡">
- <el-date-picker
- v-model="filters.maintenanceActuallyTime"
- type="date"
- placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
- size="default"
- @change="(date) => handleDateChange(date,1)"
- />
- </el-form-item>
- <el-form-item label="瀹為檯淇濆吇浜�">
- <el-input
- v-model="filters.maintenanceActuallyName"
- style="width: 240px"
- placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
- clearable
- :prefix-icon="Search"
- @change="getTableData"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
- <el-button @click="resetFilters">閲嶇疆</el-button>
- </el-form-item>
- </el-form>
- <div class="table_list">
- <div class="actions">
- <el-text class="mx-1" size="large">璁惧淇濆吇</el-text>
- <div>
- <el-button
- type="primary"
- icon="Plus"
- :disabled="multipleList.length !== 1"
- @click="addMaintain"
- >
- 鏂板淇濆吇
- </el-button>
- <el-button type="success" icon="Van" @click="addPlan">
- 鏂板璁″垝
- </el-button>
- <el-button
- type="danger"
- icon="Delete"
- :disabled="multipleList.length <= 0"
- @click="delRepairByIds(multipleList.map((item) => item.id))"
- >
- 鎵归噺鍒犻櫎
- </el-button>
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <!-- 瀹氭椂浠诲姟绠$悊tab -->
+ <el-tab-pane label="瀹氭椂浠诲姟绠$悊" name="scheduled">
+ <div class="search_form">
+ <el-form :model="scheduledFilters" :inline="true">
+ <el-form-item label="浠诲姟鍚嶇О">
+ <el-input
+ v-model="scheduledFilters.taskName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ヤ换鍔″悕绉�"
+ clearable
+ :prefix-icon="Search"
+ @change="getScheduledTableData"
+ />
+ </el-form-item>
+ <el-form-item label="浠诲姟鐘舵��">
+ <el-select v-model="scheduledFilters.status" placeholder="璇烽�夋嫨浠诲姟鐘舵��" clearable style="width: 200px">
+ <el-option label="鍚敤" value="1" />
+ <el-option label="鍋滅敤" value="0" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getScheduledTableData">鎼滅储</el-button>
+ <el-button @click="resetScheduledFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
</div>
- </div>
- <PIMTable
+ <div class="table_list">
+ <div class="actions">
+ <el-text class="mx-1" size="large">瀹氭椂浠诲姟绠$悊</el-text>
+ <div>
+ <el-button type="primary" icon="Plus" @click="addScheduledTask">
+ 鏂板浠诲姟
+ </el-button>
+ <el-button
+ type="danger"
+ icon="Delete"
+ :disabled="scheduledMultipleList.length <= 0"
+ @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))"
+ >
+ 鎵归噺鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ isSelection
+ :column="scheduledColumns"
+ :tableData="scheduledDataList"
+ :page="{
+ current: scheduledPagination.currentPage,
+ size: scheduledPagination.pageSize,
+ total: scheduledPagination.total,
+ }"
+ @selection-change="handleScheduledSelectionChange"
+ @pagination="changeScheduledPage"
+ >
+ <template #statusRef="{ row }">
+ <el-tag v-if="row.status === 1" type="success">鍚敤</el-tag>
+ <el-tag v-if="row.status === 0" type="danger">鍋滅敤</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button
+ type="primary"
+ link
+ @click="editScheduledTask(row)"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ type="danger"
+ link
+ @click="delScheduledTaskByIds(row.id)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </PIMTable>
+ </div>
+ </el-tab-pane>
+
+ <!-- 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛� -->
+ <el-tab-pane label="浠诲姟璁板綍" name="record">
+ <div class="search_form">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="璁惧鍚嶇О">
+ <el-input
+ v-model="filters.deviceName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+ clearable
+ :prefix-icon="Search"
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item label="璁″垝淇濆吇鏃ユ湡">
+ <el-date-picker
+ v-model="filters.maintenancePlanTime"
+ type="date"
+ placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡"
+ size="default"
+ @change="(date) => handleDateChange(date,2)"
+ />
+ </el-form-item>
+ <el-form-item label="瀹為檯淇濆吇鏃ユ湡">
+ <el-date-picker
+ v-model="filters.maintenanceActuallyTime"
+ type="date"
+ placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
+ size="default"
+ @change="(date) => handleDateChange(date,1)"
+ />
+ </el-form-item>
+ <el-form-item label="瀹為檯淇濆吇浜�">
+ <el-input
+ v-model="filters.maintenanceActuallyName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
+ clearable
+ :prefix-icon="Search"
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div class="actions">
+ <el-text class="mx-1" size="large">浠诲姟璁板綍</el-text>
+ <div>
+ <el-button type="success" icon="Van" @click="addPlan">
+ 鏂板璁″垝
+ </el-button>
+ <el-button @click="handleOut">
+ 瀵煎嚭
+ </el-button>
+ <el-button
+ type="danger"
+ icon="Delete"
+ :disabled="multipleList.length <= 0 || hasFinishedStatus"
+ @click="delRepairByIds(multipleList.map((item) => item.id))"
+ >
+ 鎵归噺鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <PIMTable
rowKey="id"
isSelection
:column="columns"
@@ -83,203 +163,422 @@
@pagination="changePage"
>
<template #maintenanceResultRef="{ row }">
- <el-tag v-if="row.maintenanceResult === 1" type="success">
- 瀹屽ソ
- </el-tag>
- <el-tag v-if="row.maintenanceResult === 0" type="danger">
- 缁翠慨
- </el-tag>
+ <div>{{ row.maintenanceResult || '-' }}</div>
</template>
<template #statusRef="{ row }">
+ <el-tag v-if="row.status === 2" type="danger">澶辫触</el-tag>
<el-tag v-if="row.status === 1" type="success">瀹岀粨</el-tag>
- <el-tag v-if="row.status === 0" type="danger">寰呬繚鍏�</el-tag>
+ <el-tag v-if="row.status === 0" type="warning">寰呬繚鍏�</el-tag>
</template>
<template #operation="{ row }">
+ <!-- 杩欎釜鍔熻兘璺熸柊澧炰繚鍏诲姛鑳戒竴妯′竴鏍凤紝鏈夊暐鎰忎箟锛� -->
+ <!-- <el-button
+ type="primary"
+ text
+ @click="addMaintain(row)"
+ >
+ 鏂板淇濆吇
+ </el-button> -->
<el-button
type="primary"
- text
- icon="editPen"
+ link
+ :disabled="row.status === 1"
@click="editPlan(row.id)"
>
缂栬緫
</el-button>
<el-button
+ type="success"
+ link
+ :disabled="row.status === 1"
+ @click="addMaintain(row)"
+ >
+ 淇濆吇
+ </el-button>
+ <el-button
type="danger"
- text
- icon="delete"
+ link
+ :disabled="row.status === 1"
@click="delRepairByIds(row.id)"
>
鍒犻櫎
</el-button>
</template>
</PIMTable>
- </div>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
<PlanModal ref="planModalRef" @ok="getTableData" />
- <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
+ <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
+ <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" />
</div>
</template>
<script setup>
-import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { getUpkeepPage, delUpkeep } from "@/api/equipmentManagement/upkeep";
-import { onMounted } from "vue";
-import PlanModal from "./Modal/PlanModal.vue";
-import MaintenanceModal from "./Modal/MaintenanceModal.vue";
-import dayjs from "dayjs";
-import { ElMessageBox, ElMessage } from "element-plus";
-import {Search} from "@element-plus/icons-vue";
+import { ref, onMounted, reactive, getCurrentInstance, nextTick, computed } from 'vue'
+import { Search } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import PlanModal from './Form/PlanModal.vue'
+import MaintenanceModal from './Form/MaintenanceModal.vue'
+import FormDia from './Form/formDia.vue'
+import {
+ getUpkeepPage,
+ delUpkeep,
+ deviceMaintenanceTaskList,
+ deviceMaintenanceTaskDel,
+} from '@/api/equipmentManagement/upkeep'
+import dayjs from 'dayjs'
-defineOptions({
- name: "璁惧淇濆吇",
-});
+const { proxy } = getCurrentInstance()
+
+// Tab鐩稿叧
+const activeTab = ref('scheduled')
// 璁″垝寮圭獥鎺у埗鍣�
-const planModalRef = ref();
+const planModalRef = ref()
// 淇濆吇寮圭獥鎺у埗鍣�
-const maintainModalRef = ref();
+const maintainModalRef = ref()
+// 瀹氭椂浠诲姟寮圭獥鎺у埗鍣�
+const formDiaRef = ref()
-// 琛ㄦ牸澶氶�夋閫変腑椤�
-const multipleList = ref([]);
+// 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
+const filters = reactive({
+ deviceName: '',
+ maintenancePlanTime: '',
+ maintenanceActuallyTime: '',
+ maintenanceActuallyName: '',
+})
-// 澶氶�夊悗鍋氫粈涔�
-const handleSelectionChange = (selectionList) => {
- multipleList.value = selectionList;
-};
+const dataList = ref([])
+const pagination = ref({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+})
+const multipleList = ref([])
-// 琛ㄦ牸閽╁瓙
-const {
- filters,
- columns,
- dataList,
- pagination,
- getTableData,
- resetFilters,
- onCurrentChange,
-} = usePaginationApi(getUpkeepPage, {
- deviceName: undefined,
- maintenancePlanTime: undefined,
- maintenanceActuallyTime: undefined,
- maintenanceActuallyName: undefined,
-}, [
- {
- label: "璁惧鍚嶇О",
- align: "center",
- prop: "deviceName",
- },
- {
- label: "瑙勬牸鍨嬪彿",
- align: "center",
- prop: "deviceModel",
- },
- {
- label: "璁″垝淇濆吇鏃ユ湡",
- align: "center",
- prop: "maintenancePlanTime",
- formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
- },
- {
- label: "褰曞叆浜�",
- align: "center",
- prop: "createUserName",
- },
- {
- label: "褰曞叆鏃ユ湡",
- align: "center",
- prop: "createTime",
- formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
- width: 200,
- },
- {
- label: "瀹為檯淇濆吇浜�",
- align: "center",
- prop: "maintenanceActuallyName",
- },
- {
- label: "瀹為檯淇濆吇鏃ユ湡",
- align: "center",
- prop: "maintenanceActuallyTime",
- formatData: (cell) =>
- cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
- },
- {
- label: "淇濆吇缁撴灉",
- align: "center",
- prop: "maintenanceResult",
- dataType: "slot",
- slot: "maintenanceResultRef",
- },
- {
- label: "鐘舵��",
- align: "center",
- prop: "status",
- dataType: "slot",
- slot: "statusRef",
- },
- {
- fixed: "right",
- label: "鎿嶄綔",
- dataType: "slot",
- slot: "operation",
- align: "center",
- width: "200px",
- },
-]);
-// type == 1瀹為檯淇濆吇鏃堕棿 2璁″垝淇濆吇鏃堕棿
-const handleDateChange = (value,type) => {
- filters.maintenanceActuallyTimeReq = null
- filters.maintenancePlanTimeReq = null
- if(type === 1){
- if (value) {
- filters.maintenanceActuallyTimeReq = dayjs(value).format("YYYY-MM-DD");
- }
- }else{
- if (value) {
- filters.maintenancePlanTimeReq = dayjs(value).format("YYYY-MM-DD");
- }
+// 瀹氭椂浠诲姟绠$悊tab鐩稿叧鍙橀噺
+const scheduledFilters = reactive({
+ taskName: '',
+ status: '',
+})
+
+const scheduledDataList = ref([])
+const scheduledPagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+})
+const scheduledMultipleList = ref([])
+
+// 瀹氭椂浠诲姟绠$悊琛ㄦ牸鍒楅厤缃�
+const scheduledColumns = ref([
+ { prop: "taskName", label: "璁惧鍚嶇О"},
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "deviceModel",
+ },
+ {
+ prop: "frequencyType",
+ label: "棰戞",
+ minWidth: 150,
+ // PIMTable 浣跨敤鐨勬槸 formatData锛岃�屼笉鏄� Element-Plus 鐨� formatter
+ formatData: (cell) => ({
+ DAILY: "姣忔棩",
+ WEEKLY: "姣忓懆",
+ MONTHLY: "姣忔湀",
+ QUARTERLY: "瀛e害"
+ }[cell] || "")
+ },
+ {
+ prop: "frequencyDetail",
+ label: "寮�濮嬫棩鏈熶笌鏃堕棿",
+ minWidth: 150,
+ // 鍚屾牱鏀圭敤 formatData锛孭IMTable 鍐呴儴浼氭妸鍗曞厓鏍煎�间紶杩涙潵
+ formatData: (cell) => {
+ if (typeof cell !== 'string') return '';
+ let val = cell;
+ const replacements = {
+ MON: '鍛ㄤ竴',
+ TUE: '鍛ㄤ簩',
+ WED: '鍛ㄤ笁',
+ THU: '鍛ㄥ洓',
+ FRI: '鍛ㄤ簲',
+ SAT: '鍛ㄥ叚',
+ SUN: '鍛ㄦ棩'
+ };
+ // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
+ return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
+ }
+ },
+ { prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
+ { prop: "registrationDate", label: "鐧昏鏃ユ湡", minWidth: 100 },
+ {
+ fixed: "right",
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ align: "center",
+ width: "200px",
+ },
+])
+
+// 浠诲姟璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
+const columns = ref([
+ {
+ label: "璁惧鍚嶇О",
+ align: "center",
+ prop: "deviceName",
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ align: "center",
+ prop: "deviceModel",
+ },
+ {
+ label: "璁″垝淇濆吇鏃ユ湡",
+ align: "center",
+ prop: "maintenancePlanTime",
+ formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"),
+ },
+ {
+ label: "褰曞叆浜�",
+ align: "center",
+ prop: "createUserName",
+ },
+ // {
+ // label: "褰曞叆鏃ユ湡",
+ // align: "center",
+ // prop: "createTime",
+ // formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
+ // width: 200,
+ // },
+ {
+ label: "瀹為檯淇濆吇浜�",
+ align: "center",
+ prop: "maintenanceActuallyName",
+ },
+ {
+ label: "瀹為檯淇濆吇鏃ユ湡",
+ align: "center",
+ prop: "maintenanceActuallyTime",
+ formatData: (cell) =>
+ cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
+ },
+ {
+ label: "淇濆吇缁撴灉",
+ align: "center",
+ prop: "maintenanceResult",
+ dataType: "slot",
+ slot: "maintenanceResultRef",
+ },
+ {
+ label: "鐘舵��",
+ align: "center",
+ prop: "status",
+ dataType: "slot",
+ slot: "statusRef",
+ },
+ {
+ fixed: "right",
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ align: "center",
+ width: "300px",
+ },
+])
+
+// Tab鍒囨崲澶勭悊
+const handleTabChange = (tabName) => {
+ if (tabName === 'record') {
+ getTableData()
+ } else if (tabName === 'scheduled') {
+ getScheduledTableData()
}
- getTableData();
-};
+}
-// 鏂板淇濆吇
-const addMaintain = () => {
- const row = multipleList.value[0];
- maintainModalRef.value.open(row.id, row);
-};
-
-// 鏂板璁″垝
-const addPlan = () => {
- planModalRef.value.openModal();
-};
-
-// 缂栬緫璁″垝
-const editPlan = (id) => {
- planModalRef.value.openEdit(id);
-};
-
-const changePage = ({ page, limit }) => {
- pagination.currentPage = page;
- pagination.pageSize = limit;
- onCurrentChange(page);
-};
-
-// 鍗曡鍒犻櫎
-const delRepairByIds = async (ids) => {
- ElMessageBox.confirm("纭鍒犻櫎鎶ヤ慨鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
- confirmButtonText: "纭畾",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- }).then(async () => {
- const { code } = await delUpkeep(ids);
- if (code === 200) {
- ElMessage.success("鍒犻櫎鎴愬姛");
- getTableData();
+// 瀹氭椂浠诲姟绠$悊鐩稿叧鏂规硶
+const getScheduledTableData = async () => {
+ try {
+ const params = {
+ current: scheduledPagination.currentPage,
+ size: scheduledPagination.pageSize,
+ taskName: scheduledFilters.taskName || undefined,
+ status: scheduledFilters.status || undefined,
}
- });
-};
+ const { code, data } = await deviceMaintenanceTaskList(params)
+ if (code === 200) {
+ scheduledDataList.value = data?.records || []
+ scheduledPagination.total = data?.total || 0
+ }
+ } catch (error) {
+ ElMessage.error('鑾峰彇瀹氭椂浠诲姟鍒楄〃澶辫触')
+ }
+}
+
+const resetScheduledFilters = () => {
+ scheduledFilters.taskName = ''
+ scheduledFilters.status = ''
+ getScheduledTableData()
+}
+
+const handleScheduledSelectionChange = (selection) => {
+ scheduledMultipleList.value = selection
+}
+
+const changeScheduledPage = (page) => {
+ scheduledPagination.currentPage = page.page
+ scheduledPagination.pageSize = page.limit
+ getScheduledTableData()
+}
+
+const addScheduledTask = () => {
+ nextTick(() => {
+ formDiaRef.value?.openDialog('add');
+ });
+}
+
+const editScheduledTask = (row) => {
+ if (row) {
+ nextTick(() => {
+ formDiaRef.value?.openDialog('edit', row);
+ });
+ }
+}
+
+const delScheduledTaskByIds = async (ids) => {
+ try {
+ await ElMessageBox.confirm('纭畾鍒犻櫎閫変腑鐨勫畾鏃朵换鍔″悧锛�', '鎻愮ず', {
+ type: 'warning',
+ })
+ const payload = Array.isArray(ids) ? ids : [ids]
+ await deviceMaintenanceTaskDel(payload)
+ ElMessage.success('鍒犻櫎瀹氭椂浠诲姟鎴愬姛')
+ getScheduledTableData()
+ } catch (error) {
+ // 鐢ㄦ埛鍙栨秷鍒犻櫎
+ }
+}
+
+const handleScheduledOut = () => {
+ ElMessage.info('瀵煎嚭瀹氭椂浠诲姟鍔熻兘寰呭疄鐜�')
+}
+
+// 浠诲姟璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
+const getTableData = async () => {
+ try {
+ const params = {
+ current: pagination.value.currentPage,
+ size: pagination.value.pageSize,
+ deviceName: filters.deviceName || undefined,
+ maintenancePlanTime: filters.maintenancePlanTime ? dayjs(filters.maintenancePlanTime).format('YYYY-MM-DD') : undefined,
+ maintenanceActuallyTime: filters.maintenanceActuallyTime ? dayjs(filters.maintenanceActuallyTime).format('YYYY-MM-DD') : undefined,
+ maintenanceActuallyName: filters.maintenanceActuallyName || undefined,
+ }
+
+ const { code, data } = await getUpkeepPage(params)
+ if (code === 200) {
+ dataList.value = data.records
+ pagination.value.total = data.total
+ }
+ } catch (error) {
+ console.log(error);
+
+ }
+}
+
+const resetFilters = () => {
+ filters.deviceName = ''
+ filters.maintenancePlanTime = ''
+ filters.maintenanceActuallyTime = ''
+ filters.maintenanceActuallyName = ''
+ getTableData()
+}
+
+const handleSelectionChange = (selection) => {
+ multipleList.value = selection
+}
+
+// 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
+const hasFinishedStatus = computed(() => {
+ return multipleList.value.some(item => item.status === 1)
+})
+
+const changePage = (page) => {
+ pagination.value.currentPage = page.page
+ pagination.value.pageSize = page.limit
+ getTableData()
+}
+
+const addMaintain = (row) => {
+ maintainModalRef.value.open(row.id, row)
+}
+
+const addPlan = () => {
+ planModalRef.value.openModal()
+}
+
+const editPlan = (id) => {
+ planModalRef.value.openEdit(id)
+}
+
+const delRepairByIds = async (ids) => {
+ // 妫�鏌ユ槸鍚︽湁瀹岀粨鐘舵�佺殑璁板綍
+ const hasFinished = multipleList.value.some(item => item.status === 1)
+ if (hasFinished) {
+ ElMessage.warning('涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�')
+ return
+ }
+
+ try {
+ await ElMessageBox.confirm('纭鍒犻櫎淇濆吇鏁版嵁, 姝ゆ搷浣滀笉鍙��?', '璀﹀憡', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ })
+
+ const { code } = await delUpkeep(ids)
+ if (code === 200) {
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getTableData()
+ }
+ } catch (error) {
+ // 鐢ㄦ埛鍙栨秷鍒犻櫎
+ }
+}
+
+const handleOut = () => {
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�', '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ })
+ .then(() => {
+ proxy.download('/device/maintenance/export', {}, '璁惧淇濆吇.xlsx')
+ })
+ .catch(() => {
+ ElMessage.info('宸插彇娑�')
+ })
+}
+
+const handleDateChange = (date, type) => {
+ if (type === 1) {
+ filters.maintenanceActuallyTime = date ? dayjs(date).format('YYYY-MM-DD') : ''
+ } else {
+ filters.maintenancePlanTime = date ? dayjs(date).format('YYYY-MM-DD') : ''
+ }
+ getTableData()
+}
onMounted(() => {
- getTableData();
-});
+ // 鏍规嵁榛樿婵�娲荤殑 Tab 璋冪敤瀵瑰簲鐨勬煡璇㈡帴鍙�
+ if (activeTab.value === 'scheduled') {
+ getScheduledTableData()
+ } else {
+ getTableData()
+ }
+})
</script>
<style lang="scss" scoped>
@@ -292,3 +591,8 @@
margin-bottom: 10px;
}
</style>
+
+
+
+
+
diff --git a/src/views/example/DynamicTableExample.vue b/src/views/example/DynamicTableExample.vue
new file mode 100644
index 0000000..038cd43
--- /dev/null
+++ b/src/views/example/DynamicTableExample.vue
@@ -0,0 +1,354 @@
+<template>
+ <div class="app-container">
+ <div class="search-form">
+ <el-form :inline="true" :model="searchForm">
+ <el-form-item label="閮ㄩ棬">
+ <el-input
+ v-model="searchForm.department"
+ placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�"
+ clearable
+ style="width: 200px"
+ />
+ </el-form-item>
+ <el-form-item label="濮撳悕">
+ <el-input
+ v-model="searchForm.name"
+ placeholder="璇疯緭鍏ュ鍚�"
+ clearable
+ style="width: 200px"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ <el-button type="success" @click="handleAdd">鏂板</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <div class="table-container">
+ <DynamicTable
+ ref="dynamicTableRef"
+ :data="tableData"
+ :dict-types="dictTypes"
+ :loading="loading"
+ :show-selection="true"
+ :show-actions="true"
+ :show-pagination="true"
+ :pagination="pagination"
+ height="calc(100vh - 280px)"
+ @selection-change="handleSelectionChange"
+ @edit="handleEdit"
+ @delete="handleDelete"
+ @select-change="handleSelectChange"
+ @input-change="handleInputChange"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog
+ v-model="dialogVisible"
+ :title="dialogTitle"
+ width="600px"
+ append-to-body
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="100px"
+ >
+ <el-form-item label="閮ㄩ棬" prop="department">
+ <el-input v-model="form.department" placeholder="璇疯緭鍏ラ儴闂�" />
+ </el-form-item>
+ <el-form-item label="濮撳悕" prop="name">
+ <el-input v-model="form.name" placeholder="璇疯緭鍏ュ鍚�" />
+ </el-form-item>
+ <el-form-item label="宸ュ彿" prop="employeeId">
+ <el-input v-model="form.employeeId" placeholder="璇疯緭鍏ュ伐鍙�" />
+ </el-form-item>
+
+ <!-- 鍔ㄦ�佽〃鍗曢」锛氭牴鎹瓧鍏哥敓鎴� -->
+ <el-form-item
+ v-for="dictItem in dynamicFormItems"
+ :key="dictItem.value"
+ :label="dictItem.label"
+ :prop="dictItem.value"
+ >
+ <el-select
+ v-model="form[dictItem.value]"
+ placeholder="璇烽�夋嫨"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="option in dictItem.options"
+ :key="option.value"
+ :label="option.label"
+ :value="option.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import DynamicTable from '@/components/DynamicTable/index.vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const editIndex = ref(-1)
+const selectedRows = ref([])
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ department: '',
+ name: ''
+})
+
+// 琛ㄦ牸鏁版嵁
+const tableData = ref([
+ {
+ id: 1,
+ department: '鎶�鏈儴',
+ name: '寮犱笁',
+ employeeId: 'EMP001',
+ status: '1',
+ level: '2',
+ position: '1'
+ },
+ {
+ id: 2,
+ department: '浜轰簨閮�',
+ name: '鏉庡洓',
+ employeeId: 'EMP002',
+ status: '0',
+ level: '1',
+ position: '2'
+ },
+ {
+ id: 3,
+ department: '璐㈠姟閮�',
+ name: '鐜嬩簲',
+ employeeId: 'EMP003',
+ status: '1',
+ level: '3',
+ position: '1'
+ }
+])
+
+// 瀛楀吀绫诲瀷閰嶇疆
+const dictTypes = ref([
+ 'sys_normal_disable', // 鐘舵�佸瓧鍏�
+ 'sys_user_level', // 绾у埆瀛楀吀
+ 'sys_user_position' // 鑱屼綅瀛楀吀
+])
+
+// 鍒嗛〉閰嶇疆
+const pagination = reactive({
+ current: 1,
+ size: 10,
+ total: 0
+})
+
+// 琛ㄥ崟鏁版嵁
+const form = reactive({
+ department: '',
+ name: '',
+ employeeId: '',
+ status: '',
+ level: '',
+ position: ''
+})
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const rules = {
+ department: [
+ { required: true, message: '璇疯緭鍏ラ儴闂�', trigger: 'blur' }
+ ],
+ name: [
+ { required: true, message: '璇疯緭鍏ュ鍚�', trigger: 'blur' }
+ ],
+ employeeId: [
+ { required: true, message: '璇疯緭鍏ュ伐鍙�', trigger: 'blur' }
+ ]
+}
+
+// 鍔ㄦ�佽〃鍗曢」
+const dynamicFormItems = computed(() => {
+ // 杩欓噷鍙互鏍规嵁瀛楀吀鏁版嵁鍔ㄦ�佺敓鎴愯〃鍗曢」
+ return [
+ {
+ label: '鐘舵��',
+ value: 'status',
+ options: [
+ { label: '鍚敤', value: '1' },
+ { label: '绂佺敤', value: '0' }
+ ]
+ },
+ {
+ label: '绾у埆',
+ value: 'level',
+ options: [
+ { label: '鍒濈骇', value: '1' },
+ { label: '涓骇', value: '2' },
+ { label: '楂樼骇', value: '3' }
+ ]
+ },
+ {
+ label: '鑱屼綅',
+ value: 'position',
+ options: [
+ { label: '鍛樺伐', value: '1' },
+ { label: '涓荤', value: '2' },
+ { label: '缁忕悊', value: '3' }
+ ]
+ }
+ ]
+})
+
+// 缁勪欢寮曠敤
+const dynamicTableRef = ref(null)
+const formRef = ref(null)
+
+// 浜嬩欢澶勭悊鍑芥暟
+const handleSearch = () => {
+ // 瀹炵幇鎼滅储閫昏緫
+ console.log('鎼滅储鏉′欢:', searchForm)
+ ElMessage.success('鎼滅储鍔熻兘寰呭疄鐜�')
+}
+
+const handleReset = () => {
+ searchForm.department = ''
+ searchForm.name = ''
+}
+
+const handleAdd = () => {
+ dialogTitle.value = '鏂板鍛樺伐'
+ editIndex.value = -1
+ resetForm()
+ dialogVisible.value = true
+}
+
+const handleEdit = (row, index) => {
+ dialogTitle.value = '缂栬緫鍛樺伐'
+ editIndex.value = index
+ Object.assign(form, row)
+ dialogVisible.value = true
+}
+
+const handleDelete = async (row, index) => {
+ try {
+ await ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ type: 'warning'
+ })
+
+ tableData.value.splice(index, 1)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ } catch (error) {
+ // 鐢ㄦ埛鍙栨秷鍒犻櫎
+ }
+}
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection
+}
+
+const handleSelectChange = (row, prop, value) => {
+ console.log('閫夋嫨鍙樺寲:', row, prop, value)
+ // 鍙互鍦ㄨ繖閲屽鐞嗘暟鎹洿鏂伴�昏緫
+}
+
+const handleInputChange = (row, prop, value) => {
+ console.log('杈撳叆鍙樺寲:', row, prop, value)
+ // 鍙互鍦ㄨ繖閲屽鐞嗘暟鎹洿鏂伴�昏緫
+}
+
+const handleSizeChange = (size) => {
+ pagination.size = size
+ // 閲嶆柊鍔犺浇鏁版嵁
+}
+
+const handleCurrentChange = (current) => {
+ pagination.current = current
+ // 閲嶆柊鍔犺浇鏁版嵁
+}
+
+const handleSubmit = async () => {
+ try {
+ await formRef.value.validate()
+
+ if (editIndex.value === -1) {
+ // 鏂板
+ const newRow = {
+ id: Date.now(),
+ ...form
+ }
+ tableData.value.push(newRow)
+ ElMessage.success('鏂板鎴愬姛')
+ } else {
+ // 缂栬緫
+ Object.assign(tableData.value[editIndex.value], form)
+ ElMessage.success('缂栬緫鎴愬姛')
+ }
+
+ dialogVisible.value = false
+ } catch (error) {
+ console.error('琛ㄥ崟楠岃瘉澶辫触:', error)
+ }
+}
+
+const resetForm = () => {
+ Object.assign(form, {
+ department: '',
+ name: '',
+ employeeId: '',
+ status: '',
+ level: '',
+ position: ''
+ })
+ formRef.value?.resetFields()
+}
+
+// 缁勪欢鎸傝浇鏃跺垵濮嬪寲鏁版嵁
+onMounted(() => {
+ pagination.total = tableData.value.length
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.search-form {
+ margin-bottom: 20px;
+ padding: 20px;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+}
+
+.table-container {
+ background-color: #fff;
+ border-radius: 4px;
+ padding: 20px;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+</style>
diff --git a/src/views/example/SimpleExample.vue b/src/views/example/SimpleExample.vue
new file mode 100644
index 0000000..fb528eb
--- /dev/null
+++ b/src/views/example/SimpleExample.vue
@@ -0,0 +1,135 @@
+<template>
+ <div class="app-container">
+ <!-- 绠�鍗曠殑鎼滅储鍖哄煙 -->
+ <el-card class="search-card">
+ <el-form :inline="true">
+ <el-form-item label="閮ㄩ棬">
+ <el-input v-model="searchForm.department" placeholder="璇疯緭鍏ラ儴闂�" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鍔ㄦ�佽〃鏍� -->
+ <el-card class="table-card">
+ <template #header>
+ <div class="card-header">
+ <span>鍛樺伐淇℃伅琛�</span>
+ <el-button type="primary" size="small" @click="handleAdd">鏂板鍛樺伐</el-button>
+ </div>
+ </template>
+
+ <DynamicTable
+ :data="tableData"
+ :dict-types="dictTypes"
+ :loading="loading"
+ :show-selection="true"
+ :show-actions="true"
+ height="400px"
+ @selection-change="handleSelectionChange"
+ @edit="handleEdit"
+ @delete="handleDelete"
+ />
+ </el-card>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue'
+import { ElMessage } from 'element-plus'
+import DynamicTable from '@/components/DynamicTable/index.vue'
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ department: ''
+})
+
+// 琛ㄦ牸鏁版嵁
+const tableData = ref([
+ {
+ id: 1,
+ department: '鎶�鏈儴',
+ name: '寮犱笁',
+ employeeId: 'EMP001',
+ sys_normal_disable: '1', // 鐘舵��
+ sys_user_level: '2', // 绾у埆
+ sys_user_position: '1' // 鑱屼綅
+ },
+ {
+ id: 2,
+ department: '浜轰簨閮�',
+ name: '鏉庡洓',
+ employeeId: 'EMP002',
+ sys_normal_disable: '0', // 鐘舵��
+ sys_user_level: '1', // 绾у埆
+ sys_user_position: '2' // 鑱屼綅
+ }
+])
+
+// 瀛楀吀绫诲瀷
+const dictTypes = ref([
+ 'sys_normal_disable', // 鐘舵�侊細鍚敤/绂佺敤
+ 'sys_user_level', // 绾у埆锛氬垵绾�/涓骇/楂樼骇
+ 'sys_user_position' // 鑱屼綅锛氬憳宸�/涓荤/缁忕悊
+])
+
+// 鍔犺浇鐘舵��
+const loading = ref(false)
+
+// 浜嬩欢澶勭悊
+const handleSearch = () => {
+ loading.value = true
+ // 妯℃嫙鎼滅储
+ setTimeout(() => {
+ loading.value = false
+ ElMessage.success('鎼滅储瀹屾垚')
+ }, 1000)
+}
+
+const handleReset = () => {
+ searchForm.department = ''
+}
+
+const handleAdd = () => {
+ ElMessage.info('鏂板鍔熻兘寰呭疄鐜�')
+}
+
+const handleSelectionChange = (selection) => {
+ console.log('閫変腑鐨勮:', selection)
+}
+
+const handleEdit = (row, index) => {
+ ElMessage.info(`缂栬緫绗�${index + 1}琛屾暟鎹甡)
+}
+
+const handleDelete = (row, index) => {
+ ElMessage.warning(`鍒犻櫎绗�${index + 1}琛屾暟鎹甡)
+}
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.table-card {
+ margin-bottom: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+:deep(.el-form-item) {
+ margin-bottom: 0;
+}
+</style>
diff --git a/src/views/fileManagement/bookshelf/detail.vue b/src/views/fileManagement/bookshelf/detail.vue
new file mode 100644
index 0000000..5d7d3ac
--- /dev/null
+++ b/src/views/fileManagement/bookshelf/detail.vue
@@ -0,0 +1,110 @@
+<template>
+ <div class="detail-container">
+ <div class="header">
+ <el-button @click="handleBack" type="primary" size="small">杩斿洖</el-button>
+ <h2>鍥句功璇︽儏</h2>
+ </div>
+
+ <div class="content" v-loading="loading">
+ <el-card v-if="current">
+ <template #header>
+ <div class="card-header">
+ <span>鍩烘湰淇℃伅</span>
+ </div>
+ </template>
+
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鍥句功缂栧彿">{{ current.docNumber }}</el-descriptions-item>
+ <el-descriptions-item label="鍥句功鍚嶇О">{{ current.docName }}</el-descriptions-item>
+ <el-descriptions-item label="鍏ュ簱鏃堕棿">{{ current.createTime }}</el-descriptions-item>
+ <!-- <el-descriptions-item label="褰撳墠浣嶇疆">{{ current.currentLocation }}</el-descriptions-item> -->
+ <el-descriptions-item label="鐘舵��">{{ current.docStatus }}</el-descriptions-item>
+ </el-descriptions>
+
+ <!-- <div class="additional-info" v-if="current.description">
+ <h4>鍥句功绠�浠�</h4>
+ <p>{{ current.description }}</p>
+ </div> -->
+ </el-card>
+
+ <el-empty v-else description="鏆傛棤鏁版嵁" />
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+// 瀹氫箟props
+const props = defineProps({
+ current: {
+ type: Object,
+ required: true
+ }
+})
+
+// 瀹氫箟emits
+const emit = defineEmits(['hanldeBack'])
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+// const bookInfo = ref(null)
+
+// 鏂规硶
+const handleBack = () => {
+ emit('hanldeBack')
+}
+
+</script>
+
+<style scoped>
+.detail-container {
+ padding: 20px;
+ height: 100%;
+ background-color: #f5f5f5;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+ background-color: #fff;
+ padding: 15px 20px;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.header h2 {
+ margin: 0 0 0 20px;
+ color: #333;
+}
+
+.content {
+ background-color: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.card-header {
+ font-weight: bold;
+ color: #333;
+}
+
+.additional-info {
+ margin-top: 20px;
+ padding-top: 20px;
+ border-top: 1px solid #ebeef5;
+}
+
+.additional-info h4 {
+ margin: 0 0 10px 0;
+ color: #333;
+ font-size: 16px;
+}
+
+.additional-info p {
+ margin: 0;
+ color: #666;
+ line-height: 1.6;
+}
+</style>
diff --git a/src/views/fileManagement/bookshelf/index.vue b/src/views/fileManagement/bookshelf/index.vue
new file mode 100644
index 0000000..2689900
--- /dev/null
+++ b/src/views/fileManagement/bookshelf/index.vue
@@ -0,0 +1,688 @@
+<template>
+ <div class="sample">
+ <div class="main-content" v-if="!isDetail">
+ <div class="search">
+ <div class="search_thing">
+ <div class="search_label">浠撳簱鍚嶇О锛�</div>
+ <div class="search_input">
+ <el-select v-model="entity.warehouseId" placeholder="閫夋嫨浠撳簱" size="small" @change="warehouseChange">
+ <el-option v-for="item in warehouse" :key="item.id" :label="item.label" :value="item.id">
+ </el-option>
+ </el-select>
+ </div>
+ </div>
+ <div class="search_thing">
+ <div class="search_label">璐ф灦锛�</div>
+ <div class="search_input">
+ <el-select v-model="entity.shelfId" placeholder="閫夋嫨璐ф灦" size="small" @change="handleShelf">
+ <el-option v-for="item in shelf" :key="item.id" :label="item.label" :value="item.id">
+ </el-option>
+ </el-select>
+ </div>
+ </div>
+ <!-- <div class="search_thing">
+ <el-button size="small" @click="handleShelf(entity.shelfId,'')">閲嶇疆</el-button>
+ <el-button size="small" type="primary" @click="handleShelf(entity.shelfId)">鏌ヨ</el-button>
+ </div> -->
+ <div class="btns">
+ <el-button size="small" style="color:#3A7BFA" @click="keepVisible=true">缁存姢</el-button>
+ <el-button size="small" style="color:#3A7BFA" @click="warehouseVisible=true,isEdit=false">娣诲姞浠撳簱</el-button>
+ <el-button size="small" style="color:#3A7BFA" @click="shelvesVisible=true,isEdit=false"
+ :disabled="entity.warehouseId==null">娣诲姞璐ф灦</el-button>
+ </div>
+ </div>
+ <div class="table" v-loading="tableLoading">
+ <table class="tables" style="table-layout:fixed;" v-if="tableList.length>0">
+ <tbody>
+ <tr v-for="(item,index) in tableList" :key="index">
+ <td v-for="(m,i) in item" :key="i" class="content">
+ <h4 v-if="m.row!=undefined">{{ m.row }} - {{ m.col }}</h4>
+ <ul>
+ <el-tooltip
+ effect="dark"
+ placement="top"
+ v-for="(n,j) in m.documentationDtoList"
+ :key="j">
+ <template #content><span>{{ n.docName }}</span>
+ <span> [{{ n.docNumber }}]</span></template>
+ <li class="green"
+ @click="handelDetail(n)">
+ <i></i>
+ <span>{{ n.docName }}</span>
+ <span> [{{ n.docNumber }}] <span :style="{ color: getStatusColor(n.docStatus) }">锛坽{ n.docStatus }}锛�</span></span>
+ </li>
+ </el-tooltip>
+ </ul>
+ </td>
+ </tr>
+ <tr>
+ <td v-for="(item,index) in rowList" :key="index" style="background: ghostwhite;height: 20px;">{{ item }}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <span v-else style="color: rgb(144, 147, 153);display: inline-block;position: absolute;top: 60%;left: 50%;transform: translate(-50%,-50%);">鏆傛棤鏁版嵁</span>
+ </div>
+ </div>
+ <Detail v-else @hanldeBack="isDetail=false" :current="current" />
+
+ <!-- 搴撲綅缁存姢瀵硅瘽妗� -->
+ <el-dialog v-model="keepVisible" title="搴撲綅缁存姢" width="350px" :append-to-body="true">
+ <el-tree :data="warehouse" ref="tree" node-key="id"
+ highlight-current v-if="keepVisible"
+ empty-text="鏆傛棤鏁版嵁">
+ <template #default="{ node, data }">
+ <div class="custom-tree-node" style="width: 100%;">
+ <el-row style="width: 100%;display: flex;align-items: center;">
+ <el-col :span="14">
+ <span>
+ <el-icon v-if="node.level < 2" class="folder-icon">
+ <FolderOpened />
+ </el-icon>
+ <el-icon v-else class="file-icon">
+ <Document />
+ </el-icon>
+ {{ data.label }}
+ </span>
+ </el-col>
+ <el-col :span="10" v-if="node.level<3">
+ <el-button type="link" size="small" :icon="Edit" @click.stop="handleEdit(data,node.level)">
+ </el-button>
+ <el-button type="danger" size="small" :icon="Delete" @click.stop="handleDelete(data,node.level)">
+ </el-button>
+ </el-col>
+ </el-row>
+ </div>
+ </template>
+ </el-tree>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="keepVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="keepVisible = false" >纭� 瀹�</el-button>
+ </span>
+ </template>
+ </el-dialog>
+
+ <!-- 浠撳簱鏂板/淇敼瀵硅瘽妗� -->
+ <el-dialog v-model="warehouseVisible" :title="isEdit?'浠撳簱淇敼':'浠撳簱鏂板'" width="350px">
+ <el-row>
+ <el-col class="search_thing" :span="24">
+ <div class="search_label"><span class="required-span">* </span>浠撳簱鍚嶇О锛�</div>
+ <div class="search_input">
+ <el-input v-model="name" size="small" @keyup.enter="confirmWarehouse"></el-input>
+ </div>
+ </el-col>
+ </el-row>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="warehouseVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="confirmWarehouse" :loading="upLoadWarehouse">纭� 瀹�</el-button>
+ </span>
+ </template>
+ </el-dialog>
+
+ <!-- 璐ф灦鏂板/淇敼瀵硅瘽妗� -->
+ <el-dialog v-model="shelvesVisible" :title="isEdit?'璐ф灦淇敼':'璐ф灦鏂板'" width="350px">
+ <el-row>
+ <el-col class="search_thing" :span="24">
+ <div class="search_label"><span class="required-span">* </span>璐ф灦鍚嶇О锛�</div>
+ <div class="search_input">
+ <el-input v-model="shelves.name" size="small"></el-input>
+ </div>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col class="search_thing" :span="24">
+ <div class="search_label"><span class="required-span">* </span>璐ф灦灞傛暟锛�</div>
+ <div class="search_input">
+ <el-input v-model="shelves.row" size="small"></el-input>
+ </div>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col class="search_thing" :span="24">
+ <div class="search_label"><span class="required-span">* </span>璐ф灦鍒楁暟锛�</div>
+ <div class="search_input">
+ <el-input v-model="shelves.col" size="small"></el-input>
+ </div>
+ </el-col>
+ </el-row>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="shelvesVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="confirmShelves" :loading="upLoadShelves">纭� 瀹�</el-button>
+ </span>
+ </template>
+ </el-dialog>
+
+
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Edit, Delete, FolderOpened, Document } from '@element-plus/icons-vue'
+import { getWarehouseList, addWarehouse, updateWarehouse, deleteWarehouse, getWarehouseStructure, addShelf, updateShelf, deleteShelf } from '@/api/fileManagement/bookshelf'
+import Detail from './detail.vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const entity = reactive({
+ warehouseId: null,
+ shelfId: null
+})
+
+const warehouse = ref([])
+const shelf = ref([])
+const keepVisible = ref(false)
+const warehouseVisible = ref(false)
+const shelvesVisible = ref(false)
+const upLoadWarehouse = ref(false)
+const upLoadShelves = ref(false)
+const tableList = ref([])
+const rowList = ref([])
+const value = ref('')
+const name = ref('')
+const shelves = reactive({})
+const isEdit = ref(false)
+const isDetail = ref(false)
+const currentEdit = ref(null)
+const tableLoading = ref(false)
+const current = ref({})
+
+// 妯℃澘寮曠敤
+const organization = ref(null)
+
+// 鐩戝惉鍣�
+watch(isEdit, (newVal) => {
+ if (!newVal) {
+ Object.keys(shelves).forEach(key => delete shelves[key])
+ }
+})
+
+// 鏂规硶
+
+const selectList = async () => {
+ // 杩欓噷闇�瑕佹浛鎹负瀹為檯鐨凙PI璋冪敤
+ const res = await getWarehouseList()
+ warehouse.value = res.data
+
+ if (warehouse.value.length == 0) {
+ entity.warehouseId = ''
+ entity.shelfId = ''
+ tableList.value = []
+ }
+
+
+
+ if (!entity.warehouseId && warehouse.value.length > 0) {
+ entity.warehouseId = warehouse.value[0].id
+ warehouseChange(entity.warehouseId)
+ if (shelf.value.length > 0) {
+ entity.shelfId = shelf.value[0].id
+ handleShelf(entity.shelfId)
+ } else {
+ tableList.value = []
+ }
+ } else if (warehouse.value.length > 0) {
+ warehouseChange(entity.warehouseId)
+ if (shelf.value.length > 0) {
+ entity.shelfId = shelf.value[0].id
+ handleShelf(entity.shelfId)
+ } else {
+ tableList.value = []
+ }
+ }
+}
+
+const confirmWarehouse = () => {
+ if (!name.value) {
+ ElMessage.error('璇峰~鍐欎粨搴撳悕绉�')
+ return
+ }
+ upLoadWarehouse.value = true
+
+ if (currentEdit.value && currentEdit.value.id) {
+ // 淇敼浠撳簱
+ // 杩欓噷闇�瑕佹浛鎹负瀹為檯鐨凙PI璋冪敤
+ updateWarehouse({
+ id: currentEdit.value.id,
+ warehouseName: name.value
+ }).then(res => {
+ upLoadWarehouse.value = false
+ warehouseVisible.value = false
+ currentEdit.value = null
+ ElMessage.success('淇敼鎴愬姛')
+ selectList()
+ name.value = ''
+ warehouseChange(entity.warehouseId)
+ })
+
+ } else {
+ // 鏂板浠撳簱
+ // 杩欓噷闇�瑕佹浛鎹负瀹為檯鐨凙PI璋冪敤
+ addWarehouse({
+ warehouseName: name.value
+ }).then(res => {
+ upLoadWarehouse.value = false
+ warehouseVisible.value = false
+ ElMessage.success('娣诲姞鎴愬姛')
+ selectList()
+ name.value = ''
+ warehouseChange(entity.warehouseId)
+ })
+ }
+}
+
+const confirmShelves = () => {
+ if (!shelves.name) {
+ ElMessage.error('璇峰~鍐欒揣鏋跺悕绉�')
+ return
+ }
+ if (!shelves.row) {
+ ElMessage.error('璇峰~鍐欒揣鏋跺眰鏁�')
+ return
+ }
+ if (!shelves.col) {
+ ElMessage.error('璇峰~鍐欒揣鏋跺垪鏁�')
+ return
+ }
+ upLoadShelves.value = true
+
+ if (currentEdit.value && currentEdit.value.id) {
+ // 淇敼
+ updateShelf({
+ id: currentEdit.value.id,
+ name: shelves.name,
+ row: Number(shelves.row),
+ col: Number(shelves.col),
+ warehouseId: entity.warehouseId
+ }).then(res => {
+ upLoadShelves.value = false
+ shelvesVisible.value = false
+ ElMessage.success('淇敼鎴愬姛')
+ selectList()
+ currentEdit.value = {}
+ }).catch(err => {
+ upLoadShelves.value = false
+ shelvesVisible.value = false
+ ElMessage.error('淇敼澶辫触')
+ })
+
+ } else {
+ // 鏂板
+ // 杩欓噷闇�瑕佹浛鎹负瀹為檯鐨凙PI璋冪敤
+ addShelf({
+ name: shelves.name,
+ row: Number(shelves.row),
+ col: Number(shelves.col),
+ warehouseId: entity.warehouseId
+ }).then(res => {
+ upLoadShelves.value = false
+ shelvesVisible.value = false
+ ElMessage.success('娣诲姞鎴愬姛')
+ selectList()
+ Object.keys(shelves).forEach(key => delete shelves[key])
+ }).catch(err => {
+ upLoadShelves.value = false
+ shelvesVisible.value = false
+ ElMessage.error('娣诲姞澶辫触')
+ })
+ }
+ warehouseChange(entity.warehouseId)
+}
+
+
+
+const handleDelete = (row, level) => {
+ ElMessageBox.confirm('鏄惁鍒犻櫎褰撳墠鏁版嵁?', "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }).then(() => {
+ if (level == 1) {
+ // 鍒犻櫎浠撳簱
+ deleteWarehouse([row.id]).then(res => {
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ selectList()
+ })
+ } else {
+ // 鍒犻櫎璐ф灦
+ deleteShelf({
+ id: row.id
+ }).then(res => {
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ selectList()
+ })
+ }
+ warehouseChange(entity.warehouseId)
+ }).catch(() => {})
+}
+
+const handleEdit = (data, level) => {
+ isEdit.value = true
+ if (level == 1) {
+ warehouseVisible.value = true
+ currentEdit.value = data
+ name.value = data.label
+ } else {
+ shelvesVisible.value = true
+ currentEdit.value = data
+ Object.assign(shelves, {
+ name: data.label,
+ row: data.row,
+ col: data.col,
+ warehouseId: data.warehouseId
+ })
+ }
+}
+
+const handelDetail = (row) => {
+ current.value = row
+ isDetail.value = true
+}
+
+// 鏍规嵁鏂囨。鐘舵�佽繑鍥炲搴旂殑棰滆壊
+const getStatusColor = (status) => {
+ if (status === '姝e父') {
+ return '#34BD66' // 缁胯壊
+ } else if (status === '鍊熷嚭') {
+ return '#F56C6C' // 绾㈣壊
+ }
+ return '#606266' // 榛樿棰滆壊
+}
+
+const warehouseChange = (val) => {
+tableList.value = []
+let map = warehouse.value.find(a => {
+ return a && a.id === val ? a : null
+})
+if (map && map.children) {
+ shelf.value = map.children
+ entity.shelfId = ''
+} else {
+ shelf.value = []
+}
+currentEdit.value = null
+}
+
+const handleShelf = async(e) => {
+ if (e) {
+ tableLoading.value = true
+ let data = []
+ const res = await getWarehouseStructure({warehouseGoodsShelvesId:e})
+ if(res.code == 200){
+ data = res.data.map(m=>{
+ m.books = m.documentationDtoList|[]
+ return m
+ })
+ }else{
+ ElMessage.error(res.message)
+ }
+ setTimeout(() => {
+ tableLoading.value = false
+ let set = new Set()
+ tableList.value = []
+ let arr = []
+
+ if (data && data.length > 0) {
+ data.forEach(m => {
+ if (m && m.row && m.col) {
+ set.add(m.col)
+ if (arr.length > 0) {
+ if (arr.find(n => n.row == m.row)) {
+ arr.push(m)
+ } else {
+ tableList.value.push(arr)
+ arr = []
+ arr.push(m)
+ }
+ } else {
+ arr.push(m)
+ }
+ }
+ })
+
+ if (arr.length > 0) {
+ tableList.value.push(arr)
+ }
+ }
+
+ rowList.value = []
+ for (let i = 0; i < set.size; i++) {
+ rowList.value.push(`${i + 1} 鍒梎)
+ }
+ console.log(6666, tableList.value,rowList.value,data)
+ }, 1000)
+ }
+}
+
+
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ selectList()
+})
+</script>
+
+<style scoped>
+ .main-content {
+ width: 100%;
+ height: 100%;
+ padding: 20px;
+ box-sizing: border-box;
+ }
+
+ .title {
+ height: 20px;
+ line-height: 20px;
+ margin-bottom: 20px;
+ }
+
+ .search {
+ background-color: #fff;
+ height: 80px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ margin-bottom: 20px;
+ }
+
+ .search_thing {
+ display: flex;
+ align-items: center;
+ height: 50px;
+ margin-right: 20px;
+ }
+
+ .search_label {
+ width: 90px;
+ font-size: 14px;
+ text-align: right;
+ color: #606266;
+ font-weight: 500;
+ margin-right: 10px;
+ }
+
+ .search_input {
+ width: 200px;
+ }
+
+ .table {
+ background-color: #fff;
+ width: 100%;
+ height: calc(100% - 100px);
+ padding: 20px;
+ overflow-y: auto;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ }
+
+ .el-form-item {
+ margin-bottom: 16px;
+ }
+
+ .btns {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+
+ .tables {
+ width: 100%;
+ height: 100%;
+ border-collapse: collapse;
+ border: 1px solid #e4e7ed;
+ }
+
+ .tables th {
+ font-size: 14px;
+ border: 1px solid #e4e7ed;
+ background-color: #fafafa;
+ padding: 8px;
+ font-weight: 500;
+ }
+
+ .tables td {
+ font-size: 12px;
+ text-align: center;
+ vertical-align: top;
+ border: 1px solid #e4e7ed;
+ padding: 8px;
+ box-sizing: border-box;
+ height: 120px;
+ background-color: #fff;
+ }
+
+ .tables ul {
+ list-style-type: none;
+ }
+
+ .tables ul li {
+ border-radius: 3px;
+ padding: 4px 10px;
+ box-sizing: border-box;
+ margin-bottom: 5px;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ color: #333333;
+ cursor: pointer;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .tables h4 {
+ color: #999999;
+ font-size: 14px;
+ font-weight: 400;
+ padding: 6px 0;
+ }
+
+ .tables i {
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ margin-right: 6px;
+ }
+
+ li:hover {
+ background: rgba(58, 123, 250, 0.18);
+ }
+
+ li:hover i {
+ background: #3A7BFA;
+ }
+
+ li:hover .num {
+ color: #3A7BFA;
+ }
+
+ .green {
+ background: #E0F6EA;
+ }
+
+ .green i {
+ background: #34BD66;
+ }
+
+ .green .num {
+ color: #34BD66;
+ }
+
+ .el-dialog {
+ position: relative;
+ }
+
+ .shaoma {
+ display: flex;
+ align-items: center;
+ font-size: 14px;
+ color: #3A7BFA;
+ position: absolute;
+ top: 23px;
+ right: 54px;
+ cursor: pointer;
+ }
+
+ .folder-icon {
+ color: #409eff;
+ font-size: 16px;
+ margin-right: 6px;
+ }
+
+ .file-icon {
+ color: #67c23a;
+ font-size: 16px;
+ margin-right: 6px;
+ }
+
+ .node_i {
+ color: orange;
+ font-size: 18px;
+ }
+
+ .custom-tree-node .el-button {
+ opacity: 0;
+ }
+
+ .custom-tree-node:hover .el-button {
+ opacity: 1;
+ }
+
+ :deep(.el-loading-mask) {
+ z-index: 10;
+ }
+
+ .required-span {
+ color: #f56c6c;
+ }
+
+ .table-row {
+ border-bottom: 1px solid #e4e7ed;
+ }
+
+ .table-row:last-child {
+ border-bottom: none;
+ }
+
+ .column-header {
+ background-color: #fafafa !important;
+ font-weight: 500;
+ color: #606266;
+ }
+
+ .content {
+ transition: background-color 0.2s ease;
+ }
+
+ .content:hover {
+ background-color: #f5f7fa;
+ }
+</style>
diff --git a/src/views/fileManagement/borrow/index.vue b/src/views/fileManagement/borrow/index.vue
new file mode 100644
index 0000000..705a0f8
--- /dev/null
+++ b/src/views/fileManagement/borrow/index.vue
@@ -0,0 +1,647 @@
+<template>
+ <div class="app-container borrow-view">
+ <!-- 鏌ヨ鍖哄煙 -->
+ <div class="search-container">
+ <el-form :model="searchForm" :inline="true" class="search-form">
+ <el-form-item label="鍊熼槄鐘舵�侊細">
+ <el-select v-model="searchForm.borrowStatus" placeholder="璇烽�夋嫨鍊熼槄鐘舵��" clearable style="width: 150px">
+ <el-option label="鍊熼槄" value="鍊熼槄" />
+ <el-option label="褰掕繕" value="褰掕繕" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍊熼槄浜猴細">
+ <el-input
+ v-model="searchForm.borrower"
+ placeholder="璇疯緭鍏ュ�熼槄浜�"
+ clearable
+ style="width: 200px"
+ />
+ </el-form-item>
+ <el-form-item label="鍊熼槄鏃ユ湡鑼冨洿锛�">
+ <el-date-picker
+ v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 300px"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">
+ <el-icon><Search /></el-icon>
+ 鏌ヨ
+ </el-button>
+ <el-button @click="handleReset">
+ <el-icon><Refresh /></el-icon>
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ <el-form-item style="margin-left: auto;">
+ <el-button type="primary" @click="openBorrowDia('add')">
+ <el-icon><Plus /></el-icon>
+ 鏂板鍊熼槄
+ </el-button>
+ <el-button @click="handleOut">
+ 瀵煎嚭
+ </el-button>
+ <el-button
+ type="danger"
+ @click="handleBatchDelete"
+ :disabled="selectedRows.length === 0"
+ >
+ <el-icon><Delete /></el-icon>
+ 鎵归噺鍒犻櫎 ({{ selectedRows.length }})
+ </el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <!-- 琛ㄦ牸鍖哄煙 -->
+ <div class="table-container">
+ <PIMTable
+ :table-data="borrowList"
+ :column="tableColumns"
+ :is-selection="true"
+ :border="true"
+ :table-loading="tableLoading"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ layout: 'total, sizes, prev, pager, next, jumper'
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="handlePagination"
+ />
+ </div>
+
+ <!-- 鍊熼槄鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog
+ v-model="borrowDia"
+ :title="borrowOperationType === 'add' ? '鏂板鍊熼槄' : '缂栬緫鍊熼槄'"
+ width="800px"
+ @close="closeBorrowDia"
+ @keydown.enter.prevent
+ >
+ <el-form
+ :model="borrowForm"
+ label-width="140px"
+ :rules="borrowRules"
+ ref="borrowFormRef"
+ >
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍊熼槄浜猴細" prop="borrower">
+ <el-input v-model="borrowForm.borrower" placeholder="璇疯緭鍏ュ�熼槄浜�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍊熼槄涔︾睄锛�" prop="documentationId">
+ <!-- <el-select v-model="borrowForm.documentationId" placeholder="璇烽�夋嫨鍊熼槄涔︾睄" style="width: 100%" @change="handleScanContent">
+ <el-option
+ v-for="item in documentList"
+ :key="item.id"
+ :label="item.docName || item.name"
+ :value="item.id"
+ />
+ </el-select> -->
+ <div style="display: flex; gap: 10px;">
+ <el-select v-model="borrowForm.documentationId" placeholder="璇烽�夋嫨鍊熼槄涔︾睄" style="flex: 1;width: 100px;" @change="handleSelectChange">
+ <el-option
+ v-for="item in documentList"
+ :key="item.id"
+ :label="item.docName || item.name"
+ :value="item.id"
+ />
+ </el-select>
+ <el-input
+ v-model="scanContent"
+ placeholder="鎵爜杈撳叆"
+ style="width: 100px;"
+ @input="handleScanContent"
+ clearable
+ />
+ </div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍊熼槄鏃ユ湡锛�" prop="borrowDate">
+ <el-date-picker
+ v-model="borrowForm.borrowDate"
+ type="date"
+ placeholder="閫夋嫨鍊熼槄鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="搴斿綊杩樻棩鏈燂細" prop="dueReturnDate">
+ <el-date-picker
+ v-model="borrowForm.dueReturnDate"
+ type="date"
+ placeholder="閫夋嫨搴斿綊杩樻棩鏈�"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="鍊熼槄鐩殑锛�" prop="borrowPurpose">
+ <el-input v-model="borrowForm.borrowPurpose" placeholder="璇疯緭鍏ュ�熼槄鐩殑" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞锛�" prop="remark">
+ <el-input
+ v-model="borrowForm.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitBorrowForm">纭</el-button>
+ <el-button @click="closeBorrowDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import { Search, Refresh, Plus, Delete } from '@element-plus/icons-vue';
+import PIMTable from '@/components/PIMTable/PIMTable.vue';
+import { getBorrowList, addBorrow, updateBorrow, deleteBorrow, getDocumentList } from '@/api/fileManagement/borrow';
+
+const { proxy } = getCurrentInstance();
+
+// 鍝嶅簲寮忔暟鎹�
+const borrowDia = ref(false);
+const borrowOperationType = ref("");
+const tableLoading = ref(false);
+const borrowList = ref([]);
+const selectedRows = ref([]);
+const documentList = ref([]); // 鏂囨。鍒楄〃锛岀敤浜庡�熼槄涔︾睄閫夋嫨
+const scanContent = ref() // 鎵爜鍐呭
+// 鍒嗛〉鐩稿叧
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+// 鏌ヨ琛ㄥ崟
+const searchForm = reactive({
+ documentationId: "",
+ borrowStatus: "",
+ borrower: "",
+ returnerId: "",
+ dateRange: []
+});
+
+// 鍊熼槄琛ㄥ崟
+const borrowForm = reactive({
+ id: "",
+ documentationId: "",
+ borrower: "",
+ returnerId: "",
+ borrowPurpose: "",
+ borrowDate: "",
+ dueReturnDate: "",
+ returnDate: "",
+ borrowStatus: "",
+ remark: ""
+});
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const borrowRules = reactive({
+
+ borrower: [{ required: true, message: "璇疯緭鍏ュ�熼槄浜�", trigger: "blur" }],
+ borrowPurpose: [{ required: true, message: "璇疯緭鍏ュ�熼槄鐩殑", trigger: "blur" }],
+ borrowDate: [{ required: true, message: "璇烽�夋嫨鍊熼槄鏃ユ湡", trigger: "change" }],
+ dueReturnDate: [{ required: true, message: "璇烽�夋嫨搴斿綊杩樻棩鏈�", trigger: "change" }],
+ borrowStatus: [{ required: true, message: "璇烽�夋嫨鍊熼槄鐘舵��", trigger: "change" }]
+});
+
+// 琛ㄦ牸鍒楅厤缃�
+const tableColumns = ref([
+ {
+ label: '鏂囨。鍚嶇О',
+ prop: 'docName',
+ width: '200',
+ },
+ { label: '鍊熼槄浜�', prop: 'borrower' },
+ { label: '鍊熼槄鐩殑', prop: 'borrowPurpose' },
+ { label: '鍊熼槄鏃ユ湡', prop: 'borrowDate' },
+ { label: '搴斿綊杩樻棩鏈�', prop: 'dueReturnDate' },
+ {
+ label: '鍊熼槄鐘舵��',
+ prop: 'borrowStatus',
+ width: '100',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ return params;
+ },
+ formatType: (params) => {
+ if (params === '褰掕繕') return 'success';
+ if (params === '鍊熼槄') return 'warning';
+ return 'info';
+ }
+ },
+ { label: '澶囨敞', prop: 'remark', width: '150' },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: 'right',
+ width: '150',
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ openBorrowDia('edit', row)
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "text",
+ clickFun: (row) => {
+ handleDelete(row)
+ },
+ },
+ ],
+ }
+]);
+
+// 鍒濆鍖栨暟鎹�
+const initData = async () => {
+ await Promise.all([
+ loadDocumentList(),
+ loadBorrowList()
+ ]);
+};
+
+// 鍔犺浇鏂囨。鍒楄〃
+const loadDocumentList = async () => {
+ try {
+ const res = await getDocumentList();
+ if (res.code === 200) {
+ documentList.value = res.data || [];
+ console.log("shuju",documentList.value)
+ } else {
+ ElMessage.error(res.msg || "鑾峰彇鏂囨。鍒楄〃澶辫触");
+ documentList.value = [];
+ }
+ } catch (error) {
+ ElMessage.error("鑾峰彇鏂囨。鍒楄〃澶辫触锛岃閲嶈瘯");
+ documentList.value = [];
+ }
+};
+
+// 鍔犺浇鍊熼槄鍒楄〃
+const loadBorrowList = async () => {
+ try {
+ tableLoading.value = true;
+
+ // 鏋勫缓鏌ヨ鍙傛暟
+ const query = {
+ page: pagination.currentPage,
+ size: pagination.pageSize,
+ documentationId: searchForm.documentationId || undefined,
+ borrowStatus: searchForm.borrowStatus || undefined,
+ borrower: searchForm.borrower || undefined,
+ returnerId: searchForm.returnerId || undefined,
+ entryDateStart: searchForm.dateRange && searchForm.dateRange.length > 0 ? searchForm.dateRange[0] : undefined,
+ entryDateEnd: searchForm.dateRange && searchForm.dateRange.length > 1 ? searchForm.dateRange[1] : undefined
+ };
+
+ // 绉婚櫎undefined鐨勫弬鏁�
+ Object.keys(query).forEach(key => {
+ if (query[key] === undefined) {
+ delete query[key];
+ }
+ });
+
+ const res = await getBorrowList(query);
+ if (res.code === 200) {
+ borrowList.value = res.data.records || [];
+ pagination.total = res.data.total || 0;
+ } else {
+ ElMessage.error(res.msg || "鑾峰彇鍊熼槄鍒楄〃澶辫触");
+ borrowList.value = [];
+ pagination.total = 0;
+ }
+
+ // 閲嶇疆閫夋嫨鐘舵��
+ selectedRows.value = [];
+ } catch (error) {
+ ElMessage.error("鑾峰彇鍊熼槄鍒楄〃澶辫触锛岃閲嶈瘯");
+ borrowList.value = [];
+ pagination.total = 0;
+ } finally {
+ tableLoading.value = false;
+ }
+};
+
+// 鏌ヨ
+const handleSearch = () => {
+ pagination.currentPage = 1;
+ loadBorrowList();
+};
+
+// 閲嶇疆鏌ヨ
+const handleReset = () => {
+ searchForm.documentationId = "";
+ searchForm.borrowStatus = "";
+ searchForm.borrower = "";
+ searchForm.returnerId = "";
+ searchForm.dateRange = [];
+ pagination.currentPage = 1;
+ loadBorrowList();
+ ElMessage.success("鏌ヨ鏉′欢宸查噸缃�");
+};
+
+// 澶勭悊涓嬫媺閫夋嫨鍙樺寲
+const handleSelectChange = (value) => {
+ // 褰撲笅鎷夋閫夋嫨鏃讹紝娓呯┖鎵爜杈撳叆妗�
+ scanContent.value = '';
+};
+
+// 澶勭悊鎵爜鍐呭
+const handleScanContent = async (value) => {
+ if (!value) return;
+ try {
+ // 鏌ユ壘鎵弿鍐呭瀵瑰簲鐨勬枃妗�
+ const matchedDoc = documentList.value.find(item =>
+ item.id == value
+ );
+ console.log("matchedDoc", matchedDoc);
+
+
+ if (matchedDoc) {
+
+ // 鎵惧埌鍖归厤鐨勬枃妗o紝璁剧疆琛ㄥ崟鍊�
+ borrowForm.documentationId = matchedDoc.id;
+ ElMessage.success(`宸查�夋嫨: ${matchedDoc.docName || matchedDoc.name}`);
+ } else {
+ // 鏈壘鍒板尮閰嶇殑鏂囨。锛屾彁绀虹敤鎴�
+ ElMessage.warning('鏈壘鍒板搴旂殑涔︾睄锛岃妫�鏌ユ壂鐮佸唴瀹规垨鎵嬪姩閫夋嫨');
+ }
+ } catch (error) {
+ ElMessage.error('鎵爜澶勭悊澶辫触锛岃閲嶈瘯');
+ console.error('鎵爜澶勭悊閿欒:', error);
+ }
+}
+// 鎵撳紑鍊熼槄寮规
+const openBorrowDia = async (type, data) => {
+ // 鍏堝埛鏂版枃妗e垪琛�
+ await loadDocumentList();
+
+ borrowOperationType.value = type;
+ borrowDia.value = true;
+ scanContent.value = ''; // 娓呯┖鎵爜鍐呭
+
+ if (type === "edit") {
+ // 缂栬緫妯″紡锛屽姞杞界幇鏈夋暟鎹�
+ Object.assign(borrowForm, data);
+ } else {
+ // 鏂板妯″紡锛屾竻绌鸿〃鍗�
+ Object.keys(borrowForm).forEach(key => {
+ borrowForm[key] = "";
+ });
+ // 璁剧疆榛樿鐘舵��
+ borrowForm.borrowStatus = "鍊熼槄";
+ // 璁剧疆褰撳墠鏃ユ湡涓哄�熼槄鏃ユ湡
+ borrowForm.borrowDate = new Date().toISOString().split('T')[0];
+ }
+};
+
+// 鍏抽棴鍊熼槄寮规
+const closeBorrowDia = () => {
+ proxy.$refs.borrowFormRef.resetFields();
+ borrowDia.value = false;
+ scanContent.value = ''; // 娓呯┖鎵爜鍐呭
+};
+
+// 鎻愪氦鍊熼槄琛ㄥ崟
+const submitBorrowForm = () => {
+ proxy.$refs.borrowFormRef.validate(async (valid) => {
+ if (valid) {
+ try {
+ if (borrowOperationType.value === "edit") {
+ // 缂栬緫妯″紡锛屾洿鏂扮幇鏈夋暟鎹�
+ const res = await updateBorrow({
+ borrower:borrowForm.borrower,
+ id: borrowForm.id,
+ borrowPurpose: borrowForm.borrowPurpose,
+ borrowDate: borrowForm.borrowDate,
+ dueReturnDate: borrowForm.dueReturnDate,
+ returnDate: borrowForm.returnDate,
+ remark: borrowForm.remark
+ });
+
+ if (res.code === 200) {
+ ElMessage.success("缂栬緫鎴愬姛");
+ await loadBorrowList();
+ closeBorrowDia();
+ } else {
+ ElMessage.error(res.msg || "缂栬緫澶辫触");
+ }
+ } else {
+ // 鏂板妯″紡锛屾坊鍔犳柊鏁版嵁
+ const res = await addBorrow({
+ documentationId: borrowForm.documentationId,
+ borrower: borrowForm.borrower,
+ returnerId: borrowForm.returnerId,
+ borrowPurpose: borrowForm.borrowPurpose,
+ borrowDate: borrowForm.borrowDate,
+ dueReturnDate: borrowForm.dueReturnDate,
+ returnDate: borrowForm.returnDate,
+ borrowStatus: borrowForm.borrowStatus,
+ remark: borrowForm.remark
+ });
+
+ if (res.code === 200) {
+ ElMessage.success("鏂板鎴愬姛");
+ await loadBorrowList();
+ closeBorrowDia();
+ } else {
+ ElMessage.error(res.msg || "鏂板澶辫触");
+ }
+ }
+ } catch (error) {
+ ElMessage.error("鎿嶄綔澶辫触锛岃閲嶈瘯");
+ }
+ }
+ });
+};
+
+// 鍒犻櫎鍊熼槄璁板綍
+const handleDelete = (row) => {
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄よ繖鏉″�熼槄璁板綍鍚楋紵`,
+ "鍒犻櫎鎻愮ず",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ ).then(async () => {
+ try {
+ const res = await deleteBorrow([row.id]);
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ await loadBorrowList();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error("鍒犻櫎澶辫触锛岃閲嶈瘯");
+ }
+ }).catch(() => {
+ ElMessage.info("宸插彇娑堝垹闄�");
+ });
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑璁板綍");
+ return;
+ }
+
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉″�熼槄璁板綍鍚楋紵`,
+ "鎵归噺鍒犻櫎鎻愮ず",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ ).then(async () => {
+ try {
+ const selectedIds = selectedRows.value.map(row => row.id);
+ const res = await deleteBorrow(selectedIds);
+ if (res.code === 200) {
+ ElMessage.success("鎵归噺鍒犻櫎鎴愬姛");
+ await loadBorrowList();
+ } else {
+ ElMessage.error(res.msg || "鎵归噺鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error("鎵归噺鍒犻櫎澶辫触锛岃閲嶈瘯");
+ }
+ }).catch(() => {
+ ElMessage.info("宸插彇娑堝垹闄�");
+ });
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/documentationBorrowManagement/export", {}, "鍊熼槄鐧昏.xlsx");
+ })
+ .catch(() => {
+ ElMessage.info("宸插彇娑�");
+ });
+};
+
+// 閫夋嫨鍙樺寲浜嬩欢
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 澶勭悊鍒嗛〉鍙樺寲
+const handlePagination = (current, size) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ loadBorrowList();
+};
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ initData();
+});
+</script>
+
+<style scoped>
+.borrow-view {
+ padding: 20px;
+}
+
+.search-container {
+ background: #ffffff;
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.search-form {
+ margin: 0;
+}
+
+.table-container {
+ background: #ffffff;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.empty-data {
+ text-align: center;
+ color: #909399;
+ padding: 40px;
+ font-size: 14px;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+:deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #303133;
+}
+
+:deep(.el-input__wrapper) {
+ box-shadow: 0 0 0 1px #dcdfe6 inset;
+}
+
+:deep(.el-input__wrapper:hover) {
+ box-shadow: 0 0 0 1px #c0c4cc inset;
+}
+
+:deep(.el-input__wrapper.is-focus) {
+ box-shadow: 0 0 0 1px #409eff inset;
+}
+</style>
diff --git a/src/views/fileManagement/document/attachmentManager.vue b/src/views/fileManagement/document/attachmentManager.vue
new file mode 100644
index 0000000..a4e1d43
--- /dev/null
+++ b/src/views/fileManagement/document/attachmentManager.vue
@@ -0,0 +1,426 @@
+<template>
+ <el-dialog v-model="dialogVisible" title="闄勪欢绠$悊" width="60%" :before-close="handleClose">
+ <div class="attachment-manager">
+ <!-- 涓婁紶鍖哄煙 -->
+ <div class="upload-section">
+ <el-upload
+ ref="uploadRef"
+ :action="uploadUrl"
+ :headers="uploadHeaders"
+ :before-upload="handleBeforeUpload"
+ :on-success="handleUploadSuccess"
+ :on-error="handleUploadError"
+ :on-remove="handleRemove"
+ :file-list="fileList"
+ multiple
+ :limit="10"
+ :show-file-list="false"
+ :data="{documentId: currentDocumentId}"
+ accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.xml,.jpg,.jpeg,.png,.gif,.bmp,.rar,.zip,.7z"
+ >
+ <el-button type="primary" :icon="Plus">涓婁紶闄勪欢</el-button>
+ <template #tip>
+ <div class="el-upload__tip">
+ 鏀寔鏍煎紡锛歞oc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
+ <br>鍗曚釜鏂囦欢澶у皬涓嶈秴杩�50MB
+ </div>
+ </template>
+ </el-upload>
+ </div>
+
+ <!-- 闄勪欢鍒楄〃 -->
+ <div class="attachment-list">
+ <el-table :data="fileList" border height="400px" v-loading="loading">
+ <el-table-column label="搴忓彿" type="index" width="60" align="center" />
+ <el-table-column label="闄勪欢鍚嶇О" prop="name" min-width="200" show-overflow-tooltip />
+ <el-table-column label="鏂囦欢澶у皬" prop="size" width="100" align="center">
+ <template #default="scope">
+ {{ formatFileSize(scope.row.size) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婁紶鏃堕棿" prop="uploadTime" width="160" align="center">
+ <template #default="scope">
+ {{ formatDate(scope.row.uploadTime) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" prop="status" width="80" align="center">
+ <template #default="scope">
+ <el-tag :type="scope.row.status === 'success' ? 'success' : 'danger'" size="small">
+ {{ scope.row.status === 'success' ? '鎴愬姛' : '澶辫触' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔" width="200" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="previewFile(scope.row)">
+ 棰勮
+ </el-button>
+ <el-button link type="primary" size="small" @click="downloadFile(scope.row)">
+ 涓嬭浇
+ </el-button>
+ <el-button link type="danger" size="small" @click="removeFile(scope.row)">
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+
+ <!-- 鏂囦欢棰勮缁勪欢 -->
+ <filePreview ref="filePreviewRef" />
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus } from '@element-plus/icons-vue'
+import { getToken } from "@/utils/auth"
+import { addDocumentationFile, getDocumentationFileList, deleteDocumentationFile } from '@/api/fileManagement/document'
+import filePreview from '@/components/filePreview/index.vue'
+
+const props = defineProps({
+ // documentId 閫氳繃 open 浜嬩欢浼犲叆锛屼笉闇�瑕佷綔涓� props
+})
+
+const emit = defineEmits(['update:attachments'])
+
+const dialogVisible = ref(false)
+const loading = ref(false)
+const fileList = ref([])
+const uploadRef = ref()
+const filePreviewRef = ref()
+const currentDocumentId = ref('') // 鍐呴儴绠$悊褰撳墠鏂囨。ID
+
+// 涓婁紶閰嶇疆
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload"
+const uploadHeaders = computed(() => ({
+ Authorization: "Bearer " + getToken()
+}))
+
+// 鎵撳紑寮规
+const open = (attachments = [], documentId = '') => {
+ dialogVisible.value = true
+ currentDocumentId.value = documentId // 璁剧疆褰撳墠鏂囨。ID
+ // 濡傛灉鏈夋枃妗D锛屽垯鍔犺浇闄勪欢鍒楄〃
+ if (documentId) {
+ loadAttachmentList(documentId)
+ } else {
+ fileList.value = attachments || []
+ // total.value = fileList.value.length // Removed total.value
+ }
+ // currentPage.value = 1 // Removed currentPage.value
+}
+
+// 鍔犺浇闄勪欢鍒楄〃
+const loadAttachmentList = async (documentId) => {
+ try {
+ loading.value = true
+ const params = {
+ page: 1, // Always load from page 1
+ size: 1000, // Load all for now
+ documentationId: documentId
+ }
+
+ const res = await getDocumentationFileList(params)
+ if (res.code === 200) {
+ const records = res.data
+
+ // 杞崲鏁版嵁鏍煎紡
+ fileList.value = records.map(item => ({
+ id: item.id,
+ name: item.name,
+ size: item.fileSize,
+ url: item.url,
+ uploadTime: item.createTime || item.uploadTime,
+ status: 'success',
+ uid: item.id
+ }))
+
+ // total.value = totalCount // Removed total.value
+ } else {
+ ElMessage.error(res.msg || '鑾峰彇闄勪欢鍒楄〃澶辫触')
+ fileList.value = []
+ // total.value = 0 // Removed total.value
+ }
+ } catch (error) {
+ console.error('鑾峰彇闄勪欢鍒楄〃澶辫触:', error)
+ ElMessage.error('鑾峰彇闄勪欢鍒楄〃澶辫触')
+ fileList.value = []
+ // total.value = 0 // Removed total.value
+ } finally {
+ loading.value = false
+ }
+}
+
+// 鍏抽棴寮规
+const handleClose = () => {
+ dialogVisible.value = false
+ emit('update:attachments', fileList.value)
+}
+
+// 鏂囦欢涓婁紶鍓嶆牎楠�
+const handleBeforeUpload = (file) => {
+ // 妫�鏌ユ枃浠跺ぇ灏忥紙50MB锛�
+ const isLt50M = file.size / 1024 / 1024 < 50
+ if (!isLt50M) {
+ ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃50MB!')
+ return false
+ }
+
+ // 妫�鏌ユ枃浠剁被鍨�
+ const allowedTypes = [
+ 'application/msword',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.ms-excel',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.ms-powerpoint',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'application/pdf',
+ 'text/plain',
+ 'text/xml',
+ 'image/jpeg',
+ 'image/png',
+ 'image/gif',
+ 'image/bmp',
+ 'application/x-rar-compressed',
+ 'application/zip',
+ 'application/x-7z-compressed'
+ ]
+
+ if (!allowedTypes.includes(file.type)) {
+ ElMessage.error('涓嶆敮鎸佺殑鏂囦欢绫诲瀷!')
+ return false
+ }
+
+ return true
+}
+
+// 鏂囦欢涓婁紶鎴愬姛
+const handleUploadSuccess = (response, file, fileList) => {
+ console.log('鏂囦欢涓婁紶鎴愬姛鍝嶅簲:', response);
+ console.log('鏂囦欢淇℃伅:', file);
+
+ if (response.code === 200) {
+ // 鏋勫缓闄勪欢鏁版嵁 - 纭繚姝g‘鑾峰彇URL
+ const attachmentData = {
+ name: file.name,
+ url: response.data.url || response.data.path || response.data.tempPath || file.url,
+ fileSize: file.size,
+ documentationId: currentDocumentId.value
+ };
+
+ console.log('鏋勫缓鐨勯檮浠舵暟鎹�:', attachmentData);
+
+ // 璋冪敤淇濆瓨闄勪欢鎺ュ彛
+ saveAttachment(attachmentData, file, fileList);
+ } else {
+ ElMessage.error(response.msg || '鏂囦欢涓婁紶澶辫触')
+ }
+}
+
+// 淇濆瓨闄勪欢淇℃伅
+const saveAttachment = async (attachmentData, file, fileList) => {
+ try {
+ console.log('寮�濮嬩繚瀛橀檮浠讹紝鏁版嵁:', attachmentData);
+
+ // 纭繚URL瀛楁瀛樺湪涓旀湁鏁�
+ if (!attachmentData.url) {
+ console.error('闄勪欢URL涓虹┖锛屾棤娉曚繚瀛�');
+ ElMessage.error('鏂囦欢URL鑾峰彇澶辫触锛屾棤娉曚繚瀛橀檮浠�');
+ return;
+ }
+
+ const res = await addDocumentationFile(attachmentData);
+ console.log('淇濆瓨闄勪欢鎺ュ彛鍝嶅簲:', res);
+
+ if (res.code === 200) {
+ const newFile = {
+ id: res.data.id || Date.now(),
+ name: attachmentData.name,
+ size: attachmentData.fileSize,
+ url: attachmentData.url,
+ uploadTime: new Date().toISOString(),
+ status: 'success',
+ uid: file.uid
+ }
+
+ console.log('鍒涘缓鐨勬柊鏂囦欢瀵硅薄:', newFile);
+ fileList.push(newFile)
+ ElMessage.success('鏂囦欢涓婁紶骞朵繚瀛樻垚鍔�')
+
+ // 淇濆瓨鎴愬姛鍚庡埛鏂伴檮浠跺垪琛�
+ if (currentDocumentId.value) {
+ await loadAttachmentList(currentDocumentId.value);
+ }
+ } else {
+ ElMessage.error(res.msg || '淇濆瓨闄勪欢淇℃伅澶辫触')
+ // 淇濆瓨澶辫触鏃剁Щ闄ゆ枃浠�
+ const index = fileList.findIndex(item => item.uid === file.uid)
+ if (index > -1) {
+ fileList.splice(index, 1)
+ }
+ }
+ } catch (error) {
+ console.error('淇濆瓨闄勪欢澶辫触:', error)
+ ElMessage.error('淇濆瓨闄勪欢淇℃伅澶辫触')
+ // 淇濆瓨澶辫触鏃剁Щ闄ゆ枃浠�
+ const index = fileList.findIndex(item => item.uid === file.uid)
+ if (index > -1) {
+ fileList.splice(index, 1)
+ }
+ }
+}
+
+// 鏂囦欢涓婁紶澶辫触
+const handleUploadError = (error, file, fileList) => {
+ console.error('鏂囦欢涓婁紶澶辫触:', error);
+ console.error('澶辫触鐨勬枃浠�:', file);
+ console.error('褰撳墠鏂囦欢鍒楄〃:', fileList);
+
+ ElMessage.error('鏂囦欢涓婁紶澶辫触锛岃妫�鏌ョ綉缁滆繛鎺ユ垨鏂囦欢鏍煎紡')
+}
+
+// 绉婚櫎鏂囦欢
+const handleRemove = (file, fileList) => {
+ const index = fileList.findIndex(item => item.uid === file.uid)
+ if (index > -1) {
+ fileList.splice(index, 1)
+ // total.value = fileList.length // Removed total.value
+ }
+}
+
+// 鍒犻櫎鏂囦欢
+const removeFile = (file) => {
+ ElMessageBox.confirm(`纭畾瑕佸垹闄ゆ枃浠� "${file.name}" 鍚楋紵`, '鍒犻櫎纭', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(async () => {
+ try {
+ // 璋冪敤鍒犻櫎鎺ュ彛
+ const res = await deleteDocumentationFile([file.id]);
+ if (res.code === 200) {
+ // 浠庢湰鍦板垪琛ㄤ腑绉婚櫎
+ const index = fileList.value.findIndex(item => item.id === file.id);
+ if (index > -1) {
+ fileList.value.splice(index, 1);
+ }
+ ElMessage.success('鍒犻櫎鎴愬姛');
+
+ // 濡傛灉鏈夋枃妗D锛屽埛鏂伴檮浠跺垪琛�
+ if (currentDocumentId.value) {
+ await loadAttachmentList(currentDocumentId.value);
+ }
+ } else {
+ ElMessage.error(res.msg || '鍒犻櫎澶辫触');
+ }
+ } catch (error) {
+ console.error('鍒犻櫎闄勪欢澶辫触:', error);
+ ElMessage.error('鍒犻櫎闄勪欢澶辫触');
+ }
+ }).catch(() => {
+ // 鍙栨秷鍒犻櫎
+ })
+}
+
+// 棰勮鏂囦欢
+const previewFile = (file) => {
+ if (file.url) {
+ filePreviewRef.value.open(file.url)
+ } else {
+ ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曢瑙�')
+ }
+}
+
+// 涓嬭浇鏂囦欢
+const downloadFile = (file) => {
+ if (file.url) {
+ // 鍒涘缓涓嬭浇閾炬帴
+ const link = document.createElement('a')
+ link.href = file.url
+ link.download = file.name
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ ElMessage.success('寮�濮嬩笅杞芥枃浠�')
+ } else {
+ ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曚笅杞�')
+ }
+}
+
+// 鏍煎紡鍖栨枃浠跺ぇ灏�
+const formatFileSize = (bytes) => {
+ if (bytes === 0) return '0 B'
+ const k = 1024
+ const sizes = ['B', 'KB', 'MB', 'GB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (dateString) => {
+ if (!dateString) return ''
+ const date = new Date(dateString)
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ })
+}
+
+// 娴嬭瘯鏂囦欢涓婁紶
+const testUpload = () => {
+ console.log('褰撳墠鏂囨。ID:', currentDocumentId.value);
+ console.log('涓婁紶URL:', uploadUrl);
+ console.log('涓婁紶Headers:', uploadHeaders.value);
+}
+
+// 鏆撮湶鏂规硶
+defineExpose({
+ open,
+ loadAttachmentList,
+ testUpload
+})
+</script>
+
+<style scoped>
+.attachment-manager {
+ padding: 20px;
+}
+
+.upload-section {
+ margin-bottom: 20px;
+ padding: 20px;
+ background-color: #f8f9fa;
+ border-radius: 8px;
+ border: 2px dashed #d9d9d9;
+}
+
+.upload-section:hover {
+ border-color: #409eff;
+}
+
+.attachment-list {
+ margin-bottom: 20px;
+}
+
+.el-upload__tip {
+ margin-top: 10px;
+ color: #666;
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+:deep(.el-upload) {
+ width: 100%;
+}
+
+:deep(.el-upload-dragger) {
+ width: 100%;
+ height: 120px;
+}
+</style>
diff --git a/src/views/fileManagement/document/index.vue b/src/views/fileManagement/document/index.vue
new file mode 100644
index 0000000..a0d824a
--- /dev/null
+++ b/src/views/fileManagement/document/index.vue
@@ -0,0 +1,1416 @@
+<template>
+ <div class="app-container document-view">
+ <div class="left">
+ <div>
+ <el-input
+ v-model="search"
+ style="width: 210px"
+ placeholder="杈撳叆鍏抽敭瀛楄繘琛屾悳绱�"
+ @change="searchFilter"
+ @clear="searchFilter"
+ clearable
+ prefix-icon="Search"
+ />
+ <el-button
+ type="primary"
+ @click="openCategoryDia('addOne')"
+ style="margin-left: 10px"
+ >鏂板鍒嗙被</el-button
+ >
+ </div>
+ <div ref="containerRef">
+ <el-tree
+ ref="tree"
+ v-loading="treeLoad"
+ :data="categoryList"
+ @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: 'category' }"
+ highlight-current
+ node-key="id"
+ style="
+ height: calc(100vh - 190px);
+ overflow-y: scroll;
+ scrollbar-width: none;
+ margin-top: 10px;
+ "
+ >
+ <template #default="{ node, data }">
+ <div class="custom-tree-node">
+ <span class="tree-node-content">
+ <el-icon class="orange-icon">
+ <component :is="data.children && data.children.length > 0
+ ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
+ </el-icon>
+ {{ data.category }}
+ </span>
+ <div>
+ <el-button
+ type="primary"
+ link
+ @click="openCategoryDia('edit', data)"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ type="primary"
+ link
+ @click="openCategoryDia('addSub', data)"
+ v-if="node.level < 2"
+ >
+ 娣诲姞瀛愬垎绫�
+ </el-button>
+ <el-button
+ v-if="!node.childNodes.length"
+ style="margin-left: 4px"
+ type="danger"
+ link
+ @click="removeCategory(node, data)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ </template>
+ </el-tree>
+ </div>
+ </div>
+ <div class="right">
+ <div style="margin-bottom: 10px" v-if="isShowButton">
+ <el-button type="primary" @click="openDocumentDia('add')">
+ 鏂板鏂囨。
+ </el-button>
+ <el-button
+ type="danger"
+ @click="handleDelete"
+ style="margin-left: 10px"
+ plain
+ :disabled="selectedRows.length === 0"
+ >
+ 鍒犻櫎 ({{ selectedRows.length }})
+ </el-button>
+ </div>
+ <div class="table-container">
+
+ <!-- PIMTable 缁勪欢 -->
+ <PIMTable
+ :table-data="documentList"
+ :column="tableColumns"
+ :is-selection="true"
+ :border="true"
+ :table-loading="tableLoading"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ layout: 'total, sizes, prev, pager, next, jumper'
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="handlePagination"
+ />
+ </div>
+ </div>
+
+ <!-- 鍒嗙被鏂板/淇敼瀵硅瘽妗� -->
+ <el-dialog v-model="categoryDia" title="鍒嗙被" width="400px" @keydown.enter.prevent>
+ <el-form
+ :model="categoryForm"
+ label-width="140px"
+ label-position="top"
+ :rules="categoryRules"
+ ref="categoryFormRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="24" v-if="categoryOperationType === 'addSub'">
+ <el-form-item label="鐖跺垎绫伙細" prop="parentName">
+ <el-input
+ v-model="categoryForm.parentName"
+ placeholder="鐖跺垎绫诲悕绉�"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="鍒嗙被鍚嶇О锛�" prop="category">
+ <el-input
+ v-model="categoryForm.category"
+ placeholder="璇疯緭鍏ュ垎绫诲悕绉�"
+ clearable
+ @keydown.enter.prevent
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitCategoryForm">纭</el-button>
+ <el-button @click="closeCategoryDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+<el-dialog
+ v-model="qrCodeDialogVisible"
+ title="鏂囨。浜岀淮鐮�"
+ width="400px"
+ @close="closeQrCodeDialog"
+ >
+ <div class="qr-code-container">
+ <div v-if="qrCodeUrl" class="qr-code-image">
+ <img :src="qrCodeUrl" alt="鏂囨。浜岀淮鐮�" class="qr-image" />
+ <div class="qr-info">
+ <p><strong>鏂囨。鍚嶇О锛�</strong>{{ currentDocument.docName }}</p>
+ <p><strong>鏂囨。缂栧彿锛�</strong>{{ currentDocument.docNumber }}</p>
+ </div>
+ </div>
+ <div v-else class="qr-loading">
+ <el-icon class="is-loading"><Loading /></el-icon>
+ <p>姝e湪鐢熸垚浜岀淮鐮�...</p>
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="closeQrCodeDialog">鍏抽棴</el-button>
+ <el-button
+ v-if="qrCodeUrl"
+ type="primary"
+ @click="downloadQRCode"
+ icon="Download"
+ >
+ 涓嬭浇浜岀淮鐮�
+ </el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 鏂囨。鏂板/淇敼瀵硅瘽妗� -->
+ <el-dialog
+ v-model="documentDia"
+ :title="documentOperationType === 'add' ? '鏂板鏂囨。' : '缂栬緫鏂囨。'"
+ width="600px"
+ @close="closeDocumentDia"
+ @keydown.enter.prevent
+ >
+ <el-form
+ :model="documentForm"
+ label-width="140px"
+ label-position="top"
+ :rules="documentRules"
+ ref="documentFormRef"
+ >
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏂囨。鍚嶇О锛�" prop="docName">
+ <el-input v-model="documentForm.docName" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="骞村害锛�" prop="year">
+ <el-date-picker
+ v-model="documentForm.year"
+ type="year"
+ value-format="YYYY"
+ format="YYYY"
+ placeholder="閫夋嫨骞村害"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏂囨。缂栧彿锛�" prop="docNumber">
+ <el-input v-model="documentForm.docNumber" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璐d换浜猴細" prop="responsiblePerson">
+ <el-input v-model="documentForm.responsiblePerson" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏂囨。鍒嗙被锛�" prop="documentClassificationId">
+ <el-select v-model="documentForm.documentClassificationId" placeholder="璇烽�夋嫨鏂囨。鍒嗙被" style="width: 100%">
+ <el-option
+ v-for="item in categoryList"
+ :key="item.id"
+ :label="item.category"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏂囨。鏀剧疆浣嶇疆锛�" prop="warehouseGoodsShelvesRowcolId">
+ <el-tree-select
+ v-model="documentForm.warehouseGoodsShelvesRowcolId"
+ :data="locationTree"
+ placeholder="璇烽�夋嫨鏂囦欢鏀剧疆浣嶇疆"
+ clearable
+ check-strictly
+ :render-after-expand="false"
+ :props="{ children: 'children', label: 'label', value: 'value' }"
+ style="width: 100%"
+ @change="handleLocationChange"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏂囨。鏃ユ湡锛�" prop="docData">
+ <el-date-picker
+ v-model="documentForm.docData"
+ type="date"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ placeholder="閫夋嫨鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="淇濈鏈熼檺锛�" prop="retentionPeriod">
+ <el-select v-model="documentForm.retentionPeriod" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="item in retention_period"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="淇濆瘑绾у埆锛�" prop="securityLevel">
+ <el-select v-model="documentForm.securityLevel" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="item in confidentiality_level"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍒嗘暟锛�" prop="copyCount">
+ <el-input v-model="documentForm.copyCount" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="椤垫暟锛�" prop="pageCount">
+ <el-input v-model="documentForm.pageCount" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏂囨。绫诲埆锛�" prop="docCategory">
+ <el-select v-model="documentForm.docCategory" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="item in document_type"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏂囨。绉嶇被锛�" prop="docType">
+ <el-select v-model="documentForm.docType" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="item in document_categories"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绱ф�ョ▼搴︼細" prop="urgencyLevel">
+ <el-select v-model="documentForm.urgencyLevel" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="item in document_urgency"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏂囨。鐘舵�侊細" prop="docStatus">
+ <el-select v-model="documentForm.docStatus" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="item in document_status"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞锛�" prop="remark">
+ <el-input
+ v-model="documentForm.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitDocumentForm">纭</el-button>
+ <el-button @click="closeDocumentDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <AttachmentManager ref="attachmentManagerRef" />
+ </div>
+ </template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance, toRefs, watch } from "vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import { ArrowRight, Folder, FolderOpened, Tickets, Document } from '@element-plus/icons-vue';
+import PIMTable from '@/components/PIMTable/PIMTable.vue';
+import { getToken } from "@/utils/auth";
+import { getCategoryTree, addCategory, updateCategory, deleteCategory, getDocumentList, addDocument, updateDocument, deleteDocument, getDocumentDetail, searchDocument, getWarehouseStructure } from '@/api/fileManagement/document'
+import { getWarehouseList } from '@/api/fileManagement/bookshelf'
+import AttachmentManager from './attachmentManager.vue'
+import { useDict } from '@/utils/dict'
+
+const { proxy } = getCurrentInstance();
+const tree = ref(null);
+const containerRef = ref(null);
+// 瀵煎叆qrcode搴�
+import QRCode from 'qrcode'
+import { Loading, Download } from '@element-plus/icons-vue'
+// 浣跨敤瀛楀吀鏁版嵁
+const { confidentiality_level, document_urgency, document_status, document_type, document_categories, retention_period } = useDict('confidentiality_level', 'document_urgency', 'document_status', 'document_type', 'document_categories', 'retention_period')
+
+// 鐩戝惉瀛楀吀鏁版嵁鍙樺寲
+watch([confidentiality_level, document_urgency, document_status, document_type, document_categories, retention_period], () => {
+ // 瀛楀吀鏁版嵁宸叉洿鏂�
+}, { immediate: true, deep: true });
+
+const categoryDia = ref(false);
+const documentDia = ref(false);
+const categoryOperationType = ref("");
+const documentOperationType = ref("");
+const search = ref("");
+const currentId = ref("");
+const currentParentId = ref("");
+const treeLoad = ref(false);
+const categoryList = ref([]);
+const expandedKeys = ref([]);
+const documentList = ref([]);
+const isShowButton = ref(false);
+const selectedRows = ref([]);
+const selectAll = ref(false);
+const isIndeterminate = ref(false);
+const tableLoading = ref(false);
+const attachmentManagerRef = ref(null);
+
+// 鏂囦欢涓婁紶閰嶇疆
+const upload = reactive({
+ url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
+ headers: { Authorization: "Bearer " + getToken() },
+});
+
+// 浣嶇疆鏍戞暟鎹�
+const locationTree = ref([]);
+
+// 浜岀淮鐮佺浉鍏冲彉閲�
+const qrCodeDialogVisible = ref(false)
+const qrCodeUrl = ref('')
+const currentDocument = ref({})
+// 琛ㄦ牸鍒楅厤缃�
+const tableColumns = ref([
+ { label: '鏂囨。鍚嶇О', prop: 'docName', width: '200' },
+ { label: '鏂囨。缂栧彿', prop: 'docNumber', width: '120' },
+ { label: '骞村害', prop: 'year', width: '80' },
+ { label: '璐d换浜�', prop: 'responsiblePerson', width: '100' },
+ {
+ label: '鏂囨。鏀剧疆浣嶇疆',
+ prop: 'warehouseGoodsShelvesRowcolId',
+ width: '150',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ return getLocationName(params);
+ }
+ },
+ { label: '鏂囨。鏃ユ湡', prop: 'docData', width: '120' },
+ {
+ label: '淇濈鏈熼檺',
+ prop: 'retentionPeriod',
+ width: '100',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ if (!retention_period.value || retention_period.value.length === 0) {
+ return params;
+ }
+ const item = retention_period.value.find(item => item.value == params);
+ return item ? item.label : params;
+ },
+ formatType: (params) => {
+ if (params === null || params === undefined || params === '') return 'info';
+ if (!retention_period.value || retention_period.value.length === 0) {
+ return 'info';
+ }
+ const item = retention_period.value.find(item => item.value == params);
+ const validTypes = ['success', 'warning', 'danger', 'info'];
+ return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
+ }
+ },
+ {
+ label: '淇濆瘑绾у埆',
+ prop: 'securityLevel',
+ width: '80',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ if (!confidentiality_level.value || confidentiality_level.value.length === 0) {
+ return params;
+ }
+ const item = confidentiality_level.value.find(item => item.value == params);
+ return item ? item.label : params;
+ },
+ formatType: (params) => {
+ if (params === null || params === undefined || params === '') return 'info';
+ if (!confidentiality_level.value || confidentiality_level.value.length === 0) {
+ return 'info';
+ }
+ const item = confidentiality_level.value.find(item => item.value == params);
+ const validTypes = ['success', 'warning', 'danger', 'info'];
+ return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
+ }
+ },
+ { label: '鍒嗘暟', prop: 'copyCount', width: '80' },
+ { label: '椤垫暟', prop: 'pageCount', width: '80' },
+ {
+ label: '鏂囨。绫诲埆',
+ prop: 'docCategory',
+ width: '100',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ if (!document_type.value || document_type.value.length === 0) {
+ return params;
+ }
+ const item = document_type.value.find(item => item.value == params);
+ return item ? item.label : params;
+ },
+ formatType: (params) => {
+ if (params === null || params === undefined || params === '') return 'info';
+ if (!document_type.value || document_type.value.length === 0) {
+ return 'info';
+ }
+ const item = document_type.value.find(item => item.value == params);
+ const validTypes = ['success', 'warning', 'danger', 'info'];
+ return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
+ }
+ },
+ {
+ label: '鏂囨。绉嶇被',
+ prop: 'docType',
+ width: '100',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ if (!document_categories.value || document_categories.value.length === 0) {
+ return params;
+ }
+ const item = document_categories.value.find(item => item.value == params);
+ return item ? item.label : params;
+ },
+ formatType: (params) => {
+ if (params === null || params === undefined || params === '') return 'info';
+ if (!document_categories.value || document_categories.value.length === 0) {
+ return 'info';
+ }
+ const item = document_categories.value.find(item => item.value == params);
+ const validTypes = ['success', 'warning', 'danger', 'info'];
+ return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
+ }
+ },
+ {
+ label: '绱ф�ョ▼搴�',
+ prop: 'urgencyLevel',
+ width: '100',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ if (!document_urgency.value || document_urgency.value.length === 0) {
+ return params;
+ }
+ const item = document_urgency.value.find(item => item.value == params);
+ return item ? item.label : params;
+ },
+ formatType: (params) => {
+ if (params === null || params === undefined || params === '') return 'info';
+ if (!document_urgency.value || document_urgency.value.length === 0) {
+ return 'info';
+ }
+ const item = document_urgency.value.find(item => item.value == params);
+ const validTypes = ['success', 'warning', 'danger', 'info'];
+ return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
+ }
+ },
+ {
+ label: '鏂囨。鐘舵��',
+ prop: 'docStatus',
+ width: '100',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ if (!document_status.value || document_status.value.length === 0) {
+ return params;
+ }
+ const item = document_status.value.find(item => item.value == params);
+ return item ? item.label : params;
+ },
+ formatType: (params) => {
+ if (params === null || params === undefined || params === '') return 'info';
+ if (!document_status.value || document_status.value.length === 0) {
+ return 'info';
+ }
+ const item = document_status.value.find(item => item.value == params);
+ const validTypes = ['success', 'warning', 'danger', 'info'];
+ return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
+ }
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: 'right',
+ width: '200',
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ openDocumentDia('edit', row)
+ },
+ },
+ {
+ name: "闄勪欢",
+ type: "text",
+ clickFun: (row) => {
+ openAttachment(row)
+ },
+ },
+ {
+ name: "鐢熸垚浜岀淮鐮�",
+ type: "text",
+ clickFun: (row) => {
+ generateQRCode(row)
+ },
+ },
+ ],
+ }
+]);
+// 鐢熸垚浜岀淮鐮�
+const generateQRCode = async (row) => {
+ try {
+ // 妫�鏌ュ繀瑕佸瓧娈�
+ if (!row.docName || !row.docNumber) {
+ ElMessage.warning('鏂囨。淇℃伅涓嶅畬鏁达紝鏃犳硶鐢熸垚浜岀淮鐮�')
+ return
+ }
+
+ currentDocument.value = row
+ qrCodeUrl.value = ''
+ qrCodeDialogVisible.value = true
+
+ // 鏋勫缓浜岀淮鐮佸唴瀹�
+ // const qrContent = `${row.id}|${row.docName}|${row.docNumber}`
+ const qrContent = `${row.id}`
+ // 鐢熸垚浜岀淮鐮�
+ qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
+ width: 256,
+ margin: 2,
+ color: {
+ dark: '#000000',
+ light: '#FFFFFF'
+ },
+ errorCorrectionLevel: 'M'
+ })
+
+ // ElMessage.success('浜岀淮鐮佺敓鎴愭垚鍔燂紒')
+
+ } catch (error) {
+ console.error('鐢熸垚浜岀淮鐮佸け璐�:', error)
+ ElMessage.error('鐢熸垚浜岀淮鐮佸け璐ワ細' + error.message)
+ qrCodeDialogVisible.value = false
+ }
+}
+
+// 涓嬭浇浜岀淮鐮�
+const downloadQRCode = () => {
+ if (!qrCodeUrl.value) {
+ ElMessage.warning('璇峰厛鐢熸垚浜岀淮鐮�')
+ return
+ }
+
+ const a = document.createElement('a')
+ a.href = qrCodeUrl.value
+ a.download = `${currentDocument.value.docName}_浜岀淮鐮乢${new Date().getTime()}.png`
+ document.body.appendChild(a)
+ a.click()
+ document.body.removeChild(a)
+ ElMessage.success('涓嬭浇鎴愬姛锛�')
+}
+
+// 鍏抽棴浜岀淮鐮佸脊绐�
+const closeQrCodeDialog = () => {
+ qrCodeDialogVisible.value = false
+ qrCodeUrl.value = ''
+ currentDocument.value = {}
+}
+// 鍒嗙被琛ㄥ崟
+const categoryForm = reactive({
+ category: "",
+ parentId: "",
+ parentName: "",
+});
+
+const categoryRules = reactive({
+ category: [{ required: true, message: "璇疯緭鍏ュ垎绫诲悕绉�", trigger: "blur" }],
+});
+
+// 鏂囨。琛ㄥ崟
+const documentForm = reactive({
+ id: "",
+ documentClassificationId: "",
+ docName: "",
+ docNumber: "",
+ year: "",
+ responsiblePerson: "",
+ warehouseGoodsShelvesRowcolId: "",
+ docData: "",
+ retentionPeriod: "",
+ securityLevel: "",
+ copyCount: "",
+ pageCount: "",
+ docCategory: "",
+ docType: "",
+ urgencyLevel: "",
+ docStatus: "",
+ remark: "",
+ attachments: [], // 鏂板闄勪欢鏁扮粍
+});
+
+const documentRules = reactive({
+ docName: [{ required: true, message: "璇疯緭鍏ユ枃妗e悕绉�", trigger: "blur" }],
+ docNumber: [{ required: true, message: "璇疯緭鍏ユ枃妗g紪鍙�", trigger: "blur" }],
+ year: [{ required: true, message: "璇烽�夋嫨骞村害", trigger: "change" }],
+ documentClassificationId: [{ required: true, message: "璇烽�夋嫨鏂囨。鍒嗙被", trigger: "change" }],
+ warehouseGoodsShelvesRowcolId: [{ required: true, message: "璇烽�夋嫨鏂囨。鏀剧疆浣嶇疆", trigger: "change" }],
+});
+
+// 鍒嗛〉鐩稿叧
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+// 鍒濆鍖栧垎绫绘爲鏁版嵁
+const initCategoryTree = async() => {
+ try {
+ treeLoad.value = true;
+ const res = await getCategoryTree();
+ if (res.code === 200) {
+ categoryList.value = res.data || [];
+
+ // 璁剧疆灞曞紑鐨勮妭鐐�
+ expandedKeys.value = [];
+ categoryList.value.forEach((item) => {
+ if (item.id) {
+ expandedKeys.value.push(item.id);
+ }
+ });
+ } else {
+ ElMessage.error(res.msg || "鑾峰彇鍒嗙被鏍戝け璐�");
+ }
+ } catch (error) {
+ ElMessage.error("鑾峰彇鍒嗙被鏍戝け璐ワ紝璇烽噸璇�");
+ } finally {
+ treeLoad.value = false;
+ }
+};
+
+// 鍒濆鍖栦粨搴撲綅缃暟鎹�
+const initLocationTree = async() => {
+ try {
+ const res = await getWarehouseList();
+ if (res.code === 200) {
+ // 杞崲鏁版嵁鏍煎紡锛岄�傞厤el-tree-select缁勪欢
+ locationTree.value = transformWarehouseData(res.data || []);
+ } else {
+ ElMessage.error(res.msg || "鑾峰彇浠撳簱浣嶇疆澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error("鑾峰彇浠撳簱浣嶇疆澶辫触锛岃閲嶈瘯");
+ }
+};
+
+// 杞崲浠撳簱鏁版嵁鏍煎紡
+const transformWarehouseData = (data) => {
+ return data.map(item => ({
+ id: item.id,
+ label: item.name || item.warehouseName || item.label,
+ value: item.id,
+ children: item.children ? transformWarehouseData(item.children) : []
+ }));
+};
+
+// 鏍规嵁ID鑾峰彇浣嶇疆鍚嶇О
+const getLocationName = (locationId) => {
+ if (!locationId || !locationTree.value || locationTree.value.length === 0) {
+ return locationId || '-';
+ }
+
+ const findLocation = (tree, id) => {
+ for (let item of tree) {
+ if (item.value === locationId || item.id === locationId) {
+ return item.label;
+ }
+ if (item.children && item.children.length > 0) {
+ const result = findLocation(item.children, id);
+ if (result) return result;
+ }
+ }
+ return null;
+ };
+
+ const locationName = findLocation(locationTree.value, locationId);
+ return locationName || locationId;
+};
+
+// 杩囨护鍒嗙被鏍�
+const searchFilter = () => {
+ if (proxy.$refs.tree) {
+ proxy.$refs.tree.filter(search.value);
+ }
+};
+
+// 鎵撳紑鍒嗙被寮规
+const openCategoryDia = (type, data) => {
+ categoryOperationType.value = type;
+ categoryDia.value = true;
+ categoryForm.category = "";
+ categoryForm.parentId ="";
+ categoryForm.parentName = "";
+
+ if (type === "edit") {
+ categoryForm.category = data.category;
+ // 淇濆瓨褰撳墠缂栬緫鐨勫垎绫籌D
+ currentId.value = data.id;
+ } else if (type === "addSub") {
+ categoryForm.parentId = data.id;
+ categoryForm.parentName = data.category;
+ }
+};
+
+// 鎵撳紑鏂囨。寮规
+const openDocumentDia = (type, data) => {
+ documentOperationType.value = type;
+ documentDia.value = true;
+
+ if (type === "edit") {
+ // 缂栬緫妯″紡锛屽姞杞界幇鏈夋暟鎹�
+ Object.assign(documentForm, data);
+ documentForm.retentionPeriod = String(documentForm.retentionPeriod)
+ documentForm.securityLevel = String(documentForm.securityLevel)
+ documentForm.docCategory = String(documentForm.docCategory)
+ documentForm.docType = String(documentForm.docType)
+ documentForm.urgencyLevel = String(documentForm.urgencyLevel)
+ documentForm.docStatus = String(documentForm.docStatus)
+
+ // 鍔犺浇闄勪欢淇℃伅
+ if (data.attachments) {
+ documentForm.attachments = [...data.attachments];
+ } else {
+ documentForm.attachments = [];
+ }
+ } else {
+ // 鏂板妯″紡锛屾竻绌鸿〃鍗�
+ Object.keys(documentForm).forEach(key => {
+ documentForm[key] = "";
+ });
+ documentForm.attachments = []; // 鏂板妯″紡涓嬩篃娓呯┖闄勪欢
+ // 璁剧疆榛樿鍊� - 浣跨敤瀛楀吀鏁版嵁鐨勭涓�涓�夐」浣滀负榛樿鍊�
+ if (document_status.value && document_status.value.length > 0) {
+ documentForm.docStatus = document_status.value[0].value;
+ }
+ if (document_urgency.value && document_urgency.value.length > 0) {
+ documentForm.urgencyLevel = document_urgency.value[0].value;
+ }
+ }
+};
+
+// 鎻愪氦鍒嗙被琛ㄥ崟
+const submitCategoryForm = () => {
+ proxy.$refs.categoryFormRef.validate(async (valid) => {
+ if (valid) {
+ try {
+ if (categoryOperationType.value === "addSub") {
+ // 娣诲姞瀛愬垎绫�
+ const res = await addCategory({
+ category: categoryForm.category,
+ parentId: categoryForm.parentId
+ });
+ if (res.code === 200) {
+ ElMessage.success("娣诲姞瀛愬垎绫绘垚鍔�");
+ // 閲嶆柊鍔犺浇鍒嗙被鏍�
+ await initCategoryTree();
+ } else {
+ ElMessage.error(res.msg || "娣诲姞瀛愬垎绫诲け璐�");
+ }
+ } else if (categoryOperationType.value === "edit") {
+ // 缂栬緫鍒嗙被
+ const res = await updateCategory({
+ id: currentId.value,
+ category: categoryForm.category
+ });
+ if (res.code === 200) {
+ ElMessage.success("缂栬緫鍒嗙被鎴愬姛");
+ // 閲嶆柊鍔犺浇鍒嗙被鏍�
+ await initCategoryTree();
+ } else {
+ ElMessage.error(res.msg || "缂栬緫鍒嗙被澶辫触");
+ }
+ } else {
+ // 鏂板椤剁骇鍒嗙被
+ const res = await addCategory({
+ category: categoryForm.category,
+ parentId: null
+ });
+ if (res.code === 200) {
+ ElMessage.success("鏂板鍒嗙被鎴愬姛");
+ // 閲嶆柊鍔犺浇鍒嗙被鏍�
+ await initCategoryTree();
+ } else {
+ ElMessage.error(res.msg || "鏂板鍒嗙被澶辫触");
+ }
+ }
+
+ closeCategoryDia();
+ } catch (error) {
+ ElMessage.error("鎿嶄綔澶辫触锛岃閲嶈瘯");
+ }
+ }
+ });
+};
+
+// 鍏抽棴鍒嗙被寮规
+const closeCategoryDia = () => {
+ proxy.$refs.categoryFormRef.resetFields();
+ categoryForm.parentId = "";
+ categoryForm.parentName = "";
+ categoryDia.value = false;
+};
+
+// 鍒犻櫎鍒嗙被
+const removeCategory = (node, data) => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(async () => {
+ try {
+ const res = await deleteCategory([data.id]);
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ // 閲嶆柊鍔犺浇鍒嗙被鏍�
+ await initCategoryTree();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error("鍒犻櫎澶辫触锛岃閲嶈瘯");
+ }
+ })
+ .catch(() => {
+ ElMessage("宸插彇娑�");
+ });
+};
+
+// 閫夋嫨鍒嗙被
+const handleNodeClick = (val, node, el) => {
+ // 鍒ゆ柇鏄惁涓哄彾瀛愯妭鐐�
+ isShowButton.value = true;
+ // 鍙湁鍙跺瓙鑺傜偣鎵嶆墽琛屼互涓嬮�昏緫
+ currentId.value = val.id;
+ currentParentId.value = val.parentId;
+
+ // 娓呯┖閫夋嫨鐘舵��
+ selectedRows.value = [];
+ selectAll.value = false;
+ isIndeterminate.value = false;
+
+ // 閲嶇疆鍒嗛〉
+ pagination.currentPage = 1;
+ pagination.total = 0;
+
+ // 鍔犺浇鏂囨。鍒楄〃
+ if (isShowButton.value) {
+ loadDocumentList();
+ } else {
+ // 濡傛灉涓嶆槸鍙跺瓙鑺傜偣锛屾竻绌烘枃妗e垪琛�
+ documentList.value = [];
+ }
+};
+
+// 鎻愪氦鏂囨。琛ㄥ崟
+const submitDocumentForm = () => {
+ proxy.$refs.documentFormRef.validate(async (valid) => {
+ if (valid) {
+ try {
+ // 鏋勫缓鎻愪氦鏁版嵁
+ const submitData = {
+ ...documentForm,
+ // 璁剧疆褰撳墠閫変腑鐨勫垎绫籌D
+ documentClassificationId: currentId.value || documentForm.documentClassificationId,
+ // 娣诲姞闄勪欢淇℃伅
+ // attachments: documentForm.attachments
+ };
+
+ if (documentOperationType.value === "edit") {
+ // 缂栬緫妯″紡锛屾洿鏂扮幇鏈夋暟鎹�
+ const res = await updateDocument(submitData);
+ if (res.code === 200) {
+ ElMessage.success("缂栬緫鎴愬姛");
+ // 閲嶆柊鍔犺浇鏂囨。鍒楄〃
+ await loadDocumentList();
+ // 鍒锋柊闄勪欢鍒楄〃
+ if (attachmentManagerRef.value && documentForm.id) {
+ attachmentManagerRef.value.loadAttachmentList(documentForm.id);
+ }
+ } else {
+ ElMessage.error(res.msg || "缂栬緫澶辫触");
+ }
+ } else {
+ // 鏂板妯″紡锛屾坊鍔犳柊鏁版嵁
+ const res = await addDocument(submitData);
+ if (res.code === 200) {
+ ElMessage.success("鏂板鎴愬姛");
+ // 閲嶆柊鍔犺浇鏂囨。鍒楄〃
+ await loadDocumentList();
+ // 鍒锋柊闄勪欢鍒楄〃
+ if (attachmentManagerRef.value && res.data && res.data.id) {
+ attachmentManagerRef.value.loadAttachmentList(res.data.id);
+ }
+ } else {
+ ElMessage.error(res.msg || "鏂板澶辫触");
+ }
+ }
+ closeDocumentDia();
+ } catch (error) {
+ ElMessage.error("鎿嶄綔澶辫触锛岃閲嶈瘯");
+ }
+ }
+ });
+};
+
+// 鍏抽棴鏂囨。寮规
+const closeDocumentDia = () => {
+ proxy.$refs.documentFormRef.resetFields();
+ documentDia.value = false;
+ // 娓呯┖琛ㄥ崟鏁版嵁
+ Object.keys(documentForm).forEach(key => {
+ documentForm[key] = "";
+ });
+ documentForm.attachments = []; // 鍏抽棴寮规鏃朵篃娓呯┖闄勪欢
+};
+
+// 澶勭悊浣嶇疆閫夋嫨鍙樺寲
+const handleLocationChange = (value) => {
+ if (value) {
+ // 妫�鏌ラ�夋嫨鐨勬槸鍚︿负鍙跺瓙鑺傜偣
+ const isLeafNode = checkIfLeafNode(locationTree.value, value);
+ if (!isLeafNode) {
+ ElMessage.warning("璇烽�夋嫨鏈�搴曞眰鐨勪綅缃紙濡傦細鏌滃眰锛�");
+ documentForm.warehouseGoodsShelvesRowcolId = "";
+ return;
+ }
+ }
+};
+
+// 妫�鏌ユ槸鍚︿负鍙跺瓙鑺傜偣
+const checkIfLeafNode = (tree, value) => {
+ for (let item of tree) {
+ if (item.value === value || item.id === value) {
+ // 濡傛灉娌℃湁瀛愯妭鐐癸紝鍒欎负鍙跺瓙鑺傜偣
+ return !item.children || item.children.length === 0;
+ }
+ if (item.children && item.children.length > 0) {
+ const result = checkIfLeafNode(item.children, value);
+ if (result !== null) {
+ return result;
+ }
+ }
+ }
+ return null;
+};
+
+// 鍒犻櫎鏂囨。
+const handleDelete = () => {
+ if (selectedRows.value.length > 0) {
+ ElMessageBox.confirm(`纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉¤褰曞悧锛焋, "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(async () => {
+ try {
+ const selectedIds = selectedRows.value.map(row => row.id);
+ const res = await deleteDocument(selectedIds);
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ // 閲嶆柊鍔犺浇鏂囨。鍒楄〃
+ await loadDocumentList();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error("鍒犻櫎澶辫触锛岃閲嶈瘯");
+ }
+ })
+ .catch(() => {
+ ElMessage("宸插彇娑�");
+ });
+ } else {
+ ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ }
+};
+
+// PIMTable 閫夋嫨鍙樺寲浜嬩欢
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+
+ // 鏇存柊鍏ㄩ�夌姸鎬�
+ const selectedCount = selection.length;
+ const totalCount = documentList.value.length;
+
+ if (selectedCount === 0) {
+ selectAll.value = false;
+ isIndeterminate.value = false;
+ } else if (selectedCount === totalCount) {
+ selectAll.value = true;
+ isIndeterminate.value = false;
+ } else {
+ selectAll.value = false;
+ isIndeterminate.value = true;
+ }
+};
+
+// 鍔犺浇鏂囨。鍒楄〃
+const loadDocumentList = async () => {
+ try {
+ tableLoading.value = true;
+
+ // 鏋勫缓鏌ヨ鍙傛暟
+ const query = {
+ page: pagination.currentPage,
+ size: pagination.pageSize,
+ documentClassificationId:currentId.value
+ };
+
+ const res = await getDocumentList(query);
+ if (res.code === 200) {
+ documentList.value = res.data.records || [];
+ pagination.total = res.data.total || 0;
+ } else {
+ ElMessage.error(res.msg || "鑾峰彇鏂囨。鍒楄〃澶辫触");
+ documentList.value = [];
+ pagination.total = 0;
+ }
+
+ // 閲嶇疆閫夋嫨鐘舵��
+ selectedRows.value = [];
+ selectAll.value = false;
+ isIndeterminate.value = false;
+ } catch (error) {
+ ElMessage.error("鑾峰彇鏂囨。鍒楄〃澶辫触锛岃閲嶈瘯");
+ documentList.value = [];
+ pagination.total = 0;
+ } finally {
+ tableLoading.value = false;
+ }
+};
+
+// 澶勭悊鍒嗛〉鍙樺寲
+const handlePagination = (current, size) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ loadDocumentList();
+};
+
+// 璋冪敤tree杩囨护鏂规硶
+const filterNode = (value, data, node) => {
+ if (!value) {
+ return true;
+ }
+ let val = value.toLowerCase();
+ return chooseNode(val, data, node);
+};
+
+// 杩囨护鐖惰妭鐐� / 瀛愯妭鐐�
+const chooseNode = (value, data, node) => {
+ if (data.category && data.category.toLowerCase().indexOf(value) !== -1) {
+ return true;
+ }
+ const level = node.level;
+ if (level === 1) {
+ return false;
+ }
+ let parentData = node.parent;
+ let index = 0;
+ while (index < level - 1) {
+ if (parentData.data.category && parentData.data.category.toLowerCase().indexOf(value) !== -1) {
+ return true;
+ }
+ parentData = parentData.parent;
+ index++;
+ }
+ return false;
+};
+
+// 鎵撳紑闄勪欢
+const openAttachment = (row) => {
+ attachmentManagerRef.value.open([], row.id);
+};
+
+onMounted(() => {
+ initCategoryTree();
+ initLocationTree();
+
+ // 涓嶅湪鍒濆鍖栨椂鍔犺浇鏂囨。鍒楄〃锛岀瓑寰呯敤鎴烽�夋嫨鍒嗙被鍚庡啀鍔犺浇
+});
+</script>
+
+<style scoped>
+.document-view {
+ display: flex;
+ height: 100%;
+}
+
+.left {
+ width: 380px;
+ padding: 16px;
+ background: #ffffff;
+ border-right: 1px solid #e4e7ed;
+}
+
+.right {
+ width: calc(100% - 380px);
+ padding: 16px;
+ background: #ffffff;
+}
+
+.custom-tree-node {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ padding-right: 8px;
+}
+
+.tree-node-content {
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+
+.orange-icon {
+ color: orange;
+ font-size: 18px;
+ margin-right: 8px;
+}
+
+.table-container {
+ background: #ffffff;
+ border-radius: 8px;
+ overflow: hidden;
+ position: relative;
+}
+
+.add-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background-color: #f5f7fa;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+ padding: 12px 16px;
+ margin-bottom: 16px;
+ border-radius: 6px;
+ border: 1px dashed #d9d9d9;
+}
+
+.add-row:hover {
+ background-color: #e4e7ed;
+ border-color: #c0c4cc;
+}
+
+.add-icon {
+ color: #909399;
+ font-size: 16px;
+}
+
+.add-row span {
+ color: #606266;
+ font-size: 14px;
+}
+
+.empty-data {
+ text-align: center;
+ color: #909399;
+ padding: 40px;
+ font-size: 14px;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+.operation-column {
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 120px;
+ background: #ffffff;
+ border-left: 1px solid #e4e7ed;
+ z-index: 1;
+ box-shadow: -2px 0 4px rgba(0, 0, 0, 0.1);
+}
+
+.operation-header {
+ height: 40px;
+ line-height: 40px;
+ text-align: center;
+ background: #fafafa;
+ border-bottom: 1px solid #e4e7ed;
+ font-weight: 500;
+ color: #606266;
+}
+
+.operation-cell {
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-bottom: 1px solid #e4e7ed;
+}
+
+.operation-cell:last-child {
+ border-bottom: none;
+}
+
+.attachment-section {
+ width: 100%;
+}
+
+.attachment-list {
+ margin-bottom: 10px;
+}
+
+.attachment-item {
+ display: flex;
+ align-items: center;
+ padding: 8px 12px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ margin-bottom: 8px;
+}
+
+.file-icon {
+ margin-right: 8px;
+ color: #409eff;
+}
+
+.file-name {
+ flex: 1;
+ color: #606266;
+ font-size: 14px;
+}
+/* 浜岀淮鐮侀瑙堟牱寮� */
+.qr-code-container {
+ text-align: center;
+ padding: 20px;
+}
+
+.qr-code-image {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+}
+
+.qr-image {
+ max-width: 100%;
+ height: auto;
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.qr-info {
+ text-align: left;
+ background: #f8f9fa;
+ padding: 15px;
+ border-radius: 8px;
+ min-width: 300px;
+}
+
+.qr-info p {
+ margin: 8px 0;
+ color: #666;
+ font-size: 14px;
+}
+
+.qr-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 15px;
+ padding: 40px 0;
+}
+
+.qr-loading .el-icon {
+ font-size: 32px;
+ color: #409EFF;
+}
+
+.qr-loading p {
+ color: #666;
+ margin: 0;
+}
+</style>
diff --git a/src/views/fileManagement/return/index.vue b/src/views/fileManagement/return/index.vue
new file mode 100644
index 0000000..34e733f
--- /dev/null
+++ b/src/views/fileManagement/return/index.vue
@@ -0,0 +1,699 @@
+<template>
+ <div class="app-container return-view">
+ <!-- 鏌ヨ鍖哄煙 -->
+ <div class="search-container">
+ <el-form :model="searchForm" :inline="true" class="search-form">
+ <!-- <el-form-item label="鍊熼槄鐘舵�侊細">
+ <el-select v-model="searchForm.borrowStatus" placeholder="璇烽�夋嫨鍊熼槄鐘舵��" clearable style="width: 150px">
+ <el-option label="鍊熼槄" value="鍊熼槄" />
+ <el-option label="褰掕繕" value="褰掕繕" />
+ </el-select>
+ </el-form-item> -->
+ <el-form-item label="鍊熼槄浜猴細">
+ <el-input
+ v-model="searchForm.borrower"
+ placeholder="璇疯緭鍏ュ�熼槄浜�"
+ clearable
+ style="width: 200px"
+ />
+ </el-form-item>
+ <el-form-item label="褰掕繕浜猴細">
+ <el-input
+ v-model="searchForm.returner"
+ placeholder="璇疯緭鍏ュ綊杩樹汉"
+ clearable
+ style="width: 200px"
+ />
+ </el-form-item>
+ <el-form-item label="褰掕繕鏃ユ湡鑼冨洿锛�">
+ <el-date-picker
+ v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 300px"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">
+ <el-icon><Search /></el-icon>
+ 鏌ヨ
+ </el-button>
+ <el-button @click="handleReset">
+ <el-icon><Refresh /></el-icon>
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ <el-form-item style="margin-left: auto;">
+ <el-button type="primary" @click="openReturnDia('add')">
+ <el-icon><Plus /></el-icon>
+ 鏂板褰掕繕
+ </el-button>
+ <el-button @click="handleOut">
+ 瀵煎嚭
+ </el-button>
+ <el-button
+ type="danger"
+ @click="handleBatchDelete"
+ :disabled="selectedRows.length === 0"
+ >
+ <el-icon><Delete /></el-icon>
+ 鎵归噺鍒犻櫎 ({{ selectedRows.length }})
+ </el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <!-- 琛ㄦ牸鍖哄煙 -->
+ <div class="table-container">
+ <PIMTable
+ :table-data="returnList"
+ :column="tableColumns"
+ :is-selection="true"
+ :border="true"
+ :table-loading="tableLoading"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ layout: 'total, sizes, prev, pager, next, jumper'
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="handlePagination"
+ />
+ </div>
+
+ <!-- 褰掕繕鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog
+ v-model="returnDia"
+ :title="returnOperationType === 'add' ? '鏂板褰掕繕' : '缂栬緫褰掕繕'"
+ width="800px"
+ @close="closeReturnDia"
+ @keydown.enter.prevent
+ >
+ <el-form
+ :model="returnForm"
+ label-width="140px"
+ :rules="returnRules"
+ ref="returnFormRef"
+ >
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏂囨。锛�" prop="borrowId">
+ <!-- <el-select v-model="returnForm.borrowId" placeholder="璇烽�夋嫨鏂囨。" style="flex: 1;" @change="handleDocumentChange">
+ <el-option
+ v-for="item in documentList"
+ :key="item.id"
+ :label="item.docName || item.name"
+ :value="item.id"
+ />
+ </el-select> -->
+ <div style="display: flex; gap: 10px;">
+ <el-select v-model="returnForm.borrowId" placeholder="璇烽�夋嫨鏂囨。" style="width: 120px;" @change="handleDocumentChange">
+ <el-option
+ v-for="item in documentList"
+ :key="item.id"
+ :label="item.docName || item.name"
+ :value="item.id"
+ />
+ </el-select>
+ <el-input
+ v-model="scanContent"
+ placeholder="鎵爜杈撳叆"
+ style="flex: 1;"
+ @input="handleScanContent"
+ clearable
+ />
+ </div>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍊熼槄浜猴細" prop="borrower">
+ <el-input v-model="returnForm.borrower" placeholder="鍊熼槄浜哄皢鏍规嵁鏂囨。閫夋嫨鑷姩甯﹀嚭" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="褰掕繕浜猴細" prop="returner">
+ <el-input v-model="returnForm.returner" placeholder="璇疯緭鍏ュ綊杩樹汉" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰掕繕鏃ユ湡锛�" prop="returnDate">
+ <el-date-picker
+ v-model="returnForm.returnDate"
+ type="date"
+ placeholder="閫夋嫨褰掕繕鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="搴斿綊杩樻棩鏈燂細" prop="dueReturnDate">
+ <el-date-picker
+ v-model="returnForm.dueReturnDate"
+ type="date"
+ placeholder="搴斿綊杩樻棩鏈熷皢鏍规嵁鏂囨。閫夋嫨鑷姩甯﹀嚭"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞璇存槑锛�" prop="remark">
+ <el-input
+ v-model="returnForm.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉ㄨ鏄�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitReturnForm">纭</el-button>
+ <el-button @click="closeReturnDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import { Search, Refresh, Plus, Delete } from '@element-plus/icons-vue';
+import PIMTable from '@/components/PIMTable/PIMTable.vue';
+import { getReturnListPage, returnDocument, deleteReturn, getDocumentList, updateBorrow, reventUpdate,getBorrowListByDocumentationId } from '@/api/fileManagement/return';
+
+const { proxy } = getCurrentInstance();
+
+// 鍝嶅簲寮忔暟鎹�
+const returnDia = ref(false);
+const returnOperationType = ref("");
+const tableLoading = ref(false);
+const returnList = ref([]);
+const selectedRows = ref([]);
+const documentList = ref([]); // 鏂囨。鍒楄〃
+const borrowInfoList = ref([]); // 鍊熼槄淇℃伅鍒楄〃
+const scanContent = ref(); // 鎵爜鍐呭
+
+// 鍒嗛〉鐩稿叧
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+// 鏌ヨ琛ㄥ崟
+const searchForm = reactive({
+ borrowStatus: "",
+ borrower: "",
+ returner: "",
+ dateRange: []
+});
+
+// 褰掕繕琛ㄥ崟
+const returnForm = reactive({
+ id: "",
+ borrowId: "",
+ borrower: "",
+ returner: "",
+ borrowStatus: "",
+ returnDate: "",
+ dueReturnDate: "",
+ remark: ""
+});
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const returnRules = reactive({
+ borrowId: [{ required: true, message: "璇烽�夋嫨鏂囨。", trigger: "change" }],
+ returner: [{ required: true, message: "璇疯緭鍏ュ綊杩樹汉", trigger: "blur" }],
+ returnDate: [{ required: true, message: "璇烽�夋嫨褰掕繕鏃ユ湡", trigger: "change" }]
+});
+
+// 琛ㄦ牸鍒楅厤缃�
+const tableColumns = ref([
+ {
+ label: '鏂囨。鍚嶇О',
+ prop: 'docName',
+ width: '200',
+ },
+ { label: '鍊熼槄浜�', prop: 'borrower' },
+ { label: '褰掕繕浜�', prop: 'returner' },
+ {
+ label: '鍊熼槄鐘舵��',
+ prop: 'borrowStatus',
+ dataType: 'tag',
+ formatData: (params) => {
+ if (params === null || params === undefined || params === '') return '-';
+ return params;
+ },
+ formatType: (params) => {
+ if (params === '褰掕繕') return 'success';
+ if (params === '鍊熼槄') return 'warning';
+ return 'info';
+ }
+ },
+ { label: '褰掕繕鏃ユ湡', prop: 'returnDate' },
+ { label: '搴斿綊杩樻棩鏈�', prop: 'dueReturnDate' },
+ { label: '澶囨敞', prop: 'remark', width: '150' },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: 'right',
+ width: '150',
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ openReturnDia('edit', row)
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "text",
+ clickFun: (row) => {
+ handleDelete(row)
+ },
+ },
+ ],
+ }
+]);
+
+// 鍒濆鍖栨暟鎹�
+const initData = async () => {
+ await Promise.all([
+ loadDocumentList(),
+ loadReturnList()
+ ]);
+};
+
+// 鍔犺浇鏂囨。鍒楄〃
+const loadDocumentList = async () => {
+ try {
+ const res = await getDocumentList();
+ if (res.code === 200) {
+ documentList.value = res.data || [];
+ } else {
+ ElMessage.error(res.msg || "鑾峰彇鏂囨。鍒楄〃澶辫触");
+ documentList.value = [];
+ }
+ } catch (error) {
+ ElMessage.error("鑾峰彇鏂囨。鍒楄〃澶辫触锛岃閲嶈瘯");
+ documentList.value = [];
+ }
+};
+
+// 鍔犺浇褰掕繕鍒楄〃
+const loadReturnList = async () => {
+ try {
+ tableLoading.value = true;
+
+ // 鏋勫缓鏌ヨ鍙傛暟
+ const query = {
+ page: pagination.currentPage,
+ size: pagination.pageSize,
+ borrowStatus: searchForm.borrowStatus || undefined,
+ borrower: searchForm.borrower || undefined,
+ returner: searchForm.returner || undefined,
+ entryDateStart: searchForm.dateRange && searchForm.dateRange.length > 0 ? searchForm.dateRange[0] : undefined,
+ entryDateEnd: searchForm.dateRange && searchForm.dateRange.length > 1 ? searchForm.dateRange[1] : undefined
+ };
+
+ // 绉婚櫎undefined鐨勫弬鏁�
+ Object.keys(query).forEach(key => {
+ if (query[key] === undefined) {
+ delete query[key];
+ }
+ });
+
+ const res = await getReturnListPage(query);
+ if (res.code === 200) {
+ returnList.value = res.data.records || [];
+ pagination.total = res.data.total || 0;
+ } else {
+ ElMessage.error(res.msg || "鑾峰彇褰掕繕鍒楄〃澶辫触");
+ returnList.value = [];
+ pagination.total = 0;
+ }
+
+ // 閲嶇疆閫夋嫨鐘舵��
+ selectedRows.value = [];
+ } catch (error) {
+ ElMessage.error("鑾峰彇褰掕繕鍒楄〃澶辫触锛岃閲嶈瘯");
+ returnList.value = [];
+ pagination.total = 0;
+ } finally {
+ tableLoading.value = false;
+ }
+};
+
+// 鏌ヨ
+const handleSearch = () => {
+ pagination.currentPage = 1;
+ loadReturnList();
+};
+
+// 閲嶇疆鏌ヨ
+const handleReset = () => {
+ searchForm.borrowStatus = "";
+ searchForm.borrower = "";
+ searchForm.returner = "";
+ searchForm.dateRange = [];
+ pagination.currentPage = 1;
+ loadReturnList();
+ ElMessage.success("鏌ヨ鏉′欢宸查噸缃�");
+};
+
+// 鎵撳紑褰掕繕寮规
+const openReturnDia = (type, data) => {
+ returnOperationType.value = type;
+ returnDia.value = true;
+ scanContent.value = ''; // 娓呯┖鎵爜鍐呭
+ borrowInfoList.value = []; // 娓呯┖鍊熼槄淇℃伅鍒楄〃
+
+ if (type === "edit") {
+ // 缂栬緫妯″紡锛屽姞杞界幇鏈夋暟鎹�
+ Object.assign(returnForm, data);
+ // 缂栬緫妯″紡涓嬶紝鏂囨。閫夋嫨鍚庤嚜鍔ㄥ~鍏呭�熼槄浜哄拰搴斿綊杩樻棩鏈�
+ if (returnForm.borrowId) {
+ handleDocumentChange(returnForm.borrowId);
+ }
+ } else {
+ // 鏂板妯″紡锛屾竻绌鸿〃鍗�
+ Object.keys(returnForm).forEach(key => {
+ returnForm[key] = "";
+ });
+ // 璁剧疆榛樿鐘舵��
+ returnForm.borrowStatus = "褰掕繕";
+ // 璁剧疆褰撳墠鏃ユ湡涓哄綊杩樻棩鏈�
+ returnForm.returnDate = new Date().toISOString().split('T')[0];
+ }
+};
+
+// 鍏抽棴褰掕繕寮规
+const closeReturnDia = () => {
+ proxy.$refs.returnFormRef.resetFields();
+ returnDia.value = false;
+ scanContent.value = ''; // 娓呯┖鎵爜鍐呭
+ borrowInfoList.value = []; // 娓呯┖鍊熼槄淇℃伅鍒楄〃
+};
+
+// 鎻愪氦褰掕繕琛ㄥ崟
+const submitReturnForm = () => {
+ proxy.$refs.returnFormRef.validate(async (valid) => {
+ if (valid) {
+ try {
+ if (returnOperationType.value === "edit") {
+ // 缂栬緫妯″紡锛岃皟鐢ㄥ綊杩樻洿鏂版帴鍙�
+ const res = await reventUpdate({
+ id: returnForm.id,
+ documentationId: returnForm.documentationId,
+ borrower: returnForm.borrower,
+ returner: returnForm.returner,
+ borrowStatus: returnForm.borrowStatus,
+ returnDate: returnForm.returnDate,
+ dueReturnDate: returnForm.dueReturnDate,
+ remark: returnForm.remark
+ });
+
+ if (res.code === 200) {
+ ElMessage.success("缂栬緫鎴愬姛");
+ await loadReturnList();
+ closeReturnDia();
+ } else {
+ ElMessage.error(res.msg || "缂栬緫澶辫触");
+ }
+ } else {
+ // 鏂板妯″紡锛岃皟鐢ㄥ綊杩樻帴鍙�
+ const res = await returnDocument({
+ borrowId: returnForm.borrowId,
+ borrower: returnForm.borrower,
+ returner: returnForm.returner,
+ borrowStatus: returnForm.borrowStatus,
+ returnDate: returnForm.returnDate,
+ dueReturnDate: returnForm.dueReturnDate,
+ remark: returnForm.remark
+ });
+
+ if (res.code === 200) {
+ ElMessage.success("鏂板鎴愬姛");
+ await loadReturnList();
+ closeReturnDia();
+ } else {
+ ElMessage.error(res.msg || "鏂板澶辫触");
+ }
+ }
+ } catch (error) {
+ ElMessage.error("鎿嶄綔澶辫触锛岃閲嶈瘯");
+ }
+ }
+ });
+};
+
+// 鍒犻櫎褰掕繕璁板綍
+const handleDelete = (row) => {
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄よ繖鏉″綊杩樿褰曞悧锛焋,
+ "鍒犻櫎鎻愮ず",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ ).then(async () => {
+ try {
+ const res = await deleteReturn([row.id]);
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ await loadReturnList();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error("鍒犻櫎澶辫触锛岃閲嶈瘯");
+ }
+ }).catch(() => {
+ ElMessage.info("宸插彇娑堝垹闄�");
+ });
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑璁板綍");
+ return;
+ }
+
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉″綊杩樿褰曞悧锛焋,
+ "鎵归噺鍒犻櫎鎻愮ず",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ ).then(async () => {
+ try {
+ const selectedIds = selectedRows.value.map(row => row.id);
+ const res = await deleteReturn(selectedIds);
+ if (res.code === 200) {
+ ElMessage.success("鎵归噺鍒犻櫎鎴愬姛");
+ await loadReturnList();
+ } else {
+ ElMessage.error(res.msg || "鎵归噺鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error("鎵归噺鍒犻櫎澶辫触锛岃閲嶈瘯");
+ }
+ }).catch(() => {
+ ElMessage.info("宸插彇娑堝垹闄�");
+ });
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/documentationBorrowManagement/exportrevent", {}, "褰掕繕鐧昏.xlsx");
+ })
+ .catch(() => {
+ ElMessage.info("宸插彇娑�");
+ });
+};
+
+// 閫夋嫨鍙樺寲浜嬩欢
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 澶勭悊鍒嗛〉鍙樺寲
+const handlePagination = (current, size) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ loadReturnList();
+};
+// 澶勭悊鎵爜鍐呭
+const handleScanContent = async (value) => {
+ if (!value) return;
+
+ try {
+ // 璋冪敤API鏍规嵁涔︾睄ID鑾峰彇鍊熼槄淇℃伅
+ const res = await getBorrowListByDocumentationId(value);
+
+ if (res.code === 200 && res.data && res.data.length > 0) {
+ // 淇濆瓨鑾峰彇鍒扮殑鍊熼槄淇℃伅鍒楄〃
+ borrowInfoList.value = res.data;
+
+ // 濡傛灉鍙湁涓�鏉¤褰曪紝鐩存帴閫夋嫨
+ if (res.data.length === 1) {
+ const borrowInfo = res.data[0];
+ returnForm.borrowId = borrowInfo.id;
+ returnForm.borrower = borrowInfo.borrower || borrowInfo.borrowerName || '';
+ returnForm.dueReturnDate = borrowInfo.dueReturnDate || borrowInfo.expectedReturnDate || '';
+ ElMessage.success(`宸查�夋嫨: ${borrowInfo.docName || borrowInfo.name}`);
+ } else {
+ // 濡傛灉鏈夊鏉¤褰曪紝鏄剧ず閫夋嫨鎻愮ず
+ ElMessage.success(`鎵惧埌 ${res.data.length} 鏉$浉鍏冲�熼槄璁板綍锛岃浠庝笅鎷夊垪琛ㄤ腑閫夋嫨`);
+ // 閲嶆柊鍔犺浇鏂囨。鍒楄〃锛屽寘鍚渶鏂扮殑鍊熼槄淇℃伅
+ await loadDocumentList();
+ }
+ } else {
+ // 鏈壘鍒板尮閰嶇殑鍊熼槄璁板綍
+ ElMessage.warning('鏈壘鍒板搴旂殑鍊熼槄璁板綍锛岃妫�鏌ユ壂鐮佸唴瀹规垨鎵嬪姩閫夋嫨');
+ }
+ } catch (error) {
+ ElMessage.error('鎵爜澶勭悊澶辫触锛岃閲嶈瘯');
+ console.error('鎵爜澶勭悊閿欒:', error);
+ }
+};
+// 澶勭悊鏂囨。閫夋嫨鍙樺寲
+// 澶勭悊鏂囨。閫夋嫨鍙樺寲
+const handleDocumentChange = (borrowId) => {
+ // 褰撲笅鎷夋閫夋嫨鏃讹紝娓呯┖鎵爜杈撳叆妗�
+ scanContent.value = '';
+
+ if (borrowId) {
+ // 浼樺厛浠庡�熼槄淇℃伅鍒楄〃涓煡鎵�
+ let selectedInfo;
+ if (borrowInfoList.value.length > 0) {
+ selectedInfo = borrowInfoList.value.find(info => info.id === borrowId);
+ }
+
+ // 濡傛灉鍊熼槄淇℃伅鍒楄〃涓病鏈夋壘鍒帮紝浠庢枃妗e垪琛ㄤ腑鏌ユ壘
+ if (!selectedInfo) {
+ selectedInfo = documentList.value.find(doc => doc.id === borrowId);
+ }
+
+ if (selectedInfo) {
+ // 鑷姩濉厖鍊熼槄浜哄拰搴斿綊杩樻棩鏈�
+ returnForm.borrower = selectedInfo.borrower || selectedInfo.borrowerName || '';
+ returnForm.dueReturnDate = selectedInfo.dueReturnDate || selectedInfo.expectedReturnDate || '';
+ }
+ } else {
+ // 娓呯┖鐩稿叧瀛楁
+ returnForm.borrower = '';
+ returnForm.dueReturnDate = '';
+ }
+};
+// const handleDocumentChange = (documentId) => {
+// // 褰撲笅鎷夋閫夋嫨鏃讹紝娓呯┖鎵爜杈撳叆妗�
+// scanContent.value = '';
+// if (documentId) {
+// // 鏍规嵁閫夋嫨鐨勬枃妗D锛屼粠鏂囨。鍒楄〃涓煡鎵惧搴旂殑鏂囨。淇℃伅
+// const selectedDoc = documentList.value.find(doc => doc.id === documentId);
+// if (selectedDoc) {
+// // 鑷姩濉厖鍊熼槄浜哄拰搴斿綊杩樻棩鏈�
+// returnForm.borrower = selectedDoc.borrower || selectedDoc.borrowerName || '';
+// returnForm.dueReturnDate = selectedDoc.dueReturnDate || selectedDoc.expectedReturnDate || '';
+// }
+// } else {
+// // 娓呯┖鐩稿叧瀛楁
+// returnForm.borrower = '';
+// returnForm.dueReturnDate = '';
+// }
+// };
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ initData();
+});
+</script>
+
+<style scoped>
+.return-view {
+ padding: 20px;
+}
+
+.search-container {
+ background: #ffffff;
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.search-form {
+ margin: 0;
+}
+
+.table-container {
+ background: #ffffff;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.empty-data {
+ text-align: center;
+ color: #909399;
+ padding: 40px;
+ font-size: 14px;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+:deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #303133;
+}
+
+:deep(.el-input__wrapper) {
+ box-shadow: 0 0 0 1px #dcdfe6 inset;
+}
+
+:deep(.el-input__wrapper:hover) {
+ box-shadow: 0 0 0 1px #c0c4cc inset;
+}
+
+:deep(.el-input__wrapper.is-focus) {
+ box-shadow: 0 0 0 1px #409eff inset;
+}
+</style>
diff --git a/src/views/fileManagement/statistics/index.vue b/src/views/fileManagement/statistics/index.vue
new file mode 100644
index 0000000..42b81e4
--- /dev/null
+++ b/src/views/fileManagement/statistics/index.vue
@@ -0,0 +1,539 @@
+<template>
+ <div class="app-container statistics-container">
+
+ <!-- 鎬讳綋缁熻鍗$墖 -->
+ <el-row :gutter="20" class="statistics-cards">
+ <el-col :span="6" v-for="(item, index) in overviewData" :key="index">
+ <el-card class="statistics-card" :class="item.type">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon :size="32">
+ <component :is="item.icon" />
+ </el-icon>
+ </div>
+ <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 }}</span>
+ </div>
+ <div class="card-label">{{ item.label }}</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <el-row :gutter="20" class="charts-section">
+ <el-col :span="12">
+ <el-card class="chart-card">
+ <template #header>
+ <div class="card-header">
+ <span>妗f鍒嗙被缁熻</span>
+ </div>
+ </template>
+ <div class="chart-container">
+ <div ref="categoryChartRef" class="chart"></div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="12">
+ <el-card class="chart-card">
+ <template #header>
+ <div class="card-header">
+ <span>妗f鐘舵�佺粺璁�</span>
+ </div>
+ </template>
+ <div class="chart-container">
+ <div ref="statusChartRef" class="chart"></div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, nextTick, onUnmounted } from "vue";
+import { ElMessage } from "element-plus";
+import { Refresh } from "@element-plus/icons-vue";
+import * as echarts from "echarts";
+import {
+ getDocumentationOverview,
+ getDocumentationCategoryStats,
+ getDocumentationStatusStats
+} from "@/api/fileManagement/document";
+import {
+ Document,
+ Folder,
+ Tickets,
+ Calendar
+} from "@element-plus/icons-vue";
+
+// 鍝嶅簲寮忔暟鎹�
+const overviewData = ref([
+ {
+ label: "鎬绘。妗堟暟",
+ value: 0,
+ icon: "Document",
+ type: "primary",
+ },
+ {
+ label: "鍒嗙被鏁伴噺",
+ value: 0,
+ icon: "Folder",
+ type: "success",
+ },
+ {
+ label: "鍊熷嚭妗f",
+ value: 0,
+ icon: "Tickets",
+ type: "warning",
+ },
+ {
+ label: "鏈湀鏂板",
+ value: 0,
+ icon: "Calendar",
+ type: "info",
+ },
+]);
+
+const categoryChartRef = ref(null);
+const statusChartRef = ref(null);
+
+// 鍥捐〃瀹炰緥
+let categoryChart = null;
+let statusChart = null;
+
+// 鍔犺浇鐘舵��
+const loading = ref(false);
+const autoRefreshInterval = ref(null);
+
+// 鑷姩鍒锋柊寮�鍏�
+const autoRefreshEnabled = ref(true);
+
+// 鑷姩鍒锋柊闂撮殧锛�5鍒嗛挓锛�
+const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000;
+
+// 鍚姩鑷姩鍒锋柊
+const startAutoRefresh = () => {
+ if (autoRefreshInterval.value) {
+ clearInterval(autoRefreshInterval.value);
+ }
+ if (autoRefreshEnabled.value) {
+ autoRefreshInterval.value = setInterval(() => {
+ refreshData();
+ }, AUTO_REFRESH_INTERVAL);
+ }
+};
+
+// 鍋滄鑷姩鍒锋柊
+const stopAutoRefresh = () => {
+ if (autoRefreshInterval.value) {
+ clearInterval(autoRefreshInterval.value);
+ autoRefreshInterval.value = null;
+ }
+};
+
+// 鍒囨崲鑷姩鍒锋柊鐘舵��
+const toggleAutoRefresh = (value) => {
+ if (value) {
+ startAutoRefresh();
+ } else {
+ stopAutoRefresh();
+ }
+};
+
+// 鍔犺浇鎬讳綋缁熻鏁版嵁
+const loadOverviewData = async () => {
+ try {
+ const response = await getDocumentationOverview();
+ if (response.code === 200) {
+ const data = response.data;
+ overviewData.value[0].value = data.totalDocsCount || 0;
+ overviewData.value[1].value = data.categoryNumCount || 0;
+ overviewData.value[2].value = data.borrowedDocsCount || 0;
+ overviewData.value[3].value = data.monthlyAddedDocsCount || 0;
+ }
+ } catch (error) {
+ console.error('鍔犺浇鎬讳綋缁熻鏁版嵁澶辫触:', error);
+ ElMessage.error('鍔犺浇鎬讳綋缁熻鏁版嵁澶辫触');
+ }
+};
+
+// 鍔犺浇鍒嗙被缁熻鏁版嵁
+const loadCategoryData = async () => {
+ try {
+ const response = await getDocumentationCategoryStats();
+ if (response.code === 200) {
+ renderCategoryChart(response.data);
+ }
+ } catch (error) {
+ console.error('鍔犺浇鍒嗙被缁熻鏁版嵁澶辫触:', error);
+ ElMessage.error('鍔犺浇鍒嗙被缁熻鏁版嵁澶辫触');
+ }
+};
+
+// 鍔犺浇鐘舵�佺粺璁℃暟鎹�
+const loadStatusData = async () => {
+ try {
+ const response = await getDocumentationStatusStats();
+ if (response.code === 200) {
+ renderStatusChart(response.data);
+ }
+ } catch (error) {
+ console.error('鍔犺浇鐘舵�佺粺璁℃暟鎹け璐�:', error);
+ ElMessage.error('鍔犺浇鐘舵�佺粺璁℃暟鎹け璐�');
+ }
+};
+
+// 鍒锋柊鏁版嵁
+const refreshData = async () => {
+ loading.value = true;
+ try {
+ await Promise.all([
+ loadOverviewData(),
+ loadCategoryData(),
+ loadStatusData()
+ ]);
+ ElMessage.success('鏁版嵁鍒锋柊鎴愬姛');
+ } catch (error) {
+ console.error('鍒锋柊鏁版嵁澶辫触:', error);
+ ElMessage.error('鍒锋柊鏁版嵁澶辫触');
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM鍏冪礌宸茬粡娓叉煋
+ setTimeout(() => {
+ if (categoryChartRef.value) {
+ categoryChart = echarts.init(categoryChartRef.value);
+ }
+
+ if (statusChartRef.value) {
+ statusChart = echarts.init(statusChartRef.value);
+ }
+
+ // 鍒濆鍖栧畬鎴愬悗鍔犺浇鏁版嵁
+ loadCategoryData();
+ loadStatusData();
+ }, 300);
+};
+
+// 娓叉煋鍒嗙被缁熻鍥捐〃
+const renderCategoryChart = (data) => {
+ if (!categoryChart) return;
+ let newData = data.map(item => {
+ return {
+ name: item.category,
+ value: item.count
+ }
+ })
+
+ const option = {
+ title: {
+ text: "妗f鍒嗙被鍒嗗竷",
+ left: "center",
+ textStyle: {
+ fontSize: 16,
+ fontWeight: "normal",
+ },
+ },
+ tooltip: {
+ trigger: "item",
+ formatter: "{a} <br/>{b}: {c} ({d}%)",
+ },
+ legend: {
+ orient: "vertical",
+ left: "left",
+ top: "middle",
+ },
+ series: [
+ {
+ name: "妗f鏁伴噺",
+ type: "pie",
+ radius: ["40%", "70%"],
+ center: ["60%", "50%"],
+ data: newData || [
+ { name: "鎶�鏈枃妗�", value: 450 },
+ { name: "绠$悊鏂囨。", value: 320 },
+ { name: "璐㈠姟鏂囨。", value: 280 },
+ { name: "浜轰簨鏂囨。", value: 200 },
+ ],
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: "rgba(0, 0, 0, 0.5)",
+ },
+ },
+ },
+ ],
+ };
+
+ try {
+ categoryChart.setOption(option);
+ } catch (error) {
+ console.error('鍒嗙被鍥捐〃娓叉煋澶辫触:', error);
+ }
+};
+
+// 娓叉煋鐘舵�佺粺璁″浘琛�
+const renderStatusChart = (data) => {
+ if (!statusChart) return;
+ let newData = data.map(item => {
+ return {
+ name: item.docStatus,
+ value: item.count
+ }
+ })
+ const option = {
+ title: {
+ text: "妗f鐘舵�佸垎甯�",
+ left: "center",
+ textStyle: {
+ fontSize: 16,
+ fontWeight: "normal",
+ },
+ },
+ tooltip: {
+ trigger: "item",
+ formatter: "{a} <br/>{b}: {c} ({d}%)",
+ },
+ legend: {
+ orient: "vertical",
+ left: "left",
+ top: "middle",
+ },
+ series: [
+ {
+ name: "妗f鏁伴噺",
+ type: "pie",
+ radius: ["40%", "70%"],
+ center: ["60%", "50%"],
+ roseType: false,
+ data: newData || [
+ { name: "姝e父", value: 1150, itemStyle: { color: "#67C23A" } },
+ { name: "鍊熷嚭", value: 89, itemStyle: { color: "#E6A23C" } },
+ { name: "涓㈠け", value: 8, itemStyle: { color: "#F56C6C" } },
+ { name: "鎹熷潖", value: 4, itemStyle: { color: "#909399" } },
+ ],
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: "rgba(0, 0, 0, 0.5)",
+ },
+ },
+ },
+ ],
+ };
+
+ try {
+ statusChart.setOption(option);
+ } catch (error) {
+ console.error('鐘舵�佸浘琛ㄦ覆鏌撳け璐�:', error);
+ }
+};
+
+onMounted(() => {
+ loadOverviewData();
+ initCharts();
+ startAutoRefresh();
+});
+
+// 缁勪欢鍗歌浇鏃舵竻鐞嗗畾鏃跺櫒
+onUnmounted(() => {
+ stopAutoRefresh();
+});
+</script>
+
+<style scoped>
+.statistics-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: 30px;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 12px;
+ color: white;
+}
+
+.page-header h2 {
+ color: white;
+ margin-bottom: 10px;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.page-header p {
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 14px;
+ margin: 0 0 15px 0;
+}
+
+.header-controls {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 10px;
+ gap: 20px;
+}
+
+.refresh-btn {
+ margin-left: 20px;
+}
+
+.statistics-cards {
+ margin-bottom: 30px;
+}
+
+.statistics-card {
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s ease;
+ border: none;
+ overflow: hidden;
+}
+
+.statistics-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.statistics-card.primary {
+ border-left: 4px solid #409EFF;
+ background: linear-gradient(135deg, #409EFF 0%, #36a3f7 100%);
+}
+
+.statistics-card.success {
+ border-left: 4px solid #67C23A;
+ background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%);
+}
+
+.statistics-card.warning {
+ border-left: 4px solid #E6A23C;
+ background: linear-gradient(135deg, #E6A23C 0%, #ebb563 100%);
+}
+
+.statistics-card.info {
+ border-left: 4px solid #909399;
+ background: linear-gradient(135deg, #909399 0%, #a6a9ad 100%);
+}
+
+.card-content {
+ display: flex;
+ align-items: center;
+ padding: 20px;
+}
+
+.card-icon {
+ margin-right: 20px;
+ color: white;
+}
+
+.card-info {
+ flex: 1;
+}
+
+.card-number {
+ font-size: 32px;
+ font-weight: 600;
+ color: white;
+ margin-bottom: 5px;
+}
+
+.card-label {
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.charts-section {
+ margin-bottom: 30px;
+}
+
+.chart-card {
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ border: none;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: 600;
+ color: #303133;
+ padding: 15px 20px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.chart-container {
+ height: 400px;
+ padding: 20px;
+}
+
+.chart {
+ width: 100%;
+ height: 100%;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .statistics-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;
+ }
+
+ .statistics-cards .el-col {
+ margin-bottom: 15px;
+ }
+
+ .charts-section .el-col {
+ margin-bottom: 20px;
+ }
+
+ .chart-container {
+ height: 300px;
+ }
+}
+
+@media (max-width: 480px) {
+ .page-header h2 {
+ font-size: 20px;
+ }
+
+ .card-number {
+ font-size: 24px;
+ }
+
+ .chart-container {
+ height: 250px;
+ }
+}
+</style>
diff --git a/src/views/financialManagement/accounting/index.vue b/src/views/financialManagement/accounting/index.vue
index 91588fd..40bf1ae 100644
--- a/src/views/financialManagement/accounting/index.vue
+++ b/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>
@@ -142,20 +162,27 @@
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
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銆乧ountData 鍜� 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));
}
}
diff --git a/src/views/financialManagement/expenseManagement/Form.vue b/src/views/financialManagement/expenseManagement/Form.vue
deleted file mode 100644
index 9cfe5da..0000000
--- a/src/views/financialManagement/expenseManagement/Form.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<template>
- <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>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getAccountExpense } from "@/api/financialManagement/expenseManagement";
-import {ref} from "vue";
-const { proxy } = getCurrentInstance();
-
-
-defineOptions({
- name: "鏂板鏀嚭",
-});
-const { expense_types } = proxy.useDict("expense_types");
-const { checkout_payment } = proxy.useDict("checkout_payment");
-const formRef = ref(null);
-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 loadForm = async (id) => {
- const { code, data } = await getAccountExpense(id);
- 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;
- }
-};
-
-// 娓呴櫎琛ㄥ崟鏍¢獙鐘舵��
-const clearValidate = () => {
- formRef.value?.clearValidate();
-};
-
-// 閲嶇疆琛ㄥ崟鏁版嵁鍜屾牎楠岀姸鎬�
-const resetFormAndValidate = () => {
- resetForm();
- clearValidate();
-};
-
-defineExpose({
- form,
- loadForm,
- resetForm,
- clearValidate,
- resetFormAndValidate,
- formRef,
-});
-</script>
diff --git a/src/views/financialManagement/expenseManagement/Modal.vue b/src/views/financialManagement/expenseManagement/Modal.vue
index 8e5b171..4d743c1 100644
--- a/src/views/financialManagement/expenseManagement/Modal.vue
+++ b/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({
diff --git a/src/views/financialManagement/expenseManagement/index.vue b/src/views/financialManagement/expenseManagement/index.vue
index a45c32d..801fa1f 100644
--- a/src/views/financialManagement/expenseManagement/index.vue
+++ b/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(() => {
diff --git a/src/views/financialManagement/financialStatements/index.vue b/src/views/financialManagement/financialStatements/index.vue
index e5f9b23..cf4eee5 100644
--- a/src/views/financialManagement/financialStatements/index.vue
+++ b/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;"
@@ -28,109 +28,141 @@
<main class="container mx-auto px-4 pb-10">
<!-- 璐㈠姟鎸囨爣鍗$墖 -->
- <div class="grid-container">
- <!-- 鎬绘敹鍏� -->
- <el-card class="bg1">
- <p>鎬绘敹鍏�</p>
- <h3>
- 楼{{ pageInfo.totalIncome }}
- </h3>
- </el-card>
-
- <!-- 鏀跺叆绗旀暟 -->
- <el-card class="bg2">
- <p>鏀跺叆绗旀暟</p>
- <h3>
- {{ pageInfo.incomeNumber }}
- </h3>
- </el-card>
+ <div class="stats-cards">
+ <!-- 鎬昏惀鏀� -->
+ <div class="stat-card stat-card-blue">
+ <div class="stat-icon">
+ <img src="@/assets/icons/png/walletBlue@2x.png" alt="鎬昏惀鏀�" />
+ </div>
+ <div class="stat-content">
+ <div class="stat-label">鎬昏惀鏀�</div>
+ <div class="stat-value">{{ formatMoney(pageInfo.totalIncome || 0) }} 鍏�</div>
+ </div>
+ </div>
<!-- 鎬绘敮鍑� -->
- <el-card class="bg3">
- <p>鎬绘敮鍑�</p>
- <h3>
- 楼{{ pageInfo.totalExpense }}
- </h3>
- </el-card>
+ <div class="stat-card stat-card-orange">
+ <div class="stat-icon">
+ <img src="@/assets/icons/png/walletOrange@2x.png" alt="鎬绘敮鍑�" />
+ </div>
+ <div class="stat-content">
+ <div class="stat-label">鎬绘敮鍑�</div>
+ <div class="stat-value">{{ formatMoney(pageInfo.totalExpense || 0) }} 鍏�</div>
+ </div>
+ </div>
- <!-- 鏀嚭绗旀暟 -->
- <el-card class="bg4">
- <p>鏀嚭绗旀暟</p>
- <h3>
- {{ pageInfo.expenseNumber }}
- </h3>
- </el-card>
+ <!-- 鎬绘敹鍏ョ瑪鏁� -->
+ <div class="stat-card stat-card-green">
+ <div class="stat-icon">
+ <img src="@/assets/icons/png/walletGreen@2x.png" alt="鎬绘敹鍏ョ瑪鏁�" />
+ </div>
+ <div class="stat-content">
+ <div class="stat-label">鎬绘敹鍏ョ瑪鏁�</div>
+ <div class="stat-value">{{ pageInfo.incomeNumber || 0 }} 绗�</div>
+ </div>
+ </div>
+
+ <!-- 鎬绘敮鍑虹瑪鏁� -->
+ <div class="stat-card stat-card-red">
+ <div class="stat-icon">
+ <img src="@/assets/icons/png/walletRed@2x.png" alt="鎬绘敮鍑虹瑪鏁�" />
+ </div>
+ <div class="stat-content">
+ <div class="stat-label">鎬绘敮鍑虹瑪鏁�</div>
+ <div class="stat-value">{{ pageInfo.expenseNumber || 0 }} 绗�</div>
+ </div>
+ </div>
<!-- 鍑�鏀跺叆 -->
- <el-card class="bg5">
- <p>鍑�鏀跺叆</p>
- <h3>
- 楼{{ pageInfo.netRevenue }}
- </h3>
+ <div class="stat-card stat-card-yellow">
+ <div class="stat-icon">
+ <img src="@/assets/icons/png/walletYellow@2x.png" alt="鍑�鏀跺叆" />
+ </div>
+ <div class="stat-content">
+ <div class="stat-label">鍑�鏀跺叆</div>
+ <div class="stat-value">{{ formatMoney(pageInfo.netRevenue || 0) }} 鍏�</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 涓棿鍥捐〃鍖哄煙 -->
+ <div class="charts-row">
+ <!-- 宸︿晶锛氭敹鍏ユ敮鍑哄垎鏋� -->
+ <el-card class="chart-card">
+ <h2 class="section-title">鏀跺叆鏀嚭鍒嗘瀽</h2>
+ <div class="pie-chart-container">
+ <Echarts
+ :legend="pieLegendIncomeExpense"
+ :chartStyle="chartStylePie"
+ :series="pieSeriesIncomeExpense"
+ :tooltip="pieTooltipIncomeExpense"
+ style="height: 320px; width: 100%;">
+ </Echarts>
+ <div class="pie-stats">
+ <div class="bar-stat-item">
+ <span class="bar-stat-label">鏀跺叆鏁伴噺</span>
+ <span class="bar-stat-value">{{ pageInfo.incomeNumber || 0 }}</span>
+ </div>
+ <div class="bar-stat-item">
+ <span class="bar-stat-label">鏀嚭鏁伴噺</span>
+ <span class="bar-stat-value">{{ pageInfo.expenseNumber || 0 }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+
+ <!-- 鍙充晶锛氳椤圭泩鍒╁垎鏋� -->
+ <el-card class="chart-card">
+ <h2 class="section-title">琛岄」鐩堝埄鍒嗘瀽</h2>
+ <div class="bar-chart-header">
+ <div class="bar-stat-item">
+ <span class="bar-stat-label">褰撳墠鎬讳釜鏁�</span>
+ <span class="bar-stat-value">{{ allBarTypes.value?.length || 0 }}</span>
+ </div>
+ <div class="bar-stat-item">
+ <span class="bar-stat-label">鏀嚭閲戦</span>
+ <span class="bar-stat-value">{{ formatMoney(pageInfo.totalExpense || 0) }}</span>
+ </div>
+ <div class="bar-stat-item">
+ <span class="bar-stat-label">鏀跺叆閲戦</span>
+ <span class="bar-stat-value">{{ formatMoney(pageInfo.totalIncome || 0) }}</span>
+ </div>
+ </div>
+ <Echarts
+ ref="barChart"
+ :chartStyle="chartStyle"
+ :grid="barGrid"
+ :legend="barLegend"
+ :series="barSeries"
+ :tooltip="barTooltip"
+ :xAxis="barXAxis"
+ :yAxis="barYAxis"
+ style="height: 300px; width: 100%;">
+ </Echarts>
</el-card>
</div>
- <!-- 鏀跺叆缁熻鍥捐〃 -->
- <div class="grid-layout">
- <el-card style="margin-bottom: 20px;">
- <h2 class="section-title">鏀跺叆缁熻(鍏�)</h2>
- <div class="echarts">
- <Echarts :legend="pieLegend0" :chartStyle="chartStylePie"
- :series="materialPieSeries0"
- :tooltip="pieTooltip" 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;">{{ pageInfo.totalIncome }}</span>
- </div>
- </Echarts>
- <Echarts ref="chart"
- :chartStyle="chartStyle"
- :grid="grid"
- :legend="lineLegend"
- :series="lineSeries0"
- :tooltip="tooltip"
- :xAxis="xAxis0"
- :yAxis="yAxis0"
- style="height: 260px;width: 64%;"></Echarts>
- </div>
- </el-card>
-
- <!-- 鏀嚭缁熻鍥捐〃 -->
- <el-card>
- <h2 class="section-title">鏀嚭缁熻(鍏�)</h2>
- <div class="echarts">
- <Echarts ref="chart"
- :legend="pieLegend1"
- :chartStyle="chartStylePie"
- :series="materialPieSeries1"
- :tooltip="pieTooltip"
- 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;">{{ pageInfo.totalExpense }}</span>
- </div></Echarts>
- <Echarts ref="chart"
- :chartStyle="chartStyle"
- :grid="grid"
- :legend="lineLegend"
- :series="lineSeries1"
- :tooltip="tooltip"
- :xAxis="xAxis1"
- :yAxis="yAxis1"
- style="height: 260px;width: 64%;"></Echarts>
- </div>
- </el-card>
- </div>
+ <!-- 搴曢儴锛氳惀鏀惰秼鍔垮垎鏋� -->
+ <el-card class="trend-chart-card">
+ <h2 class="section-title">钀ユ敹瓒嬪娍鍒嗘瀽</h2>
+ <Echarts
+ ref="trendChart"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="trendLegend"
+ :series="trendSeries"
+ :tooltip="tooltip"
+ :xAxis="xAxis0"
+ :yAxis="trendYAxis"
+ style="height: 350px; width: 100%;">
+ </Echarts>
+ </el-card>
</main>
</div>
</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 +170,7 @@
// 鏃ユ湡鑼冨洿
const dateRange = ref(null);
+const { proxy } = getCurrentInstance();
const chartStyle = {
width: '100%',
height: '100%', // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
@@ -172,22 +205,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 +278,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 +297,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 +324,7 @@
label: {
show: false
},
- data: pieData0.value,
+ data: (pieData0.value || []).filter(item => item && item.name),
color: pieColors
}
]);
@@ -293,7 +341,7 @@
label: {
show: false
},
- data: pieData1.value,
+ data: (pieData1.value || []).filter(item => item && item.name),
color: pieColors
}
]);
@@ -318,53 +366,337 @@
const pageInfo = ref({
})
+// 鏍煎紡鍖栭噾棰�
+const formatMoney = (value) => {
+ if (!value && value !== 0) return '0';
+ return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+};
+
+// 鏀跺叆鏀嚭鍒嗘瀽楗煎浘
+const pieDataIncomeExpense = computed(() => {
+ const totalIncome = Number(pageInfo.value.totalIncome) || 0;
+ const totalExpense = Number(pageInfo.value.totalExpense) || 0;
+ const total = totalIncome + totalExpense;
+ if (total === 0) {
+ return [
+ { name: '鏀跺叆', value: 0, percent: '0%' },
+ { name: '鏀嚭', value: 0, percent: '0%' }
+ ];
+ }
+ const incomePercent = ((totalIncome / total) * 100).toFixed(0);
+ const expensePercent = ((totalExpense / total) * 100).toFixed(0);
+ return [
+ { name: '鏀跺叆', value: totalIncome, percent: `${incomePercent}%` },
+ { name: '鏀嚭', value: totalExpense, percent: `${expensePercent}%` }
+ ];
+});
+
+const pieLegendIncomeExpense = computed(() => ({
+ show: false
+}));
+
+const pieTooltipIncomeExpense = reactive({
+ trigger: 'item',
+ formatter: function(params) {
+ if (!params.data) return params.name;
+ return `${params.name}鍗犳瘮 ${params.percent}%`;
+ }
+});
+
+const pieSeriesIncomeExpense = computed(() => [
+ {
+ type: 'pie',
+ radius: ['0%', '70%'],
+ center: ['50%', '50%'],
+ avoidLabelOverlap: true,
+ itemStyle: {
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: true,
+ position: 'outside',
+ formatter: function(params) {
+ return `${params.name}鍗犳瘮 ${params.percent}%`;
+ },
+ fontSize: 14,
+ color: '#333'
+ },
+ labelLine: {
+ show: true,
+ length: 15,
+ length2: 10,
+ lineStyle: {
+ color: '#333'
+ }
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: 16,
+ fontWeight: 'bold'
+ }
+ },
+ data: pieDataIncomeExpense.value,
+ color: ['#1890FF', '#FACC14']
+ }
+]);
+
+// 琛岄」鐩堝埄鍒嗘瀽鏌辩姸鍥�
+const barXAxis = computed(() => {
+ return [{
+ type: 'category',
+ data: (allBarTypes.value && allBarTypes.value.length > 0) ? allBarTypes.value : ['椤圭洰1', '椤圭洰2', '椤圭洰3', '椤圭洰4', '椤圭洰5', '椤圭洰6', '椤圭洰7'],
+ axisTick: { show: true, alignWithLabel: true },
+ }];
+});
+
+const barYAxis = [{
+ type: 'value',
+ name: '鍗曚綅: 鍏�',
+ position: 'left',
+ min: 0,
+ nameTextStyle: {
+ color: '#000',
+ fontSize: 14,
+ },
+}];
+
+const barGrid = {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+};
+
+const barLegend = {
+ show: true,
+ top: 10,
+ right: 10,
+};
+
+// 鑾峰彇鎵�鏈夌被鍨嬪悕绉�
+const allBarTypes = computed(() => {
+ const incomeTypes = (lineSeries0.value || []).map(item => item.name || item.typeName).filter(Boolean);
+ const expenseTypes = (lineSeries1.value || []).map(item => item.name || item.typeName).filter(Boolean);
+ return [...new Set([...incomeTypes, ...expenseTypes])];
+});
+
+const barSeries = computed(() => {
+ if (allBarTypes.value.length === 0) {
+ return [
+ {
+ name: '鏀嚭',
+ type: 'bar',
+ data: [],
+ itemStyle: { color: '#1890FF' }
+ },
+ {
+ name: '鏀跺叆',
+ type: 'bar',
+ data: [],
+ itemStyle: { color: '#13C2C2' }
+ }
+ ];
+ }
+
+ // 璁$畻姣忎釜椤圭洰鐨勬�绘敹鍏ワ紙姹囨�绘墍鏈夋湀浠斤級
+ const incomeData = allBarTypes.value.map(typeName => {
+ const incomeItem = (lineSeries0.value || []).find(item => (item.name || item.typeName) === typeName);
+ if (incomeItem && incomeItem.data && Array.isArray(incomeItem.data)) {
+ return incomeItem.data.reduce((sum, val) => sum + (Number(val) || 0), 0);
+ }
+ return 0;
+ });
+
+ // 璁$畻姣忎釜椤圭洰鐨勬�绘敮鍑猴紙姹囨�绘墍鏈夋湀浠斤級
+ const expenseData = allBarTypes.value.map(typeName => {
+ const expenseItem = (lineSeries1.value || []).find(item => (item.name || item.typeName) === typeName);
+ if (expenseItem && expenseItem.data && Array.isArray(expenseItem.data)) {
+ return expenseItem.data.reduce((sum, val) => sum + (Number(val) || 0), 0);
+ }
+ return 0;
+ });
+
+ return [
+ {
+ name: '鏀嚭',
+ type: 'bar',
+ data: expenseData,
+ itemStyle: { color: '#1890FF' }
+ },
+ {
+ name: '鏀跺叆',
+ type: 'bar',
+ data: incomeData,
+ itemStyle: { color: '#13C2C2' }
+ }
+ ];
+});
+
+const barTooltip = reactive({
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ },
+ formatter: function (params) {
+ if (!params || !params.length) return '';
+ const axisLabel = params[0].axisValueLabel || params[0].axisValue || '';
+ const rows = params
+ .map(p => {
+ const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`;
+ const value = typeof p.value === 'number' ? p.value.toFixed(2) : p.value;
+ return `${colorDot}${p.seriesName} ${value}`;
+ })
+ .join('<br/>');
+ return `<div>${axisLabel}</div><div>${rows}</div>`;
+ }
+});
+
+// 钀ユ敹瓒嬪娍鍒嗘瀽
+const trendLegend = {
+ show: true,
+ top: 10,
+ right: 10,
+};
+
+const trendYAxis = [{
+ type: 'value',
+ name: '鍗曚綅: 鍏�',
+ position: 'left',
+ min: 0,
+ nameTextStyle: {
+ color: '#000',
+ fontSize: 14,
+ },
+}];
+
+const trendSeries = computed(() => {
+ // 姹囨�绘墍鏈夋敮鍑虹被鍨嬬殑鏁版嵁
+ let expenseTrend = [];
+ if (lineSeries1.value.length > 0) {
+ const monthCount = Math.max(...lineSeries1.value.map(item => item.data?.length || 0));
+ expenseTrend = Array(monthCount).fill(0);
+ lineSeries1.value.forEach(item => {
+ if (item.data && Array.isArray(item.data)) {
+ item.data.forEach((val, index) => {
+ if (index < monthCount) {
+ expenseTrend[index] += Number(val) || 0;
+ }
+ });
+ }
+ });
+ }
+
+ // 姹囨�绘墍鏈夋敹鍏ョ被鍨嬬殑鏁版嵁
+ let incomeTrend = [];
+ if (lineSeries0.value.length > 0) {
+ const monthCount = Math.max(...lineSeries0.value.map(item => item.data?.length || 0));
+ incomeTrend = Array(monthCount).fill(0);
+ lineSeries0.value.forEach(item => {
+ if (item.data && Array.isArray(item.data)) {
+ item.data.forEach((val, index) => {
+ if (index < monthCount) {
+ incomeTrend[index] += Number(val) || 0;
+ }
+ });
+ }
+ });
+ }
+
+ return [
+ {
+ name: '鏀嚭',
+ type: 'line',
+ data: expenseTrend,
+ itemStyle: { color: '#1890FF' },
+ smooth: true
+ },
+ {
+ name: '鏀跺叆',
+ type: 'line',
+ data: incomeTrend,
+ itemStyle: { color: '#13C2C2' },
+ smooth: true
+ }
+ ];
+});
+
+// 鑾峰彇鏈�杩戝叚涓湀鐨勮寖鍥�
+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 +706,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>
@@ -397,111 +775,220 @@
:root {
--el-color-primary: #4f46e5;
}
-.el-card{
- position: relative;
- border-radius: 12px;
- padding: 14px 10px 10px 10px;
- box-shadow: 0 2px 8px #eee;
- :deep(.el-card__body){
- padding: 10px 20px !important;
- }
- &.bg1{
- background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
- }
- &.bg2{
- background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
- }
- &.bg3{
- background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
- }
- &.bg4{
- background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
- }
- &.bg5{
- background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
- }
-}
-.grid-container {
- /* grid 瀹瑰櫒鍩虹鏍峰紡 */
+/* 缁熻鍗$墖鏍峰紡 */
+.stats-cards {
display: grid;
- gap: 1rem; /* gap-4 瀵瑰簲 1rem (16px) */
- margin-bottom: 2rem; /* mb-8 瀵瑰簲 2rem (32px) */
+ grid-template-columns: repeat(5, 1fr);
+ gap: 20px;
+ margin-bottom: 20px;
+}
+
+.stat-card {
+ background: #fff;
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ padding: 20px;
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ transition: all 0.3s;
- p{
- font-size: 22px;
- margin-top: 0px;
- color: #fff;
- }
- h3{
- font-size: 36px;
- font-weight: 500;
- font-family: 'MyCustomFont', sans-serif;
- margin: 10px 0;
- color: #fff;
+ &:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ transform: translateY(-2px);
}
-}
-
-/* 绉诲姩绔粯璁ゆ牱寮� (grid-cols-1) */
-.grid-container {
- grid-template-columns: repeat(1, minmax(0, 1fr));
-}
-
-/* 灏忓睆骞曞強浠ヤ笂 (sm:grid-cols-2) */
-@media (min-width: 640px) {
- .grid-container {
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ .stat-icon {
+ width: 48px;
+ height: 48px;
+ flex-shrink: 0;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ }
+ }
+
+ .stat-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .stat-label {
+ font-size: 14px;
+ color: #666;
+ line-height: 1.2;
+ }
+
+ .stat-value {
+ font-size: 24px;
+ font-weight: 600;
+ color: #333;
+ line-height: 1.2;
+ }
+
+ .stat-trend {
+ font-size: 12px;
+ line-height: 1.2;
+
+ &.trend-up {
+ color: #f56c6c;
+ }
+
+ &.trend-down {
+ color: #67c23a;
+ }
}
}
-/* 澶у睆骞曞強浠ヤ笂 (lg:grid-cols-5) */
-@media (min-width: 1024px) {
- .grid-container {
- grid-template-columns: repeat(5, minmax(0, 1fr));
+/* 鍥捐〃琛屽竷灞� */
+.charts-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+ margin-bottom: 20px;
+}
+
+.chart-card {
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+
+ :deep(.el-card__body) {
+ padding: 20px !important;
}
}
-/* 鍗$墖鎮仠鏁堟灉澧炲己 */
-.el-card:hover {
- transform: translateY(-2px);
+.trend-chart-card {
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+
+ :deep(.el-card__body) {
+ padding: 20px !important;
+ }
}
-.echarts{
+
+/* 楗煎浘瀹瑰櫒 */
+.pie-chart-container {
+ position: relative;
+
+ .pie-stats {
+ display: flex;
+ justify-content: space-between;
+ gap: 20px;
+ margin-top: 20px;
+
+ .bar-stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ padding: 15px;
+ background: #f5f7fa;
+ border-radius: 6px;
+ flex: 1;
+
+ .bar-stat-label {
+ font-size: 14px;
+ color: #666;
+ }
+
+ .bar-stat-value {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ }
+ }
+ }
+}
+
+/* 鏌辩姸鍥惧ご閮ㄧ粺璁� */
+.bar-chart-header {
display: flex;
justify-content: space-between;
+ gap: 20px;
+ margin-bottom: 20px;
+
+ .bar-stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ padding: 15px;
+ background: #f5f7fa;
+ border-radius: 6px;
+ flex: 1;
+
+ .bar-stat-label {
+ font-size: 14px;
+ color: #666;
+ }
+
+ .bar-stat-value {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ }
+ }
}
-/* 鍥捐〃瀹瑰櫒鏍峰紡 */
-.el-chart {
- width: 100%;
- height: 100%;
-}
+/* 鏍囬鏍峰紡 */
.section-title {
- position: relative;
- font-size: 18px;
- color: #333;
- padding-left: 10px;
- margin-bottom: 10px;
- font-weight: 700;
+ position: relative;
+ font-size: 18px;
+ color: #333;
+ padding-left: 12px;
+ margin-bottom: 20px;
+ font-weight: 700;
+
+ &::before {
+ position: absolute;
+ left: 0;
+ top: 2px;
+ content: '';
+ width: 4px;
+ height: 18px;
+ background-color: #002FA7;
+ border-radius: 2px;
+ }
}
-.section-title::before {
- position: absolute;
- left: 0;
- top: 0px;
- content: '';
- width: 4px;
- height: 18px;
- background-color: #002FA7;
- border-radius: 2px;
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 1400px) {
+ .stats-cards {
+ grid-template-columns: repeat(3, 1fr);
+ }
}
-.chart-num{
- position: absolute;
- z-index: 3;
- top: 92px;
- left: 92px;
- display: flex;
- flex-direction: column;
- justify-content: center;
+
+@media (max-width: 1024px) {
+ .stats-cards {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .charts-row {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 640px) {
+ .stats-cards {
+ grid-template-columns: 1fr;
+ }
}
</style>
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/financialManagement/inventoryAccounting/index.vue b/src/views/financialManagement/inventoryAccounting/index.vue
index cca6861..3acf5d9 100644
--- a/src/views/financialManagement/inventoryAccounting/index.vue
+++ b/src/views/financialManagement/inventoryAccounting/index.vue
@@ -121,7 +121,7 @@
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
-import {getStockInPage} from "@/api/inventoryManagement/stockIn.js";
+import {getStockInChartData, getStockInPage} from "@/api/inventoryManagement/stockIn.js";
// 鐘舵�佸彉閲�
const loading = ref(false)
diff --git a/src/views/financialManagement/loanManagement/Modal.vue b/src/views/financialManagement/loanManagement/Modal.vue
new file mode 100644
index 0000000..73b2cc3
--- /dev/null
+++ b/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 浠h〃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>
diff --git a/src/views/financialManagement/loanManagement/index.vue b/src/views/financialManagement/loanManagement/index.vue
new file mode 100644
index 0000000..7580d3b
--- /dev/null
+++ b/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>
diff --git a/src/views/financialManagement/revenueManagement/Form.vue b/src/views/financialManagement/revenueManagement/Form.vue
deleted file mode 100644
index 67b175e..0000000
--- a/src/views/financialManagement/revenueManagement/Form.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<template>
- <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>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getAccountIncome } from "@/api/financialManagement/revenueManagement";
-import {ref} from "vue";
-const { proxy } = getCurrentInstance();
-
-
-defineOptions({
- name: "鏂板鏀跺叆",
-});
-const { income_types } = proxy.useDict("income_types");
-const { payment_methods } = proxy.useDict("payment_methods");
-const formRef = ref(null);
-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 loadForm = async (id) => {
- const { code, data } = await getAccountIncome(id);
- 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;
- }
-};
-
-// 娓呴櫎琛ㄥ崟鏍¢獙鐘舵��
-const clearValidate = () => {
- formRef.value?.clearValidate();
-};
-
-// 閲嶇疆琛ㄥ崟鏁版嵁鍜屾牎楠岀姸鎬�
-const resetFormAndValidate = () => {
- resetForm();
- clearValidate();
-};
-
-defineExpose({
- form,
- loadForm,
- resetForm,
- clearValidate,
- resetFormAndValidate,
- formRef,
-});
-</script>
diff --git a/src/views/financialManagement/revenueManagement/Modal.vue b/src/views/financialManagement/revenueManagement/Modal.vue
index 480b4fd..245cdf2 100644
--- a/src/views/financialManagement/revenueManagement/Modal.vue
+++ b/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({
diff --git a/src/views/financialManagement/revenueManagement/index.vue b/src/views/financialManagement/revenueManagement/index.vue
index 9dcd23e..a8a59c8 100644
--- a/src/views/financialManagement/revenueManagement/index.vue
+++ b/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(() => {
diff --git a/src/views/index.vue b/src/views/index.vue
index e06a40b..2888b16 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -18,126 +18,126 @@
</div>
</div>
</div>
-<!-- <div class="data-cards">-->
-<!-- <div class="data-card sales">-->
-<!-- <div class="data-title">閿�鍞暟鎹�</div>-->
-<!-- <div class="data-num">-->
-<!-- <div>-->
-<!-- <div class="data-desc">鏈湀閿�鍞/鍏�</div>-->
-<!-- <div class="data-value">{{businessInfo.monthSaleMoney}}</div>-->
-<!-- </div>-->
-<!-- <div>-->
-<!-- <div class="data-desc">鏈紑绁ㄩ噾棰�/鍏�</div>-->
-<!-- <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- -->
-<!-- </div>-->
-<!-- <div class="data-card purchase">-->
-<!-- <div class="data-title">閲囪喘鏁版嵁</div>-->
-<!-- <div class="data-num">-->
-<!-- <div>-->
-<!-- <div class="data-desc">鏈湀閲囪喘棰�/鍏�</div>-->
-<!-- <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div>-->
-<!-- </div>-->
-<!-- <div>-->
-<!-- <div class="data-desc">寰呬粯娆鹃噾棰�/鍏�</div>-->
-<!-- <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- <div class="data-card inventory">-->
-<!-- <div class="data-title">搴撳瓨鏁版嵁</div>-->
-<!-- <div class="data-num">-->
-<!-- <div>-->
-<!-- <div class="data-desc">褰撳墠搴撳瓨鎬婚噺/浠�</div>-->
-<!-- <div class="data-value">{{businessInfo.inventoryNum}}</div>-->
-<!-- </div>-->
-<!-- <div>-->
-<!-- <div class="data-desc">浠婃棩鍏ュ簱/浠�</div>-->
-<!-- <div class="data-value">{{businessInfo.todayInventoryNum}}</div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </div>-->
+ <div class="data-cards">
+ <div class="data-card sales">
+ <div class="data-title">閿�鍞暟鎹�</div>
+ <div class="data-num">
+ <div>
+ <div class="data-desc">鏈湀閿�鍞/鍏�</div>
+ <div class="data-value">{{businessInfo.monthSaleMoney}}</div>
+ </div>
+ <div>
+ <div class="data-desc">鏈紑绁ㄩ噾棰�/鍏�</div>
+ <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div>
+ </div>
+ </div>
+
+ </div>
+ <div class="data-card purchase">
+ <div class="data-title">閲囪喘鏁版嵁</div>
+ <div class="data-num">
+ <div>
+ <div class="data-desc">鏈湀閲囪喘棰�/鍏�</div>
+ <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div>
+ </div>
+ <div>
+ <div class="data-desc">寰呬粯娆鹃噾棰�/鍏�</div>
+ <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div>
+ </div>
+ </div>
+ </div>
+ <div class="data-card inventory">
+ <div class="data-title">搴撳瓨鏁版嵁</div>
+ <div class="data-num">
+ <div>
+ <div class="data-desc">褰撳墠搴撳瓨鎬婚噺/浠�</div>
+ <div class="data-value">{{businessInfo.inventoryNum}}</div>
+ </div>
+ <div>
+ <div class="data-desc">浠婃棩鍏ュ簱/浠�</div>
+ <div class="data-value">{{businessInfo.todayInventoryNum}}</div>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
<!-- 鍙筹細寰呭姙浜嬮」 -->
-<!-- <div class="todo-panel">-->
-<!-- <div class="section-title">寰呭姙浜嬮」</div>-->
-<!-- <ul class="todo-list" v-if="todoList.length > 0">-->
-<!-- <li v-for="item in todoList" :key="item.id">-->
-<!-- <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">-->
-<!-- <div style="display: flex;justify-content: space-between;align-items: center;">-->
-<!-- <div class="todo-title">娴佺▼缂栧彿锛歿{item.approveId}}</div>-->
-<!-- <div class="todo-division">鐢宠閮ㄩ棬锛歿{item.approveDeptName}}</div>-->
-<!-- <div class="todo-time">{{item.approveTime}}</div>-->
-<!-- </div>-->
-<!-- <div class="todo-division">瀹℃壒浜嬬敱锛歿{item.approveReason}}</div>-->
-<!-- </div>-->
-<!-- </li>-->
-<!-- </ul>-->
-<!-- <div v-else style="text-align: center">-->
-<!-- 鏆傛棤鏁版嵁-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- -->
-<!-- <!– 涓儴妯悜涓ゆ爮 –>-->
-<!-- <div class="dashboard-row">-->
-<!-- <div class="main-panel">-->
-<!-- <div class="section-title">瀹㈡埛鍚堝悓閲戦鍒嗘瀽</div>-->
-<!-- <div class="contract-summary">-->
-<!-- <div class="contract-info">-->
-<!-- <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/>-->
-<!-- <div class="contract-card">-->
-<!-- <div class="contract-name">鎬诲悎鍚岄噾棰�(鍏�)</div>-->
-<!-- <div class="contract-meta">-->
-<!-- <div class="main-amount">{{sum}}</div>-->
-<!-- <div>鍛ㄥ悓姣�: <span class="up">{{yny}}% </span> 鏃ョ幆姣�: <span class="up">{{chain}}% </span></div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">-->
-<!-- <div>-->
-<!-- <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"-->
-<!-- :series="materialPieSeries"-->
-<!-- :tooltip="pieTooltip"></Echarts>-->
-<!-- </div>-->
-<!-- <ul class="contract-list">-->
-<!-- <li v-for="item in materialPieSeries[0].data" :key="item.name">-->
-<!-- <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">-->
-<!-- <div class="line" :style="{color: item.itemStyle.color}">鈼弡{item.name}}</div>-->
-<!-- <div style="width: 70px">{{item.rate}}%</div>-->
-<!-- <div>锟{item.value}}</div>-->
-<!-- </div>-->
-<!-- </li>-->
-<!-- </ul>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- <div class="main-panel">-->
-<!-- <div style="display: flex;justify-content: space-between;">-->
-<!-- <div class="section-title">搴旀敹搴斾粯缁熻</div>-->
-<!--<!– <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">–>-->
-<!--<!– <el-radio-button label="鎸夊懆" :value="1" />–>-->
-<!--<!– <el-radio-button label="鎸夋湀" :value="2" />–>-->
-<!--<!– <el-radio-button label="鎸夊搴�" :value="3" />–>-->
-<!--<!– </el-radio-group>–>-->
-<!-- </div>-->
-<!-- <Echarts ref="chart"-->
-<!-- :color="barColors2"-->
-<!-- :chartStyle="chartStyle"-->
-<!-- :grid="grid"-->
-<!-- :series="barSeries"-->
-<!-- :tooltip="tooltip"-->
-<!-- :xAxis="xAxis"-->
-<!-- :yAxis="yAxis"-->
-<!-- style="height: 260px"></Echarts>-->
-<!-- </div>-->
-<!-- </div>-->
+ <div class="todo-panel">
+ <div class="section-title">寰呭姙浜嬮」</div>
+ <ul class="todo-list" v-if="todoList.length > 0">
+ <li v-for="item in todoList" :key="item.id">
+ <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">
+ <div style="display: flex;justify-content: space-between;align-items: center;">
+ <div class="todo-title">寰呭姙缂栧彿锛歿{item.approveId}}</div>
+ <div class="todo-division">閮ㄩ棬锛歿{item.approveDeptName}}</div>
+ <div class="todo-time">{{item.approveTime}}</div>
+ </div>
+ <div class="todo-division">寰呭姙浜嬬敱锛歿{item.approveReason}}</div>
+ </div>
+ </li>
+ </ul>
+ <div v-else style="text-align: center">
+ 鏆傛棤鏁版嵁
+ </div>
+ </div>
+ </div>
+
+ <!-- 涓儴妯悜涓ゆ爮 -->
+ <div class="dashboard-row">
+ <div class="main-panel">
+ <div class="section-title">瀹㈡埛鍚堝悓閲戦鍒嗘瀽</div>
+ <div class="contract-summary">
+ <div class="contract-info">
+ <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/>
+ <div class="contract-card">
+ <div class="contract-name">鎬诲悎鍚岄噾棰�(鍏�)</div>
+ <div class="contract-meta">
+ <div class="main-amount">{{sum}}</div>
+ <div>鍛ㄥ悓姣�: <span class="up">{{yny}}% </span> 鏃ョ幆姣�: <span class="up">{{chain}}% </span></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">
+ <div>
+ <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"
+ :series="materialPieSeries"
+ :tooltip="pieTooltip"></Echarts>
+ </div>
+ <ul class="contract-list">
+ <li v-for="item in materialPieSeries[0].data" :key="item.name">
+ <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">
+ <div class="line" :style="{color: item.itemStyle.color}">鈼弡{item.name}}</div>
+ <div style="width: 70px">{{item.rate}}%</div>
+ <div>锟{item.value}}</div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="main-panel">
+ <div style="display: flex;justify-content: space-between;">
+ <div class="section-title">搴旀敹搴斾粯缁熻</div>
+ <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">
+ <el-radio-button label="鎸夊懆" :value="1" />
+ <el-radio-button label="鎸夋湀" :value="2" />
+ <el-radio-button label="鎸夊搴�" :value="3" />
+ </el-radio-group>
+ </div>
+ <Echarts ref="chart"
+ :color="barColors2"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :series="barSeries"
+ :tooltip="tooltip"
+ :xAxis="xAxis"
+ :yAxis="yAxis"
+ style="height: 260px"></Echarts>
+ </div>
+ </div>
<!-- 搴曢儴妯悜涓ゆ爮 -->
-<!-- <div class="dashboard-row">-->
+ <div class="dashboard-row">
<!-- <div class="main-panel">-->
<!-- <div class="section-title">璐ㄩ噺缁熻</div>-->
<!-- <div class="quality-cards">-->
@@ -155,11 +155,11 @@
<!-- :yAxis="yAxis1"-->
<!-- style="height: 260px"></Echarts>-->
<!-- </div>-->
-<!-- <div class="main-panel">-->
-<!-- <div class="section-title">鍥炴涓庡紑绁ㄥ垎鏋�</div>-->
-<!-- <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"-->
-<!-- :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts>-->
-<!-- </div>-->
+ <div class="main-panel">
+ <div class="section-title">鍥炴涓庡紑绁ㄥ垎鏋�</div>
+ <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"
+ :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts>
+ </div>
</div>
</div>
</template>
@@ -386,7 +386,6 @@
}
// 搴斾粯搴旀敹缁熻
const statisticsReceivable = (type) => {
- console.log(type)
statisticsReceivablePayable({type: radio1.value}).then((res) => {
barSeries.value[0].data = [
// { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } },
@@ -507,7 +506,7 @@
min-width: 0;
background-color: #EFF2FB; /* 浣跨敤鎸囧畾鐨勮儗鏅鑹� */
background-image: url("../assets/images/denglu.png");
- background-size: 100% 260%;
+ background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 12px;
diff --git a/src/views/inventoryManagement/dispatchLog/Record.vue b/src/views/inventoryManagement/dispatchLog/Record.vue
new file mode 100644
index 0000000..6fa29c8
--- /dev/null
+++ b/src/views/inventoryManagement/dispatchLog/Record.vue
@@ -0,0 +1,711 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <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-select v-model="searchForm.recordType"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨"
+ clearable>
+ <el-option v-for="item in stockRecordTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ </div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ <el-button type="primary" plain @click="handlePrint">鎵撳嵃</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"
+ style="width: 100%"
+ height="calc(100vh - 18.5em)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column
+ label="鍑哄簱鎵规"
+ prop="outboundBatches"
+ min-width="100"
+ 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="stockOutNum"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍑哄簱浜�"
+ prop="createBy"
+ show-overflow-tooltip
+ />
+ <el-table-column label="鏉ユ簮"
+ prop="recordType"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ getRecordType(scope.row.recordType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+ </div>
+</template>
+
+<script setup>
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { ref } from "vue";
+import { ElMessageBox } from "element-plus";
+import useUserStore from "@/store/modules/user";
+import { getCurrentDate } from "@/utils/index.js";
+import {
+ getStockOutPage,
+ delStockOut,
+} from "@/api/inventoryManagement/stockOut.js";
+import {
+ findAllQualifiedStockRecordTypeOptions,
+ findAllStockRecordTypeOptions,
+ findAllUnqualifiedStockRecordTypeOptions
+} from "@/api/basicData/enum.js";
+
+const userStore = useUserStore();
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+// 鏉ユ簮绫诲瀷閫夐」
+const stockRecordTypeOptions = ref([]);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+
+const props = defineProps({
+ type: {
+ type: String,
+ required: true,
+ default: '0'
+ }
+})
+
+// 鎵撳嵃鐩稿叧
+const printPreviewVisible = ref(false);
+const printData = ref([]);
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const data = reactive({
+ searchForm: {
+ supplierName: "",
+ timeStr: "",
+ recordType: "",
+ }
+});
+const { searchForm } = toRefs(data);
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ getStockOutPage({ ...searchForm.value, ...page, type: props.type })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ tableData.value.map((item) => {
+ item.children = [];
+ });
+ total.value = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
+
+const getRecordType = (recordType) => {
+ return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
+const fetchStockRecordTypeOptions = () => {
+ if (props.type === '0') {
+ findAllQualifiedStockRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+ return
+ }
+ findAllUnqualifiedStockRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter((item) => item.id);
+ console.log("selection", selectedRows.value);
+};
+const expandedRowKeys = ref([]);
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/stockOutRecord/exportStockOutRecord", {type: props.type}, props.type === '0' ? "鍚堟牸鍑哄簱鍙拌处.xlsx" : "涓嶅悎鏍煎嚭搴撳彴璐�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鍒犻櫎
+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(() => {
+ delStockOut(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鎵撳嵃鍔熻兘
+const handlePrint = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
+ return;
+ }
+ printData.value = [...selectedRows.value];
+ console.log('鎵撳嵃鏁版嵁:', printData.value);
+ printPreviewVisible.value = true;
+};
+
+// 鎵ц鎵撳嵃
+const executePrint = () => {
+ console.log('寮�濮嬫墽琛屾墦鍗帮紝鏁版嵁鏉℃暟:', printData.value.length);
+ console.log('鎵撳嵃鏁版嵁:', printData.value);
+
+ // 鍒涘缓涓�涓柊鐨勬墦鍗扮獥鍙�
+ const printWindow = window.open('', '_blank', 'width=800,height=600');
+
+ // 鏋勫缓鎵撳嵃鍐呭
+ let printContent = `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="UTF-8">
+ <title>鎵撳嵃棰勮</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ font-family: "SimSun", serif;
+ background: white;
+ }
+ .print-page {
+ width: 200mm;
+ height: 75mm;
+ padding: 10mm;
+ padding-left: 20mm;
+ background: white;
+ box-sizing: border-box;
+ page-break-after: always;
+ page-break-inside: avoid;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+ .delivery-note {
+ width: 100%;
+ height: 100%;
+ font-size: 12px;
+ line-height: 1.2;
+ display: flex;
+ flex-direction: column;
+ color: #000;
+ }
+ .header {
+ text-align: center;
+ margin-bottom: 8px;
+ }
+ .company-name {
+ font-size: 18px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+ .document-title {
+ font-size: 16px;
+ font-weight: bold;
+ }
+ .info-section {
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+ .info-row {
+ line-height: 20px;
+ }
+ .label {
+ font-weight: bold;
+ width: 60px;
+ font-size: 12px;
+ }
+ .value {
+ margin-right: 20px;
+ min-width: 80px;
+ font-size: 12px;
+ }
+ .table-section {
+ margin-bottom: 40px;
+ // flex: 0.6;
+ }
+ .product-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #000;
+ }
+ .product-table th, .product-table td {
+ border: 1px solid #000;
+ padding: 6px;
+ text-align: center;
+ font-size: 12px;
+ line-height: 1.4;
+ }
+ .product-table th {
+ font-weight: bold;
+ }
+ .total-value {
+ font-weight: bold;
+ }
+ .footer-section {
+ margin-top: auto;
+ }
+ .footer-row {
+ display: flex;
+ margin-bottom: 3px;
+ line-height: 22px;
+ justify-content: space-between;
+ }
+ .footer-item {
+ display: flex;
+ margin-right: 20px;
+ }
+ .footer-item .label {
+ font-weight: bold;
+ width: 80px;
+ font-size: 12px;
+ }
+ .footer-item .value {
+ min-width: 80px;
+ font-size: 12px;
+ }
+ .address-item .address-value {
+ min-width: 200px;
+ }
+ @media print {
+ body {
+ margin: 0;
+ padding: 0;
+ }
+ .print-page {
+ margin: 0;
+ padding: 10mm;
+ /* padding-left: 20mm; */
+ page-break-inside: avoid;
+ page-break-after: always;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+ }
+ </style>
+ </head>
+ <body>
+ `;
+
+ // 涓烘瘡鏉℃暟鎹敓鎴愭墦鍗伴〉闈�
+ printData.value.forEach((item, index) => {
+ printContent += `
+ <div 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.productName || '鐮傜伆鐮�'}</td>
+ <td>${item.model || '鏍囧噯'}</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>
+ `;
+ });
+
+ printContent += `
+ </body>
+ </html>
+ `;
+
+ // 鍐欏叆鍐呭鍒版柊绐楀彛
+ printWindow.document.write(printContent);
+ printWindow.document.close();
+
+ // 绛夊緟鍐呭鍔犺浇瀹屾垚鍚庢墦鍗�
+ printWindow.onload = () => {
+ setTimeout(() => {
+ printWindow.print();
+ printWindow.close();
+ printPreviewVisible.value = false;
+ }, 500);
+ };
+};
+
+
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (dateString) => {
+ if (!dateString) return getCurrentDate();
+ const date = new Date(dateString);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}/${month}/${day}`;
+};
+
+// 鏍煎紡鍖栨棩鏈熸椂闂�
+const formatDateTime = (date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ const hours = String(date.getHours()).padStart(2, "0");
+ const minutes = String(date.getMinutes()).padStart(2, "0");
+ const seconds = String(date.getSeconds()).padStart(2, "0");
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
+};
+onMounted(() => {
+ getList();
+ fetchStockRecordTypeOptions();
+});
+</script>
+
+<style scoped lang="scss">
+.print-preview-dialog {
+ .el-dialog__body {
+ padding: 0;
+ max-height: 80vh;
+ overflow-y: auto;
+ }
+}
+
+.print-preview-container {
+ .print-preview-header {
+ padding: 15px;
+ border-bottom: 1px solid #e4e7ed;
+ text-align: center;
+
+ .el-button {
+ margin: 0 10px;
+ }
+ }
+
+ .print-preview-content {
+ padding: 20px;
+ background-color: #f5f5f5;
+ min-height: 400px;
+ }
+}
+
+.print-page {
+ width: 220mm;
+ height: 90mm;
+ padding: 10mm;
+ margin: 0 auto;
+ background: white;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.delivery-note {
+ width: 100%;
+ height: 100%;
+ font-family: "SimSun", serif;
+ font-size: 10px;
+ line-height: 1.2;
+ display: flex;
+ flex-direction: column;
+}
+
+.header {
+ text-align: center;
+ margin-bottom: 8px;
+
+ .company-name {
+ font-size: 18px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+
+ .document-title {
+ font-size: 16px;
+ font-weight: bold;
+ }
+}
+
+.info-section {
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .info-row {
+ line-height: 20px;
+
+ .label {
+ font-weight: bold;
+ width: 60px;
+ font-size: 14px;
+ }
+
+ .value {
+ margin-right: 20px;
+ min-width: 80px;
+ font-size: 14px;
+ }
+ }
+}
+
+.table-section {
+ margin-bottom: 4px;
+ flex: 1;
+
+ .product-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #000;
+
+ th, td {
+ border: 1px solid #000;
+ padding: 6px;
+ text-align: center;
+ font-size: 14px;
+ line-height: 1.4;
+ }
+
+ th {
+ font-weight: bold;
+ }
+
+ .total-label {
+ text-align: right;
+ font-weight: bold;
+ }
+
+ .total-value {
+ font-weight: bold;
+ }
+ }
+}
+
+.footer-section {
+ .footer-row {
+ display: flex;
+ margin-bottom: 3px;
+ line-height: 20px;
+ justify-content: space-between;
+
+ .footer-item {
+ display: flex;
+ margin-right: 20px;
+
+ .label {
+ font-weight: bold;
+ width: 80px;
+ font-size: 14px;
+ }
+
+ .value {
+ min-width: 80px;
+ font-size: 14px;
+ }
+
+ &.address-item {
+ .address-value {
+ min-width: 200px;
+ }
+ }
+ }
+ }
+}
+
+@media print {
+ .app-container {
+ display: none;
+ }
+
+ .print-page {
+ box-shadow: none;
+ margin: 0;
+ padding: 10mm;
+ padding-left: 20mm;
+ page-break-inside: avoid;
+ page-break-after: always;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+}
+</style>
diff --git a/src/views/inventoryManagement/dispatchLog/index.vue b/src/views/inventoryManagement/dispatchLog/index.vue
index c15e73f..88d9984 100644
--- a/src/views/inventoryManagement/dispatchLog/index.vue
+++ b/src/views/inventoryManagement/dispatchLog/index.vue
@@ -1,371 +1,38 @@
+<!-- 鍦ㄤ綘鐨勪富椤甸潰涓� -->
<template>
- <div class="app-container">
- <div class="search_form">
- <div>
- <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.productCategory"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- clearable
- />
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
- </div>
- <div>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
- </div>
- </div>
- <div class="table_list">
- <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" show-overflow-tooltip width="130"/>
- <el-table-column label="鎵规鍙�" prop="code" width="130" show-overflow-tooltip />
- <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
- <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
- <el-table-column label="鍗曚綅" prop="unit" show-overflow-tooltip />
- <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" show-overflow-tooltip />
- <el-table-column label="鍙戞枡鏁伴噺" prop="inboundNum" show-overflow-tooltip />
- <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150"></el-table-column>
- <el-table-column label="鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="150"></el-table-column>
- <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"
- />
- </div>
- </div>
+ <div class="app-container">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane v-for="tab in tabs"
+ :label="tab.label"
+ :name="tab.name"
+ :key="tab.name">
+ <record :type="tab.type" v-if="activeTab === tab.name" />
+ </el-tab-pane>
+ </el-tabs>
+ </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 {
- delStockOut,
-} from "@/api/inventoryManagement/stockOut.js";
-import {
- getStockInPageByCustom,
-} from "@/api/inventoryManagement/stockIn.js";
-import { getCurrentDate } from "@/utils/index.js";
-const { proxy } = getCurrentInstance();
-const tableData = ref([]);
-const selectedRows = ref([]);
-const tableLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
-});
-const total = ref(0);
+import Record from "@/views/inventoryManagement/dispatchLog/Record.vue";
+const activeTab = ref('qualified')
+const type = ref(0)
+const tabs = computed(() => {
+ return [
+ {
+ label: '鍚堟牸鍑哄簱',
+ name: 'qualified',
+ type: '0'
+ },
+ {
+ label: '涓嶅悎鏍煎嚭搴�',
+ name: 'unqualified',
+ type: '1'
+ }
+ ]
+})
-// 鏌ヨ琛ㄥ崟鏁版嵁
-const data = reactive({
- searchForm: {
- supplierName: "",
- customerName: "",
- productCategory:'',
- timeStr: getCurrentDate(),
- },
-});
-const { searchForm } = toRefs(data);
-
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
-const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-const getList = () => {
- tableLoading.value = true;
- const params = { ...page }
- params.supplierName = searchForm.value.supplierName
- params.timeStr = searchForm.value.timeStr
- params.productCategory = searchForm.value.productCategory
-
- // 鏉愭枡鍑哄簱锛氳皟鐢ㄨ嚜瀹氫箟鍑哄簱璁板綍鎺ュ彛
- const apiCall = getStockInPageByCustom(params)
-
- apiCall
- .then((res) => {
- tableLoading.value = false;
- tableData.value = res.data.records;
- tableData.value.map((item) => {
- item.children = [];
- // 鍓嶇璁$畻鎬讳环
- const inboundNum = Number(item.inboundNum) || 0;
- // 鏉愭枡鍑哄簱锛氭�讳环 = taxInclusiveUnitPrice 脳 inboundNum
- const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0;
- item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * inboundNum).toFixed(2);
- });
- total.value = res.data.total;
- })
- .catch(() => {
- tableLoading.value = false;
- });
-};
-
-// 琛ㄦ牸閫夋嫨鏁版嵁
-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 handleOut = () => {
- ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- // 鏍规嵁涓嶅悓鐨� tab 绫诲瀷璋冪敤涓嶅悓鐨勫鍑烘帴鍙�
- const exportUrl = "/stockmanagement/exportTwo"
- proxy.download(exportUrl, {}, "鍑哄簱鍙拌处.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-// 鍒犻櫎
-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(() => {
- delStockOut({ids:ids}).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-onMounted(() => {
- getList();
-});
+const handleTabChange = (tabName) => {
+ activeTab.value = tabName;
+ type.value = tabName === 'qualified' ? 0 : 1
+}
</script>
-
-<style scoped lang="scss">
-.print-preview-dialog {
- .el-dialog__body {
- padding: 0;
- max-height: 80vh;
- overflow-y: auto;
- }
-}
-
-.print-preview-container {
- .print-preview-header {
- padding: 15px;
- border-bottom: 1px solid #e4e7ed;
- text-align: center;
-
- .el-button {
- margin: 0 10px;
- }
- }
-
- .print-preview-content {
- padding: 20px;
- background-color: #f5f5f5;
- min-height: 400px;
- }
-}
-
-.print-page {
- width: 220mm;
- height: 90mm;
- padding: 10mm;
- margin: 0 auto;
- background: white;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
- margin-bottom: 10px;
- box-sizing: border-box;
-}
-
-.delivery-note {
- width: 100%;
- height: 100%;
- font-family: "SimSun", serif;
- font-size: 10px;
- line-height: 1.2;
- display: flex;
- flex-direction: column;
-}
-
-.header {
- text-align: center;
- margin-bottom: 8px;
-
- .company-name {
- font-size: 18px;
- font-weight: bold;
- margin-bottom: 4px;
- }
-
- .document-title {
- font-size: 16px;
- font-weight: bold;
- }
-}
-
-.info-section {
- margin-bottom: 8px;
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- .info-row {
- line-height: 20px;
-
- .label {
- font-weight: bold;
- width: 60px;
- font-size: 14px;
- }
-
- .value {
- margin-right: 20px;
- min-width: 80px;
- font-size: 14px;
- }
- }
-}
-
-.table-section {
- margin-bottom: 4px;
- flex: 1;
-
- .product-table {
- width: 100%;
- border-collapse: collapse;
- border: 1px solid #000;
-
- th, td {
- border: 1px solid #000;
- padding: 6px;
- text-align: center;
- font-size: 14px;
- line-height: 1.4;
- }
-
- th {
- font-weight: bold;
- }
-
- .total-label {
- text-align: right;
- font-weight: bold;
- }
-
- .total-value {
- font-weight: bold;
- }
- }
-}
-
-.footer-section {
- .footer-row {
- display: flex;
- margin-bottom: 3px;
- line-height: 20px;
- justify-content: space-between;
-
- .footer-item {
- display: flex;
- margin-right: 20px;
-
- .label {
- font-weight: bold;
- width: 80px;
- font-size: 14px;
- }
-
- .value {
- min-width: 80px;
- font-size: 14px;
- }
-
- &.address-item {
- .address-value {
- min-width: 200px;
- }
- }
- }
- }
-}
-
-@media print {
- .app-container {
- display: none;
- }
-
- .print-page {
- box-shadow: none;
- margin: 0;
- padding: 10mm;
- padding-left: 20mm;
- page-break-inside: avoid;
- page-break-after: always;
- }
- .print-page:last-child {
- page-break-after: avoid;
- }
-}
-</style>
-
-
diff --git a/src/views/inventoryManagement/issueManagement/index.vue b/src/views/inventoryManagement/issueManagement/index.vue
index 92d6073..f5d2ea9 100644
--- a/src/views/inventoryManagement/issueManagement/index.vue
+++ b/src/views/inventoryManagement/issueManagement/index.vue
@@ -2,70 +2,66 @@
<div class="app-container">
<div class="search_form">
<div>
- <span class="search_title ml10">浜у搧澶х被锛�</span>
- <el-input
- v-model="searchForm.productCategory"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- clearable
- />
+ <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>
</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">
- <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 :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="code" width="130" show-overflow-tooltip />
- <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
- <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
+ <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="itemType" show-overflow-tooltip />
- <el-table-column label="鍓╀綑搴撳瓨" prop="inboundNum0" width="90" show-overflow-tooltip />
- <el-table-column fixed="right" label="鎿嶄綔" width="100" align="center">
+ <el-table-column label="鍏ュ簱鏁伴噺" prop="inboundNum" width="90" show-overflow-tooltip />
+ <el-table-column label="搴撳瓨鏁伴噺" prop="inboundNum0" 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(scope.row);">鍙戞枡</el-button>
+ <el-button link type="primary" size="small" @click="openForm(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="getDialogTitle()" width="40%" @close="closeDia" draggable>
+ <el-dialog v-model="dialogFormVisible" :title="'鏂板鍑哄簱'" width="40%" @close="closeDia">
<el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
- <div>{{getAvailableQuantityText()}}:{{currentRowNum}}</div>
- <el-form-item :label="getQuantityLabel()" prop="inboundQuantity">
- <el-input-number :step="0.01" :min="0" :max="currentRowNum" style="width: 100%" v-model="form.inboundQuantity" placeholder="璇疯緭鍏�" clearable />
+ <el-form-item label="鍑哄簱鏁伴噺锛�" prop="salesContractNo">
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.inboundQuantity" placeholder="璇疯緭鍏�" clearable />
</el-form-item>
- <el-form-item :label="getDateLabel()" prop="inboundTime">
+ <el-form-item label="鍑哄簱鏃ユ湡锛�" prop="projectName">
<el-date-picker style="width: 100%" v-model="form.inboundTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
type="date" placeholder="璇烽�夋嫨" clearable />
</el-form-item>
- <el-form-item :label="getPersonLabel()" prop="nickName">
- <el-select v-model="form.nickName"
- filterable
- default-first-option
- :reserve-keyword="false" placeholder="璇烽�夋嫨" clearable>
+ <el-form-item label="鍑哄簱浜猴細" prop="entryPerson">
+ <el-select v-model="form.nickName" placeholder="璇烽�夋嫨" clearable>
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
</el-select>
</el-form-item>
@@ -82,18 +78,18 @@
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
-import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import { ref } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import { userListNoPageByTenantId } from "@/api/system/user.js";
import {
- getInPageByCustom
+ getStockInPage
} from "@/api/inventoryManagement/stockIn.js";
import {
+ getStockManagePage,
delStockManage,
stockOut,
} from "@/api/inventoryManagement/stockManage.js";
-import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
@@ -112,12 +108,12 @@
const dialogFormVisible = ref(false)
const data = reactive({
searchForm: {
+ supplierName: '',
inboundQuantity:'',
inboundTime:'',
nickName: '',
userId: '',
- productCategory:'',
- // timeStr: getCurrentDate(),
+ timeStr: '',
},
form: {
productrecordId: '',
@@ -125,7 +121,7 @@
rules: {
inboundTime: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
inboundQuantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- nickName: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }]
+ nickname: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }]
}
})
const { searchForm, form, rules } = toRefs(data)
@@ -143,14 +139,11 @@
}
const getList = () => {
tableLoading.value = true
- const params = { ...page }
- params.productCategory = searchForm.value.productCategory
- params.flag = true
- const apiCall = getInPageByCustom(params)
- apiCall.then(res => {
+ getStockInPage({ ...searchForm.value, ...page }).then(res => {
tableLoading.value = false
tableData.value = res.data.records
total.value = res.data.total
+ console.log('res', res.data.records)
}).catch(() => {
tableLoading.value = false
})
@@ -194,34 +187,20 @@
currentRowNum.value = row.inboundNum0
salesLedgerProductId.value = row.salesLedgerProductId
form.value = {}
-
+ // 鍒濆鍖栬〃鍗曟暟鎹�
+ form.value = {
+ productrecordId: '',
+ inboundQuantity: '', // 鍑哄簱鏁伴噺娓呯┖
+ inboundTime: getCurrentDate(), // 榛樿褰撳墠鏃ユ湡
+ nickName: '', // 榛樿褰撳墠鐢ㄦ埛
+ }
+ console.log('form',form.value)
// 鍔犺浇鐢ㄦ埛鍒楄〃
try {
const userLists = await userListNoPageByTenantId()
userList.value = userLists.data
-
- // 鏉愭枡鍑哄簱榛樿鍙戞枡浜�
- const defaultPersonName = '鍚寸帀姊�'
- const defaultPerson = userList.value.find(user => user.nickName === defaultPersonName)
- const defaultUserId = defaultPerson ? defaultPerson.userId : ''
-
- // 鍒濆鍖栬〃鍗曟暟鎹�
- form.value = {
- productrecordId: '',
- inboundQuantity: currentRowNum.value, // 浣跨敤currentRowNum浣滀负榛樿鍊�
- inboundTime: getCurrentDate(), // 榛樿褰撳墠鏃ユ湡
- nickName: defaultUserId, // 鏍规嵁tab绫诲瀷璁剧疆榛樿鍙戣揣浜�
- }
- console.log('form',form.value)
} catch (error) {
console.error('鍔犺浇鐢ㄦ埛鍒楄〃澶辫触:', error)
- // 濡傛灉鍔犺浇澶辫触锛屼娇鐢ㄧ┖鍊煎垵濮嬪寲
- form.value = {
- productrecordId: '',
- inboundQuantity: currentRowNum.value,
- inboundTime: getCurrentDate(),
- nickName: '',
- }
}
}
@@ -235,11 +214,10 @@
if (valid && currentRowId.value) {
const outData = {
id: currentRowId.value, // 鍘熷璁板綍ID
- salesLedgerProductId: 0,
+ salesLedgerProductId: salesLedgerProductId.value,
quantity: form.value.inboundQuantity, // 鍑哄簱鏁伴噺
time: form.value.inboundTime, // 鍑哄簱鏃堕棿
- userId: form.value.nickName, // 鎿嶄綔浜�
- type: 3 // 鍑哄簱绫诲瀷锛氳嚜瀹氫箟/鏉愭枡
+ userId: form.value.nickName // 鎿嶄綔浜�
}
console.log(outData)
@@ -269,8 +247,7 @@
type: 'warning',
}
).then(() => {
- const exportUrl = "/stockin/exportTwo"
- proxy.download(exportUrl, {}, '鍑哄簱鍙拌处.xlsx')
+ proxy.download("/stockin/export", {}, '鍏ュ簱鍙拌处.xlsx')
}).catch(() => {
proxy.$modal.msg("宸插彇娑�")
})
@@ -300,38 +277,9 @@
proxy.$modal.msg("宸插彇娑�")
})
}
-
-// 鏍规嵁tab绫诲瀷鑾峰彇寮规鏍囬
-const getDialogTitle = () => {
- return '鏂板鍙戞枡';
-};
-
-// 鏍规嵁tab绫诲瀷鑾峰彇鍙嚭搴撴暟閲忔枃鏈�
-const getAvailableQuantityText = () => {
- return '鍙彂鏂欐暟閲�';
-};
-
-// 鏍规嵁tab绫诲瀷鑾峰彇鏁伴噺瀛楁鏍囩
-const getQuantityLabel = () => {
- return '鍙戞枡鏁伴噺锛�';
-};
-
-// 鏍规嵁tab绫诲瀷鑾峰彇鏃ユ湡瀛楁鏍囩
-const getDateLabel = () => {
- return '鍙戞枡鏃ユ湡锛�';
-};
-
-// 鏍规嵁tab绫诲瀷鑾峰彇浜哄憳瀛楁鏍囩
-const getPersonLabel = () => {
- return '鍙戞枡浜猴細';
-};
-
onMounted(() => {
getList()
})
</script>
<style scoped lang="scss"></style>
-
-
-
diff --git a/src/views/inventoryManagement/receiptManagement/Record.vue b/src/views/inventoryManagement/receiptManagement/Record.vue
new file mode 100644
index 0000000..e36c787
--- /dev/null
+++ b/src/views/inventoryManagement/receiptManagement/Record.vue
@@ -0,0 +1,250 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <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/>
+ <span class="search_title ml10">鏉ユ簮锛�</span>
+ <el-select v-model="searchForm.recordType"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨"
+ clearable>
+ <el-option v-for="item in stockRecordTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ <el-button type="primary"
+ @click="handleQuery"
+ style="margin-left: 10px">鎼滅储
+ </el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger"
+ plain
+ @click="handleDelete">鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <el-table :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="row => row.id"
+ style="width: 100%"
+ height="calc(100vh - 18.5em)">
+ <el-table-column align="center"
+ type="selection"
+ width="55"/>
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"/>
+ <el-table-column label="鍏ュ簱鎵规"
+ 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-column label="鏉ユ簮"
+ prop="recordType"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ getRecordType(scope.row.recordType) }}
+ </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="pageProductChange"/>
+ </div>
+ </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 {
+ getStockInRecordListPage,
+ batchDeleteStockInRecords,
+} from "@/api/inventoryManagement/stockInRecord.js";
+import {
+ findAllQualifiedStockRecordTypeOptions,
+ findAllUnqualifiedStockRecordTypeOptions
+} from "@/api/basicData/enum.js";
+
+const {proxy} = getCurrentInstance();
+
+const props = defineProps({
+ type: {
+ type: String,
+ required: true,
+ default: '0'
+ }
+})
+
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+// 鏉ユ簮绫诲瀷閫夐」
+const stockRecordTypeOptions = ref([]);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+
+const data = reactive({
+ searchForm: {
+ productName: "",
+ timeStr: "",
+ recordType: "",
+ },
+});
+const {searchForm} = toRefs(data);
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+const getRecordType = (recordType) => {
+ return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+const pageProductChange = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+
+const getList = () => {
+ tableLoading.value = true;
+ const params = {...page, type: props.type};
+ params.timeStr = searchForm.value.timeStr;
+ params.productName = searchForm.value.productName;
+ getStockInRecordListPage(params)
+ .then(res => {
+ tableData.value = res.data.records;
+ }).finally(() => {
+ tableLoading.value = false;
+ })
+};
+
+// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
+const fetchStockRecordTypeOptions = () => {
+ if (props.type === '0') {
+ findAllQualifiedStockRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+ return
+ }
+ findAllUnqualifiedStockRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = selection => {
+ selectedRows.value = selection.filter(item => item.id);
+};
+
+const expandedRowKeys = ref([]);
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ // 鏍规嵁涓嶅悓鐨� tab 绫诲瀷璋冪敤涓嶅悓鐨勫鍑烘帴鍙�
+ proxy.download("/stockInRecord/exportStockInRecord", {type: props.type}, props.type === '0' ? "鍚堟牸鍏ュ簱.xlsx" : "涓嶅悎鏍煎叆搴�.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();
+ fetchStockRecordTypeOptions();
+});
+</script>
+
+<style scoped lang="scss"></style>
+
+
+
diff --git a/src/views/inventoryManagement/receiptManagement/index.vue b/src/views/inventoryManagement/receiptManagement/index.vue
index 7f1f83c..8ca110f 100644
--- a/src/views/inventoryManagement/receiptManagement/index.vue
+++ b/src/views/inventoryManagement/receiptManagement/index.vue
@@ -1,212 +1,36 @@
<template>
<div class="app-container">
- <div class="search_form">
- <div>
- <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.productCategory"
- 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>
- </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="inboundDate" width="100" show-overflow-tooltip />
- <el-table-column label="鎵规鍙�" prop="code" width="130" show-overflow-tooltip />
- <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
- <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
- <el-table-column label="鍗曚綅" prop="unit" width="70" show-overflow-tooltip />
- <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" show-overflow-tooltip />
- <el-table-column label="鍏ュ簱鏁伴噺" prop="inboundNum" width="100" show-overflow-tooltip />
- <el-table-column label="鍓╀綑搴撳瓨" prop="inboundNum0" show-overflow-tooltip />
- <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150" />
- <el-table-column label="鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="150" />
- <el-table-column label="鍏ュ簱浜�" prop="createBy" width="80" show-overflow-tooltip />
- <el-table-column fixed="right" label="鎿嶄綔" width="100" 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"
- :total="total"
- layout="total, sizes, prev, pager, next, jumper"
- :page="page.current"
- :limit="page.size"
- @pagination="paginationChange"
- />
- </div>
-
- <form-dia-manual ref="formDiaManual" @close="handleQuery" @success="handleQuery"></form-dia-manual>
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane v-for="tab in tabs"
+ :label="tab.label"
+ :name="tab.name"
+ :key="tab.name">
+ <record :type="tab.type" v-if="activeTab === tab.name" />
+ </el-tab-pane>
+ </el-tabs>
</div>
</template>
<script setup>
-import pagination from '@/components/PIMTable/Pagination.vue'
-import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue'
-import { ElMessageBox } from "element-plus";
-import useUserStore from '@/store/modules/user'
-import dayjs from 'dayjs'
-import {
- delStockInCustom,
- getInPageByCustom,
-} from "@/api/inventoryManagement/stockIn.js";
-import FormDiaManual from './components/formDiaManual.vue'
-import { getCurrentDate } from "@/utils/index.js";
+import Record from "@/views/inventoryManagement/receiptManagement/Record.vue";
-const { proxy } = getCurrentInstance()
-
-const tableData = ref([])
-const selectedRows = ref([])
-const tableLoading = ref(false)
-const formDiaManual = ref()
-
-const page = reactive({
- current: 1,
- size: 100,
-})
-const total = ref(0)
-
-const data = reactive({
- searchForm: {
- productCategory:'',
- timeStr: getCurrentDate(),
+const activeTab = ref('qualified')
+const type = ref(0)
+const tabs = ref([
+ {
+ label: '鍚堟牸鍏ュ簱',
+ name: 'qualified',
+ type: '0'
},
-})
-const { searchForm } = toRefs(data)
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1
- getList()
-}
-const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList()
-}
-const getList = () => {
- tableLoading.value = true
- const params = { ...page }
- params.timeStr = searchForm.value.timeStr
- params.productCategory = searchForm.value.productCategory
- params.flag = false
- const apiCall = getInPageByCustom(params)
-
- apiCall.then(res => {
- tableLoading.value = false
- tableData.value = res.data.records
-
- // 鍓嶇璁$畻鍚◣鎬讳环锛氬惈绋庢�讳环 = taxInclusiveUnitPrice * 鍏ュ簱鏁伴噺
- tableData.value = tableData.value.map(item => {
- const inboundNum = Number(item.inboundNum) || 0
- const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0
- item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * inboundNum).toFixed(2)
- return item
- })
-
- total.value = res.data.total
- }).catch(() => {
- tableLoading.value = false
- })
-}
-
-// 鎵撳紑寮规
-const openForm = async (type, row) => {
- await nextTick(() => {
- formDiaManual.value?.openDialog(type, row)
- })
-}
-
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection.filter(item => item.id)
-}
-
-const expandedRowKeys = ref([])
-
-// 涓昏〃鍚堣鏂规硶
-const summarizeMainTable = (param) => {
- return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice'])
-}
-
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm('鏄惁纭瀵煎嚭锛�', '瀵煎嚭', {
- confirmButtonText: '纭',
- cancelButtonText: '鍙栨秷',
- type: 'warning',
- }).then(() => {
- const exportUrl = "/stockin/exportTwo"
- proxy.download(exportUrl, {}, '鍏ュ簱鍙拌处.xlsx')
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�")
- })
-}
-
-// 鍒犻櫎
-const handleDelete = () => {
- if (selectedRows.value.length === 0) {
- proxy.$modal.msgWarning('璇烽�夋嫨鏁版嵁')
- return
+ {
+ label: '涓嶅悎鏍煎叆搴�',
+ name: 'unqualified',
+ type: '1'
}
- const ids = selectedRows.value.map(item => item.id)
-
- ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎', {
- confirmButtonText: '纭',
- cancelButtonText: '鍙栨秷',
- type: 'warning',
- }).then(() => {
- // 鏉愭枡鍏ュ簱鍒犻櫎
- delStockInCustom(ids).then(() => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
- getList()
- }).catch(() => {
- proxy.$modal.msgError("鍒犻櫎澶辫触")
- })
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�")
- })
+])
+
+const handleTabChange = (tabName) => {
+ activeTab.value = tabName;
+ type.value = tabName === 'qualified' ? 0 : 1
}
-
-onMounted(() => {
- getList()
-})
</script>
-
-<style scoped lang="scss"></style>
-
-
-
diff --git a/src/views/inventoryManagement/stockManagement/New.vue b/src/views/inventoryManagement/stockManagement/New.vue
new file mode 100644
index 0000000..751c639
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/New.vue
@@ -0,0 +1,180 @@
+<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";
+import {createStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ type: {
+ type: String,
+ required: true,
+ default: 'qualified',
+ },
+});
+
+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];
+ 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;
+ }
+ if (props.type === 'qualified') {
+ createStockInventory(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ } else {
+ createStockUnInventory(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+
+ }
+ })
+};
+
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/inventoryManagement/stockManagement/Qualified.vue b/src/views/inventoryManagement/stockManagement/Qualified.vue
new file mode 100644
index 0000000..cfc9ed1
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/Qualified.vue
@@ -0,0 +1,158 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <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="isShowNewModal = true">鏂板搴撳瓨</el-button>
+ <el-button @click="handleOut">瀵煎嚭</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" 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="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="remark" 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="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>
+ <new-stock-inventory v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ type="qualified"
+ @completed="handleQuery" />
+
+ <subtract-stock-inventory v-if="isShowSubtractModal"
+ v-model:visible="isShowSubtractModal"
+ :record="record"
+ @completed="handleQuery" />
+ </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 { 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 { proxy } = getCurrentInstance()
+const tableData = ref([])
+const selectedRows = ref([])
+const record = ref({})
+const tableLoading = ref(false)
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+// 鏄惁鏄剧ず鏂板寮规
+const isShowNewModal = ref(false)
+// 鏄惁鏄剧ず棰嗙敤寮规
+const isShowSubtractModal = ref(false)
+const data = reactive({
+ searchForm: {
+ productName: '',
+ }
+})
+const { searchForm } = toRefs(data)
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1
+ getList()
+}
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList()
+}
+const getList = () => {
+ tableLoading.value = true
+ getStockInventoryListPage({ ...searchForm.value, ...page }).then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+ total.value = res.data.total
+ // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+ // checkStockAndCreatePurchase();
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+// 鐐瑰嚮棰嗙敤
+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 tableRowClassName = ({ row }) => {
+ const stock = Number(row?.inboundNum0 ?? 0);
+ const warn = Number(row?.warnNum ?? 0);
+ if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
+ return '';
+ }
+ return stock < warn ? 'row-low-stock' : '';
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm(
+ '鏄惁纭瀵煎嚭锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ proxy.download("/stockInventory/exportStockInventory", {}, '鍚堟牸搴撳瓨淇℃伅.xlsx')
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+
+onMounted(() => {
+ getList()
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.row-low-stock td) {
+ background-color: #fde2e2;
+ color: #c45656;
+}
+
+:deep(.row-low-stock:hover > td) {
+ background-color: #fcd4d4;
+}
+</style>
diff --git a/src/views/inventoryManagement/stockManagement/Subtract.vue b/src/views/inventoryManagement/stockManagement/Subtract.vue
new file mode 100644
index 0000000..082153c
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/Subtract.vue
@@ -0,0 +1,199 @@
+<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";
+import {subtractStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ record: {
+ type: Object,
+ default: () => {},
+ },
+ type: {
+ type: String,
+ required: true,
+ default: 'qualified',
+ },
+});
+
+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;
+ }
+ if (props.type === 'qualified') {
+ subtractStockInventory(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ } else {
+ subtractStockUnInventory(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ }
+ })
+};
+
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/inventoryManagement/stockManagement/Unqualified.vue b/src/views/inventoryManagement/stockManagement/Unqualified.vue
new file mode 100644
index 0000000..67d5f58
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/Unqualified.vue
@@ -0,0 +1,158 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <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="isShowNewModal = true">鏂板搴撳瓨</el-button>
+ <el-button @click="handleOut">瀵煎嚭</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" 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="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="remark" 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="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>
+ <new-stock-inventory v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ type="unqualified"
+ @completed="handleQuery" />
+
+ <subtract-stock-inventory v-if="isShowSubtractModal"
+ v-model:visible="isShowSubtractModal"
+ :record="record"
+ @completed="handleQuery" />
+ </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 { getStockUninventoryListPage } from "@/api/inventoryManagement/stockUninventory.js";
+const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue"));
+const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue"));
+
+const { proxy } = getCurrentInstance()
+const tableData = ref([])
+const selectedRows = ref([])
+const record = ref({})
+const tableLoading = ref(false)
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+// 鏄惁鏄剧ず鏂板寮规
+const isShowNewModal = ref(false)
+// 鏄惁鏄剧ず棰嗙敤寮规
+const isShowSubtractModal = ref(false)
+const data = reactive({
+ searchForm: {
+ productName: '',
+ }
+})
+const { searchForm } = toRefs(data)
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1
+ getList()
+}
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList()
+}
+const getList = () => {
+ tableLoading.value = true
+ getStockUninventoryListPage({ ...searchForm.value, ...page }).then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+ total.value = res.data.total
+ // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+ // checkStockAndCreatePurchase();
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+// 鐐瑰嚮棰嗙敤
+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 tableRowClassName = ({ row }) => {
+ const stock = Number(row?.inboundNum0 ?? 0);
+ const warn = Number(row?.warnNum ?? 0);
+ if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
+ return '';
+ }
+ return stock < warn ? 'row-low-stock' : '';
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm(
+ '鏄惁纭瀵煎嚭锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ proxy.download("/stockUninventory/exportStockUninventory", {}, '涓嶅悎鏍煎簱瀛樹俊鎭�.xlsx')
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+
+onMounted(() => {
+ getList()
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.row-low-stock td) {
+ background-color: #fde2e2;
+ color: #c45656;
+}
+
+:deep(.row-low-stock:hover > td) {
+ background-color: #fcd4d4;
+}
+</style>
diff --git a/src/views/inventoryManagement/stockManagement/index.vue b/src/views/inventoryManagement/stockManagement/index.vue
index 658aeda..347de38 100644
--- a/src/views/inventoryManagement/stockManagement/index.vue
+++ b/src/views/inventoryManagement/stockManagement/index.vue
@@ -1,389 +1,33 @@
<template>
<div class="app-container">
- <div class="search_form">
- <div>
- <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.productCategory"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- clearable
- />
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
- </div>
- <div>
- <el-button @click="handleOut">瀵煎嚭</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)"
- >
- <el-table-column align="center" type="selection" width="55" />
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
- <el-table-column label="鍏ュ簱鏃ユ湡" prop="inboundDate" width="100" show-overflow-tooltip />
- <el-table-column label="鎵规鍙�" prop="code" width="130" show-overflow-tooltip />
- <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
- <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
- <el-table-column label="鍗曚綅" prop="unit" width="80" show-overflow-tooltip />
- <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" width="120" show-overflow-tooltip />
- <el-table-column label="宸插彂鏂欐暟閲�" prop="totalInboundNum" width="100" show-overflow-tooltip />
- <el-table-column label="鍓╀綑搴撳瓨" prop="inboundNum0" width="100" show-overflow-tooltip />
- <el-table-column label="棰勮鍊�" prop="warnNum" width="100" show-overflow-tooltip />
- <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150" />
- <el-table-column label="鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="150" />
- <el-table-column fixed="right" label="鎿嶄綔" width="100" 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"
- :total="total"
- layout="total, sizes, prev, pager, next, jumper"
- :page="page.current"
- :limit="page.size"
- @pagination="paginationChange"
- />
- </div>
-
- <!-- 鏉愭枡搴撳瓨寮规 -->
- <FormDiaManual
- v-model:dialogFormVisible="manualDialogVisible"
- :operationType="operationType"
- :formData="form"
- @submit="submitForm"
- @close="closeDia"
- />
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane v-for="tab in tabs"
+ :label="tab.label"
+ :name="tab.name"
+ :key="tab.name">
+ <component :is="tab.name === 'qualified' ? QualifiedRecord : UnqualifiedRecord" />
+ </el-tab-pane>
+ </el-tabs>
</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 { userListNoPageByTenantId } from "@/api/system/user.js";
-import { productTreeList,modelList } from "@/api/basicData/product.js"
-import {
- getStockManagePageByCustom,
- delStockManage,
-} from "@/api/inventoryManagement/stockManage.js";
-import {
- updateManagement, updateManagementByCustom, updateStockIn
-} from "@/api/inventoryManagement/stockIn.js";
-import { getCurrentDate } from "@/utils/index.js";
+import QualifiedRecord from "@/views/inventoryManagement/stockManagement/Qualified.vue";
+import UnqualifiedRecord from "@/views/inventoryManagement/stockManagement/Unqualified.vue";
-// 瀵煎叆鏉愭枡搴撳瓨寮规缁勪欢
-import FormDiaManual from './components/FormDiaManual.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 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 manualDialogVisible = ref(false)
-
-const data = reactive({
- searchForm: {
- // supplierName: '',
- productCategory:'',
- customerName: '',
- timeStr: getCurrentDate(),
+const activeTab = ref('qualified')
+const tabs = ref([
+ {
+ label: '鍚堟牸搴撳瓨',
+ name: 'qualified'
},
- form: {
- supplierId: null,
- 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' }],
- unitPrice: [{ 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' }],
+ {
+ label: '涓嶅悎鏍煎簱瀛�',
+ name: 'unqualified'
}
-})
-const { searchForm, form, rules } = toRefs(data)
+])
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1
- getList()
+const handleTabChange = (tabName) => {
+ activeTab.value = tabName;
}
-const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList()
-}
-const buildQueryParams = () => {
- const params = {
- ...page,
- timeStr: searchForm.value.timeStr,
- }
- params.productCategory = searchForm.value.productCategory
- return params
-}
-
-const getList = () => {
- tableLoading.value = true
- const params = buildQueryParams()
- getStockManagePageByCustom(params).then(res => {
- tableLoading.value = false
- tableData.value = res.data.records
-
- // 涓鸿〃鏍兼暟鎹嚜鍔ㄨ绠楁�讳环
- tableData.value = tableData.value.map(item => {
- // 璁$畻鍓╀綑搴撳瓨
- const stockQuantity = parseFloat(item.inboundNum) || 0
- const outboundQuantity = parseFloat(item.totalInboundNum) || 0
- const remainingStock = Math.max(stockQuantity - outboundQuantity, 0)
-
- // 鏉愭枡搴撳瓨锛氬惈绋庢�讳环 = 鍚◣鍗曚环 脳 鍓╀綑搴撳瓨
- const taxInclusiveUnitPrice = parseFloat(item.taxInclusiveUnitPrice) || 0
- item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * remainingStock).toFixed(2)
-
- return item
- })
-
- total.value = res.data.total
- // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
- // checkStockAndCreatePurchase();
- }).catch(() => {
- tableLoading.value = false
- })
-}
-
-// 琛ㄦ牸閫夋嫨鏁版嵁
-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 }) => {
- const stock = Number(row?.inboundNum0 ?? 0);
- const warn = Number(row?.warnNum ?? 0);
- if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
- return '';
- }
- return stock < warn ? 'row-low-stock' : '';
-};
-
-// 鎵撳紑寮规
-const openForm = async (type, row) => {
- console.log(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() // 璁剧疆榛樿褰曞叆鏃ユ湡涓哄綋鍓嶆棩鏈�
-
- // 浠呮潗鏂欏簱瀛樺脊妗�
- manualDialogVisible.value = true
-}
-
-// 鎻愪氦琛ㄥ崟
-const submitForm = (submittedData) => {
- console.log('瀛愮粍浠舵彁浜ょ殑鏁版嵁:', submittedData)
-
- // 浣跨敤瀛愮粍浠舵彁浜ょ殑鏁版嵁锛岃�屼笉鏄埗缁勪欢鐨刦orm瀵硅薄
- const submitData = { ...submittedData }
-
- // 鏉愭枡搴撳瓨锛氱Щ闄ゅ惈绋庢�讳环瀛楁
- delete submitData.taxInclusiveTotalPrice
- // 绉婚櫎鍏朵粬鍙兘鐨勬�讳环瀛楁
- delete submitData.taxExclusiveTotalPrice
-
- console.log('鎻愪氦缁欏悗绔殑鏁版嵁锛堝凡绉婚櫎鎬讳环瀛楁锛�:', submitData)
-
- // 鏉愭枡搴撳瓨浣跨敤 updateManagementByCustom 鎺ュ彛
- updateManagementByCustom(submitData).then(res => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛")
- closeDia()
- getList()
- // 鎻愪氦鍚庢鏌ュ簱瀛樺苟灏濊瘯鍒涘缓璇疯喘鍗�
- // checkStockAndCreatePurchase();
- }).catch(error => {
- console.error('鎻愪氦澶辫触:', error)
- proxy.$modal.msgError("鎻愪氦澶辫触锛岃閲嶈瘯")
- })
-}
-// 妫�鏌ュ簱瀛樺苟鍒涘缓璇疯喘鍗�
-// 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")
- manualDialogVisible.value = false
-}
-
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm(
- '鏄惁纭瀵煎嚭锛�',
- '瀵煎嚭', {
- confirmButtonText: '纭',
- cancelButtonText: '鍙栨秷',
- type: 'warning',
- }
- ).then(() => {
- const exportParams = buildQueryParams()
- const exportUrl = "/stockin/exportCopyTwo"
- proxy.download(exportUrl, exportParams, '搴撳瓨淇℃伅.xlsx')
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�")
- })
-}
-// 鍒犻櫎
-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(() => {
- 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>
-
-<style scoped lang="scss">
-:deep(.row-low-stock td) {
- background-color: #fde2e2;
- color: #c45656;
-}
-
-:deep(.row-low-stock:hover > td) {
- background-color: #fcd4d4;
-}
-</style>
diff --git a/src/views/inventoryManagement/stockReport/index.vue b/src/views/inventoryManagement/stockReport/index.vue
index 728c1ab..354c775 100644
--- a/src/views/inventoryManagement/stockReport/index.vue
+++ b/src/views/inventoryManagement/stockReport/index.vue
@@ -189,6 +189,12 @@
show-overflow-tooltip
/>
<el-table-column
+ label="渚涘簲鍟嗗悕绉�"
+ prop="supplierName"
+ min-width="240"
+ show-overflow-tooltip
+ />
+ <el-table-column
label="浜у搧澶х被"
prop="productCategory"
width="100"
diff --git a/src/views/lavorissue/ledger/Form.vue b/src/views/lavorissue/ledger/Form.vue
new file mode 100644
index 0000000..785ef7a
--- /dev/null
+++ b/src/views/lavorissue/ledger/Form.vue
@@ -0,0 +1,158 @@
+<template>
+ <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
+ <el-form-item label="閮ㄩ棬鍚嶇О" prop="deptId">
+ <el-select
+ v-model="form.deptId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ disabled
+ >
+ <el-option :label="item.deptName" :value="item.deptId" v-for="(item,index) in productOptions" :key="deptId" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍛樺伐鍚嶇О" prop="staffId">
+ <el-select
+ v-model="form.staffId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option :label="item.staffName" :value="item.id" v-for="(item,index) in personList" :key="id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍔充繚绫诲瀷" prop="dictType">
+ <el-select
+ v-model="form.dictType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in sys_lavor_issue_type" :key="value" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍔充繚闃插叿" prop="dictId">
+ <el-select
+ v-model="form.dictId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in sys_lavor_issue" :key="value" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍙戞斁鏁伴噺" prop="num">
+ <el-input-number :step="1" :min="0" style="width: 100%"
+ v-model="form.num"
+ placeholder="璇疯緭鍏�"
+ />
+ </el-form-item>
+ <el-form-item label="杩涘巶鏃ユ湡" prop="factoryDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.factoryDate"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鍙戞斁鏃ユ湡" prop="issueDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.issueDate"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ clearable
+ />
+ </el-form-item>
+
+ </el-form>
+</template>
+
+<script setup>
+import useFormData from "@/hooks/useFormData";
+import {ref,onMounted} from "vue";
+import useUserStore from "@/store/modules/user";
+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();
+
+
+defineOptions({
+ name: "鏂板鏀跺叆",
+});
+const { sys_lavor_issue } = proxy.useDict("sys_lavor_issue")
+const { sys_lavor_issue_type } = proxy.useDict("sys_lavor_issue_type")
+const formRef = ref(null);
+const productOptions = ref([]);
+const personList = ref([]);
+const formRules = {
+ deptId: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ dictType: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ staffId: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ dictId: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ num: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ adoptedDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ factoryDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ issueDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+}
+
+const { form, resetForm } = useFormData({
+ deptId: undefined, //
+ dictType: undefined,
+ staffId: undefined, //
+ dictId: undefined, //
+ num: undefined, //
+ adoptedDate: undefined,
+ factoryDate: undefined,
+ issueDate: undefined,
+});
+const getPersonList = () => {
+ staffOnJobListPage({
+ current: -1,
+ size: -1,
+ staffState: 1
+ }).then(res => {
+ personList.value = res.data.records
+ })
+};
+const loadForm = (data) => {
+ deepCopySameProperties(data, form)
+};
+
+const getProductOptions = () => {
+ getDept().then((res) => {
+ productOptions.value = res.data;
+ });
+}
+// 娓呴櫎琛ㄥ崟鏍¢獙鐘舵��
+const clearValidate = () => {
+ formRef.value?.clearValidate();
+};
+
+// 閲嶇疆琛ㄥ崟鏁版嵁鍜屾牎楠岀姸鎬�
+const resetFormAndValidate = () => {
+ resetForm();
+ clearValidate();
+ form.deptId = userStore.currentDeptId
+ getProductOptions();
+ getPersonList();
+};
+onMounted(() => {
+ form.deptId = userStore.currentDeptId
+ getProductOptions();
+ getPersonList();
+})
+defineExpose({
+ form,
+ resetForm,
+ clearValidate,
+ loadForm,
+ resetFormAndValidate,
+ formRef,
+});
+</script>
diff --git a/src/views/lavorissue/ledger/Modal.vue b/src/views/lavorissue/ledger/Modal.vue
new file mode 100644
index 0000000..5d63236
--- /dev/null
+++ b/src/views/lavorissue/ledger/Modal.vue
@@ -0,0 +1,70 @@
+<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>
+</template>
+
+<script setup>
+import { useModal } from "@/hooks/useModal";
+import { add, update } from "@/api/lavorissce/ledger";
+import Form from "./Form.vue";
+import { ElMessage } from "element-plus";
+const { proxy } = getCurrentInstance()
+
+defineOptions({
+ name: "鏀跺叆鏂板缂栬緫",
+});
+
+const emits = defineEmits(["success"]);
+
+const formRef = ref();
+const {
+ id,
+ visible,
+ loading,
+ openModal,
+ modalOptions,
+ handleConfirm,
+ closeModal,
+} = useModal({ title: "鍔充繚鍙拌处" });
+
+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;
+ }
+ }
+ })
+};
+
+const close = () => {
+ formRef.value.resetFormAndValidate();
+ closeModal();
+};
+
+const loadForm = async (row) => {
+ openModal(row.id);
+ await nextTick();
+ formRef.value.loadForm(row);
+
+};
+
+defineExpose({
+ openModal,
+ loadForm,
+});
+</script>
diff --git a/src/views/financialManagement/revenueManagement/filesDia.vue b/src/views/lavorissue/ledger/filesDia.vue
similarity index 100%
rename from src/views/financialManagement/revenueManagement/filesDia.vue
rename to src/views/lavorissue/ledger/filesDia.vue
diff --git a/src/views/lavorissue/ledger/index.vue b/src/views/lavorissue/ledger/index.vue
new file mode 100644
index 0000000..19d0e59
--- /dev/null
+++ b/src/views/lavorissue/ledger/index.vue
@@ -0,0 +1,300 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍙戞斁瀛e害:" prop="season">
+ <el-select
+ style="width: 200px;"
+ @change="handleQuery"
+ v-model="filters.season"
+ placeholder="璇烽�夋嫨"
+ :clearable="false"
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="value" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍛樺伐鍚嶇О:">
+ <el-input
+ v-model="filters.staffName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+ </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" text @click="edit(row)" icon="editPen">
+ 缂栬緫
+ </el-button>
+ <el-button type="primary" :disabled="row.adoptedDate ? true : false" text @click="adopted(row)">
+ 棰嗙敤
+ </el-button>
+ </template>
+ </PIMTable>
+ </div>
+ <Modal ref="modalRef" @success="getTableData"></Modal>
+ <files-dia ref="filesDia"></files-dia>
+ </div>
+</template>
+
+<script setup>
+import { usePaginationApi } from "@/hooks/usePaginationApi";
+import { listPage,deleteLedger,update } from "@/api/lavorissce/ledger";
+import { onMounted, getCurrentInstance } from "vue";
+import Modal from "./Modal.vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import dayjs from "dayjs";
+import FilesDia from "./filesDia.vue";
+import { getCurrentMonth } from "@/utils/util"
+
+// 琛ㄦ牸澶氶�夋閫変腑椤�
+const multipleList = ref([]);
+const { proxy } = getCurrentInstance();
+const modalRef = ref();
+const { payment_methods } = proxy.useDict("payment_methods");
+const { income_types } = proxy.useDict("income_types");
+const filesDia = ref()
+
+const {
+ filters,
+ columns,
+ dataList,
+ pagination,
+ getTableData,
+ resetFilters,
+ onCurrentChange,
+} = usePaginationApi(
+ listPage,
+ {
+ staffName: '',
+ season: getCurrentMonth(),
+ },
+ [
+ {
+ label: "鍔充繚鍗曞彿",
+ align: "center",
+ prop: "orderNo",
+ },
+ {
+ label: "鍛樺伐鍚嶇О",
+ align: "center",
+ prop: "staffName",
+ },
+ {
+ label: "鍛樺伐缂栧彿",
+ align: "center",
+ prop: "staffNo"
+ },
+
+ {
+ label: "鍔充繚绫诲瀷",
+ align: "center",
+ prop: "dictTypeName",
+
+ },
+ {
+ label: "鍔充繚闃插叿",
+ align: "center",
+ prop: "dictName",
+
+ },
+ {
+ label: "鍙戞斁鏁伴噺",
+ align: "center",
+ prop: "num",
+
+ },
+ {
+ label: "杩涘巶鏃ユ湡",
+ align: "center",
+ prop: "factoryDate",
+
+ },
+ {
+ label: "鍙戞斁鏃ユ湡",
+ align: "center",
+ prop: "issueDate",
+
+ },
+ {
+ label: "棰嗙敤鏃ユ湡",
+ align: "center",
+ prop: "adoptedDate",
+
+ },
+ {
+ fixed: "right",
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ align: "center",
+ width: "200px",
+ },
+ ]
+);
+
+const jidu = ref([
+ {
+ value: '1',
+ label: '绗竴瀛e害'
+ },
+ {
+ value: '2',
+ label: '绗簩瀛e害'
+ },
+ {
+ value: '3',
+ label: '绗笁瀛e害'
+ },
+ {
+ value: '4',
+ label: '绗洓瀛e害'
+ }
+])
+
+// 澶氶�夊悗鍋氫粈涔�
+const handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
+};
+
+const adopted = (row) => {
+ ElMessageBox.confirm("鏄惁纭棰嗙敤?", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(async () => {
+ const params = {
+ id: row.id,
+ adoptedDate: dayjs().format("YYYY-MM-DD")
+ }
+ const { code } = await update(params);
+ if (code == 200) {
+ ElMessage({
+ type: "success",
+ message: "棰嗙敤鎴愬姛",
+ });
+ getTableData();
+ }
+ })
+}
+
+const add = () => {
+ modalRef.value.openModal();
+};
+const edit = (row) => {
+ modalRef.value.loadForm(row);
+};
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ getTableData();
+};
+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 deleteLedger(id);
+ if (code == 200) {
+ ElMessage({
+ type: "success",
+ message: "鍒犻櫎鎴愬姛",
+ });
+ getTableData();
+ }
+ });
+};
+
+const changeDaterange = (value) => {
+ if (value) {
+ filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ } else {
+ filters.entryDateStart = undefined;
+ filters.entryDateEnd = undefined;
+ }
+ getTableData();
+};
+
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download(`/lavorIssue/export`, {}, "鍔充繚鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鎵撳紑闄勪欢寮规
+const openFilesFormDia = (row) => {
+ nextTick(() => {
+ filesDia.value?.openDialog( row,'鏀跺叆')
+ })
+};
+
+onMounted(() => {
+ filters.entryDate = [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ]
+ filters.entryDateStart = dayjs().format("YYYY-MM-DD")
+ filters.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.table_list {
+ margin-top: unset;
+}
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+</style>
+
diff --git a/src/views/lavorissue/statistics/index.vue b/src/views/lavorissue/statistics/index.vue
new file mode 100644
index 0000000..2c34f67
--- /dev/null
+++ b/src/views/lavorissue/statistics/index.vue
@@ -0,0 +1,285 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">鍙戞斁瀛e害锛�</span>
+ <el-select
+ style="width: 200px;"
+ @change="handleQuery"
+ v-model="searchForm.season"
+ placeholder="璇烽�夋嫨"
+ @clear="clearSeason"
+ clearable
+ :disabled="searchForm.issueDate ? true : false"
+
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="item.value" />
+ </el-select>
+ <span class="search_title ml10">鍙戞斁鏈堜唤锛�</span>
+ <el-date-picker
+ style="width: 200px;"
+ :disabled="searchForm.season ? true : false"
+ v-model="searchForm.issueDate"
+ @change="handleQuery"
+ @clear="clearIssueDaten"
+ type="month"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM"
+ placeholder="璇烽�夋嫨鏈堜唤"
+ clearable
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ <el-button type="primary" @click="resetHandleQuery" style="margin-left: 10px"
+ >閲嶇疆</el-button
+ >
+ </div>
+ <div>
+ <el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <div class="actions">
+ <div class="head" @click="handleQuery(1)">宸查鍙栧姵淇濇暟閲�:{{statisticsObj.ylqNum}}</div>
+ <div class="head" @click="handleQuery(2)">鏈鍙栧姵淇濇暟閲�: {{ statisticsObj.wlqNum }}</div>
+ <div class="head" @click="handleQuery(3)">瓒呮椂宸查鍙栧姵淇濇暟閲�: {{statisticsObj.csylqNum}}</div>
+ <div class="head" @click="handleQuery(4)">瓒呮椂鏈鍙栧姵淇濇暟閲�: {{statisticsObj.cswlqNum}}</div>
+ </div>
+ <el-table
+ ref="tableRef"
+ v-loading="tableLoading"
+ :data="tableData"
+ border
+ height="calc(100vh - 21em)"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ style="width: 100%"
+ @selection-change="handleSelectionChange"
+ >
+ <!-- 閫夋嫨鍒� -->
+ <el-table-column
+ align="center"
+ type="selection"
+ width="55"
+ fixed="left"
+ />
+
+ <!-- 搴忓彿鍒� -->
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ fixed="left"
+ />
+
+ <!-- 鍥哄畾鍒楋細濮撳悕 -->
+ <el-table-column
+ label="濮撳悕"
+ prop="staffName"
+ width="100"
+ show-overflow-tooltip
+ align="center"
+ fixed="left"
+ />
+
+ <!-- 鍥哄畾鍒楋細宸ュ彿 -->
+ <el-table-column
+ label="宸ュ彿"
+ prop="staffNo"
+ width="100"
+ show-overflow-tooltip
+ align="center"
+ fixed="left"
+ />
+
+ <!-- 鍔ㄦ�佸垪锛氭牴鎹瓧鍏告覆鏌� -->
+ <el-table-column
+ v-for="(dictItem, index) in sys_lavor_issue"
+ :key="dictItem.value"
+ :label="dictItem.label"
+ :prop="dictItem.value"
+ show-overflow-tooltip
+ >
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, reactive, toRefs, nextTick, getCurrentInstance } from 'vue'
+import dayjs from "dayjs";
+import {statisticsList, statistics} from "@/api/lavorissce/ledger.js";
+import {ElMessageBox, ElMessage} from "element-plus";
+const { proxy } = getCurrentInstance();
+import { getCurrentMonth } from "@/utils/util"
+
+const page = ref({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+// 鍝嶅簲寮忔暟鎹�
+const tableRef = ref(null)
+const tableData = ref([])
+const tableLoading = ref(false)
+const { sys_lavor_issue } = proxy.useDict("sys_lavor_issue")
+const data = reactive({
+ searchForm: {
+ season: getCurrentMonth(),
+ issueDate: "",
+ status: 0
+ },
+});
+const { searchForm } = toRefs(data);
+
+const modalRef = ref();
+const filesDia = ref();
+const multipleList = ref([]);
+const jidu = ref([
+ {
+ value: '1',
+ label: '绗竴瀛e害'
+ },
+ {
+ value: '2',
+ label: '绗簩瀛e害'
+ },
+ {
+ value: '3',
+ label: '绗笁瀛e害'
+ },
+ {
+ value: '4',
+ label: '绗洓瀛e害'
+ }
+])
+const clearSeason = () => {
+ console.log("req")
+ searchForm.value.season = ""
+ searchForm.value.issueDate = dayjs().format("YYYY-MM-DD");
+}
+
+const clearIssueDaten = () => {
+ searchForm.value.issueDate = ""
+ searchForm.value.season = getCurrentMonth()
+}
+const statisticsObj = ref({
+ ylqNum: 0, // 宸查鍙栨暟閲�
+ wlqNum: 0, // 鏈鍙栨暟閲�
+ csylqNum: 0, // 瓒呮椂宸查鍙栨暟閲�
+ cswlqNum: 0 // 瓒呮椂鏈鍙栨暟閲�
+})
+const resetHandleQuery = () => {
+ searchForm.value.issueDate = "";
+ searchForm.value.season = getCurrentMonth();
+ handleQuery(0)
+};
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = (status) => {
+ switch (status){
+ case 1:
+ searchForm.value.status = 1
+ break;
+ case 2:
+ searchForm.value.status = 2
+ break;
+ case 3:
+ searchForm.value.status = 3
+ break;
+ case 4:
+ searchForm.value.status = 4
+ break;
+ default:
+ searchForm.value.status = 0
+ }
+ getList();
+ getStatistics();
+};
+
+const getStatistics = () => {
+ statistics(searchForm.value).then(res => {
+ statisticsObj.value.cswlqNum = res.data.cswlqNum
+ statisticsObj.value.csylqNum = res.data.csylqNum
+ statisticsObj.value.ylqNum = res.data.ylqNum
+ statisticsObj.value.wlqNum = res.data.wlqNum
+ })
+}
+// 鑾峰彇瀛楀吀鏁版嵁
+const getList = async () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value};
+ statisticsList(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+}
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download(`/lavorIssue/exportCopy`, {season: searchForm.value.season,issueDate: searchForm.value.issueDate}, "鍔充繚鍙拌处.xlsx");
+ })
+ .catch(() => {
+ ElMessage.info("宸插彇娑�");
+ });
+};
+
+// 浜嬩欢澶勭悊鍑芥暟
+const handleSelectionChange = (selection) => {
+ multipleList.value = selection;
+}
+
+// 缁勪欢鎸傝浇鏃跺姞杞藉瓧鍏告暟鎹�
+onMounted(() => {
+ handleQuery()
+})
+</script>
+
+<style scoped>
+.dynamic-table-container {
+ width: 100%;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+ background-color: #F0F1F5 !important;
+ color: #333333;
+ font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+ padding: 8px 0;
+}
+
+:deep(.el-select) {
+ width: 100%;
+}
+
+:deep(.el-input) {
+ width: 100%;
+}
+.actions {
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ margin-bottom: 30px;
+}
+.head{
+ cursor: pointer;
+ font-size: 18px;
+ font-weight: 600;
+}
+</style>
diff --git a/src/views/login.vue b/src/views/login.vue
index 5300637..6217877 100644
--- a/src/views/login.vue
+++ b/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>
diff --git a/src/views/monitorManagement/areaControl/index.vue b/src/views/monitorManagement/areaControl/index.vue
new file mode 100644
index 0000000..1dd5a36
--- /dev/null
+++ b/src/views/monitorManagement/areaControl/index.vue
@@ -0,0 +1,264 @@
+<template>
+ <div class="app-container">
+ <el-row :gutter="16">
+ <el-col :span="16">
+ <el-card shadow="never" class="section-card">
+ <template #header>
+ <div class="card-header">
+ <span>鍖哄煙绠$悊锛堝弻閲嶉棬绂侊級</span>
+ <div class="header-actions">
+ <el-select v-model="selectedPlant" placeholder="閫夋嫨鍘傚尯" size="small" style="width: 160px" @change="filterZones">
+ <el-option v-for="plant in plants" :key="plant.id" :label="plant.name" :value="plant.id" />
+ </el-select>
+ <el-switch v-model="onlyCritical" inline-prompt :active-text="'浠呭叧閿尯'" :inactive-text="'鍏ㄩ儴'" @change="filterZones" />
+ </div>
+ </div>
+ </template>
+ <el-table :data="filteredZones" border style="width: 100%" height="320">
+ <el-table-column type="index" width="60" label="搴忓彿" align="center" />
+ <el-table-column prop="name" label="鍖哄煙鍚嶇О" min-width="160" show-overflow-tooltip />
+ <el-table-column prop="zoneType" label="绫诲瀷" width="120" />
+ <el-table-column label="鍙岄棬鑱斿姩" width="120" align="center">
+ <template #default="{ row }">
+ <el-tag v-if="row.dualAccess" type="success">宸插惎鐢�</el-tag>
+ <el-tag v-else type="info">鏈惎鐢�</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍦ㄧ嚎浜烘暟" width="100" align="center">
+ <template #default="{ row }">{{ row.currentPersons }}</template>
+ </el-table-column>
+ <el-table-column label="瀹夊叏鐘舵��" width="140" align="center">
+ <template #default="{ row }">
+ <el-tag :type="row.status === '姝e父' ? 'success' : row.status === '棰勮' ? 'warning' : 'danger'">
+ {{ row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="180" align="center" fixed="right">
+ <template #default="{ row }">
+ <el-button link type="primary" size="small" @click="toggleDual(row)">
+ {{ row.dualAccess ? '鍋滅敤鍙岄棬' : '鍚敤鍙岄棬' }}
+ </el-button>
+ <el-button link type="success" size="small" @click="openAccessSim(row)">妯℃嫙寮�闂�</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <el-card shadow="never" class="section-card">
+ <template #header>
+ <div class="card-header">
+ <span>鍩硅鑱斿姩锛堟湭瀹屾垚/杩囨湡绂佹杩涘叆锛�</span>
+ <div class="header-actions">
+ <el-input v-model="accessSim.personId" placeholder="浜哄憳宸ュ彿" size="small" style="width: 140px" />
+ <el-select v-model="accessSim.targetZoneId" placeholder="閫夋嫨鐩爣鍖哄煙" size="small" style="width: 180px">
+ <el-option v-for="z in zones" :key="z.id" :label="z.name" :value="z.id" />
+ </el-select>
+ <el-button type="primary" size="small" @click="simulateAccess">妫�楠屽噯鍏�</el-button>
+ </div>
+ </div>
+ </template>
+ <el-descriptions :column="3" border size="small" v-if="accessResult">
+ <el-descriptions-item label="宸ュ彿">{{ accessResult.person.id }}锛坽{ accessResult.person.dept }}锛�</el-descriptions-item>
+ <el-descriptions-item label="鍩硅鐘舵��">
+ <el-tag :type="accessResult.person.training.valid ? 'success' : 'danger'">
+ {{ accessResult.person.training.valid ? '鏈夋晥' : '澶辨晥/鏈畬鎴�' }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鐩爣鍖哄煙">{{ accessResult.zone.name }}</el-descriptions-item>
+ <el-descriptions-item label="鏈�杩戝煿璁�">{{ accessResult.person.training.lastDate }}</el-descriptions-item>
+ <el-descriptions-item label="閫傚矖璇佹湁鏁堟湡">{{ accessResult.person.training.expireDate }}</el-descriptions-item>
+ <el-descriptions-item label="鍑嗗叆缁撴灉">
+ <el-tag :type="accessResult.allowed ? 'success' : 'danger'">{{ accessResult.allowed ? '鍏佽杩涘叆' : '绂佹杩涘叆' }}</el-tag>
+ </el-descriptions-item>
+ </el-descriptions>
+ <el-empty v-else description="璇疯緭鍏ヤ汉鍛樹笌鍖哄煙杩涜妫�楠�" />
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card shadow="never" class="section-card">
+ <template #header>
+ <div class="card-header">
+ <span>浣╂埓璁惧婊炵暀鍛婅锛堝嵄闄╁尯瓒呮椂锛�</span>
+ <div class="header-actions">
+ <el-select v-model="stayThreshold" size="small" style="width: 140px">
+ <el-option :value="10" label="闃堝�� 10 鍒嗛挓" />
+ <el-option :value="20" label="闃堝�� 20 鍒嗛挓" />
+ <el-option :value="30" label="闃堝�� 30 鍒嗛挓" />
+ </el-select>
+ <el-switch v-model="alarmOn" inline-prompt :active-text="'鍛婅寮�'" :inactive-text="'鍛婅鍏�'" />
+ </div>
+ </div>
+ </template>
+ <el-timeline style="max-height: 520px; overflow: auto">
+ <el-timeline-item v-for="(item, idx) in alarms" :key="idx" :type="item.level" :timestamp="item.time">
+ <div class="alarm-item">
+ <div class="title">
+ {{ item.personId }} 路 {{ item.zoneName }} 路 婊炵暀 {{ item.stayMins }} 鍒嗛挓
+ </div>
+ <div class="desc">璁惧锛歿{ item.deviceId }}锛堜俊鍙峰己搴� {{ item.rssi }} dBm锛�</div>
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ <el-dialog v-model="doorSimVisible" title="闂ㄧ寮�闂ㄦā鎷�" width="420px">
+ <el-form :model="doorSim" label-width="90px">
+ <el-form-item label="鍖哄煙">
+ <el-input v-model="doorSim.zoneName" disabled />
+ </el-form-item>
+ <el-form-item label="闂ㄧ1">
+ <el-switch v-model="doorSim.door1" />
+ </el-form-item>
+ <el-form-item label="闂ㄧ2">
+ <el-switch v-model="doorSim.door2" />
+ </el-form-item>
+ <el-alert type="info" show-icon :closable="false" title="鍙岄棬鍧囦负寮�鍚柟鍙�氳" />
+ </el-form>
+ <template #footer>
+ <el-button @click="doorSimVisible = false">鍏抽棴</el-button>
+ <el-button type="primary" :disabled="!(doorSim.door1 && doorSim.door2)" @click="confirmPass">閫氳</el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from "vue";
+
+// 鍘傚尯涓庡尯鍩燂紙鐓ょ偔琛屼笟璇箟銆佸敖閲忚创杩戠湡瀹烇級
+const plants = ref([
+ { id: "P01", name: "涓�鍙烽�夌叅鍘�" },
+ { id: "P02", name: "浜屽彿娲楃叅鍒嗗巶" },
+]);
+const zones = ref([
+ { id: "Z01", plantId: "P01", name: "涓帶瀹�", zoneType: "鎺у埗瀹�", dualAccess: true, currentPersons: 4, status: "姝e父" },
+ { id: "Z02", plantId: "P01", name: "鐓ゅ満A鍖�", zoneType: "鍫嗗瓨鍖�", dualAccess: true, currentPersons: 12, status: "棰勮" },
+ { id: "Z03", plantId: "P01", name: "鍗遍櫓鍝佸簱", zoneType: "鍗卞寲鍝�", dualAccess: true, currentPersons: 1, status: "姝e父" },
+ { id: "Z04", plantId: "P01", name: "楂樺帇閰嶇數瀹�", zoneType: "鐢垫皵闂�", dualAccess: true, currentPersons: 2, status: "姝e父" },
+ { id: "Z05", plantId: "P02", name: "鐨甫寤婂寳娈�", zoneType: "杈撻�佸粖閬�", dualAccess: false, currentPersons: 5, status: "姝e父" },
+ { id: "Z06", plantId: "P02", name: "绛涘垎杞﹂棿", zoneType: "浣滀笟鍖�", dualAccess: false, currentPersons: 9, status: "棰勮" },
+]);
+
+const selectedPlant = ref(plants.value[0].id);
+const onlyCritical = ref(true);
+const filteredZones = ref([]);
+
+function filterZones() {
+ const data = zones.value.filter((z) => z.plantId === selectedPlant.value);
+ filteredZones.value = onlyCritical.value ? data.filter((z) => z.dualAccess) : data;
+}
+
+function toggleDual(row) {
+ row.dualAccess = !row.dualAccess;
+ filterZones();
+}
+
+// 闂ㄧ寮�闂ㄦā鎷�
+const doorSimVisible = ref(false);
+const doorSim = reactive({ zoneId: "", zoneName: "", door1: false, door2: false });
+function openAccessSim(row) {
+ doorSim.zoneId = row.id;
+ doorSim.zoneName = row.name;
+ doorSim.door1 = false;
+ doorSim.door2 = false;
+ doorSimVisible.value = true;
+}
+function confirmPass() {
+ doorSimVisible.value = false;
+}
+
+// 鍩硅鑱斿姩妯℃嫙
+const persons = ref([
+ { id: "EMP1001", dept: "鐢熶骇涓�闃�", training: { valid: true, lastDate: "2025-09-12", expireDate: "2026-09-12" } },
+ { id: "EMP1018", dept: "鏈虹數鐝�", training: { valid: false, lastDate: "2024-07-03", expireDate: "2025-07-03" } },
+ { id: "EMP1022", dept: "瀹夌洃绉�", training: { valid: true, lastDate: "2025-08-01", expireDate: "2026-08-01" } },
+]);
+const accessSim = reactive({ personId: "", targetZoneId: "" });
+const accessResult = ref(null);
+
+function simulateAccess() {
+ const person = persons.value.find((p) => p.id === accessSim.personId);
+ const zone = zones.value.find((z) => z.id === accessSim.targetZoneId);
+ if (!person || !zone) {
+ accessResult.value = null;
+ return;
+ }
+ const allowed = person.training.valid && (zone.zoneType !== "鍗卞寲鍝�" || person.dept === "瀹夌洃绉�");
+ accessResult.value = { allowed, person, zone };
+}
+
+// 浣╂埓璁惧婊炵暀鍛婅锛堝亣鏁版嵁瀹氭椂鎺ㄩ�侊級
+const stayThreshold = ref(20);
+const alarmOn = ref(true);
+const alarms = ref([
+ { time: "09:35", level: "warning", personId: "EMP1001", zoneName: "鐓ゅ満A鍖�", stayMins: 18, deviceId: "TAG-7A12", rssi: -67 },
+]);
+
+let timer = null;
+function pushMockAlarm() {
+ if (!alarmOn.value) return;
+ const candidates = [
+ { personId: "EMP1018", zoneName: "绛涘垎杞﹂棿", base: 12 },
+ { personId: "EMP1022", zoneName: "楂樺帇閰嶇數瀹�", base: 9 },
+ { personId: "EMP1001", zoneName: "鐓ゅ満A鍖�", base: 16 },
+ ];
+ const pick = candidates[Math.floor(Math.random() * candidates.length)];
+ const stay = pick.base + Math.floor(Math.random() * 10);
+ if (stay >= stayThreshold.value) {
+ const now = new Date();
+ const hh = String(now.getHours()).padStart(2, "0");
+ const mm = String(now.getMinutes()).padStart(2, "0");
+ alarms.value.unshift({
+ time: `${hh}:${mm}`,
+ level: stay >= stayThreshold.value + 10 ? "danger" : "warning",
+ personId: pick.personId,
+ zoneName: pick.zoneName,
+ stayMins: stay,
+ deviceId: `TAG-${Math.random().toString(16).slice(2, 6).toUpperCase()}`,
+ rssi: -60 - Math.floor(Math.random() * 15),
+ });
+ if (alarms.value.length > 30) alarms.value.pop();
+ }
+}
+
+onMounted(() => {
+ filterZones();
+ timer = setInterval(pushMockAlarm, 4500);
+});
+
+// 绂诲紑鏃舵竻鐞�
+if (import.meta.hot) {
+ import.meta.hot.dispose(() => {
+ if (timer) clearInterval(timer);
+ });
+}
+</script>
+
+<style scoped lang="scss">
+.section-card {
+ margin-bottom: 16px;
+}
+.card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.header-actions {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+.alarm-item .title {
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+.alarm-item .desc {
+ color: #666;
+ font-size: 12px;
+}
+</style>
+
+
diff --git a/src/views/monitorManagement/videoMonitor/index.vue b/src/views/monitorManagement/videoMonitor/index.vue
new file mode 100644
index 0000000..b78c7f5
--- /dev/null
+++ b/src/views/monitorManagement/videoMonitor/index.vue
@@ -0,0 +1,990 @@
+<template>
+ <div class="app-container">
+ <el-row :gutter="16">
+ <!-- 宸︿晶锛氳棰戠洃鎺у垪琛ㄤ笌鎶撴媿璁板綍 -->
+ <el-col :span="16">
+ <!-- 瑙嗛鐩戞帶鍒楄〃 -->
+ <el-card shadow="never" class="section-card">
+ <template #header>
+ <div class="card-header">
+ <span>瑙嗛鐩戞帶鐐逛綅绠$悊</span>
+ <div class="header-actions">
+ <el-select v-model="selectedArea" placeholder="閫夋嫨鍖哄煙" size="small" style="width: 160px" @change="filterCameras">
+ <el-option label="鍏ㄩ儴鍖哄煙" value="all" />
+ <el-option v-for="area in areas" :key="area.id" :label="area.name" :value="area.id" />
+ </el-select>
+ <el-select v-model="cameraStatus" placeholder="璁惧鐘舵��" size="small" style="width: 120px" @change="filterCameras">
+ <el-option label="鍏ㄩ儴鐘舵��" value="all" />
+ <el-option label="鍦ㄧ嚎" value="online" />
+ <el-option label="绂荤嚎" value="offline" />
+ </el-select>
+ </div>
+ </div>
+ </template>
+ <el-table :data="filteredCameras" border style="width: 100%" max-height="320">
+ <el-table-column type="index" width="50" label="搴忓彿" align="center" />
+ <el-table-column prop="name" label="鐩戞帶鐐逛綅" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="areaName" label="鎵�灞炲尯鍩�" width="120" />
+ <el-table-column label="璁惧鐘舵��" width="100" align="center">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 'online' ? 'success' : 'danger'" size="small">
+ {{ row.status === 'online' ? '鍦ㄧ嚎' : '绂荤嚎' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="AI璇嗗埆" width="100" align="center">
+ <template #default="{ row }">
+ <el-tag v-if="row.aiEnabled" type="success" size="small">宸插惎鐢�</el-tag>
+ <el-tag v-else type="info" size="small">鏈惎鐢�</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="闂ㄧ鑱斿姩" width="100" align="center">
+ <template #default="{ row }">
+ <el-tag v-if="row.doorLinked" type="primary" size="small">宸茬粦瀹�</el-tag>
+ <el-tag v-else type="info" size="small">鏈粦瀹�</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="180" align="center" fixed="right">
+ <template #default="{ row }">
+ <el-button link type="primary" size="small" @click="viewRealtime(row)">瀹炴椂鐢婚潰</el-button>
+ <el-button link type="success" size="small" @click="viewCaptures(row)">鎶撴媿璁板綍</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 闂ㄧ鎶撴媿璁板綍 -->
+ <el-card shadow="never" class="section-card">
+ <template #header>
+ <div class="card-header">
+ <span>闂ㄧ鎶撴媿璁板綍</span>
+ <div class="header-actions">
+ <el-date-picker
+ v-model="captureDate"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ size="small"
+ style="width: 260px"
+ @change="loadCaptures"
+ />
+ <el-select v-model="captureEventType" placeholder="浜嬩欢绫诲瀷" size="small" style="width: 100px" clearable>
+ <el-option label="鍏ㄩ儴" value="" />
+ <el-option label="杩涘叆" value="entry" />
+ <el-option label="绂诲紑" value="exit" />
+ </el-select>
+ <el-input v-model="captureSearch" placeholder="鎼滅储宸ュ彿/鍖哄煙" size="small" style="width: 140px" clearable>
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ <el-button type="primary" size="small" @click="exportCaptures">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭
+ </el-button>
+ </div>
+ </div>
+ </template>
+
+ <!-- 缁熻淇℃伅 -->
+ <div class="capture-stats">
+ <el-row :gutter="16">
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-label">浠婃棩鎶撴媿</div>
+ <div class="stat-value">{{ captureStats.today }}</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-label">杩涘叆璁板綍</div>
+ <div class="stat-value" style="color: #67c23a">{{ captureStats.entry }}</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-label">绂诲紑璁板綍</div>
+ <div class="stat-value" style="color: #e6a23c">{{ captureStats.exit }}</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-label">浣庡尮閰嶅害</div>
+ <div class="stat-value" style="color: #f56c6c">{{ captureStats.lowMatch }}</div>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+
+ <el-table
+ :data="paginatedCaptures"
+ border
+ style="width: 100%"
+ max-height="350"
+ @selection-change="handleCaptureSelection"
+ >
+ <el-table-column type="selection" width="45" align="center" />
+ <el-table-column type="index" width="50" label="搴忓彿" align="center" :index="indexMethod" />
+ <el-table-column prop="time" label="鎶撴媿鏃堕棿" width="155" sortable />
+ <el-table-column prop="personId" label="宸ュ彿" width="110" show-overflow-tooltip />
+ <el-table-column prop="department" label="閮ㄩ棬" width="100" show-overflow-tooltip />
+ <el-table-column prop="areaName" label="鍖哄煙" width="110" show-overflow-tooltip />
+ <el-table-column label="闂ㄧ浜嬩欢" width="90" align="center">
+ <template #default="{ row }">
+ <el-tag :type="row.eventType === 'entry' ? 'success' : 'warning'" size="small">
+ {{ row.eventType === 'entry' ? '杩涘叆' : '绂诲紑' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="浜鸿劯鍖归厤" width="95" align="center" sortable :sort-method="(a, b) => a.faceMatch - b.faceMatch">
+ <template #default="{ row }">
+ <el-tag :type="getFaceMatchType(row.faceMatch)" size="small">
+ {{ row.faceMatch }}%
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="cameraName" label="鎽勫儚澶�" width="120" show-overflow-tooltip />
+ <el-table-column label="鎿嶄綔" width="150" align="center" fixed="right">
+ <template #default="{ row }">
+ <el-button link type="primary" size="small" @click="viewSnapshot(row)">
+ <el-icon><View /></el-icon>
+ 鏌ョ湅
+ </el-button>
+ <el-button link type="success" size="small" @click="downloadSnapshot(row)">
+ <el-icon><Download /></el-icon>
+ 涓嬭浇
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-container">
+ <el-pagination
+ v-model:current-page="capturePage"
+ v-model:page-size="capturePageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="filteredCaptures.length"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </el-col>
+
+ <!-- 鍙充晶锛氳繚瑙勫憡璀� -->
+ <el-col :span="8">
+ <el-card shadow="never" class="section-card">
+ <template #header>
+ <div class="card-header">
+ <span>杩濊琛屼负鍛婅</span>
+ <div class="header-actions">
+ <el-badge :value="unreadAlarmCount" :max="99" type="danger">
+ <el-switch v-model="alarmEnabled" inline-prompt active-text="鍛婅寮�" inactive-text="鍛婅鍏�" />
+ </el-badge>
+ </div>
+ </div>
+ </template>
+ <el-tabs v-model="alarmTab" type="border-card" style="height: 650px">
+ <el-tab-pane label="鍏ㄩ儴鍛婅" name="all">
+ <div>
+ <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+ <el-timeline-item
+ v-for="(alarm, idx) in displayAlarms"
+ :key="idx"
+ :type="getAlarmType(alarm.type)"
+ :timestamp="alarm.time"
+ :hollow="alarm.handled"
+ >
+ <div class="alarm-item" :class="{ handled: alarm.handled }">
+ <div class="alarm-header">
+ <el-tag :type="getAlarmTagType(alarm.type)" size="small">{{ alarm.typeText }}</el-tag>
+ <span class="alarm-area">{{ alarm.areaName }}</span>
+ </div>
+ <div class="alarm-content">
+ {{ alarm.description }}
+ </div>
+ <div class="alarm-footer">
+ <span class="alarm-camera">鎽勫儚澶�: {{ alarm.cameraName }}</span>
+ <el-button
+ v-if="!alarm.handled"
+ link
+ type="primary"
+ size="small"
+ @click="handleAlarm(alarm)"
+ >
+ 澶勭悊
+ </el-button>
+ <span v-else class="handled-text">宸插鐞�</span>
+ </div>
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ </div>
+ </el-tab-pane>
+ <el-tab-pane label="寮洪棷鍛婅" name="intrusion">
+ <div>
+ <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+ <el-timeline-item
+ v-for="(alarm, idx) in intrusionAlarms"
+ :key="idx"
+ type="danger"
+ :timestamp="alarm.time"
+ >
+ <div class="alarm-item">
+ <div class="alarm-header">
+ <el-tag type="danger" size="small">寮洪棷鍛婅</el-tag>
+ <span class="alarm-area">{{ alarm.areaName }}</span>
+ </div>
+ <div class="alarm-content">{{ alarm.description }}</div>
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ </div>
+ </el-tab-pane>
+ <el-tab-pane label="灏鹃殢鍛婅" name="tailgating">
+ <div>
+ <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+ <el-timeline-item
+ v-for="(alarm, idx) in tailgatingAlarms"
+ :key="idx"
+ type="warning"
+ :timestamp="alarm.time"
+ >
+ <div class="alarm-item">
+ <div class="alarm-header">
+ <el-tag type="warning" size="small">灏鹃殢鍛婅</el-tag>
+ <span class="alarm-area">{{ alarm.areaName }}</span>
+ </div>
+ <div class="alarm-content">{{ alarm.description }}</div>
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ </div>
+ </el-tab-pane>
+ <el-tab-pane label="澶氫汉閫氳" name="multiple">
+ <div>
+ <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+ <el-timeline-item
+ v-for="(alarm, idx) in multipleAlarms"
+ :key="idx"
+ type="warning"
+ :timestamp="alarm.time"
+ >
+ <div class="alarm-item">
+ <div class="alarm-header">
+ <el-tag type="warning" size="small">澶氫汉閫氳</el-tag>
+ <span class="alarm-area">{{ alarm.areaName }}</span>
+ </div>
+ <div class="alarm-content">{{ alarm.description }}</div>
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 瀹炴椂鐢婚潰瀵硅瘽妗� -->
+ <el-dialog v-model="realtimeVisible" :title="`瀹炴椂鐩戞帶 - ${currentCamera.name}`" width="800px">
+ <div class="video-container">
+ <div class="video-placeholder">
+ <el-icon :size="80" color="#909399"><VideoCameraFilled /></el-icon>
+ <div class="video-info">
+ <p>鎽勫儚澶�: {{ currentCamera.name }}</p>
+ <p>浣嶇疆: {{ currentCamera.areaName }}</p>
+ <p>鐘舵��: <el-tag :type="currentCamera.status === 'online' ? 'success' : 'danger'" size="small">{{ currentCamera.status === 'online' ? '鍦ㄧ嚎' : '绂荤嚎' }}</el-tag></p>
+ <p class="tip">锛堝疄闄呯幆澧冨皢鏄剧ず瀹炴椂瑙嗛娴侊級</p>
+ </div>
+ </div>
+ </div>
+ <template #footer>
+ <el-button @click="realtimeVisible = false">鍏抽棴</el-button>
+ <el-button type="primary" @click="captureSnapshot">鎵嬪姩鎶撴媿</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 蹇収鏌ョ湅瀵硅瘽妗� -->
+ <el-dialog v-model="snapshotVisible" title="鎶撴媿蹇収璇︽儏" width="800px" :close-on-click-modal="false">
+ <div class="snapshot-dialog-body">
+ <el-row :gutter="20">
+ <el-col :span="14">
+ <div class="snapshot-preview">
+ <div class="snapshot-placeholder">
+ <el-icon :size="80" color="#909399"><Picture /></el-icon>
+ <p>鎶撴媿鍥剧墖棰勮</p>
+ <p class="tip">锛堝疄闄呯幆澧冨皢鏄剧ず楂樻竻鎶撴媿鍥剧墖锛�</p>
+ <div class="snapshot-tools">
+ <el-button-group>
+ <el-button size="small">
+ <el-icon><ZoomIn /></el-icon>
+ 鏀惧ぇ
+ </el-button>
+ <el-button size="small">
+ <el-icon><ZoomOut /></el-icon>
+ 缂╁皬
+ </el-button>
+ <el-button size="small">
+ <el-icon><RefreshRight /></el-icon>
+ 鏃嬭浆
+ </el-button>
+ </el-button-group>
+ </div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="10">
+ <el-descriptions :column="1" border size="small">
+ <el-descriptions-item label="鎶撴媿鏃堕棿">
+ <el-icon><Clock /></el-icon>
+ {{ currentSnapshot.time }}
+ </el-descriptions-item>
+ <el-descriptions-item label="宸ュ彿">
+ <el-icon><User /></el-icon>
+ {{ currentSnapshot.personId }}
+ </el-descriptions-item>
+ <el-descriptions-item label="閮ㄩ棬">
+ {{ currentSnapshot.department }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎵�灞炲尯鍩�">
+ <el-icon><Location /></el-icon>
+ {{ currentSnapshot.areaName }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎽勫儚澶�">
+ <el-icon><VideoCameraFilled /></el-icon>
+ {{ currentSnapshot.cameraName }}
+ </el-descriptions-item>
+ <el-descriptions-item label="浜嬩欢绫诲瀷">
+ <el-tag :type="currentSnapshot.eventType === 'entry' ? 'success' : 'warning'" size="small">
+ {{ currentSnapshot.eventType === 'entry' ? '杩涘叆' : '绂诲紑' }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="浜鸿劯鍖归厤搴�">
+ <el-progress
+ :percentage="currentSnapshot.faceMatch"
+ :color="currentSnapshot.faceMatch >= 90 ? '#67c23a' : '#e6a23c'"
+ :stroke-width="12"
+ >
+ <span style="font-size: 12px">{{ currentSnapshot.faceMatch }}%</span>
+ </el-progress>
+ </el-descriptions-item>
+ <el-descriptions-item label="浣撴俯妫�娴�">
+ <span :style="{ color: currentSnapshot.temperature > 37.3 ? '#f56c6c' : '#67c23a' }">
+ {{ currentSnapshot.temperature }}掳C
+ </span>
+ <el-tag v-if="currentSnapshot.temperature > 37.3" type="danger" size="small" style="margin-left: 8px">
+ 寮傚父
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍙g僵浣╂埓">
+ <el-tag :type="currentSnapshot.maskWearing ? 'success' : 'warning'" size="small">
+ {{ currentSnapshot.maskWearing ? '宸蹭僵鎴�' : '鏈僵鎴�' }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="瀹夊叏甯�">
+ <el-tag :type="currentSnapshot.helmetWearing ? 'success' : 'danger'" size="small">
+ {{ currentSnapshot.helmetWearing ? '宸蹭僵鎴�' : '鏈僵鎴�' }}
+ </el-tag>
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-col>
+ </el-row>
+
+ <div class="snapshot-notes">
+ <el-divider content-position="left">澶囨敞淇℃伅</el-divider>
+ <el-input
+ v-model="currentSnapshot.notes"
+ type="textarea"
+ :rows="3"
+ placeholder="娣诲姞澶囨敞淇℃伅..."
+ />
+ </div>
+
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer-custom">
+ <div>
+ <el-button size="small" @click="printSnapshot">
+ <el-icon><Printer /></el-icon>
+ 鎵撳嵃
+ </el-button>
+ </div>
+ <div>
+ <el-button @click="snapshotVisible = false">鍏抽棴</el-button>
+ <el-button type="success" @click="downloadSnapshot(currentSnapshot)">
+ <el-icon><Download /></el-icon>
+ 涓嬭浇鍥剧墖
+ </el-button>
+ <el-button type="primary" @click="saveNotes">淇濆瓨澶囨敞</el-button>
+ </div>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted, onBeforeUnmount } from "vue";
+import {
+ VideoCameraFilled, Picture, Search, Download, View,
+ Clock, User, Location, ZoomIn, ZoomOut, RefreshRight, Printer
+} from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+// 鍖哄煙鏁版嵁
+const areas = ref([
+ { id: "A01", name: "鐓ゅ満鍏ュ彛" },
+ { id: "A02", name: "娲楃叅杞﹂棿" },
+ { id: "A03", name: "鍗遍櫓鍝佸簱" },
+ { id: "A04", name: "涓帶瀹�" },
+ { id: "A05", name: "閰嶇數瀹�" },
+]);
+
+// 鎽勫儚澶存暟鎹�
+const cameras = ref([
+ { id: "C001", name: "鐓ゅ満鍏ュ彛涓滈棬", areaId: "A01", areaName: "鐓ゅ満鍏ュ彛", status: "online", aiEnabled: true, doorLinked: true },
+ { id: "C002", name: "鐓ゅ満鍏ュ彛瑗块棬", areaId: "A01", areaName: "鐓ゅ満鍏ュ彛", status: "online", aiEnabled: true, doorLinked: true },
+ { id: "C003", name: "娲楃叅杞﹂棿涓婚�氶亾", areaId: "A02", areaName: "娲楃叅杞﹂棿", status: "online", aiEnabled: true, doorLinked: true },
+ { id: "C004", name: "娲楃叅杞﹂棿涓滀晶", areaId: "A02", areaName: "娲楃叅杞﹂棿", status: "online", aiEnabled: false, doorLinked: false },
+ { id: "C005", name: "鍗遍櫓鍝佸簱澶ч棬", areaId: "A03", areaName: "鍗遍櫓鍝佸簱", status: "online", aiEnabled: true, doorLinked: true },
+ { id: "C006", name: "鍗遍櫓鍝佸簱鍐呴儴", areaId: "A03", areaName: "鍗遍櫓鍝佸簱", status: "offline", aiEnabled: true, doorLinked: false },
+ { id: "C007", name: "涓帶瀹ゅ叆鍙�", areaId: "A04", areaName: "涓帶瀹�", status: "online", aiEnabled: true, doorLinked: true },
+ { id: "C008", name: "閰嶇數瀹ら棬绂�", areaId: "A05", areaName: "閰嶇數瀹�", status: "online", aiEnabled: true, doorLinked: true },
+]);
+
+const selectedArea = ref("all");
+const cameraStatus = ref("all");
+const filteredCameras = ref([]);
+
+function filterCameras() {
+ let result = cameras.value;
+ if (selectedArea.value !== "all") {
+ result = result.filter(c => c.areaId === selectedArea.value);
+ }
+ if (cameraStatus.value !== "all") {
+ result = result.filter(c => c.status === cameraStatus.value);
+ }
+ filteredCameras.value = result;
+}
+
+// 闂ㄧ鎶撴媿璁板綍
+const captureDate = ref([new Date(), new Date()]);
+const captureSearch = ref("");
+const captureEventType = ref("");
+const capturePage = ref(1);
+const capturePageSize = ref(10);
+const selectedCaptures = ref([]);
+
+// 鐢熸垚鏇村妯℃嫙鏁版嵁
+const generateCaptureData = () => {
+ const departments = ["鐢熶骇涓�闃�", "鏈虹數鐝�", "瀹夌洃绉�", "璋冨害瀹�", "鍖栭獙瀹�", "杩愯緭闃�", "缁翠慨鐝�", "浠撳偍绉�"];
+ const areaList = ["鐓ゅ満鍏ュ彛", "娲楃叅杞﹂棿", "鍗遍櫓鍝佸簱", "涓帶瀹�", "閰嶇數瀹�"];
+ const cameraList = [
+ "鐓ゅ満鍏ュ彛涓滈棬", "鐓ゅ満鍏ュ彛瑗块棬", "娲楃叅杞﹂棿涓婚�氶亾", "娲楃叅杞﹂棿涓滀晶",
+ "鍗遍櫓鍝佸簱澶ч棬", "鍗遍櫓鍝佸簱鍐呴儴", "涓帶瀹ゅ叆鍙�", "閰嶇數瀹ら棬绂�"
+ ];
+
+ const data = [];
+ const now = new Date();
+
+ for (let i = 0; i < 86; i++) {
+ const randomMinutes = Math.floor(Math.random() * 1440); // 24灏忔椂鍐�
+ const captureTime = new Date(now.getTime() - randomMinutes * 60 * 1000);
+ const hh = String(captureTime.getHours()).padStart(2, "0");
+ const mm = String(captureTime.getMinutes()).padStart(2, "0");
+ const ss = String(captureTime.getSeconds()).padStart(2, "0");
+
+ const area = areaList[Math.floor(Math.random() * areaList.length)];
+ const eventType = Math.random() > 0.5 ? "entry" : "exit";
+ const faceMatch = Math.floor(Math.random() * 15) + 85; // 85-100
+ const temperature = (36.0 + Math.random() * 1.5).toFixed(1);
+
+ data.push({
+ id: i + 1,
+ time: `2025-10-28 ${hh}:${mm}:${ss}`,
+ personId: `EMP${String(1001 + i).padStart(4, '0')}`,
+ department: departments[Math.floor(Math.random() * departments.length)],
+ areaName: area,
+ cameraName: cameraList[Math.floor(Math.random() * cameraList.length)],
+ eventType: eventType,
+ faceMatch: faceMatch,
+ temperature: parseFloat(temperature),
+ maskWearing: Math.random() > 0.1,
+ helmetWearing: Math.random() > 0.15,
+ notes: ""
+ });
+ }
+
+ return data.sort((a, b) => b.time.localeCompare(a.time));
+};
+
+const captures = ref(generateCaptureData());
+
+// 缁熻淇℃伅
+const captureStats = computed(() => {
+ const today = captures.value.length;
+ const entry = captures.value.filter(c => c.eventType === 'entry').length;
+ const exit = captures.value.filter(c => c.eventType === 'exit').length;
+ const lowMatch = captures.value.filter(c => c.faceMatch < 90).length;
+
+ return { today, entry, exit, lowMatch };
+});
+
+// 杩囨护鎶撴媿璁板綍
+const filteredCaptures = computed(() => {
+ let result = captures.value;
+
+ // 鎸夊叧閿瘝鎼滅储
+ if (captureSearch.value) {
+ const keyword = captureSearch.value.toLowerCase();
+ result = result.filter(c =>
+ c.personId.toLowerCase().includes(keyword) ||
+ c.areaName.toLowerCase().includes(keyword)
+ );
+ }
+
+ // 鎸変簨浠剁被鍨嬭繃婊�
+ if (captureEventType.value) {
+ result = result.filter(c => c.eventType === captureEventType.value);
+ }
+
+ return result;
+});
+
+// 鍒嗛〉鏁版嵁
+const paginatedCaptures = computed(() => {
+ const start = (capturePage.value - 1) * capturePageSize.value;
+ const end = start + capturePageSize.value;
+ return filteredCaptures.value.slice(start, end);
+});
+
+// 鍒嗛〉绱㈠紩鏂规硶
+const indexMethod = (index) => {
+ return (capturePage.value - 1) * capturePageSize.value + index + 1;
+};
+
+function loadCaptures() {
+ ElMessage.success("宸插姞杞介�夊畾鏃ユ湡鑼冨洿鐨勬姄鎷嶈褰�");
+}
+
+function handleSizeChange(val) {
+ capturePageSize.value = val;
+ capturePage.value = 1;
+}
+
+function handleCurrentChange(val) {
+ capturePage.value = val;
+}
+
+function handleCaptureSelection(selection) {
+ selectedCaptures.value = selection;
+}
+
+function getFaceMatchType(faceMatch) {
+ if (faceMatch >= 95) return 'success';
+ if (faceMatch >= 90) return 'info';
+ if (faceMatch >= 85) return 'warning';
+ return 'danger';
+}
+
+// 瀵煎嚭鎶撴媿璁板綍
+function exportCaptures() {
+ if (filteredCaptures.value.length === 0) {
+ ElMessage.warning("娌℃湁鍙鍑虹殑璁板綍");
+ return;
+ }
+ ElMessage.success(`姝e湪瀵煎嚭 ${filteredCaptures.value.length} 鏉℃姄鎷嶈褰�...`);
+ // 瀹為檯鐜涓繖閲屼細璋冪敤瀵煎嚭鎺ュ彛
+}
+
+// 涓嬭浇蹇収
+function downloadSnapshot(capture) {
+ ElMessage.success(`姝e湪涓嬭浇 ${capture.personId} 鐨勬姄鎷嶅浘鐗�...`);
+ // 瀹為檯鐜涓繖閲屼細涓嬭浇鍥剧墖鏂囦欢
+}
+
+// 鎵撳嵃蹇収
+function printSnapshot() {
+ ElMessage.info("姝e湪鍑嗗鎵撳嵃...");
+ // 瀹為檯鐜涓繖閲屼細璋冪敤鎵撳嵃鍔熻兘
+}
+
+// 淇濆瓨澶囨敞
+function saveNotes() {
+ ElMessage.success("澶囨敞淇℃伅宸蹭繚瀛�");
+ snapshotVisible.value = false;
+}
+
+// 鍛婅鏁版嵁
+const alarmEnabled = ref(true);
+const alarmTab = ref("all");
+const alarms = ref([
+ {
+ id: 1,
+ time: "09:25:15",
+ type: "intrusion",
+ typeText: "寮洪棷鍛婅",
+ areaName: "鍗遍櫓鍝佸簱",
+ cameraName: "鍗遍櫓鍝佸簱澶ч棬",
+ description: "妫�娴嬪埌鏈巿鏉冧汉鍛樺己琛岄棷鍏ワ紝鏈埛鍗$洿鎺ュ紑闂�",
+ handled: false
+ },
+ {
+ id: 2,
+ time: "09:18:42",
+ type: "tailgating",
+ typeText: "灏鹃殢鍛婅",
+ areaName: "涓帶瀹�",
+ cameraName: "涓帶瀹ゅ叆鍙�",
+ description: "妫�娴嬪埌灏鹃殢琛屼负锛屼竴浜哄埛鍗″悗涓や汉杩涘叆",
+ handled: false
+ },
+ {
+ id: 3,
+ time: "09:10:28",
+ type: "multiple",
+ typeText: "澶氫汉閫氳",
+ areaName: "鐓ゅ満鍏ュ彛",
+ cameraName: "鐓ゅ満鍏ュ彛涓滈棬",
+ description: "妫�娴嬪埌3浜哄悓鏃堕�氳繃鍗曚汉闂ㄧ閫氶亾",
+ handled: true
+ },
+]);
+
+const unreadAlarmCount = computed(() => alarms.value.filter(a => !a.handled).length);
+
+const displayAlarms = computed(() => {
+ if (alarmTab.value === "all") return alarms.value;
+ return alarms.value.filter(a => a.type === alarmTab.value);
+});
+
+const intrusionAlarms = computed(() => alarms.value.filter(a => a.type === "intrusion"));
+const tailgatingAlarms = computed(() => alarms.value.filter(a => a.type === "tailgating"));
+const multipleAlarms = computed(() => alarms.value.filter(a => a.type === "multiple"));
+
+function getAlarmType(type) {
+ const typeMap = { intrusion: "danger", tailgating: "warning", multiple: "warning" };
+ return typeMap[type] || "info";
+}
+
+function getAlarmTagType(type) {
+ const typeMap = { intrusion: "danger", tailgating: "warning", multiple: "warning" };
+ return typeMap[type] || "info";
+}
+
+function handleAlarm(alarm) {
+ alarm.handled = true;
+ ElMessage.success("鍛婅宸叉爣璁颁负宸插鐞�");
+}
+
+// 瀹炴椂鐢婚潰
+const realtimeVisible = ref(false);
+const currentCamera = ref({});
+
+function viewRealtime(camera) {
+ currentCamera.value = camera;
+ realtimeVisible.value = true;
+}
+
+function captureSnapshot() {
+ ElMessage.success("鎶撴媿鎴愬姛锛屽凡淇濆瓨鑷虫姄鎷嶈褰�");
+}
+
+// 鏌ョ湅鎶撴媿璁板綍璇︽儏
+const snapshotVisible = ref(false);
+const currentSnapshot = ref({});
+
+function viewSnapshot(capture) {
+ currentSnapshot.value = capture;
+ snapshotVisible.value = true;
+}
+
+function viewCaptures(camera) {
+ ElMessage.info(`鏌ョ湅 ${camera.name} 鐨勫巻鍙叉姄鎷嶈褰昤);
+}
+
+// 妯℃嫙鍛婅鎺ㄩ��
+let alarmTimer = null;
+const alarmTemplates = [
+ { type: "intrusion", typeText: "寮洪棷鍛婅", areas: ["鍗遍櫓鍝佸簱", "閰嶇數瀹�", "涓帶瀹�"] },
+ { type: "tailgating", typeText: "灏鹃殢鍛婅", areas: ["涓帶瀹�", "閰嶇數瀹�", "鍗遍櫓鍝佸簱"] },
+ { type: "multiple", typeText: "澶氫汉閫氳", areas: ["鐓ゅ満鍏ュ彛", "娲楃叅杞﹂棿"] },
+];
+
+function pushMockAlarm() {
+ if (!alarmEnabled.value) return;
+
+ const template = alarmTemplates[Math.floor(Math.random() * alarmTemplates.length)];
+ const area = template.areas[Math.floor(Math.random() * template.areas.length)];
+ const camera = cameras.value.find(c => c.areaName === area);
+
+ const now = new Date();
+ const hh = String(now.getHours()).padStart(2, "0");
+ const mm = String(now.getMinutes()).padStart(2, "0");
+ const ss = String(now.getSeconds()).padStart(2, "0");
+
+ let description = "";
+ if (template.type === "intrusion") {
+ description = "妫�娴嬪埌鏈巿鏉冧汉鍛樺己琛岄棷鍏ワ紝鏈埛鍗$洿鎺ュ紑闂�";
+ } else if (template.type === "tailgating") {
+ description = "妫�娴嬪埌灏鹃殢琛屼负锛屼竴浜哄埛鍗″悗涓や汉杩涘叆";
+ } else if (template.type === "multiple") {
+ const count = Math.floor(Math.random() * 3) + 2;
+ description = `妫�娴嬪埌${count}浜哄悓鏃堕�氳繃鍗曚汉闂ㄧ閫氶亾`;
+ }
+
+ alarms.value.unshift({
+ id: Date.now(),
+ time: `${hh}:${mm}:${ss}`,
+ type: template.type,
+ typeText: template.typeText,
+ areaName: area,
+ cameraName: camera ? camera.name : area + "鐩戞帶",
+ description: description,
+ handled: false
+ });
+
+ // 闄愬埗鍛婅鍒楄〃闀垮害
+ if (alarms.value.length > 50) {
+ alarms.value = alarms.value.slice(0, 50);
+ }
+}
+
+onMounted(() => {
+ filterCameras();
+ // 姣忛殧8绉掓ā鎷熶竴娆″憡璀�
+ alarmTimer = setInterval(pushMockAlarm, 8000);
+});
+
+onBeforeUnmount(() => {
+ if (alarmTimer) {
+ clearInterval(alarmTimer);
+ }
+});
+</script>
+
+<style scoped lang="scss">
+.section-card {
+ margin-bottom: 16px;
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.header-actions {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.alarm-item {
+ padding: 8px;
+ background: #f5f7fa;
+ border-radius: 4px;
+
+ &.handled {
+ opacity: 0.6;
+ }
+}
+
+.alarm-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 6px;
+}
+
+.alarm-area {
+ font-weight: 600;
+ color: #303133;
+}
+
+.alarm-content {
+ color: #606266;
+ font-size: 14px;
+ margin-bottom: 6px;
+ line-height: 1.5;
+}
+
+.alarm-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 12px;
+ color: #909399;
+}
+
+.alarm-camera {
+ flex: 1;
+}
+
+.handled-text {
+ color: #67c23a;
+ font-size: 12px;
+}
+
+.video-container {
+ width: 100%;
+ height: 450px;
+ background: #000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.video-placeholder {
+ text-align: center;
+ color: #909399;
+}
+
+.video-info {
+ margin-top: 20px;
+
+ p {
+ margin: 8px 0;
+ font-size: 14px;
+ }
+
+ .tip {
+ color: #c0c4cc;
+ font-size: 12px;
+ margin-top: 16px;
+ }
+}
+
+.snapshot-placeholder {
+ margin-top: 20px;
+ height: 300px;
+ background: #f5f7fa;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ color: #909399;
+
+ p {
+ margin: 8px 0;
+ }
+
+ .tip {
+ color: #c0c4cc;
+ font-size: 12px;
+ }
+}
+
+:deep(.el-tabs--border-card) {
+ border: none;
+ box-shadow: none;
+}
+
+:deep(.el-tabs__content) {
+ padding: 10px;
+}
+
+// 鎶撴媿璁板綍缁熻
+.capture-stats {
+ margin-bottom: 16px;
+ padding: 16px;
+ background: #f5f7fa;
+ border-radius: 4px;
+}
+
+.stat-item {
+ text-align: center;
+ padding: 8px;
+ background: #fff;
+ border-radius: 4px;
+}
+
+.stat-label {
+ font-size: 13px;
+ color: #909399;
+ margin-bottom: 8px;
+}
+
+.stat-value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #303133;
+}
+
+// 鍒嗛〉瀹瑰櫒
+.pagination-container {
+ margin-top: 16px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+// 蹇収棰勮
+.snapshot-preview {
+ width: 100%;
+}
+
+.snapshot-placeholder {
+ width: 100%;
+ height: 420px;
+ background: #f5f7fa;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ border: 1px dashed #dcdfe6;
+ color: #909399;
+
+ p {
+ margin: 8px 0;
+ font-size: 14px;
+ }
+
+ .tip {
+ color: #c0c4cc;
+ font-size: 12px;
+ }
+}
+
+.snapshot-tools {
+ margin-top: 20px;
+}
+
+.snapshot-notes {
+ margin-top: 20px;
+
+ :deep(.el-divider__text) {
+ font-weight: 600;
+ color: #606266;
+ }
+}
+
+.dialog-footer-custom {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ > div {
+ display: flex;
+ gap: 8px;
+ }
+}
+
+// 鎻忚堪鍒楄〃鏍峰紡浼樺寲
+:deep(.el-descriptions__label) {
+ width: 100px;
+}
+
+:deep(.el-descriptions__content) {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+</style>
+
diff --git a/src/views/oaSystem/projectManagement/components/milestoneList.vue b/src/views/oaSystem/projectManagement/components/milestoneList.vue
new file mode 100644
index 0000000..47b0027
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/components/milestoneList.vue
@@ -0,0 +1,289 @@
+// ... existing code ...
+<template>
+ <div class="milestone-list-container">
+ <el-timeline>
+ <el-timeline-item
+ v-for="milestone in milestoneList"
+ :key="milestone.phaseId"
+ :timestamp="milestone.endDate"
+ >
+ <el-card>
+ <template #header>
+ <div class="card-header">
+ <span>{{ milestone.phaseName }}</span>
+ <div class="milestone-actions">
+ <el-button type="text" size="small" @click="handleEdit(milestone)">缂栬緫</el-button>
+ <el-button type="text" size="small" @click="handleDelete(milestone)" danger>鍒犻櫎</el-button>
+ </div>
+ </div>
+ </template>
+ <div class="milestone-content">
+ <p>{{ milestone.description }}</p>
+ <div class="milestone-status">
+ <el-tag :type="getStatusType(milestone.status)">{{ getStatusText(milestone.status) }}</el-tag>
+ </div>
+ </div>
+ </el-card>
+ </el-timeline-item>
+ </el-timeline>
+
+ <!-- 鏃犻噷绋嬬鏃剁殑鎻愮ず -->
+ <div v-if="milestoneList.length === 0" class="empty-tip">
+ <el-empty description="鏆傛棤閲岀▼纰戞暟鎹�" />
+ </div>
+
+ <!-- 缂栬緫閲岀▼纰戝璇濇 -->
+ <el-dialog
+ v-model="dialogVisible"
+ :title="'缂栬緫閲岀▼纰�: ' + (form.phaseName || '')"
+ width="600px"
+ :close-on-click-modal="false"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="100px"
+ >
+ <el-form-item label="閲岀▼纰戝悕绉�" prop="phaseName">
+ <el-input v-model="form.phaseName" placeholder="璇疯緭鍏ラ噷绋嬬鍚嶇О" />
+ </el-form-item>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="form.startDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="form.endDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨缁撴潫鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��">
+ <el-option label="鏈紑濮�" value="notStarted" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="宸插欢杩�" value="delayed" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitEditForm">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch, reactive } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { getProject, listProjectPhase, updateProjectPhase,delProjectPhase } from '@/api/oaSystem/projectManagement';
+
+const props = defineProps({
+ projectId: {
+ type: String,
+ required: true
+ }
+});
+
+const emit = defineEmits(['refresh']);
+
+const milestoneList = ref([]);
+const dialogVisible = ref(false);
+const formRef = ref(null);
+const form = reactive({
+ phaseId: '',
+ phaseName: '',
+ startDate: '',
+ endDate: '',
+ status: 'notStarted',
+ projectId: props.projectId
+});
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const rules = {
+ phaseName: [
+ { required: true, message: '璇疯緭鍏ラ噷绋嬬鍚嶇О', trigger: 'blur' },
+ { min: 2, max: 50, message: '闀垮害鍦� 2 鍒� 50 涓瓧绗�', trigger: 'blur' }
+ ],
+ status: [
+ { required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }
+ ]
+};
+
+// 鑾峰彇閲岀▼纰戝垪琛�
+const getMilestoneList = async () => {
+ try {
+ listProjectPhase(props.projectId).then(res => {
+ milestoneList.value = res.data.rows || res.data;
+ // 鎸夌洰鏍囨棩鏈熸帓搴�
+ // milestoneList.value.sort((a, b) => new Date(a.endDate) - new Date(b.endDate));
+ })
+ } catch (error) {
+ ElMessage.error('鑾峰彇閲岀▼纰戝垪琛ㄥけ璐�');
+ console.error('鑾峰彇閲岀▼纰戝垪琛ㄥけ璐�:', error);
+ }
+};
+
+// 缂栬緫閲岀▼纰�
+const handleEdit = (milestone) => {
+ // 澶嶅埗閲岀▼纰戞暟鎹埌琛ㄥ崟
+ Object.assign(form, {
+ phaseId: milestone.phaseId,
+ phaseName: milestone.phaseName,
+ description: milestone.description,
+ endDate: milestone.endDate,
+ status: milestone.status,
+ projectId: props.projectId
+ });
+ dialogVisible.value = true;
+};
+
+// 鎻愪氦缂栬緫琛ㄥ崟
+const submitEditForm = async () => {
+ try {
+ await formRef.value.validate();
+
+ // 鍙戦�佹洿鏂拌姹�
+ const res = await updateProjectPhase(form);
+
+ if (res.code === 200) {
+ ElMessage.success('閲岀▼纰戠紪杈戞垚鍔�');
+ dialogVisible.value = false;
+ getMilestoneList(); // 鍒锋柊鍒楄〃
+ emit('refresh'); // 閫氱煡鐖剁粍浠跺埛鏂�
+ } else {
+ ElMessage.error(res.msg || '閲岀▼纰戠紪杈戝け璐�');
+ }
+ } catch (error) {
+ if (error.name === 'ValidationError') {
+ // 琛ㄥ崟楠岃瘉澶辫触锛孍lement Plus浼氳嚜鍔ㄦ彁绀�
+ return;
+ }
+ ElMessage.error('閲岀▼纰戠紪杈戝け璐�');
+ console.error('缂栬緫閲岀▼纰戝け璐�:', error);
+ }
+};
+
+// 鍒犻櫎閲岀▼纰�
+const handleDelete = (milestone) => {
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ら噷绋嬬 "${milestone.phaseName}" 鍚楋紵鍒犻櫎鍚庡皢鏃犳硶鎭㈠銆俙,
+ '鍒犻櫎纭',
+ {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ )
+ .then(async () => {
+ try {
+ // 璋冪敤鍒犻櫎API
+ const res = await delProjectPhase(milestone.phaseId);
+
+ if (res.code === 200) {
+ ElMessage.success('閲岀▼纰戝垹闄ゆ垚鍔�');
+ getMilestoneList(); // 鍒锋柊鍒楄〃
+ emit('refresh'); // 閫氱煡鐖剁粍浠跺埛鏂�
+ } else {
+ ElMessage.error(res.msg || '閲岀▼纰戝垹闄ゅけ璐�');
+ }
+ } catch (error) {
+ ElMessage.error('閲岀▼纰戝垹闄ゅけ璐�');
+ console.error('鍒犻櫎閲岀▼纰戝け璐�:', error);
+ }
+ })
+ .catch(() => {
+ // 鐢ㄦ埛鍙栨秷鍒犻櫎
+ ElMessage.info('宸插彇娑堝垹闄�');
+ });
+};
+
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status) => {
+ const statusTypeMap = {
+ notStarted: 'info',
+ completed: 'success',
+ delayed: 'danger'
+ };
+ return statusTypeMap[status] || 'default';
+};
+
+// 鑾峰彇鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+ const statusTextMap = {
+ notStarted: '鏈紑濮�',
+ completed: '宸插畬鎴�',
+ delayed: '宸插欢杩�'
+ };
+ return statusTextMap[status] || status;
+};
+
+// 鐩戝惉椤圭洰ID鍙樺寲
+watch(() => props.projectId, () => {
+ if (props.projectId) {
+ getMilestoneList();
+ }
+});
+
+// 鍒濆鍖�
+onMounted(() => {
+ if (props.projectId) {
+ getMilestoneList();
+ }
+});
+</script>
+
+<style scoped>
+.milestone-list-container {
+ padding: 10px 0;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.milestone-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.milestone-content {
+ padding: 10px 0;
+}
+
+.milestone-status {
+ margin-top: 10px;
+}
+
+.empty-tip {
+ margin-top: 40px;
+ text-align: center;
+}
+
+.dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+}
+</style>
diff --git a/src/views/oaSystem/projectManagement/components/phaseGoalList.vue b/src/views/oaSystem/projectManagement/components/phaseGoalList.vue
new file mode 100644
index 0000000..5054299
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/components/phaseGoalList.vue
@@ -0,0 +1,165 @@
+
+<template>
+ <div class="phase-goal-list-container">
+ <el-table
+ v-loading="loading"
+ :data="phaseGoalList"
+ style="width: 100%"
+ >
+ <el-table-column prop="phaseName" label="鎵�灞為樁娈�" width="180" />
+ <el-table-column prop="taskName" label="鐩爣鍚嶇О" min-width="200" />
+ <el-table-column prop="targetValue" label="鐩爣鍊�" width="120" />
+ <el-table-column prop="currentValue" label="褰撳墠鍊�" width="120" />
+ <el-table-column prop="unit" label="鍗曚綅" width="80" />
+ <el-table-column prop="targetDate" label="鐩爣鏃ユ湡" width="120" />
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="completionRate" label="瀹屾垚搴�" width="120">
+ <template #default="scope">
+ <el-progress :percentage="scope.row.completionRate" :stroke-width="6" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150" fixed="right">
+ <template #default="scope">
+ <el-button type="text" size="small" @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button type="text" size="small" @click="handleDelete(scope.row)" danger>鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鏃犻樁娈电洰鏍囨椂鐨勬彁绀� -->
+ <div v-if="!loading && phaseGoalList.length === 0" class="empty-tip">
+ <el-empty description="鏆傛棤闃舵鐩爣鏁版嵁" />
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { listProjectPhase, updateProjectTask, delProjectTask } from '@/api/oaSystem/projectManagement';
+
+const props = defineProps({
+ projectId: {
+ type: String,
+ required: true
+ }
+});
+
+const emit = defineEmits(['refresh']);
+
+const phaseGoalList = ref([]);
+const loading = ref(true);
+
+// 鑾峰彇闃舵鐩爣鍒楄〃
+const getPhaseGoalList = async () => {
+ loading.value = true;
+ try {
+ const { data } = await listProjectPhase(props.projectId);
+ // 鍋囪phase鏁版嵁涓寘鍚簡鐩爣淇℃伅
+ // 杩欓噷绠�鍖栧鐞嗭紝瀹為檯搴旇璋冪敤涓撻棬鐨勮幏鍙栭樁娈电洰鏍囩殑API
+ const phases = data.rows || data;
+ phaseGoalList.value = [];
+
+ phases.forEach(phase => {
+ if (phase.oaProjectPhaseTasks && phase.oaProjectPhaseTasks.length > 0) {
+ phase.oaProjectPhaseTasks.forEach(goal => {
+ phaseGoalList.value.push({
+ ...goal,
+ phaseName: phase.phaseName
+ });
+ });
+ }
+ });
+ } catch (error) {
+ ElMessage.error('鑾峰彇闃舵鐩爣鍒楄〃澶辫触');
+ console.error('鑾峰彇闃舵鐩爣鍒楄〃澶辫触:', error);
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 缂栬緫闃舵鐩爣
+const handleEdit = (goal) => {
+ // 瑙﹀彂鐖剁粍浠剁殑缂栬緫浜嬩欢锛屼紶閫掔洰鏍囨暟鎹�
+ emit('editGoal', goal);
+};
+
+// 鍒犻櫎闃舵鐩爣
+const handleDelete = async (goal) => {
+ try {
+ await ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ょ洰鏍囥��${goal.taskName}銆嶅悧锛焋,
+ '纭鍒犻櫎',
+ {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ );
+
+ // 璋冪敤鍒犻櫎API
+ await delProjectTask(goal.taskId);
+ ElMessage.success('鍒犻櫎闃舵鐩爣鎴愬姛');
+
+ // 鍒锋柊鍒楄〃
+ getPhaseGoalList();
+ emit('refresh');
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error('鍒犻櫎闃舵鐩爣澶辫触');
+ console.error('鍒犻櫎闃舵鐩爣澶辫触:', error);
+ }
+ }
+};
+
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status) => {
+ const statusTypeMap = {
+ notStarted: 'info',
+ inProgress: 'primary',
+ completed: 'success',
+ delayed: 'danger'
+ };
+ return statusTypeMap[status] || 'default';
+};
+
+// 鑾峰彇鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+ const statusTextMap = {
+ notStarted: '鏈紑濮�',
+ inProgress: '杩涜涓�',
+ completed: '宸插畬鎴�',
+ delayed: '宸插欢杩�'
+ };
+ return statusTextMap[status] || status;
+};
+
+// 鐩戝惉椤圭洰ID鍙樺寲
+watch(() => props.projectId, () => {
+ if (props.projectId) {
+ getPhaseGoalList();
+ }
+});
+
+// 鍒濆鍖�
+onMounted(() => {
+ if (props.projectId) {
+ getPhaseGoalList();
+ }
+});
+</script>
+
+<style scoped>
+.phase-goal-list-container {
+ padding: 10px 0;
+}
+
+.empty-tip {
+ margin-top: 40px;
+ text-align: center;
+}
+</style>
diff --git a/src/views/oaSystem/projectManagement/components/projectForm.vue b/src/views/oaSystem/projectManagement/components/projectForm.vue
new file mode 100644
index 0000000..50b671c
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/components/projectForm.vue
@@ -0,0 +1,221 @@
+<template>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-form-item label="椤圭洰鍚嶇О" prop="projectName">
+ <el-input
+ v-model="form.projectName"
+ placeholder="璇疯緭鍏ラ」鐩悕绉�"
+ maxlength="50"
+ />
+ </el-form-item>
+ <el-form-item label="椤圭洰鎻忚堪" prop="description">
+ <el-input
+ v-model="form.description"
+ type="textarea"
+ :rows="4"
+ placeholder="璇疯緭鍏ラ」鐩弿杩�"
+ />
+ </el-form-item>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="form.startDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="form.endDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨缁撴潫鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="椤圭洰璐熻矗浜�" prop="managerId">
+ <!-- <el-input
+ v-model="form.managerName"
+ placeholder="璇烽�夋嫨椤圭洰璐熻矗浜�"
+ readonly
+ @focus="showUserSelect = true"
+ /> -->
+ <!-- <el-input
+ v-model="userSearchKey"
+ placeholder="鎼滅储鐢ㄦ埛"
+ v-if="showUserSelect"
+ @keyup.enter="searchUsers"
+ style="margin-top: 10px"
+ /> -->
+ <el-select
+ v-model="form.managerId"
+ style="width: 100%; margin-top: 10px"
+ @change="selectUser"
+ filterable
+ remote
+ :remote-method="searchUsers"
+ :loading="userLoading"
+ placeholder="閫夋嫨椤圭洰璐熻矗浜�"
+ >
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="椤圭洰瀹屾垚搴�" prop="completionRate">
+ <el-slider v-model="form.completionRate" :max="100" />
+ </el-form-item>
+ <el-form-item label="椤圭洰鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio label="planning">瑙勫垝涓�</el-radio>
+ <el-radio label="inProgress">杩涜涓�</el-radio>
+ <el-radio label="completed">宸插畬鎴�</el-radio>
+ <el-radio label="paused">宸叉殏鍋�</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+</template>
+
+<script setup>
+import { ref, watch, defineExpose } from 'vue';
+import { listUser } from '@/api/system/user';
+import { ElMessage } from 'element-plus';
+const props = defineProps({
+ form: {
+ type: Object,
+ required: true
+ },
+ rules: {
+ type: Object,
+ required: true
+ },
+ visible: {
+ type: Boolean,
+ required: true
+ }
+});
+
+const formRef = ref();
+const showUserSelect = ref(false);
+const userSearchKey = ref('');
+const userList = ref([]);
+const userLoading = ref(false);
+// 妯℃嫙鐢ㄦ埛鏁版嵁
+const mockUserData = [
+ { userId: '1', nickName: '寮犱笁', userName: 'zhangsan' },
+ { userId: '2', nickName: '鏉庡洓', userName: 'lisi' },
+ { userId: '3', nickName: '鐜嬩簲', userName: 'wangwu' },
+ { userId: '4', nickName: '璧靛叚', userName: 'zhaoliu' },
+ { userId: '5', nickName: '閽变竷', userName: 'qianqi' },
+ { userId: '6', nickName: '瀛欏叓', userName: 'sunba' },
+ { userId: '7', nickName: '鍛ㄤ節', userName: 'zhoujiu' },
+ { userId: '8', nickName: '鍚村崄', userName: 'wushi' }
+];
+// 鎼滅储鐢ㄦ埛
+const searchUsers = async (query) => {
+ userLoading.value = true;
+ try {
+ // 妯℃嫙缃戠粶寤惰繜
+ await new Promise(resolve => setTimeout(resolve, 300));
+
+ // 姝g‘璁剧疆鐢ㄦ埛鍒楄〃
+ if (!query) {
+ userList.value = [...mockUserData];
+ } else {
+ userList.value = mockUserData.filter(user =>
+ user.nickName.includes(query) || user.userName.includes(query)
+ );
+ }
+ } catch (error) {
+ console.error('鎼滅储鐢ㄦ埛澶辫触:', error);
+ } finally {
+ userLoading.value = false;
+ }
+};
+
+// 閫夋嫨鐢ㄦ埛
+const selectUser = (userId) => {
+ // 鍏堜粠userList鏌ユ壘锛屽鏋滄壘涓嶅埌鍐嶄粠mockUserData涓煡鎵�
+ let selectedUser = userList.value.find(user => user.userId === userId);
+ if (!selectedUser) {
+ selectedUser = mockUserData.find(user => user.userId === userId);
+ }
+
+ // 浣跨敤Vue.set纭繚鍝嶅簲寮忔洿鏂�
+ if (selectedUser) {
+ Object.assign(props.form, { managerName: selectedUser.nickName });
+ } else {
+ Object.assign(props.form, { managerName: '' });
+ }
+ showUserSelect.value = false;
+};
+
+// 閲嶇疆琛ㄥ崟
+const resetFields = () => {
+ if (formRef.value) {
+ formRef.value.resetFields();
+ }
+ showUserSelect.value = false;
+ userSearchKey.value = '';
+ userList.value = [];
+};
+
+// 楠岃瘉琛ㄥ崟
+const validate = () => {
+ return new Promise((resolve, reject) => {
+ if (formRef.value) {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ // 棰濆楠岃瘉锛氱粨鏉熸棩鏈熶笉鑳芥棭浜庡紑濮嬫棩鏈�
+ if (props.form.startDate && props.form.endDate) {
+ const startDate = new Date(props.form.startDate);
+ const endDate = new Date(props.form.endDate);
+ if (endDate < startDate) {
+ ElMessage.error('缁撴潫鏃ユ湡涓嶈兘鏃╀簬寮�濮嬫棩鏈�');
+ reject(new Error('鏃ユ湡楠岃瘉澶辫触'));
+ return;
+ }
+ }
+ resolve();
+ } else {
+ reject(new Error('琛ㄥ崟楠岃瘉澶辫触'));
+ }
+ });
+ } else {
+ resolve();
+ }
+ });
+};
+
+// 鐩戝惉瀵硅瘽妗嗘樉绀虹姸鎬�
+watch(() => props.visible, (newVisible) => {
+ if (!newVisible) {
+ resetFields();
+ // 寤惰繜閲嶇疆锛岀‘淇濇暟鎹凡缁忔彁浜ゅ埌鍚庣
+ // setTimeout(() => {
+ // resetFields();
+ // }, 300);
+ }
+});
+
+// 鏆撮湶鏂规硶
+defineExpose({
+ resetFields,
+ validate
+});
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/oaSystem/projectManagement/components/taskTree.vue b/src/views/oaSystem/projectManagement/components/taskTree.vue
new file mode 100644
index 0000000..11e3ae8
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/components/taskTree.vue
@@ -0,0 +1,834 @@
+<template>
+ <div class="task-tree-container">
+ <!-- 浠诲姟鏍戞搷浣滄寜閽� -->
+ <div class="tree-actions mb10">
+ <el-button type="primary" size="small" icon="Plus" @click="handleAddTask">娣诲姞浠诲姟</el-button>
+ <el-button type="success" size="small" icon="RefreshRight" @click="refreshTree">鍒锋柊</el-button>
+ <el-button type="info" size="small" icon="Filter" @click="toggleFilter">
+ {{ showFilter ? '闅愯棌绛涢��' : '鏄剧ず绛涢��' }}
+ </el-button>
+ </div>
+
+ <!-- 绛涢�夋潯浠� -->
+ <div v-if="showFilter" class="filter-section mb10">
+ <el-form :inline="true" :model="filterParams">
+ <el-form-item label="浠诲姟鐘舵��">
+ <el-select v-model="filterParams.status" placeholder="鍏ㄩ儴" clearable style="width: 120px">
+ <el-option label="鏈紑濮�" value="notStarted" />
+ <el-option label="杩涜涓�" value="inProgress" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="宸查�炬湡" value="overdue" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璐熻矗浜�">
+ <el-input v-model="filterParams.assignee" placeholder="杈撳叆璐熻矗浜�" clearable style="width: 150px" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" size="small" @click="filterTree">绛涢��</el-button>
+ <el-button size="small" @click="resetFilter">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <!-- 浠诲姟鏍� -->
+ <div class="tree-content">
+ <el-tree
+ v-loading="loading"
+ :data="taskTreeData"
+ :props="defaultProps"
+ :expand-on-click-node="false"
+ node-key="nodeId"
+ ref="treeRef"
+ @node-contextmenu="handleContextMenu"
+ @node-click="handleNodeClick"
+ >
+ <template #default="{ node, data }">
+ <!-- 鑺傜偣鍐呭 -->
+ <div class="tree-node-content" :class="{ 'phase-node': data.type === 'phase', 'task-node': data.type === 'task' }">
+ <!-- 鑺傜偣鍥炬爣 -->
+ <div class="node-icon">
+ <i v-if="data.type === 'phase'" class="el-icon-folder text-primary" />
+ <i v-else-if="data.status === 'completed'" class="el-icon-circle-check text-success" />
+ <i v-else-if="data.status === 'inProgress'" class="el-icon-circle-check text-primary" />
+ <i v-else-if="data.status === 'overdue'" class="el-icon-alarm-clock text-danger" />
+ <i v-else class="el-icon-circle-close text-gray-400" />
+ </div>
+
+ <!-- 鑺傜偣鏍囬鍜屾弿杩� -->
+ <div class="node-info">
+ <div class="node-title" :class="{ 'overdue-title': data.type === 'task' && data.status === 'overdue' }">
+ {{ node.label }}
+ <span v-if="data.type === 'task' && data.priority === 'high'" class="priority-tag">楂樹紭</span>
+ <span v-else-if="data.type === 'task' && data.priority === 'medium'" class="priority-tag medium">涓紭</span>
+ </div>
+ <div v-if="data.description" class="node-description">{{ data.description }}</div>
+
+ <!-- 浠诲姟鍏冧俊鎭� -->
+ <div v-if="data.type === 'task'" class="task-meta">
+ <span class="meta-item">
+ <i class="el-icon-user"></i>
+ {{ data.assigneeName || '鏈垎閰�' }}
+ </span>
+ <span class="meta-item">
+ <i class="el-icon-calendar"></i>
+ {{ formatDateRange(data.startDate, data.endDate) }}
+ </span>
+ </div>
+ </div>
+
+ <!-- 浠诲姟杩涘害鏉� -->
+ <div v-if="data.type === 'task'" class="task-progress">
+ <el-progress :percentage="data.progress || 0" :stroke-width="4" :show-text="false" />
+ </div>
+
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div class="node-actions">
+ <el-button
+ v-if="data.type === 'task'"
+ type="text"
+ size="small"
+ icon="Edit"
+ @click.stop="handleEditTask(data)"
+ v-hasPermi="['oaSystem:task:edit']"
+ />
+ <el-button
+ v-if="data.type === 'phase'"
+ type="text"
+ size="small"
+ icon="Plus"
+ @click.stop="handleAddTaskUnderPhase(data)"
+ v-hasPermi="['oaSystem:task:add']"
+ />
+ <el-button
+ type="text"
+ size="small"
+ icon="Delete"
+ @click.stop="handleDeleteNode(data)"
+ v-hasPermi="['oaSystem:task:remove']"
+ />
+ </div>
+ </div>
+ </template>
+ </el-tree>
+ </div>
+
+ <!-- 鍙抽敭鑿滃崟 -->
+ <div v-if="showContextMenu" :style="contextMenuStyle" class="context-menu">
+ <el-menu @select="handleContextMenuSelect">
+ <el-menu-item v-if="selectedNode.type === 'task'" index="edit">缂栬緫浠诲姟</el-menu-item>
+ <el-menu-item v-if="selectedNode.type === 'phase'" index="addTask">娣诲姞瀛愪换鍔�</el-menu-item>
+ <el-menu-item index="delete">鍒犻櫎</el-menu-item>
+ <el-menu-item index="expandAll">灞曞紑鍏ㄩ儴</el-menu-item>
+ <el-menu-item index="collapseAll">鏀惰捣鍏ㄩ儴</el-menu-item>
+ </el-menu>
+ </div>
+
+ <!-- 浠诲姟琛ㄥ崟瀵硅瘽妗� -->
+ <el-dialog :title="dialogTitle" v-model="dialogOpen" width="600px" append-to-body>
+ <el-form ref="taskFormRef" :model="taskForm" :rules="taskRules" label-width="80px">
+ <el-form-item label="浠诲姟鍚嶇О" prop="taskName">
+ <el-input v-model="taskForm.taskName" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" />
+ </el-form-item>
+ <el-form-item label="璐熻矗浜�" prop="assigneeId">
+ <el-input v-model="taskForm.assigneeId" placeholder="璇疯緭鍏ヨ礋璐d汉ID" />
+ </el-form-item>
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="taskForm.startDate"
+ type="date"
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="taskForm.endDate"
+ type="date"
+ placeholder="閫夋嫨缁撴潫鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="浼樺厛绾�" prop="priority">
+ <el-select v-model="taskForm.priority" placeholder="閫夋嫨浼樺厛绾�">
+ <el-option label="浣�" value="low" />
+ <el-option label="涓�" value="medium" />
+ <el-option label="楂�" value="high" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="杩涘害" prop="progress">
+ <el-input-number v-model="taskForm.progress" :min="0" :max="100" style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="鎻忚堪" prop="description">
+ <el-input v-model="taskForm.description" type="textarea" placeholder="璇疯緭鍏ヤ换鍔℃弿杩�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogOpen = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitTaskForm">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, watch, onMounted } from 'vue';
+import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from 'element-plus';
+// import { getProject, addTask, updateTask, deleteTask, deletePhase } from '@/api/oaSystem/projectManagement';
+
+const props = defineProps({
+ projectId: {
+ type: String,
+ required: true
+ }
+});
+
+const emit = defineEmits(['refresh']);
+
+// 缁勪欢鐘舵��
+const loading = ref(false);
+const treeRef = ref();
+const showContextMenu = ref(false);
+const contextMenuStyle = ref({});
+const selectedNode = ref({});
+const showFilter = ref(false);
+const dialogOpen = ref(false);
+const dialogTitle = ref('');
+const taskFormRef = ref();
+
+// 绛涢�夊弬鏁�
+const filterParams = reactive({
+ status: '',
+ assignee: ''
+});
+
+// 浠诲姟琛ㄥ崟鏁版嵁
+const taskForm = reactive({
+ taskId: undefined,
+ taskName: '',
+ description: '',
+ startDate: '',
+ endDate: '',
+ assigneeId: '',
+ assigneeName: '',
+ status: 'notStarted',
+ progress: 0,
+ priority: 'medium',
+ phaseId: '',
+ projectId: props.projectId
+});
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const taskRules = {
+ taskName: [
+ { required: true, message: '浠诲姟鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
+ { min: 2, max: 50, message: '浠诲姟鍚嶇О闀垮害鍦� 2 鍒� 50 涓瓧绗�', trigger: 'blur' }
+ ],
+ startDate: [
+ { required: true, message: '寮�濮嬫棩鏈熶笉鑳戒负绌�', trigger: 'change' }
+ ],
+ endDate: [
+ { required: true, message: '缁撴潫鏃ユ湡涓嶈兘涓虹┖', trigger: 'change' }
+ ],
+ assigneeId: [
+ { required: true, message: '璐熻矗浜轰笉鑳戒负绌�', trigger: 'blur' }
+ ],
+ progress: [
+ { required: true, message: '杩涘害涓嶈兘涓虹┖', trigger: 'blur' },
+ { type: 'number', min: 0, max: 100, message: '杩涘害蹇呴』鍦� 0 鍒� 100 涔嬮棿', trigger: 'blur' }
+ ]
+};
+
+// 浠诲姟鏍戞暟鎹�
+const rawTaskTreeData = ref([]);
+
+// 妯℃嫙浠诲姟鏁版嵁
+const mockTaskData = {
+ 'PRJ2023001': [
+ {
+ phaseId: 'PHASE001',
+ phaseName: '闇�姹傚垎鏋�',
+ startDate: '2023-11-01',
+ endDate: '2023-11-15',
+ status: 'completed',
+ tasks: [
+ {
+ taskId: 'TASK001',
+ taskName: '闇�姹傝皟鐮�',
+ description: '璋冪爺鐢ㄦ埛闇�姹傚拰涓氬姟娴佺▼',
+ startDate: '2023-11-01',
+ endDate: '2023-11-05',
+ assigneeId: 'USER001',
+ assigneeName: '寮犱笁',
+ status: 'completed',
+ progress: 100,
+ priority: 'medium'
+ },
+ {
+ taskId: 'TASK002',
+ taskName: '闇�姹傛枃妗g紪鍐�',
+ description: '缂栧啓璇︾粏鐨勯渶姹傝鏍艰鏄庝功',
+ startDate: '2023-11-06',
+ endDate: '2023-11-15',
+ assigneeId: 'USER002',
+ assigneeName: '鏉庡洓',
+ status: 'completed',
+ progress: 100,
+ priority: 'high'
+ }
+ ]
+ },
+ {
+ phaseId: 'PHASE002',
+ phaseName: '绯荤粺璁捐',
+ startDate: '2023-11-16',
+ endDate: '2023-12-10',
+ status: 'completed',
+ tasks: [
+ {
+ taskId: 'TASK003',
+ taskName: '绯荤粺鏋舵瀯璁捐',
+ description: '璁捐绯荤粺鏁翠綋鏋舵瀯',
+ startDate: '2023-11-16',
+ endDate: '2023-11-25',
+ assigneeId: 'USER003',
+ assigneeName: '鐜嬩簲',
+ status: 'completed',
+ progress: 100,
+ priority: 'high'
+ },
+ {
+ taskId: 'TASK004',
+ taskName: '鏁版嵁搴撹璁�',
+ description: '璁捐鏁版嵁搴撹〃缁撴瀯鍜屽叧绯�',
+ startDate: '2023-11-26',
+ endDate: '2023-12-10',
+ assigneeId: 'USER004',
+ assigneeName: '璧靛叚',
+ status: 'completed',
+ progress: 100,
+ priority: 'medium'
+ }
+ ]
+ },
+ {
+ phaseId: 'PHASE003',
+ phaseName: '寮�鍙戝疄鐜�',
+ startDate: '2023-12-11',
+ endDate: '2024-01-31',
+ status: 'inProgress',
+ tasks: [
+ {
+ taskId: 'TASK005',
+ taskName: '鍓嶇寮�鍙�',
+ description: '寮�鍙戠敤鎴风晫闈㈠拰浜や簰閫昏緫',
+ startDate: '2023-12-11',
+ endDate: '2024-01-15',
+ assigneeId: 'USER005',
+ assigneeName: '閽变竷',
+ status: 'inProgress',
+ progress: 70,
+ priority: 'high'
+ },
+ {
+ taskId: 'TASK006',
+ taskName: '鍚庣寮�鍙�',
+ description: '寮�鍙戜笟鍔¢�昏緫鍜孉PI鎺ュ彛',
+ startDate: '2023-12-11',
+ endDate: '2024-01-20',
+ assigneeId: 'USER006',
+ assigneeName: '瀛欏叓',
+ status: 'inProgress',
+ progress: 60,
+ priority: 'high'
+ }
+ ]
+ }
+ ],
+ // 榛樿鏁版嵁
+ default: [
+ {
+ phaseId: 'PHASE_DEFAULT1',
+ phaseName: '鍑嗗闃舵',
+ startDate: '2023-01-01',
+ endDate: '2023-03-31',
+ status: 'completed',
+ tasks: [
+ {
+ taskId: 'TASK_DEFAULT1',
+ taskName: '椤圭洰鍚姩',
+ description: '鍙紑椤圭洰鍚姩浼氳',
+ startDate: '2023-01-01',
+ endDate: '2023-01-05',
+ assigneeId: 'USER_DEFAULT1',
+ assigneeName: '璐熻矗浜篈',
+ status: 'completed',
+ progress: 100,
+ priority: 'high'
+ }
+ ]
+ },
+ {
+ phaseId: 'PHASE_DEFAULT2',
+ phaseName: '鎵ц闃舵',
+ startDate: '2023-04-01',
+ endDate: '2023-09-30',
+ status: 'inProgress',
+ tasks: [
+ {
+ taskId: 'TASK_DEFAULT2',
+ taskName: '鏍稿績鍔熻兘寮�鍙�',
+ description: '寮�鍙戠郴缁熸牳蹇冨姛鑳芥ā鍧�',
+ startDate: '2023-04-01',
+ endDate: '2023-06-30',
+ assigneeId: 'USER_DEFAULT2',
+ assigneeName: '璐熻矗浜築',
+ status: 'inProgress',
+ progress: 50,
+ priority: 'high'
+ }
+ ]
+ }
+ ]
+};
+
+const taskTreeData = computed(() => {
+ // 搴旂敤绛涢�夋潯浠�
+ if (!showFilter.value || (!filterParams.status && !filterParams.assignee)) {
+ return rawTaskTreeData.value;
+ }
+
+ // 娣辨嫹璐濆師濮嬫暟鎹互閬垮厤淇敼
+ const filteredData = JSON.parse(JSON.stringify(rawTaskTreeData.value));
+
+ // 閫掑綊绛涢�夎妭鐐�
+ const filterNodes = (nodes) => {
+ const result = [];
+
+ nodes.forEach(node => {
+ // 瀵逛簬闃舵鑺傜偣锛屾鏌ュ叾瀛愪换鍔℃槸鍚︾鍚堢瓫閫夋潯浠�
+ if (node.type === 'phase' && node.children) {
+ const filteredChildren = filterNodes(node.children);
+ if (filteredChildren.length > 0) {
+ // 淇濈暀鑷冲皯鏈変竴涓瓙浠诲姟绗﹀悎鏉′欢鐨勯樁娈�
+ node.children = filteredChildren;
+ result.push(node);
+ }
+ }
+ // 瀵逛簬浠诲姟鑺傜偣锛岀洿鎺ュ簲鐢ㄧ瓫閫夋潯浠�
+ else if (node.type === 'task') {
+ const statusMatch = !filterParams.status || node.status === filterParams.status;
+ const assigneeMatch = !filterParams.assignee ||
+ (node.assigneeName && node.assigneeName.includes(filterParams.assignee));
+
+ if (statusMatch && assigneeMatch) {
+ result.push(node);
+ }
+ }
+ });
+
+ return result;
+ };
+
+ return filterNodes(filteredData);
+});
+
+// 鏍戣妭鐐归厤缃�
+const defaultProps = {
+ children: 'children',
+ label: (data) => {
+ if (data.type === 'phase') {
+ return `${data.phaseName}`;
+ } else {
+ return `${data.taskName}`;
+ }
+ }
+};
+
+// 鍔犺浇浠诲姟鏍戞暟鎹�
+const loadTaskTree = async () => {
+ loading.value = true;
+ // try {
+ // const { data } = await getProject(props.projectId);
+ // rawTaskTreeData.value = buildTaskTree(data.phases || []);
+ // } catch (error) {
+ // ElMessage.error('鍔犺浇浠诲姟鏍戝け璐�');
+ // console.error('鍔犺浇浠诲姟鏍戝け璐�:', error);
+ // } finally {
+ // loading.value = false;
+ // }
+ try {
+ // 妯℃嫙缃戠粶寤惰繜
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // 浣跨敤妯℃嫙鏁版嵁鏇夸唬API璇锋眰
+ const phases = mockTaskData[props.projectId] || mockTaskData.default;
+ rawTaskTreeData.value = buildTaskTree(phases);
+ } catch (error) {
+ ElMessage.error('鍔犺浇浠诲姟鏍戝け璐�');
+ console.error('鍔犺浇浠诲姟鏍戝け璐�:', error);
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 鏋勫缓浠诲姟鏍�
+const buildTaskTree = (phases) => {
+ return phases.map(phase => ({
+ nodeId: phase.phaseId,
+ phaseId: phase.phaseId,
+ phaseName: phase.phaseName,
+ type: 'phase',
+ children: (phase.tasks || []).map(task => ({
+ nodeId: task.taskId,
+ taskId: task.taskId,
+ taskName: task.taskName,
+ description: task.description,
+ startDate: task.startDate,
+ endDate: task.endDate,
+ assigneeId: task.assigneeId,
+ assigneeName: task.assigneeName,
+ status: task.status,
+ progress: task.progress,
+ priority: task.priority,
+ phaseId: task.phaseId,
+ projectId: props.projectId,
+ type: 'task'
+ }))
+ }));
+};
+
+// 鏍煎紡鍖栨棩鏈熻寖鍥�
+const formatDateRange = (startDate, endDate) => {
+ if (!startDate || !endDate) return '';
+ return `${startDate} - ${endDate}`;
+};
+
+// 鍒锋柊鏍�
+const refreshTree = () => {
+ loadTaskTree();
+ // 閫氱煡鐖剁粍浠跺埛鏂版暟鎹�
+ emit('refresh');
+};
+
+// 鍒囨崲绛涢�夐潰鏉�
+const toggleFilter = () => {
+ showFilter.value = !showFilter.value;
+};
+
+// 搴旂敤绛涢��
+const filterTree = () => {
+ // 绛涢�夐�昏緫宸茬粡鍦╟omputed涓疄鐜�
+};
+
+// 閲嶇疆绛涢��
+const resetFilter = () => {
+ filterParams.status = '';
+ filterParams.assignee = '';
+};
+
+// 澶勭悊鑺傜偣鐐瑰嚮
+const handleNodeClick = (data, node) => {
+ // 鍒囨崲灞曞紑/鏀惰捣鐘舵��
+ if (data.type === 'phase') {
+ node.expanded = !node.expanded;
+ }
+};
+
+// 澶勭悊鍙抽敭鑿滃崟
+const handleContextMenu = (event, data) => {
+ event.preventDefault();
+ selectedNode.value = data;
+ contextMenuStyle.value = {
+ position: 'fixed',
+ left: `${event.clientX}px`,
+ top: `${event.clientY}px`,
+ zIndex: 1000
+ };
+ showContextMenu.value = true;
+};
+
+// 澶勭悊鍙抽敭鑿滃崟閫夋嫨
+const handleContextMenuSelect = (index) => {
+ showContextMenu.value = false;
+ switch (index) {
+ case 'edit':
+ if (selectedNode.value.type === 'task') {
+ handleEditTask(selectedNode.value);
+ }
+ break;
+ case 'addTask':
+ if (selectedNode.value.type === 'phase') {
+ handleAddTaskUnderPhase(selectedNode.value);
+ }
+ break;
+ case 'delete':
+ handleDeleteNode(selectedNode.value);
+ break;
+ case 'expandAll':
+ treeRef.value?.expandAll();
+ break;
+ case 'collapseAll':
+ treeRef.value?.collapseAll();
+ break;
+ }
+};
+
+// 娣诲姞浠诲姟
+const handleAddTask = () => {
+ resetTaskForm();
+ dialogTitle.value = '娣诲姞浠诲姟';
+ dialogOpen.value = true;
+};
+
+// 鍦ㄦ寚瀹氶樁娈典笅娣诲姞浠诲姟
+const handleAddTaskUnderPhase = (phase) => {
+ resetTaskForm();
+ taskForm.phaseId = phase.phaseId;
+ dialogTitle.value = '娣诲姞瀛愪换鍔�';
+ dialogOpen.value = true;
+};
+
+// 缂栬緫浠诲姟
+const handleEditTask = (task) => {
+ resetTaskForm();
+ Object.assign(taskForm, { ...task });
+ dialogTitle.value = '缂栬緫浠诲姟';
+ dialogOpen.value = true;
+};
+
+// 鍒犻櫎鑺傜偣
+const handleDeleteNode = async (node) => {
+ const confirmMessage = node.type === 'phase'
+ ? `纭畾瑕佸垹闄ら樁娈� "${node.phaseName}" 鍙婂叾鎵�鏈夊瓙浠诲姟鍚楋紵`
+ : `纭畾瑕佸垹闄や换鍔� "${node.taskName}" 鍚楋紵`;
+
+ await ElMessageBox.confirm(confirmMessage, '纭鎿嶄綔', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).catch(() => {
+ throw new Error('鍙栨秷鍒犻櫎');
+ });
+
+ try {
+ if (node.type === 'phase') {
+ await deletePhase(node.phaseId);
+ } else {
+ await deleteTask(node.taskId);
+ }
+ ElMessage.success('鍒犻櫎鎴愬姛');
+ refreshTree();
+ } catch (error) {
+ if (error.message !== '鍙栨秷鍒犻櫎') {
+ ElMessage.error('鍒犻櫎澶辫触');
+ console.error('鍒犻櫎澶辫触:', error);
+ }
+ }
+};
+
+// 閲嶇疆浠诲姟琛ㄥ崟
+const resetTaskForm = () => {
+ taskForm.taskId = undefined;
+ taskForm.taskName = '';
+ taskForm.description = '';
+ taskForm.startDate = '';
+ taskForm.endDate = '';
+ taskForm.assigneeId = '';
+ taskForm.assigneeName = '';
+ taskForm.status = 'notStarted';
+ taskForm.progress = 0;
+ taskForm.priority = 'medium';
+ taskForm.phaseId = '';
+ taskForm.projectId = props.projectId;
+
+ if (taskFormRef.value) {
+ taskFormRef.value.resetFields();
+ }
+};
+
+// 鎻愪氦浠诲姟琛ㄥ崟
+const submitTaskForm = async () => {
+ try {
+ await taskFormRef.value.validate();
+
+ if (taskForm.taskId) {
+ await updateTask(taskForm);
+ ElMessage.success('淇敼浠诲姟鎴愬姛');
+ } else {
+ await addTask(taskForm);
+ ElMessage.success('娣诲姞浠诲姟鎴愬姛');
+ }
+
+ dialogOpen.value = false;
+ refreshTree();
+ } catch (error) {
+ console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
+ }
+};
+
+// 鐐瑰嚮鍏朵粬鍖哄煙鍏抽棴鍙抽敭鑿滃崟
+document.addEventListener('click', () => {
+ if (showContextMenu.value) {
+ showContextMenu.value = false;
+ }
+});
+
+// 鐩戝惉椤圭洰ID鍙樺寲
+watch(() => props.projectId, (newProjectId) => {
+ if (newProjectId) {
+ loadTaskTree();
+ }
+});
+
+// 鍒濆鍖�
+onMounted(() => {
+ loadTaskTree();
+});
+</script>
+
+<style scoped>
+.task-tree-container {
+ padding: 10px;
+}
+
+.tree-actions {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.filter-section {
+ background: #f5f7fa;
+ padding: 10px;
+ border-radius: 4px;
+}
+
+.tree-content {
+ background: #fff;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+ padding: 10px;
+ max-height: 600px;
+ overflow-y: auto;
+}
+
+.tree-node-content {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 5px 0;
+ min-height: 40px;
+}
+
+.phase-node {
+ font-weight: bold;
+ color: #409eff;
+}
+
+.task-node {
+ color: #606266;
+}
+
+.node-icon {
+ display: flex;
+ align-items: center;
+ width: 20px;
+}
+
+.node-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.node-title {
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 2px;
+}
+
+.overdue-title {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.priority-tag {
+ background: #f56c6c;
+ color: white;
+ font-size: 10px;
+ padding: 1px 4px;
+ border-radius: 2px;
+ margin-left: 5px;
+}
+
+.priority-tag.medium {
+ background: #e6a23c;
+}
+
+.node-description {
+ font-size: 12px;
+ color: #909399;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.task-meta {
+ display: flex;
+ gap: 15px;
+ font-size: 12px;
+ color: #909399;
+ margin-top: 2px;
+}
+
+.meta-item {
+ display: flex;
+ align-items: center;
+ gap: 3px;
+}
+
+.task-progress {
+ width: 120px;
+ margin: 0 10px;
+}
+
+.node-actions {
+ display: flex;
+ gap: 5px;
+ opacity: 0;
+ transition: opacity 0.3s;
+}
+
+.tree-node-content:hover .node-actions {
+ opacity: 1;
+}
+
+.context-menu {
+ background: white;
+ border: 1px solid #ebeef5;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ border-radius: 4px;
+}
+
+.context-menu .el-menu {
+ min-width: 120px;
+ border: none;
+}
+
+.context-menu .el-menu-item {
+ padding: 0 15px;
+ height: 36px;
+ line-height: 36px;
+}
+
+.context-menu .el-menu-item:hover {
+ background-color: #f5f7fa;
+}
+
+.text-gray-400 {
+ color: #c0c4cc;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/oaSystem/projectManagement/index.vue b/src/views/oaSystem/projectManagement/index.vue
new file mode 100644
index 0000000..2a0ec3a
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/index.vue
@@ -0,0 +1,481 @@
+<template>
+ <div class="app-container">
+ <!-- 椤堕儴鎼滅储鍜屾搷浣滄爮 -->
+ <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
+ <el-form-item label="椤圭洰鍚嶇О" prop="projectName">
+ <el-input
+ v-model="queryParams.projectName"
+ placeholder="璇疯緭鍏ラ」鐩悕绉�"
+ clearable
+ style="width: 240px"
+ @keyup.enter="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="璐熻矗浜�" prop="managerName">
+ <el-input
+ v-model="queryParams.managerName"
+ placeholder="璇疯緭鍏ヨ礋璐d汉濮撳悕"
+ clearable
+ style="width: 240px"
+ @keyup.enter="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select
+ v-model="queryParams.status"
+ placeholder="椤圭洰鐘舵��"
+ clearable
+ style="width: 150px"
+ >
+ <el-option label="瑙勫垝涓�" value="planning" />
+ <el-option label="杩涜涓�" value="inProgress" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="宸叉殏鍋�" value="paused" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 宸ュ叿鏍� -->
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button
+ type="primary"
+ plain
+ icon="Plus"
+ @click="handleAdd"
+ v-hasPermi="['oaSystem:project:add']"
+ >鏂板椤圭洰</el-button>
+ </el-col>
+ <!-- <el-col :span="1.5">
+ <el-button
+ type="success"
+ plain
+ icon="Edit"
+ :disabled="single"
+ @click="handleUpdate"
+ v-hasPermi="['oaSystem:project:edit']"
+ >缂栬緫椤圭洰</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="danger"
+ plain
+ icon="Delete"
+ :disabled="multiple"
+ @click="handleDelete"
+ v-hasPermi="['oaSystem:project:remove']"
+ >鍒犻櫎椤圭洰</el-button>
+ </el-col> -->
+ <el-col :span="1.5">
+ <el-button
+ type="warning"
+ plain
+ icon="Download"
+ @click="handleExport"
+ v-hasPermi="['oaSystem:project:export']"
+ >瀵煎嚭椤圭洰</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 椤圭洰鍒楄〃琛ㄦ牸 -->
+ <el-table
+ v-loading="loading"
+ :data="projectList"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="50" align="center" />
+ <el-table-column
+ label="椤圭洰缂栧彿"
+ align="center"
+ prop="projectId"
+ width="100"
+ />
+ <el-table-column
+ label="椤圭洰鍚嶇О"
+ align="center"
+ prop="projectName"
+ :show-overflow-tooltip="true"
+ />
+ <el-table-column
+ label="璐熻矗浜�"
+ align="center"
+ prop="managerName"
+ />
+ <el-table-column
+ label="寮�濮嬫棩鏈�"
+ align="center"
+ prop="startDate"
+ width="120"
+ />
+ <el-table-column
+ label="缁撴潫鏃ユ湡"
+ align="center"
+ prop="endDate"
+ width="120"
+ />
+ <el-table-column
+ label="鐘舵��"
+ align="center"
+ prop="status"
+ width="90"
+ >
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="瀹屾垚搴�"
+ align="center"
+ prop="completionRate"
+ width="100"
+ >
+ <template #default="scope">
+ <el-progress :percentage="scope.row.completionRate" :stroke-width="6" />
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="180"
+ class-name="small-padding fixed-width"
+ >
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ icon="Search"
+ @click="handleView(scope.row)"
+ v-hasPermi="['oaSystem:project:query']"
+ >璇︽儏</el-button>
+ <el-button
+ link
+ type="primary"
+ icon="Edit"
+ @click="handleUpdate(scope.row)"
+ v-hasPermi="['oaSystem:project:edit']"
+ >缂栬緫</el-button>
+ <el-button
+ link
+ type="danger"
+ icon="Delete"
+ @click="handleDelete(scope.row)"
+ v-hasPermi="['oaSystem:project:remove']"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉缁勪欢 -->
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ v-model:page="queryParams.pageNum"
+ v-model:limit="queryParams.pageSize"
+ @pagination="getList"
+ />
+
+ <!-- 椤圭洰琛ㄥ崟瀵硅瘽妗� -->
+ <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+ <project-form
+ ref="projectFormRef"
+ :form="form"
+ :rules="rules"
+ :visible="open"
+ />
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="cancel">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import Pagination from '@/components/Pagination';
+import ProjectForm from './components/projectForm.vue';
+import { useRouter } from 'vue-router';
+const { proxy } = getCurrentInstance();
+// 瀵煎叆椤圭洰绠$悊API鎺ュ彛
+import { listProject, addProject, updateProject, delProject, exportProject } from '@/api/oaSystem/projectManagement';
+// import { listUser } from '@/api/system/user'; // 瀵煎叆鐢ㄦ埛鍒楄〃API鎺ュ彛
+
+// 鍒涘缓router瀹炰緥
+const router = useRouter();
+
+// 琛ㄦ牸鏁版嵁
+const projectList = ref([]);
+const loading = ref(true);
+const total = ref(0);
+const queryParams = reactive({
+ pageNum: 1,
+ pageSize: 10,
+ projectName: '',
+ managerName: '',
+ status: ''
+});
+
+// 琛ㄥ崟鏁版嵁
+const form = reactive({
+ projectId: undefined,
+ projectName: '',
+ description: '',
+ startDate: '',
+ endDate: '',
+ managerId: '',
+ managerName: '',
+ status: 'planning',
+ completionRate: 0
+});
+
+// 琛ㄥ崟鏍¢獙瑙勫垯
+const rules = {
+ projectName: [
+ { required: true, message: '椤圭洰鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
+ { min: 2, max: 50, message: '椤圭洰鍚嶇О闀垮害鍦� 2 鍒� 50 涓瓧绗�', trigger: 'blur' }
+ ],
+ startDate: [
+ { required: true, message: '寮�濮嬫棩鏈熶笉鑳戒负绌�', trigger: 'change' }
+ ],
+ endDate: [
+ { required: true, message: '缁撴潫鏃ユ湡涓嶈兘涓虹┖', trigger: 'change' }
+ ],
+ managerId: [
+ { required: true, message: '璐熻矗浜轰笉鑳戒负绌�', trigger: 'blur' }
+ ]
+};
+
+// 瀵硅瘽妗嗙姸鎬�
+const open = ref(false);
+const title = ref('');
+const projectFormRef = ref();
+const queryRef = ref();
+
+// 閫変腑鐘舵��
+const multiple = computed(() => {
+ return selectedRowKeys.value.length === 0;
+});
+const single = computed(() => {
+ return selectedRowKeys.value.length !== 1;
+});
+const selectedRowKeys = ref([]);
+
+// 鑾峰彇椤圭洰鍒楄〃
+const getList = async () => {
+ loading.value = true;
+ try {
+ const { data } = await listProject(queryParams);
+ projectList.value = data.records;
+ total.value = data.total;
+ } catch (error) {
+ ElMessage.error('鑾峰彇椤圭洰鍒楄〃澶辫触');
+ console.error('鑾峰彇椤圭洰鍒楄〃澶辫触:', error);
+ } finally {
+ loading.value = false;
+ }
+};
+
+// 鎼滅储
+const handleQuery = () => {
+ queryParams.pageNum = 1;
+ getList();
+};
+
+// 閲嶇疆
+const resetQuery = () => {
+ if (queryRef.value) {
+ queryRef.value.resetFields();
+ }
+ handleQuery();
+};
+
+// 閫変腑琛屽彉鍖�
+const handleSelectionChange = (selection) => {
+ selectedRowKeys.value = selection.map(item => item.projectId);
+};
+
+// 鏂板椤圭洰
+const handleAdd = () => {
+ resetForm();
+ open.value = true;
+ title.value = '鏂板椤圭洰';
+};
+
+// 缂栬緫椤圭洰
+const handleUpdate = async (row) => {
+ resetForm();
+ const projectId = row.projectId || selectedRowKeys.value[0];
+ try {
+ // const { data } = await getProject(projectId);
+ Object.assign(form, row);
+ open.value = true;
+ title.value = '缂栬緫椤圭洰';
+ } catch (error) {
+ ElMessage.error('鑾峰彇椤圭洰璇︽儏澶辫触');
+ console.error('鑾峰彇椤圭洰璇︽儏澶辫触:', error);
+ }
+};
+
+// 鍒犻櫎椤圭洰
+const handleDelete = async (row) => {
+ // const projectIds = row.projectId ? [row.projectId] : selectedRowKeys.value;
+ const projectNames = row.projectName ? [row.projectName] :
+ projectList.value.filter(item => projectIds.includes(item.projectId)).map(item => item.projectName);
+
+ const confirmMessage = `纭畾瑕佸垹闄ら」鐩� "${projectNames.join('銆�')}" 鍚楋紵`;
+ await ElMessageBox.confirm(confirmMessage, '纭鎿嶄綔', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).catch(() => {
+ throw new Error('鍙栨秷鍒犻櫎');
+ });
+
+ try {
+ // if (projectIds.length === 1) {
+ await delProject(row.projectId);
+ // } else {
+ // await delProjectBatch(projectIds);
+ // }
+ ElMessage.success('鍒犻櫎鎴愬姛');
+ getList();
+ } catch (error) {
+ if (error.message !== '鍙栨秷鍒犻櫎') {
+ ElMessage.error('鍒犻櫎澶辫触');
+ console.error('鍒犻櫎椤圭洰澶辫触:', error);
+ }
+ }
+ // try {
+ // await ElMessageBox.confirm(confirmMessage, '纭鎿嶄綔', {
+ // confirmButtonText: '纭畾',
+ // cancelButtonText: '鍙栨秷',
+ // type: 'warning'
+ // });
+
+ // // 妯℃嫙缃戠粶寤惰繜
+ // await new Promise(resolve => setTimeout(resolve, 300));
+
+
+ // ElMessage.success('鍒犻櫎鎴愬姛');
+ // getList();
+ // } catch (error) {
+ // if (error !== 'cancel') {
+ // console.error('鍒犻櫎椤圭洰澶辫触:', error);
+ // }
+ // }
+};
+
+// 鏌ョ湅椤圭洰璇︽儏
+const handleView = (row) => {
+ const projectId = row.projectId;
+ // 璺宠浆鍒伴」鐩鎯呴〉闈�
+ router.push({
+ path: `/oaSystem/projectManagement/projectDetail/${projectId}`,
+ query: { projectName: row.projectName }
+ });
+};
+
+// 瀵煎嚭椤圭洰
+const handleExport = async () => {
+ let ids = [];
+ if (selectedRowKeys.value.length > 0) {
+ ids = selectedRowKeys.value; // 瀵煎嚭閫変腑鐨勯」鐩�
+ } else {
+ ids = projectList.value.map(item => item.projectId); // 瀵煎嚭鎵�鏈夐」鐩�
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download(`/oA/project/export/${ids.join(',')}`, {}, "椤圭洰鏁版嵁.xlsx");
+ ElMessage.success("瀵煎嚭鎴愬姛");
+ ids = [];
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鎻愪氦琛ㄥ崟
+const submitForm = async () => {
+ try {
+ await projectFormRef.value.validate();
+
+ if (form.projectId) {
+ await updateProject(form);
+ ElMessage.success('淇敼椤圭洰鎴愬姛');
+ } else {
+ console.log("form",form);
+ await addProject(form);
+ ElMessage.success('鏂板椤圭洰鎴愬姛');
+ }
+ open.value = false;
+ getList();
+ } catch (error) {
+ console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
+ }
+};
+
+// 鍙栨秷
+const cancel = () => {
+ open.value = false;
+ resetForm();
+};
+
+// 閲嶇疆琛ㄥ崟
+const resetForm = () => {
+ form.projectId = undefined;
+ form.projectName = '';
+ form.description = '';
+ form.startDate = '';
+ form.endDate = '';
+ form.managerId = '';
+ form.managerName = '';
+ form.status = 'planning';
+ form.completionRate = 0;
+ if (projectFormRef.value) {
+ projectFormRef.value.resetFields();
+ }
+};
+
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status) => {
+ const statusTypeMap = {
+ planning: 'info',
+ inProgress: 'primary',
+ completed: 'success',
+ paused: 'warning'
+ };
+ return statusTypeMap[status] || 'default';
+};
+
+// 鑾峰彇鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+ const statusTextMap = {
+ planning: '瑙勫垝涓�',
+ inProgress: '杩涜涓�',
+ completed: '宸插畬鎴�',
+ paused: '宸叉殏鍋�'
+ };
+ return statusTextMap[status] || status;
+};
+
+// 鍒濆鍖�
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/oaSystem/projectManagement/projectDetail.vue b/src/views/oaSystem/projectManagement/projectDetail.vue
new file mode 100644
index 0000000..c3b0779
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/projectDetail.vue
@@ -0,0 +1,565 @@
+// ... existing code ...
+<template>
+ <div class="app-container">
+ <!-- 椤圭洰鍩烘湰淇℃伅 -->
+ <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰鍩烘湰淇℃伅</span>
+ </div>
+ </template>
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="椤圭洰鍚嶇О">{{ projectInfo.projectName }}</el-descriptions-item>
+ <el-descriptions-item label="椤圭洰璐熻矗浜�">{{ projectInfo.managerName }}</el-descriptions-item>
+ <el-descriptions-item label="寮�濮嬫棩鏈�">{{ projectInfo.startDate }}</el-descriptions-item>
+ <el-descriptions-item label="缁撴潫鏃ユ湡">{{ projectInfo.endDate }}</el-descriptions-item>
+ <el-descriptions-item label="椤圭洰鐘舵��">
+ <el-tag :type="getStatusType(projectInfo.status)">{{ getStatusText(projectInfo.status) }}</el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="瀹屾垚搴�">
+ <el-progress :percentage="projectInfo.completionRate" :stroke-width="6" />
+ </el-descriptions-item>
+ <el-descriptions-item label="椤圭洰鎻忚堪" :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <!-- 椤圭洰杩涘害姒傝 -->
+ <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰杩涘害姒傝</span>
+ </div>
+ </template>
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">鎬讳綋杩涘害</div>
+ <div class="progress-number">{{ projectInfo.completionRate }}%</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">闃舵鎬绘暟</div>
+ <div class="progress-number">{{ statistics.totalPhases }}</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">浠诲姟鎬绘暟</div>
+ <div class="progress-number">{{ statistics.totalTasks }}</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">宸插畬鎴愪换鍔�</div>
+ <div class="progress-number">{{ statistics.completedTasks }}</div>
+ </div>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 闃舵鍜屼换鍔$鐞� -->
+ <!-- <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰浠诲姟缁撴瀯</span>
+ <el-button type="primary" size="small" @click="handleAddPhase">娣诲姞闃舵</el-button>
+ </div>
+ </template>
+ <task-tree :project-id="projectId" @refresh="getProjectDetail" />
+ </el-card> -->
+
+ <!-- 閲岀▼纰戠鐞� -->
+ <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰闃舵閲岀▼纰�</span>
+ <el-button type="primary" size="small" @click="handleAddMilestone">娣诲姞閲岀▼纰�</el-button>
+ </div>
+ </template>
+ <milestone-list :project-id="projectId" @refresh="getProjectDetail" :key="`milestone-${refreshProjectId}`"/>
+ </el-card>
+
+ <!-- 闃舵鐩爣绠$悊 -->
+ <el-card>
+ <template #header>
+ <div class="card-header">
+ <span>闃舵浠诲姟</span>
+ <el-button type="primary" size="small" @click="handleAddPhaseGoal">娣诲姞闃舵鐩爣</el-button>
+ </div>
+ </template>
+ <phase-goal-list :project-id="projectId" @refresh="getProjectDetail" @editGoal="handleEditPhaseGoal" :key="`phaseGoal-${refreshProjectId}`"/>
+ </el-card>
+
+ <!-- 閲岀▼纰戠鐞嗗脊妗� -->
+ <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+ <el-form :model="form" ref="formRef" label-width="100px">
+ <el-form-item label="椤圭洰闃舵鍚嶇О" prop="phaseName">
+ <el-input
+ v-model="form.phaseName"
+ placeholder="璇疯緭鍏ラ」鐩樁娈靛悕绉�"
+ maxlength="50"
+ />
+ </el-form-item>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="form.startDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="form.endDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨缁撴潫鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio label="notStarted">鏈紑濮�</el-radio>
+ <el-radio label="completed">宸插畬鎴�</el-radio>
+ <el-radio label="delayed">宸插欢杩�</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="cancel">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 闃舵浠诲姟绠$悊寮规 -->
+ <el-dialog :title="goalTitle" v-model="goalOpen" width="600px" append-to-body>
+ <el-form :model="goalForm" ref="goalFormRef" label-width="100px">
+ <el-form-item label="鎵�灞為樁娈�" prop="phaseId">
+ <el-select v-model="goalForm.phaseId" placeholder="璇烽�夋嫨鎵�灞為樁娈�">
+ <el-option
+ v-for="phase in phaseList"
+ :key="phase.phaseId"
+ :label="phase.phaseName"
+ :value="phase.phaseId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐩爣鍚嶇О" prop="taskName">
+ <el-input
+ v-model="goalForm.taskName"
+ placeholder="璇疯緭鍏ョ洰鏍囧悕绉�"
+ maxlength="50"
+ />
+ </el-form-item>
+ <el-form-item label="鐩爣鍊�" prop="targetValue">
+ <el-input-number
+ v-model="goalForm.targetValue"
+ :min="0"
+ :precision="2"
+ placeholder="璇疯緭鍏ョ洰鏍囧��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="褰撳墠鍊�" prop="currentValue">
+ <el-input-number
+ v-model="goalForm.currentValue"
+ :min="0"
+ :precision="2"
+ placeholder="璇疯緭鍏ュ綋鍓嶅��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鍗曚綅" prop="unit">
+ <el-input
+ v-model="goalForm.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ maxlength="10"
+ />
+ </el-form-item>
+ <el-form-item label="浠诲姟瀹屾垚鏃ユ湡" prop="targetDate">
+ <el-date-picker
+ v-model="goalForm.targetDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨鐩爣鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="goalForm.startDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨鐩爣鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="goalForm.endDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨鐩爣鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="goalForm.status" placeholder="璇烽�夋嫨鐘舵��">
+ <el-option label="鏈紑濮�" value="notStarted" />
+ <el-option label="杩涜涓�" value="inProgress" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="宸插欢杩�" value="delayed" />
+ </el-select>
+ </el-form-item>
+ <!-- <el-form-item label="瀹屾垚搴�" prop="completionRate">
+ <el-input-number
+ v-model="goalForm.completionRate"
+ :min="0"
+ :max="100"
+ :precision="2"
+ placeholder="璇疯緭鍏ュ畬鎴愬害"
+ style="width: 100%"
+ />
+ </el-form-item> -->
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="cancelGoal">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitGoalForm">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, watch } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import TaskTree from './components/taskTree.vue';
+import MilestoneList from './components/milestoneList.vue';
+import ProjectForm from './components/projectForm.vue';
+import PhaseGoalList from './components/phaseGoalList.vue';
+import { getProject, addProjectPhase, listProjectPhase, addProjectTask, updateProjectTask } from '@/api/oaSystem/projectManagement';
+
+const route = useRoute();
+const router = useRouter();
+const open = ref(false);
+const title = ref('');
+const projectFormRef = ref();
+const formRef = ref();
+// 椤圭洰ID
+// 鍦ㄥ叾浠杛ef瀹氫箟闄勮繎娣诲姞
+const refreshProjectId = ref(0);
+
+const projectId = ref(route.params.projectId);
+
+// 椤圭洰淇℃伅
+const projectInfo = reactive({
+ projectId: '',
+ projectName: '',
+ description: '',
+ startDate: '',
+ endDate: '',
+ managerId: '',
+ managerName: '',
+ status: 'planning',
+ completionRate: 0
+});
+
+// 缁熻淇℃伅
+const statistics = reactive({
+ totalPhases: 0,
+ totalTasks: 0,
+ completedTasks: 0
+});
+const form = reactive({
+ phaseId: '',
+ phaseName: '',
+ startDate: '',
+ endDate: '',
+ status: 'planning',
+ oaProjectId: projectId.value,
+})
+
+// 闃舵鐩爣鐩稿叧
+const goalOpen = ref(false);
+const goalTitle = ref('');
+const goalFormRef = ref();
+const phaseList = ref([]);
+const goalForm = reactive({
+ taskId: '',
+ phaseId: '',
+ taskName: '',
+ targetValue: 100,
+ currentValue: 0,
+ unit: '%',
+ targetDate: '',
+ startDate: '',
+ endDate: '',
+ status: 'notStarted',
+ completionRate: 0
+});
+
+// 鑾峰彇椤圭洰璇︽儏
+const getProjectDetail = async () => {
+ try {
+ getProject().then((res)=>{
+ console.log("椤圭洰璇︽儏",res)
+ const projectData = res.data[projectId.value];
+ // 鏇存柊椤圭洰淇℃伅
+ Object.assign(projectInfo, projectData);
+
+ // 鏇存柊缁熻淇℃伅
+ updateStatistics(projectData);
+
+ // 寮哄埗鏇存柊DOM浠ョ‘淇濆瓙缁勪欢鑳芥纭埛鏂�
+ // 杩欓噷閫氳繃瑙﹀彂refreshProjectId浜嬩欢鏉ュ己鍒跺埛鏂板瓙缁勪欢
+ refreshProjectId.value++;
+ })
+ } catch (error) {
+ ElMessage.error('鑾峰彇椤圭洰璇︽儏澶辫触');
+ console.error('鑾峰彇椤圭洰璇︽儏澶辫触:', error);
+ }
+};
+
+// 鏇存柊缁熻淇℃伅
+const updateStatistics = (projectData) => {
+ // 杩欓噷鍋囪projectData涓寘鍚簡缁熻淇℃伅
+ // 濡傛灉娌℃湁锛岄渶瑕佸崟鐙姹傜粺璁℃暟鎹�
+ statistics.totalPhases = projectData.phases ? projectData.phases.length : 0;
+ statistics.totalTasks = projectData.tasks ? projectData.tasks.length : 0;
+ statistics.completedTasks = projectData.tasks ?
+ projectData.tasks.filter(task => task.status === 'completed').length : 0;
+};
+
+// 鑾峰彇椤圭洰闃舵鍒楄〃
+const getPhaseList = async () => {
+ try {
+ const { data } = await listProjectPhase(projectId.value);
+ phaseList.value = data.rows || data;
+ } catch (error) {
+ ElMessage.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触');
+ console.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触:', error);
+ }
+};
+
+// 璁$畻瀹屾垚搴�
+const calculateCompletionRate = () => {
+ if (goalForm.targetValue > 0) {
+ goalForm.completionRate = Math.min(Math.round((goalForm.currentValue / goalForm.targetValue) * 100), 100);
+ } else {
+ goalForm.completionRate = 0;
+ }
+};
+
+// 娣诲姞闃舵
+const handleAddPhase = () => {
+ // resetForm();
+ ElMessage.info('娣诲姞闃舵鍔熻兘寰呭疄鐜�');
+};
+
+// 娣诲姞閲岀▼纰�
+const handleAddMilestone = () => {
+ resetForm();
+ open.value = true;
+ title.value = '鏂板椤圭洰闃舵';
+};
+
+// 娣诲姞闃舵浠诲姟
+const handleAddPhaseGoal = () => {
+ goalForm.taskId = '';
+ goalForm.phaseId = '';
+ goalForm.taskName = '';
+ goalForm.targetValue = 0;
+ goalForm.currentValue = 0;
+ goalForm.unit = '%';
+ goalForm.targetDate = '';
+ goalForm.startDate = '';
+ goalForm.endDate = '';
+ goalForm.status = 'notStarted';
+ goalForm.completionRate = 0;
+ if (goalFormRef.value) {
+ goalFormRef.value.resetFields();
+ }
+ getPhaseList();
+ goalTitle.value = '鏂板闃舵鐩爣';
+ goalOpen.value = true;
+};
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = async () => {
+ try {
+ await formRef.value.validate();
+
+ if (form.phaseId) {
+ // await updateProject(form);
+ // ElMessage.success('淇敼椤圭洰闃舵鎴愬姛');
+ } else {
+ console.log("form",form);
+ await addProjectPhase(form);
+ ElMessage.success('鏂板椤圭洰闃舵鎴愬姛');
+ getProjectDetail();
+ }
+ open.value = false;
+ } catch (error) {
+ console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
+ }
+};
+
+// 鎻愪氦闃舵浠诲姟琛ㄥ崟
+const submitGoalForm = async () => {
+ try {
+ await goalFormRef.value.validate();
+ calculateCompletionRate();
+
+ const goalData = {
+ ...goalForm,
+ oaProjectId: projectId.value
+ };
+
+ if (goalForm.taskId) {
+ await updateProjectTask(goalData);
+ ElMessage.success('淇敼闃舵鐩爣鎴愬姛');
+
+ } else {
+ await addProjectTask(goalData);
+ ElMessage.success('鏂板闃舵鐩爣鎴愬姛');
+
+ }
+ // 璋冪敤getProjectDetail鍒锋柊鎵�鏈夌浉鍏虫暟鎹�
+ getProjectDetail();
+ goalOpen.value = false;
+
+ } catch (error) {
+ console.error('鎻愪氦闃舵鐩爣琛ㄥ崟澶辫触:', error);
+ }
+};
+
+// 閲嶇疆閲岀▼纰戣〃鍗�
+const resetForm = () => {
+ form.phaseId = '';
+ form.phaseName = '';
+ form.startDate = '';
+ form.endDate = '';
+ form.status = 'planning';
+ form.oaProjectId = projectId.value;
+ if (formRef.value) {
+ formRef.value.resetFields();
+ }
+};
+
+// 鍙栨秷闃舵浠诲姟鎿嶄綔
+const cancelGoal = () => {
+ goalOpen.value = false;
+};
+
+// 鍙栨秷鎿嶄綔
+const cancel = () => {
+ open.value = false;
+};
+// 缂栬緫闃舵浠诲姟
+const handleEditPhaseGoal = async (goal) => {
+ // 澶嶅埗鐩爣鏁版嵁鍒拌〃鍗�
+ Object.assign(goalForm, goal);
+
+ // 鑾峰彇椤圭洰闃舵鍒楄〃
+ await getPhaseList();
+
+ // 鎵撳紑缂栬緫寮圭獥
+ goalTitle.value = '缂栬緫闃舵鐩爣';
+ goalOpen.value = true;
+};
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status) => {
+ const statusTypeMap = {
+ planning: 'info',
+ inProgress: 'primary',
+ completed: 'success',
+ paused: 'warning'
+ };
+ return statusTypeMap[status] || 'default';
+};
+
+// 鑾峰彇鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+ const statusTextMap = {
+ planning: '瑙勫垝涓�',
+ inProgress: '杩涜涓�',
+ completed: '宸插畬鎴�',
+ paused: '宸叉殏鍋�'
+ };
+ return statusTextMap[status] || status;
+};
+
+// 鐩戝惉璺敱鍙傛暟鍙樺寲
+watch(() => route.params.projectId, (newProjectId) => {
+ // console.log('璺敱鍙傛暟鍙樺寲:', projectId);
+ if (newProjectId) {
+ projectId.value = newProjectId;
+ getProjectDetail();
+ }
+});
+
+// 鐩戝惉褰撳墠鍊煎拰鐩爣鍊煎彉鍖栵紝閲嶆柊璁$畻瀹屾垚搴�
+watch(() => [goalForm.currentValue, goalForm.targetValue], () => {
+ calculateCompletionRate();
+});
+
+// 鍒濆鍖�
+onMounted(() => {
+ if (projectId.value) {
+ getProjectDetail();
+ }
+});
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.progress-item {
+ text-align: center;
+ padding: 20px;
+ background-color: #f5f7fa;
+ border-radius: 8px;
+}
+
+.progress-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 10px;
+}
+
+.progress-number {
+ font-size: 24px;
+ font-weight: bold;
+ color: #409eff;
+}
+
+.mb20 {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/src/views/personnelManagement/analytics/index.vue b/src/views/personnelManagement/analytics/index.vue
new file mode 100644
index 0000000..9e6e449
--- /dev/null
+++ b/src/views/personnelManagement/analytics/index.vue
@@ -0,0 +1,702 @@
+<template>
+ <div class="app-container analytics-container" v-loading="loading">
+
+ <!-- 鍏抽敭鎸囨爣鍗$墖 -->
+ <el-row :gutter="20" class="metrics-cards">
+ <el-col :span="6" v-for="(item, index) in keyMetrics" :key="index">
+ <el-card class="metric-card" :class="item.type">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon :size="32">
+ <component :is="item.icon" />
+ </el-icon>
+ </div>
+ <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>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <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="info">杩�12涓湀</el-tag>
+ </div>
+ </template>
+ <div class="chart-container">
+ <div ref="turnoverChartRef" class="chart"></div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <!-- 閮ㄩ棬浜哄憳鍒嗗竷 -->
+ <el-col :span="12">
+ <el-card class="chart-card">
+ <template #header>
+ <div class="card-header">
+ <span>閮ㄩ棬浜哄憳鍒嗗竷</span>
+ <el-tag type="success">褰撳墠鐘舵��</el-tag>
+ </div>
+ </template>
+ <div class="chart-container">
+ <div ref="departmentChartRef" class="chart"></div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 绗簩琛屽浘琛� -->
+ <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="danger">骞村害缁熻</el-tag>
+ </div>
+ </template>
+ <div class="chart-container">
+ <div ref="attritionChartRef" class="chart"></div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import * as echarts from 'echarts'
+import {listDept} from "@/api/system/dept.js";
+import {
+ findStaffAnalysisMonthlyTurnoverRateFor12Months,
+ findStaffLeaveReasonAnalysis,
+ findStaffAnalysisTotalStatistic
+} from "@/api/personnelManagement/staffAnalytics.js";
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const autoRefreshEnabled = ref(true)
+const autoRefreshInterval = ref(null)
+
+// 鍥捐〃寮曠敤
+const turnoverChartRef = ref(null)
+const departmentChartRef = ref(null)
+const staffingChartRef = ref(null)
+const attritionChartRef = ref(null)
+
+// 鍥捐〃瀹炰緥
+let turnoverChart = null
+let departmentChart = null
+let staffingChart = null
+let attritionChart = null
+
+// 鑷姩鏇存柊闂撮殧锛�10鍒嗛挓锛�
+const AUTO_REFRESH_INTERVAL = 10 * 60 * 1000
+
+// 鍏抽敭鎸囨爣鏁版嵁
+const keyMetrics = ref([
+ {
+ label: '鍛樺伐娴佸姩鐜�',
+ value: 0,
+ unit: '%',
+ icon: 'TrendCharts',
+ type: 'primary',
+ trend: 0
+ },
+ {
+ label: '鍛樺伐娴佸け鐜�',
+ value: 0,
+ unit: '%',
+ icon: 'User',
+ type: 'danger',
+ trend: 0
+ },
+ {
+ label: '鍦ㄨ亴鍛樺伐鏁�',
+ value: 0,
+ unit: '浜�',
+ icon: 'PieChart',
+ type: 'warning',
+ trend: 0,
+ showTrend: false
+ }
+])
+
+// 閮ㄩ棬鏁版嵁
+const departmentData = ref([])
+// 鍛樺伐娴佸け鍘熷洜鍒嗘瀽鏁版嵁
+const staffLeaveReasons = ref([])
+// 12涓湀鍛樺伐娴佸姩娴佸け鐜囧垎鏋愭暟鎹�
+const turnoverRateStatistics = ref([])
+
+// 鑾峰彇閮ㄩ棬鏁版嵁
+const getDepartmentData = async () => {
+ try {
+ const res = await listDept()
+ if (res && res.data) {
+ departmentData.value = res.data
+ }
+ } catch (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)
+ }
+}
+
+// 淇敼涓鸿繑鍥濸romise鐨勫紓姝ュ嚱鏁�
+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)
+ }
+}
+
+// 鍚姩鑷姩鍒锋柊
+const startAutoRefresh = () => {
+ if (autoRefreshInterval.value) {
+ clearInterval(autoRefreshInterval.value)
+ }
+ if (autoRefreshEnabled.value) {
+ autoRefreshInterval.value = setInterval(() => {
+ refreshData()
+ }, AUTO_REFRESH_INTERVAL)
+ }
+}
+
+// 鍋滄鑷姩鍒锋柊
+const stopAutoRefresh = () => {
+ if (autoRefreshInterval.value) {
+ clearInterval(autoRefreshInterval.value)
+ autoRefreshInterval.value = null
+ }
+}
+
+// 鍒囨崲鑷姩鍒锋柊鐘舵��
+const toggleAutoRefresh = (value) => {
+ if (value) {
+ startAutoRefresh()
+ } else {
+ stopAutoRefresh()
+ }
+}
+
+// 淇敼涓哄紓姝ュ嚱鏁帮紝纭繚鏁版嵁鍔犺浇瀹屾垚鍚庡啀娓叉煋鍥捐〃
+const refreshData = async () => {
+ try {
+ 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('鏁版嵁鍒锋柊澶辫触')
+ } finally {
+ loading.value = false
+ }
+}
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+ setTimeout(() => {
+ if (turnoverChartRef.value) {
+ turnoverChart = echarts.init(turnoverChartRef.value)
+ }
+ if (departmentChartRef.value) {
+ departmentChart = echarts.init(departmentChartRef.value)
+ }
+ if (staffingChartRef.value) {
+ staffingChart = echarts.init(staffingChartRef.value)
+ }
+ if (attritionChartRef.value) {
+ attritionChart = echarts.init(attritionChartRef.value)
+ }
+
+ // 鍒濆鍖栨椂涔熷厛鍔犺浇鏁版嵁鍐嶆覆鏌撳浘琛�
+ refreshData()
+ }, 300)
+}
+
+// 娓叉煋鎵�鏈夊浘琛�
+const renderAllCharts = () => {
+ renderTurnoverChart()
+ renderDepartmentChart()
+ renderStaffingChart()
+ renderAttritionChart()
+}
+
+// 淇敼涓轰娇鐢ˋPI杩斿洖鐨勫疄闄呮暟鎹�
+const renderTurnoverChart = () => {
+ if (!turnoverChart) return
+
+ // 浣跨敤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: '鍛樺伐娴佸姩鐜囪秼鍔�',
+ left: 'center',
+ textStyle: { fontSize: 16, fontWeight: 'normal' }
+ },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: { type: 'cross' }
+ },
+ legend: {
+ data: ['娴佸姩鐜�', '娴佸け鐜�'],
+ bottom: 10
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '15%',
+ top: '15%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: months,
+ boundaryGap: false
+ },
+ yAxis: {
+ type: 'value',
+ axisLabel: { formatter: '{value}%' }
+ },
+ series: [
+ {
+ name: '娴佸姩鐜�',
+ type: 'line',
+ data: turnoverData,
+ smooth: true,
+ lineStyle: { color: '#409EFF' },
+ itemStyle: { color: '#409EFF' }
+ },
+ {
+ name: '娴佸け鐜�',
+ type: 'line',
+ data: attritionData,
+ smooth: true,
+ lineStyle: { color: '#F56C6C' },
+ itemStyle: { color: '#F56C6C' }
+ }
+ ]
+ }
+
+ turnoverChart.setOption(option)
+}
+
+// 娓叉煋閮ㄩ棬浜哄憳鍒嗗竷鍥�
+const renderDepartmentChart = () => {
+ if (!departmentChart) return
+
+ const data = departmentData.value.map(item => ({
+ name: item.deptName,
+ value: item.staffCount
+ }))
+
+ const option = {
+ title: {
+ text: '閮ㄩ棬浜哄憳鍒嗗竷',
+ left: 'center',
+ textStyle: { fontSize: 16, fontWeight: 'normal' }
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b}: {c}浜� ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'left',
+ top: 'middle'
+ },
+ series: [
+ {
+ name: '浜哄憳鏁伴噺',
+ type: 'pie',
+ radius: ['40%', '70%'],
+ center: ['60%', '50%'],
+ data: data,
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
+ }
+ }
+ }
+ ]
+ }
+
+ departmentChart.setOption(option)
+}
+
+// 娓叉煋缂栧埗杈炬垚鐜囧浘
+const renderStaffingChart = () => {
+ if (!staffingChart) return
+
+ const departments = departmentData.value.map(item => item.deptName)
+ const rates = departmentData.value.map(item => item.staffingRate)
+
+ const option = {
+ title: {
+ text: '缂栧埗杈炬垚鐜�',
+ left: 'center',
+ textStyle: { fontSize: 16, fontWeight: 'normal' }
+ },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: { type: 'shadow' }
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '15%',
+ top: '15%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: departments,
+ axisLabel: { rotate: 45 }
+ },
+ yAxis: {
+ type: 'value',
+ axisLabel: { formatter: '{value}%' },
+ max: 100
+ },
+ series: [
+ {
+ name: '杈炬垚鐜�',
+ type: 'bar',
+ data: rates,
+ itemStyle: {
+ color: function(params) {
+ const value = params.value
+ if (value >= 90) return '#67C23A'
+ if (value >= 80) return '#E6A23C'
+ return '#F56C6C'
+ }
+ }
+ }
+ ]
+ }
+
+ staffingChart.setOption(option)
+}
+
+// 娓叉煋鍛樺伐娴佸け鍘熷洜鍒嗘瀽鍥�
+const renderAttritionChart = () => {
+ if (!attritionChart) return
+
+ const reasons = staffLeaveReasons.value.map(item => item.reasonText)
+ const data = staffLeaveReasons.value.map(item => item.count)
+
+ const option = {
+ title: {
+ text: '鍛樺伐娴佸け鍘熷洜鍒嗘瀽',
+ left: 'center',
+ textStyle: { fontSize: 16, fontWeight: 'normal' }
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b}: {c}浜� ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'left',
+ top: 'middle'
+ },
+ series: [
+ {
+ name: '娴佸け浜烘暟',
+ type: 'pie',
+ radius: '50%',
+ center: ['60%', '50%'],
+ data: reasons.map((reason, index) => ({
+ name: reason,
+ value: data[index]
+ })),
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
+ }
+ }
+ }
+ ]
+ }
+
+ attritionChart.setOption(option)
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ initCharts()
+ startAutoRefresh()
+})
+
+onUnmounted(() => {
+ stopAutoRefresh()
+})
+</script>
+
+<style scoped>
+.analytics-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: 30px;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 12px;
+ color: white;
+}
+
+.page-header h2 {
+ color: white;
+ margin-bottom: 10px;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.page-header p {
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 14px;
+ margin: 0 0 15px 0;
+}
+
+.header-controls {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 20px;
+}
+
+.refresh-btn {
+ margin-left: 20px;
+}
+
+.metrics-cards {
+ margin-bottom: 30px;
+}
+
+.metric-card {
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s ease;
+ border: none;
+ overflow: hidden;
+}
+
+.metric-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.metric-card.primary {
+ border-left: 4px solid #409EFF;
+ background: linear-gradient(135deg, #409EFF 0%, #36a3f7 100%);
+}
+
+.metric-card.danger {
+ border-left: 4px solid #F56C6C;
+ background: linear-gradient(135deg, #F56C6C 0%, #f78989 100%);
+}
+
+.metric-card.success {
+ border-left: 4px solid #67C23A;
+ background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%);
+}
+
+.metric-card.warning {
+ border-left: 4px solid #E6A23C;
+ background: linear-gradient(135deg, #E6A23C 0%, #ebb563 100%);
+}
+
+.card-content {
+ display: flex;
+ align-items: center;
+ padding: 20px;
+}
+
+.card-icon {
+ margin-right: 20px;
+ color: white;
+}
+
+.card-info {
+ flex: 1;
+}
+
+.card-number {
+ font-size: 32px;
+ font-weight: 600;
+ color: white;
+ margin-bottom: 5px;
+}
+
+.card-label {
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.9);
+ margin-bottom: 5px;
+}
+
+.card-trend {
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.card-trend.positive {
+ color: #67C23A;
+}
+
+.card-trend.negative {
+ color: #F56C6C;
+}
+
+.charts-section {
+ margin-bottom: 30px;
+}
+
+.chart-card {
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ border: none;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: 600;
+ color: #303133;
+ padding: 15px 20px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.chart-container {
+ height: 350px;
+ padding: 20px;
+}
+
+.chart {
+ width: 100%;
+ height: 100%;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .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;
+ }
+}
+
+@media (max-width: 480px) {
+ .page-header h2 {
+ font-size: 20px;
+ }
+
+ .card-number {
+ font-size: 24px;
+ }
+
+ .chart-container {
+ height: 250px;
+ }
+}
+</style>
diff --git a/src/views/personnelManagement/contractManagement/components/formDia.vue b/src/views/personnelManagement/contractManagement/components/formDia.vue
index 6538273..54b2ef9 100644
--- a/src/views/personnelManagement/contractManagement/components/formDia.vue
+++ b/src/views/personnelManagement/contractManagement/components/formDia.vue
@@ -19,15 +19,17 @@
</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([
@@ -43,6 +45,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([]);
const tableLoading = ref(false);
@@ -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;
diff --git a/src/views/personnelManagement/contractManagement/filesDia.vue b/src/views/personnelManagement/contractManagement/filesDia.vue
index f752496..c0c5ee9 100644
--- a/src/views/personnelManagement/contractManagement/filesDia.vue
+++ b/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>
diff --git a/src/views/personnelManagement/contractManagement/index.vue b/src/views/personnelManagement/contractManagement/index.vue
index 98f0272..1d2aab7 100644
--- a/src/views/personnelManagement/contractManagement/index.vue
+++ b/src/views/personnelManagement/contractManagement/index.vue
@@ -74,19 +74,16 @@
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";
const data = reactive({
searchForm: {
staffName: "",
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDate: null, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
},
});
const { searchForm } = toRefs(data);
@@ -127,7 +124,7 @@
prop: "sex",
},
{
- label: "绫嶈疮",
+ label: "鎴风睄浣忓潃",
prop: "nativePlace",
},
{
@@ -135,7 +132,7 @@
prop: "postJob",
},
{
- label: "瀹跺涵浣忓潃",
+ label: "鐜颁綇鍧�",
prop: "adress",
width: 200
},
@@ -147,11 +144,6 @@
label: "涓撲笟",
prop: "profession",
width: 100
- },
- {
- label: "韬唤璇佸彿",
- prop: "identityCard",
- width: 200
},
{
label: "骞撮緞",
@@ -172,10 +164,10 @@
prop: "emergencyContactPhone",
width: 150
},
- {
- label: "鍚堝悓骞撮檺",
- prop: "contractTerm",
- },
+ // {
+ // label: "鍚堝悓骞撮檺",
+ // prop: "contractTerm",
+ // },
// {
// label: "鍚堝悓寮�濮嬫棩鏈�",
// prop: "contractStartTime",
@@ -199,14 +191,7 @@
clickFun: (row) => {
openForm("edit", row);
},
- },
- {
- name: "闄勪欢",
- type: "text",
- clickFun: (row) => {
- openFilesFormDia(row);
- },
- },
+ }
],
},
]);
@@ -253,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
@@ -280,7 +266,7 @@
type: "warning",
})
.then(() => {
- proxy.download("/staff/staffOnJob/export", {}, "鍚堝悓绠$悊.xlsx");
+ proxy.download("/staff/staffOnJob/export", {staffState: 1}, "鍚堝悓绠$悊.xlsx");
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
diff --git a/src/views/personnelManagement/dimission/components/formDia.vue b/src/views/personnelManagement/dimission/components/formDia.vue
index f63b011..2b8a7fd 100644
--- a/src/views/personnelManagement/dimission/components/formDia.vue
+++ b/src/views/personnelManagement/dimission/components/formDia.vue
@@ -6,133 +6,146 @@
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="staffName">
- <!-- <el-input v-model="form.staffName" placeholder="璇疯緭鍏�" clearable/> -->
- <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-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍛樺伐缂栧彿锛�" prop="staffNo">
- <el-input v-model="form.staffNo" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鎬у埆锛�" prop="sex">
- <el-select v-model="form.sex" disabled>
- <el-option label="鐢�" value="鐢�" />
- <el-option label="濂�" value="濂�" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="绫嶈疮锛�" prop="nativePlace">
- <el-input v-model="form.nativePlace" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="宀椾綅锛�" prop="postJob">
- <el-input v-model="form.postJob" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="瀹跺涵浣忓潃锛�" prop="adress">
- <el-input v-model="form.adress" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="绗竴瀛﹀巻锛�" prop="firstStudy">
- <el-input v-model="form.firstStudy" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="涓撲笟锛�" prop="profession">
- <el-input v-model="form.profession" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="韬唤璇佸彿锛�" prop="identityCard">
- <el-input v-model="form.identityCard" placeholder="璇疯緭鍏�" clearable disabled/>
- </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%" disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鑱旂郴鐢佃瘽锛�" prop="phone">
- <el-input v-model="form.phone" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="绱ф�ヨ仈绯讳汉锛�" prop="emergencyContact">
- <el-input v-model="form.emergencyContact" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="绱ф�ヨ仈绯讳汉鑱旂郴鐢佃瘽锛�" prop="emergencyContactPhone">
- <el-input v-model="form.emergencyContactPhone" placeholder="璇疯緭鍏�" clearable disabled/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍚堝悓骞撮檺锛�" prop="contractTerm">
- <el-input-number v-model="form.contractTerm" :precision="0" :step="1" style="width: 100%" disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鍚堝悓寮�濮嬫棩鏈燂細" prop="contractStartTime">
- <el-date-picker
- disabled
- v-model="form.contractStartTime"
- 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="contractEndTime">
- <el-date-picker
- disabled
- v-model="form.contractEndTime"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
+ <!-- 鍛樺伐淇℃伅灞曠ず鍖哄煙 -->
+ <div class="info-section">
+ <div class="info-title">鍛樺伐淇℃伅</div>
+ <el-form :model="form" label-width="200px" label-position="left" :rules="rules" ref="formRef" style="margin-top: 20px">
+ <el-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-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>
+
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">纭</el-button>
@@ -144,8 +157,9 @@
</template>
<script setup>
-import {ref} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate,getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
+import {ref, reactive, toRefs, getCurrentInstance} from "vue";
+import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
+import {createStaffLeave, updateStaffLeave} from "@/api/personnelManagement/staffLeave.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -153,78 +167,81 @@
const operationType = ref('')
const data = reactive({
form: {
- staffNo: "",
- staffName: "",
- sex: "",
- nativePlace: "",
- postJob: "",
- adress: "",
- firstStudy: "",
- profession: "",
- identityCard: "",
- age: 0,
- phone: "",
- emergencyContact: "",
- emergencyContactPhone: "",
- contractTerm: 0,
- contractStartTime: "",
- contractEndTime: "",
- staffState: "",
+ staffOnJobId: undefined,
+ reason: "",
+ remark: "",
},
rules: {
- staffNo: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
- staffName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- sex: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- nativePlace: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- postJob: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- adress: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- firstStudy: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- profession: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- identityCard: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- age: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- phone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- emergencyContact: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- emergencyContactPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- contractTerm: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- contractStartTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- contractEndTime: [{ 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 = () => {
- proxy.$refs.formRef.validate(valid => {
+ form.value.staffState = 0
+ if (form.value.reason !== 'other') {
+ form.value.remark = ''
+ }
+ proxy.$refs["formRef"].validate(valid => {
if (valid) {
- form.value.staffState = 0
if (operationType.value === "add") {
- staffJoinAdd(form.value).then(res => {
+ createStaffLeave(form.value).then(res => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
})
} else {
- staffJoinUpdate(form.value).then(res => {
+ updateStaffLeave(currentStaffRecord.value.id, form.value).then(res => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
})
}
}
})
+
}
// 鍏抽棴寮规
const closeDia = () => {
- proxy.resetForm("formRef");
+ // 琛ㄥ崟宸叉敞閲婏紝鎵嬪姩閲嶇疆琛ㄥ崟鏁版嵁
+ form.value = {
+ staffOnJobId: undefined,
+ reason: "",
+ remark: "",
+ };
dialogFormVisible.value = false;
emit('close')
};
@@ -235,50 +252,22 @@
* 鑾峰彇褰撳墠鍦ㄨ亴浜哄憳鍒楄〃
*/
const getList = () => {
- getStaffOnJob().then(res => {
- personList.value = res.data
+ staffOnJobListPage({
+ current: -1,
+ size: -1,
+ staffState: 1
+ }).then(res => {
+ personList.value = res.data.records || []
})
};
const handleSelect = (val) => {
- let obj = personList.value.find(item => item.staffName === val)
- let {
- sex,
- phone,
- staffNo,
- nativePlace,
- postJob,
- adress,
- firstStudy,
- profession,
- identityCard,
- age,
- emergencyContact,
- emergencyContactPhone,
- contractTerm,
- contractStartTime,
- contractEndTime,
- staffName
- } = obj
- form.value = {
- sex,
- phone,
- staffNo,
- nativePlace,
- postJob,
- adress,
- firstStudy,
- profession,
- identityCard,
- age,
- emergencyContact,
- emergencyContactPhone,
- contractTerm,
- contractStartTime,
- contractEndTime,
- staffName
+ let obj = personList.value.find(item => item.id === val)
+ currentStaffRecord.value = {}
+ if (obj) {
+ // 淇濈暀绂昏亴鏃ユ湡鍜岀鑱屽師鍥狅紝鍙洿鏂板憳宸ヤ俊鎭�
+ currentStaffRecord.value = obj
}
-
}
defineExpose({
openDialog,
@@ -286,5 +275,39 @@
</script>
<style scoped>
+.info-section {
+ background: #f5f7fa;
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+}
+.info-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 20px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #e4e7ed;
+}
+
+.info-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 16px;
+ min-height: 32px;
+}
+
+.info-label {
+ min-width: 140px;
+ color: #606266;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.info-value {
+ flex: 1;
+ color: #303133;
+ font-size: 14px;
+}
</style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/dimission/index.vue b/src/views/personnelManagement/dimission/index.vue
index 6e20b1e..c6ed705 100644
--- a/src/views/personnelManagement/dimission/index.vue
+++ b/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: {
@@ -105,15 +88,19 @@
prop: "sex",
},
{
- label: "绫嶈疮",
+ label: "鎴风睄浣忓潃",
prop: "nativePlace",
},
{
- label: "宀椾綅",
- prop: "postJob",
+ label: "閮ㄩ棬",
+ prop: "deptName",
},
{
- label: "瀹跺涵浣忓潃",
+ label: "宀椾綅",
+ prop: "postName",
+ },
+ {
+ label: "鐜颁綇鍧�",
prop: "adress",
width:200
},
@@ -125,11 +112,6 @@
label: "涓撲笟",
prop: "profession",
width:100
- },
- {
- label: "韬唤璇佸彿",
- prop: "identityCard",
- width:200
},
{
label: "骞撮緞",
@@ -151,23 +133,10 @@
width:150
},
{
- label: "鍚堝悓骞撮檺",
- prop: "contractTerm",
- },
- {
- label: "鍚堝悓寮�濮嬫棩鏈�",
- prop: "contractStartTime",
- width: 120
- },
- {
- label: "鍚堝悓缁撴潫鏃ユ湡",
- prop: "contractEndTime",
- width: 120
- },
- {
dataType: "action",
label: "鎿嶄綔",
align: "center",
+ fixed: 'right',
operation: [
{
name: "缂栬緫",
@@ -191,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 = () => {
@@ -218,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;
@@ -253,7 +208,7 @@
type: "warning",
})
.then(() => {
- staffJoinDel(ids).then((res) => {
+ batchDeleteStaffLeaves(ids).then((res) => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
getList();
});
@@ -270,7 +225,7 @@
type: "warning",
})
.then(() => {
- proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 0}, "浜哄憳绂昏亴.xlsx");
+ proxy.download("/staff/staffLeave/export", {}, "浜哄憳绂昏亴.xlsx");
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
diff --git a/src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue b/src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue
new file mode 100644
index 0000000..06eee04
--- /dev/null
+++ b/src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue
@@ -0,0 +1,321 @@
+<template>
+ <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="staffNo">
+ <el-input v-model="form.staffNo" placeholder="璇疯緭鍏�" clearable :disabled="operationType !== 'add'"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="濮撳悕锛�" prop="staffName">
+ <el-input v-model="form.staffName" placeholder="璇疯緭鍏�" clearable/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鎬у埆锛�" prop="sex">
+ <el-select v-model="form.sex">
+ <el-option label="鐢�" value="鐢�" />
+ <el-option label="濂�" value="濂�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎴风睄浣忓潃锛�" prop="nativePlace">
+ <el-input v-model="form.nativePlace" placeholder="璇疯緭鍏�" clearable/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <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>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="绗竴瀛﹀巻锛�" prop="firstStudy">
+ <el-input v-model="form.firstStudy" placeholder="璇疯緭鍏�" clearable/>
+ </el-form-item>
+ </el-col>
+ <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="phone">
+ <el-input v-model="form.phone" placeholder="璇疯緭鍏�" clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绱ф�ヨ仈绯讳汉锛�" prop="emergencyContact">
+ <el-input v-model="form.emergencyContact" placeholder="璇疯緭鍏�" clearable/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="绱ф�ヨ仈绯讳汉鑱旂郴鐢佃瘽锛�" prop="emergencyContactPhone">
+ <el-input v-model="form.emergencyContactPhone" placeholder="璇疯緭鍏�" clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <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-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <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-col>
+ <el-col :span="12">
+ <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-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>
+ </div>
+</template>
+
+<script setup>
+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: "",
+ staffName: "",
+ sex: "",
+ nativePlace: "",
+ postJob: "",
+ adress: "",
+ firstStudy: "",
+ profession: "",
+ age: 0,
+ phone: "",
+ emergencyContact: "",
+ emergencyContactPhone: "",
+ contractTerm: 0,
+ contractStartTime: "",
+ contractEndTime: "",
+ staffState: "",
+ sysPostId: undefined,
+ sysDeptId: undefined,
+ },
+ rules: {
+ staffNo: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
+ staffName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ sex: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ nativePlace: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ postJob: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ adress: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ firstStudy: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ profession: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ age: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ phone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ emergencyContact: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ emergencyContactPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ contractTerm: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ contractStartTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ contractEndTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ },
+ postOptions: [], // 宀椾綅閫夐」
+ deptOptions: [], // 閮ㄩ棬閫夐」
+});
+const { form, rules, postOptions, deptOptions } = toRefs(data);
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ if (operationType.value === 'edit') {
+ 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") {
+ createStaffOnJob(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ })
+ } else {
+ updateStaffOnJob(id.value, form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ })
+ }
+ }
+ })
+}
+// 璁$畻鍚堝悓骞撮檺
+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 closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ emit('close')
+};
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/employeeRecord/components/RenewContract.vue b/src/views/personnelManagement/employeeRecord/components/RenewContract.vue
new file mode 100644
index 0000000..9c2acfc
--- /dev/null
+++ b/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>
diff --git a/src/views/personnelManagement/employeeRecord/components/Show.vue b/src/views/personnelManagement/employeeRecord/components/Show.vue
new file mode 100644
index 0000000..9220d45
--- /dev/null
+++ b/src/views/personnelManagement/employeeRecord/components/Show.vue
@@ -0,0 +1,73 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ title="璇︽儏"
+ width="70%"
+ @close="closeDia"
+ >
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :tableLoading="tableLoading"
+ height="600"
+ ></PIMTable>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {staffOnJobInfo} from "@/api/personnelManagement/staffOnJob.js";
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+const tableColumn = ref([
+ // {
+ // label: "鍚堝悓骞撮檺",
+ // prop: "contractTerm",
+ // },
+ {
+ label: "鍚堝悓寮�濮嬫棩鏈�",
+ prop: "contractStartTime",
+ },
+ {
+ label: "鍚堝悓缁撴潫鏃ユ湡",
+ prop: "contractEndTime",
+ },
+]);
+const tableData = ref([]);
+const tableLoading = ref(false);
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ if (operationType.value === 'edit') {
+ staffOnJobInfo({staffNo: row.staffNo}).then(res => {
+ tableData.value = res.data
+ })
+ }
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ dialogFormVisible.value = false;
+ emit('close')
+};
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/employeeRecord/components/formDia.vue b/src/views/personnelManagement/employeeRecord/components/formDia.vue
deleted file mode 100644
index 6538273..0000000
--- a/src/views/personnelManagement/employeeRecord/components/formDia.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<template>
- <div>
- <el-dialog
- v-model="dialogFormVisible"
- title="璇︽儏"
- width="70%"
- @close="closeDia"
- >
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :tableLoading="tableLoading"
- height="600"
- ></PIMTable>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="closeDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
-</template>
-
-<script setup>
-import {ref} from "vue";
-import {staffOnJobInfo} from "@/api/personnelManagement/employeeRecord.js";
-const { proxy } = getCurrentInstance()
-const emit = defineEmits(['close'])
-
-const dialogFormVisible = ref(false);
-const operationType = ref('')
-const tableColumn = ref([
- {
- label: "鍚堝悓骞撮檺",
- prop: "contractTerm",
- },
- {
- label: "鍚堝悓寮�濮嬫棩鏈�",
- prop: "contractStartTime",
- },
- {
- label: "鍚堝悓缁撴潫鏃ユ湡",
- prop: "contractEndTime",
- },
-]);
-const tableData = ref([]);
-const tableLoading = ref(false);
-
-// 鎵撳紑寮规
-const openDialog = (type, row) => {
- operationType.value = type;
- dialogFormVisible.value = true;
- if (operationType.value === 'edit') {
- staffOnJobInfo({staffNo: row.staffNo}).then(res => {
- tableData.value = res.data
- })
- }
-}
-
-// 鍏抽棴寮规
-const closeDia = () => {
- dialogFormVisible.value = false;
- emit('close')
-};
-defineExpose({
- openDialog,
-});
-</script>
-
-<style scoped>
-
-</style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/employeeRecord/index.vue b/src/views/personnelManagement/employeeRecord/index.vue
index 6965e96..19addfa 100644
--- a/src/views/personnelManagement/employeeRecord/index.vue
+++ b/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,30 +37,38 @@
: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: {
staffName: "",
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDate: undefined, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
},
});
const { searchForm } = toRefs(data);
+const isShowRenewContractModal = ref(false);
+const id = ref(0);
const tableColumn = ref([
{
label: "鐘舵��",
@@ -98,15 +106,19 @@
prop: "sex",
},
{
- label: "绫嶈疮",
+ label: "鎴风睄浣忓潃",
prop: "nativePlace",
+ },
+ {
+ label: "閮ㄩ棬",
+ prop: "deptName",
},
{
label: "宀椾綅",
prop: "postJob",
},
{
- label: "瀹跺涵浣忓潃",
+ label: "鐜颁綇鍧�",
prop: "adress",
width:200
},
@@ -118,11 +130,6 @@
label: "涓撲笟",
prop: "profession",
width:100
- },
- {
- label: "韬唤璇佸彿",
- prop: "identityCard",
- width:200
},
{
label: "骞撮緞",
@@ -143,10 +150,10 @@
prop: "emergencyContactPhone",
width:150
},
- {
- label: "鍚堝悓骞撮檺",
- prop: "contractTerm",
- },
+ // {
+ // label: "鍚堝悓骞撮檺",
+ // prop: "contractTerm",
+ // },
// {
// label: "鍚堝悓寮�濮嬫棩鏈�",
// prop: "contractStartTime",
@@ -162,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);
+ // },
+ // },
],
},
]);
@@ -182,6 +206,7 @@
total: 0
});
const formDia = ref()
+const formDiaNewOrEditFormDia = ref()
const { proxy } = getCurrentInstance()
const changeDaterange = (value) => {
@@ -208,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;
@@ -227,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("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
@@ -235,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("宸插彇娑�");
diff --git a/src/views/personnelManagement/onboarding/components/formDia.vue b/src/views/personnelManagement/onboarding/components/formDia.vue
deleted file mode 100644
index 7244ab0..0000000
--- a/src/views/personnelManagement/onboarding/components/formDia.vue
+++ /dev/null
@@ -1,280 +0,0 @@
-<template>
- <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="staffNo">
- <el-input v-model="form.staffNo" placeholder="璇疯緭鍏�" clearable :disabled="operationType !== 'add'"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="濮撳悕锛�" prop="staffName">
- <el-input v-model="form.staffName" placeholder="璇疯緭鍏�" clearable/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鎬у埆锛�" prop="sex">
- <el-select v-model="form.sex">
- <el-option label="鐢�" value="鐢�" />
- <el-option label="濂�" value="濂�" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="绫嶈疮锛�" prop="nativePlace">
- <el-input v-model="form.nativePlace" placeholder="璇疯緭鍏�" clearable/>
- </el-form-item>
- </el-col>
- </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>
- </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="firstStudy">
- <el-input v-model="form.firstStudy" placeholder="璇疯緭鍏�" clearable/>
- </el-form-item>
- </el-col>
- <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="identityCard">
- <el-input v-model="form.identityCard" placeholder="璇疯緭鍏�" clearable/>
- </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>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鑱旂郴鐢佃瘽锛�" prop="phone">
- <el-input v-model="form.phone" placeholder="璇疯緭鍏�" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="绱ф�ヨ仈绯讳汉锛�" prop="emergencyContact">
- <el-input v-model="form.emergencyContact" placeholder="璇疯緭鍏�" clearable/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="绱ф�ヨ仈绯讳汉鑱旂郴鐢佃瘽锛�" prop="emergencyContactPhone">
- <el-input v-model="form.emergencyContactPhone" placeholder="璇疯緭鍏�" clearable/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <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-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <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-col>
- <el-col :span="12">
- <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-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="璇曠敤鏈燂紙鏈堬級锛�" prop="probationPeriod">
- <el-input-number v-model="form.probationPeriod" :precision="0" :step="1" min="0" style="width: 100%"/>
- </el-form-item>
- </el-col>
- <!-- <el-col :span="12">
- <el-form-item label="鍏ヨ亴鏃ユ湡锛�" prop="entryDate">
- <el-date-picker
- v-model="form.entryDate"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- style="width: 100%"
- />
- </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>
- </div>
-</template>
-
-<script setup>
-import {ref, reactive, toRefs} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
-const { proxy } = getCurrentInstance()
-const emit = defineEmits(['close'])
-
-const dialogFormVisible = ref(false);
-const operationType = ref('')
-const data = reactive({
- form: {
- staffNo: "",
- staffName: "",
- sex: "",
- nativePlace: "",
- postJob: "",
- adress: "",
- firstStudy: "",
- profession: "",
- identityCard: "",
- age: 0,
- phone: "",
- emergencyContact: "",
- emergencyContactPhone: "",
- contractTerm: 0,
- contractStartTime: "",
- contractEndTime: "",
- staffState: "",
- probationPeriod: 3, // 榛樿璇曠敤鏈�3涓湀
- },
- rules: {
- staffNo: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
- staffName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- sex: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- nativePlace: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- postJob: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- adress: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- firstStudy: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- profession: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- identityCard: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- age: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- phone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- emergencyContact: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- emergencyContactPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- contractTerm: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- contractStartTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- contractEndTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- },
-});
-const { form, rules } = toRefs(data);
-
-// 鎵撳紑寮规
-const openDialog = (type, row) => {
- operationType.value = type;
- dialogFormVisible.value = true;
- if (operationType.value === 'edit') {
- getStaffJoinInfo(row.id).then(res => {
- form.value = {...res.data}
- // 缂栬緫鏃朵篃璁$畻涓�娆″悎鍚屽勾闄�
- calculateContractTerm();
- })
- }
-}
-// 鎻愪氦浜у搧琛ㄥ崟
-const submitForm = () => {
- proxy.$refs.formRef.validate(valid => {
- if (valid) {
- form.value.staffState = 1
- if (operationType.value === "add") {
- staffJoinAdd(form.value).then(res => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- })
- } else {
- staffJoinUpdate(form.value).then(res => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- })
- }
- }
- })
-}
-// 璁$畻鍚堝悓骞撮檺
-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 closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
- emit('close')
-};
-defineExpose({
- openDialog,
-});
-</script>
-
-<style scoped>
-
-</style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/onboarding/index.vue b/src/views/personnelManagement/onboarding/index.vue
deleted file mode 100644
index d993ee6..0000000
--- a/src/views/personnelManagement/onboarding/index.vue
+++ /dev/null
@@ -1,348 +0,0 @@
-<template>
- <div class="app-container">
- <div class="search_form">
- <div>
- <span class="search_title">濮撳悕锛�</span>
- <el-input
- v-model="searchForm.staffName"
- style="width: 240px"
- placeholder="璇疯緭鍏ュ鍚嶆悳绱�"
- @change="handleQuery"
- clearable
- :prefix-icon="Search"
- />
- <span style="margin-left: 10px;" class="search_title">鍚堝悓寮�濮嬫棩鏈燂細</span>
- <el-date-picker
- v-model="searchForm.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
- >
- </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>
- <form-dia ref="formDia" @close="handleQuery"></form-dia>
- </div>
-</template>
-
-<script setup>
-import { Search } from "@element-plus/icons-vue";
-import {onMounted, ref} from "vue";
-import FormDia from "@/views/personnelManagement/onboarding/components/formDia.vue";
-import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
-import {ElMessageBox} from "element-plus";
-import dayjs from "dayjs";
-
-const data = reactive({
- searchForm: {
- staffName: "",
- },
-});
-const { searchForm } = toRefs(data);
-const tableColumn = ref([
- {
- label: "鐘舵��",
- prop: "staffState",
- dataType: "tag",
- formatData: (params) => {
- if (params == 0) {
- return "绂昏亴";
- } else if (params == 1) {
- return "鍏ヨ亴";
- } else {
- return null;
- }
- },
- formatType: (params) => {
- if (params == 0) {
- return "danger";
- } else if (params == 1) {
- return "primary";
- } else {
- return null;
- }
- },
- },
- {
- label: "鍛樺伐缂栧彿",
- prop: "staffNo",
- },
- {
- label: "濮撳悕",
- prop: "staffName",
- },
- {
- label: "鎬у埆",
- prop: "sex",
- },
- {
- label: "绫嶈疮",
- prop: "nativePlace",
- },
- {
- label: "宀椾綅",
- prop: "postJob",
- },
- {
- label: "瀹跺涵浣忓潃",
- prop: "adress",
- width:200
- },
- {
- label: "绗竴瀛﹀巻",
- prop: "firstStudy",
- },
- {
- label: "涓撲笟",
- prop: "profession",
- width:100
- },
- {
- label: "韬唤璇佸彿",
- prop: "identityCard",
- width:200
- },
- {
- label: "骞撮緞",
- prop: "age",
- },
- {
- label: "鑱旂郴鐢佃瘽",
- prop: "phone",
- width:150
- },
- {
- label: "绱ф�ヨ仈绯讳汉",
- prop: "emergencyContact",
- width: 120
- },
- {
- label: "鑱旂郴鐢佃瘽",
- prop: "emergencyContactPhone",
- width:150
- },
- {
- label: "璇曠敤鏈燂紙鏈堬級",
- prop: "probationPeriod",
- width: 120,
- },
- // {
- // label: "杞鏃ユ湡",
- // prop: "probationEndDate",
- // width: 120,
- // formatData: (row) => {
- // // 淇敼涓轰娇鐢ㄥ悎鍚屽紑濮嬫棩鏈熻绠楄浆姝f棩鏈�
- // if (row.contractStartTime && row.probationPeriod) {
- // // 璁$畻杞鏃ユ湡锛堝悎鍚屽紑濮嬫棩鏈熷姞涓婅瘯鐢ㄦ湡鏈堟暟锛�
- // return dayjs(row.contractStartTime).add(row.probationPeriod, 'month').format('YYYY-MM-DD');
- // }
- // return '';
- // },
- // formatType: (row) => {
- // // 淇敼涓轰娇鐢ㄥ悎鍚屽紑濮嬫棩鏈熸鏌ユ槸鍚︿复杩戣浆姝o紙7澶╁唴锛�
- // if (row.contractStartTime && row.probationPeriod) {
- // const probationEndDate = dayjs(row.contractStartTime).add(row.probationPeriod, 'month');
- // const daysUntilProbationEnd = probationEndDate.diff(dayjs(), 'day');
-
- // if (daysUntilProbationEnd >= 0 && daysUntilProbationEnd <= 7) {
- // return 'warning'; // 浣跨敤璀﹀憡鏍峰紡鏍囪涓磋繎杞鐨勫憳宸�
- // }
- // }
- // return '';
- // }
- // },
- {
- label: "鍚堝悓骞撮檺锛堝勾锛�",
- prop: "contractTerm",
- width: 120,
- },
- {
- label: "鍚堝悓寮�濮嬫棩鏈�",
- prop: "contractStartTime",
- width: 120
- },
- {
- label: "鍚堝悓缁撴潫鏃ユ湡",
- prop: "contractEndTime",
- width: 120
- },
- {
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
- fixed: 'right',
- operation: [
- {
- name: "缂栬緫",
- type: "text",
- clickFun: (row) => {
- openForm("edit", row);
- },
- },
- ],
- },
-]);
-const tableData = ref([]);
-const selectedRows = ref([]);
-const tableLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
- total: 0,
-});
-const formDia = ref()
-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 = () => {
- page.current = 1;
- getList();
-};
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-const getList = () => {
- tableLoading.value = true;
- staffJoinListPage({...page, ...searchForm.value, staffState: 1}).then(res => {
- tableLoading.value = false;
- tableData.value = res.data.records
- page.total = res.data.total;
-
- // 妫�鏌ユ槸鍚︽湁涓磋繎杞鐨勫憳宸ュ苟鎻愰啋
- checkProbationEnding(tableData.value);
- }).catch(err => {
- tableLoading.value = false;
- })
-};
-// 妫�鏌ヤ复杩戣浆姝g殑鍛樺伐骞舵彁閱�
-const checkProbationEnding = (data) => {
- const probationEndingSoon = [];
-
- data.forEach(item => {
- // 淇敼涓轰娇鐢ㄥ悎鍚屽紑濮嬫棩鏈熸鏌�
- if (item.contractStartTime && item.probationPeriod) {
- const probationEndDate = dayjs(item.contractStartTime).add(item.probationPeriod, 'month');
- const daysUntilProbationEnd = probationEndDate.diff(dayjs(), 'day');
-
- if (daysUntilProbationEnd >= 0 && daysUntilProbationEnd <= 7) {
- probationEndingSoon.push({
- staffName: item.staffName,
- probationEndDate: probationEndDate.format('YYYY-MM-DD'),
- daysLeft: daysUntilProbationEnd
- });
- }
- }
- });
-
- if (probationEndingSoon.length > 0) {
- let message = '浠ヤ笅鍛樺伐灏嗗湪7澶╁唴杞锛歕n';
- probationEndingSoon.forEach(item => {
- message += `${item.staffName}锛�${item.probationEndDate}锛岃繕鏈�${item.daysLeft}澶╋級\n`;
- });
-
- // 鏄剧ず鎻愰啋娑堟伅
- proxy.$modal.msgInfo(message);
- }
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-
-// 鎵撳紑寮规
-const openForm = (type, row) => {
- nextTick(() => {
- formDia.value?.openDialog(type, row)
- })
-};
-
-// 鍒犻櫎
-const handleDelete = () => {
- let ids = [];
- if (selectedRows.value.length > 0) {
- ids = selectedRows.value.map((item) => item.id);
- } else {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- staffJoinDel(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "浜哄憳鍏ヨ亴.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-onMounted(() => {
- getList();
-});
-</script>
-
-<style scoped></style>
diff --git a/src/views/personnelManagement/payrollManagement/components/formDia.vue b/src/views/personnelManagement/payrollManagement/components/formDia.vue
index e4cf0b3..cf93559 100644
--- a/src/views/personnelManagement/payrollManagement/components/formDia.vue
+++ b/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'
})
diff --git a/src/views/personnelManagement/payrollManagement/index.vue b/src/views/personnelManagement/payrollManagement/index.vue
index 24e3dd8..f17f42e 100644
--- a/src/views/personnelManagement/payrollManagement/index.vue
+++ b/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";
diff --git a/src/views/personnelManagement/scheduling/index.vue b/src/views/personnelManagement/scheduling/index.vue
new file mode 100644
index 0000000..19d2062
--- /dev/null
+++ b/src/views/personnelManagement/scheduling/index.vue
@@ -0,0 +1,622 @@
+<template>
+ <div class="app-container scheduling-container">
+ <!-- 绛涢�夊尯鍩� -->
+ <div class="filter-section">
+ <el-form :inline="true" :model="filterForm" class="filter-form">
+ <el-form-item label="鍛樺伐濮撳悕锛�">
+ <el-input
+ v-model="filterForm.staffName"
+ placeholder="璇疯緭鍏ュ憳宸ュ鍚�"
+ clearable
+ style="width: 150px"
+ />
+ </el-form-item>
+ <el-form-item label="鐝绫诲瀷锛�">
+ <el-select v-model="filterForm.shiftType" placeholder="璇烽�夋嫨鐝" clearable style="width: 120px">
+ <el-option v-for="item in shift_type" :label="item.label" :value="item.value" :key="item.value"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏃ユ湡鑼冨洿锛�">
+ <el-date-picker
+ v-model="filterForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 250px"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleFilter">
+ <el-icon><Search/></el-icon>
+ 绛涢��
+ </el-button>
+ <el-button @click="resetFilter">
+ <el-icon><Refresh/></el-icon>
+ 閲嶇疆
+ </el-button>
+ <el-button @click="handleExport">
+ <el-icon><Download/></el-icon>
+ 瀵煎嚭
+ </el-button>
+ <el-button type="primary" @click="openScheduleDialog('add')">
+ <el-icon><Plus/></el-icon>
+ 鏂板鎺掔彮
+ </el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <!-- 鎺掔彮琛ㄦ牸 -->
+ <div class="table-section">
+ <el-table
+ :data="scheduleList"
+ border
+ :loading="tableLoading"
+ stripe
+ style="width: 100%"
+ height="calc(100vh - 18.5em)"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="55"/>
+ <el-table-column prop="staffName" label="鍛樺伐濮撳悕" width="120"/>
+ <el-table-column prop="staffNo" label="鍛樺伐宸ュ彿" width="100"/>
+ <el-table-column prop="department" label="閮ㄩ棬" width="120">
+ <template #default="scope">
+ {{ (department_type.find(i => i.value === String(scope.row.department)) || {}).label }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="shiftType" label="鐝绫诲瀷" width="100">
+ <template #default="scope">
+ <el-tag :type="getShiftTagType(scope.row.shiftType)">
+ {{ (shift_type.find(i => i.value === String(scope.row.shiftType)) || {}).label }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="workDate" label="宸ヤ綔鏃ユ湡" width="120"/>
+ <el-table-column prop="startTime" label="寮�濮嬫椂闂�" width="100"/>
+ <el-table-column prop="endTime" label="缁撴潫鏃堕棿" width="100"/>
+ <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" width="100">
+ <template #default="scope">
+ {{ scope.row.workHours }}灏忔椂
+ </template>
+ </el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusTagType(scope.row.status)">
+ {{ (schedule_status.find(i => i.value === String(scope.row.status)) || {}).label }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" min-width="150"/>
+ <el-table-column label="鎿嶄綔" width="200" fixed="right">
+ <template #default="scope">
+ <el-button
+ type="primary"
+ size="small"
+ @click="openScheduleDialog('edit', scope.row)"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ type="danger"
+ size="small"
+ @click="handleDelete(scope.row)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-if="tableCount > 0"
+ :total="tableCount"
+ :page="filterForm.current"
+ :limit="filterForm.size"
+ @pagination="paginationChange"
+ />
+ </div>
+
+ <!-- 鎵归噺鎿嶄綔 -->
+ <div class="batch-actions" v-if="selectedRows.length > 0">
+ <el-button
+ type="danger"
+ @click="handleBatchDelete"
+ :disabled="selectedRows.length === 0"
+ >
+ 鎵归噺鍒犻櫎 ({{ selectedRows.length }})
+ </el-button>
+ </div>
+
+ <!-- 鎺掔彮鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog
+ v-model="scheduleDialog"
+ :title="dialogType === 'add' ? '鏂板鎺掔彮' : '缂栬緫鎺掔彮'"
+ width="700px"
+ @close="closeScheduleDialog"
+ >
+ <el-form
+ :model="scheduleForm"
+ :rules="scheduleRules"
+ ref="scheduleFormRef"
+ label-width="120px"
+ >
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍛樺伐濮撳悕锛�" prop="staffId">
+ <el-select v-model="scheduleForm.staffId" placeholder="璇疯緭鍏ュ憳宸ュ鍚�" style="width: 100%"
+ @change="handleSelectStaff">
+ <el-option v-for="item in personList" :label="item.staffName" :value="item.id" :key="item.id"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍛樺伐宸ュ彿锛�" prop="staffNo">
+ <el-input :disabled="true" v-model="scheduleForm.staffNo" placeholder=""/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閮ㄩ棬锛�" prop="department">
+ <el-select v-model="scheduleForm.department" placeholder="璇烽�夋嫨閮ㄩ棬" style="width: 100%">
+ <el-option v-for="item in department_type" :label="item.label" :value="item.value" :key="item.value"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐝绫诲瀷锛�" prop="shiftType">
+ <el-select v-model="scheduleForm.shiftType" placeholder="璇烽�夋嫨鐝" style="width: 100%">
+ <el-option v-for="item in shift_type" :label="item.label" :value="item.value" :key="item.value"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="宸ヤ綔鏃ユ湡锛�" prop="workDate">
+ <el-date-picker
+ v-model="scheduleForm.workDate"
+ type="date"
+ placeholder="閫夋嫨宸ヤ綔鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐘舵�侊細" prop="status">
+ <el-select v-model="scheduleForm.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option v-for="item in schedule_status" :label="item.label" :value="item.value" :key="item.value"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="寮�濮嬫椂闂达細" prop="startTime">
+ <el-time-picker
+ v-model="scheduleForm.startTime"
+ placeholder="閫夋嫨寮�濮嬫椂闂�"
+ style="width: 100%"
+ format="HH:mm"
+ value-format="HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁撴潫鏃堕棿锛�" prop="endTime">
+ <el-time-picker
+ v-model="scheduleForm.endTime"
+ placeholder="閫夋嫨缁撴潫鏃堕棿"
+ style="width: 100%"
+ format="HH:mm"
+ value-format="HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞锛�" prop="remark">
+ <el-input
+ v-model="scheduleForm.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitScheduleForm">纭</el-button>
+ <el-button @click="closeScheduleDialog">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, reactive, computed, onMounted, getCurrentInstance} from 'vue'
+import {ElMessage, ElMessageBox} from 'element-plus'
+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 dayjs from "dayjs";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
+
+const { proxy } = getCurrentInstance();
+
+const tableCount = ref(0)
+// 鍝嶅簲寮忔暟鎹�
+const scheduleDialog = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+const scheduleFormRef = ref()
+
+// 绛涢�夎〃鍗�
+const filterForm = reactive({
+ staffName: '',
+ shiftType: '',
+ dateRange: [],
+ current:1,
+ size: 10
+})
+
+// 鎺掔彮琛ㄥ崟
+const scheduleForm = reactive({
+ id: '',
+ staffId: '',
+ staffNo: '',
+ department: '',
+ shiftType: '',
+ workDate: '',
+ startTime: '',
+ endTime: '',
+ workStartTime: '',
+ workEndTime: '',
+ workHours: 0,
+ status: '',
+ remark: ''
+})
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const scheduleRules = reactive({
+ staffId: [{required: true, message: '璇烽�夋嫨鍛樺伐', trigger: 'change'}],
+ department: [{required: true, message: '璇烽�夋嫨閮ㄩ棬', trigger: 'change'}],
+ shiftType: [{required: true, message: '璇烽�夋嫨鐝绫诲瀷', trigger: 'change'}],
+ workDate: [{required: true, message: '璇烽�夋嫨宸ヤ綔鏃ユ湡', trigger: 'change'}],
+ startTime: [{required: true, message: '璇烽�夋嫨寮�濮嬫椂闂�', trigger: 'change'}],
+ endTime: [{required: true, message: '璇烽�夋嫨缁撴潫鏃堕棿', trigger: 'change'}],
+ status: [{required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change'}]
+})
+const tableLoading = ref(false)
+
+//瀛楀吀
+const {department_type, schedule_status, shift_type} = useDict("department_type", "schedule_status", "shift_type")
+// 浜哄憳鍒楄〃
+const personList = ref([]);
+
+// 妯℃嫙鎺掔彮鏁版嵁
+const scheduleList = ref([])
+
+
+/**
+ * 鑾峰彇褰撳墠鍦ㄨ亴浜哄憳鍒楄〃
+ */
+const getPersonList = () => {
+ staffOnJobListPage({
+ current: -1,
+ size: -1,
+ staffState: 1
+ }).then(res => {
+ personList.value = res.data.records || []
+ })
+};
+const paginationChange = (obj) => {
+ filterForm.current = obj.page;
+ filterForm.size = obj.limit;
+ handleFilter();
+};
+
+const handleSelectStaff = (val) => {
+ let obj = personList.value.find(item => item.id === val)
+ scheduleForm.staffNo = obj.staffNo
+
+}
+
+// 鑾峰彇鐝鏍囩绫诲瀷
+const getShiftTagType = (shiftType) => {
+ const typeMap = Object.fromEntries(shift_type.value.map(i => [i.value, i.elTagType]))
+ return typeMap[shiftType] || 'info'
+}
+
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusTagType = (status) => {
+ const typeMap = Object.fromEntries(schedule_status.value.map(i => [i.value, i.elTagType]))
+ return typeMap[status] || 'info'
+}
+
+// 绛涢��
+const handleFilter = async () => {
+ tableLoading.value = true
+ let searchForm = {
+ ...filterForm,
+ ...(filterForm.dateRange.length > 0 && {
+ startDate: filterForm.dateRange[0],
+ endDate: filterForm.dateRange[1],
+ })
+ }
+ let resp = await listPage(searchForm)
+ tableCount.value = resp.data.total
+ scheduleList.value = resp.data.records.map(it => {
+ return {
+ ...it,
+ 'startTime': dayjs(it.workStartTime).format('HH:mm'),
+ 'endTime': dayjs(it.workEndTime).format('HH:mm'),
+ }
+ })
+ tableLoading.value = false
+
+}
+
+// 閲嶇疆绛涢��
+const resetFilter = () => {
+ filterForm.staffName = ''
+ filterForm.shiftType = ''
+ filterForm.dateRange = []
+}
+
+// 鎵撳紑鎺掔彮瀵硅瘽妗�
+const openScheduleDialog = (type, data) => {
+ dialogType.value = type
+ scheduleDialog.value = true
+ getPersonList()
+ if (type === 'edit' && data) {
+ // 缂栬緫妯″紡锛屽鍒舵暟鎹�
+ Object.assign(scheduleForm, {...data})
+ } else {
+ // 鏂板妯″紡锛岄噸缃〃鍗�
+ Object.keys(scheduleForm).forEach(key => {
+ scheduleForm[key] = ''
+ })
+ // scheduleForm.status = '宸插畨鎺�'
+ scheduleForm.workDate = new Date().toISOString().split('T')[0]
+ }
+}
+
+// 鍏抽棴鎺掔彮瀵硅瘽妗�
+const closeScheduleDialog = () => {
+ scheduleFormRef.value?.resetFields()
+ scheduleDialog.value = false
+}
+
+// 璁$畻宸ヤ綔鏃堕暱
+const calculateWorkHours = () => {
+ if (scheduleForm.workDate && scheduleForm.startTime && scheduleForm.endTime) {
+ // 浣跨敤 workDate 涓� startTime 鍜� endTime 缁勫悎
+ const startDateTime = new Date(`${scheduleForm.workDate} ${scheduleForm.startTime}`)
+ const endDateTime = new Date(`${scheduleForm.workDate} ${scheduleForm.endTime}`)
+
+ // 澶勭悊璺ㄥぉ鎯呭喌锛堢粨鏉熸椂闂存棭浜庡紑濮嬫椂闂达級
+ if (endDateTime < startDateTime) {
+ // 璺ㄥぉ锛屽皢缁撴潫鏃ユ湡鍔犱竴澶�
+ endDateTime.setDate(endDateTime.getDate() + 1)
+ }
+ // 璁$畻宸ヤ綔鏃堕暱锛堝皬鏃讹級
+ const diffMs = endDateTime - startDateTime
+ const diffHours = diffMs / (1000 * 60 * 60)
+ scheduleForm.workHours = Math.round(diffHours * 100) / 100
+ scheduleForm.workStartTime = dayjs(startDateTime).format("YYYY-MM-DD HH:mm:ss")
+ scheduleForm.workEndTime = dayjs(endDateTime).format("YYYY-MM-DD HH:mm:ss")
+
+ }
+}
+
+// 鎻愪氦鎺掔彮琛ㄥ崟
+const submitScheduleForm = async () => {
+ const valid = await scheduleFormRef.value.validate()
+ if (!valid) return
+
+ calculateWorkHours()
+ const newSchedule = {...scheduleForm}
+
+ try {
+ await save(newSchedule)
+ ElMessage.success('淇濆瓨鎺掔彮鎴愬姛')
+
+ handleFilter()
+ closeScheduleDialog()
+ } catch (err) {
+ ElMessage.error('淇濆瓨澶辫触')
+ }
+}
+
+// 鍒犻櫎鎺掔彮
+const handleDelete = (row) => {
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄� ${row.staffName} 鐨勬帓鐝褰曞悧锛焋,
+ '鍒犻櫎鎻愮ず',
+ {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ ).then(() => {
+ del(row.id)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ handleFilter()
+ }).catch(() => {
+ ElMessage.info('宸插彇娑堝垹闄�')
+ })
+}
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning('璇烽�夋嫨瑕佸垹闄ょ殑璁板綍')
+ return
+ }
+
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉℃帓鐝褰曞悧锛焋,
+ '鎵归噺鍒犻櫎鎻愮ず',
+ {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ ).then(() => {
+ delByIds(selectedRows.value.map(item => item.id))
+ handleFilter()
+ ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+ }).catch(() => {
+ ElMessage.info('宸插彇娑堝垹闄�')
+ })
+}
+
+// 閫夋嫨鍙樺寲浜嬩欢
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection
+}
+
+// 瀵煎嚭
+const handleExport = () => {
+ let searchForm = {
+ ...filterForm,
+ ...(filterForm.dateRange.length > 0 && {
+ startDate: filterForm.dateRange[0],
+ endDate: filterForm.dateRange[1],
+ })
+ }
+ proxy.download('/staff/staffScheduling/export', {}, '浜哄憳鎺掔彮.xlsx')
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ // 椤甸潰鍒濆鍖�
+ handleFilter()
+})
+</script>
+
+<style scoped>
+.scheduling-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: 30px;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 12px;
+ color: white;
+}
+
+.page-header h2 {
+ color: white;
+ margin-bottom: 10px;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.page-header p {
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 14px;
+ margin: 0 0 15px 0;
+}
+
+.header-controls {
+ display: flex;
+ justify-content: center;
+ gap: 15px;
+}
+
+.filter-section {
+ background: white;
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.filter-form {
+ margin: 0;
+}
+
+.table-section {
+ background: white;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ margin-bottom: 20px;
+}
+
+.batch-actions {
+ background: white;
+ padding: 15px 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+:deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #303133;
+}
+
+:deep(.el-input__wrapper) {
+ box-shadow: 0 0 0 1px #dcdfe6 inset;
+}
+
+:deep(.el-input__wrapper:hover) {
+ box-shadow: 0 0 0 1px #c0c4cc inset;
+}
+
+:deep(.el-input__wrapper.is-focus) {
+ box-shadow: 0 0 0 1px #409eff inset;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .scheduling-container {
+ padding: 10px;
+ }
+
+ .page-header {
+ padding: 15px;
+ }
+
+ .page-header h2 {
+ font-size: 24px;
+ }
+
+ .header-controls {
+ flex-direction: column;
+ gap: 10px;
+ }
+}
+
+@media (max-width: 768px) {
+ .filter-form .el-form-item {
+ margin-bottom: 10px;
+ }
+}
+</style>
diff --git a/src/views/personnelManagement/selfService/index.vue b/src/views/personnelManagement/selfService/index.vue
new file mode 100644
index 0000000..647f149
--- /dev/null
+++ b/src/views/personnelManagement/selfService/index.vue
@@ -0,0 +1,800 @@
+<template>
+ <div class="app-container self-service-container">
+
+ <!-- 鍔熻兘瀵艰埅鍗$墖 -->
+ <el-row :gutter="20" class="nav-cards">
+ <el-col :span="6" v-for="(item, index) in navItems" :key="index">
+ <el-card class="nav-card" @click="handleNavClick(item.type)">
+ <div class="nav-content">
+ <el-icon :size="40" class="nav-icon">
+ <component :is="item.icon" />
+ </el-icon>
+ <h3>{{ item.title }}</h3>
+ <p>{{ item.desc }}</p>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 涓昏鍐呭鍖哄煙 -->
+ <div class="main-content">
+ <!-- 鑰冨嫟璁板綍 -->
+ <el-card v-if="currentView === 'attendance'" class="content-card">
+ <template #header>
+ <div class="card-header">
+ <span>涓汉鑰冨嫟璁板綍</span>
+ <el-button type="primary" @click="addAttendanceRecord">鏂板璁板綍</el-button>
+ </div>
+ </template>
+ <el-table :data="attendanceData" style="width: 100%" :loading="tableLoading">
+ <el-table-column prop="date" label="鏃ユ湡" />
+ <el-table-column prop="checkIn" label="绛惧埌鏃堕棿" />
+ <el-table-column prop="checkOut" label="绛鹃��鏃堕棿" />
+ <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" width="100" />
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="scope.row.status === '姝e父' ? 'success' : 'danger'">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150">
+ <template #default="scope">
+ <el-button size="small" @click="editAttendanceRecord(scope.row)">缂栬緫</el-button>
+ <el-button size="small" type="danger" @click="deleteAttendanceRecord(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 钖祫鍗� -->
+ <el-card v-if="currentView === 'salary'" class="content-card">
+ <template #header>
+ <div class="card-header">
+ <span>钖祫鍗曟煡璇�</span>
+ <el-date-picker v-model="payDateStr" type="month" placeholder="閫夋嫨鏈堜唤" value-format="YYYY-MM" format="YYYY-MM" @change="changMonth"/>
+ </div>
+ </template>
+ <el-table :data="salaryData" style="width: 100%">
+ <el-table-column prop="payDate" label="鏈堜唤" />
+ <el-table-column prop="basicSalary" label="鍩烘湰宸ヨ祫" />
+ <el-table-column prop="bonus" label="濂栭噾" />
+ <el-table-column prop="deduction" label="鎵f" />
+ <el-table-column prop="actualWages" label="瀹炲彂宸ヨ祫" />
+ <el-table-column prop="status" label="鐘舵��" >
+ <template #default="scope">
+ <el-tag :type="scope.row.status === '宸插彂鏀�' ? 'success' : 'warning'">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 鍋囨湡鐢宠 -->
+ <el-card v-if="currentView === 'leave'" class="content-card">
+ <template #header>
+ <div class="card-header">
+ <span>鍋囨湡鐢宠绠$悊</span>
+ <el-button type="primary" @click="openLeaveForm">鐢宠鍋囨湡</el-button>
+ </div>
+ </template>
+ <el-table :data="leaveData" style="width: 100%">
+ <el-table-column prop="type" label="鍋囨湡绫诲瀷" />
+ <el-table-column prop="startDate" label="寮�濮嬫棩鏈�" />
+ <el-table-column prop="endDate" label="缁撴潫鏃ユ湡" />
+ <el-table-column prop="days" label="澶╂暟" width="80" />
+ <el-table-column prop="reason" label="鐢宠鍘熷洜" />
+ <el-table-column prop="status" label="瀹℃壒鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150">
+ <template #default="scope">
+ <el-button size="small" @click="editLeaveRecord(scope.row)">缂栬緫</el-button>
+ <el-button size="small" type="danger" @click="deleteLeaveRecord(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 涓汉淇℃伅 -->
+ <el-card v-if="currentView === 'profile'" class="content-card">
+ <template #header>
+ <div class="card-header">
+ <span>涓汉淇℃伅缁存姢</span>
+ <el-button type="primary" @click="editProfileForm">缂栬緫淇℃伅</el-button>
+ </div>
+ </template>
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="濮撳悕">{{ profile.name }}</el-descriptions-item>
+ <el-descriptions-item label="宸ュ彿">{{ profile.employeeId }}</el-descriptions-item>
+ <el-descriptions-item label="閮ㄩ棬">{{ profile.department }}</el-descriptions-item>
+ <el-descriptions-item label="鑱屼綅">{{ profile.position }}</el-descriptions-item>
+ <el-descriptions-item label="鍏ヨ亴鏃ユ湡">{{ profile.hireDate }}</el-descriptions-item>
+ <el-descriptions-item label="鑱旂郴鐢佃瘽">{{ profile.phone }}</el-descriptions-item>
+ <el-descriptions-item label="閭">{{ profile.email }}</el-descriptions-item>
+ <el-descriptions-item label="鍦板潃">{{ profile.adress }}</el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+ </div>
+
+ <!-- 鍋囨湡鐢宠寮圭獥 -->
+ <el-dialog v-model="showLeaveDialog" :title="leaveOperationType === 'add' ? '鐢宠鍋囨湡' : '缂栬緫鍋囨湡'" width="500px">
+ <el-form :model="leaveForm" label-width="100px">
+ <el-form-item label="鍋囨湡绫诲瀷">
+ <el-select v-model="leaveForm.type" placeholder="璇烽�夋嫨鍋囨湡绫诲瀷">
+ <el-option label="骞村亣" value="骞村亣" />
+ <el-option label="鐥呭亣" value="鐥呭亣" />
+ <el-option label="璋冧紤" value="璋冧紤" />
+ <el-option label="浜嬪亣" value="浜嬪亣" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="寮�濮嬫棩鏈�">
+ <el-date-picker v-model="leaveForm.startDate" type="date" placeholder="閫夋嫨寮�濮嬫棩鏈�" />
+ </el-form-item>
+ <el-form-item label="缁撴潫鏃ユ湡">
+ <el-date-picker v-model="leaveForm.endDate" type="date" placeholder="閫夋嫨缁撴潫鏃ユ湡" />
+ </el-form-item>
+ <el-form-item label="鐢宠鍘熷洜">
+ <el-input v-model="leaveForm.reason" type="textarea" rows="3" />
+ </el-form-item>
+ <!-- <el-form-item label="瀹℃壒鐘舵��">
+ <el-select v-model="leaveForm.status" placeholder="璇烽�夋嫨瀹℃壒鐘舵��">
+ <el-option label="瀹℃壒涓�" value="瀹℃壒涓�" />
+ <el-option label="宸查�氳繃" value="宸查�氳繃" />
+ <el-option label="宸叉嫆缁�" value="宸叉嫆缁�" />
+ </el-select>
+ </el-form-item> -->
+ </el-form>
+ <template #footer>
+ <el-button @click="showLeaveDialog = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitLeaveApplication">鎻愪氦鐢宠</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 鏂板-缂栬緫鑰冨嫟璁板綍寮圭獥 -->
+ <el-dialog v-model="showAttendanceDialog" :title="operationType === 'add' ? '鏂板鑰冨嫟璁板綍' : '缂栬緫鑰冨嫟璁板綍'" width="500px">
+ <el-form :model="attendanceForm" :rules="attendanceRules" ref="attendanceFormRef" label-width="100px">
+ <el-form-item label="鏃ユ湡" prop="date">
+ <el-date-picker v-model="attendanceForm.date" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="閫夋嫨鏃ユ湡" />
+ </el-form-item>
+ <el-form-item label="绛惧埌鏃堕棿" prop="checkIn">
+ <el-time-picker v-model="attendanceForm.checkIn" placeholder="閫夋嫨绛惧埌鏃堕棿" format="HH:mm" value-format="HH:mm" />
+ </el-form-item>
+ <el-form-item label="绛鹃��鏃堕棿" prop="checkOut">
+ <el-time-picker v-model="attendanceForm.checkOut" placeholder="閫夋嫨绛鹃��鏃堕棿" format="HH:mm" value-format="HH:mm" />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="attendanceForm.status" placeholder="璇烽�夋嫨鐘舵��">
+ <el-option label="姝e父" value="姝e父" />
+ <el-option label="杩熷埌" value="杩熷埌" />
+ <el-option label="鏃╅��" value="鏃╅��" />
+ <el-option label="缂哄嫟" value="缂哄嫟" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="showAttendanceDialog = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitAttendance">鎻愪氦</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 涓汉淇℃伅缂栬緫寮圭獥 -->
+ <el-dialog v-model="editProfile" title="缂栬緫涓汉淇℃伅" width="500px">
+ <el-form :model="profileForm" label-width="100px">
+ <el-form-item label="濮撳悕">
+ <el-input v-model="profileForm.name" />
+ </el-form-item>
+ <el-form-item label="鑱旂郴鐢佃瘽">
+ <el-input v-model="profileForm.phone" />
+ </el-form-item>
+ <el-form-item label="閭">
+ <el-input v-model="profileForm.email" />
+ </el-form-item>
+ <el-form-item label="鍦板潃">
+ <el-input v-model="profileForm.adress" type="textarea" rows="2" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="editProfile = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="saveProfile">淇濆瓨</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+ Calendar,
+ Money,
+ Clock,
+ User
+} from '@element-plus/icons-vue'
+import { personalAttendanceRecordsListPage, personalAttendanceRecordsAdd, personalAttendanceRecordsUpdate, personalAttendanceRecordsDelete, holidayApplicationListPage, holidayApplicationAdd, holidayApplicationUpdate, holidayApplicationDelete } from '@/api/personnelManagement/selfService'
+import { compensationListPage, compensationAdd, compensationUpdate, compensationDelete } from '@/api/personnelManagement/payrollManagement'
+
+const { proxy } = getCurrentInstance()
+import { getUserProfile } from '@/api/system/user.js'
+import {staffOnJobListPage, updateStaffOnJob} from "@/api/personnelManagement/staffOnJob.js";
+
+const tableLoading = ref(false)
+// 鍒嗛〉鍙傛暟
+const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0
+})
+
+// 褰撳墠瑙嗗浘
+const currentView = ref('attendance')
+
+// 瀵艰埅椤�
+const navItems = [
+ { type: 'attendance', title: '鑰冨嫟璁板綍', desc: '鏌ヨ涓汉鑰冨嫟淇℃伅', icon: 'Calendar' },
+ { type: 'salary', title: '钖祫鍗�', desc: '鏌ョ湅钖祫鍙戞斁璁板綍', icon: 'Money' },
+ { type: 'leave', title: '鍋囨湡鐢宠', desc: '鍦ㄧ嚎鐢宠鍚勭被鍋囨湡', icon: 'Clock' },
+ { type: 'profile', title: '涓汉淇℃伅', desc: '缁存姢涓汉鍩烘湰淇℃伅', icon: 'User' }
+]
+
+// 鑰冨嫟鏁版嵁
+const attendanceData = ref([])
+
+// 钖祫鏁版嵁
+const salaryData = ref([])
+
+
+// 鍋囨湡鏁版嵁
+const leaveData = ref([])
+
+const currentUser = ref()
+const user= ref()
+// 涓汉淇℃伅
+const profile = ref({
+ id: '',
+ name: '',
+ employeeId: '',
+ department: '',
+ position: '',
+ hireDate: '',
+ phone: '',
+ email: '',
+ adress: ''
+ })
+
+// 寮圭獥鎺у埗
+const showLeaveDialog = ref(false)
+const editProfile = ref(false)
+const payDateStr = ref('')
+
+// 琛ㄥ崟鏁版嵁
+const leaveForm = reactive({
+ id: '',
+ type: '',
+ startDate: '',
+ endDate: '',
+ days: 0,
+ reason: '',
+ status: ''
+})
+const profileForm = reactive({
+ name: "",
+ email: "",
+ adress: "",
+ phone: "",
+})
+const joinForm = reactive({
+ id: "",
+ staffNo: "",
+ staffName: "",
+ email: "",
+ adress: "",
+ sex: "",
+ nativePlace: "",
+ postJob: "",
+ firstStudy: "",
+ profession: "",
+ age: 0,
+ phone: "",
+ emergencyContact: "",
+ emergencyContactPhone: "",
+ contractTerm: 0,
+ contractStartTime: "",
+ contractEndTime: "",
+ staffState: 1,
+})
+
+// 鏂板鑰冨嫟璁板綍锛氬脊绐椾笌琛ㄥ崟
+const operationType = ref('add')
+const leaveOperationType = ref('add')
+const showAttendanceDialog = ref(false)
+const attendanceFormRef = ref(null)
+const attendanceForm = reactive({
+ id: '',
+ date: '',
+ checkIn: '',
+ checkOut: '',
+ workHours: '',
+ status: '姝e父'
+})
+const attendanceRules = {
+ date: [{ required: true, message: '璇烽�夋嫨鏃ユ湡', trigger: 'change' }],
+ checkIn: [{ required: true, message: '璇烽�夋嫨绛惧埌鏃堕棿', trigger: 'change' }],
+ checkOut: [{ required: true, message: '璇烽�夋嫨绛鹃��鏃堕棿', trigger: 'change' }],
+ status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+}
+
+// 澶勭悊瀵艰埅鐐瑰嚮
+const handleNavClick = (type) => {
+ currentView.value = type
+}
+
+// 鑾峰彇鐘舵�佺被鍨�
+const getStatusType = (status) => {
+ const types = {
+ '宸查�氳繃': 'success',
+ '瀹℃壒涓�': 'warning',
+ '宸叉嫆缁�': 'danger'
+ }
+ return types[status] || 'info'
+}
+
+// 鏂板鑰冨嫟璁板綍锛堟墦寮�寮圭獥骞堕濉粯璁ゅ�硷級
+const addAttendanceRecord = () => {
+ operationType.value = 'add'
+ attendanceForm.date = new Date().toISOString().split('T')[0]
+ attendanceForm.checkIn = '09:00'
+ attendanceForm.checkOut = '18:00'
+ attendanceForm.status = '姝e父'
+ showAttendanceDialog.value = true
+}
+
+// 璁$畻宸ユ椂
+const computeWorkHours = (inStr, outStr) => {
+ const [inH, inM] = inStr.split(':').map(n => parseInt(n, 10))
+ const [outH, outM] = outStr.split(':').map(n => parseInt(n, 10))
+ const inMin = inH * 60 + inM
+ const outMin = outH * 60 + outM
+ const diff = Math.max(0, outMin - inMin)
+ const h = Math.floor(diff / 60)
+ const m = diff % 60
+ return m === 0 ? `${h}灏忔椂` : `${h}灏忔椂${m}鍒哷
+}
+
+// 缂栬緫鑰冨嫟璁板綍
+const editAttendanceRecord = (row) => {
+ operationType.value = 'edit'
+ Object.assign(attendanceForm, row)
+ showAttendanceDialog.value = true
+}
+// 鎻愪氦鏂板-缂栬緫鑰冨嫟璁板綍
+const submitAttendance = () => {
+ // if (!attendanceFormRef.value) return
+ const workHours = computeWorkHours(attendanceForm.checkIn, attendanceForm.checkOut)
+ const newRecord = {
+ date: attendanceForm.date,
+ checkIn: attendanceForm.checkIn,
+ checkOut: attendanceForm.checkOut,
+ workHours,
+ status: attendanceForm.status
+ }
+ if (operationType.value === 'add') {
+ personalAttendanceRecordsAdd(newRecord)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success('鑰冨嫟璁板綍娣诲姞鎴愬姛')
+ getPersonalAttendanceRecordsList()
+ showAttendanceDialog.value = false
+ // 閲嶇疆琛ㄥ崟
+ attendanceForm.date = ''
+ attendanceForm.checkIn = ''
+ attendanceForm.checkOut = ''
+ attendanceForm.status = '姝e父'
+ }
+ }).catch(err => {
+ ElMessage.error('鑰冨嫟璁板綍娣诲姞澶辫触')
+ })
+ }else{
+ attendanceForm.workHours = computeWorkHours(attendanceForm.checkIn, attendanceForm.checkOut)
+ personalAttendanceRecordsUpdate(attendanceForm)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success('鑰冨嫟璁板綍鏇存柊鎴愬姛')
+ getPersonalAttendanceRecordsList()
+ showAttendanceDialog.value = false
+ // 閲嶇疆琛ㄥ崟
+ attendanceForm.date = ''
+ attendanceForm.checkIn = ''
+ attendanceForm.checkOut = ''
+ attendanceForm.status = '姝e父'
+ }
+ }).catch(err => {
+ ElMessage.error('鑰冨嫟璁板綍鏇存柊澶辫触')
+ })
+ }
+ // attendanceFormRef.value.validate((valid) => {
+ // if (!valid) return
+
+
+ // })
+}
+// 鍒犻櫎鑰冨嫟璁板綍
+const deleteAttendanceRecord = (row) => {
+
+ ElMessageBox.confirm('纭畾鍒犻櫎璇ヨ�冨嫟璁板綍鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ personalAttendanceRecordsDelete(row.id)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success('鑰冨嫟璁板綍鍒犻櫎鎴愬姛')
+ getPersonalAttendanceRecordsList()
+ }
+ }).catch(err => {
+ ElMessage.error('鑰冨嫟璁板綍鍒犻櫎澶辫触')
+ })
+ }).catch(() => {
+ ElMessage({
+ type: 'info',
+ message: '宸插彇娑堝垹闄�'
+ })
+ })
+}
+// 鐢宠鍋囨湡
+const openLeaveForm = () => {
+ leaveOperationType.value = 'add'
+ showLeaveDialog.value = true
+ // leaveForm.type = ''
+ // leaveForm.startDate = ''
+ // leaveForm.endDate = ''
+ // leaveForm.days = 0
+ // leaveForm.reason = ''
+ // leaveForm.status = 'warning'
+}
+// 缂栬緫鍋囨湡璁板綍
+const editLeaveRecord = (row) => {
+ leaveOperationType.value = 'edit'
+ showLeaveDialog.value = true
+ Object.assign(leaveForm, row)
+ // ElMessage.info('缂栬緫鍔熻兘寮�鍙戜腑...')
+}
+
+// 鍒犻櫎鍋囨湡璁板綍
+const deleteLeaveRecord = (row) => {
+ ElMessageBox.confirm('纭畾鍒犻櫎璇ュ亣鏈熻褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ holidayApplicationDelete(row.id)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success('鍋囨湡璁板綍鍒犻櫎鎴愬姛')
+ getHolidayApplicationList()
+ }
+ }).catch(err => {
+ ElMessage.error('鍋囨湡璁板綍鍒犻櫎澶辫触')
+ })
+ }).catch(() => {
+ ElMessage({
+ type: 'info',
+ message: '宸插彇娑堝垹闄�'
+ })
+ })
+}
+
+//璁$畻鍋囨湡澶╂暟
+const calculateDays = () => {
+ try {
+ if (leaveForm.startDate && leaveForm.endDate) {
+ const start = new Date(leaveForm.startDate)
+ const end = new Date(leaveForm.endDate)
+ leaveForm.startDate = start.toISOString().split('T')[0]
+ leaveForm.endDate = end.toISOString().split('T')[0]
+
+ if (isNaN(start.getTime()) || isNaN(end.getTime())) {
+ console.warn('鏃犳晥鐨勬棩鏈熸牸寮�')
+ return
+ }
+
+ const diffTime = Math.abs(end - start)
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1
+ leaveForm.days = diffDays
+ }
+ } catch (error) {
+ console.error('璁$畻澶╂暟澶辫触:', error)
+ }
+}
+
+// 鎻愪氦鍋囨湡鐢宠
+const submitLeaveApplication = () => {
+ if (leaveOperationType.value === 'add') {
+ if (!leaveForm.type || !leaveForm.startDate || !leaveForm.endDate || !leaveForm.reason) {
+ ElMessage.warning('璇峰~鍐欏畬鏁翠俊鎭�')
+ return
+ }
+ calculateDays()
+ const newLeave = {
+ type: leaveForm.type,
+ startDate: leaveForm.startDate,
+ endDate: leaveForm.endDate,
+ days: leaveForm.days, // 绠�鍗曡绠�
+ reason: leaveForm.reason,
+ status: '瀹℃壒涓�'
+ }
+
+ holidayApplicationAdd(newLeave)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success('鍋囨湡鐢宠鎻愪氦鎴愬姛')
+ getHolidayApplicationList()
+ showLeaveDialog.value = false
+ // 閲嶇疆琛ㄥ崟
+ Object.keys(leaveForm).forEach(key => {
+ leaveForm[key] = ''
+ })
+ }
+ }).catch(err => {
+ ElMessage.error('鍋囨湡鐢宠鎻愪氦澶辫触')
+ })
+ }else{
+ calculateDays()
+ holidayApplicationUpdate(leaveForm)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success('鍋囨湡鐢宠鏇存柊鎴愬姛')
+ getHolidayApplicationList()
+ showLeaveDialog.value = false
+ // 閲嶇疆琛ㄥ崟
+ Object.keys(leaveForm).forEach(key => {
+ leaveForm[key] = ''
+ })
+ }
+ }).catch(err => {
+ ElMessage.error('鍋囨湡鐢宠鏇存柊澶辫触')
+ })
+ }
+}
+
+// 鑾峰彇涓汉淇℃伅
+const getProfile = () => {
+ tableLoading.value = true;
+ getUserProfile().then(res => {
+ if (res.code === 200) {
+ currentUser.value = res.data
+ // console.log("----",currentUser.value)
+ //寰楀埌浜哄憳鍒楄〃
+ staffOnJobListPage({staffState: 1}).then(res => {
+ //绛涢�夊嚭鍜宑urrentUser鍚屽悕鐨勪汉鍛�
+ // let tableData = res.data.records
+ user.value = res.data.records.find(item => item.staffName === currentUser.value.userName)
+ // console.log("++++",user.value)
+ if(user.value){
+ profile.value.id=user.value.id
+ profile.value.name=user.value.staffName
+ profile.value.employeeId=user.value.staffNo
+ profile.value.phone=user.value.phone
+ profile.value.email=currentUser.value.email
+ profile.value.adress=user.value.adress
+ profile.value.position=user.value.postJob
+ profile.value.hireDate=user.value.createTime
+ profile.value.department=currentUser.value.deptNames
+ }
+ // console.log(profile.value)
+ // tableLoading.value = false;
+ }).catch(err => {})
+ }
+ }).catch(err => {
+ tableLoading.value = false;
+ ElMessage.error('鑾峰彇涓汉淇℃伅澶辫触')
+ })
+}
+// 淇濆瓨涓汉淇℃伅
+const saveProfile = async () => {
+ tableLoading.value = true;
+ try {
+ const userRes = await getUserProfile();
+ if (userRes.code === 200) {
+ currentUser.value = userRes.data;
+ const staffListRes = await staffOnJobListPage({ staffState: 1 });
+ user.value = staffListRes.data.records.find(item => item.staffName === currentUser.value.userName);
+ Object.assign(joinForm, user.value);
+ joinForm.staffName = profileForm.name;
+ joinForm.phone = profileForm.phone;
+ joinForm.email = profileForm.email;
+ joinForm.adress = profileForm.adress;
+ // 璋冪敤鏇存柊涓汉淇℃伅鐨勬帴鍙�
+ updateStaffOnJob(user.value.id, joinForm).then(res => {
+ if (res.code === 200) {
+ ElMessage.success('涓汉淇℃伅淇濆瓨鎴愬姛');
+ getProfile();
+ editProfile.value = false;
+ }
+ }).catch(err => {
+ ElMessage.error('涓汉淇℃伅淇濆瓨澶辫触');
+ })
+ }
+ } catch (err) {
+ ElMessage.error('鑾峰彇涓汉淇℃伅澶辫触');
+ } finally {
+ tableLoading.value = false;
+ }
+};
+
+// 缂栬緫涓汉淇℃伅
+const editProfileForm = () => {
+ editProfile.value = true;
+ Object.assign(profileForm, {
+ name: profile.value.name,
+ phone: profile.value.phone,
+ email: profile.value.email,
+ adress: profile.value.adress,
+ });
+};
+
+//鏈堜唤鏀瑰彉
+const changMonth = () => {
+ getCompensationList()
+}
+//鑾峰彇鑰冨嫟璁板綍鍒楄〃
+const getPersonalAttendanceRecordsList = async () => {
+ tableLoading.value = true
+ personalAttendanceRecordsListPage(page)
+ .then(res => {
+
+ attendanceData.value = res.data.records
+ page.value.total = res.data.total;
+ tableLoading.value = false;
+
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+}
+//钖祫鍗曟煡璇�
+const getCompensationList = async () => {
+ tableLoading.value = true
+ compensationListPage({...page,payDateStr:payDateStr.value})
+ .then(res => {
+ salaryData.value = res.data.records
+ //杩囨护鍑哄綋鍓嶆湀浠界殑鎵f鍚堣
+ salaryData.value.forEach(item => {
+ item.deduction =0 + item.deductionAbsenteeism+item.sickLeaveDeductions+item.deductionPersonalLeave+item.forgetClockDeduct,
+ item.bonus=0,
+ item.status='宸插彂鏀�'
+ })
+
+ page.value.total = res.data.total;
+ tableLoading.value = false;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+}
+//鑾峰彇鍋囨湡鐢宠鍒楄〃
+const getHolidayApplicationList = async () => {
+ tableLoading.value = true
+ holidayApplicationListPage(page)
+ .then(res => {
+ leaveData.value = res.data.records
+ page.value.total = res.data.total;
+ tableLoading.value = false;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+}
+onMounted(() => {
+ // 鍒濆鍖�
+ getPersonalAttendanceRecordsList()
+ getCompensationList()
+ getHolidayApplicationList()
+ getProfile()
+})
+</script>
+
+<style scoped>
+.self-service-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: 30px;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 12px;
+ color: white;
+}
+
+.page-header h2 {
+ color: white;
+ margin-bottom: 10px;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.page-header p {
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 14px;
+ margin: 0;
+}
+
+.nav-cards {
+ margin-bottom: 30px;
+}
+
+.nav-card {
+ cursor: pointer;
+ transition: all 0.3s ease;
+ border-radius: 12px;
+ border: none;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.nav-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.nav-content {
+ text-align: center;
+ padding: 20px;
+}
+
+.nav-icon {
+ color: #409EFF;
+ margin-bottom: 15px;
+}
+
+.nav-content h3 {
+ margin: 0 0 10px 0;
+ color: #303133;
+ font-size: 18px;
+}
+
+.nav-content p {
+ margin: 0;
+ color: #909399;
+ font-size: 14px;
+}
+
+.main-content {
+ margin-bottom: 30px;
+}
+
+.content-card {
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ border: none;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: 600;
+ color: #303133;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .self-service-container {
+ padding: 10px;
+ }
+
+ .nav-cards .el-col {
+ margin-bottom: 15px;
+ }
+
+ .page-header h2 {
+ font-size: 24px;
+ }
+}
+</style>
diff --git a/src/views/procurementManagement/advancedPriceManagement/index.vue b/src/views/procurementManagement/advancedPriceManagement/index.vue
new file mode 100644
index 0000000..84bd160
--- /dev/null
+++ b/src/views/procurementManagement/advancedPriceManagement/index.vue
@@ -0,0 +1,773 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true" label-width="100px">
+ <el-form-item label="鍟嗗搧鍚嶇О锛�">
+ <el-input v-model="searchForm.productName" placeholder="璇疯緭鍏ュ晢鍝佸悕绉�" clearable style="width: 200px" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗭細">
+ <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>
+ <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-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鍔熻兘鎸夐挳鍖哄煙 -->
+ <el-card class="action-card" shadow="never">
+ <div class="action-buttons">
+ <el-button type="primary" @click="openDialog('add')">
+ <el-icon><Plus /></el-icon>
+ 鏂板浠锋牸
+ </el-button>
+ <el-button type="success" @click="openBatchDiscountDialog">
+ <el-icon><Discount /></el-icon>
+ 鎵归噺鎶樻墸
+ </el-button>
+ <el-button type="info" @click="exportData">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭鏁版嵁
+ </el-button>
+ <el-button type="danger" @click="handleBatchDelete" :disabled="selectedRows.length === 0">
+ <el-icon><Delete /></el-icon>
+ 鎵归噺鍒犻櫎
+ </el-button>
+ </div>
+ </el-card>
+
+
+ <!-- 涓昏〃鏍� -->
+ <el-card class="table-card" shadow="never">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="loading"
+ @selection-change="handleSelectionChange"
+ :default-sort="{ prop: 'updateTime', order: 'descending' }"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="鍟嗗搧淇℃伅" min-width="200">
+ <template #default="{ row }">
+ <div class="product-info">
+ <div class="product-name">{{ row.productName }}</div>
+ <div class="product-spec">{{ row.specification }}</div>
+ <div class="product-code">缂栫爜: {{ row.productCode }}</div>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="渚涘簲鍟�" prop="supplierName" width="150" />
+ <el-table-column label="鍩虹浠锋牸" width="120" align="right">
+ <template #default="{ row }">
+ <span class="price-text">楼{{ row.basePrice }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎶樻墸淇℃伅" width="150">
+ <template #default="{ row }">
+ <div v-if="row.discountType">
+ <el-tag :type="getDiscountTagType(row.discountType)" size="small">
+ {{ getDiscountText(row.discountType) }}
+ </el-tag>
+ <div class="discount-value">{{ row.discountValue }}{{ row.discountType === 'percentage' ? '%' : '鍏�' }}</div>
+ </div>
+ <span v-else class="no-discount">鏃犳姌鎵�</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹為檯浠锋牸" width="120" align="right">
+ <template #default="{ row }">
+ <span class="final-price">楼{{ calculateFinalPrice(row) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="浠锋牸鎺у埗" width="120">
+ <template #default="{ row }">
+ <div class="price-control">
+ <div v-if="row.minPrice" class="control-item">
+ 鏈�浣�: 楼{{ row.minPrice }}
+ </div>
+ <div v-if="row.maxPrice" class="control-item">
+ 鏈�楂�: 楼{{ row.maxPrice }}
+ </div>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" width="100" align="center">
+ <template #default="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
+ <div v-if="isPriceWarning(row)" class="warning-indicator">
+ <el-icon color="#F56C6C"><Warning /></el-icon>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢熸晥鏃堕棿" prop="effectiveTime" width="180" />
+ <el-table-column label="鏇存柊鏃堕棿" prop="updateTime" width="180" sortable />
+ <el-table-column label="鎿嶄綔" width="250" align="center" fixed="right">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="openDialog('edit', row)">
+ <el-icon><Edit /></el-icon>
+ 缂栬緫
+ </el-button>
+ <el-button type="danger" link @click="handleDelete(row)">
+ <el-icon><Delete /></el-icon>
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-wrapper">
+ <el-pagination
+ v-model:current-page="pagination.current"
+ v-model:page-size="pagination.size"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <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">
+ <el-form-item label="鍟嗗搧鍚嶇О" prop="productName">
+ <el-select v-model="formData.productName" placeholder="璇烽�夋嫨鍟嗗搧" style="width: 100%" filterable>
+ <el-option v-for="product in productList" :key="product.id" :label="product.name" :value="product.name" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍟嗗搧缂栫爜" prop="productCode">
+ <el-input v-model="formData.productCode" placeholder="璇疯緭鍏ュ晢鍝佺紪鐮�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿" prop="specification">
+ <el-input v-model="formData.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierName">
+ <el-select v-model="formData.supplierName" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%">
+ <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.name" :value="supplier.name" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍩虹浠锋牸" prop="basePrice">
+ <el-input-number v-model="formData.basePrice" :min="0" :precision="2" placeholder="璇疯緭鍏ュ熀纭�浠锋牸" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅">
+ <el-input v-model="formData.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 鎶樻墸璁剧疆 -->
+ <el-divider content-position="left">鎶樻墸璁剧疆</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鎶樻墸绫诲瀷">
+ <el-select v-model="formData.discountType" placeholder="璇烽�夋嫨鎶樻墸绫诲瀷" style="width: 100%">
+ <el-option label="鏃犳姌鎵�" value="" />
+ <el-option label="鐧惧垎姣旀姌鎵�" value="percentage" />
+ <el-option label="鍥哄畾閲戦" value="fixed" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎶樻墸鍊�" v-if="formData.discountType && formData.discountType !== 'tiered'">
+ <el-input-number
+ v-model="formData.discountValue"
+ :min="0"
+ :max="formData.discountType === 'percentage' ? 100 : undefined"
+ :precision="2"
+ placeholder="璇疯緭鍏ユ姌鎵e��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎶樻墸鏈夋晥鏈�">
+ <el-date-picker
+ v-model="formData.discountEndTime"
+ type="datetime"
+ placeholder="閫夋嫨缁撴潫鏃堕棿"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 浠锋牸鎺у埗 -->
+ <el-divider content-position="left">浠锋牸鎺у埗</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鏈�浣庝环鏍�">
+ <el-input-number v-model="formData.minPrice" :min="0" :precision="2" placeholder="鏈�浣庝环鏍�" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鏈�楂樹环鏍�">
+ <el-input-number v-model="formData.maxPrice" :min="0" :precision="2" placeholder="鏈�楂樹环鏍�" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="棰勮闃堝��(%)">
+ <el-input-number v-model="formData.warningThreshold" :min="0" :max="100" :precision="1" placeholder="棰勮闃堝��" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢熸晥鏃堕棿" prop="effectiveTime">
+ <el-date-picker v-model="formData.effectiveTime" type="datetime" placeholder="閫夋嫨鐢熸晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="澶辨晥鏃堕棿">
+ <el-date-picker v-model="formData.expireTime" type="datetime" placeholder="閫夋嫨澶辨晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="璋冧环鍘熷洜" prop="reason">
+ <el-select v-model="formData.reason" placeholder="璇烽�夋嫨璋冧环鍘熷洜" style="width: 100%">
+ <el-option label="甯傚満浠锋牸鍙樺姩" value="market" />
+ <el-option label="鎴愭湰鍙樺寲" value="cost" />
+ <el-option label="渚涘簲鍟嗚皟鏁�" value="supplier" />
+ <el-option label="瀛h妭鎬ц皟鏁�" value="seasonal" />
+ <el-option label="淇冮攢娲诲姩" value="promotion" />
+ <el-option label="鍏朵粬鍘熷洜" value="other" />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="澶囨敞">
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+
+ <!-- 鎵归噺鎶樻墸瀵硅瘽妗� -->
+ <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%">
+ <el-option label="鐧惧垎姣旀姌鎵�" value="percentage" />
+ <el-option label="鍥哄畾閲戦" value="fixed" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎶樻墸鍊�">
+ <el-input-number
+ v-model="batchDiscountForm.discountValue"
+ :min="0"
+ :max="batchDiscountForm.discountType === 'percentage' ? 100 : undefined"
+ :precision="2"
+ placeholder="璇疯緭鍏ユ姌鎵e��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鐢熸晥鏃堕棿">
+ <el-date-picker v-model="batchDiscountForm.effectiveTime" type="datetime" placeholder="閫夋嫨鐢熸晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="澶辨晥鏃堕棿">
+ <el-date-picker v-model="batchDiscountForm.expireTime" type="datetime" placeholder="閫夋嫨澶辨晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="閫傜敤鍟嗗搧">
+ <div class="selected-items">
+ 宸查�夋嫨 {{ selectedRows.length }} 涓晢鍝�
+ </div>
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+
+ <!-- 浠锋牸鎺у埗瀵硅瘽妗� -->
+ <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" />
+ </el-form-item>
+ <el-form-item label="榛樿鏈�楂樹环鏍�">
+ <el-input-number v-model="priceControlForm.defaultMaxPrice" :min="0" :precision="2" style="width: 200px" />
+ </el-form-item>
+ <el-form-item label="浠锋牸鍙樺姩闃堝��">
+ <el-input-number v-model="priceControlForm.changeThreshold" :min="0" :max="100" :precision="1" style="width: 200px" />
+ </el-form-item>
+ </el-form>
+ </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 {
+ Search, Refresh, Plus, Discount, Setting, Download, Delete, Edit,
+ Warning
+} from '@element-plus/icons-vue'
+import { listPage, update, del, add } from '@/api/procurementManagement/advancedPriceManagement'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const submitLoading = ref(false)
+const dialogVisible = ref(false)
+const batchDiscountVisible = ref(false)
+const priceControlVisible = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+const formRef = ref()
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ productName: '',
+ supplierId: ''
+})
+
+const total = ref(0)
+
+// 鍒嗛〉
+const pagination = reactive({
+ current: 1,
+ size: 10
+})
+
+
+// 琛ㄥ崟鏁版嵁
+const formData = reactive({
+ productName: '',
+ productCode: '',
+ specification: '',
+ supplierName: '',
+ basePrice: 0,
+ unit: '',
+ discountType: '',
+ discountValue: 0,
+ discountEndTime: '',
+ tieredDiscount: [],
+ minPrice: null,
+ maxPrice: null,
+ warningThreshold: 10,
+ effectiveTime: '',
+ expireTime: '',
+ reason: '',
+ remark: ''
+})
+
+const tableData = ref([])
+
+// 鎵归噺鎶樻墸琛ㄥ崟
+const batchDiscountForm = reactive({
+ discountType: 'percentage',
+ discountValue: 0,
+ effectiveTime: '',
+ expireTime: ''
+})
+
+// 浠锋牸鎺у埗琛ㄥ崟
+const priceControlForm = reactive({
+ defaultMinPrice: 0,
+ defaultMaxPrice: 0,
+ changeThreshold: 10,
+})
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const formRules = {
+ productName: [{ required: true, message: '璇烽�夋嫨鍟嗗搧鍚嶇О', trigger: 'change' }],
+ productCode: [{ required: true, message: '璇疯緭鍏ュ晢鍝佺紪鐮�', trigger: 'blur' }],
+ supplierName: [{ required: true, message: '璇烽�夋嫨渚涘簲鍟�', trigger: 'change' }],
+ basePrice: [{ required: true, message: '璇疯緭鍏ュ熀纭�浠锋牸', trigger: 'blur' }],
+ effectiveTime: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃堕棿', trigger: 'change' }],
+ reason: [{ required: true, message: '璇烽�夋嫨璋冧环鍘熷洜', trigger: 'change' }]
+}
+
+const supplierList = ref([
+ { id: 1, name: '浼樿川浜旈噾渚涘簲鍟�' },
+ { id: 2, name: '閽㈡潗璐告槗鍏徃' },
+ { id: 3, name: '寤烘潗鎵瑰彂鍟�' }
+])
+
+const productList = ref([
+ { id: 1, name: '楂樺己搴﹁灪鏍�' },
+ { id: 2, name: '涓嶉攬閽㈢' },
+ { id: 3, name: '閾濆悎閲戝瀷鏉�' }
+])
+
+
+// 鏂规硶
+const calculateFinalPrice = (row) => {
+ let finalPrice = row.basePrice
+ if (row.discountType === 'percentage') {
+ finalPrice = row.basePrice * (1 - row.discountValue / 100)
+ } else if (row.discountType === 'fixed') {
+ finalPrice = row.basePrice - row.discountValue
+ }
+ return Math.max(finalPrice, 0)
+}
+
+const getDiscountTagType = (discountType) => {
+ const typeMap = {
+ percentage: 'success',
+ fixed: 'warning',
+ tiered: 'info'
+ }
+ return typeMap[discountType] || 'info'
+}
+
+const getDiscountText = (discountType) => {
+ const textMap = {
+ percentage: '鐧惧垎姣�',
+ fixed: '鍥哄畾閲戦'
+ }
+ return textMap[discountType] || '鏈煡'
+}
+
+const getStatusType = (status) => {
+ const statusMap = {
+ active: 'success',
+ pending: 'warning',
+ expired: 'info'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = {
+ active: '鏈夋晥',
+ pending: '寰呯敓鏁�',
+ expired: '宸茶繃鏈�'
+ }
+ return statusMap[status] || '鏈煡'
+}
+
+const isPriceWarning = (row) => {
+ if (!row.priceControl) return false
+ const finalPrice = calculateFinalPrice(row)
+ return finalPrice < row.priceControl.minPrice || finalPrice > row.priceControl.maxPrice
+}
+
+
+const handleSearch = () => {
+ loading.value = true
+ // 妯℃嫙API璋冪敤
+ listPage({ ...searchForm, ...pagination}).then(res => {
+ tableData.value = res.data.records
+ total.value = res.data.total
+ loading.value = false
+ })
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, {
+ productName: '',
+ supplierId: ''
+ })
+ handleSearch()
+}
+
+
+const openDialog = (type, row = {}) => {
+ dialogType.value = type
+ if (type === 'edit' && row.id) {
+ Object.assign(formData, {
+ ...row,
+ minPrice: row.priceControl?.minPrice,
+ maxPrice: row.priceControl?.maxPrice,
+ tieredDiscount: row.tieredDiscount || []
+ })
+ } else {
+ resetFormData()
+ }
+ dialogVisible.value = true
+}
+
+const resetFormData = () => {
+ Object.assign(formData, {
+ productName: '',
+ productCode: '',
+ specification: '',
+ supplierName: '',
+ basePrice: 0,
+ unit: '',
+ discountType: '',
+ discountValue: 0,
+ discountEndTime: '',
+ tieredDiscount: [],
+ minPrice: null,
+ maxPrice: null,
+ warningThreshold: 10,
+ effectiveTime: '',
+ expireTime: '',
+ reason: '',
+ remark: ''
+ })
+}
+
+const addTieredRow = () => {
+ formData.tieredDiscount.push({
+ minQty: 0,
+ maxQty: 0,
+ discount: 0
+ })
+}
+
+const removeTieredRow = (index) => {
+ formData.tieredDiscount.splice(index, 1)
+}
+
+const handleSubmit = async () => {
+ if (!formRef.value) return
+
+ try {
+ await formRef.value.validate()
+ submitLoading.value = true
+
+ if (dialogType.value === 'add') {
+ add(formData).then(res => {
+ if (res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ handleSearch()
+ }
+ })
+ } else {
+ update(formData).then(res => {
+ if (res.code === 200){
+ ElMessage.success('缂栬緫鎴愬姛')
+ handleSearch()
+ }
+ })
+ }
+
+ } catch (error) {
+ console.error('琛ㄥ崟楠岃瘉澶辫触:', error)
+ }finally {
+ dialogVisible.value = false
+ submitLoading.value = false
+ }
+}
+
+const openBatchDiscountDialog = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning('璇峰厛閫夋嫨瑕佽缃姌鎵g殑鍟嗗搧')
+ return
+ }
+ batchDiscountVisible.value = true
+}
+
+const handleBatchDiscount = () => {
+ // 鎵归噺璁剧疆鎶樻墸閫昏緫
+ selectedRows.value.forEach(row => {
+ row.discountType = batchDiscountForm.discountType
+ row.discountValue = batchDiscountForm.discountValue
+ update(row).then(res => {
+ handleSearch()
+ })
+ })
+ ElMessage.success('鎶樻墸璁剧疆鎴愬姛')
+ batchDiscountVisible.value = false
+
+}
+
+const openPriceControlDialog = () => {
+ priceControlVisible.value = true
+}
+
+const handlePriceControl = () => {
+ ElMessage.success('浠锋牸鎺у埗璁剧疆宸蹭繚瀛�')
+ priceControlVisible.value = false
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ let ids = [row.id]
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ handleSearch()
+ }
+ })
+ })
+}
+
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning('璇峰厛閫夋嫨瑕佸垹闄ょ殑璁板綍')
+ return
+ }
+
+ ElMessageBox.confirm(`纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉¤褰曞悧锛焋, '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ del(selectedRows.value.map(item => item.id)).then(i =>{
+ if(i.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ handleSearch()
+ }
+ })
+ })
+}
+
+const handleSelectionChange = (rows) => {
+ selectedRows.value = rows
+}
+
+const handleSizeChange = (size) => {
+ pagination.size = size
+ handleSearch()
+}
+
+const handleCurrentChange = (page) => {
+ pagination.current = page
+ handleSearch()
+}
+const { proxy } = getCurrentInstance();
+
+const exportData = () => {
+ ElMessageBox.confirm("鍐呭灏嗚瀵煎嚭锛屾槸鍚︾‘璁ゅ鍑猴紵", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/procurementPriceManagement/export", {}, "閲囪喘浠锋牸绠$悊.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ handleSearch()
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.search-card, .action-card, .table-card {
+ margin-bottom: 20px;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+
+.product-info {
+ line-height: 1.4;
+}
+
+.product-name {
+ font-weight: bold;
+ color: #303133;
+}
+
+.product-spec, .product-code {
+ font-size: 12px;
+ color: #909399;
+}
+
+.price-text {
+ font-weight: bold;
+ color: #409EFF;
+}
+
+.final-price {
+ font-weight: bold;
+ color: #67C23A;
+ font-size: 16px;
+}
+
+.discount-value {
+ font-size: 12px;
+ color: #E6A23C;
+ margin-top: 2px;
+}
+
+.no-discount {
+ color: #C0C4CC;
+ font-size: 12px;
+}
+
+.price-control {
+ font-size: 12px;
+ line-height: 1.3;
+}
+
+.control-item {
+ color: #909399;
+}
+
+.warning-indicator {
+ margin-top: 2px;
+}
+
+.pagination-wrapper {
+ display: flex;
+ justify-content: end;
+ margin-top: 20px;
+}
+
+.selected-items {
+ color: #409EFF;
+ font-weight: bold;
+}
+
+
+.mt-2 {
+ margin-top: 8px;
+}
+
+.ml-2 {
+ margin-left: 8px;
+}
+
+:deep(.el-table) {
+ font-size: 13px;
+}
+
+:deep(.el-table th) {
+ background-color: #fafafa;
+}
+
+:deep(.el-card__body) {
+ padding: 15px;
+}
+
+:deep(.el-divider__text) {
+ font-weight: bold;
+ color: #409EFF;
+}
+</style>
diff --git a/src/views/procurementManagement/arrivalManagement/index.vue b/src/views/procurementManagement/arrivalManagement/index.vue
new file mode 100644
index 0000000..a1b5eed
--- /dev/null
+++ b/src/views/procurementManagement/arrivalManagement/index.vue
@@ -0,0 +1,237 @@
+<template>
+ <div class="app-container">
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="閲囪喘璁㈠崟鍙凤細">
+ <el-input v-model="searchForm.orderNo" placeholder="璇疯緭鍏ヨ鍗曞彿" clearable />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細">
+ <el-input v-model="searchForm.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <el-card class="table-card" shadow="never">
+ <div class="table-header">
+ <el-button type="primary" @click="openDialog('add')">鏂板鍒拌揣</el-button>
+ <el-button type="danger" @click="handleBatchDelete">鎵归噺鍒犻櫎</el-button>
+ </div>
+
+ <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="鍒拌揣鍗曞彿" prop="arrivalNo" width="180" />
+ <el-table-column label="閲囪喘璁㈠崟鍙�" prop="orderNo" width="180" />
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" />
+ <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="arrivalQuantity" width="100" />
+ <el-table-column label="鍒拌揣鏃堕棿" prop="arrivalTime" width="180" />
+ <el-table-column label="鎿嶄綔" width="200" align="center">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="openDialog('edit', row)">缂栬緫</el-button>
+ <el-button type="success" v-if="row.status === 'pending'" link @click="handleReceive(row)">鏀惰揣</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.current"
+ :limit="pagination.size"
+ @pagination="handleCurrentChange"
+ />
+ </el-card>
+
+ <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="鍒拌揣鍗曞彿" />
+ </el-form-item>
+ <el-form-item label="閲囪喘璁㈠崟鍙�">
+ <el-input v-model="formData.orderNo" placeholder="閲囪喘璁㈠崟鍙�" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗗悕绉�">
+ <el-input v-model="formData.supplierName" placeholder="渚涘簲鍟嗗悕绉�" />
+ </el-form-item>
+ <el-form-item label="鍒拌揣鏁伴噺">
+ <el-input-number :min="0" v-model="formData.arrivalQuantity" placeholder="鍒拌揣鏁伴噺" />
+ </el-form-item>
+ <el-form-item label="澶囨敞">
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
+ </el-form-item>
+ </el-form>
+ </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"
+import Pagination from '@/components/PIMTable/Pagination.vue'
+
+onMounted(() => {
+ getList()
+})
+
+const tableData = ref([])
+
+const getList = () => {
+ loading.value = true
+ listPage({...searchForm,...pagination}).then(res =>{
+ if(res.code === 200){
+ tableData.value = res.data.records
+ total.value = res.data.total
+ loading.value = false
+ }
+ })
+}
+
+const pagination = reactive({
+ current: 1,
+ size: 10
+})
+
+const total = ref(0)
+
+const handleCurrentChange = (val) => {
+ pagination.current = val.page
+ pagination.size = val.limit
+ getList()
+}
+
+const loading = ref(false)
+const dialogVisible = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+
+const searchForm = reactive({
+ orderNo: '',
+ supplierName: ''
+})
+
+const formData = reactive({
+ arrivalNo: '',
+ arrivalQuantity: 0,
+ orderNo: '',
+ supplierName: '',
+ remark: '',
+ status: 'pending'
+})
+
+const getStatusType = (status) => {
+ const statusMap = { pending: 'warning', received: 'success', stored: 'info' }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = { pending: '寰呮敹璐�', received: '宸叉敹璐�', stored: '宸插叆搴�' }
+ return statusMap[status] || '鏈煡'
+}
+
+const handleSearch = () => {
+ loading.value = true
+ getList()
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, { orderNo: '', supplierName: '' })
+}
+
+const openDialog = (type, row = {}) => {
+ dialogType.value = type
+ if (type === 'edit' && row.id) {
+ obj.id = row.id
+ Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark, arrivalQuantity: row.arrivalQuantity,arrivalNo: row.arrivalNo })
+ } else {
+ Object.assign(formData, { orderNo: '', supplierName: '', remark: '',arrivalQuantity: 0,arrivalNo: '' })
+ }
+ dialogVisible.value = true
+}
+
+const obj = reactive({
+ id:''
+})
+
+const handleSubmit = () => {
+ if (dialogType.value === 'add') {
+ add(formData).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ getList()
+ }
+ })
+ }else{
+ update({...formData, ...obj}).then(res => {
+ if(res.code === 200){
+ ElMessage.success('缂栬緫鎴愬姛')
+ getList()
+ }
+ })
+ }
+ dialogVisible.value = false
+}
+
+const handleReceive = (row) => {
+ row.status = 'received'
+ update(row).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏀惰揣鎴愬姛')
+ getList()
+ }
+ })
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ let ids = [row.id]
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
+ })
+}
+
+const handleBatchDelete = () => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄ら�変腑鐨勮褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ let ids = selectedRows.value.map(item => item.id)
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
+ })
+}
+
+const handleSelectionChange = (rows) => {
+ selectedRows.value = rows
+}
+</script>
+
+<style scoped>
+.app-container { padding: 20px; }
+.search-card { margin-bottom: 20px; }
+.table-card { margin-bottom: 20px; }
+.table-header { margin-bottom: 20px; }
+</style>
diff --git a/src/views/procurementManagement/index.vue b/src/views/procurementManagement/index.vue
new file mode 100644
index 0000000..bf54384
--- /dev/null
+++ b/src/views/procurementManagement/index.vue
@@ -0,0 +1,418 @@
+<template>
+ <div class="app-container">
+ <!-- 椤甸潰鏍囬 -->
+ <div class="page-header">
+ <h2>閲囪喘绠$悊绯荤粺</h2>
+ <p>缁熶竴绠$悊閲囪喘鍏ㄦ祦绋嬶紝鎻愬崌閲囪喘鏁堢巼涓庤川閲�</p>
+ </div>
+
+ <!-- 鍔熻兘妯″潡鍗$墖 -->
+ <el-row :gutter="20" class="module-cards">
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/purchaseOrder')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#409EFF"><Document /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>閲囪喘璁㈠崟绠$悊</h3>
+ <p>鏂板缓銆佺紪杈戙�佸垹闄ら噰璐鍗曪紝閫夋嫨渚涘簲鍟嗭紝濉啓鍟嗗搧鏄庣粏</p>
+ <div class="card-stats">
+ <span>寰呭鏍�: {{ stats.pendingOrders }}</span>
+ <span>宸插鏍�: {{ stats.approvedOrders }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/arrivalManagement')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#67C23A"><Box /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>鍒拌揣绠$悊</h3>
+ <p>鑷姩鍏宠仈閲囪喘璁㈠崟锛屽綍鍏ュ埌璐у晢鍝佷俊鎭紝鏀寔鎵撳嵃鏌ョ湅</p>
+ <div class="card-stats">
+ <span>寰呮敹璐�: {{ stats.pendingArrivals }}</span>
+ <span>宸叉敹璐�: {{ stats.receivedArrivals }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/qualityInspection')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#E6A23C"><Search /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>璐ㄦ绠$悊</h3>
+ <p>鍒拌揣鍚庤嚜鍔ㄧ敓鎴愯川妫�鍗曪紝濉啓鍚堟牸涓庝笉鍚堟牸鍟嗗搧鏁伴噺鍙婂師鍥�</p>
+ <div class="card-stats">
+ <span>寰呰川妫�: {{ stats.pendingInspections }}</span>
+ <span>宸插畬鎴�: {{ stats.completedInspections }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" class="module-cards">
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/returnManagement')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#F56C6C"><RefreshLeft /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>閫�璐х鐞�</h3>
+ <p>鐢熸垚閲囪喘閫�璐у崟鍜岃川妫�閫�璐у崟锛屾敮鎸佺瓫閫夋煡璇笌鍗曟嵁璇︽儏</p>
+ <div class="card-stats">
+ <span>寰呭鏍�: {{ stats.pendingReturns }}</span>
+ <span>宸插鏍�: {{ stats.approvedReturns }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/priceManagement')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#909399"><Money /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>浠锋牸绠$悊</h3>
+ <p>鏍规嵁鍟嗗搧鍙婂競鍦轰环鏍煎彉鍖栬繘琛岄噰璐环璋冩暣锛岃嚜鍔ㄦ洿鏂伴噰璐崟鎹�</p>
+ <div class="card-stats">
+ <span>鏈夋晥浠锋牸: {{ stats.activePrices }}</span>
+ <span>寰呯敓鏁�: {{ stats.pendingPrices }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementPlan')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#9C27B0"><Calendar /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>閲囪喘璁″垝</h3>
+ <p>鏅鸿兘閲囪喘璁″垝閰嶇疆锛岃嚜鍔ㄨ绠楅噰璐渶姹傦紝鑰冭檻搴撳瓨鍜屽畨鍏ㄥ簱瀛�</p>
+ <div class="card-stats">
+ <span>娲昏穬璁″垝: {{ stats.activePlans }}</span>
+ <span>寰呰绠�: {{ stats.pendingCalculations }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" class="module-cards">
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementLedger')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#9C27B0"><List /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>閲囪喘鍙拌处</h3>
+ <p>鏌ョ湅閲囪喘鍘嗗彶璁板綍锛岀粺璁″垎鏋愰噰璐暟鎹紝鐢熸垚閲囪喘鎶ヨ〃</p>
+ <div class="card-stats">
+ <span>鎬昏鍗�: {{ stats.totalOrders }}</span>
+ <span>鎬婚噾棰�: 楼{{ stats.totalAmount.toFixed(2) }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementReport')">
+ <div class="card-content">
+ <div class="card-icon">
+ <el-icon size="48" color="#FF6B6B"><TrendCharts /></el-icon>
+ </div>
+ <div class="card-info">
+ <h3>閲囪喘鎶ヨ〃</h3>
+ <p>閲囪喘璁㈠崟鎵ц姹囨�汇�佹槑缁嗗垎鏋愩�佷笟鍔$粺璁°�佷緵搴斿晢渚涜揣姹囨��</p>
+ <div class="card-stats">
+ <span>鎶ヨ〃绫诲瀷: 4绉�</span>
+ <span>鏁版嵁鏇存柊: 瀹炴椂</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 缁熻姒傝 -->
+ <el-card class="stats-card" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>閲囪喘缁熻姒傝</span>
+ <el-button type="primary" size="small" @click="refreshStats">鍒锋柊鏁版嵁</el-button>
+ </div>
+ </template>
+
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-number">{{ stats.totalOrders }}</div>
+ <div class="stat-label">閲囪喘璁㈠崟鎬绘暟</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-number">{{ stats.totalAmount.toFixed(2) }}</div>
+ <div class="stat-label">閲囪喘鎬婚噾棰�(涓囧厓)</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-number">{{ stats.avgDeliveryTime }}</div>
+ <div class="stat-label">骞冲潎浜や粯澶╂暟</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-number">{{ stats.qualityRate }}%</div>
+ <div class="stat-label">璐ㄦ鍚堟牸鐜�</div>
+ </div>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鏈�杩戞椿鍔� -->
+ <el-card class="activity-card" shadow="never">
+ <template #header>
+ <span>鏈�杩戞椿鍔�</span>
+ </template>
+
+ <el-timeline>
+ <el-timeline-item
+ v-for="(activity, index) in recentActivities"
+ :key="index"
+ :timestamp="activity.time"
+ :type="activity.type"
+ >
+ {{ activity.content }}
+ </el-timeline-item>
+ </el-timeline>
+ </el-card>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { Document, Box, Search, RefreshLeft, Money, List, Calendar, TrendCharts } from '@element-plus/icons-vue'
+
+const router = useRouter()
+
+// 缁熻鏁版嵁
+const stats = ref({
+ pendingOrders: 5,
+ approvedOrders: 25,
+ pendingArrivals: 3,
+ receivedArrivals: 18,
+ pendingInspections: 2,
+ completedInspections: 15,
+ pendingReturns: 1,
+ approvedReturns: 3,
+ activePrices: 45,
+ pendingPrices: 2,
+ activePlans: 8,
+ pendingCalculations: 3,
+ totalOrders: 30,
+ totalAmount: 125.8,
+ avgDeliveryTime: 7,
+ qualityRate: 96.5
+})
+
+// 鏈�杩戞椿鍔�
+const recentActivities = ref([
+ {
+ time: '2025-12-01 18:30',
+ content: '鏂板閲囪喘璁㈠崟 PO20241201004',
+ type: 'primary'
+ },
+ {
+ time: '2025-12-01 17:45',
+ content: '瀹屾垚璐ㄦ鍗� QI20241201002',
+ type: 'success'
+ },
+ {
+ time: '2025-12-01 16:20',
+ content: '鍒拌揣鍗� AR20241201003 宸叉敹璐�',
+ type: 'success'
+ },
+ {
+ time: '2025-12-01 15:15',
+ content: '浠锋牸璋冩暣锛氬晢鍝丅 浠� 楼80 璋冩暣涓� 楼75',
+ type: 'warning'
+ },
+ {
+ time: '2025-12-01 14:30',
+ content: '閫�璐у崟 RT20241201003 宸插鏍�',
+ type: 'info'
+ }
+])
+
+// 瀵艰埅鍒版寚瀹氶〉闈�
+const navigateTo = (path) => {
+ router.push(path)
+}
+
+// 鍒锋柊缁熻鏁版嵁
+const refreshStats = () => {
+ // 妯℃嫙鍒锋柊鏁版嵁
+ stats.value.pendingOrders = Math.floor(Math.random() * 10) + 1
+ stats.value.totalAmount = (Math.random() * 100 + 100).toFixed(1)
+}
+
+onMounted(() => {
+ // 椤甸潰鍔犺浇瀹屾垚鍚庣殑鍒濆鍖栭�昏緫
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: 30px;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 10px;
+ color: white;
+}
+
+.page-header h2 {
+ margin: 0 0 10px 0;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.page-header p {
+ margin: 0;
+ font-size: 16px;
+ opacity: 0.9;
+}
+
+.module-cards {
+ margin-bottom: 20px;
+}
+
+.module-card {
+ cursor: pointer;
+ transition: all 0.3s ease;
+ border: none;
+ border-radius: 12px;
+}
+
+.module-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.card-content {
+ display: flex;
+ align-items: center;
+ padding: 20px;
+}
+
+.card-icon {
+ margin-right: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 80px;
+ height: 80px;
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+ border-radius: 50%;
+ color: white;
+}
+
+.card-info h3 {
+ margin: 0 0 10px 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.card-info p {
+ margin: 0 0 15px 0;
+ font-size: 14px;
+ color: #606266;
+ line-height: 1.5;
+}
+
+.card-stats {
+ display: flex;
+ gap: 15px;
+}
+
+.card-stats span {
+ font-size: 12px;
+ color: #909399;
+ background-color: #f5f7fa;
+ padding: 4px 8px;
+ border-radius: 4px;
+}
+
+.stats-card {
+ margin-bottom: 20px;
+ border-radius: 12px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.stat-item {
+ text-align: center;
+ padding: 20px;
+}
+
+.stat-number {
+ font-size: 32px;
+ font-weight: 600;
+ color: #409EFF;
+ margin-bottom: 8px;
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #606266;
+}
+
+.activity-card {
+ border-radius: 12px;
+}
+
+.el-timeline-item {
+ padding-bottom: 20px;
+}
+
+.el-timeline-item:last-child {
+ padding-bottom: 0;
+}
+</style>
diff --git a/src/views/procurementManagement/invoiceEntry/components/Modal.vue b/src/views/procurementManagement/invoiceEntry/components/Modal.vue
index f29b78a..2288183 100644
--- a/src/views/procurementManagement/invoiceEntry/components/Modal.vue
+++ b/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, // 褰曞叆浜篿d
- 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, // 褰曞叆浜篿d
+ 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
+ };
+ });
+
+ // 鍙皟鐢ㄤ竴娆℃帴鍙o紝浼犻�掑寘鍚墍鏈夊悎鍚屾暟鎹殑鏁扮粍
+ 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, // 褰曞叆浜篿d
+ 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>
diff --git a/src/views/procurementManagement/invoiceEntry/index.vue b/src/views/procurementManagement/invoiceEntry/index.vue
index 4b25c38..3719ffe 100644
--- a/src/views/procurementManagement/invoiceEntry/index.vue
+++ b/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>
@@ -45,10 +38,10 @@
<div class="actions">
<div></div>
<div>
+ <el-button @click="handleExport" style="margin-right: 10px">瀵煎嚭</el-button>
<el-button type="primary" @click="handleAdd('add')">
- 鏂板鐧昏
+ 鏉ョエ鐧昏
</el-button>
-<!-- <el-button @click="handleOut">瀵煎嚭</el-button>-->
<!-- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>-->
</div>
</div>
@@ -92,7 +85,7 @@
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import {delRegistration, gePurchaseListPage} from "@/api/procurementManagement/invoiceEntry.js";
-import { nextTick, onMounted, getCurrentInstance } from "vue";
+import { nextTick, onMounted, getCurrentInstance, ref } from "vue";
import ExpandTable from "./components/ExpandTable.vue";
import Modal from "./components/Modal.vue";
import {ElMessageBox} from "element-plus";
@@ -141,11 +134,6 @@
label: "渚涘簲鍟嗗悕绉�",
prop: "supplierName",
width:300
- },
- {
- label: "椤圭洰鍚嶇О",
- prop: "projectName",
- width:400
},
{
label: "褰曞叆浜�",
@@ -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) => {
@@ -223,6 +211,22 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎嚭閲囪喘鍙拌处
+const handleExport = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/purchase/ledger/exportOne", {}, "鏉ョエ鐧昏.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
// 鍒犻櫎
const handleDelete = () => {
let ids = [];
diff --git a/src/views/procurementManagement/invoiceEntry/indexOld.vue b/src/views/procurementManagement/invoiceEntry/indexOld.vue
deleted file mode 100644
index 52fe222..0000000
--- a/src/views/procurementManagement/invoiceEntry/indexOld.vue
+++ /dev/null
@@ -1,730 +0,0 @@
-<template>
- <div class="app-container">
- <div class="search_form">
- <div>
- <span class="search_title">閲囪喘鍚堝悓鍙凤細</span>
- <el-input
- v-model="searchForm.purchaseContractNumber"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- @change="handleQuery"
- clearable
- prefix-icon="Search"
- />
- <el-button
- type="primary"
- @click="handleQuery"
- style="margin-left: 10px"
- >
- 鎼滅储
- </el-button>
- </div>
- <div>
- <el-button type="primary" @click="handleAdd">鏂板鐧昏</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
- :summary-method="summarizeMainTable"
- @expand-change="expandChange"
- height="calc(100vh - 18.5em)"
- stripe
- >
- <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"
- stripe
- >
- <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-column label="鏈鏉ョエ鏁�" prop="ticketsNum" />
- <el-table-column
- label="鏈鏉ョエ閲戦(鍏�)"
- prop="ticketsAmount"
- :formatter="formattedNumber"
- />
- <el-table-column label="鏈潵绁ㄦ暟" prop="futureTickets" />
- <el-table-column
- label="鏈潵绁ㄩ噾棰�(鍏�)"
- prop="futureTicketsAmount"
- :formatter="formattedNumber"
- />
- </el-table>
- </template>
- </el-table-column>
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
- <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="projectName"
- show-overflow-tooltip
- />
- <el-table-column
- label="鍚堝悓閲戦(鍏�)"
- prop="contractAmount"
- show-overflow-tooltip
- :formatter="formattedNumber"
- />
- <el-table-column
- label="宸插紑绁ㄩ噾棰�(鍏�)"
- prop="receiptPaymentAmount"
- show-overflow-tooltip
- :formatter="formattedNumber"
- />
- <el-table-column
- label="寰呭紑绁ㄩ噾棰�(鍏�)"
- prop="unReceiptPaymentAmount"
- show-overflow-tooltip
- :formatter="formattedNumber"
- />
- <el-table-column
- fixed="right"
- label="鎿嶄綔"
- min-width="60"
- align="center"
- >
- <template #default="scope">
- <el-button
- text
- type="primary"
- size="small"
- @click="openForm('edit', 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"
- />
- </div>
- <el-dialog
- v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板鏉ョエ鐧昏' : '缂栬緫鏉ョエ鐧昏'"
- width="80%"
- @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="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-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="projectName">
- <el-input
- v-model="form.projectName"
- placeholder="鑷姩濉厖"
- clearable
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <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
- type="number"
- :step="0.01"
- v-model="form.invoiceAmount"
- placeholder="鑷姩濉厖"
- clearable
- :disabled="true"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <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="issueDate">
- <el-date-picker
- style="width: 100%"
- v-model="form.issueDate"
- type="date"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="涓婁紶闄勪欢">
- <FileUpload :showTip="false" accept="*" :autoUpload="true" />
- </el-form-item>
- </el-col>
- </el-row>
- <!-- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="褰曞叆鏃ユ湡锛�" prop="createTime">
- <el-date-picker
- style="width: 100%"
- v-model="form.createTime"
- type="date"
- placeholder="璇烽�夋嫨"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row> -->
- <el-row>
- <el-form-item label="浜у搧淇℃伅锛�" prop="entryDate"> </el-form-item>
- </el-row>
- <el-table
- :data="productData"
- border
- @selection-change="productSelected"
- stripe
- show-summary
- style="width: 100%"
- :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" width="70" />
- <el-table-column label="鏁伴噺" prop="quantity" width="70" />
- <el-table-column label="绋庣巼(%)" prop="taxRate" width="80" />
- <el-table-column label="褰曞叆鏃ユ湡" prop="createTime" width="120" />
- <el-table-column
- label="鍚◣鍗曚环(鍏�)"
- width="200"
- prop="taxInclusiveUnitPrice"
- :formatter="formattedNumber"
- />
- <el-table-column
- label="鍚◣鎬讳环(鍏�)"
- width="200"
- prop="taxInclusiveTotalPrice"
- :formatter="formattedNumber"
- />
- <el-table-column
- label="涓嶅惈绋庢�讳环(鍏�)"
- width="200"
- prop="taxExclusiveTotalPrice"
- :formatter="formattedNumber"
- />
- <el-table-column label="鏈鏉ョエ鏁�" prop="ticketsNum" width="170">
- <template #default="scope">
- <el-input-number
- v-model="scope.row.ticketsNum"
- placeholder="璇烽�夋嫨"
- :min="0"
- :precision="2"
- :step="0.1"
- clearable
- style="width: 100%"
- @change="invoiceNumBlur(scope.row)"
- />
- </template>
- </el-table-column>
- <el-table-column
- label="鏈鏉ョエ閲戦(鍏�)"
- prop="ticketsAmount"
- :min="0"
- :step="0.1"
- :formatter="formattedNumber"
- width="200"
- >
- <template #default="scope">
- <el-input-number
- v-model="scope.row.ticketsAmount"
- placeholder="璇烽�夋嫨"
- :min="0"
- :precision="2"
- :step="0.1"
- clearable
- style="width: 100%"
- @change="invoiceAmountBlur(scope.row)"
- />
- </template>
- </el-table-column>
- <el-table-column
- label="鏈潵绁ㄦ暟"
- prop="futureTickets"
- :formatter="
- (row) =>
- row.futureTickets == null || row.futureTickets === ''
- ? row.quantity
- : row.futureTickets
- "
- >
- </el-table-column>
- <el-table-column
- label="鏈潵绁ㄩ噾棰�(鍏�)"
- prop="futureTicketsAmount"
- :formatter="
- (row) =>
- row.futureTicketsAmount !== undefined &&
- row.futureTicketsAmount !== null &&
- row.futureTicketsAmount !== ''
- ? row.futureTicketsAmount
- : row.taxExclusiveTotalPrice
- "
- >
- </el-table-column>
- </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 { onMounted, ref } from "vue";
-import { ElMessageBox } from "element-plus";
-import { userListNoPage } from "@/api/system/user.js";
-import { productList } from "@/api/procurementManagement/procurementLedger.js";
-import useUserStore from "@/store/modules/user";
-import FileUpload from "@/components/Upload/FileUpload.vue";
-
-const userStore = useUserStore();
-const { proxy } = getCurrentInstance();
-const tableData = ref([]);
-const productData = ref([]);
-const selectedRows = ref([]);
-const productSelectedRows = ref([]);
-const userList = ref([]);
-const purchaseLedgerList = ref([]);
-const tableLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
-});
-const total = ref(0);
-const fileList = ref([]);
-import {
- addOrUpdateRegistration,
- delRegistration,
- gePurchaseListPage,
- getInfo,
- getProduct,
- getPurchaseNoById,
- getRegistrationById,
-} from "@/api/procurementManagement/invoiceEntry.js";
-
-// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
-const operationType = ref("");
-const dialogFormVisible = ref(false);
-const data = reactive({
- searchForm: {
- purchaseContractNumber: "",
- },
- form: {
- issueDate: "", // 寮�绁ㄦ棩鏈�
- purchaseLedgerId: "",
- purchaseLedgerNo: "",
- issUerId: "", // 寮�绁ㄤ汉id
- issUer: "", // 寮�绁ㄤ汉濮撳悕
- invoiceNumber: "", // 鍙戠エ鍙�
- invoiceAmount: "", // 鍙戠エ閲戦
- createTime: "", // 褰曞叆鏃ユ湡
- },
- rules: {
- invoiceNumber: [
- { required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿", trigger: "blur" },
- { type: "string" },
- ],
- invoiceAmount: [
- { required: true, message: "璇疯緭鍏ュ彂绁ㄩ噾棰�", trigger: "blur" },
- ],
- },
-});
-const { searchForm, form, rules } = toRefs(data);
-// 浜у搧琛ㄥ崟寮规鏁版嵁
-const productFormVisible = ref(false);
-const productOperationType = ref("");
-const currentId = ref("");
-
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
-const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-const getList = () => {
- tableLoading.value = true;
- gePurchaseListPage({ ...searchForm.value, ...page })
- .then((res) => {
- tableLoading.value = false;
- tableData.value = res.records;
- tableData.value.map((item) => {
- item.children = [];
- });
- total.value = res.total;
- expandedRowKeys.value = [];
- })
- .catch(() => {
- tableLoading.value = false;
- });
-};
-const formattedNumber = (row, column, cellValue) => {
- return parseFloat(cellValue).toFixed(2) ?? 0;
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection.filter(
- (item) => item.purchaseContractNumber !== undefined
- );
-};
-const productSelected = (selectedRows) => {
- productSelectedRows.value = selectedRows;
-};
-const expandedRowKeys = ref([]);
-// 灞曞紑琛�
-const expandChange = (row, expandedRows) => {
- if (expandedRows.length > 0) {
- expandedRowKeys.value = [];
- try {
- productList({ salesLedgerId: row.id, type: 2 }).then((res) => {
- const index = tableData.value.findIndex((item) => item.id === row.id);
- if (index > -1) {
- tableData.value[index].children = res;
- }
- expandedRowKeys.value.push(row.id);
- });
- } catch (error) {
- console.log(error);
- }
- } else {
- expandedRowKeys.value = [];
- }
-};
-// 涓昏〃鍚堣鏂规硶
-const summarizeMainTable = (param) => {
- return proxy.summarizeTable(
- param,
- ["contractAmount", "receiptPaymentAmount", "unReceiptPaymentAmount"],
- {
- ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
- futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
- }
- );
-};
-// 瀛愯〃鍚堣鏂规硶
-const summarizeChildrenTable = (param) => {
- return proxy.summarizeTable(
- param,
- [
- "taxInclusiveUnitPrice",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- "ticketsNum",
- "ticketsAmount",
- "futureTickets",
- "futureTicketsAmount",
- ],
- {
- ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
- futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
- }
- );
-};
-
-const handleAdd = () => {
- if (selectedRows.value.length !== 1) {
- proxy.$modal.msgWarning("璇峰厛閫変腑涓�鏉℃暟鎹�");
- return;
- }
- openForm("add", selectedRows.value[0]);
-};
-
-// 鎵撳紑寮规
-const openForm = (type, row) => {
- invoiceNumBlur(row);
- operationType.value = type;
- form.value = {};
- productData.value = [];
- fileList.value = [];
- form.value.issUerId = userStore.id;
- form.value.issUer = userStore.name;
- form.value.issueDate = getNowFormatDate();
- userListNoPage().then((res) => {
- userList.value = res.data;
- });
- // 鏂板鏃朵紶鍏ュ綋鍓嶈id骞舵煡璇㈤噰璐悎鍚屽彿
- if (type === "add" && row && row.id) {
- form.value.purchaseLedgerId = row.id;
- getPurchaseNoById({ id: row.id }).then((res) => {
- let result = res.data;
- purchaseLedgerList.value = result;
- form.value.purchaseLedgerNo = result.purchaseContractNumber;
- form.value.invoiceAmount = result.invoiceAmount;
- form.value.invoiceNumber = result.invoiceNumber;
- setInfo(result.id);
- });
- } else {
- getProduct().then((res) => {
- purchaseLedgerList.value = res;
- });
- }
- if (type === "edit") {
- currentId.value = row.id;
- getRegistrationById({ id: row.id }).then((res) => {
- const { code, data } = res;
- if (code == 200) {
- form.value.invoiceAmount = data.invoiceAmount;
- productData.value = data.productData;
- if (data.salesLedgerFiles) {
- fileList.value = data.salesLedgerFiles;
- } else {
- fileList.value = [];
- }
- }
- });
- }
- dialogFormVisible.value = true;
-};
-// 閫夋嫨閲囪喘鍚堝悓鍙疯祴鍊�
-const setInfo = (value) => {
- getInfo({ id: value }).then((res) => {
- let result = res.data;
- form.value.salesContractNo = result.salesContractNo;
- form.value.projectName = result.projectName;
- productData.value = result.productData;
- form.value.supplierName = result.supplierName;
- });
-};
-// 鎻愪氦琛ㄥ崟
-const submitForm = () => {
- proxy.$refs["formRef"].validate((valid) => {
- if (valid) {
- form.value.productData = proxy.HaveJson(productData.value);
- addOrUpdateRegistration(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
- }
- });
-};
-// 鍏抽棴寮规
-const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
-};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/purchase/registration/export", {}, "鏉ョエ鐧昏.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-// 鍒犻櫎
-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(() => {
- delRegistration(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-//鏈鏉ョエ鏁板け鐒︽搷浣�
-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;
- // 璁$畻鏈潵绁ㄦ暟
- row.futureTickets = row.tempFutureTickets - row.ticketsNum;
- // 璁$畻鏈潵绁ㄩ噾棰�
- row.futureTicketsAmount = row.tempFutureTicketsAmount - row.ticketsAmount;
- calculateinvoiceAmount();
-};
-// 鏈鏉ョエ閲戦澶辩劍鎿嶄綔
-const invoiceAmountBlur = (row) => {
- if (!row.ticketsAmount) {
- row.ticketsAmount = 0;
- }
- // 璁$畻鏄惁瓒呰繃鏉ョエ鎬婚噾棰�
- if (row.ticketsAmount > row.tempFutureTicketsAmount) {
- proxy.$modal.msgWarning("鏈鏉ョエ閲戦涓嶅緱澶т簬鏈潵绁ㄩ噾棰�");
- row.ticketsAmount = 0;
- }
- // 璁$畻鏈鏉ョエ鏁�
- row.ticketsNum = (row.ticketsAmount / row.taxInclusiveUnitPrice).toFixed(2);
- // 璁$畻鏈潵绁ㄦ暟
- row.futureTickets = row.tempFutureTickets - row.ticketsNum;
- // 璁$畻鏈潵绁ㄩ噾棰�
- row.futureTicketsAmount = row.tempFutureTicketsAmount - row.ticketsAmount;
- calculateinvoiceAmount();
-};
-
-// 鑾峰彇褰撳墠鏃ユ湡鍑芥暟
-function getNowFormatDate() {
- let date = new Date(),
- year = date.getFullYear(), //鑾峰彇瀹屾暣鐨勫勾浠�(4浣�)
- month = date.getMonth() + 1, //鑾峰彇褰撳墠鏈堜唤(0-11,0浠h〃1鏈�)
- strDate = date.getDate(); // 鑾峰彇褰撳墠鏃�(1-31)
- if (month < 10) month = `0${month}`; // 濡傛灉鏈堜唤鏄釜浣嶆暟锛屽湪鍓嶉潰琛�0
- if (strDate < 10) strDate = `0${strDate}`; // 濡傛灉鏃ユ槸涓綅鏁帮紝鍦ㄥ墠闈㈣ˉ0
- return `${year}-${month}-${strDate}`;
-}
-
-function calculateinvoiceAmount() {
- console.log("productData", productData.value);
- var invoiceAmountTotal = 0;
- productData.value.forEach((item) => {
- if (item.ticketsAmount) {
- invoiceAmountTotal += item.ticketsAmount;
- }
- });
- form.value.invoiceAmount = invoiceAmountTotal.toFixed(2);
-}
-
-onMounted(() => {
- getList();
-});
-</script>
-
-<style scoped lang="scss"></style>
diff --git a/src/views/procurementManagement/paymentEntry/index.vue b/src/views/procurementManagement/paymentEntry/index.vue
index 02fda1a..5ee8d17 100644
--- a/src/views/procurementManagement/paymentEntry/index.vue
+++ b/src/views/procurementManagement/paymentEntry/index.vue
@@ -30,6 +30,7 @@
<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>-->
@@ -61,7 +62,6 @@
show-summary
v-loading="childrenLoading"
:summary-method="summarizeMainTable2"
- stripe
>
<el-table-column
align="center"
@@ -83,12 +83,15 @@
</el-table-column>
<el-table-column label="浠樻鏂瑰紡" prop="paymentMethod">
<template #default="scope">
- <el-input
+ <el-select
:disabled="!scope.row.editType"
v-model="scope.row.paymentMethod"
- placeholder="璇疯緭鍏�"
+ placeholder="璇烽�夋嫨"
clearable
- />
+ >
+ <el-option label="鐢垫眹" value="鐢垫眹" />
+ <el-option label="鎵垮厬" value="鎵垮厬" />
+ </el-select>
</template>
</el-table-column>
<el-table-column label="鐧昏浜�" prop="registrant" />
@@ -101,7 +104,6 @@
size="small"
@click="changeEditType(scope.row)"
v-if="!scope.row.editType"
- :disabled="scope.row.registrant !== userStore.nickName"
>缂栬緫</el-button
>
<el-button
@@ -110,7 +112,6 @@
size="small"
@click="saveReceiptPayment(scope.row)"
v-if="scope.row.editType"
- :disabled="scope.row.registrant !== userStore.nickName"
>淇濆瓨</el-button
>
<el-button
@@ -118,7 +119,6 @@
type="primary"
size="small"
@click="handleDelete(scope.row)"
- :disabled="scope.row.registrant !== userStore.nickName"
>鍒犻櫎</el-button
>
</template>
@@ -127,144 +127,101 @@
</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-input
- v-model="form.paymentMethod"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </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
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
+ <el-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 { ref } from "vue";
+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";
import useUserStore from "@/store/modules/user.js";
@@ -282,6 +239,7 @@
updatePaymentRegistration
} from "@/api/procurementManagement/procurementInvoiceLedger.js";
import useFormData from "@/hooks/useFormData";
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance();
const tableColumn = ref([
@@ -293,45 +251,60 @@
{
label: "閲囪喘鍚堝悓鍙�",
prop: "purchaseContractNumber",
+ width:160
},
{
label: "閿�鍞悎鍚屽彿",
prop: "salesContractNo",
+ width:160
},
{
label: "渚涘簲鍟嗗悕绉�",
prop: "supplierName",
width:240
},
- {
- label: "鍙戠エ鍙�",
- prop: "invoiceNumber",
- width:200
- },
- {
- label: "鍙戠エ閲戦(鍏�)",
- prop: "invoiceAmount",
- formatData: (params) => {
- return params ? parseFloat(params).toFixed(2) : 0;
- },
- },
+ {
+ label: "浠樻鐘舵��",
+ prop: "statusName",
+ width:110,
+ dataType: "tag",
+ formatType: (params) => {
+ if (params == '鏈畬鎴愪粯娆�') {
+ return "danger";
+ } else if (params == '宸插畬鎴愪粯娆�') {
+ return "success";
+ } else {
+ return null;
+ }
+ },
+ },
+ {
+ 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([]);
@@ -339,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,
@@ -359,8 +333,6 @@
purchaseLedgerId: "",
salesContractNo: "",
supplierName: "",
- invoiceNumber: "",
- invoiceAmount: "",
taxRate: "",
currentPaymentAmount: "",
paymentMethod: "",
@@ -377,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);
@@ -387,12 +356,21 @@
const isShowSummarySon = ref(true);
const expandedRowKeys = ref([]);
+const getStatusTagType = (statusName = '') => {
+ const normalized = statusName.trim();
+ 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 }, // 涓嶄繚鐣欏皬鏁�
@@ -421,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]);
@@ -480,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();
@@ -529,7 +525,7 @@
};
// 鍏抽棴寮规
const closeDia = () => {
- proxy.resetForm("formRef");
+ forms.value = [];
dialogFormVisible.value = false;
};
// 鍒犻櫎
@@ -541,7 +537,7 @@
})
.then(() => {
tableLoading.value = true;
- delPaymentRegistration(row.id)
+ delPaymentRegistration([row.id])
.then((res) => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
getList();
@@ -554,15 +550,24 @@
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}`;
-}
-getList();
+// 瀵煎嚭
+const handleExport = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/purchase/registration/exportOne", { ...searchForm, ...page }, "浠樻鐧昏.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+onMounted(() => {
+ getList();
+});
</script>
<style scoped lang="scss">
@@ -572,4 +577,9 @@
::v-deep(.el-checkbox__label) {
font-weight: bold;
}
+.empty-tip {
+ text-align: center;
+ padding: 20px 0;
+ color: #909399;
+}
</style>
diff --git a/src/views/procurementManagement/paymentHistory/index.vue b/src/views/procurementManagement/paymentHistory/index.vue
index 7c71979..179373b 100644
--- a/src/views/procurementManagement/paymentHistory/index.vue
+++ b/src/views/procurementManagement/paymentHistory/index.vue
@@ -1,6 +1,16 @@
<template>
<div class="app-container">
<el-form :model="searchForm" :inline="true">
+ <el-form-item label="閲囪喘鍚堝悓鍙�">
+ <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.searchText"
@@ -32,6 +42,14 @@
>
鎼滅储
</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">
@@ -47,15 +65,28 @@
: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>
<script setup>
-import { ref } from "vue";
+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";
@@ -94,6 +125,13 @@
label: "鐧昏鏃ユ湡",
prop: "registrationtDate",
},
+ {
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ width: 100,
+ align: "center",
+ },
]);
const tableData = ref([]);
const selectedRows = ref([]);
@@ -106,12 +144,10 @@
const total = ref(0);
const { form: searchForm } = useFormData({
searchText: undefined,
- paymentDate: [
- dayjs().startOf("month").format("YYYY-MM-DD"),
- dayjs().endOf("month").format("YYYY-MM-DD"),
- ],
- paymentDateStart: dayjs().startOf("month").format("YYYY-MM-DD"),
- paymentDateEnd: dayjs().endOf("month").format("YYYY-MM-DD"),
+ purchaseContractNumber: undefined,
+ paymentDate: [],
+ paymentDateStart: undefined,
+ paymentDateEnd: undefined,
});
// 鏌ヨ鍒楄〃
@@ -161,6 +197,68 @@
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;
+ proxy.download("/purchase/paymentRegistration/export", { ...rest, ...page }, "浠樻娴佹按.xlsx");
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/procurementManagement/paymentLedger/index.vue b/src/views/procurementManagement/paymentLedger/index.vue
index 2f001a5..7354adf 100644
--- a/src/views/procurementManagement/paymentLedger/index.vue
+++ b/src/views/procurementManagement/paymentLedger/index.vue
@@ -44,7 +44,7 @@
/>
<el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" />
<el-table-column
- label="鍙戠エ閲戦(鍏�)"
+ label="鍚堝悓閲戦(鍏�)"
prop="invoiceAmount"
show-overflow-tooltip
:formatter="formattedNumber"
@@ -84,6 +84,7 @@
:column="tableColumnSon"
:tableData="originalTableDataSon"
:isSelection="false"
+ :isShowPagination="false"
:tableLoading="tableLoadingSon"
:isShowSummary="isShowSummarySon"
:summaryMethod="summarizeMainTable1"
@@ -95,14 +96,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>
@@ -118,25 +111,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({
@@ -165,11 +139,16 @@
const tableColumnSon = ref([
{
label: "鍙戠敓鏃ユ湡",
- prop: "happenTime",
+ prop: "paymentDate",
width: 110,
},
{
- label: "鍙戠エ閲戦(鍏�)",
+ label: "閲囪喘鍚堝悓鍙�",
+ prop: "purchaseContractNumber",
+ width: 150,
+ },
+ {
+ label: "鍚堝悓閲戦(鍏�)",
prop: "invoiceAmount",
width: 200,
formatData: (params) => {
@@ -178,7 +157,7 @@
},
{
label: "浠樻閲戦(鍏�)",
- prop: "currentPaymentAmount",
+ prop: "paymentAmount",
width: 200,
formatData: (params) => {
return params ? parseFloat(params).toFixed(2) : 0;
@@ -215,7 +194,7 @@
const summarizeMainTable1 = (param) => {
let summarizeTable = proxy.summarizeTable(
param,
- ["invoiceAmount", "currentPaymentAmount"],
+ ["invoiceAmount", "paymentAmount"],
{
ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
@@ -246,8 +225,6 @@
paymentLedgerList({
...searchForm.value,
...page,
- detailPageNum: detailPageNum.value, // 鏂板
- detailPageSize: detailPageSize.value, // 鏂板
}).then((res) => {
let result = res.data;
tableLoading.value = false;
diff --git a/src/views/procurementManagement/priceManagement/index.vue b/src/views/procurementManagement/priceManagement/index.vue
new file mode 100644
index 0000000..76a39ed
--- /dev/null
+++ b/src/views/procurementManagement/priceManagement/index.vue
@@ -0,0 +1,273 @@
+<template>
+ <div class="app-container">
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="鍟嗗搧鍚嶇О锛�">
+ <el-input v-model="searchForm.productName" placeholder="璇疯緭鍏ュ晢鍝佸悕绉�" clearable />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細">
+ <el-input v-model="searchForm.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О" clearable />
+ </el-form-item>
+ <el-form-item label="浠锋牸鐘舵�侊細">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" clearable>
+ <el-option label="鏈夋晥" value="active" />
+ <el-option label="宸茶繃鏈�" value="expired" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <el-card class="table-card" shadow="never">
+ <div class="table-header">
+ <el-button type="primary" @click="openDialog('add')">鏂板浠锋牸</el-button>
+ <el-button type="success" @click="handleBatchUpdate">鎵归噺鏇存柊</el-button>
+ <el-button type="danger" @click="handleBatchDelete">鎵归噺鍒犻櫎</el-button>
+ </div>
+
+ <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="鍟嗗搧鍚嶇О" prop="productName" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specification" />
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" />
+ <el-table-column label="鍘熶环鏍�" prop="oldPrice" width="120">
+ <template #default="{ row }">楼{{ row.oldPrice.toFixed(2) }}</template>
+ </el-table-column>
+ <el-table-column label="鏂颁环鏍�" prop="newPrice" width="120">
+ <template #default="{ row }">楼{{ row.newPrice.toFixed(2) }}</template>
+ </el-table-column>
+ <el-table-column label="璋冧环骞呭害" prop="priceChange" width="120">
+ <template #default="{ row }">
+ <span :style="{ color: row.priceChange >= 0 ? '#f56c6c' : '#67c23a' }">
+ {{ row.priceChange >= 0 ? '+' : '' }}{{ row.priceChange.toFixed(2) }}%
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢熸晥鏃堕棿" prop="effectiveTime" width="180" />
+ <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="鎿嶄綔" width="200" align="center">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="openDialog('edit', row)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleApply(row)" v-if="row.status === 'pending'">搴旂敤</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <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%">
+ <el-option label="鍟嗗搧A" value="鍟嗗搧A" />
+ <el-option label="鍟嗗搧B" value="鍟嗗搧B" />
+ <el-option label="鍟嗗搧C" value="鍟嗗搧C" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="瑙勬牸鍨嬪彿">
+ <el-input v-model="formData.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗗悕绉�">
+ <el-select v-model="formData.supplierName" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%">
+ <el-option label="渚涘簲鍟咥" value="渚涘簲鍟咥" />
+ <el-option label="渚涘簲鍟咮" value="渚涘簲鍟咮" />
+ <el-option label="渚涘簲鍟咰" value="渚涘簲鍟咰" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍘熶环鏍�">
+ <el-input v-model="formData.oldPrice" placeholder="璇疯緭鍏ュ師浠锋牸" type="number" />
+ </el-form-item>
+ <el-form-item label="鏂颁环鏍�">
+ <el-input v-model="formData.newPrice" placeholder="璇疯緭鍏ユ柊浠锋牸" type="number" />
+ </el-form-item>
+ <el-form-item label="鐢熸晥鏃堕棿">
+ <el-date-picker v-model="formData.effectiveTime" type="datetime" placeholder="閫夋嫨鐢熸晥鏃堕棿" style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="璋冧环鍘熷洜">
+ <el-select v-model="formData.reason" placeholder="璇烽�夋嫨璋冧环鍘熷洜" style="width: 100%">
+ <el-option label="甯傚満浠锋牸鍙樺姩" value="market" />
+ <el-option label="鎴愭湰鍙樺寲" value="cost" />
+ <el-option label="渚涘簲鍟嗚皟鏁�" value="supplier" />
+ <el-option label="鍏朵粬鍘熷洜" value="other" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞">
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import FormDialog from '@/components/Dialog/FormDialog.vue';
+import { ref, reactive, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const loading = ref(false)
+const dialogVisible = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+
+const searchForm = reactive({
+ productName: '',
+ supplierName: '',
+ status: ''
+})
+
+const formData = reactive({
+ productName: '',
+ specification: '',
+ supplierName: '',
+ oldPrice: 0,
+ newPrice: 0,
+ effectiveTime: '',
+ reason: '',
+ remark: ''
+})
+
+const mockData = [
+ {
+ id: 1,
+ productName: '鍟嗗搧A',
+ specification: '瑙勬牸1',
+ supplierName: '渚涘簲鍟咥',
+ oldPrice: 50.00,
+ newPrice: 55.00,
+ priceChange: 10.00,
+ effectiveTime: '2025-12-01 00:00:00',
+ status: 'active',
+ reason: '甯傚満浠锋牸鍙樺姩',
+ remark: '甯傚満浠锋牸涓婃定'
+ },
+ {
+ id: 2,
+ productName: '鍟嗗搧B',
+ specification: '瑙勬牸2',
+ supplierName: '渚涘簲鍟咮',
+ oldPrice: 80.00,
+ newPrice: 75.00,
+ priceChange: -6.25,
+ effectiveTime: '2025-12-01 00:00:00',
+ status: 'active',
+ reason: '鎴愭湰鍙樺寲',
+ remark: '鎴愭湰涓嬮檷'
+ }
+]
+
+const tableData = ref([...mockData])
+
+const getStatusType = (status) => {
+ const statusMap = { active: 'success', expired: 'info', pending: 'warning' }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = { active: '鏈夋晥', expired: '宸茶繃鏈�', pending: '寰呯敓鏁�' }
+ return statusMap[status] || '鏈煡'
+}
+
+const handleSearch = () => {
+ loading.value = true
+ setTimeout(() => { loading.value = false }, 500)
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, { productName: '', supplierName: '', status: '' })
+}
+
+const openDialog = (type, row = {}) => {
+ dialogType.value = type
+ if (type === 'edit' && row.id) {
+ Object.assign(formData, {
+ productName: row.productName,
+ specification: row.specification,
+ supplierName: row.supplierName,
+ oldPrice: row.oldPrice,
+ newPrice: row.newPrice,
+ effectiveTime: row.effectiveTime,
+ reason: row.reason,
+ remark: row.remark
+ })
+ } else {
+ Object.assign(formData, {
+ productName: '',
+ specification: '',
+ supplierName: '',
+ oldPrice: 0,
+ newPrice: 0,
+ effectiveTime: '',
+ reason: '',
+ remark: ''
+ })
+ }
+ dialogVisible.value = true
+}
+
+const handleSubmit = () => {
+ if (dialogType.value === 'add') {
+ const priceChange = ((formData.newPrice - formData.oldPrice) / formData.oldPrice) * 100
+ const newPrice = {
+ id: Date.now(),
+ productName: formData.productName,
+ specification: formData.specification,
+ supplierName: formData.supplierName,
+ oldPrice: formData.oldPrice,
+ newPrice: formData.newPrice,
+ priceChange: priceChange,
+ effectiveTime: formData.effectiveTime,
+ status: 'pending',
+ reason: formData.reason,
+ remark: formData.remark
+ }
+ tableData.value.unshift(newPrice)
+ ElMessage.success('鏂板鎴愬姛')
+ }
+ dialogVisible.value = false
+}
+
+const handleApply = (row) => {
+ row.status = 'active'
+ ElMessage.success('浠锋牸宸插簲鐢�')
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = tableData.value.findIndex(item => item.id === row.id)
+ if (index !== -1) {
+ tableData.value.splice(index, 1)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ }
+ })
+}
+
+const handleBatchUpdate = () => {
+ ElMessage.success('鎵归噺鏇存柊鎴愬姛')
+}
+
+const handleBatchDelete = () => {
+ ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+}
+
+const handleSelectionChange = (rows) => {
+ selectedRows.value = rows
+}
+</script>
+
+<style scoped>
+.app-container { padding: 20px; }
+.search-card { margin-bottom: 20px; }
+.table-card { margin-bottom: 20px; }
+.table-header { margin-bottom: 20px; }
+</style>
diff --git a/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue b/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
index 6290804..7f8b852 100644
--- a/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
+++ b/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
@@ -1,196 +1,148 @@
<template>
- <el-form :model="form">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="閲囪喘鍚堝悓鍙凤細">
- <el-tag size="large">{{ form.purchaseContractNumber }}</el-tag>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="閿�鍞悎鍚屽彿锛�">
- <el-text>{{ form.salesContractNo }}</el-text>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍚◣鍗曚环(鍏�)锛�">
- <el-text type="primary">{{ form.taxInclusiveUnitPrice }}</el-text>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍒涘缓鏃堕棿锛�">
- <el-text>{{ form.createdAt }}</el-text>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍙戠エ鍙凤細">
- <el-input v-model="form.invoiceNumber" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏉ョエ鏁帮細">
- <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsNum" @change="inputTicketsNum" :precision="2"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏈鏉ョエ閲戦(鍏�)锛�">
- <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsAmount" @change="inputTicketsAmount" :precision="2"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏈潵绁ㄦ暟锛�">
- <el-text type="success">{{ form.futureTickets }}</el-text>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
+ <el-form :model="form">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閲囪喘鍚堝悓鍙凤細">
+ <el-tag size="large">{{ form.purchaseContractNumber }}</el-tag>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�">
+ <el-text>{{ form.salesContractNo }}</el-text>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�">
+ <el-text type="primary">{{ form.taxInclusiveUnitPrice }}</el-text>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍒涘缓鏃堕棿锛�">
+ <el-text>{{ form.createdAt }}</el-text>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙凤細">
+ <el-input disabled
+ v-model="form.invoiceNumber" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏉ョエ鏁帮細">
+ <el-input-number :step="0.1"
+ :min="0"
+ style="width: 100%"
+ v-model="form.ticketsNum"
+ @change="inputTicketsNum"
+ :precision="2" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈鏉ョエ閲戦(鍏�)锛�">
+ <el-input-number :step="0.1"
+ :min="0"
+ style="width: 100%"
+ v-model="form.ticketsAmount"
+ @change="inputTicketsAmount"
+ :precision="2" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈潵绁ㄦ暟锛�">
+ <el-text type="success">{{ form.futureTickets }}</el-text>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
</template>
<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger";
-const { proxy } = getCurrentInstance()
+ import useFormData from "@/hooks/useFormData";
+ import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger";
+ const { proxy } = getCurrentInstance();
-defineOptions({
- name: "鏉ョエ鍙拌处琛ㄥ崟",
-});
-const temFutureTickets = ref(0) // 鍒濆鏈潵绁ㄦ暟
-const initialTicketsNum = ref(0) // 鍒濆鏉ョエ鏁�
-const initialTicketsAmount = ref(0) // 鍒濆鏉ョエ閲戦
-const quantity = ref(0) // 鎬绘暟閲�
-const { form, resetForm } = useFormData({
- id: undefined,
- purchaseContractNumber: undefined, // 閲囪喘鍚堝悓鍙�
- salesContractNo: undefined, // 閿�鍞悎鍚屽彿
- createdAt: undefined, // 鍒涘缓鏃堕棿
- invoiceNumber: undefined, // 鍙戠エ鍙�
- ticketsNum: undefined, // 鏉ョエ鏁�
- ticketsAmount: undefined, // 鏉ョエ閲戦
- taxInclusiveUnitPrice: undefined, // 鍚◣鍗曚环
- ticketRegistrationId: undefined, // 鍚◣鍗曚环
-});
+ defineOptions({
+ name: "鏉ョエ鍙拌处琛ㄥ崟",
+ });
+ const temFutureTickets = ref(0);
+ const { form, resetForm } = useFormData({
+ id: undefined,
+ purchaseContractNumber: undefined, // 閲囪喘鍚堝悓鍙�
+ salesContractNo: undefined, // 閿�鍞悎鍚屽彿
+ createdAt: undefined, // 鍒涘缓鏃堕棿
+ invoiceNumber: undefined, // 鍙戠エ鍙�
+ ticketsNum: undefined, // 鏉ョエ鏁�
+ ticketsAmount: undefined, // 鏉ョエ閲戦
+ taxInclusiveUnitPrice: undefined, // 鍚◣鍗曚环
+ });
-const load = async (id) => {
- const { code, data } = await getProductRecordById({ id });
- if (code === 200) {
- form.id = data.id;
- form.purchaseContractNumber = data.purchaseContractNumber;
- form.salesContractNo = data.salesContractNo;
- form.createdAt = data.createdAt;
- form.invoiceNumber = data.invoiceNumber;
- form.ticketsNum = data.ticketsNum;
- form.ticketsAmount = data.ticketsAmount ? Number(data.ticketsAmount).toFixed(2) : 0;
- form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
- form.futureTickets = data.futureTickets;
- temFutureTickets.value = data.futureTickets;
- initialTicketsNum.value = data.ticketsNum || 0;
- initialTicketsAmount.value = data.ticketsAmount || 0;
- form.ticketRegistrationId = data.ticketRegistrationId;
- // 鑾峰彇鎬绘暟閲忥紝濡傛灉鏁版嵁涓湁 quantity 瀛楁鍒欎娇鐢紝鍚﹀垯浣跨敤鏉ョエ鏁�+鏈潵绁ㄦ暟
- quantity.value = data.quantity || (Number(data.ticketsNum || 0) + Number(data.futureTickets || 0));
- }
-};
+ const load = async (id, purchaseLedgerId, productModelId) => {
+ const { code, data } = await getProductRecordById({
+ id: id,
+ purchaseLedgerId: purchaseLedgerId,
+ productModelId: productModelId,
+ });
+ if (code === 200) {
+ form.id = data.id;
+ form.purchaseContractNumber = data.purchaseContractNumber;
+ form.salesContractNo = data.salesContractNo;
+ form.createdAt = data.createdAt;
+ form.invoiceNumber = data.invoiceNumber;
+ form.ticketsNum = data.ticketsNum;
+ form.ticketsAmount = data.ticketsAmount.toFixed(2);
+ form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
+ form.futureTickets = data.futureTickets;
+ temFutureTickets.value = data.futureTickets;
+ }
+ };
-const inputTicketsNum = (val) => {
- // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
- if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
- proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
- return;
- }
-
- const newTicketsNum = Number(form.ticketsNum) || 0;
- const currentTicketsNum = Number(initialTicketsNum.value) || 0;
-
- // 璁$畻鏂板鐨勬潵绁ㄦ暟
- const addedTicketsNum = newTicketsNum - currentTicketsNum;
-
- // 璁$畻鏂扮殑鏈潵绁ㄦ暟 = 鍒濆鏈潵绁ㄦ暟 - 鏂板鐨勬潵绁ㄦ暟
- const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
-
- // 楠岃瘉锛氭柊鐨勬潵绁ㄦ暟 + 鏂扮殑鏈潵绁ㄦ暟 鈮� quantity
- if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
- proxy.$modal.msgWarning(`鏉ョエ鏁�+鏈潵绁ㄦ暟涓嶈兘澶т簬鎬绘暟閲�(${quantity.value})`);
- // 闄愬埗鏉ョエ鏁帮紝浣垮叾婊¤冻锛氭潵绁ㄦ暟 + 鏈潵绁ㄦ暟 鈮� quantity
- // 鏈�澶ф潵绁ㄦ暟 = quantity - 鍒濆鏈潵绁ㄦ暟 + 鍒濆鏉ョエ鏁�
- const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
- form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
- // 閲嶆柊璁$畻
- const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
- const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
- form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
- const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
- form.ticketsAmount = Number(ticketsAmount.toFixed(2));
- return;
- }
-
- // 妫�鏌ユ柊澧炵殑鏉ョエ鏁版槸鍚﹀ぇ浜庡垵濮嬫湭鏉ョエ鏁�
- if (addedTicketsNum > Number(temFutureTickets.value)) {
- proxy.$modal.msgWarning("鏂板寮�绁ㄦ暟涓嶅緱澶т簬鏈紑绁ㄦ暟");
- form.ticketsNum = Number(initialTicketsNum.value) + Number(temFutureTickets.value);
- }
-
- // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
- const finalTicketsNum = Number(form.ticketsNum) || 0;
- const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
- const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
- const ticketsAmount = finalTicketsNum * Number(form.taxInclusiveUnitPrice);
- form.futureTickets = Number(finalFutureTickets.toFixed(2));
- form.ticketsAmount = Number(ticketsAmount.toFixed(2));
-};
-const inputTicketsAmount = (val) => {
- // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
- if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
- proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
- return;
- }
-
- const newTicketsAmount = Number(val) || 0;
-
- // 璁$畻鏂扮殑鏉ョエ鏁�
- const newTicketsNum = newTicketsAmount / Number(form.taxInclusiveUnitPrice);
- const currentTicketsNum = Number(initialTicketsNum.value) || 0;
-
- // 璁$畻鏂板鐨勬潵绁ㄦ暟
- const addedTicketsNum = newTicketsNum - currentTicketsNum;
-
- // 璁$畻鏂扮殑鏈潵绁ㄦ暟 = 鍒濆鏈潵绁ㄦ暟 - 鏂板鐨勬潵绁ㄦ暟
- const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
-
- // 楠岃瘉锛氭柊鐨勬潵绁ㄦ暟 + 鏂扮殑鏈潵绁ㄦ暟 鈮� quantity
- if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
- proxy.$modal.msgWarning(`鏉ョエ鏁�+鏈潵绁ㄦ暟涓嶈兘澶т簬鎬绘暟閲�(${quantity.value})`);
- // 闄愬埗鏉ョエ鏁帮紝浣垮叾婊¤冻锛氭潵绁ㄦ暟 + 鏈潵绁ㄦ暟 鈮� quantity
- const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
- form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
- form.ticketsAmount = Number((form.ticketsNum * Number(form.taxInclusiveUnitPrice)).toFixed(2));
- const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
- const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
- form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
- return;
- }
-
- // 妫�鏌ユ柊澧炵殑鏉ョエ閲戦鏄惁澶т簬鍒濆鏈潵绁ㄦ暟瀵瑰簲鐨勯噾棰�
- const maxAddedAmount = Number(temFutureTickets.value * form.taxInclusiveUnitPrice);
- if (addedTicketsNum > 0 && addedTicketsNum * Number(form.taxInclusiveUnitPrice) > maxAddedAmount) {
- proxy.$modal.msgWarning("鏂板鏉ョエ閲戦涓嶅緱澶т簬鏈紑绁ㄩ噾棰�");
- form.ticketsAmount = Number((initialTicketsAmount.value + maxAddedAmount).toFixed(2));
- form.ticketsNum = Number((currentTicketsNum + Number(temFutureTickets.value)).toFixed(2));
- form.futureTickets = 0;
- return;
- }
-
- // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
- const finalTicketsNum = Number(newTicketsNum.toFixed(2));
- const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
- const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
- form.ticketsNum = finalTicketsNum;
- form.futureTickets = Number(finalFutureTickets.toFixed(2));
-};
+ const inputTicketsNum = val => {
+ // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
+ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
+ proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
+ return;
+ }
+ if (Number(form.ticketsNum) > Number(temFutureTickets.value)) {
+ proxy.$modal.msgWarning("寮�绁ㄦ暟涓嶅緱澶т簬鏈紑绁ㄦ暟");
+ form.ticketsNum = temFutureTickets.value;
+ }
-defineExpose({
- load,
- form,
- resetForm,
-});
+ // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
+ const ticketsAmount =
+ Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
+ const futureTickets =
+ Number(temFutureTickets.value) - Number(form.ticketsNum);
+ form.futureTickets = Number(futureTickets.toFixed(2));
+ form.ticketsAmount = Number(ticketsAmount.toFixed(2));
+ };
+ const inputTicketsAmount = val => {
+ // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
+ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
+ proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
+ return;
+ }
+
+ if (Number(val) > Number(form.futureTickets * form.taxInclusiveUnitPrice)) {
+ proxy.$modal.msgWarning("鏈鏉ョエ閲戦涓嶅緱澶т簬鎬婚噾棰�");
+ form.ticketsAmount = (
+ form.futureTickets * form.taxInclusiveUnitPrice
+ ).toFixed(2);
+ const ticketsNum =
+ Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice);
+ form.ticketsNum = Number(ticketsNum.toFixed(2));
+ return;
+ }
+
+ // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
+ const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice);
+ form.ticketsNum = Number(ticketsNum.toFixed(2));
+ };
+
+ defineExpose({
+ load,
+ form,
+ resetForm,
+ });
</script>
<style lang="scss" scoped></style>
diff --git a/src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue b/src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue
index 82b4164..68f3650 100644
--- a/src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue
+++ b/src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue
@@ -1,62 +1,66 @@
<template>
- <el-dialog :title="modalOptions.title" v-model="visible" @close="close">
+ <el-dialog :title="modalOptions.title"
+ v-model="visible"
+ @close="close">
<EditForm ref="editFormRef" />
<template #footer>
- <el-button type="primary" :loading="loading" @click="sendForm">
- {{ modalOptions.confirmText }}
- </el-button>
+ <el-button type="primary"
+ :loading="loading"
+ @click="sendForm">
+ {{ modalOptions.confirmText }}
+ </el-button>
<el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
</template>
</el-dialog>
</template>
<script setup>
-import { useModal } from "@/hooks/useModal";
-import EditForm from "../Form/EditForm.vue";
-import { updateRegistration } from "@/api/procurementManagement/procurementInvoiceLedger";
-import { ElMessage } from "element-plus";
+ import { useModal } from "@/hooks/useModal";
+ import EditForm from "../Form/EditForm.vue";
+ import { updateRegistration } from "@/api/procurementManagement/procurementInvoiceLedger";
+ import { ElMessage } from "element-plus";
-defineOptions({
- name: "鏉ョエ鍙拌处缂栬緫",
-});
-const emits = defineEmits(["success"]);
+ defineOptions({
+ name: "鏉ョエ鍙拌处缂栬緫",
+ });
+ const emits = defineEmits(["success"]);
-const saleLedgerProjectId = ref('')
-const editFormRef = ref();
-const {
- id,
- visible,
- loading,
- openModal,
- modalOptions,
- handleConfirm,
- closeModal,
-} = useModal({ title: "鏉ョエ鍙拌处" });
+ const saleLedgerProjectId = ref("");
+ const editFormRef = ref();
+ const {
+ id,
+ visible,
+ loading,
+ openModal,
+ modalOptions,
+ handleConfirm,
+ closeModal,
+ } = useModal({ title: "鏉ョエ鍙拌处" });
-const open = async (row) => {
- openModal(row.id);
- saleLedgerProjectId.value = row.saleLedgerProjectId;
- await nextTick();
- editFormRef.value.load(row.id);
-};
+ const open = async row => {
+ openModal(row.id);
+ saleLedgerProjectId.value = row.saleLedgerProjectId;
+ await nextTick();
+ editFormRef.value.load(row.id, row.purchaseLedgerId, row.productModelId);
+ };
-const close = () => {
- editFormRef.value.resetForm();
- closeModal();
-};
+ const close = () => {
+ editFormRef.value.resetForm();
+ closeModal();
+ };
-const sendForm = async () => {
- const form = editFormRef.value.form;
- form.saleLedgerProjectId = saleLedgerProjectId.value;
- const { code } = await updateRegistration(form);
- if (code === 200) {
- emits("success");
- ElMessage({ message: "鎿嶄綔鎴愬姛", type: "success" });
- close();
- }
-};
+ const sendForm = async () => {
+ const form = editFormRef.value.form;
+ form.saleLedgerProjectId = saleLedgerProjectId.value;
+ const { code } = await updateRegistration(form);
+ if (code === 200) {
+ emits("success");
+ ElMessage({ message: "鎿嶄綔鎴愬姛", type: "success" });
+ close();
+ }
+ };
-defineExpose({
- open,
-});
+ defineExpose({
+ open,
+ });
</script>
diff --git a/src/views/procurementManagement/procurementInvoiceLedger/index.vue b/src/views/procurementManagement/procurementInvoiceLedger/index.vue
index 8ad8799..b33e3e4 100644
--- a/src/views/procurementManagement/procurementInvoiceLedger/index.vue
+++ b/src/views/procurementManagement/procurementInvoiceLedger/index.vue
@@ -82,14 +82,12 @@
type="primary"
text
@click="openEdit(row)"
- :disabled="row.issUerId !== userStore.id"
>
缂栬緫
</el-button>
<el-button
type="primary"
text
- :disabled="row.issUerId !== userStore.id"
@click="handleDelete(row)"
>
鍒犻櫎
@@ -172,11 +170,6 @@
width: 240,
},
{
- label: "浜у搧澶х被",
- prop: "productCategory",
- width: 150,
- },
- {
label: "瑙勬牸鍨嬪彿",
prop: "specificationModel",
width: 150,
@@ -195,17 +188,12 @@
},
},
{
- label: "鏈鏉ョエ鏁�",
- prop: "ticketsNum",
- width: 110,
- },
- {
- label: "鏉ョエ鏃ユ湡",
+ label: "寮�绁ㄦ棩鏈�",
prop: "createdAt",
width: 110,
},
{
- label: "鏉ョエ閲戦(鍏�)",
+ label: "寮�绁ㄩ噾棰�",
prop: "ticketsAmount",
width: 200,
formatData: (cell) => {
diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
index a9f2802..9b86624 100644
--- a/src/views/procurementManagement/procurementLedger/index.vue
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -2,404 +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 - 19em)"
- >
- <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"
- show-overflow-tooltip
- />
- <el-table-column
- label="渚涘簲鍟嗗悕绉�"
- prop="supplierName"
- show-overflow-tooltip
- />
- <el-table-column
- label="椤圭洰鍚嶇О"
- prop="projectName"
- width="420"
- 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="100"
- show-overflow-tooltip
- />
- <el-table-column
- label="褰曞叆鏃ユ湡"
- prop="entryDate"
- width="100"
- show-overflow-tooltip
- />
- <el-table-column
- fixed="right"
- label="鎿嶄綔"
- min-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-button
- link
- type="primary"
- size="small"
- @click="openForm('edit', scope.row)"
- >缂栬緫</el-button
- >
- <el-button
- link
- type="primary"
- size="small"
- @click="downLoadFile(scope.row)"
- >闄勪欢</el-button
- >
-
+ <el-tag v-if="scope.row.isInvalid"
+ type="danger"
+ size="small">澶辨晥</el-tag>
+ <el-tag v-else
+ type="success"
+ size="small">姝e父</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>
+ <template #default="scope">
+ <el-tag size="small">
+ {{ approvalStatusText[scope.row.approvalStatus] || '鏈煡鐘舵��' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="绛捐鏃ユ湡"
+ prop="executionDate"
+ width="100"
+ show-overflow-tooltip />
+ <el-table-column label="浠樻鏂瑰紡"
+ width="100"
+ prop="paymentMethod"
+ show-overflow-tooltip />
+ <el-table-column label="鍚堝悓閲戦(鍏�)"
+ prop="contractAmount"
+ width="200"
+ show-overflow-tooltip
+ :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="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="salesContractNo">
- <el-input
- v-model="form.salesContractNo"
- placeholder="璇疯緭鍏�"
- clearable
- />
-<!-- <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 label="閿�鍞悎鍚屽彿锛�"
+ prop="salesLedgerId">
+ <el-select v-model="form.salesLedgerId"
+ placeholder="璇烽�夋嫨"
+ filterable
+ clearable
+ @change="salesLedgerChange">
+ <el-option v-for="item in salesContractList"
+ :key="item.id"
+ :label="item.salesContractNo"
+ :value="item.id" />
+ </el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierId">
- <el-select
- v-model="form.supplierId"
- placeholder="璇烽�夋嫨"
- filterable
- clearable
- >
- <el-option
- v-for="item in supplierList"
- :key="item.id"
- :label="item.supplierName"
- :value="item.id"
- />
+ <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-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>
- </el-col>
- </el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="褰曞叆浜猴細" prop="recorderId">
- <el-select
- v-model="form.recorderId"
- placeholder="璇烽�夋嫨"
- clearable
- disabled
- filterable
- >
- <el-option
- v-for="item in userList"
- :key="item.userId"
- :label="item.nickName"
- :value="item.userId"
- />
+ <el-form-item label="浠樻鏂瑰紡">
+ <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>
+ </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-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-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>
+ </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>
+ </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">
@@ -414,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>
- </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-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>
- </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>
- </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="unit">
+ <el-input v-model="productForm.unit"
+ placeholder="璇疯緭鍏�"
+ clearable />
</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>
- </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="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-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>
- </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"
+ :min="0"
+ clearable
+ style="width: 100%"
+ @change="mathNum" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏁伴噺锛�"
+ prop="quantity">
+ <el-input-number :step="0.1"
+ clearable
+ :precision="2"
+ :min="0"
+ style="width: 100%"
+ v-model="productForm.quantity"
+ placeholder="璇疯緭鍏�"
+ @change="mathNum" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鎬讳环(鍏�)锛�"
+ prop="taxInclusiveTotalPrice">
+ <el-input-number v-model="productForm.taxInclusiveTotalPrice"
+ :precision="2"
+ :step="0.1"
+ :min="0"
+ clearable
+ style="width: 100%"
+ @change="reverseMathNum('taxInclusiveTotalPrice')" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓嶅惈绋庢�讳环(鍏�)锛�"
+ prop="taxExclusiveTotalPrice">
+ <el-input-number v-model="productForm.taxExclusiveTotalPrice"
+ :precision="2"
+ :step="0.1"
+ :min="0"
+ clearable
+ style="width: 100%"
+ @change="reverseMathNum('taxExclusiveTotalPrice')" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷锛�"
+ prop="invoiceType">
+ <el-select v-model="productForm.invoiceType"
+ placeholder="璇烽�夋嫨"
+ clearable>
+ <el-option label="澧炴櫘绁�"
+ value="澧炴櫘绁�" />
+ <el-option label="澧炰笓绁�"
+ value="澧炰笓绁�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="搴撳瓨棰勮鏁伴噺锛�"
+ prop="warnNum">
+ <el-input-number v-model="productForm.warnNum"
+ :precision="2"
+ :step="0.1"
+ :min="0"
+ clearable
+ style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鏄惁璐ㄦ锛�"
+ prop="isChecked">
+ <el-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">
@@ -756,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>
@@ -787,484 +832,790 @@
</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" />
+ <FileList ref="fileListRef" />
</div>
</template>
<script setup>
-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 } 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,
- delPurchase,
- getSalesNo,
- purchaseListPage,
- productList,
- getPurchaseById,
- getOptions,
- createPurchaseNo,
-} 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([]);
-import useUserStore from "@/store/modules/user";
-import { modelList, productTreeList } from "@/api/basicData/product.js";
-import dayjs from "dayjs";
+ 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 userStore = useUserStore();
+ 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 qrCodeDialogVisible = ref(false);
-const qrCodeUrl = ref("");
+ const userStore = useUserStore();
-// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
-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" },
+ // 浜岀淮鐮佺浉鍏冲彉閲�
+ const qrCodeDialogVisible = ref(false);
+ const qrCodeUrl = ref("");
+
+ // 璁㈠崟瀹℃壒鐘舵�佹樉绀烘枃鏈�
+ 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: "",
+ 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"),
],
- 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);
+ 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: "",
- },
- 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 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 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 summarizeChildrenTable = (param) => {
- return proxy.summarizeTable(
- param,
- [
+ 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: "璇疯緭鍏ユā鏉垮悕绉�",
+ 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",
+ });
+ }
+ };
+ // 瀛愯〃鍚堣鏂规硶
+ const summarizeChildrenTable = param => {
+ return proxy.summarizeTable(
+ param,
+ [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ "ticketsNum",
+ "ticketsAmount",
+ "futureTickets",
+ "futureTicketsAmount",
+ ],
+ {
+ 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 => {
+ tableLoading.value = false;
+ // tableData.value = res.data.records;
+ tableData.value = res.data.records.map(record => ({
+ ...record,
+ isInvalid: record.isWhite === 1,
+ }));
+ // 鍒濆鍖栧瓙鏁版嵁鏁扮粍
+ tableData.value.forEach(item => {
+ item.children = [];
+ });
+ total.value = res.data.total;
+ expandedRowKeys.value = [];
+ })
+ .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",
- "ticketsNum",
- "ticketsAmount",
- "futureTickets",
- "futureTicketsAmount",
- ],
- {
- 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) => {
- tableLoading.value = false;
- tableData.value = res.data.records;
- tableData.value.map((item) => {
- item.children = [];
- });
- total.value = res.data.total;
- expandedRowKeys.value = [];
- })
- .catch(() => {
- tableLoading.value = false;
- });
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-const productSelected = (selectedRows) => {
- productSelectedRows.value = selectedRows;
-};
-const expandedRowKeys = ref([]);
-// 灞曞紑琛�
-const expandChange = (row, expandedRows) => {
- if (expandedRows.length > 0) {
- expandedRowKeys.value = [];
+ ]);
+ };
+ // 鎵撳紑寮规
+ const openForm = async (type, row) => {
+ await getTemplateList();
+ operationType.value = type;
+ form.value = {};
+ productData.value = [];
+ fileList.value = [];
+ templateName.value = "";
+ filterInputValue.value = "";
+ isTemplateNameDuplicate.value = false;
try {
- productList({ salesLedgerId: row.id, type: 2 }).then((res) => {
- const index = tableData.value.findIndex((item) => item.id === row.id);
- if (index > -1) {
- tableData.value[index].children = res.data;
+ // 骞惰鍔犺浇鍩虹鏁版嵁
+ 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") {
+ // 鏂板鏃剁敓鎴愰噰璐悎鍚屽彿
+ try {
+ const purchaseNoRes = await createPurchaseNo();
+ if (purchaseNoRes?.data) {
+ form.value.purchaseContractNumber = purchaseNoRes.data;
+ }
+ } catch (error) {
+ console.error("鐢熸垚閲囪喘鍚堝悓鍙峰け璐�:", error);
+ proxy.$modal.msgWarning("鐢熸垚閲囪喘鍚堝悓鍙峰け璐�");
}
- expandedRowKeys.value.push(row.id);
- });
+ } 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.log(error);
+ console.error("鎵撳紑琛ㄥ崟澶辫触:", error);
+ proxy.$modal.msgError("鍔犺浇鍩虹鏁版嵁澶辫触");
}
- } else {
- expandedRowKeys.value = [];
- }
-};
-// 涓昏〃鍚堣鏂规硶
-const summarizeMainTable = (param) => {
- return proxy.summarizeTable(param, ["contractAmount"]);
-};
-// 瀛愯〃鍚堣鏂规硶
-const summarizeProTable = (param) => {
- return proxy.summarizeTable(param, [
- "taxInclusiveUnitPrice",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- ]);
-};
-// 鎵撳紑寮规
-const openForm = (type, row) => {
- operationType.value = type;
- form.value = {};
- productData.value = [];
- fileList.value = [];
- if (operationType.value == "add") {
- createPurchaseNo().then((res) => {
- form.value.purchaseContractNumber = res.data;
- });
- }
- userListNoPage().then((res) => {
- userList.value = res.data;
- });
- getSalesNo().then((res) => {
- salesContractList.value = res;
- });
- getOptions().then((res) => {
- supplierList.value = res.data;
- });
- form.value.recorderId = userStore.id;
- form.value.entryDate = getCurrentDate();
- if (type === "edit") {
- currentId.value = row.id;
- getPurchaseById({ id: row.id, type: 2 }).then((res) => {
- form.value = { ...res };
- productData.value = form.value.productData;
- if (form.value.salesLedgerFiles) {
- fileList.value = form.value.salesLedgerFiles;
- } else {
- fileList.value = [];
- }
- });
- }
- dialogFormVisible.value = true;
-};
-// 涓婁紶鍓嶆牎妫�
-function handleBeforeUpload(file) {
- // 鏍℃鏂囦欢澶у皬
- if (file.size > 1024 * 1024 * 10) {
- proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃10MB!");
- return false;
- }
- proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
- 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);
- }
-}
-// 绉婚櫎鏂囦欢
-function handleRemove(file) {
- console.log("handleRemove", file.id);
- if (file.size > 1024 * 1024 * 10) {
- // 浠呭墠绔竻鐞嗭紝涓嶈皟鐢ㄥ垹闄ゆ帴鍙e拰鎻愮ず
- return;
- }
- if (operationType.value === "edit") {
- let ids = [];
- ids.push(file.id);
- delLedgerFile(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- });
- }
-}
-// 鎻愪氦琛ㄥ崟
-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;
- addOrEditPurchase(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
+ };
+ // 涓婁紶鍓嶆牎妫�
+ function handleBeforeUpload(file) {
+ // 鏍℃鏂囦欢澶у皬
+ if (file.size > 1024 * 1024 * 10) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃10MB!");
+ return false;
}
- });
-};
-// 鍏抽棴寮规
-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 };
+ proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
+ return true;
}
- 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) || "";
- productForm.value.productId = value;
- modelList({ id: value }).then((res) => {
- modelOptions.value = res;
- });
- } else {
- productForm.value.productCategory = "";
- modelOptions.value = [];
+ // 涓婁紶澶辫触
+ function handleUploadError(err) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢澶辫触");
+ proxy.$modal.closeLoading();
}
-};
-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; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣鐨刲abel
- }
- if (nodes[i].children && nodes[i].children.length > 0) {
- const foundNode = findNodeById(nodes[i].children, productId);
- if (foundNode) {
- return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝鐩存帴杩斿洖锛堝凡缁忔槸label瀛楃涓诧級
- }
+ // 涓婁紶鎴愬姛鍥炶皟
+ 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);
}
}
- return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
-};
-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);
+ // 绉婚櫎鏂囦欢
+ async function handleRemove(file) {
+ if (!file?.id) {
+ return;
+ }
+ console.log("handleRemove", file.id);
+ if (file.size > 1024 * 1024 * 10) {
+ // 浠呭墠绔竻鐞嗭紝涓嶈皟鐢ㄥ垹闄ゆ帴鍙e拰鎻愮ず
+ return;
}
- 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();
+ if (operationType.value === "edit" && file.id) {
+ try {
+ await delLedgerFile([file.id]);
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ } catch (error) {
+ console.error("鍒犻櫎鏂囦欢澶辫触:", error);
+ proxy.$modal.msgError("鍒犻櫎鏂囦欢澶辫触");
}
}
- });
-};
-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;
}
- // 淇濊瘉鑷冲皯淇濈暀涓�鏉′骇鍝佷俊鎭�
- const remainingCount =
- productData.value.length - productSelectedRows.value.length;
- if (remainingCount < 1) {
- proxy.$modal.msgWarning("鑷冲皯淇濈暀涓�鏉′骇鍝佷俊鎭紝鏃犳硶鍏ㄩ儴鍒犻櫎");
- return;
- }
- 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);
+ // 鎻愪氦琛ㄥ崟
+ 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涓虹┖锛屽垯涓嶄紶閫抯alesContractNo
+ if (!form.value.salesLedgerId) {
+ form.value.salesContractNo = "";
+ }
+
+ addOrEditPurchase(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
}
});
- } else {
+ };
+ // 鍏抽棴寮规
+ 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; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣鐨刲abel
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝鐩存帴杩斿洖锛堝凡缁忔槸label瀛楃涓诧級
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+ };
+ 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;
+ }
+ 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("宸插彇娑�");
+ });
+ };
+ // 鍒犻櫎
+ const handleDelete = () => {
let ids = [];
- if (productSelectedRows.value.length > 0) {
- ids = productSelectedRows.value.map((item) => item.id);
+ if (selectedRows.value.length > 0) {
+ // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
+ const unauthorizedData = selectedRows.value.filter(
+ item => item.recorderName !== userStore.nickName
+ );
+ if (unauthorizedData.length > 0) {
+ proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
+ return;
+ }
+ ids = selectedRows.value.map(item => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
}
ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
confirmButtonText: "纭",
@@ -1272,357 +1623,385 @@
type: "warning",
})
.then(() => {
- delProduct(ids).then((res) => {
+ delPurchase(ids).then(res => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- closeProductDia();
- getPurchaseById({ id: currentId.value, type: 2 }).then(
- (res) => {
- productData.value = res.productData;
- }
- );
+ 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 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("宸插彇娑�");
- });
-};
-// 鍒犻櫎
-const handleDelete = () => {
- let ids = [];
- if (selectedRows.value.length > 0) {
- // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
- const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName);
- if (unauthorizedData.length > 0) {
- proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
- return;
- }
- ids = selectedRows.value.map((item) => item.id);
- } else {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- 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') {
- // 宸茬煡鍚◣鎬讳环鍜屾暟閲忥紝鍙嶇畻鍚◣鍗曚环
- 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) => {
- console.log("row", row);
- var index = salesContractList.value.findIndex((item) => item.id == row);
- console.log("index", index);
- 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 fileListRef = ref(null)
-const downLoadFile = (row) => {
- fileListRef.value.open(row.salesLedgerFiles)
-}
-
-// 鏄剧ず浜岀淮鐮�
-const showQRCode = async (row) => {
- try {
- // 鏋勫缓浜岀淮鐮佸唴瀹癸紝鍙寘鍚噰璐悎鍚屽彿锛堢函鏂囨湰锛�
- const qrContent = row.purchaseContractNumber || '';
- // 妫�鏌ュ唴瀹规槸鍚︿负绌�
- if (!qrContent || qrContent.trim() === '') {
- proxy.$modal.msgWarning("璇ヨ娌℃湁閲囪喘鍚堝悓鍙凤紝鏃犳硶鐢熸垚浜岀淮鐮�");
+ const mathNum = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
return;
}
- qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
- width: 200,
- margin: 2,
- color: {
- dark: '#000000',
- light: '#FFFFFF'
+ 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,
+ });
+ if (code == 200) {
+ productData.value = data;
+ }
+ };
+
+ const fileListRef = ref(null);
+ const downLoadFile = row => {
+ fileListRef.value.open(row.salesLedgerFiles);
+ };
+
+ // 鏄剧ず浜岀淮鐮�
+ 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 = `閲囪喘鍚堝悓鍙蜂簩缁寸爜_${new Date().getTime()}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ proxy.$modal.msgSuccess("涓嬭浇鎴愬姛");
+ };
+
+ // 鎵爜鏂板瀵硅瘽妗嗙浉鍏冲彉閲�
+ 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 openScanAddDialog = () => {
+ scanAddForm.scanContent = "";
+ scanAddForm.purchaseContractNumber = "";
+ scanAddForm.supplierName = "";
+ scanAddForm.projectName = "";
+ scanAddForm.contractAmount = "";
+ scanAddForm.paymentMethod = "";
+ scanAddForm.recorderName = userStore.nickName;
+ scanAddForm.scanRemark = "";
+ scanAddDialogVisible.value = true;
+ };
+
+ // 瑙f瀽鎵爜鍐呭锛堟ā鎷熻В鏋愪簩缁寸爜鏁版嵁锛�
+ const parseScanContent = content => {
+ if (!content) return;
+
+ // 妯℃嫙瑙f瀽浜岀淮鐮佸唴瀹癸紝杩欓噷鍙互鏍规嵁瀹為檯闇�姹傝皟鏁磋В鏋愰�昏緫
+ // 鍋囪鎵爜鍐呭鏍煎紡涓猴細鍚堝悓鍙穦渚涘簲鍟唡閲戦|浠樻鏂瑰紡
+ 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 closeScanAddDialog = () => {
+ scanAddDialogVisible.value = false;
+ proxy.resetForm("scanAddFormRef");
+ };
+
+ // 鎻愪氦鎵爜鏂板
+ 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,
+ };
+
+ // 妯℃嫙鏂板鎴愬姛
+ proxy.$modal.msgSuccess("鎵爜鏂板鎴愬姛锛�");
+ closeScanAddDialog();
+
+ // 鍙互閫夋嫨鏄惁鍒锋柊鍒楄〃
+ // getList();
}
});
- qrCodeDialogVisible.value = true;
- } catch (error) {
- console.error('鐢熸垚浜岀淮鐮佸け璐�:', error);
- proxy.$modal.msgError("鐢熸垚浜岀淮鐮佸け璐ワ細" + error.message);
- }
-};
+ };
-// 涓嬭浇浜岀淮鐮�
-const downloadQRCode = () => {
- if (!qrCodeUrl.value) {
- proxy.$modal.msgWarning("浜岀淮鐮佹湭鐢熸垚");
- return;
+ // 鎵撳紑鎵爜鐧昏瀵硅瘽妗�
+ 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}`;
}
- const a = document.createElement('a');
- a.href = qrCodeUrl.value;
- a.download = `閲囪喘鍚堝悓鍙蜂簩缁寸爜_${new Date().getTime()}.png`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- proxy.$modal.msgSuccess("涓嬭浇鎴愬姛");
-};
+ // 娣诲姞琛岀被鍚嶆柟娉�
+ const tableRowClassName = ({ row }) => {
+ return row.isInvalid ? "invalid-row" : "";
+ };
-// 鎵爜鏂板瀵硅瘽妗嗙浉鍏冲彉閲�
-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 openScanAddDialog = () => {
- scanAddForm.scanContent = "";
- scanAddForm.purchaseContractNumber = "";
- scanAddForm.supplierName = "";
- scanAddForm.projectName = "";
- scanAddForm.contractAmount = "";
- scanAddForm.paymentMethod = "";
- scanAddForm.recorderName = userStore.nickName;
- scanAddForm.scanRemark = "";
- scanAddDialogVisible.value = true;
-};
-
-// 瑙f瀽鎵爜鍐呭锛堟ā鎷熻В鏋愪簩缁寸爜鏁版嵁锛�
-const parseScanContent = (content) => {
- if (!content) return;
-
- // 妯℃嫙瑙f瀽浜岀淮鐮佸唴瀹癸紝杩欓噷鍙互鏍规嵁瀹為檯闇�姹傝皟鏁磋В鏋愰�昏緫
- // 鍋囪鎵爜鍐呭鏍煎紡涓猴細鍚堝悓鍙穦渚涘簲鍟唡椤圭洰|閲戦|浠樻鏂瑰紡
- 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 = () => {
- 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
- };
-
- // 妯℃嫙鏂板鎴愬姛
- proxy.$modal.msgSuccess("鎵爜鏂板鎴愬姛锛�");
- closeScanAddDialog();
-
- // 鍙互閫夋嫨鏄惁鍒锋柊鍒楄〃
- // getList();
+ // 鑾峰彇妯℃澘淇℃伅
+ const getTemplateList = async () => {
+ let res = await getPurchaseTemplateList();
+ if (res && res.code === 200 && Array.isArray(res.data)) {
+ templateList.value = res.data;
}
+ };
+
+ onMounted(() => {
+ getList();
+ getTemplateList();
});
-};
-
-// 鎵撳紑鎵爜鐧昏瀵硅瘽妗�
-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}`;
-}
-
-
-
-onMounted(() => {
- getList();
-});
</script>
-<style scoped lang="scss"></style>
+<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;
+ }
+</style>
diff --git a/src/views/procurementManagement/procurementPlan/index.vue b/src/views/procurementManagement/procurementPlan/index.vue
new file mode 100644
index 0000000..42a1bcf
--- /dev/null
+++ b/src/views/procurementManagement/procurementPlan/index.vue
@@ -0,0 +1,772 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true" class="search-form">
+ <el-form-item label="璁″垝鍚嶇О">
+ <el-input v-model="searchForm.planName" placeholder="璇疯緭鍏ヨ鍒掑悕绉�" clearable />
+ </el-form-item>
+ <el-form-item label="鐘舵��">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px">
+ <el-option label="鍚敤" value="active" />
+ <el-option label="绂佺敤" value="disabled" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">
+ <el-icon><Search /></el-icon>
+ 鎼滅储
+ </el-button>
+ <el-button @click="handleReset">
+ <el-icon><Refresh /></el-icon>
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <el-card class="table-card" shadow="never">
+ <div class="table-header">
+ <div class="table-title">閲囪喘璁″垝鍒楄〃</div>
+ <div class="table-actions">
+ <el-button type="primary" @click="handleAdd">
+ <el-icon><Plus /></el-icon>
+ 鏂板璁″垝
+ </el-button>
+ <el-button type="info" @click="handleExport">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-table
+ v-loading="loading"
+ :data="tableData"
+ stripe
+ border
+ style="width: 100%"
+ >
+ <el-table-column prop="planName" label="璁″垝鍚嶇О" min-width="150" />
+ <el-table-column prop="description" label="鎻忚堪" min-width="200" show-overflow-tooltip />
+ <el-table-column prop="formula" label="璁$畻鍏紡" min-width="200" show-overflow-tooltip>
+ <template #default="{ row }">
+ <el-tag type="info" size="small">{{ row.formula }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="80" align="center">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 'active' ? 'success' : 'info'" size="small">
+ {{ row.status === 'active' ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="updateTime" label="鏈�鍚庤绠楁椂闂�" width="160" />
+ <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleCalculate(row)">璁$畻</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-container">
+ <el-pagination
+ v-model:current-page="pagination.current"
+ v-model:page-size="pagination.size"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <FormDialog
+ v-model="dialogVisible"
+ :title="dialogType === 'add' ? '鏂板閲囪喘璁″垝' : '缂栬緫閲囪喘璁″垝'"
+ :width="'1000px'"
+ :operation-type="dialogType"
+ :close-on-click-modal="false"
+ @close="dialogVisible = false"
+ @confirm="handleSubmit"
+ @cancel="dialogVisible = false"
+ >
+ <div class="form-container">
+ <!-- 鍩烘湰淇℃伅 -->
+ <div class="form-section">
+ <div class="section-title">鍩烘湰淇℃伅</div>
+ <el-form
+ ref="formRef"
+ :model="formData"
+ :rules="formRules"
+ label-width="120px"
+ >
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="缂栫爜" prop="code">
+ <el-input v-model="formData.code" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍚嶇О" prop="planName" required>
+ <el-input v-model="formData.planName" placeholder="璇疯緭鍏ヨ鍒掑悕绉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="鎻忚堪" prop="description">
+ <el-input
+ v-model="formData.description"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ヨ鍒掓弿杩�"
+ />
+ </el-form-item>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="formData.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option label="鍚敤" value="active" />
+ <el-option label="绂佺敤" value="disabled" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏄惁绯荤粺棰勭疆">
+ <el-checkbox v-model="formData.isSystemPreset">鏄�</el-checkbox>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+
+ <!-- 璁$畻鍙傛暟 -->
+ <div class="form-section">
+ <div class="section-title">璁$畻鍙傛暟</div>
+ <el-tabs v-model="activeTab" class="param-tabs">
+ <el-tab-pane label="闇�姹傚弬鏁�" name="demand">
+ <div class="checkbox-group">
+ <el-checkbox v-model="formData.considerExistingStock">鑰冭檻鐜版湁搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.warehouseControl">浠撳簱杩愯MRP鐨勬帶鍒�</el-checkbox>
+ <el-checkbox v-model="formData.calculateTotalDemand">璁$畻鎬婚渶姹�</el-checkbox>
+ <el-checkbox v-model="formData.considerSafetyStock">鑰冭檻瀹夊叏搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.considerLockedStock">鑰冭檻閿佸簱</el-checkbox>
+ <el-checkbox v-model="formData.notConsiderMaterialAux">涓嶈�冭檻鐗╂枡杈呭姪灞炴��</el-checkbox>
+ <el-checkbox v-model="formData.negativeStockAsDemand">璐熷簱瀛樹綔涓洪渶姹�</el-checkbox>
+ </div>
+ </el-tab-pane>
+ <el-tab-pane label="璁$畻鍙傛暟" name="calculation">
+ <div class="checkbox-group">
+ <el-checkbox v-model="formData.considerExistingStock">鑰冭檻鐜版湁搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.warehouseControl">浠撳簱杩愯MRP鐨勬帶鍒�</el-checkbox>
+ <el-checkbox v-model="formData.calculateTotalDemand">璁$畻鎬婚渶姹�</el-checkbox>
+ <el-checkbox v-model="formData.considerSafetyStock">鑰冭檻瀹夊叏搴撳瓨</el-checkbox>
+ <el-checkbox v-model="formData.considerLockedStock">鑰冭檻閿佸簱</el-checkbox>
+ <el-checkbox v-model="formData.notConsiderMaterialAux">涓嶈�冭檻鐗╂枡杈呭姪灞炴��</el-checkbox>
+ <el-checkbox v-model="formData.negativeStockAsDemand">璐熷簱瀛樹綔涓洪渶姹�</el-checkbox>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
+ </div>
+
+ <!-- 姹囨�诲悎骞堕�夐」 -->
+ <div class="form-section">
+ <div class="section-title">姹囨�诲悎骞堕�夐」</div>
+ <div class="checkbox-group">
+ <el-checkbox v-model="formData.summaryMaterial">鐗╂枡</el-checkbox>
+ <el-checkbox v-model="formData.summaryAuxAttributes">杈呭姪灞炴��</el-checkbox>
+ <el-checkbox v-model="formData.summaryDemandDate">闇�姹傛棩鏈�</el-checkbox>
+ </div>
+ </div>
+
+ <!-- 璁$畻鍏紡 -->
+ <div class="form-section">
+ <div class="section-title">璁$畻鍏紡</div>
+ <div class="formula-input-section">
+ <el-form-item label="璁$畻鍏紡" prop="formula" required>
+ <el-input
+ v-model="formData.formula"
+ placeholder="渚嬪: 棰勮鍑哄簱鏁伴噺 - 鐜版湁搴撳瓨 + 瀹夊叏搴撳瓨 - 棰勮鍏ュ簱鏁伴噺"
+ @input="validateFormula"
+ />
+ </el-form-item>
+ <div class="formula-help">
+ <el-text type="info" size="small">
+ 鏀寔鍙橀噺锛氶璁″嚭搴撴暟閲忋�佺幇鏈夊簱瀛樸�佸畨鍏ㄥ簱瀛樸�侀璁″叆搴撴暟閲�
+ </el-text>
+ </div>
+ </div>
+ </div>
+ </div>
+ </FormDialog>
+
+ <!-- 浜у搧閫夋嫨瀵硅瘽妗� -->
+ <FormDialog
+ v-model="productSelectDialogVisible"
+ title="閫夋嫨浜у搧"
+ :width="'800px'"
+ :close-on-click-modal="false"
+ @close="productSelectDialogVisible = false"
+ @confirm="handleConfirmProductSelection"
+ @cancel="productSelectDialogVisible = false"
+ >
+ <div class="product-select">
+ <el-alert
+ title="璇烽�夋嫨瑕佽绠楃殑浜у搧"
+ type="info"
+ :closable="false"
+ show-icon
+ >
+ <template #default>
+ <p>閫夋嫨浜у搧鍚庯紝绯荤粺灏嗘牴鎹綋鍓嶈绠楀叕寮忓拰浜у搧搴撳瓨鎯呭喌杩涜璁$畻銆�</p>
+ </template>
+ </el-alert>
+
+ <el-table
+ v-loading="productLoading"
+ :data="productList"
+ @selection-change="handleProductSelectionChange"
+ stripe
+ border
+ style="width: 100%; margin-top: 20px;"
+ >
+ <el-table-column type="selection" width="55" />
+ <el-table-column prop="productCategory" label="浜у搧澶х被" min-width="150" />
+ <el-table-column prop="specificationModel" label="浜у搧瑙勬牸" width="120" />
+ <el-table-column prop="inboundNum0" label="鐜版湁搴撳瓨" width="100" align="right" />
+ <el-table-column prop="inboundNum" label="瀹夊叏搴撳瓨" width="100" align="right" />
+ <el-table-column prop="inboundNum" label="棰勮鍑哄簱" width="100" align="right" />
+ <el-table-column prop="inboundNum0" label="棰勮鍏ュ簱" width="100" align="right" />
+ </el-table>
+ </div>
+ </FormDialog>
+
+ <!-- 璁$畻缁撴灉瀵硅瘽妗� -->
+ <FormDialog
+ v-model="calculateDialogVisible"
+ title="閲囪喘璁$畻缁撴灉"
+ :width="'1000px'"
+ :close-on-click-modal="false"
+ @close="calculateDialogVisible = false"
+ @confirm="handleCreatePurchaseOrder"
+ @cancel="calculateDialogVisible = false"
+ >
+ <div class="calculate-result">
+ <el-alert
+ title="璁$畻缁撴灉"
+ type="success"
+ :closable="false"
+ show-icon
+ >
+ <template #default>
+ <p>鍩轰簬褰撳墠閰嶇疆鐨勮绠楀叕寮忓拰搴撳瓨鎯呭喌锛岀郴缁熷凡璁$畻鍑哄悇浜у搧鐨勯噰璐渶姹傘��</p>
+ </template>
+ </el-alert>
+
+ <el-table :data="calculateResult" stripe border style="width: 100%; margin-top: 20px;">
+ <el-table-column prop="productCategory" label="浜у搧澶х被" min-width="150" />
+ <el-table-column prop="specificationModel" label="浜у搧瑙勬牸" width="120" />
+ <el-table-column prop="inboundNum0" label="鐜版湁搴撳瓨" width="100" align="right" />
+ <el-table-column prop="inboundNum" label="瀹夊叏搴撳瓨" width="100" align="right" />
+ <el-table-column prop="inboundNum" label="棰勮鍑哄簱鏁伴噺" width="120" align="right" />
+ <el-table-column prop="inboundNum0" label="棰勮鍏ュ簱鏁伴噺" width="120" align="right" />
+ <el-table-column prop="weeklyNetDemand" label="鎸夊懆鍑�闇�姹�" width="120" align="right">
+ <template #default="{ row }">
+ <el-tag :type="row.weeklyNetDemand > 0 ? 'warning' : 'success'" size="small">
+ {{ row.weeklyNetDemand }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="suggestedPurchase" label="寤鸿閲囪喘" width="100" align="right">
+ <template #default="{ row }">
+ <el-tag :type="row.suggestedPurchase > 0 ? 'danger' : 'success'" size="small">
+ {{ row.suggestedPurchase }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </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'
+import {listPage,add,update,del,listPageCopy} from "@/api/procurementManagement/procurementPlan.js"
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const submitLoading = ref(false)
+const dialogVisible = ref(false)
+const productSelectDialogVisible = ref(false)
+const calculateDialogVisible = ref(false)
+const dialogType = ref('add')
+const productLoading = ref(false)
+const selectedProducts = ref([])
+const currentPlan = ref(null)
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ planName: '',
+ status: ''
+})
+
+// 鍒嗛〉鏁版嵁
+const pagination = reactive({
+ current: 1,
+ size: 20
+})
+
+// 琛ㄥ崟鏁版嵁
+const formData = reactive({
+ code: '',
+ planName: '',
+ description: '',
+ status: '',
+ isSystemPreset: false,
+ formula: '',
+ // 璁$畻鍙傛暟
+ considerExistingStock: false,
+ warehouseControl: false,
+ calculateTotalDemand: false,
+ considerSafetyStock: false,
+ considerLockedStock: false,
+ notConsiderMaterialAux: false,
+ negativeStockAsDemand: false,
+ // 姹囨�诲悎骞堕�夐」
+ summaryMaterial: false,
+ summaryAuxAttributes: false,
+ summaryDemandDate: false
+})
+
+// 褰撳墠婵�娲荤殑鏍囩椤�
+const activeTab = ref('demand')
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const formRules = {
+ planName: [
+ { required: true, message: '璇疯緭鍏ヨ鍒掑悕绉�', trigger: 'blur' }
+ ],
+ status: [
+ { required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }
+ ],
+ formula: [
+ { required: true, message: '璇疯緭鍏ヨ绠楀叕寮�', trigger: 'blur' }
+ ]
+}
+
+// 琛ㄦ牸鏁版嵁
+const tableData = ref([])
+
+// 浜у搧鍒楄〃鏁版嵁
+const productList = ref([
+ {
+ id: 4,
+ productName: '浜у搧D',
+ productCode: 'PD004',
+ existingStock: 90,
+ safetyStock: 40,
+ expectedOutbound: 160,
+ expectedInbound: 35
+ }
+])
+
+// 璁$畻缁撴灉鏁版嵁
+const calculateResult = ref([
+ {
+ productName: '浜у搧A',
+ existingStock: 100,
+ safetyStock: 50,
+ expectedOutbound: 200,
+ expectedInbound: 30,
+ weeklyNetDemand: 120,
+ suggestedPurchase: 150
+ },
+ {
+ productName: '浜у搧B',
+ existingStock: 80,
+ safetyStock: 30,
+ expectedOutbound: 150,
+ expectedInbound: 20,
+ weeklyNetDemand: 100,
+ suggestedPurchase: 120
+ }
+])
+const total = ref(0)
+// 鏂规硶
+const handleSearch = () => {
+ pagination.current = 1
+ loadData()
+}
+
+const handleReset = () => {
+ Object.assign(searchForm, {
+ planName: '',
+ status: ''
+ })
+ handleSearch()
+}
+
+const loadData = () => {
+ loading.value = true
+ listPage({...searchForm,...pagination}).then(res => {
+ if(res.code === 200){
+ tableData.value = res.data.records
+ total.value = res.data.total
+ loading.value = false
+ }
+ })
+}
+
+const handleAdd = () => {
+ dialogType.value = 'add'
+ resetForm()
+ // 鑷姩鐢熸垚缂栫爜
+ formData.code = 'CGJH' + String(Date.now()).slice(-4)
+ dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+ dialogType.value = 'edit'
+ Object.assign(formData, row)
+ dialogVisible.value = true
+}
+
+const handleDelete = async (row) => {
+ try {
+ await ElMessageBox.confirm('纭畾瑕佸垹闄よ繖涓噰璐鍒掑悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ let ids = [row.id]
+ del(ids).then(res =>{
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ loadData()
+ }
+ })
+ } catch {
+ // 鐢ㄦ埛鍙栨秷鍒犻櫎
+ }
+}
+
+const handleSubmit = async () => {
+ try {
+ // 琛ㄥ崟楠岃瘉
+ if (!formData.planName || !formData.formula) {
+ ElMessage.error('璇峰~鍐欏繀濉」')
+ return
+ }
+
+ submitLoading.value = true
+
+ if (dialogType.value === 'add') {
+ add(formData).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ dialogVisible.value = false
+ loadData()
+ }
+ })
+ } else {
+ // 缂栬緫
+ update(formData).then(res => {
+ if(res.code === 200){
+ ElMessage.success('缂栬緫鎴愬姛')
+ dialogVisible.value = false
+ loadData()
+ }
+ })
+ }
+
+
+ } catch (error) {
+ ElMessage.error('鎿嶄綔澶辫触')
+ } finally {
+ submitLoading.value = false
+ }
+}
+
+const resetForm = () => {
+ Object.assign(formData, {
+ code: '',
+ planName: '',
+ description: '',
+ status: '',
+ isSystemPreset: false,
+ formula: '棰勮鍑哄簱鏁伴噺 - 鐜版湁搴撳瓨 + 瀹夊叏搴撳瓨 - 棰勮鍏ュ簱鏁伴噺',
+ // 璁$畻鍙傛暟
+ considerExistingStock: false,
+ warehouseControl: false,
+ calculateTotalDemand: false,
+ considerSafetyStock: false,
+ considerLockedStock: false,
+ notConsiderMaterialAux: false,
+ negativeStockAsDemand: false,
+ // 姹囨�诲悎骞堕�夐」
+ summaryMaterial: false,
+ summaryAuxAttributes: false,
+ summaryDemandDate: false
+ })
+ activeTab.value = 'demand'
+}
+
+const validateFormula = () => {
+ // 绠�鍗曠殑鍏紡楠岃瘉
+ const formula = formData.formula
+ if (formula && !/^[a-zA-Z\u4e00-\u9fa5\s\*\+\-\/\(\)\d\.]+$/.test(formula)) {
+ ElMessage.warning('鍏紡鏍煎紡鍙兘涓嶆纭紝璇锋鏌�')
+ }
+}
+
+const handleCalculate = (row) => {
+ currentPlan.value = row
+ productSelectDialogVisible.value = true
+ loadProductList()
+}
+
+const loadProductList = () => {
+ productLoading.value = true
+ // 妯℃嫙鍔犺浇浜у搧鏁版嵁
+ listPageCopy({size:-1}).then(res => {
+ if(res.code === 200){
+ productList.value = res.data.records
+ productLoading.value = false
+ }
+ })
+}
+
+const handleProductSelectionChange = (selection) => {
+ selectedProducts.value = selection
+}
+
+const handleConfirmProductSelection = () => {
+ if (selectedProducts.value.length === 0) {
+ ElMessage.warning('璇烽�夋嫨瑕佽绠楃殑浜у搧')
+ return
+ }
+
+ ElMessage.success(`姝e湪璁$畻 ${currentPlan.value.planName} 鐨勯噰璐渶姹�...`)
+ productSelectDialogVisible.value = false
+
+ // 鏍规嵁閫夋嫨鐨勪骇鍝佸拰璁$畻鍏紡杩涜璁$畻
+ calculateWithSelectedProducts()
+}
+
+const calculateWithSelectedProducts = () => {
+ // 妯℃嫙璁$畻杩囩▼
+ // 鏍规嵁閫夋嫨鐨勪骇鍝佹洿鏂拌绠楃粨鏋�
+ const result = selectedProducts.value.map(product => {
+ // 杩欓噷搴旇鏍规嵁瀹為檯鐨勮绠楀叕寮忚繘琛岃绠�
+ // 绀轰緥锛氶璁″嚭搴撴暟閲� - 鐜版湁搴撳瓨 + 瀹夊叏搴撳瓨 - 棰勮鍏ュ簱鏁伴噺
+ const weeklyNetDemand = product.inboundNum - product.inboundNum0 + product.inboundNum - product.inboundNum0
+ const suggestedPurchase = Math.max(0, weeklyNetDemand)
+
+ return {
+ productCategory: product.productCategory,
+ specificationModel: product.specificationModel,
+ inboundNum0: product.inboundNum0,
+ inboundNum: product.inboundNum,
+ weeklyNetDemand: weeklyNetDemand,
+ suggestedPurchase: suggestedPurchase
+ }
+ })
+
+ calculateResult.value = result
+ calculateDialogVisible.value = true
+}
+
+
+const handleCreatePurchaseOrder = () => {
+ calculateDialogVisible.value = false
+}
+const { proxy } = getCurrentInstance();
+const handleExport = () => {
+ ElMessageBox.confirm("鍐呭灏嗚瀵煎嚭锛屾槸鍚︾‘璁ゅ鍑猴紵", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/procurementPlan/export", {}, "閲囪喘璁″垝.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+}
+
+
+const handleSizeChange = (size) => {
+ pagination.size = size
+ loadData()
+}
+
+const handleCurrentChange = (current) => {
+ pagination.current = current
+ loadData()
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ loadData()
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.page-header {
+ margin-bottom: 20px;
+}
+
+.page-header h2 {
+ margin: 0 0 8px 0;
+ color: #303133;
+ font-size: 24px;
+ font-weight: 600;
+}
+
+.page-header p {
+ margin: 0;
+ color: #909399;
+ font-size: 14px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.search-form {
+ margin-bottom: 0;
+}
+
+.table-card {
+ margin-bottom: 20px;
+}
+
+.table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.table-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.table-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: end;
+}
+
+.form-container {
+ padding: 0 20px;
+}
+
+.formula-help {
+ margin-top: 5px;
+}
+
+.calculate-result {
+ padding: 20px 0;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+:deep(.el-card__body) {
+ padding: 20px;
+}
+
+:deep(.el-table) {
+ font-size: 14px;
+}
+
+:deep(.el-form-item__label) {
+ font-weight: 500;
+}
+
+.form-container {
+ padding: 0;
+}
+
+.form-section {
+ margin-bottom: 24px;
+ border: 1px solid #e4e7ed;
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+.section-title {
+ background-color: #f5f7fa;
+ padding: 12px 16px;
+ font-weight: 600;
+ color: #303133;
+ border-bottom: 1px solid #e4e7ed;
+}
+
+.form-section .el-form {
+ padding: 20px;
+}
+
+.param-tabs {
+ padding: 20px;
+}
+
+.param-tabs :deep(.el-tabs__header) {
+ margin-bottom: 20px;
+}
+
+.param-tabs :deep(.el-tabs__item.is-active) {
+ color: #f56c6c;
+ border-bottom-color: #f56c6c;
+}
+
+.checkbox-group {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+}
+
+.checkbox-group .el-checkbox {
+ margin-right: 0;
+ margin-bottom: 8px;
+}
+
+.formula-input-section {
+ padding: 20px;
+}
+
+.formula-input-section .el-form-item {
+ margin-bottom: 12px;
+}
+
+.formula-help {
+ text-align: center;
+ margin-top: 8px;
+}
+</style>
diff --git a/src/views/procurementManagement/procurementReport/index.vue b/src/views/procurementManagement/procurementReport/index.vue
new file mode 100644
index 0000000..8c8f3f6
--- /dev/null
+++ b/src/views/procurementManagement/procurementReport/index.vue
@@ -0,0 +1,380 @@
+<template>
+ <div class="app-container">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-form :model="searchForm" :inline="true" class="search-form">
+ <el-form-item label="鏃堕棿鑼冨洿锛�">
+ <el-date-picker
+ v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 240px"
+ />
+ </el-form-item>
+ <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-button>
+ <el-button @click="resetSearch">
+ 閲嶇疆
+ </el-button>
+ <el-button type="info" @click="exportReport">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭
+ </el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 鎶ヨ〃鍐呭 -->
+ <el-card class="report-content" shadow="never">
+ <!-- 閲囪喘涓氬姟姹囨�昏〃 -->
+ <div 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">楼{{ businessSummaryStats.totalAmount.toLocaleString() }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">鍟嗗搧绉嶇被锛�</span>
+ <span class="stat-value">{{ businessSummaryStats.productTypes }}</span>
+ </div>
+ </div>
+ </div>
+
+ <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>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+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 searchForm = reactive({
+ dateRange: [],
+ productCategory: ''
+})
+
+// 浜у搧绫诲埆鏍戦�夐」
+const productOptions = ref([])
+
+// 缁熻鏁版嵁
+const businessSummaryStats = ref({
+ totalAmount: 0,
+ productTypes: 0
+})
+
+// 琛ㄦ牸鍒楅厤缃紙鏍规嵁鍚庣瀛楁瀹氫箟锛�
+const tableColumns = ref([
+ {
+ label: '浜у搧澶х被',
+ prop: 'productCategory',
+ width: 150,
+ },
+ {
+ label: '瑙勬牸鍨嬪彿',
+ prop: 'specificationModel',
+ width: 180
+ },
+ {
+ label: '閲囪喘鏁伴噺',
+ prop: 'purchaseNum',
+ width: 120,
+ formatData: (val) => {
+ return val ? parseFloat(val).toLocaleString() : '0'
+ }
+ },
+ {
+ label: '閲囪喘閲戦',
+ prop: 'purchaseAmount',
+ width: 140,
+ formatData: (val) => {
+ return val ? `楼${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '楼0.00'
+ }
+ },
+ {
+ label: '閲囪喘娆℃暟',
+ prop: 'purchaseTimes',
+ width: 100
+ },
+ {
+ label: '骞冲潎鍗曚环',
+ prop: 'averagePrice',
+ width: 120,
+ formatData: (val) => {
+ return val ? `楼${parseFloat(val).toFixed(2)}` : '楼0.00'
+ }
+ },
+ {
+ label: '渚涘簲鍟嗗悕绉�',
+ prop: 'supplierName',
+ width: 200
+ },
+ {
+ label: '褰曞叆鏃ユ湡',
+ prop: 'entryDate',
+ width: 120
+ }
+])
+
+// 閲囪喘涓氬姟姹囨�昏〃鏁版嵁
+const businessSummaryData = ref([])
+
+// 鍒嗛〉鍙傛暟锛堝悗绔繑鍥烇細total/size/current/pages锛�
+const page = reactive({
+ total: 0,
+ current: 1,
+ size: 50,
+})
+
+// 杞崲浜у搧鏍戞暟鎹紝灏� 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 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
+ }
+}
+
+// 缈婚〉/鍒囨崲姣忛〉鏉℃暟
+const handlePagination = ({ page: current, limit }) => {
+ page.current = current
+ page.size = limit
+ handleSearch()
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, {
+ dateRange: [],
+ productCategory: ''
+ })
+ page.current = 1
+ handleSearch()
+}
+
+const exportReport = () => {
+ ElMessage.success('瀵煎嚭鍔熻兘寮�鍙戜腑...')
+}
+
+
+onMounted(() => {
+ // 鍒濆鍖栦骇鍝佺被鍒爲
+ getProductOptions()
+
+ // 璁剧疆榛樿鏃堕棿鑼冨洿涓烘渶杩�30澶�
+ const endDate = new Date()
+ const startDate = new Date()
+ startDate.setDate(startDate.getDate() - 30)
+
+ searchForm.dateRange = [
+ startDate.toISOString().split('T')[0],
+ endDate.toISOString().split('T')[0]
+ ]
+
+ // 鍒濆鍔犺浇鏁版嵁
+ handleSearch()
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: 20px;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 10px;
+ color: white;
+}
+
+.page-header h2 {
+ margin: 0 0 10px 0;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.page-header p {
+ margin: 0;
+ font-size: 16px;
+ opacity: 0.9;
+}
+
+.report-content {
+ border-radius: 8px;
+}
+
+.report-section {
+ min-height: 400px;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid #e4e7ed;
+}
+
+.section-header h3 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.summary-stats {
+ display: flex;
+ gap: 30px;
+}
+
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 5px;
+}
+
+.stat-value {
+ font-size: 18px;
+ font-weight: 600;
+ color: #409EFF;
+}
+
+.delay-text {
+ color: #F56C6C;
+ font-weight: 600;
+}
+
+
+</style>
diff --git a/src/views/procurementManagement/purchaseOrder/index.vue b/src/views/procurementManagement/purchaseOrder/index.vue
new file mode 100644
index 0000000..7e56cb4
--- /dev/null
+++ b/src/views/procurementManagement/purchaseOrder/index.vue
@@ -0,0 +1,185 @@
+<template>
+ <div class="app-container">
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細">
+ <el-input v-model="searchForm.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О" clearable />
+ </el-form-item>
+ <el-form-item label="璁㈠崟鐘舵�侊細">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" clearable>
+ <el-option label="鑽夌" value="draft" />
+ <el-option label="寰呭鏍�" value="pending" />
+ <el-option label="宸插鏍�" value="approved" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <el-card class="table-card" shadow="never">
+ <div class="table-header">
+ <el-button type="primary" @click="openDialog('add')">鏂板璁㈠崟</el-button>
+ <el-button type="danger" @click="handleBatchDelete" :disabled="!selectedRows.length">鎵归噺鍒犻櫎</el-button>
+ </div>
+
+ <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="璁㈠崟缂栧彿" prop="orderNo" width="180" />
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" />
+ <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="totalAmount" width="120">
+ <template #default="{ row }">楼{{ row.totalAmount.toFixed(2) }}</template>
+ </el-table-column>
+ <el-table-column label="鍒涘缓鏃堕棿" prop="createTime" width="180" />
+ <el-table-column label="鎿嶄綔" width="200" align="center">
+ <template #default="{ row }">
+ <el-button type="primary" size="small" @click="openDialog('edit', row)">缂栬緫</el-button>
+ <el-button type="success" size="small" @click="viewDetails(row)">鏌ョ湅</el-button>
+ <el-button type="danger" size="small" @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <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%">
+ <el-option label="渚涘簲鍟咥" value="渚涘簲鍟咥" />
+ <el-option label="渚涘簲鍟咮" value="渚涘簲鍟咮" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞">
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import FormDialog from '@/components/Dialog/FormDialog.vue';
+import { ref, reactive } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const loading = ref(false)
+const dialogVisible = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+
+const searchForm = reactive({
+ supplierName: '',
+ status: ''
+})
+
+const formData = reactive({
+ supplierName: '',
+ remark: ''
+})
+
+const mockData = [
+ {
+ id: 1,
+ orderNo: 'PO20241201001',
+ supplierName: '渚涘簲鍟咥',
+ status: 'approved',
+ totalAmount: 12500.00,
+ createTime: '2025-12-01 10:30:00',
+ remark: '甯歌閲囪喘'
+ }
+]
+
+const tableData = ref([...mockData])
+
+const getStatusType = (status) => {
+ const statusMap = { draft: 'info', pending: 'warning', approved: 'success' }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = { draft: '鑽夌', pending: '寰呭鏍�', approved: '宸插鏍�' }
+ return statusMap[status] || '鏈煡'
+}
+
+const handleSearch = () => {
+ loading.value = true
+ setTimeout(() => {
+ loading.value = false
+ }, 500)
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, { supplierName: '', status: '' })
+}
+
+const openDialog = (type, row = {}) => {
+ dialogType.value = type
+ if (type === 'edit' && row.id) {
+ Object.assign(formData, { supplierName: row.supplierName, remark: row.remark })
+ } else {
+ Object.assign(formData, { supplierName: '', remark: '' })
+ }
+ dialogVisible.value = true
+}
+
+const handleSubmit = () => {
+ if (dialogType.value === 'add') {
+ const newOrder = {
+ id: Date.now(),
+ orderNo: `PO${Date.now()}`,
+ supplierName: formData.supplierName,
+ status: 'draft',
+ totalAmount: 0,
+ createTime: new Date().toLocaleString(),
+ remark: formData.remark
+ }
+ tableData.value.unshift(newOrder)
+ ElMessage.success('鏂板鎴愬姛')
+ }
+ dialogVisible.value = false
+}
+
+const viewDetails = (row) => {
+ ElMessage.info('鏌ョ湅璇︽儏鍔熻兘')
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = tableData.value.findIndex(item => item.id === row.id)
+ if (index !== -1) {
+ tableData.value.splice(index, 1)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ }
+ })
+}
+
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning('璇烽�夋嫨瑕佸垹闄ょ殑璁板綍')
+ return
+ }
+ ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+}
+
+const handleSelectionChange = (rows) => {
+ selectedRows.value = rows
+}
+</script>
+
+<style scoped>
+.app-container { padding: 20px; }
+.search-card { margin-bottom: 20px; }
+.table-card { margin-bottom: 20px; }
+.table-header { margin-bottom: 20px; }
+</style>
diff --git a/src/views/procurementManagement/qualityInspection/index.vue b/src/views/procurementManagement/qualityInspection/index.vue
new file mode 100644
index 0000000..ab19d6f
--- /dev/null
+++ b/src/views/procurementManagement/qualityInspection/index.vue
@@ -0,0 +1,282 @@
+<template>
+ <div class="app-container">
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="璐ㄦ鍗曞彿锛�" style="width: 300px;">
+ <el-input v-model="searchForm.inspectionNo" placeholder="璇疯緭鍏ヨ川妫�鍗曞彿" clearable />
+ </el-form-item>
+ <el-form-item label="璐ㄦ鐘舵�侊細" style="width: 300px;">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" clearable>
+ <el-option label="寰呰川妫�" value="pending" />
+ <el-option label="璐ㄦ涓�" value="inspecting" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <el-card class="table-card" shadow="never">
+ <div class="table-header">
+ <el-button type="primary" @click="openDialog('add')">鏂板璐ㄦ鍗�</el-button>
+ <el-button type="success" @click="handleBatchComplete">鎵归噺瀹屾垚</el-button>
+ <el-button type="danger" @click="handleBatchDelete">鎵归噺鍒犻櫎</el-button>
+ </div>
+
+ <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="璐ㄦ鍗曞彿" prop="inspectionNo" width="180" />
+ <el-table-column label="鍒拌揣鍗曞彿" prop="arrivalNo" width="180" />
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" />
+ <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="qualifiedQuantity" width="100" />
+ <el-table-column label="涓嶅悎鏍兼暟閲�" prop="unqualifiedQuantity" width="100" />
+ <el-table-column label="璐ㄦ鏃堕棿" prop="inspectionTime" width="180" />
+ <el-table-column label="鎿嶄綔" width="200" align="center">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="openDialog('edit', row)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleComplete(row)" v-if="row.status !== 'completed'">瀹屾垚</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <FormDialog v-model="dialogVisible" :title="dialogType === 'add' ? '鏂板璐ㄦ鍗�' : '缂栬緫璐ㄦ鍗�'" :width="'1000px'" :operation-type="dialogType" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
+ <el-form :model="formData" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍒拌揣鍗曞彿">
+ <el-select v-model="formData.arrivalNo" placeholder="璇烽�夋嫨鍒拌揣鍗�" style="width: 100%">
+ <el-option label="AR20241201001" value="AR20241201001" />
+ <el-option label="AR20241201002" value="AR20241201002" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟嗗悕绉�">
+ <el-input v-model="formData.supplierName" placeholder="渚涘簲鍟嗗悕绉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="璐ㄦ鍟嗗搧">
+ <div class="product-list" style="width: 100%;">
+ <el-table :data="formData.products" border width="100%">
+ <el-table-column label="鍟嗗搧鍚嶇О" width="150">
+ <template #default="{ row }">
+ <el-input v-model="row.productName" placeholder="璇疯緭鍏ュ晢鍝佸悕绉�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="瑙勬牸鍨嬪彿" width="150">
+ <template #default="{ row }">
+ <el-input v-model="row.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒拌揣鏁伴噺" width="150">
+ <template #default="{ row }">
+ <el-input-number v-model="row.arrivalQuantity" :min="0" placeholder="鏁伴噺" style="width: 100%;"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍚堟牸鏁伴噺" width="150">
+ <template #default="{ row }">
+ <el-input-number v-model="row.qualifiedQuantity" :min="0" placeholder="鏁伴噺" style="width: 100%;"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓嶅悎鏍兼暟閲�" width="150">
+ <template #default="{ row }">
+ <el-input-number v-model="row.unqualifiedQuantity" :min="0" placeholder="鏁伴噺" style="width: 100%;"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓嶅悎鏍煎師鍥�" width="200">
+ <template #default="{ row }">
+ <el-input v-model="row.unqualifiedReason" placeholder="璇疯緭鍏ヤ笉鍚堟牸鍘熷洜" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="100">
+ <template #default="{ $index }">
+ <el-button type="danger" link @click="removeProduct($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div class="add-product-btn">
+ <el-button type="primary" @click="addProduct">娣诲姞鍟嗗搧</el-button>
+ </div>
+ </div>
+ </el-form-item>
+
+ <el-form-item label="璐ㄦ鍛�">
+ <el-input v-model="formData.inspector" placeholder="璇疯緭鍏ヨ川妫�鍛樺鍚�" />
+ </el-form-item>
+
+ <el-form-item label="澶囨敞">
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import FormDialog from '@/components/Dialog/FormDialog.vue';
+import { ref, reactive } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const loading = ref(false)
+const dialogVisible = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+
+const searchForm = reactive({
+ inspectionNo: '',
+ status: ''
+})
+
+const formData = reactive({
+ arrivalNo: '',
+ supplierName: '',
+ products: [],
+ inspector: '',
+ remark: ''
+})
+
+const mockData = [
+ {
+ id: 1,
+ inspectionNo: 'QI20241201001',
+ arrivalNo: 'AR20241201001',
+ supplierName: '渚涘簲鍟咥',
+ status: 'completed',
+ qualifiedQuantity: 240,
+ unqualifiedQuantity: 10,
+ inspectionTime: '2025-12-01 16:30:00',
+ inspector: '闄堝織寮�',
+ remark: '璐ㄦ瀹屾垚'
+ }
+]
+
+const tableData = ref([...mockData])
+
+const getStatusType = (status) => {
+ const statusMap = { pending: 'info', inspecting: 'warning', completed: 'success' }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = { pending: '寰呰川妫�', inspecting: '璐ㄦ涓�', completed: '宸插畬鎴�' }
+ return statusMap[status] || '鏈煡'
+}
+
+const handleSearch = () => {
+ loading.value = true
+ setTimeout(() => { loading.value = false }, 500)
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, { inspectionNo: '', status: '' })
+}
+
+const openDialog = (type, row = {}) => {
+ dialogType.value = type
+ if (type === 'edit' && row.id) {
+ Object.assign(formData, {
+ arrivalNo: row.arrivalNo,
+ supplierName: row.supplierName,
+ inspector: row.inspector,
+ remark: row.remark
+ })
+ } else {
+ Object.assign(formData, {
+ arrivalNo: '',
+ supplierName: '',
+ products: [],
+ inspector: '',
+ remark: ''
+ })
+ }
+ dialogVisible.value = true
+}
+
+const handleSubmit = () => {
+ if (dialogType.value === 'add') {
+ const newInspection = {
+ id: Date.now(),
+ inspectionNo: `QI${Date.now()}`,
+ arrivalNo: formData.arrivalNo,
+ supplierName: formData.supplierName,
+ status: 'pending',
+ qualifiedQuantity: 0,
+ unqualifiedQuantity: 0,
+ inspectionTime: new Date().toLocaleString(),
+ inspector: formData.inspector,
+ remark: formData.remark
+ }
+ tableData.value.unshift(newInspection)
+ ElMessage.success('鏂板鎴愬姛')
+ }
+ dialogVisible.value = false
+}
+
+const handleComplete = (row) => {
+ row.status = 'completed'
+ ElMessage.success('璐ㄦ瀹屾垚')
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = tableData.value.findIndex(item => item.id === row.id)
+ if (index !== -1) {
+ tableData.value.splice(index, 1)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ }
+ })
+}
+
+const handleBatchComplete = () => {
+ ElMessage.success('鎵归噺瀹屾垚鎴愬姛')
+}
+
+const handleBatchDelete = () => {
+ ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+}
+
+const handleSelectionChange = (rows) => {
+ selectedRows.value = rows
+}
+
+const addProduct = () => {
+ formData.products.push({
+ productName: '',
+ specification: '',
+ arrivalQuantity: 0,
+ qualifiedQuantity: 0,
+ unqualifiedQuantity: 0,
+ unqualifiedReason: ''
+ })
+}
+
+const removeProduct = (index) => {
+ formData.products.splice(index, 1)
+}
+</script>
+
+<style scoped>
+.app-container { padding: 20px; }
+.search-card { margin-bottom: 20px; }
+.table-card { margin-bottom: 20px; }
+.table-header { margin-bottom: 20px; }
+.product-list { border: 1px solid #dcdfe6; border-radius: 4px; padding: 15px; }
+.product-item { margin-bottom: 15px; padding: 10px; background-color: #f5f7fa; border-radius: 4px; }
+.add-product-btn { margin-top: 15px; text-align: center; }
+</style>
diff --git a/src/views/procurementManagement/returnManagement/index.vue b/src/views/procurementManagement/returnManagement/index.vue
new file mode 100644
index 0000000..d44b588
--- /dev/null
+++ b/src/views/procurementManagement/returnManagement/index.vue
@@ -0,0 +1,271 @@
+<template>
+ <div class="app-container">
+ <el-card class="search-card" shadow="never">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="閫�璐у崟鍙凤細" style="width: 300px;">
+ <el-input v-model="searchForm.returnNo" placeholder="璇疯緭鍏ラ��璐у崟鍙�" clearable />
+ </el-form-item>
+ <el-form-item label="閫�璐х被鍨嬶細" style="width: 300px;">
+ <el-select v-model="searchForm.returnType" placeholder="璇烽�夋嫨绫诲瀷" clearable>
+ <el-option label="閲囪喘閫�璐�" value="purchase" />
+ <el-option label="璐ㄦ閫�璐�" value="quality" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <el-card class="table-card" shadow="never">
+ <div class="table-header">
+ <el-button type="primary" @click="openDialog('add')">鏂板閫�璐у崟</el-button>
+ <el-button type="danger" @click="handleBatchDelete">鎵归噺鍒犻櫎</el-button>
+ </div>
+
+ <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="閫�璐у崟鍙�" prop="returnNo" width="180" />
+ <el-table-column label="鍏宠仈鍗曞彿" prop="relatedNo" width="180" />
+ <el-table-column label="閫�璐х被鍨�" prop="returnType" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.returnType === 'purchase' ? 'danger' : 'warning'">
+ {{ getReturnTypeText(row.returnType) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" />
+ <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="createTime" width="180" />
+ <el-table-column label="鎿嶄綔" width="200" align="center">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="openDialog('edit', row)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleApprove(row)" v-if="row.status === 'pending'">瀹℃牳</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.current"
+ :limit="pagination.size"
+ @pagination="handleCurrentChange"
+ />
+ </el-card>
+
+ <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.returnType" placeholder="璇烽�夋嫨閫�璐х被鍨�" style="width: 100%">
+ <el-option label="閲囪喘閫�璐�" value="purchase" />
+ <el-option label="璐ㄦ閫�璐�" value="quality" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍏宠仈鍗曞彿">
+ <el-select v-model="formData.relatedNo" placeholder="璇烽�夋嫨鍏宠仈鍗曞彿" style="width: 100%">
+ <el-option v-for="item in onList" :key="item.arrivalNo" :label="item.arrivalNo" :value="item.arrivalNo" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗗悕绉�">
+ <el-input v-model="formData.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О" />
+ </el-form-item>
+ <el-form-item label="閫�璐у師鍥�">
+ <el-select v-model="formData.returnReason" placeholder="璇烽�夋嫨閫�璐у師鍥�" style="width: 100%">
+ <el-option label="璐ㄩ噺闂" value="quality" />
+ <el-option label="瑙勬牸涓嶇" value="specification" />
+ <el-option label="鏁伴噺閿欒" value="quantity" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞">
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" />
+ </el-form-item>
+ </el-form>
+ </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 Pagination from '@/components/PIMTable/Pagination.vue'
+import {listPage,add,update,del} from "@/api/procurementManagement/returnManagement.js"
+import {listPageCopy} from "@/api/procurementManagement/arrivalManagement.js"
+
+onMounted(() => {
+ getList()
+ list()
+})
+const onList = ref([])
+const list = () =>{
+ listPageCopy({current:-1}).then(res=>{
+ if(res.code === 200){
+ onList.value = res.data.records
+ }
+ })
+}
+const tableData = ref([])
+const getList = () => {
+ loading.value = true
+ listPage({...searchForm,...pagination}).then(res =>{
+ if(res.code === 200){
+ tableData.value = res.data.records
+ console.log(tableData.value)
+ total.value = res.data.total
+ loading.value = false
+ }
+ })
+}
+
+const loading = ref(false)
+const dialogVisible = ref(false)
+const dialogType = ref('add')
+const selectedRows = ref([])
+
+
+const pagination = reactive({
+ current: 1,
+ size: 10
+})
+
+const total = ref(0)
+
+const searchForm = reactive({
+ returnNo: '',
+ returnType: ''
+})
+
+const formData = reactive({
+ returnType: '',
+ relatedNo: '',
+ supplierName: '',
+ returnReason: '',
+ remark: '',
+ status: ''
+})
+
+const getReturnTypeText = (type) => {
+ const typeMap = { purchase: '閲囪喘閫�璐�', quality: '璐ㄦ閫�璐�' }
+ return typeMap[type] || '鏈煡'
+}
+
+const getStatusType = (status) => {
+ const statusMap = { pending: 'warning', approved: 'success', returned: 'info' }
+ return statusMap[status] || 'info'
+}
+
+const getStatusText = (status) => {
+ const statusMap = { pending: '寰呭鏍�', approved: '宸插鏍�', returned: '宸查��璐�' }
+ return statusMap[status] || '鏈煡'
+}
+
+const handleSearch = () => {
+ loading.value = true
+ getList()
+}
+
+const resetSearch = () => {
+ Object.assign(searchForm, { returnNo: '', returnType: '' })
+}
+
+const openDialog = (type, row = {}) => {
+ dialogType.value = type
+ obj.id = row.id
+ if (type === 'edit' && row.id) {
+ Object.assign(formData, {
+ returnType: row.returnType,
+ relatedNo: row.relatedNo,
+ supplierName: row.supplierName,
+ returnReason: row.returnReason,
+ remark: row.remark,
+ status: row.status
+ })
+ } else {
+ Object.assign(formData, {
+ returnType: '',
+ relatedNo: '',
+ supplierName: '',
+ returnReason: '',
+ remark: '',
+ status: 'pending'
+ })
+ }
+ dialogVisible.value = true
+}
+const obj = reactive({
+ id: ''
+})
+const handleSubmit = () => {
+ if (dialogType.value === 'add') {
+ formData.status = 'pending'
+ add(formData).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ getList()
+ }
+ })
+ }else{
+ update({...formData,...obj}).then(res => {
+ if(res.code === 200){
+ ElMessage.success('淇敼鎴愬姛')
+ getList()
+ }
+ })
+ }
+ dialogVisible.value = false
+}
+
+const handleApprove = (row) => {
+ row.status = 'approved'
+ update(row).then(res => {
+ if(res.code === 200){
+ ElMessage.success('瀹℃牳鎴愬姛')
+ getList()
+ }
+ })
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭畾瑕佸垹闄よ繖鏉¤褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ let ids = [row.id]
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
+ })
+}
+
+const handleBatchDelete = () => {
+ let ids = selectedRows.value.map(item => item.id)
+ del(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
+}
+
+const handleSelectionChange = (rows) => {
+ selectedRows.value = rows
+}
+</script>
+
+<style scoped>
+.app-container { padding: 20px; }
+.search-card { margin-bottom: 20px; }
+.table-card { margin-bottom: 20px; }
+.table-header { margin-bottom: 20px; }
+</style>
diff --git a/src/views/procurementManagement/transferManagement/index.vue b/src/views/procurementManagement/transferManagement/index.vue
new file mode 100644
index 0000000..3646449
--- /dev/null
+++ b/src/views/procurementManagement/transferManagement/index.vue
@@ -0,0 +1,431 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储杩囨护鍖� -->
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="閲囪喘鍚堝悓鍙�">
+ <el-input v-model="searchForm.purchaseContractNumber" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�">
+ <el-input v-model="searchForm.supplierName" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="search">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 琛ㄦ牸灞曠ず鍖� -->
+ <el-table :data="orderList" border v-loading="loading" height="calc(100vh - 12em)">
+ <!-- 娣诲姞搴忓彿鍒� -->
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column prop="purchaseContractNumber" label="閲囪喘鍚堝悓鍙�" show-overflow-tooltip />
+ <el-table-column prop="supplierName" label="渚涘簲鍟�" show-overflow-tooltip />
+ <el-table-column label="浠樻鐘舵��">
+ <template #default="scope">
+ <el-tag
+ :type="getPaymentStatusType(scope.row.paymentStatus)"
+ size="small"
+ >
+ {{ getPaymentStatusText(scope.row.paymentStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏀惰揣鐘舵��">
+ <template #default="scope">
+ <el-tag
+ :type="getReceiptStatusType(scope.row.receiptStatus)"
+ size="small"
+ >
+ {{ getReceiptStatusText(scope.row.receiptStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="receivedQuantity" label="宸叉敹璐ф暟閲�"/>
+ <el-table-column prop="unreceivedQuantity" label="鏈敹璐ф暟閲�"/>
+ <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <template #default="scope">
+ <el-button
+ type="primary"
+ size="small"
+ @click="confirmReceipter(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"
+ />
+ <!-- 纭鏀惰揣瀵硅瘽妗� -->
+ <FormDialog v-model="receiptDialogVisible" title="纭鏀惰揣" :width="'70%'" @close="receiptDialogVisible = false" @confirm="submitReceipt" @cancel="receiptDialogVisible = false">
+ <el-form :model="receiptForm" label-width="120px" ref="formRef">
+ <el-form-item label="閲囪喘鍚堝悓鍙�">
+ <el-input v-model="receiptForm.purchaseContractNumber" disabled />
+ </el-form-item>
+ <el-form-item label="寮傚父鍘熷洜">
+ <el-input
+ v-model="receiptForm.exceptionReason"
+ type="textarea"
+ placeholder="璇疯緭鍏ュ紓甯稿師鍥狅紙涓嶅悎鏍兼椂濉啓锛�"
+ />
+ </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="鏄惁鍚堟牸" width="100">
+ <template #default="scope">
+ <el-select v-model="scope.row.isQualified" placeholder="璇烽�夋嫨">
+ <el-option label="鍚堟牸" value="1" />
+ <el-option label="涓嶅悎鏍�" value="2" />
+ </el-select>
+ </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>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import FormDialog from '@/components/Dialog/FormDialog.vue';
+import {ref, onMounted, getCurrentInstance} from 'vue'
+import {
+ getPurchaseOrders,
+ confirmReceipt,
+ addPurchaseException
+} from '@/api/procurementManagement/transferManagement.js'
+import {selectProductRecordListByPuechaserId, addSutockIn, updateStockIn} from "@/api/inventoryManagement/stockIn.js";
+import useUserStore from "@/store/modules/user.js";
+
+
+const userStore = useUserStore()
+const { proxy } = getCurrentInstance()
+
+// 鏁版嵁瀹氫箟
+const orderList = ref([])
+const receiptDialogVisible = ref(false)
+const receiptForm = ref({
+ purchaseContractNumber: '',
+ exceptionReason: '',
+ purchaseLedgerId: '',
+})
+const operationType = ref('')// 鎿嶄綔绫诲瀷: 'add' 鎴� 'edit'
+const productList = ref([]);// 浜у搧鍒楄〃鏁版嵁
+const loadingProducts = ref(false);// 浜у搧鍔犺浇鐘舵��
+const selectedRows = ref([]);
+const loading = ref(false);
+const total = ref(0); // 鎬昏褰曟暟
+// 鎼滅储琛ㄥ崟
+const searchForm = ref({
+ purchaseContractNumber: '',
+ supplierName: '',
+})
+// 鍒嗛〉鏁版嵁
+const page = reactive({
+ current: 1,
+ size: 100, // 姣忛〉鏄剧ず鏁伴噺
+});
+
+// 鍒嗛〉鍙樺寲澶勭悊
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getReceiptOrders(); // 閲嶆柊鑾峰彇鏁版嵁
+};
+// 鑾峰彇璁㈠崟鍒楄〃
+const getReceiptOrders = async () => {
+ loading.value = true;
+ try {
+ const response = await getPurchaseOrders({
+ ...searchForm.value,
+ current: page.current,
+ size: page.size
+ });
+ // 浣跨敤 Promise.all 澶勭悊鎵�鏈夊紓姝ヨ姹�
+ const processedOrders = await Promise.all(response.data.records.map(async (order) => {
+ // 绛夊緟寮傛鑾峰彇浜у搧璁板綍
+ const productRes = await selectProductRecordListByPuechaserId({
+ purchaseContractNumber: order.purchaseContractNumber
+ });
+
+ // 纭繚 productRes.data 瀛樺湪
+ if (productRes && productRes.data && Array.isArray(productRes.data)) {
+ // 璁$畻鎬绘暟閲�
+ order.totalQuantity = productRes.data.reduce((acc, cur) => acc + (cur.quantity || 0), 0);
+ // 璁$畻鏈敹璐ф暟閲�
+ order.unreceivedQuantity = productRes.data.reduce((acc, cur) => acc + (cur.quantity0 || 0), 0);
+ // 璁$畻宸叉敹璐ф暟閲�
+ order.receivedQuantity = order.totalQuantity - order.unreceivedQuantity;
+
+ // 淇鐘舵�佸垽鏂�昏緫锛堜娇鐢� === 杩涜姣旇緝锛�
+ if (order.unreceivedQuantity === 0) {
+ order.paymentStatus = 1;
+ order.receiptStatus = 1;
+ } else if (order.receivedQuantity === 0) {
+ order.paymentStatus = 3;
+ order.receiptStatus = 3;
+ } else {
+ order.paymentStatus = 2;
+ order.receiptStatus = 2;
+ }
+ } else {
+ // 濡傛灉娌℃湁浜у搧璁板綍锛岃缃粯璁ゅ��
+ order.totalQuantity = 0;
+ order.unreceivedQuantity = 0;
+ order.receivedQuantity = 0;
+ order.receiptStatus = 3; // 鏈叆搴�
+ }
+
+ return order;
+ }));
+
+ // 姝g‘璧嬪�肩粰 orderList
+ orderList.value = processedOrders;
+ total.value = response.data.total;
+ } catch (error) {
+ console.error('鑾峰彇璁㈠崟鍒楄〃澶辫触:', error);
+ proxy.$modal.msgError('鑾峰彇璁㈠崟鍒楄〃澶辫触');
+ } finally {
+ loading.value = false;
+ }
+}
+
+// 浠樻鐘舵�佹樉绀哄鐞�
+const getPaymentStatusText = (status) => {
+ const statusMap = { '1': '宸蹭粯娆�', '2': '閮ㄥ垎浠樻', '3': '鏈粯娆�' }
+ return statusMap[status] || '鏈煡'
+}
+
+const getPaymentStatusType = (status) => {
+ const typeMap = { '1': 'success', '2': 'warning', '3': 'danger' }
+ return typeMap[status] || 'info'
+}
+
+// 鏀惰揣鐘舵�佸鐞�
+const getReceiptStatusText = (status) => {
+ const statusMap = { '1': '鏀惰揣瀹屾垚', '2': '閮ㄥ垎鍏ュ簱', '3': '鏈叆搴�' }
+ return statusMap[status] || '鏈煡'
+}
+
+const getReceiptStatusType = (status) => {
+ const typeMap = { '1': 'success', '2': 'warning', '3': 'info' }
+ return typeMap[status] || 'info'
+}
+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 updatePro = async () => {
+ 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()
+ getReceiptOrders() // 鍒锋柊鍒楄〃
+}
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter(item => item.id);
+}
+// 鎵撳紑寮规-纭鏀惰揣
+const confirmReceipter = (row) => {
+ receiptForm.value = {
+ purchaseContractNumber: row.purchaseContractNumber,
+ purchaseLedgerId: row.id,
+ exceptionReason: ''
+ }
+ selectedRows.value = []
+ receiptDialogVisible.value = true
+ fetchProductsByContract()
+}
+
+const fetchProductsByContract = async () =>
+{
+ try {
+ loadingProducts.value = true
+ // 鏍规嵁鍚堝悓鏌ヨ浜у搧璁板綍
+ const productRes = await selectProductRecordListByPuechaserId({
+ purchaseContractNumber: receiptForm.value.purchaseContractNumber
+ });
+ console.log('productRes:', productRes)
+ operationType.value = 'add'
+ 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),
+ }))
+ selectedRows.value = productList.value
+ } catch (error) {
+ console.error('鏌ヨ浜у搧璁板綍澶辫触:', error)
+ proxy.$modal.msgError('鏌ヨ浜у搧璁板綍澶辫触')
+ productList.value = [];
+ } finally {
+ loadingProducts.value = false
+ }
+}
+
+
+// 鎻愪氦鏀惰揣纭
+const submitReceipt = async () => {
+ 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
+ }
+ loading.value = true
+ // 鍑嗗鎻愪氦鏁版嵁 - 淇敼涓哄悗绔渶瑕佺殑鏍煎紡
+ const stockInData = {
+ // 鍏ュ簱鍗曞熀鏈俊鎭�
+ ...receiptForm.value,
+ nickName: userStore.nickName,
+ details: selectedRows.value.map(product => ({
+ id: product.id,
+ inboundQuantity: Number(product.quantityStock)
+ })),
+ };
+ //濡傛灉浜у搧鍚堟牸
+ if(productList.value.every(product => product.isQualified === '1')){
+ await addSutockIn(stockInData)
+
+ proxy.$modal.msgSuccess('纭鏀惰揣,鍏ュ簱鎴愬姛')
+ }else{
+ stockInData.details.forEach(item => {
+ const ProcurementExceptionRecord = {
+ purchaseContractNumber: receiptForm.value.purchaseContractNumber,
+ purchaseLedgerId: receiptForm.value.purchaseLedgerId,
+ exceptionNum: item.inboundQuantity,
+ exceptionReason: receiptForm.value.exceptionReason
+ }
+ addPurchaseException(ProcurementExceptionRecord).then(response => {
+ proxy.$modal.msgSuccess('浜у搧涓嶅悎鏍硷紝閲囪喘寮傚父璁板綍鎴愬姛')
+ })
+ })
+ }
+ closeDia()
+ getReceiptOrders() // 鍒锋柊鍒楄〃
+
+ } catch (error) {
+ console.error('鎻愪氦澶辫触:', error)
+ if (!error.errors) {
+ proxy.$modal.msgError('鎿嶄綔澶辫触锛岃閲嶈瘯')
+ }
+ } finally {
+ loading.value = false
+ }
+}
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.$refs.formRef.resetFields()
+ receiptDialogVisible.value = false
+}
+// 鎼滅储鍜岄噸缃�
+const search = () => {
+ getReceiptOrders()
+}
+
+const resetSearch = () => {
+ searchForm.value = {
+ purchaseContractNumber: '',
+ supplierName: '',
+ }
+ getReceiptOrders()
+}
+
+onMounted(() => {
+ getReceiptOrders()
+})
+</script>
diff --git a/src/views/productManagement/productIdentifier/index.vue b/src/views/productManagement/productIdentifier/index.vue
new file mode 100644
index 0000000..519f745
--- /dev/null
+++ b/src/views/productManagement/productIdentifier/index.vue
@@ -0,0 +1,819 @@
+<template>
+ <div class="app-container">
+ <el-card class="box-card">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20"
+ class="search-row">
+ <el-col :span="6">
+ <el-input v-model="searchForm.productName"
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+ clearable
+ @keyup.enter="handleSearch">
+ <template #prefix>
+ <el-icon>
+ <Search />
+ </el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.identifierType"
+ placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷"
+ clearable>
+ <el-option label="浜岀淮鐮�"
+ value="浜岀淮鐮�"></el-option>
+ <el-option label="闃蹭吉鐮�"
+ value="闃蹭吉鐮�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ clearable>
+ <el-option label="宸茬敓鎴�"
+ value="宸茬敓鎴�"></el-option>
+ <el-option label="宸插垎閰�"
+ value="宸插垎閰�"></el-option>
+ <el-option label="宸蹭娇鐢�"
+ value="宸蹭娇鐢�"></el-option>
+ <el-option label="宸蹭綔搴�"
+ value="宸蹭綔搴�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary"
+ @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ <el-button style="float: right;"
+ type="primary"
+ @click="handleAdd">
+ 鏂板鏍囪瘑
+ </el-button>
+ </el-col>
+ </el-row>
+ <!-- 浜у搧鏍囪瘑鍒楄〃 -->
+ <el-table :data="filteredList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)">
+ <el-table-column prop="id"
+ label="ID"
+ width="80"
+ align="center" />
+ <el-table-column prop="productName"
+ label="浜у搧鍚嶇О"
+ width="150" />
+ <el-table-column prop="productCode"
+ label="浜у搧缂栫爜"
+ width="120" />
+ <el-table-column prop="batchNo"
+ label="鎵规鍙�"
+ width="120" />
+ <el-table-column prop="identifierType"
+ label="鏍囪瘑绫诲瀷"
+ width="100">
+ <template #default="scope">
+ <el-tag :type="getIdentifierTypeType(scope.row.identifierType)">
+ {{ scope.row.identifierType }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="identifierCode"
+ label="鏍囪瘑鐮�" />
+ <el-table-column prop="status"
+ label="鐘舵��"
+ width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="generateTime"
+ label="鐢熸垚鏃堕棿"
+ width="160" />
+ <el-table-column label="鎿嶄綔"
+ fixed="right"
+ align="center"
+ width="280">
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ @click="handleView(scope.row)">鏌ョ湅</el-button>
+ <el-button link
+ type="primary"
+ @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button link
+ type="success"
+ @click="generateQRCode(scope.row)">鐢熸垚浜岀淮鐮�</el-button>
+ <el-button link
+ type="primary"
+ @click="handleExport(scope.row)">瀵煎嚭</el-button>
+ <el-button link
+ type="primary"
+ @click="handleReassign(scope.row)"
+ v-if="scope.row.status === '宸插垎閰�'">閲嶆柊鍒嗛厤</el-button>
+ <el-button link
+ type="danger"
+ @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- 鍒嗛〉 -->
+ <pagination :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.currentPage"
+ :limit="pagination.pageSize"
+ @pagination="handleCurrentChange" />
+ </el-card>
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog v-model="dialogVisible"
+ :title="dialogTitle"
+ width="700px">
+ <el-form :model="form"
+ :rules="rules"
+ ref="formRef"
+ label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浜у搧鍚嶇О"
+ prop="productName">
+ <el-input v-model="form.productName"
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浜у搧缂栫爜"
+ prop="productCode">
+ <el-input v-model="form.productCode"
+ placeholder="璇疯緭鍏ヤ骇鍝佺紪鐮�"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鎵规鍙�"
+ prop="batchNo">
+ <el-input v-model="form.batchNo"
+ placeholder="璇疯緭鍏ユ壒娆″彿"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏍囪瘑绫诲瀷"
+ prop="identifierType">
+ <el-select v-model="form.identifierType"
+ placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷"
+ style="width: 100%">
+ <el-option label="浜岀淮鐮�"
+ value="浜岀淮鐮�"></el-option>
+ <el-option label="闃蹭吉鐮�"
+ value="闃蹭吉鐮�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢熸垚鏁伴噺"
+ prop="quantity">
+ <el-input-number v-model="form.quantity"
+ :min="1"
+ :max="10000"
+ style="width: 100%"></el-input-number>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐘舵��"
+ prop="status">
+ <el-select v-model="form.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ style="width: 100%">
+ <el-option label="宸茬敓鎴�"
+ value="宸茬敓鎴�"></el-option>
+ <el-option label="宸插垎閰�"
+ value="宸插垎閰�"></el-option>
+ <el-option label="宸蹭娇鐢�"
+ value="宸蹭娇鐢�"></el-option>
+ <el-option label="宸蹭綔搴�"
+ value="宸蹭綔搴�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞"
+ prop="remark">
+ <el-input type="textarea"
+ v-model="form.remark"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+ rows="3"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="handleSubmit">纭� 瀹�</el-button>
+ <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 鏍囪瘑鐢熸垚瀵硅瘽妗� -->
+ <el-dialog v-model="generateDialogVisible"
+ title="鏍囪瘑鐢熸垚"
+ width="500px">
+ <el-form label-width="100px">
+ <el-form-item label="浜у搧鍚嶇О">
+ <span>{{ currentProduct.productName }}</span>
+ </el-form-item>
+ <el-form-item label="浜у搧缂栫爜">
+ <span>{{ currentProduct.productCode }}</span>
+ </el-form-item>
+ <el-form-item label="鎵规鍙�">
+ <span>{{ currentProduct.batchNo }}</span>
+ </el-form-item>
+ <el-form-item label="鏍囪瘑绫诲瀷">
+ <span>{{ currentProduct.identifierType }}</span>
+ </el-form-item>
+ <el-form-item label="鐢熸垚鏁伴噺"
+ prop="generateQuantity">
+ <el-input-number v-model="generateQuantity"
+ :min="1"
+ :max="10000"
+ style="width: 100%"></el-input-number>
+ </el-form-item>
+ <el-form-item label="缂栫爜瑙勫垯"
+ prop="codeRule">
+ <el-select v-model="codeRule"
+ placeholder="璇烽�夋嫨缂栫爜瑙勫垯"
+ style="width: 100%">
+ <el-option label="浜у搧缂栫爜+鎵规鍙�+搴忓彿"
+ value="浜у搧缂栫爜+鎵规鍙�+搴忓彿"></el-option>
+ <el-option label="鏃堕棿鎴�+闅忔満鏁�"
+ value="鏃堕棿鎴�+闅忔満鏁�"></el-option>
+ <el-option label="鑷畾涔夎鍒�"
+ value="鑷畾涔夎鍒�"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鑷畾涔夊墠缂�"
+ prop="customPrefix"
+ v-if="codeRule === '鑷畾涔夎鍒�'">
+ <el-input v-model="customPrefix"
+ placeholder="璇疯緭鍏ヨ嚜瀹氫箟鍓嶇紑"></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="generateIdentifiers">鐢� 鎴�</el-button>
+ <el-button @click="generateDialogVisible = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 閲嶆柊鍒嗛厤瀵硅瘽妗� -->
+ <el-dialog v-model="reassignDialogVisible"
+ title="閲嶆柊鍒嗛厤鏍囪瘑"
+ width="500px">
+ <el-form label-width="100px">
+ <el-form-item label="浜у搧鍚嶇О">
+ <span>{{ currentProduct.productName }}</span>
+ </el-form-item>
+ <el-form-item label="鏍囪瘑鐮�">
+ <span>{{ currentProduct.identifierCode }}</span>
+ </el-form-item>
+ <el-form-item label="鏂版壒娆″彿"
+ prop="newBatchNo">
+ <el-input v-model="newBatchNo"
+ placeholder="璇疯緭鍏ユ柊鎵规鍙�"></el-input>
+ </el-form-item>
+ <el-form-item label="鍒嗛厤鍘熷洜"
+ prop="reassignReason">
+ <el-input type="textarea"
+ v-model="reassignReason"
+ rows="3"
+ placeholder="璇疯緭鍏ラ噸鏂板垎閰嶅師鍥�"></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="saveReassign">纭� 瀹�</el-button>
+ <el-button @click="reassignDialogVisible = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 浜岀淮鐮侀瑙堝璇濇 -->
+ <el-dialog v-model="qrCodeDialogVisible"
+ title="浜岀淮鐮侀瑙�"
+ width="500px"
+ center>
+ <div class="qr-preview-container">
+ <div v-if="qrCodeUrl"
+ class="qr-image-container">
+ <img :src="qrCodeUrl"
+ alt="浜岀淮鐮�"
+ class="qr-image" />
+ <div class="qr-info">
+ <p><strong>浜у搧鍚嶇О锛�</strong>{{ currentQRProduct.productName }}</p>
+ <p><strong>浜у搧缂栫爜锛�</strong>{{ currentQRProduct.productCode }}</p>
+ <p><strong>鎵规鍙凤細</strong>{{ currentQRProduct.batchNo }}</p>
+ <p><strong>鏍囪瘑鐮侊細</strong>{{ currentQRProduct.identifierCode }}</p>
+ <p><strong>鏍囪瘑绫诲瀷锛�</strong>{{ currentQRProduct.identifierType }}</p>
+ </div>
+ </div>
+ <div v-else
+ class="qr-loading">
+ <el-icon class="is-loading">
+ <Loading />
+ </el-icon>
+ <p>姝e湪鐢熸垚浜岀淮鐮�...</p>
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="qrCodeDialogVisible = false">鍏抽棴</el-button>
+ <el-button v-if="qrCodeUrl"
+ type="primary"
+ @click="copyQRContent"
+ icon="CopyDocument">
+ 澶嶅埗鍐呭
+ </el-button>
+ <el-button v-if="qrCodeUrl"
+ type="success"
+ @click="downloadQRCode"
+ icon="Download">
+ 涓嬭浇浜岀淮鐮�
+ </el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { ref, reactive, computed } from "vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import { Plus, Search, Loading, Download } from "@element-plus/icons-vue";
+ import Pagination from "@/components/PIMTable/Pagination.vue";
+ import QRCode from "qrcode";
+
+ // 鍝嶅簲寮忔暟鎹�
+ const loading = ref(false);
+ const searchForm = reactive({
+ productName: "",
+ identifierType: "",
+ status: "",
+ });
+
+ const identifierList = ref([
+ {
+ id: 1,
+ productName: "宸ヤ笟浼犳劅鍣ˋ鍨�",
+ productCode: "SENSOR001",
+ batchNo: "B202312001",
+ identifierType: "浜岀淮鐮�",
+ identifierCode: "QR_SENSOR001_B202312001_001",
+ status: "宸插垎閰�",
+ generateTime: "2023-12-01 10:00:00",
+ remark: "閲嶈浜у搧鏍囪瘑",
+ },
+ {
+ id: 2,
+ productName: "鎺у埗闈㈡澘B鍨�",
+ productCode: "PANEL002",
+ batchNo: "B202312002",
+ identifierType: "闃蹭吉鐮�",
+ identifierCode: "SEC_PANEL002_B202312002_001",
+ status: "宸茬敓鎴�",
+ generateTime: "2023-12-02 14:30:00",
+ remark: "甯歌浜у搧鏍囪瘑",
+ },
+ {
+ id: 3,
+ productName: "鏁版嵁閲囬泦鍣–鍨�",
+ productCode: "COLLECTOR003",
+ batchNo: "B202312003",
+ identifierType: "闃蹭吉鐮�",
+ identifierCode: "SEC_COLLECTOR003_B202312003_001",
+ status: "宸蹭娇鐢�",
+ generateTime: "2023-12-03 09:15:00",
+ remark: "娴嬭瘯浜у搧鏍囪瘑",
+ },
+ ]);
+
+ const pagination = reactive({
+ total: 3,
+ currentPage: 1,
+ pageSize: 10,
+ });
+
+ const dialogVisible = ref(false);
+ const dialogTitle = ref("鏂板鏍囪瘑");
+ const form = reactive({
+ productName: "",
+ productCode: "",
+ batchNo: "",
+ identifierType: "",
+ quantity: 1,
+ status: "宸茬敓鎴�",
+ remark: "",
+ });
+
+ const rules = {
+ productName: [{ required: true, message: "璇疯緭鍏ヤ骇鍝佸悕绉�", trigger: "blur" }],
+ productCode: [{ required: true, message: "璇疯緭鍏ヤ骇鍝佺紪鐮�", trigger: "blur" }],
+ batchNo: [{ required: true, message: "璇疯緭鍏ユ壒娆″彿", trigger: "blur" }],
+ identifierType: [
+ { required: true, message: "璇烽�夋嫨鏍囪瘑绫诲瀷", trigger: "change" },
+ ],
+ quantity: [{ required: true, message: "璇疯緭鍏ョ敓鎴愭暟閲�", trigger: "blur" }],
+ status: [{ required: true, message: "璇烽�夋嫨鐘舵��", trigger: "change" }],
+ };
+
+ const isEdit = ref(false);
+ const editId = ref(null);
+ const generateDialogVisible = ref(false);
+ const reassignDialogVisible = ref(false);
+ const currentProduct = ref({});
+ const generateQuantity = ref(1);
+ const codeRule = ref("");
+ const customPrefix = ref("");
+ const newBatchNo = ref("");
+ const reassignReason = ref("");
+ const formRef = ref();
+
+ // 浜岀淮鐮佺浉鍏冲彉閲�
+ const qrCodeDialogVisible = ref(false);
+ const qrCodeUrl = ref("");
+ const currentQRProduct = ref({});
+
+ // 璁$畻灞炴��
+ const filteredList = computed(() => {
+ let list = identifierList.value;
+ if (searchForm.productName) {
+ list = list.filter(item =>
+ item.productName.includes(searchForm.productName)
+ );
+ }
+ if (searchForm.identifierType) {
+ list = list.filter(
+ item => item.identifierType === searchForm.identifierType
+ );
+ }
+ if (searchForm.status) {
+ list = list.filter(item => item.status === searchForm.status);
+ }
+ return list;
+ });
+
+ // 鏂规硶
+ const getIdentifierTypeType = type => {
+ const typeMap = {
+ 浜岀淮鐮�: "success",
+ 闃蹭吉鐮�: "warning",
+ };
+ return typeMap[type] || "info";
+ };
+
+ const getStatusType = status => {
+ const statusMap = {
+ 宸茬敓鎴�: "info",
+ 宸插垎閰�: "primary",
+ 宸蹭娇鐢�: "success",
+ 宸蹭綔搴�: "danger",
+ };
+ return statusMap[status] || "info";
+ };
+
+ const handleSearch = () => {
+ // 鎼滅储閫昏緫宸插湪computed涓鐞�
+ };
+
+ const resetSearch = () => {
+ searchForm.productName = "";
+ searchForm.identifierType = "";
+ searchForm.status = "";
+ };
+
+ const handleAdd = () => {
+ dialogTitle.value = "鏂板鏍囪瘑";
+ isEdit.value = false;
+ form.productName = "";
+ form.productCode = "";
+ form.batchNo = "";
+ form.identifierType = "";
+ form.quantity = 1;
+ form.status = "宸茬敓鎴�";
+ form.remark = "";
+ dialogVisible.value = true;
+ };
+
+ const handleView = row => {
+ // 鏌ョ湅鏍囪瘑璇︽儏
+ ElMessage.info("鏌ョ湅鏍囪瘑璇︽儏鍔熻兘寰呭疄鐜�");
+ };
+
+ const handleEdit = row => {
+ dialogTitle.value = "缂栬緫鏍囪瘑";
+ isEdit.value = true;
+ editId.value = row.id;
+ Object.assign(form, row);
+ dialogVisible.value = true;
+ };
+
+ const handleExport = row => {
+ // 瀵煎嚭鏍囪瘑
+ ElMessage.success(`宸插鍑烘爣璇�: ${row.identifierCode}`);
+ };
+
+ const handleReassign = row => {
+ currentProduct.value = row;
+ newBatchNo.value = "";
+ reassignReason.value = "";
+ reassignDialogVisible.value = true;
+ };
+
+ const handleDelete = row => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ユ爣璇嗗悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = identifierList.value.findIndex(item => item.id === row.id);
+ if (index > -1) {
+ identifierList.value.splice(index, 1);
+ pagination.total--;
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ }
+ });
+ };
+
+ // 鐢熸垚浜岀淮鐮�
+ const generateQRCode = async row => {
+ try {
+ // 妫�鏌ュ繀瑕佸瓧娈�
+ if (!row.productName || !row.productCode || !row.batchNo) {
+ ElMessage.warning("浜у搧淇℃伅涓嶅畬鏁达紝鏃犳硶鐢熸垚浜岀淮鐮�");
+ return;
+ }
+
+ currentQRProduct.value = row;
+ qrCodeUrl.value = "";
+ qrCodeDialogVisible.value = true;
+
+ // 鏋勫缓浜岀淮鐮佸唴瀹�
+ let qrContent = "";
+ if (row.identifierType === "浜岀淮鐮�") {
+ qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`;
+ } else if (row.identifierType === "闃蹭吉鐮�") {
+ // 闃蹭吉鐮佹牸寮忥細SEC_浜у搧缂栫爜_鎵规鍙穇鏃堕棿鎴砡闅忔満鏁�
+ const timestamp = Date.now();
+ const random = Math.random().toString(36).substr(2, 8);
+ qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`;
+ }
+
+ // 鐢熸垚浜岀淮鐮�
+ qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
+ width: 256,
+ margin: 2,
+ color: {
+ dark: "#000000",
+ light: "#FFFFFF",
+ },
+ errorCorrectionLevel: row.identifierType === "闃蹭吉鐮�" ? "H" : "M",
+ });
+
+ ElMessage.success("浜岀淮鐮佺敓鎴愭垚鍔燂紒");
+ } catch (error) {
+ console.error("鐢熸垚浜岀淮鐮佸け璐�:", error);
+ ElMessage.error("鐢熸垚浜岀淮鐮佸け璐ワ細" + error.message);
+ qrCodeDialogVisible.value = false;
+ }
+ };
+
+ // 涓嬭浇浜岀淮鐮�
+ const downloadQRCode = () => {
+ if (!qrCodeUrl.value) {
+ ElMessage.warning("璇峰厛鐢熸垚浜岀淮鐮�");
+ return;
+ }
+
+ const a = document.createElement("a");
+ a.href = qrCodeUrl.value;
+ a.download = `${currentQRProduct.value.productName}_${
+ currentQRProduct.value.identifierType
+ }_${new Date().getTime()}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ ElMessage.success("涓嬭浇鎴愬姛锛�");
+ };
+
+ // 澶嶅埗浜岀淮鐮佸唴瀹�
+ const copyQRContent = async () => {
+ if (!currentQRProduct.value) {
+ ElMessage.warning("娌℃湁鍙鍒剁殑鍐呭");
+ return;
+ }
+
+ try {
+ let content = "";
+ if (currentQRProduct.value.identifierType === "浜岀淮鐮�") {
+ content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`;
+ } else if (currentQRProduct.value.identifierType === "闃蹭吉鐮�") {
+ const timestamp = Date.now();
+ const random = Math.random().toString(36).substr(2, 8);
+ content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`;
+ }
+
+ await navigator.clipboard.writeText(content);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ } catch (error) {
+ // 闄嶇骇鏂规
+ const textArea = document.createElement("textarea");
+ textArea.value = content;
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textArea);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ }
+ };
+
+ const generateIdentifiers = () => {
+ if (!codeRule.value) {
+ ElMessage.warning("璇烽�夋嫨缂栫爜瑙勫垯");
+ return;
+ }
+
+ // 鐢熸垚鏍囪瘑鐨勯�昏緫
+ const newIdentifiers = [];
+ for (let i = 1; i <= generateQuantity.value; i++) {
+ let identifierCode = "";
+ if (codeRule.value === "浜у搧缂栫爜+鎵规鍙�+搴忓彿") {
+ identifierCode = `${currentProduct.value.productCode}_${
+ currentProduct.value.batchNo
+ }_${String(i).padStart(3, "0")}`;
+ } else if (codeRule.value === "鏃堕棿鎴�+闅忔満鏁�") {
+ identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
+ } else if (codeRule.value === "鑷畾涔夎鍒�") {
+ identifierCode = `${customPrefix.value || "CUSTOM"}_${Date.now()}_${i}`;
+ }
+
+ newIdentifiers.push({
+ id: Math.max(...identifierList.value.map(item => item.id)) + i,
+ productName: currentProduct.value.productName,
+ productCode: currentProduct.value.productCode,
+ batchNo: currentProduct.value.batchNo,
+ identifierType: currentProduct.value.identifierType,
+ identifierCode: identifierCode,
+ status: "宸茬敓鎴�",
+ generateTime: new Date().toLocaleString(),
+ remark: "鎵归噺鐢熸垚",
+ });
+ }
+
+ identifierList.value.push(...newIdentifiers);
+ pagination.total += newIdentifiers.length;
+ ElMessage.success(`鎴愬姛鐢熸垚 ${newIdentifiers.length} 涓爣璇哷);
+ generateDialogVisible.value = false;
+ };
+
+ const saveReassign = () => {
+ if (!newBatchNo.value) {
+ ElMessage.warning("璇疯緭鍏ユ柊鎵规鍙�");
+ return;
+ }
+
+ const index = identifierList.value.findIndex(
+ item => item.id === currentProduct.value.id
+ );
+ if (index > -1) {
+ identifierList.value[index].batchNo = newBatchNo.value;
+ identifierList.value[index].status = "宸插垎閰�";
+ ElMessage.success("鏍囪瘑閲嶆柊鍒嗛厤鎴愬姛");
+ reassignDialogVisible.value = false;
+ }
+ };
+
+ const handleSubmit = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ if (isEdit.value) {
+ // 缂栬緫
+ const index = identifierList.value.findIndex(
+ item => item.id === editId.value
+ );
+ if (index > -1) {
+ identifierList.value[index] = { ...form, id: editId.value };
+ ElMessage.success("缂栬緫鎴愬姛");
+ }
+ } else {
+ // 鏂板
+ const newId =
+ Math.max(...identifierList.value.map(item => item.id)) + 1;
+
+ // 鏍规嵁鏍囪瘑绫诲瀷鐢熸垚涓嶅悓鐨勬爣璇嗙爜
+ let identifierCode = "";
+ if (form.identifierType === "浜岀淮鐮�") {
+ identifierCode = `QR_${form.productCode}_${form.batchNo}_001`;
+ } else if (form.identifierType === "闃蹭吉鐮�") {
+ identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`;
+ }
+
+ identifierList.value.push({
+ ...form,
+ id: newId,
+ identifierCode: identifierCode,
+ generateTime: new Date().toLocaleString(),
+ });
+ pagination.total++;
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ }
+ });
+ };
+
+ const handleCurrentChange = val => {
+ pagination.currentPage = val.page;
+ pagination.pageSize = val.limit;
+ };
+</script>
+
+<style scoped>
+ .search-row {
+ margin-bottom: 20px;
+ }
+
+ .quick-actions-row {
+ margin-bottom: 20px;
+ }
+
+ .quick-actions-row .el-alert {
+ margin-bottom: 0;
+ }
+
+ .quick-actions-row .el-alert p {
+ margin: 5px 0;
+ font-size: 14px;
+ line-height: 1.5;
+ }
+
+ /* 浜岀淮鐮侀瑙堟牱寮� */
+ .qr-preview-container {
+ text-align: center;
+ padding: 20px;
+ }
+
+ .qr-image-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ }
+
+ .qr-image {
+ max-width: 100%;
+ height: auto;
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ .qr-info {
+ text-align: left;
+ background: #f8f9fa;
+ padding: 15px;
+ border-radius: 8px;
+ min-width: 300px;
+ }
+
+ .qr-info p {
+ margin: 8px 0;
+ color: #666;
+ font-size: 14px;
+ }
+
+ .qr-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 15px;
+ padding: 40px 0;
+ }
+
+ .qr-loading .el-icon {
+ font-size: 32px;
+ color: #409eff;
+ }
+
+ .qr-loading p {
+ color: #666;
+ margin: 0;
+ }
+</style>
diff --git a/src/views/productionManagement/operationScheduling/components/formDia.vue b/src/views/productionManagement/operationScheduling/components/formDia.vue
index 5d8fe3a..06b46ac 100644
--- a/src/views/productionManagement/operationScheduling/components/formDia.vue
+++ b/src/views/productionManagement/operationScheduling/components/formDia.vue
@@ -8,24 +8,47 @@
>
<el-button type="primary" @click="addRow" style="margin-bottom: 10px;">鏂板</el-button>
<span style="font-size: 18px;margin-left: 10px">寰呮帓浜ф暟閲忥細{{pendingNum}}</span>
- <el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id" stripe>
- <el-table-column label="搴忓彿" width="60">
+<!-- <div style="margin-bottom: 10px; margin-left: 10px;">-->
+<!-- <el-form-item label="棰嗙敤锛�" style="margin-bottom: 0;">-->
+<!-- <el-input v-model="receive" placeholder="璇疯緭鍏ラ鐢�" style="width: 200px;" />-->
+<!-- </el-form-item>-->
+<!-- </div>-->
+ <el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id">
+ <el-table-column label="搴忓彿" width="60" align="center">
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
- <el-table-column label="宸ュ簭" prop="process">
+ <el-table-column label="宸ュ簭" prop="process" width="150">
<template #default="scope">
- <el-input
- v-model="scope.row.process"
- placeholder="璇疯緭鍏ュ伐搴�"
- clearable
- />
+ <el-input v-model="scope.row.process" placeholder="璇疯緭鍏ュ伐搴�" />
</template>
</el-table-column>
- <el-table-column label="鍗曚綅" prop="unit">
+ <el-table-column label="浜х嚎" prop="productionLine" width="150">
+ <template #default="scope">
+ <el-select
+ v-model="scope.row.productionLine"
+ placeholder="閫夋嫨浜х嚎"
+ style="width: 100%;"
+ clearable
+ >
+ <el-option
+ v-for="line in productionLines"
+ :key="line.value"
+ :label="line.label"
+ :value="line.value"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚綅" prop="unit" width="90">
<template #default="scope">
<el-input v-model="scope.row.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙e懗/鍝佸悕/瑙勬牸" prop="type" width="150">
+ <template #default="scope">
+ <el-input v-model="scope.row.type" placeholder="璇疯緭鍏�" />
</template>
</el-table-column>
<el-table-column label="鎺掍骇鏁伴噺" width="200" prop="schedulingNum">
@@ -54,17 +77,20 @@
/>
</template>
</el-table-column>
- <el-table-column label="鎺掍骇鏃ユ湡" prop="schedulingDate">
+ <el-table-column label="鎺掍骇鏃ユ湡" prop="schedulingDate" width="200">
<template #default="scope">
<el-date-picker v-model="scope.row.schedulingDate" type="date" placeholder="閫夋嫨鏃ユ湡" style="width: 100%;" value-format="YYYY-MM-DD" format="YYYY-MM-DD"/>
</template>
</el-table-column>
- <el-table-column label="鎺掍骇浜�" prop="schedulingUserId">
+ <el-table-column label="鎺掍骇浜�" prop="schedulingUserId" width="150">
<template #default="scope">
<el-select
v-model="scope.row.schedulingUserId"
placeholder="閫夋嫨浜哄憳"
style="width: 100%;"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
>
<el-option
v-for="user in userList"
@@ -73,6 +99,11 @@
:value="user.userId"
/>
</el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶囨敞" prop="remark" width="200">
+ <template #default="scope">
+ <el-input v-model="scope.row.remark" placeholder="璇疯緭鍏ュ娉�" />
</template>
</el-table-column>
<el-table-column label="鎿嶄綔" width="80">
@@ -92,41 +123,65 @@
</template>
<script setup>
-import {ref} from "vue";
+import {ref, getCurrentInstance} from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {processScheduling} from "@/api/productionManagement/operationScheduling.js";
const { proxy } = getCurrentInstance()
+const { work_step } = proxy.useDict("work_step")
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
-const tableData = ref([
- { process: '', schedulingDate: '', schedulingNum: '', schedulingUserId: '', workHours: '', unit: '' }
-]);
+const tableData = ref([]);
const unitFromRow = ref('');
const idFromRow = ref('');
-const pendingNum = ref('');
+const specificationModelFromRow = ref('');
+const pendingNum = ref(0);
const userList = ref([])
+const receive = ref('')
+const sunqianUserId = ref('')
+// 浜х嚎閫夐」
+const productionLines = ref([
+ { label: '浜х嚎1', value: '浜х嚎1' },
+ { label: '浜х嚎2', value: '浜х嚎2' },
+ { label: '浜х嚎3', value: '浜х嚎3' },
+ { label: '浜х嚎4', value: '浜х嚎4' }
+])
// 鎵撳紑寮规
const openDialog = (type, row) => {
operationType.value = type;
dialogFormVisible.value = true;
+ pendingNum.value = row?.pendingNum ?? 0;
+ unitFromRow.value = row?.unit ?? '';
+ idFromRow.value = row?.id ?? '';
+ specificationModelFromRow.value = row?.specificationModel ?? '';
+
userListNoPageByTenantId().then((res) => {
userList.value = res.data;
+ // 鎵惧埌瀛欏�╃殑鐢ㄦ埛ID骞惰缃负榛樿鍊�
+ const sunqianUser = userList.value.find(user => user.nickName === '瀛欏��');
+ if (sunqianUser) {
+ sunqianUserId.value = sunqianUser.userId;
+ }
+ // 鍦ㄧ敤鎴峰垪琛ㄥ姞杞藉畬鎴愬悗鍒涘缓琛屾暟鎹紝骞跺皢浜х嚎鏁版嵁甯﹀叆
+ tableData.value = [createRow(row)];
});
- pendingNum.value = row.pendingNum
- if (row && row.unit !== undefined) {
- unitFromRow.value = row.unit;
- idFromRow.value = row.id;
- tableData.value.forEach(item => {
- item.unit = row.unit;
- item.id = row.id;
- });
- } else {
- unitFromRow.value = '';
- }
}
+
+const createRow = (row) => ({
+ id: idFromRow.value,
+ process: '鍖呰',
+ schedulingDate: '',
+ schedulingNum: null,
+ schedulingUserId: sunqianUserId.value, // 榛樿璁剧疆涓哄瓩鍊╃殑鐢ㄦ埛ID
+ workHours: null,
+ unit: unitFromRow.value,
+ remark: '',
+ type: specificationModelFromRow.value,
+ productionLine: row?.productionLine ?? '', // 浠庤鏁版嵁涓幏鍙栦骇绾夸俊鎭�
+});
+
const submitForm = () => {
// 1. 妫�鏌ユ瘡涓�琛屾槸鍚﹀~鍐欏畬鏁�
for (let i = 0; i < tableData.value.length; i++) {
@@ -137,7 +192,8 @@
row.schedulingNum === '' || row.schedulingNum === null ||
!row.schedulingUserId ||
row.workHours === '' || row.workHours === null ||
- !row.unit
+ !row.unit ||
+ !row.productionLine
) {
proxy.$modal.msgError(`绗�${i + 1}琛屾暟鎹湭濉啓瀹屾暣`);
return;
@@ -151,7 +207,15 @@
proxy.$modal.msgError('鎺掍骇鏁伴噺鍚堣涓嶈兘瓒呰繃寰呮帓浜ф暟閲�');
return;
}
- processScheduling(tableData.value).then((res) => {
+ // 3. 灏� receive 瀛楁娣诲姞鍒版瘡鏉℃暟鎹腑锛屽苟绉婚櫎 loss 瀛楁
+ const submitData = tableData.value.map(row => {
+ const { loss, ...rest } = row;
+ return {
+ ...rest,
+ receive: receive.value
+ };
+ });
+ processScheduling(submitData).then((res) => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
})
@@ -162,6 +226,12 @@
// 鍏抽棴寮规
const closeDia = () => {
dialogFormVisible.value = false;
+ receive.value = '';
+ tableData.value = [];
+ unitFromRow.value = '';
+ idFromRow.value = '';
+ specificationModelFromRow.value = '';
+ pendingNum.value = 0;
emit('close')
};
defineExpose({
@@ -169,7 +239,7 @@
});
const addRow = () => {
- tableData.value.push({ id: idFromRow.value, process: '', unit: unitFromRow.value, schedulingNum: '', workHours: '', schedulingDate: '', schedulingUserId: '' });
+ tableData.value.push(createRow());
};
const removeRow = (index) => {
tableData.value.splice(index, 1);
diff --git a/src/views/productionManagement/operationScheduling/index.vue b/src/views/productionManagement/operationScheduling/index.vue
index 1427faf..a9c06fb 100644
--- a/src/views/productionManagement/operationScheduling/index.vue
+++ b/src/views/productionManagement/operationScheduling/index.vue
@@ -2,6 +2,21 @@
<div class="app-container">
<div class="search_form">
<el-form :model="searchForm" :inline="true">
+ <el-form-item label="瀹㈡埛鍚嶇О:">
+ <el-input v-model="searchForm.customerName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="鍚堝悓鍙�:">
+ <el-input v-model="searchForm.salesContractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+<!-- <el-form-item label="椤圭洰鍚嶇О:">-->
+<!-- <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"-->
+<!-- style="width: 200px;"-->
+<!-- @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" />
@@ -22,6 +37,7 @@
<div style="text-align: right" class="mb10">
<el-button type="primary" @click="openForm">宸ュ簭鎺掍骇</el-button>
<el-button type="danger" @click="handleDelete" plain>鍙栨秷鎺掍骇</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
</div>
<PIMTable
rowKey="id"
@@ -49,13 +65,12 @@
const data = reactive({
searchForm: {
staffName: "",
+ customerName: "",
+ salesContractNo: "",
status: 1,
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
+ entryDate: [dayjs().format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")], // 褰曞叆鏃ユ湡锛岄粯璁ゅ綋澶�
entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().format("YYYY-MM-DD"),
},
});
const { searchForm } = toRefs(data);
@@ -93,6 +108,26 @@
prop: "schedulingUserName",
},
{
+ label: "鍚堝悓鍙�",
+ prop: "salesContractNo",
+ width: 200,
+ },
+ // {
+ // label: "瀹㈡埛鍚堝悓鍙�",
+ // prop: "customerContractNo",
+ // width: 200,
+ // },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: 200,
+ },
+ // {
+ // label: "椤圭洰鍚嶇О",
+ // prop: "projectName",
+ // width:300
+ // },
+ {
label: "浜у搧澶х被",
prop: "productCategory",
width: 150,
@@ -102,6 +137,16 @@
prop: "specificationModel",
width: 150,
},
+ {
+ label: "缁戝畾鏈哄櫒",
+ prop: "speculativeTradingName",
+ width: 220,
+ },
+ // {
+ // label: "浜х嚎",
+ // prop: "productionLine",
+ // width: 220,
+ // },
{
label: "鍗曚綅",
prop: "unit",
@@ -220,6 +265,22 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/salesLedger/scheduling/exportTwo", {}, "宸ュ簭鎺掍骇.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/productionManagement/processRoute/Edit.vue b/src/views/productionManagement/processRoute/Edit.vue
new file mode 100644
index 0000000..0c0fe0f
--- /dev/null
+++ b/src/views/productionManagement/processRoute/Edit.vue
@@ -0,0 +1,252 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="缂栬緫宸ヨ壓璺嚎"
+ width="400"
+ @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.productModelName
+ ? `${formState.productName} - ${formState.productModelName}`
+ : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item
+ label="BOM"
+ prop="bomId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨BOM',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-select
+ v-model="formState.bomId"
+ placeholder="璇烽�夋嫨BOM"
+ clearable
+ :disabled="!formState.productModelId || bomOptions.length === 0"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in bomOptions"
+ :key="item.id"
+ :label="item.bomNo || `BOM-${item.id}`"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="澶囨敞" prop="description">
+ <el-input v-model="formState.description" 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, onMounted, nextTick, watch} from "vue";
+import {update} from "@/api/productionManagement/processRoute.js";
+import {getByModel} from "@/api/productionManagement/productBom.js";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ record: {
+ type: Object,
+ required: true,
+ }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ bomId: undefined,
+ description: '',
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const showProductSelectDialog = ref(false);
+const bomOptions = ref([]);
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+// 璁剧疆琛ㄥ崟鏁版嵁
+const setFormData = () => {
+ if (props.record) {
+ formState.value = {
+ ...props.record,
+ productId: props.record.productId,
+ productModelId: props.record.productModelId,
+ productName: props.record.productName || "",
+ // 娉ㄦ剰锛歳ecord涓殑瀛楁鏄痬odel锛岄渶瑕佹槧灏勫埌productModelName
+ productModelName: props.record.model || props.record.productModelName || "",
+ bomId: props.record.bomId,
+ description: props.record.description || '',
+ };
+ // 濡傛灉鏈変骇鍝佸瀷鍙稩D锛屽姞杞紹OM鍒楄〃
+ if (props.record.productModelId) {
+ loadBomList(props.record.productModelId);
+ }
+ }
+}
+
+// 鍔犺浇BOM鍒楄〃
+const loadBomList = async (productModelId) => {
+ if (!productModelId) {
+ bomOptions.value = [];
+ return;
+ }
+ try {
+ const res = await getByModel(productModelId);
+ // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
+ let bomList = [];
+ if (Array.isArray(res)) {
+ bomList = res;
+ } else if (res && res.data) {
+ bomList = Array.isArray(res.data) ? res.data : [res.data];
+ } else if (res && typeof res === 'object') {
+ bomList = [res];
+ }
+ bomOptions.value = bomList;
+ } catch (error) {
+ console.error("鍔犺浇BOM鍒楄〃澶辫触锛�", error);
+ bomOptions.value = [];
+ }
+};
+
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ // 鍏堟煡璇OM鍒楄〃锛堝繀閫夛級
+ try {
+ const res = await getByModel(product.id);
+ // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
+ let bomList = [];
+ if (Array.isArray(res)) {
+ bomList = res;
+ } else if (res && res.data) {
+ bomList = Array.isArray(res.data) ? res.data : [res.data];
+ } else if (res && typeof res === 'object') {
+ bomList = [res];
+ }
+
+ if (bomList.length > 0) {
+ formState.value.productModelId = product.id;
+ formState.value.productName = product.productName;
+ formState.value.productModelName = product.model;
+ // 濡傛灉褰撳墠閫夋嫨鐨凚OM涓嶅湪鏂板垪琛ㄤ腑锛屽垯閲嶇疆BOM閫夋嫨
+ const currentBomExists = bomList.some(bom => bom.id === formState.value.bomId);
+ if (!currentBomExists) {
+ formState.value.bomId = undefined;
+ }
+ bomOptions.value = bomList;
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+ proxy.$refs["formRef"]?.validateField('productModelId');
+ } else {
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
+ }
+ } catch (error) {
+ // 濡傛灉鎺ュ彛杩斿洖404鎴栧叾浠栭敊璇紝璇存槑娌℃湁BOM
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
+ }
+ }
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰BOM
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+ return;
+ }
+ if (!formState.value.bomId) {
+ proxy.$modal.msgError("璇烽�夋嫨BOM");
+ return;
+ }
+ update(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+
+
+// 鐩戝惉寮圭獥鎵撳紑锛屽垵濮嬪寲琛ㄥ崟鏁版嵁
+watch(() => props.visible, (visible) => {
+ if (visible && props.record) {
+ nextTick(() => {
+ setFormData();
+ });
+ }
+}, { immediate: true });
+
+onMounted(() => {
+ if (props.visible && props.record) {
+ setFormData();
+ }
+});
+</script>
diff --git a/src/views/productionManagement/processRoute/ItemsForm.vue b/src/views/productionManagement/processRoute/ItemsForm.vue
new file mode 100644
index 0000000..ed6e499
--- /dev/null
+++ b/src/views/productionManagement/processRoute/ItemsForm.vue
@@ -0,0 +1,531 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="宸ヨ壓璺嚎椤圭洰"
+ width="800px"
+ @close="closeModal"
+ >
+ <div class="operate-button">
+ <el-button
+ type="primary"
+ @click="isShowProductSelectDialog = true"
+ class="mb5"
+ style="margin-bottom: 10px;"
+ >
+ 閫夋嫨浜у搧
+ </el-button>
+
+ <el-switch
+ v-model="isTable"
+ inline-prompt
+ active-text="琛ㄦ牸"
+ inactive-text="鍒楄〃"
+ @change="handleViewChange"
+ />
+ </div>
+
+ <el-table
+ v-if="isTable"
+ ref="multipleTable"
+ v-loading="tableLoading"
+ border
+ :data="routeItems"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ row-key="id"
+ tooltip-effect="dark"
+ class="lims-table"
+ style="cursor: move;"
+ >
+ <el-table-column align="center" label="搴忓彿" width="60">
+ <template #default="scope">
+ {{ scope.$index + 1 }}
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ v-for="(item, index) in tableColumn"
+ :key="index"
+ :label="item.label"
+ :width="item.width"
+ show-overflow-tooltip
+ >
+ <template #default="scope" v-if="item.dataType === 'action'">
+ <el-button
+ v-for="(op, opIndex) in item.operation"
+ :key="opIndex"
+ :type="op.type"
+ :link="op.link"
+ size="small"
+ @click.stop="op.clickFun(scope.row)"
+ >
+ {{ op.name }}
+ </el-button>
+ </template>
+
+ <template #default="scope" v-else>
+ <template v-if="item.prop === 'processId'">
+ <el-select
+ v-model="scope.row[item.prop]"
+ style="width: 100%;"
+ @mousedown.stop
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </template>
+ <template v-else>
+ {{ scope.row[item.prop] || '-' }}
+ </template>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 浣跨敤鏅�歞iv鏇夸唬el-steps -->
+ <div
+ v-else
+ ref="stepsContainer"
+ class="mb5 custom-steps"
+ style="padding: 10px 0; display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-start;"
+ >
+ <div
+ v-for="(item, index) in routeItems"
+ :key="item.id"
+ class="custom-step draggable-step"
+ :data-id="item.id"
+ style="cursor: move; flex: 0 0 auto; min-width: 220px;"
+ >
+ <div class="step-content">
+ <div class="step-number">{{ index + 1 }}</div>
+ <el-card
+ :header="item.productName"
+ class="step-card"
+ style="cursor: move;"
+ >
+ <div class="step-card-content">
+ <p>{{ item.model }}</p>
+ <p>{{ item.unit }}</p>
+ <el-select
+ v-model="item.processId"
+ style="width: 100%;"
+ @mousedown.stop
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </div>
+ <template #footer>
+ <div class="step-card-footer">
+ <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">鍒犻櫎</el-button>
+ </div>
+ </template>
+ </el-card>
+ </div>
+ </div>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <ProductSelectDialog
+ v-model="isShowProductSelectDialog"
+ @confirm="handelSelectProducts"
+ />
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
+import { processList } from "@/api/productionManagement/productionProcess.js";
+import Sortable from 'sortablejs';
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ default: false
+ },
+ record: {
+ type: Object,
+ required: true,
+ default: () => ({})
+ }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+const processOptions = ref([]);
+const tableLoading = ref(false);
+const isShowProductSelectDialog = ref(false);
+const routeItems = ref([]);
+let tableSortable = null;
+let stepsSortable = null;
+const multipleTable = ref(null);
+const stepsContainer = ref(null);
+const isTable = ref(true);
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ }
+});
+
+const tableColumn = ref([
+ { label: "浜у搧鍚嶇О", prop: "productName", width: 180 },
+ { label: "瑙勬牸鍚嶇О", prop: "model", width: 150 },
+ { label: "鍗曚綅", prop: "unit", width: 80 },
+ { label: "宸ュ簭鍚嶇О", prop: "processId", width: 180 },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 100,
+ operation: [
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ clickFun: (row) => {
+ const idx = routeItems.value.findIndex(item => item.id === row.id);
+ if (idx > -1) {
+ removeItem(idx)
+ }
+ }
+ }
+ ]
+ }
+]);
+
+const removeItem = (index) => {
+ routeItems.value.splice(index, 1);
+ nextTick(() => initSortable());
+};
+
+const removeItemByID = (id) => {
+ const idx = routeItems.value.findIndex(item => item.id === id);
+ if (idx > -1) {
+ routeItems.value.splice(idx, 1);
+ nextTick(() => initSortable());
+ }
+};
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const handelSelectProducts = (products) => {
+ destroySortable();
+
+ const newData = products.map(({ id, ...product }) => ({
+ ...product,
+ productModelId: id,
+ routeId: props.record.id,
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
+ processId: undefined
+ }));
+
+ console.log('閫夋嫨浜у搧鍓嶆暟缁�:', routeItems.value);
+ routeItems.value.push(...newData);
+ routeItems.value = [...routeItems.value];
+ console.log('閫夋嫨浜у搧鍚庢暟缁�:', routeItems.value);
+
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ // 寮哄埗閲嶆柊娓叉煋缁勪欢
+ if (proxy?.$forceUpdate) {
+ proxy.$forceUpdate();
+ }
+
+ const temp = [...routeItems.value];
+ routeItems.value = [];
+ nextTick(() => {
+ routeItems.value = temp;
+ initSortable();
+ });
+ });
+};
+
+const findProcessRouteItems = () => {
+ tableLoading.value = true;
+ findProcessRouteItemList({ routeId: props.record.id })
+ .then(res => {
+ tableLoading.value = false;
+ routeItems.value = res.data.map(item => ({
+ ...item,
+ processId: item.processId === 0 ? undefined : item.processId
+ }));
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ });
+};
+
+const findProcessList = () => {
+ processList({})
+ .then(res => {
+ processOptions.value = res.data;
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+ });
+};
+
+const { proxy } = getCurrentInstance() || {};
+
+const handleSubmit = () => {
+ const hasEmptyProcess = routeItems.value.some(item => !item.processId);
+ if (hasEmptyProcess) {
+ proxy?.$modal?.msgError("璇蜂负鎵�鏈夐」鐩�夋嫨宸ュ簭");
+ return;
+ }
+
+ addOrUpdateProcessRouteItem({
+ routeId: props.record.id,
+ processRouteItem: routeItems.value.map(({ id, ...item }) => item)
+ })
+ .then(res => {
+ isShow.value = false;
+ emit('completed');
+ proxy?.$modal?.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ .catch(err => {
+ proxy?.$modal?.msgError(`鎻愪氦澶辫触锛�${err.msg || "缃戠粶寮傚父"}`);
+ });
+};
+
+const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (stepsSortable) {
+ stepsSortable.destroy();
+ stepsSortable = null;
+ }
+};
+
+const initSortable = () => {
+ destroySortable();
+
+ if (isTable.value) {
+ if (!multipleTable.value) return;
+ const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
+ multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
+ if (!tbody) return;
+
+ tableSortable = new Sortable(tbody, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ handle: '.el-table__row',
+ filter: '.el-button, .el-select',
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭锛屼笌琛ㄦ牸妯″紡淇濇寔涓�鑷�
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ routeItems.value = [...routeItems.value];
+ console.log('鎺掑簭鍚庢暟缁�:', routeItems.value);
+ }
+ });
+ } else {
+ if (!stepsContainer.value) return;
+
+ // 淇敼锛氱洿鎺ヤ娇鐢╯tepsContainer.value浣滀负鎷栨嫿瀹瑰櫒
+ const stepsList = stepsContainer.value;
+ if (!stepsList) {
+ console.warn('鏈壘鍒版楠ゆ潯鎷栨嫿瀹瑰櫒');
+ return;
+ }
+
+ // 淇敼锛氱畝鍖栨嫋鎷介厤缃�
+ stepsSortable = new Sortable(stepsList, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ draggable: '.draggable-step', // 鍙嫋鎷藉厓绱�
+ handle: '.draggable-step, .step-card', // 鎷栨嫿鎵嬫焺
+ filter: '.el-button, .el-select, .el-input', // 杩囨护鎸夐挳/閫夋嫨鍣�
+ forceFallback: true,
+ fallbackClass: 'sortable-fallback',
+ preventOnFilter: true,
+ scroll: true,
+ scrollSensitivity: 30,
+ scrollSpeed: 10,
+ bubbleScroll: true,
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ routeItems.value = [...routeItems.value];
+ }
+ });
+
+ // 璋冭瘯锛氭墦鍗板鍣ㄥ拰瀹炰緥锛岀‘璁ょ粦瀹氭垚鍔�
+ console.log('姝ラ鏉℃嫋鎷藉鍣�:', stepsList);
+ console.log('Sortable瀹炰緥:', stepsSortable);
+ }
+};
+
+const handleViewChange = () => {
+ destroySortable();
+ // 寤惰繜鍒濆鍖栵紝纭繚瑙嗗浘鍒囨崲鍚嶥OM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+};
+
+onMounted(() => {
+ findProcessRouteItems();
+ findProcessList();
+});
+
+onUnmounted(() => {
+ destroySortable();
+});
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow
+});
+</script>
+
+<style scoped>
+:deep(.sortable-ghost) {
+ opacity: 0.6;
+ background-color: #f5f7fa !important;
+}
+
+:deep(.el-table__row) {
+ transition: background-color 0.2s;
+}
+
+:deep(.el-table__row:hover) {
+ background-color: #f9fafc !important;
+}
+
+:deep(.el-card__footer){
+ padding: 0 !important;
+}
+
+.operate-button {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+/* 淇敼锛氳嚜瀹氫箟姝ラ鏉″鍣ㄦ牱寮� */
+.custom-steps {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ gap: 20px;
+ min-height: 100px;
+}
+
+/* 淇敼锛氳嚜瀹氫箟姝ラ椤规牱寮� */
+.custom-step {
+ cursor: move !important;
+ padding: 8px;
+ position: relative;
+ transition: all 0.2s ease;
+ flex: 0 0 auto;
+ min-width: 220px;
+ touch-action: none;
+}
+
+/* 鎷栨嫿鎮诞鏍峰紡锛屾彁绀哄彲鎷栨嫿 */
+.custom-step:hover {
+ background-color: rgba(64, 158, 255, 0.05);
+ transform: translateY(-2px);
+}
+
+.sortable-ghost {
+ opacity: 0.4;
+ background-color: #f5f7fa !important;
+ border: 2px dashed #409eff;
+ margin: 10px;
+ transform: scale(1.02);
+}
+
+.sortable-fallback {
+ opacity: 0.9;
+ background-color: #f5f7fa;
+ border: 1px solid #409eff;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ transform: rotate(2deg);
+ margin: 10px;
+}
+
+.step-card {
+ cursor: move !important;
+ transition: box-shadow 0.2s ease;
+ user-select: none;
+ -webkit-user-select: none;
+ pointer-events: auto;
+ margin: 10px;
+ height: 240px;
+}
+
+.step-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.step-content {
+ width: 220px;
+ user-select: none;
+}
+
+.step-card-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.step-card-footer {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 10px;
+}
+
+/* 鑷畾涔夊簭鍙锋牱寮忎紭鍖� */
+.step-number {
+ font-weight: bold;
+ text-align: center;
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ margin: 0 auto 10px;
+ background: #409eff;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 14px;
+}
+</style>
diff --git a/src/views/productionManagement/processRoute/New.vue b/src/views/productionManagement/processRoute/New.vue
new file mode 100644
index 0000000..62c6873
--- /dev/null
+++ b/src/views/productionManagement/processRoute/New.vue
@@ -0,0 +1,194 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="鏂板宸ヨ壓璺嚎"
+ width="400"
+ @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.productModelName
+ ? `${formState.productName} - ${formState.productModelName}`
+ : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item
+ label="BOM"
+ prop="bomId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨BOM',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-select
+ v-model="formState.bomId"
+ placeholder="璇烽�夋嫨BOM"
+ clearable
+ :disabled="!formState.productModelId || bomOptions.length === 0"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in bomOptions"
+ :key="item.id"
+ :label="item.bomNo || `BOM-${item.id}`"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="澶囨敞" prop="description">
+ <el-input v-model="formState.description" 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 {add} from "@/api/productionManagement/processRoute.js";
+import {getByModel} from "@/api/productionManagement/productBom.js";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ bomId: undefined,
+ description: '',
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const showProductSelectDialog = ref(false);
+const bomOptions = ref([]);
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ // 閲嶇疆琛ㄥ崟鏁版嵁
+ formState.value = {
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ bomId: undefined,
+ description: '',
+ };
+ bomOptions.value = [];
+ isShow.value = false;
+};
+
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ // 鍏堟煡璇OM鍒楄〃锛堝繀閫夛級
+ try {
+ const res = await getByModel(product.id);
+ // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
+ let bomList = [];
+ if (Array.isArray(res)) {
+ bomList = res;
+ } else if (res && res.data) {
+ bomList = Array.isArray(res.data) ? res.data : [res.data];
+ } else if (res && typeof res === 'object') {
+ bomList = [res];
+ }
+
+ if (bomList.length > 0) {
+ formState.value.productModelId = product.id;
+ formState.value.productName = product.productName;
+ formState.value.productModelName = product.model;
+ formState.value.bomId = undefined; // 閲嶇疆BOM閫夋嫨
+ bomOptions.value = bomList;
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+ proxy.$refs["formRef"]?.validateField('productModelId');
+ } else {
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
+ }
+ } catch (error) {
+ // 濡傛灉鎺ュ彛杩斿洖404鎴栧叾浠栭敊璇紝璇存槑娌℃湁BOM
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
+ }
+ }
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰BOM
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+ return;
+ }
+ if (!formState.value.bomId) {
+ proxy.$modal.msgError("璇烽�夋嫨BOM");
+ return;
+ }
+ add(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
new file mode 100644
index 0000000..41103f9
--- /dev/null
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -0,0 +1,204 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="瑙勬牸鍚嶇О:">
+ <el-input v-model="searchForm.model" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right" class="mb10">
+ <el-button type="primary" @click="showNewModal">鏂板宸ヨ壓璺嚎</el-button>
+ <el-button type="danger" @click="handleDelete" :disabled="selectedRows.length === 0" plain>鍒犻櫎宸ヨ壓璺嚎</el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ />
+ </div>
+ <new-process
+ v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ @completed="getList"
+ />
+
+ <edit-process
+ v-if="isShowEditModal"
+ v-model:visible="isShowEditModal"
+ :record="record"
+ @completed="getList"
+ />
+
+ <route-item-form
+ v-if="isShowItemModal"
+ v-model:visible="isShowItemModal"
+ :record="record"
+ @completed="getList"
+ />
+ </div>
+</template>
+
+<script setup>
+import {onMounted, ref} from "vue";
+import NewProcess from "@/views/productionManagement/processRoute/New.vue";
+import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
+import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
+import {listPage, del} from "@/api/productionManagement/processRoute.js";
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const data = reactive({
+ searchForm: {
+ model: "",
+ },
+});
+const { searchForm } = toRefs(data);
+const tableColumn = ref([
+ {
+ label: "宸ヨ壓璺嚎缂栧彿",
+ prop: "processRouteCode",
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ },
+ {
+ label: "瑙勬牸鍚嶇О",
+ prop: "model",
+ },
+ {
+ label: "BOM缂栧彿",
+ prop: "bomNo",
+ },
+ {
+ label: "鎻忚堪",
+ prop: "description",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 280,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ showEditModal(row);
+ }
+ },
+ {
+ name: "璺嚎椤圭洰",
+ type: "text",
+ clickFun: (row) => {
+ showItemModal(row);
+ }
+ }
+ ]
+ }
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const isShowNewModal = ref(false);
+const isShowEditModal = ref(false);
+const isShowItemModal = ref(false);
+const record = ref({});
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const { proxy } = getCurrentInstance()
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
+ listPage(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ }));
+ page.total = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鎵撳紑鏂板寮规
+const showNewModal = () => {
+ isShowNewModal.value = true
+};
+
+const showEditModal = (row) => {
+ isShowEditModal.value = true
+ record.value = row
+};
+
+const showItemModal = (row) => {
+ router.push({
+ path: '/productionManagement/processRouteItem',
+ query: {
+ id: row.id,
+ processRouteCode: row.processRouteCode || '',
+ productName: row.productName || '',
+ model: row.model || '',
+ bomNo: row.bomNo || '',
+ description: row.description || '',
+ type: 'route',
+ }
+ })
+};
+
+// 鍒犻櫎
+function handleDelete() {
+ const ids = selectedRows.value.map((item) => item.id);
+ proxy.$modal
+ .confirm('鏄惁纭鍒犻櫎宸插嬀閫夌殑鏁版嵁椤癸紵')
+ .then(function () {
+ return del(ids);
+ })
+ .then(() => {
+ getList();
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+}
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped></style>
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
new file mode 100644
index 0000000..18e21e8
--- /dev/null
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -0,0 +1,876 @@
+<template>
+ <div class="app-container">
+ <PageHeader content="宸ヨ壓璺嚎椤圭洰" />
+
+ <!-- 宸ヨ壓璺嚎淇℃伅灞曠ず -->
+ <el-card v-if="routeInfo.processRouteCode" class="route-info-card" shadow="hover">
+ <div class="route-info">
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">宸ヨ壓璺嚎缂栧彿</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.processRouteCode }}</span>
+ </div>
+ </div>
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">浜у搧鍚嶇О</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.productName || '-' }}</span>
+ </div>
+ </div>
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">瑙勬牸鍚嶇О</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.model || '-' }}</span>
+ </div>
+ </div>
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">BOM缂栧彿</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.bomNo || '-' }}</span>
+ </div>
+ </div>
+ <div class="info-item full-width" v-if="routeInfo.description">
+ <div class="info-label-wrapper">
+ <span class="info-label">鎻忚堪</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.description }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+
+ <!-- 琛ㄦ牸瑙嗗浘 -->
+ <div v-if="viewMode === 'table'" class="section-header">
+ <div class="section-title">宸ヨ壓璺嚎椤圭洰鍒楄〃</div>
+ <div class="section-actions">
+ <el-button
+ icon="Grid"
+ @click="toggleView"
+ style="margin-right: 10px;"
+ >
+ 鍗$墖瑙嗗浘
+ </el-button>
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ </div>
+ </div>
+ <el-table
+ v-if="viewMode === 'table'"
+ ref="tableRef"
+ v-loading="tableLoading"
+ border
+ :data="tableData"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ row-key="id"
+ tooltip-effect="dark"
+ class="lims-table"
+ >
+ <el-table-column align="center" label="搴忓彿" width="60" type="index" />
+ <el-table-column label="宸ュ簭鍚嶇О" prop="processId" width="200">
+ <template #default="scope">
+ {{ getProcessName(scope.row.processId) || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="浜у搧鍚嶇О" prop="productName" min-width="160" />
+ <el-table-column label="瑙勬牸鍚嶇О" prop="model" min-width="140" />
+ <el-table-column label="鍗曚綅" prop="unit" width="100" />
+ <el-table-column label="鎿嶄綔" align="center" fixed="right" width="150">
+ <template #default="scope">
+ <el-button type="primary" link size="small" @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button type="danger" link size="small" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍗$墖瑙嗗浘 -->
+ <template v-else>
+ <div class="section-header">
+ <div class="section-title">宸ヨ壓璺嚎椤圭洰鍒楄〃</div>
+ <div class="section-actions">
+ <el-button
+ icon="Menu"
+ @click="toggleView"
+ style="margin-right: 10px;"
+ >
+ 琛ㄦ牸瑙嗗浘
+ </el-button>
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ </div>
+ </div>
+ <div v-loading="tableLoading" class="card-container">
+ <div
+ ref="cardsContainer"
+ class="cards-wrapper"
+ >
+ <div
+ v-for="(item, index) in tableData"
+ :key="item.id || index"
+ class="process-card"
+ :data-index="index"
+ >
+ <!-- 搴忓彿鍦嗗湀 -->
+ <div class="card-header">
+ <div class="card-number">{{ index + 1 }}</div>
+ <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div>
+ </div>
+
+ <!-- 浜у搧淇℃伅 -->
+ <div class="card-content">
+ <div v-if="item.productName" class="product-info">
+ <div class="product-name">{{ item.productName }}</div>
+ <div v-if="item.model" class="product-model">
+ {{ item.model }}
+ <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
+ </div>
+ </div>
+ <div v-else class="product-info empty">鏆傛棤浜у搧淇℃伅</div>
+ </div>
+
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div class="card-footer">
+ <el-button type="primary" link size="small" @click="handleEdit(item)">缂栬緫</el-button>
+ <el-button type="danger" link size="small" @click="handleDelete(item)">鍒犻櫎</el-button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </template>
+
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog
+ v-model="dialogVisible"
+ :title="operationType === 'add' ? '鏂板宸ヨ壓璺嚎椤圭洰' : '缂栬緫宸ヨ壓璺嚎椤圭洰'"
+ width="500px"
+ @close="closeDialog"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="120px"
+ >
+ <el-form-item label="宸ュ簭" prop="processId">
+ <el-select
+ v-model="form.processId"
+ placeholder="璇烽�夋嫨宸ュ簭"
+ clearable
+ style="width: 100%"
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="浜у搧鍚嶇О" prop="productModelId">
+ <el-button type="primary" @click="showProductSelectDialog = true">
+ {{ form.productName && form.model
+ ? `${form.productName} - ${form.model}`
+ : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item label="鍗曚綅" prop="unit">
+ <el-input
+ v-model="form.unit"
+ :placeholder="form.productModelId ? '鏍规嵁閫夋嫨鐨勪骇鍝佽嚜鍔ㄥ甫鍑�' : '璇峰厛閫夋嫨浜у搧'"
+ clearable
+ :disabled="true"
+ />
+ </el-form-item>
+ </el-form>
+
+ <template #footer>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit" :loading="submitLoading">纭畾</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 浜у搧閫夋嫨瀵硅瘽妗� -->
+ <ProductSelectDialog
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
+ />
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
+import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem } from "@/api/productionManagement/productProcessRoute.js";
+import { processList } from "@/api/productionManagement/productionProcess.js";
+import { useRoute } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
+import Sortable from 'sortablejs'
+
+const route = useRoute()
+const { proxy } = getCurrentInstance() || {};
+
+const routeId = computed(() => route.query.id);
+const orderId = computed(() => route.query.orderId);
+const pageType = computed(() => route.query.type);
+
+const tableLoading = ref(false);
+const tableData = ref([]);
+const dialogVisible = ref(false);
+const operationType = ref('add'); // add | edit
+const formRef = ref(null);
+const submitLoading = ref(false);
+const cardsContainer = ref(null);
+const tableRef = ref(null);
+const viewMode = ref('table'); // table | card
+const routeInfo = ref({
+ processRouteCode: '',
+ productName: '',
+ model: '',
+ bomNo: '',
+ description: ''
+});
+
+const processOptions = ref([]);
+const showProductSelectDialog = ref(false);
+let tableSortable = null;
+let cardSortable = null;
+
+// 鍒囨崲瑙嗗浘
+const toggleView = () => {
+ viewMode.value = viewMode.value === 'table' ? 'card' : 'table';
+ // 鍒囨崲瑙嗗浘鍚庨噸鏂板垵濮嬪寲鎷栨嫿鎺掑簭
+ nextTick(() => {
+ initSortable();
+ });
+};
+
+const form = ref({
+ id: undefined,
+ routeId: routeId.value,
+ processId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+});
+
+const rules = {
+ processId: [{ required: true, message: '璇烽�夋嫨宸ュ簭', trigger: 'change' }],
+ productModelId: [{ required: true, message: '璇烽�夋嫨浜у搧', trigger: 'change' }],
+};
+
+// 鏍规嵁宸ュ簭ID鑾峰彇宸ュ簭鍚嶇О
+const getProcessName = (processId) => {
+ if (!processId) return '';
+ const process = processOptions.value.find(p => p.id === processId);
+ return process ? process.name : '';
+};
+
+// 鑾峰彇鍒楄〃
+const getList = () => {
+ tableLoading.value = true;
+ const listPromise =
+ pageType.value === "order"
+ ? findProductProcessRouteItemList({ orderId: orderId.value })
+ : findProcessRouteItemList({ routeId: routeId.value });
+
+ listPromise
+ .then(res => {
+ tableData.value = res.data || [];
+ tableLoading.value = false;
+ // 鍒楄〃鍔犺浇瀹屾垚鍚庡垵濮嬪寲鎷栨嫿鎺掑簭
+ nextTick(() => {
+ initSortable();
+ });
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ proxy?.$modal?.msgError("鑾峰彇鍒楄〃澶辫触");
+ });
+};
+
+// 鑾峰彇宸ュ簭鍒楄〃
+const getProcessList = () => {
+ processList({})
+ .then(res => {
+ processOptions.value = res.data || [];
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+ });
+};
+
+// 鑾峰彇宸ヨ壓璺嚎璇︽儏锛堜粠璺敱鍙傛暟鑾峰彇锛�
+const getRouteInfo = () => {
+ routeInfo.value = {
+ processRouteCode: route.query.processRouteCode || '',
+ productName: route.query.productName || '',
+ model: route.query.model || '',
+ bomNo: route.query.bomNo || '',
+ description: route.query.description || ''
+ };
+};
+
+// 鏂板
+const handleAdd = () => {
+ operationType.value = 'add';
+ resetForm();
+ dialogVisible.value = true;
+};
+
+// 缂栬緫
+const handleEdit = (row) => {
+ operationType.value = 'edit';
+ form.value = {
+ id: row.id,
+ routeId: routeId.value,
+ processId: row.processId,
+ productModelId: row.productModelId,
+ productName: row.productName || "",
+ model: row.model || "",
+ unit: row.unit || "",
+ };
+ dialogVisible.value = true;
+};
+
+// 鍒犻櫎
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ュ伐鑹鸿矾绾块」鐩紵', '鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ // 鐢熶骇璁㈠崟涓嬩娇鐢� productProcessRoute 鐨勫垹闄ゆ帴鍙o紙璺敱鍚庢嫾鎺� id锛夛紝鍏跺畠鎯呭喌浣跨敤宸ヨ壓璺嚎椤圭洰鎵归噺鍒犻櫎鎺ュ彛
+ const deletePromise =
+ pageType.value === 'order'
+ ? deleteRouteItem(row.id)
+ : batchDeleteProcessRouteItem([row.id]);
+
+ deletePromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess('鍒犻櫎鎴愬姛');
+ getList();
+ })
+ .catch(() => {
+ proxy?.$modal?.msgError('鍒犻櫎澶辫触');
+ });
+ })
+ .catch(() => {});
+};
+
+// 浜у搧閫夋嫨
+const handleProductSelect = (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ form.value.productModelId = product.id;
+ form.value.productName = product.productName;
+ form.value.model = product.model;
+ form.value.unit = product.unit || "";
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉
+ formRef.value?.validateField('productModelId');
+ }
+};
+
+// 鎻愪氦
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ submitLoading.value = true;
+
+ if (operationType.value === 'add') {
+ // 鏂板锛氫紶鍗曚釜瀵硅薄锛屽寘鍚玠ragSort瀛楁
+ // dragSort = 褰撳墠鍒楄〃闀垮害 + 1锛岃〃绀烘柊澧炶褰曟帓鍦ㄦ渶鍚�
+ const dragSort = tableData.value.length + 1;
+ const isOrderPage = pageType.value === 'order';
+
+ const addPromise = isOrderPage
+ ? addRouteItem({
+ productOrderId: orderId.value,
+ productRouteId: routeId.value,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ dragSort,
+ })
+ : addOrUpdateProcessRouteItem({
+ routeId: routeId.value,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ dragSort,
+ });
+
+ addPromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess('鏂板鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy?.$modal?.msgError('鏂板澶辫触');
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+ } else {
+ // 缂栬緫锛氱敓浜ц鍗曚笅浣跨敤 productProcessRoute/updateRouteItem锛屽叾瀹冩儏鍐典娇鐢ㄥ伐鑹鸿矾绾块」鐩洿鏂版帴鍙�
+ const isOrderPage = pageType.value === 'order';
+
+ const updatePromise = isOrderPage
+ ? addOrUpdateProductProcessRouteItem({
+ id: form.value.id,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ })
+ : addOrUpdateProcessRouteItem({
+ routeId: routeId.value,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ id: form.value.id,
+ });
+
+ updatePromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess('淇敼鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy?.$modal?.msgError('淇敼澶辫触');
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+ }
+ }
+ });
+};
+
+// 閲嶇疆琛ㄥ崟
+const resetForm = () => {
+ form.value = {
+ id: undefined,
+ routeId: routeId.value,
+ processId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+ };
+ formRef.value?.resetFields();
+};
+
+// 鍏抽棴寮圭獥
+const closeDialog = () => {
+ dialogVisible.value = false;
+ resetForm();
+};
+
+// 鍒濆鍖栨嫋鎷芥帓搴�
+const initSortable = () => {
+ destroySortable();
+
+ if (viewMode.value === 'table') {
+ // 琛ㄦ牸瑙嗗浘鐨勬嫋鎷芥帓搴�
+ if (!tableRef.value) return;
+
+ const tbody = tableRef.value.$el.querySelector('.el-table__body tbody') ||
+ tableRef.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
+
+ if (!tbody) return;
+
+ tableSortable = new Sortable(tbody, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ handle: '.el-table__row',
+ filter: '.el-button, .el-select',
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return;
+
+ // 閲嶆柊鎺掑簭鏁扮粍
+ const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
+ tableData.value.splice(evt.newIndex, 0, moveItem);
+
+ // 璁$畻鏂扮殑搴忓彿锛坉ragSort浠�1寮�濮嬶級
+ const newIndex = evt.newIndex;
+ const dragSort = newIndex + 1;
+
+ // 璋冪敤鎺掑簭鎺ュ彛
+ if (moveItem.id) {
+ const isOrderPage = pageType.value === 'order';
+ const sortPromise = isOrderPage
+ ? sortRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ })
+ : sortProcessRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ });
+
+ sortPromise
+ .then(() => {
+ // 鏇存柊鎵�鏈夎鐨刣ragSort
+ tableData.value.forEach((item, index) => {
+ if (item.id) {
+ item.dragSort = index + 1;
+ }
+ });
+ proxy?.$modal?.msgSuccess('鎺掑簭鎴愬姛');
+ })
+ .catch((err) => {
+ // 鎺掑簭澶辫触锛屾仮澶嶅師鏁扮粍
+ tableData.value.splice(newIndex, 1);
+ tableData.value.splice(evt.oldIndex, 0, moveItem);
+ proxy?.$modal?.msgError('鎺掑簭澶辫触');
+ console.error("鎺掑簭澶辫触锛�", err);
+ });
+ }
+ }
+ });
+ } else {
+ // 鍗$墖瑙嗗浘鐨勬嫋鎷芥帓搴�
+ if (!cardsContainer.value) return;
+
+ cardSortable = new Sortable(cardsContainer.value, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ handle: '.process-card',
+ filter: '.el-button',
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return;
+
+ // 閲嶆柊鎺掑簭鏁扮粍
+ const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
+ tableData.value.splice(evt.newIndex, 0, moveItem);
+
+ // 璁$畻鏂扮殑搴忓彿锛坉ragSort浠�1寮�濮嬶級
+ const newIndex = evt.newIndex;
+ const dragSort = newIndex + 1;
+
+ // 璋冪敤鎺掑簭鎺ュ彛
+ if (moveItem.id) {
+ const isOrderPage = pageType.value === 'order';
+ const sortPromise = isOrderPage
+ ? sortRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ })
+ : sortProcessRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ });
+
+ sortPromise
+ .then(() => {
+ // 鏇存柊鎵�鏈夎鐨刣ragSort
+ tableData.value.forEach((item, index) => {
+ if (item.id) {
+ item.dragSort = index + 1;
+ }
+ });
+ proxy?.$modal?.msgSuccess('鎺掑簭鎴愬姛');
+ })
+ .catch((err) => {
+ // 鎺掑簭澶辫触锛屾仮澶嶅師鏁扮粍
+ tableData.value.splice(newIndex, 1);
+ tableData.value.splice(evt.oldIndex, 0, moveItem);
+ proxy?.$modal?.msgError('鎺掑簭澶辫触');
+ console.error("鎺掑簭澶辫触锛�", err);
+ });
+ }
+ }
+ });
+ }
+};
+
+// 閿�姣佹嫋鎷芥帓搴�
+const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (cardSortable) {
+ cardSortable.destroy();
+ cardSortable = null;
+ }
+};
+
+onMounted(() => {
+ getRouteInfo();
+ getList();
+ getProcessList();
+});
+
+onUnmounted(() => {
+ destroySortable();
+});
+</script>
+
+<style scoped>
+.card-container {
+ padding: 20px 0;
+}
+
+.cards-wrapper {
+ display: flex;
+ gap: 16px;
+ overflow-x: auto;
+ padding: 10px 0;
+ min-height: 200px;
+}
+
+.cards-wrapper::-webkit-scrollbar {
+ height: 8px;
+}
+
+.cards-wrapper::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+.cards-wrapper::-webkit-scrollbar-thumb {
+ background: #c1c1c1;
+ border-radius: 4px;
+}
+
+.cards-wrapper::-webkit-scrollbar-thumb:hover {
+ background: #a8a8a8;
+}
+
+.process-card {
+ flex-shrink: 0;
+ width: 220px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ cursor: move;
+ transition: all 0.3s;
+}
+
+.process-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ transform: translateY(-2px);
+}
+
+.card-header {
+ text-align: center;
+ margin-bottom: 12px;
+}
+
+.card-number {
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ border-radius: 50%;
+ background: #409eff;
+ color: #fff;
+ font-weight: bold;
+ font-size: 16px;
+ margin: 0 auto 8px;
+}
+
+.card-process-name {
+ font-size: 14px;
+ color: #333;
+ font-weight: 500;
+ word-break: break-all;
+}
+
+.card-content {
+ flex: 1;
+ margin-bottom: 12px;
+ min-height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.product-info {
+ font-size: 13px;
+ color: #666;
+ text-align: center;
+ width: 100%;
+}
+
+.product-info.empty {
+ color: #999;
+ text-align: center;
+ padding: 20px 0;
+}
+
+.product-name {
+ margin-bottom: 6px;
+ word-break: break-all;
+ line-height: 1.5;
+ text-align: center;
+}
+
+.product-model {
+ color: #909399;
+ font-size: 12px;
+ word-break: break-all;
+ line-height: 1.5;
+ text-align: center;
+}
+
+.product-unit {
+ margin-left: 4px;
+ color: #409eff;
+}
+
+.card-footer {
+ display: flex;
+ justify-content: space-around;
+ padding-top: 12px;
+ border-top: 1px solid #f0f0f0;
+}
+
+.card-footer .el-button {
+ padding: 0;
+ font-size: 12px;
+}
+
+:deep(.sortable-ghost) {
+ opacity: 0.5;
+ background-color: #f5f7fa !important;
+}
+
+:deep(.sortable-drag) {
+ opacity: 0.8;
+}
+
+/* 琛ㄦ牸瑙嗗浘鏍峰紡 */
+:deep(.el-table__row) {
+ transition: background-color 0.2s;
+ cursor: move;
+}
+
+:deep(.el-table__row:hover) {
+ background-color: #f9fafc !important;
+}
+
+/* 鍖哄煙鏍囬鏍峰紡 */
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.section-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ padding-left: 12px;
+ position: relative;
+ margin-bottom: 0;
+}
+
+.section-title::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 3px;
+ height: 16px;
+ background: #409eff;
+ border-radius: 2px;
+}
+
+.section-actions {
+ display: flex;
+ align-items: center;
+}
+
+/* 宸ヨ壓璺嚎淇℃伅鍗$墖鏍峰紡 */
+.route-info-card {
+ margin-bottom: 20px;
+ border: 1px solid #e4e7ed;
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.route-info {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 16px;
+ padding: 4px;
+}
+
+.info-item {
+ display: flex;
+ flex-direction: column;
+ background: #ffffff;
+ border-radius: 6px;
+ padding: 14px 16px;
+ border: 1px solid #f0f2f5;
+ transition: all 0.3s ease;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+}
+
+.info-item:hover {
+ border-color: #409eff;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
+ transform: translateY(-1px);
+}
+
+.info-item.full-width {
+ grid-column: 1 / -1;
+}
+
+.info-label-wrapper {
+ margin-bottom: 8px;
+}
+
+.info-label {
+ display: inline-block;
+ color: #909399;
+ font-size: 12px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ padding: 2px 0;
+ position: relative;
+}
+
+.info-label::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 20px;
+ height: 2px;
+ background: linear-gradient(90deg, #409eff, transparent);
+ border-radius: 1px;
+}
+
+.info-value-wrapper {
+ flex: 1;
+}
+
+.info-value {
+ display: block;
+ color: #303133;
+ font-size: 15px;
+ font-weight: 500;
+ line-height: 1.5;
+ word-break: break-all;
+}
+</style>
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
new file mode 100644
index 0000000..360a124
--- /dev/null
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -0,0 +1,449 @@
+<template>
+ <div class="app-container">
+ <PageHeader content="浜у搧缁撴瀯璇︽儏">
+ <template #right-button>
+ <el-button v-if="!dataValue.isEdit && !isOrderPage" type="primary" @click="dataValue.isEdit = true">缂栬緫
+ </el-button>
+ <el-button v-if="dataValue.isEdit && !isOrderPage" type="primary" @click="cancelEdit">鍙栨秷
+ </el-button>
+ <el-button v-if="!isOrderPage" type="primary" :loading="dataValue.loading" @click="submit"
+ :disabled="!dataValue.isEdit">纭
+ </el-button>
+ </template>
+ </PageHeader>
+ <el-table :data="tableData" border :preserve-expanded-content="false" :default-expand-all="true"
+ style="width: 100%">
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-form ref="form" :model="dataValue">
+ <el-table :data="dataValue.dataList" row-key="tempId" default-expand-all
+ :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" style="width: 100%">
+ <el-table-column prop="productName" label="浜у搧" />
+ <el-table-column prop="model" label="瑙勬牸">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur', 'change'] }]" style="margin: 0">
+ <el-select v-model="row.model" placeholder="璇烽�夋嫨瑙勬牸" clearable
+ :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
+ style="width: 100%" @visible-change="(v) => { if (v) openDialog(row.tempId) }">
+ <el-option v-if="row.model" :label="row.model" :value="row.model" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="processName" label="娑堣�楀伐搴�">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+ style="margin: 0">
+ <el-select v-model="row.processId" placeholder="璇烽�夋嫨" filterable clearable style="width: 100%"
+ :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
+ <el-option v-for="item in dataValue.processOptions" :key="item.id" :label="item.name"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitQuantity" label="鍗曚綅浜у嚭鎵�闇�鏁伴噺">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur', 'change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.unitQuantity" :min="0" :precision="2" :step="1"
+ controls-position="right" style="width: 100%"
+ :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column v-if="isOrderPage" prop="demandedQuantity" label="闇�姹傛�婚噺">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur', 'change'] }]" style="margin: 0">
+ <el-input-number v-model="row.demandedQuantity" :min="0" :precision="2" :step="1"
+ controls-position="right" style="width: 100%"
+ :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unit" label="鍗曚綅">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur', 'change'] }]" style="margin: 0">
+ <el-input v-model="row.unit" placeholder="璇疯緭鍏ュ崟浣�" clearable
+ :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" fixed="right" width="200">
+ <template #default="{ row, $index }">
+ <el-button
+ v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
+ type="danger" text @click="removeItem(row.tempId)">鍒犻櫎
+ </el-button>
+ <el-button v-if="dataValue.isEdit" type="primary" text @click="addItem2(row.tempId)">娣诲姞
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form>
+ </template>
+ </el-table-column>
+ <el-table-column label="BOM缂栧彿" prop="bomNo" />
+ <el-table-column label="浜у搧鍚嶇О" prop="productName" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" />
+ </el-table>
+ <product-select-dialog v-if="dataValue.showProductDialog" v-model:model-value="dataValue.showProductDialog"
+ @confirm="handleProduct" />
+ </div>
+</template>
+
+<script setup lang="ts">
+import {
+ computed,
+ defineAsyncComponent,
+ defineComponent,
+ onMounted,
+ reactive,
+ ref,
+} from "vue";
+import { queryList, add } from "@/api/productionManagement/productStructure.js";
+import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
+import { list } from "@/api/productionManagement/productionProcess";
+import { ElMessage } from "element-plus";
+import { useRoute, useRouter } from "vue-router";
+
+defineComponent({
+ name: "StructureEdit",
+});
+
+const ProductSelectDialog = defineAsyncComponent(
+ () => import("@/views/basicData/product/ProductSelectDialog.vue")
+);
+const emit = defineEmits(["update:router"]);
+const form = ref();
+
+const route = useRoute();
+const router = useRouter();
+const routeId = computed({
+ get() {
+ return route.query.id;
+ },
+
+ set(val) {
+ emit("update:router", val);
+ },
+});
+
+// 浠庤矾鐢卞弬鏁拌幏鍙栦骇鍝佷俊鎭�
+const routeBomNo = computed(() => route.query.bomNo || "");
+const routeProductName = computed(() => route.query.productName || "");
+const routeProductModelName = computed(
+ () => route.query.productModelName || ""
+);
+const routeOrderId = computed(() => route.query.orderId);
+const pageType = computed(() => route.query.type);
+const isOrderPage = computed(
+ () => pageType.value === "order" && routeOrderId.value
+);
+
+const dataValue = reactive({
+ dataList: [],
+ productOptions: [],
+ processOptions: [],
+ showProductDialog: false,
+ currentRowIndex: null,
+ currentRowName: null,
+ loading: false,
+ isEdit: false,
+});
+
+const tableData = reactive([
+ {
+ productName: "",
+ model: "",
+ bomNo: "",
+ },
+]);
+
+const openDialog = (tempId: any) => {
+ console.log(tempId, "tempId");
+ dataValue.currentRowName = tempId;
+ dataValue.showProductDialog = true;
+};
+
+const fetchData = async () => {
+ if (isOrderPage.value) {
+ // 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
+ const { data } = await listProcessBom({ orderId: routeOrderId.value });
+ dataValue.dataList = (data as any) || [];
+ } else {
+ // 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
+ const { data } = await queryList(routeId.value);
+ dataValue.dataList = (data as any) || [];
+ // 涓烘墍鏈夐」鍙婂叾瀛愰」璁剧疆name灞炴��
+ const setNameRecursively = (items: any[]) => {
+ items.forEach((item: any) => {
+ item.tempId = item.id;
+ item.processName =
+ dataValue.processOptions.find(option => option.id === item.processId)
+ ?.name || "";
+ if (item.children && item.children.length > 0) {
+ setNameRecursively(item.children);
+ }
+ });
+ };
+ setNameRecursively(dataValue.dataList);
+ console.log(dataValue.dataList, "dataValue.dataList");
+ }
+};
+
+const fetchProcessOptions = async () => {
+ const { data } = await list();
+ dataValue.processOptions = data as any;
+};
+
+const handleProduct = (row: any) => {
+ if (row?.length > 1) {
+ ElMessage.error("鍙兘閫夋嫨涓�涓骇鍝�");
+ }
+ const productData = row[0];
+
+ // 鏈�澶栧眰缁勪欢涓紝涓庡綋鍓嶄骇鍝佺浉鍚岀殑浜у搧鍙兘鏈変竴涓�
+ const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName);
+ if (isTopLevel) {
+ if (productData.productName === tableData[0].productName &&
+ productData.model === tableData[0].model) {
+ // 鏌ユ壘鏄惁宸茬粡鏈夊叾浠栭《灞傝宸茬粡鏄繖涓骇鍝�
+ const hasOther = dataValue.dataList.some(item =>
+ (item as any).tempId !== dataValue.currentRowName &&
+ (item as any).productName === tableData[0].productName &&
+ (item as any).model === tableData[0].model
+ );
+ if (hasOther) {
+ ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
+ return;
+ }
+ }
+ }
+ // dataValue.dataList[dataValue.currentRowIndex].productName =
+ // row[0].productName;
+ // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
+ // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+ // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
+ dataValue.dataList.map(item => {
+ if (item.tempId === dataValue.currentRowName) {
+ item.productName = productData.productName;
+ item.model = productData.model;
+ item.productModelId = productData.id;
+ item.unit = productData.unit || "";
+ return;
+ }
+ childItem(item, dataValue.currentRowName, productData);
+ });
+ dataValue.showProductDialog = false;
+};
+const childItem = (item: any, tempId: any, productData: any) => {
+ if (item.tempId === tempId) {
+ item.productName = productData.productName;
+ item.model = productData.model;
+ item.productModelId = productData.id;
+ item.unit = productData.unit || "";
+ return true;
+ }
+ if (item.children && item.children.length > 0) {
+ for (let child of item.children) {
+ if (childItem(child, tempId, productData)) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+// 閫掑綊鏍¢獙鎵�鏈夊眰绾х殑琛ㄥ崟鏁版嵁
+const validateAll = () => {
+ let isValid = true;
+
+ // 鏍¢獙鍑芥暟
+ const validateItem = (item: any, isTopLevel = false) => {
+ // 鏍¢獙褰撳墠椤圭殑蹇呭~瀛楁
+ if (!item.model) {
+ ElMessage.error("璇烽�夋嫨瑙勬牸");
+ isValid = false;
+ return;
+ }
+ if (!isTopLevel && !item.processId) {
+ ElMessage.error("璇烽�夋嫨娑堣�楀伐搴�");
+ isValid = false;
+ return;
+ }
+ if (!item.unitQuantity) {
+ ElMessage.error("璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺");
+ isValid = false;
+ return;
+ }
+ if (isOrderPage.value && !item.demandedQuantity) {
+ ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
+ isValid = false;
+ return;
+ }
+ if (!item.unit) {
+ ElMessage.error("璇疯緭鍏ュ崟浣�");
+ isValid = false;
+ return;
+ }
+
+ // 閫掑綊鏍¢獙瀛愰」
+ if (item.children && item.children.length > 0) {
+ item.children.forEach(child => {
+ validateItem(child, false);
+ });
+ }
+ };
+
+ // 閬嶅巻鎵�鏈夐《灞傞」
+ dataValue.dataList.forEach(item => {
+ validateItem(item, true);
+ });
+
+ return isValid;
+};
+
+const submit = () => {
+ dataValue.loading = true;
+
+ // 鍏堣繘琛岃〃鍗曟牎楠�
+ const valid = validateAll();
+ console.log(dataValue.dataList, "dataValue.dataList");
+ if (valid) {
+ add({
+ bomId: routeId.value,
+ children: dataValue.dataList || [],
+ })
+ .then(res => {
+ router.push({
+ path: "/productionManagement/productionManagement/productStructure/index",
+ });
+ ElMessage.success("淇濆瓨鎴愬姛");
+ dataValue.loading = false;
+ })
+ .catch(() => {
+ dataValue.loading = false;
+ });
+ } else {
+ dataValue.loading = false;
+ }
+};
+
+const removeItem = (tempId: string) => {
+ // 鍏堝皾璇曚粠椤跺眰鍒犻櫎
+ const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId);
+ if (topIndex !== -1) {
+ dataValue.dataList.splice(topIndex, 1);
+ return;
+ }
+
+ // 閫掑綊鍒犻櫎瀛愰」
+ const delchildItem = (items: any[], tempId: any) => {
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ if (item.tempId === tempId) {
+ items.splice(i, 1);
+ return true;
+ }
+ if (item.children && item.children.length > 0) {
+ if (delchildItem(item.children, tempId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ dataValue.dataList.forEach(item => {
+ if (item.children && item.children.length > 0) {
+ delchildItem(item.children, tempId);
+ }
+ });
+};
+const addItem2 = tempId => {
+ dataValue.dataList.map(item => {
+ if (item.tempId === tempId) {
+ if (!item.children) {
+ item.children = [];
+ }
+ item.children.push({
+ parentId: item.id || "",
+ parentTempId: item.tempId || "",
+ productName: "",
+ productId: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ processName: "",
+ unitQuantity: 0,
+ demandedQuantity: 0,
+ unit: "",
+ children: [],
+
+ tempId: new Date().getTime(),
+ });
+ return;
+ }
+ addchildItem(item, tempId);
+ });
+};
+const addchildItem = (item: any, tempId: any) => {
+ if (item.tempId === tempId) {
+ console.log(item, "item");
+ if (!item.children) {
+ item.children = [];
+ }
+ item.children.push({
+ parentId: item.id || "",
+ parentTempId: item.tempId || "",
+ productName: "",
+ productId: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ unitQuantity: 0,
+ demandedQuantity: 0,
+ children: [],
+ unit: "",
+ tempId: new Date().getTime(),
+ });
+ return true;
+ }
+ if (item.children && item.children.length > 0) {
+ for (let child of item.children) {
+ if (addchildItem(child, tempId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+const cancelEdit = () => {
+ dataValue.isEdit = false;
+ // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
+ fetchData();
+};
+
+onMounted(async () => {
+ // 浠庤矾鐢卞弬鏁板洖鏄炬暟鎹�
+ tableData[0].productName = routeProductName.value as string;
+ tableData[0].model = routeProductModelName.value as string;
+ tableData[0].bomNo = routeBomNo.value as string;
+
+ // 璁㈠崟鎯呭喌涓嬬鐢ㄧ紪杈�
+ if (isOrderPage.value) {
+ dataValue.isEdit = false;
+ }
+
+ // 鍏堝姞杞藉伐搴忛�夐」锛屽啀鍔犺浇鏁版嵁锛岀‘淇漞l-select鑳藉姝g‘鍥炴樉
+ await fetchProcessOptions();
+ await fetchData();
+});
+</script>
\ No newline at end of file
diff --git a/src/views/productionManagement/productStructure/StructureEdit.vue b/src/views/productionManagement/productStructure/StructureEdit.vue
new file mode 100644
index 0000000..4d07f5d
--- /dev/null
+++ b/src/views/productionManagement/productStructure/StructureEdit.vue
@@ -0,0 +1,311 @@
+<template>
+ <el-dialog v-model="visible"
+ title="缁撴瀯"
+ width="1200"
+ close-on-click-modal
+ @close="visible = false">
+ <el-button v-if="dataValue.isEdit"
+ type="primary"
+ @click="addItem"
+ style="margin-bottom: 10px">娣诲姞
+ </el-button>
+ <el-button v-if="!dataValue.isEdit"
+ type="primary"
+ @click="dataValue.isEdit = true"
+ style="margin-bottom: 10px">缂栬緫
+ </el-button>
+ <el-button v-if="dataValue.isEdit"
+ type="primary"
+ @click="cancelEdit"
+ style="margin-bottom: 10px">鍙栨秷
+ </el-button>
+
+ <el-table
+ :data="tableData"
+ border
+ :preserve-expanded-content="false"
+ style="width: 100%"
+ >
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-form ref="form"
+ :model="dataValue">
+ <el-table :data="dataValue.dataList"
+ style="width: 100%">
+ <el-table-column prop="productName"
+ label="浜у搧"
+ width="150" />
+ <el-table-column prop="model"
+ label="瑙勬牸"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :prop="`dataList.${$index}.model`"
+ :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-select v-model="row.model"
+ placeholder="璇烽�夋嫨浜у搧"
+ clearable
+ :disabled="!dataValue.isEdit"
+ style="width: 100%"
+ @visible-change="(v) => { if (v) openDialog($index) }">
+ <el-option v-if="row.model"
+ :label="row.model"
+ :value="row.model" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="processId"
+ label="娑堣�楀伐搴�"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.processId`"
+ :rules="[{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+ style="margin: 0">
+ <el-select v-model="row.processId"
+ placeholder="璇烽�夋嫨"
+ filterable
+ clearable
+ style="width: 100%"
+ :disabled="!dataValue.isEdit">
+ <el-option v-for="item in dataValue.processOptions"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitQuantity"
+ label="鍗曚綅浜у嚭鎵�闇�鏁伴噺"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.unitQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.unitQuantity"
+ :min="0"
+ :precision="2"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="demandedQuantity"
+ label="闇�姹傛�婚噺"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.demandedQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.demandedQuantity"
+ :min="0"
+ :precision="2"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unit"
+ label="鍗曚綅"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.unit`"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input v-model="row.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="diskQuantity"
+ label="鐩樻暟锛堢洏锛�"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.diskQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ョ洏鏁�', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.diskQuantity"
+ :min="0"
+ :precision="0"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔">
+ <template #default="{ row, $index }">
+ <el-button type="danger"
+ text
+ @click="dataValue.dataList.splice($index, 1)">鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form>
+ </template>
+ </el-table-column>
+ <el-table-column label="浜у搧缂栫爜" prop="productCode" />
+ <el-table-column label="浜у搧鍚嶇О" prop="productName" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ </el-table>
+
+ <product-select-dialog v-if="dataValue.showProductDialog"
+ v-model:model-value="dataValue.showProductDialog"
+ @confirm="handleProduct" />
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ :loading="dataValue.loading"
+ @click="submit"
+ :disabled="!dataValue.isEdit">
+ 纭
+ </el-button>
+ <el-button @click="visible = false">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup lang="ts">
+ import {
+ computed,
+ defineAsyncComponent,
+ defineComponent,
+ onMounted,
+ reactive,
+ ref,
+ } from "vue";
+ import { queryList, add } from "@/api/productionManagement/productStructure.js";
+ import { list } from "@/api/productionManagement/productionProcess";
+ import { ElMessage } from "element-plus";
+
+ defineComponent({
+ name: "StructureEdit",
+ });
+
+ const ProductSelectDialog = defineAsyncComponent(
+ () => import("@/views/basicData/product/ProductSelectDialog.vue")
+ );
+ const form = ref();
+
+ const props = defineProps({
+ showModel: {
+ type: Boolean,
+ default: false,
+ },
+ record: {
+ type: Object,
+ required: true,
+ },
+ });
+
+ const emits = defineEmits(["update:showModel"]);
+ const visible = computed({
+ get() {
+ return props.showModel;
+ },
+ set(val) {
+ emits("update:showModel", val);
+ },
+ });
+
+ const dataValue = reactive({
+ dataList: [],
+ productOptions: [],
+ processOptions: [],
+ showProductDialog: false,
+ currentRowIndex: null,
+ loading: false,
+ isEdit: false,
+ });
+
+ const tableData = [
+ {
+ productName: props.record.productName,
+ model: props.record.model,
+ unit: props.record.unit,
+ productCode: props.record.productCode,
+ }
+ ]
+
+ const openDialog = index => {
+ dataValue.currentRowIndex = index;
+ dataValue.showProductDialog = true;
+ };
+
+ const fetchData = async () => {
+ const { data } = await queryList(props.record.id);
+ dataValue.dataList = data;
+ };
+
+ const fetchProcessOptions = async () => {
+ const { data } = await list(props.record.id);
+ dataValue.processOptions = data;
+ };
+
+ const handleProduct = row => {
+ if (row?.length > 1) {
+ ElMessage.error("鍙兘閫夋嫨涓�涓骇鍝�");
+ }
+ dataValue.dataList[dataValue.currentRowIndex].productName =
+ row[0].productName;
+ dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
+ dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+ dataValue.showProductDialog = false;
+ };
+
+ const submit = () => {
+ form.value
+ .validate(valid => {
+ dataValue.loading = true;
+ if (valid) {
+ add({
+ parentId: props.record.id,
+ productStructureList: dataValue.dataList || [],
+ }).then(res => {
+ ElMessage.success("淇濆瓨鎴愬姛");
+ visible.value = false;
+ dataValue.loading = false;
+ });
+ }
+ })
+ .finally(() => {
+ dataValue.loading = false;
+ });
+ };
+
+ const addItem = () => {
+ dataValue.dataList.push({
+ productName: "",
+ productId: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ unitQuantity: 0,
+ demandedQuantity: 0,
+ unit: "",
+ diskQuantity: 0,
+ });
+ };
+
+ const cancelEdit = () => {
+ dataValue.isEdit = false;
+ dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
+ };
+
+ onMounted(() => {
+ fetchData();
+ fetchProcessOptions();
+ });
+</script>
\ No newline at end of file
diff --git a/src/views/productionManagement/productStructure/index.vue b/src/views/productionManagement/productStructure/index.vue
new file mode 100644
index 0000000..ce88565
--- /dev/null
+++ b/src/views/productionManagement/productStructure/index.vue
@@ -0,0 +1,416 @@
+<template>
+ <div class="app-container">
+ <div style="text-align: right; margin-bottom: 10px;">
+ <el-button type="info" plain icon="Upload" @click="handleImport">瀵煎叆</el-button>
+ <el-button type="warning" plain icon="Download" @click="handleExport"
+ :disabled="selectedRows.length !== 1">瀵煎嚭</el-button>
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">鍒犻櫎</el-button>
+ </div>
+ <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true"
+ @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination">
+ <template #detail="{ row }">
+ <el-button type="primary" text @click="showDetail(row)">{{ row.bomNo }}
+ </el-button>
+ </template>
+ </PIMTable>
+ <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow" />
+
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog v-model="dialogVisible" :title="operationType === 'add' ? '鏂板BOM' : '缂栬緫BOM'" width="600px"
+ @close="closeDialog">
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+ <el-form-item label="浜у搧鍚嶇О" prop="productModelId">
+ <el-button type="primary" @click="showProductSelectDialog = true">
+ {{ form.productName || '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+ <el-form-item label="鐗堟湰鍙�" prop="version">
+ <el-input v-model="form.version" placeholder="璇疯緭鍏ョ増鏈彿" clearable />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" clearable />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 浜у搧閫夋嫨寮圭獥 -->
+ <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single />
+
+ <!-- BOM瀵煎叆瀵硅瘽妗� -->
+ <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
+ <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url"
+ :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess"
+ :auto-upload="false" drag>
+ <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+ <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <template #tip>
+ <div class="el-upload__tip text-center">
+ <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
+ </div>
+ </template>
+ </el-upload>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+ <el-button @click="upload.open = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
+import { getToken } from "@/utils/auth";
+import { listPage, add, update, batchDelete, exportBom } from "@/api/productionManagement/productBom.js";
+import { useRouter } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+
+const router = useRouter()
+const { proxy } = getCurrentInstance()
+const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue'))
+
+const tableColumn = ref([
+ {
+ label: "BOM缂栧彿",
+ prop: "bomNo",
+ dataType: 'slot',
+ slot: "detail",
+ minWidth: 140
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+
+ minWidth: 160
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "productModelName",
+ minWidth: 140
+ },
+ {
+ label: "鐗堟湰鍙�",
+ prop: "version",
+ width: 100
+ },
+ {
+ label: "澶囨敞",
+ prop: "remark",
+ minWidth: 160
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 150,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ handleEdit(row)
+ }
+ },
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ clickFun: (row) => {
+ handleDelete(row)
+ }
+ }
+ ]
+ }
+]);
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const showEdit = ref(false);
+const selectedRows = ref([]);
+const currentRow = ref({});
+const dialogVisible = ref(false);
+const operationType = ref('add'); // add | edit
+const formRef = ref(null);
+const showProductSelectDialog = ref(false);
+
+// BOM瀵煎叆鍙傛暟
+const upload = reactive({
+ // 鏄惁鏄剧ず寮瑰嚭灞傦紙BOM瀵煎叆锛�
+ open: false,
+ // 寮瑰嚭灞傛爣棰橈紙BOM瀵煎叆锛�
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/productBom/uploadBom"
+});
+
+const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+});
+
+const data = reactive({
+ form: {
+ id: undefined,
+ productName: "",
+ productModelName: "",
+ productModelId: "",
+ remark: "",
+ version: ""
+ },
+ rules: {
+ productModelId: [{ required: true, message: "璇烽�夋嫨浜у搧", trigger: "change" }],
+ version: [{ required: true, message: "璇疯緭鍏ョ増鏈彿", trigger: "blur" }]
+ }
+});
+
+const { form, rules } = toRefs(data);
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鍒嗛〉
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+
+// 鏌ヨ鍒楄〃
+const getList = () => {
+ tableLoading.value = true;
+ listPage({
+ current: page.current,
+ size: page.size,
+ })
+ .then((res) => {
+ const records = res?.data?.records || [];
+ tableData.value = records;
+ page.total = res?.data?.total || 0;
+ })
+ .catch((err) => {
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+// 鏂板
+const handleAdd = () => {
+ operationType.value = 'add';
+ Object.assign(form.value, {
+ id: undefined,
+ productName: "",
+ productModelName: "",
+ productModelId: "",
+ remark: "",
+ version: ""
+ });
+ dialogVisible.value = true;
+};
+
+// 缂栬緫
+const handleEdit = (row) => {
+ operationType.value = 'edit';
+ Object.assign(form.value, {
+ id: row.id,
+ productName: row.productName || "",
+ productModelName: row.productModelName || "",
+ productModelId: row.productModelId || "",
+ remark: row.remark || "",
+ version: row.version || ""
+ });
+ dialogVisible.value = true;
+};
+
+// 鍒犻櫎锛堝崟鏉★級
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇OM锛�', '鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ batchDelete([row.id])
+ .then(() => {
+ proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('鍒犻櫎澶辫触');
+ });
+ })
+ .catch(() => { });
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (!selectedRows.value.length) {
+ proxy.$modal.msgWarning('璇烽�夋嫨鏁版嵁');
+ return;
+ }
+ const ids = selectedRows.value.map(item => item.id);
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ batchDelete(ids)
+ .then(() => {
+ proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('鍒犻櫎澶辫触');
+ });
+ })
+ .catch(() => { });
+};
+
+// 浜у搧閫夋嫨
+const handleProductSelect = (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ form.value.productModelId = product.id;
+ form.value.productName = product.productName;
+ form.value.productModelName = product.model;
+ }
+ showProductSelectDialog.value = false;
+};
+
+// 鎻愪氦琛ㄥ崟
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const payload = { ...form.value };
+ if (operationType.value === 'add') {
+ add(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess('鏂板鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('鏂板澶辫触');
+ });
+ } else {
+ update(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess('淇敼鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('淇敼澶辫触');
+ });
+ }
+ }
+ });
+};
+
+// 鍏抽棴寮圭獥
+const closeDialog = () => {
+ dialogVisible.value = false;
+ formRef.value?.resetFields();
+};
+
+// 瀵煎叆鎸夐挳鎿嶄綔
+const handleImport = () => {
+ upload.title = "BOM瀵煎叆";
+ upload.open = true;
+};
+
+// 鏂囦欢涓婁紶涓鐞�
+const handleFileUploadProgress = (event, file, fileList) => {
+ upload.isUploading = true;
+};
+
+// 鏂囦欢涓婁紶鎴愬姛澶勭悊
+const handleFileSuccess = (response, file, fileList) => {
+ upload.open = false;
+ upload.isUploading = false;
+ proxy.$refs["uploadRef"].handleRemove(file);
+ if (response.code === 200) {
+ proxy.$modal.msgSuccess(response.msg || "瀵煎叆鎴愬姛");
+ getList();
+ } else {
+ proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "瀵煎叆缁撴灉", { dangerouslyUseHTMLString: true });
+ }
+};
+
+// 鎻愪氦涓婁紶鏂囦欢
+const submitFileForm = () => {
+ proxy.$refs["uploadRef"].submit();
+};
+
+// 瀵煎嚭鎸夐挳鎿嶄綔
+const handleExport = () => {
+ if (selectedRows.value.length !== 1) {
+ proxy.$modal.msgWarning("璇烽�夋嫨涓�鏉℃暟鎹繘琛屽鍑�");
+ return;
+ }
+
+ const bomId = selectedRows.value[0].id;
+ const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`;
+
+ exportBom(bomId).then(res => {
+ // 杩斿洖鐨勬暟鎹槸鍚︿负绌�
+ if (!res) {
+ proxy.$modal.msgError("瀵煎嚭澶辫触锛岃繑鍥炴暟鎹负绌�");
+ return;
+ }
+
+ const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+ const downloadElement = document.createElement('a');
+ const href = window.URL.createObjectURL(blob);
+
+ downloadElement.style.display = 'none';
+ downloadElement.href = href;
+ downloadElement.download = fileName;
+
+ document.body.appendChild(downloadElement);
+ downloadElement.click();
+
+ document.body.removeChild(downloadElement);
+ window.URL.revokeObjectURL(href);
+
+ proxy.$modal.msgSuccess("瀵煎嚭鎴愬姛");
+ }).catch(err => {
+ console.error("瀵煎嚭寮傚父锛�", err);
+ proxy.$modal.msgError("绯荤粺寮傚父锛屽鍑哄け璐�");
+ });
+};
+
+// 鏌ョ湅璇︽儏
+const showDetail = (row) => {
+ router.push({
+ path: '/productionManagement/productStructureDetail',
+ query: {
+ id: row.id,
+ bomNo: row.bomNo || '',
+ productName: row.productName || '',
+ productModelName: row.productModelName || ''
+ }
+ });
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
index 6014d00..229bf04 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -14,6 +14,15 @@
clearable
prefix-icon="Search"
/>
+ <span class="search_title ml10">鍚堝悓鍙凤細</span>
+ <el-input
+ v-model="searchForm.salesContractNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
@@ -56,6 +65,26 @@
prop: "schedulingUserName",
width: 90,
},
+ {
+ label: "鍚堝悓鍙�",
+ prop: "salesContractNo",
+ width: 220,
+ },
+ // {
+ // label: "瀹㈡埛鍚堝悓鍙�",
+ // prop: "customerContractNo",
+ // width: 250,
+ // },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: 250,
+ },
+ // {
+ // label: "椤圭洰鍚嶇О",
+ // prop: "projectName",
+ // width:300
+ // },
{
label: "浜у搧澶х被",
prop: "productCategory",
@@ -101,6 +130,7 @@
const data = reactive({
searchForm: {
schedulingUserName: "",
+ salesContractNo: "",
entryDate: [
dayjs().format("YYYY-MM-DD"),
dayjs().add(1, "day").format("YYYY-MM-DD"),
@@ -151,7 +181,7 @@
type: "warning",
})
.then(() => {
- proxy.download("/basic/customer/export", {}, "鐢熶骇鏍哥畻.xlsx");
+ proxy.download("/salesLedger/productionAccounting/export", {}, "鐢熶骇鏍哥畻.xlsx");
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
diff --git a/src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue b/src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue
new file mode 100644
index 0000000..b4a76f6
--- /dev/null
+++ b/src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue
@@ -0,0 +1,153 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ title="鑷姩娲惧伐"
+ width="80%"
+ @close="closeDia"
+ >
+ <el-form :model="form" label-width="140px" label-position="top" ref="formRef">
+ <el-divider content-position="left">娲惧伐鍒楄〃</el-divider>
+
+ <el-table
+ :data="dispatchList"
+ border
+ style="width: 100%; margin-top: 20px;"
+ :row-class-name="tableRowClassName"
+ >
+ <el-table-column label="搴忓彿" type="index" width="60" align="center" />
+ <el-table-column label="鍚堝悓鍙�" prop="salesContractNo" width="200" />
+ <el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" width="200" />
+ <!-- <el-table-column label="椤圭洰鍚嶇О" prop="projectName" width="250" /> -->
+ <el-table-column label="浜у搧澶х被" prop="productCategory" width="150" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" width="200" />
+ <el-table-column label="缁戝畾鏈哄櫒" prop="speculativeTradingName" width="120" />
+ <el-table-column label="鎬绘暟閲�" prop="quantity" width="100" align="right" />
+ <el-table-column label="宸叉帓浜�" prop="schedulingNum" width="100" align="right" fixed="right" />
+ <el-table-column label="寰呮帓浜�" prop="pendingQuantity" width="100" align="right" fixed="right" />
+ <el-table-column label="鏈鎺掍骇" width="150" align="center" fixed="right">
+ <template #default="{ row }">
+ <el-input-number
+ v-model="row.schedulingNum"
+ :min="0"
+ :max="row.pendingQuantity"
+ :step="1"
+ :precision="0"
+ size="small"
+ style="width: 120px"
+ @change="(value) => changeCurrentNum(value, row)"
+ />
+ </template>
+ </el-table-column>
+ </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 {ref, reactive, toRefs, computed} from "vue";
+import {productionDispatch, productionDispatchList} from "@/api/productionManagement/productionOrder.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+
+const data = reactive({
+ form: {},
+ dispatchList: [], // 娲惧伐鍒楄〃鏁版嵁
+});
+
+const { form, dispatchList } = toRefs(data);
+
+
+// 琛ㄦ牸琛屾牱寮�
+const tableRowClassName = ({ rowIndex }) => {
+ if (rowIndex % 2 === 1) {
+ return 'even-row'
+ }
+ return ''
+}
+
+// 淇敼鏈鎺掍骇鏁伴噺
+const changeCurrentNum = (value, row) => {
+ if (value > row.pendingQuantity) {
+ row.schedulingNum = row.pendingQuantity
+ proxy.$modal.msgWarning('鎺掍骇鏁伴噺涓嶅彲澶т簬寰呮帓浜ф暟閲�')
+ }
+}
+
+// 鎵撳紑寮规
+const openDialog = (type, rows) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+
+ // 澶勭悊浼犲叆鐨勬暟鎹�
+ dispatchList.value = rows.map(row => ({
+ ...row,
+ schedulingNum: 0, // 鍒濆鍖栨湰娆℃帓浜ф暟閲忎负0
+ pendingQuantity: (Number(row.quantity) || 0) - (Number(row.schedulingNum) || 0) // 璁$畻寰呮帓浜ф暟閲�
+ }))
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ // 妫�鏌ユ槸鍚︽湁鎺掍骇鏁版嵁
+ const hasSchedulingData = dispatchList.value.some(item => item.schedulingNum > 0)
+ if (!hasSchedulingData) {
+ proxy.$modal.msgWarning('璇疯嚦灏戜负涓�鏉¤褰曡缃帓浜ф暟閲�')
+ return
+ }
+
+ // 鏋勯�犳彁浜ゆ暟鎹� - 鐩存帴浼犻�掓暟缁勶紝涓嶈繃婊�
+ const submitData = dispatchList.value
+
+ console.log('鎻愪氦鑷姩娲惧伐鏁版嵁:', submitData)
+
+ // 璋冪敤API锛堣繖閲岄渶瑕佹牴鎹疄闄呮帴鍙h皟鏁达級
+ productionDispatchList(submitData).then(res => {
+ proxy.$modal.msgSuccess(res.msg);
+ closeDia();
+ }).catch(err => {
+ proxy.$modal.msgError("娲惧伐澶辫触");
+ console.error('娲惧伐澶辫触:', err);
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ dispatchList.value = []
+ emit('close')
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+:deep(.even-row) {
+ background-color: #fafafa;
+}
+
+:deep(.el-table .cell) {
+ padding: 8px 12px;
+}
+
+:deep(.el-table th) {
+ background-color: #f5f7fa;
+ color: #606266;
+ font-weight: 600;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/productionDispatching/components/formDia.vue b/src/views/productionManagement/productionDispatching/components/formDia.vue
index de04e07..e7e6e15 100644
--- a/src/views/productionManagement/productionDispatching/components/formDia.vue
+++ b/src/views/productionManagement/productionDispatching/components/formDia.vue
@@ -7,25 +7,43 @@
@close="closeDia"
>
<el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
- <el-row :gutter="30">
+ <!-- <el-row :gutter="30">
+ <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="productCategory">
<el-input v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable disabled/>
</el-form-item>
</el-col>
+ </el-row> -->
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="specificationModel">
+ <el-input v-model="form.specificationModel" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁戝畾鏈哄櫒锛�" prop="speculativeTradingName">
+ <el-input v-model="form.speculativeTradingName" placeholder="鑷姩鑾峰彇" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
<el-col :span="12">
<el-form-item label="鎬绘暟閲忥細" prop="quantity">
<el-input v-model="form.quantity" placeholder="璇疯緭鍏�" clearable disabled/>
</el-form-item>
</el-col>
- </el-row>
- <el-row :gutter="30">
-
<el-col :span="12">
<el-form-item label="寰呮帓浜ф暟閲忥細" prop="pendingQuantity">
<el-input v-model="form.pendingQuantity" placeholder="璇疯緭鍏�" clearable disabled/>
</el-form-item>
</el-col>
+ </el-row>
+ <el-row :gutter="30">
<el-col :span="12">
<el-form-item label="鏈鎺掍骇鏁伴噺锛�" prop="schedulingNum">
<el-input-number
@@ -40,6 +58,11 @@
/>
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <el-input v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
@@ -48,6 +71,9 @@
v-model="form.schedulingUserId"
placeholder="閫夋嫨浜哄憳"
style="width: 100%;"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
>
<el-option
v-for="user in userList"
@@ -85,7 +111,6 @@
<script setup>
import {ref} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionDispatch} from "@/api/productionManagement/productionOrder.js";
import useUserStore from "@/store/modules/user.js";
@@ -97,13 +122,15 @@
const operationType = ref('')
const data = reactive({
form: {
+ projectName: "",
productCategory: "",
+ specificationModel: "", // 瑙勬牸鍨嬪彿
quantity: "",
schedulingNum: "",
schedulingUserId: "",
schedulingDate: "",
pendingQuantity: "",
- salesLedgerProductId: "",
+ speculativeTradingName: "", // 缁戝畾鏈哄櫒鍚嶇О
},
rules: {
schedulingNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
@@ -115,6 +142,7 @@
const userList = ref([])
const userStore = useUserStore()
+
// 鎵撳紑寮规
const openDialog = (type, row) => {
operationType.value = type;
@@ -123,10 +151,6 @@
userList.value = res.data;
});
form.value = {...row}
- // 缁戝畾澶栧眰浼犲叆鐨勪骇鍝両D鍒板悗绔渶瑕佺殑 salesLedgerProductId 瀛楁
- form.value.salesLedgerProductId = row.id;
- // 纭繚涓嶄細鎶婂師濮� id 褰撲綔鎺掍骇璁板綍涓婚敭浼犵粰鍚庣
- delete form.value.id;
form.value.schedulingNum = 0
form.value.schedulingUserId = userStore.id
form.value.schedulingDate = dayjs().format("YYYY-MM-DD");
diff --git a/src/views/productionManagement/productionDispatching/index.vue b/src/views/productionManagement/productionDispatching/index.vue
index cb6accd..2d39890 100644
--- a/src/views/productionManagement/productionDispatching/index.vue
+++ b/src/views/productionManagement/productionDispatching/index.vue
@@ -1,15 +1,77 @@
<template>
<div class="app-container">
+ <!-- 鐐掓満1-4 灞曠ず锛堟�婚噺 / 姝e湪鐢熶骇閲� / 绌轰綑閲忥級 -->
+ <div class="machines-grid">
+ <div v-for="machine in machines" :key="machine.id" class="machine-card">
+ <div class="machine-title">{{ machine.name }}</div>
+ <div class="machine-metrics">
+ <div class="machine-control">
+ <span>鎬婚噺(kg)锛�</span>
+ <el-input-number v-model="machineData[machine.name].workLoad" :min="0" :step="1" size="small" />
+ </div>
+ <div><span> 棰勮鎶曞叆閲�(kg)锛�</span><span>{{ machineData[machine.name].currentWorkLoad }}</span></div>
+ <div><span>绌轰綑宸ヤ綔閲�(kg)锛�</span><span>{{ machineData[machine.name].vacant }}</span></div>
+ </div>
+ </div>
+ <div class="save-button-container">
+ <div class="loss-rate-container">
+ <span class="loss-rate-label">鎹熻�楃巼(%)锛�</span>
+ <el-select v-model="rate" placeholder="璇烽�夋嫨鎹熻�楃巼" style="width: 120px" size="small">
+ <el-option label="6" :value="6" />
+ <el-option label="7" :value="7" />
+ <el-option label="8" :value="8" />
+ <el-option label="9" :value="9" />
+ <el-option label="10" :value="10" />
+ </el-select>
+ </div>
+ <el-button type="primary" @click="saveMachineTotals" size="small">淇濆瓨璁剧疆</el-button>
+ </div>
+ </div>
<div class="search_form">
<div>
- <span class="search_title">褰曞叆鏃ユ湡锛�</span>
+ <span class="search_title">瀹㈡埛鍚嶇О锛�</span>
+ <el-input
+ v-model="searchForm.customerName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+ <span class="search_title ml10">鍚堝悓鍙凤細</span>
+ <el-input
+ v-model="searchForm.salesContractNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+<!-- <span class="search_title ml10">椤圭洰鍚嶇О锛�</span>-->
+<!-- <el-input-->
+<!-- v-model="searchForm.projectName"-->
+<!-- style="width: 240px"-->
+<!-- placeholder="璇疯緭鍏�"-->
+<!-- @change="handleQuery"-->
+<!-- clearable-->
+<!-- prefix-icon="Search"-->
+<!-- />-->
+ <span class="search_title ml10">褰曞叆鏃ユ湡锛�</span>
<el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ <el-checkbox
+ style="margin-left: 10px"
+ v-model="searchForm.status"
+ label="涓嶆樉绀哄緟鎺掓暟閲忎负0"
+ @change="handleQuery"
+ />
<el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
</div>
<div>
- <el-button type="primary" @click="openForm('add')">鐢熶骇娲惧伐</el-button>
- </div>
+ <el-button type="primary" @click="openForm('add')">鐢熶骇娲惧伐</el-button>
+ <el-button type="success" @click="openAutoDispatch">鑷姩娲惧伐</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
</div>
<div class="table_list">
<PIMTable
@@ -25,31 +87,40 @@
></PIMTable>
</div>
<form-dia ref="formDia" @close="handleQuery"></form-dia>
+ <auto-dispatch-dia ref="autoDispatchDia" @close="handleQuery"></auto-dispatch-dia>
</div>
</template>
<script setup>
-import {onMounted, ref} from "vue";
+import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, computed, watch} from "vue";
import FormDia from "@/views/productionManagement/productionDispatching/components/formDia.vue";
+import AutoDispatchDia from "@/views/productionManagement/productionDispatching/components/autoDispatchDia.vue";
import dayjs from "dayjs";
-import {schedulingListPage} from "@/api/productionManagement/productionOrder.js";
+import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading, getLossRate, addLossRate, updateLossRate} from "@/api/productionManagement/productionOrder.js";
+import { ElMessageBox } from "element-plus";
const data = reactive({
searchForm: {
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
+ customerName: "",
+ salesContractNo: "",
+ projectName: "",
+ status: true,
+ entryDate: [dayjs().format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")], // 褰曞叆鏃ユ湡锛岄粯璁ゅ綋澶�
entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().format("YYYY-MM-DD"),
},
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
{
- label: "褰曞叆鏃ユ湡",
- prop: "registerDate",
- width: 120,
+ label: "鍚堝悓鍙�",
+ prop: "salesContractNo",
+ width: 220,
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: 250,
},
{
label: "浜у搧澶х被",
@@ -59,12 +130,48 @@
{
label: "瑙勬牸鍨嬪彿",
prop: "specificationModel",
- width: 220,
+ width: 120,
+ },
+ {
+ label: "缁戝畾鏈哄櫒",
+ prop: "speculativeTradingName",
+ width: 160,
},
{
label: "鍗曚綅",
prop: "unit",
width:90
+ },
+ {
+ label: "褰曞叆鏃ユ湡",
+ prop: "entryDate",
+ width: 120,
+ },
+ {
+ label: "鐘舵��",
+ prop: "status",
+ dataType: "tag",
+ formatType: (params) => {
+ if (params == '鐢熶骇涓�') {
+ return "warning";
+ } else if (params == '鏈紑濮�') {
+ return "danger";
+ } else {
+ return "success";
+ }
+ },
+ },
+ {
+ label: "鐢熶骇杩涘害",
+ prop: "progress",
+ formatData: (cellValue) => {
+ // 濡傛灉鍊间负绌烘垨undefined锛屾樉绀虹┖瀛楃涓�
+ if (cellValue === null || cellValue === undefined || cellValue === '') {
+ return '';
+ }
+ // 鐩存帴鍦ㄦ暟瀛楀悗闈㈡坊鍔犵櫨鍒嗗彿
+ return `${cellValue}%`;
+ }
},
{
label: "鏁伴噺",
@@ -79,6 +186,7 @@
label: "寰呮帓鏁伴噺",
prop: "pendingQuantity",
width: 100,
+ fixed: 'right',
},
]);
const tableData = ref([]);
@@ -90,7 +198,140 @@
total: 0,
});
const formDia = ref()
+const autoDispatchDia = ref()
const { proxy } = getCurrentInstance()
+
+// 鐐掓満鏁版嵁
+const machineData = reactive({
+ "鐐掓満1": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
+ "鐐掓満2": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
+ "鐐掓満3": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
+ "鐐掓満4": { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
+})
+
+// 鐐掓満閰嶇疆鏁扮粍
+const machines = [
+ { id: 1, name: '鐐掓満1' },
+ { id: 2, name: '鐐掓満2' },
+ { id: 3, name: '鐐掓満3' },
+ { id: 4, name: '鐐掓満4' }
+]
+
+// 淇濆瓨鐐掓満鎬婚噺璁剧疆
+const saveMachineTotals = () => {
+ // 楠岃瘉鎹熻�楃巼鏄惁宸查�夋嫨
+ if (rate.value === null || rate.value === undefined || isNaN(rate.value)) {
+ proxy.$message.warning('璇烽�夋嫨鎹熻�楃巼');
+ return;
+ }
+
+ // 鏋勯�犱繚瀛樻暟鎹暟缁勶紝浣跨敤machines鏁扮粍寰幆鏋勫缓
+ const saveData = machines.map(machine => {
+ const saveItem = {
+ name: machine.name, // 鐐掓満鍚嶇О
+ workLoad: machineData[machine.name].workLoad, // 鎬婚噺
+ currentWorkLoad: machineData[machine.name].currentWorkLoad, // 棰勮鎶曞叆閲�
+ vacant: machineData[machine.name].vacant // 绌轰綑閲�
+ };
+
+ // 濡傛灉鏄慨鏀规搷浣滐紝闇�瑕佷紶閫抜d瀛楁
+ if (hasQueryData.value) {
+ const queryData = getMachineQueryData(machine.id);
+ if (queryData && queryData.id) {
+ saveItem.id = queryData.id;
+ }
+ }
+
+ return saveItem;
+ });
+
+ // 鏋勯�犳崯鑰楃巼鏁版嵁
+ const rateData = {
+ rate: rate.value
+ };
+
+ // 濡傛灉鏈塈D锛岃鏄庢槸淇敼鎿嶄綔
+ if (rateId.value) {
+ rateData.id = rateId.value;
+ }
+
+ // 鏍规嵁鏄惁鏈夋煡璇㈡暟鎹喅瀹氳皟鐢ㄦ柊澧炴帴鍙h繕鏄慨鏀规帴鍙�
+ const saveApi = hasQueryData.value ? updateSpeculatTrading : addSpeculatTrading;
+ const successMessage = hasQueryData.value ? '鐐掓満璁剧疆淇敼鎴愬姛' : '鐐掓満璁剧疆鏂板鎴愬姛';
+
+ // 鏍规嵁鏄惁鏈塈D鍐冲畾璋冪敤鏂板鎺ュ彛杩樻槸淇敼鎺ュ彛
+ const rateApi = rateId.value ? updateLossRate : addLossRate;
+ const rateSuccessMessage = rateId.value ? '鎹熻�楃巼淇敼鎴愬姛' : '鎹熻�楃巼鏂板鎴愬姛';
+
+ // 骞惰璋冪敤涓や釜鎺ュ彛
+ Promise.all([
+ saveApi(saveData),
+ rateApi(rateData)
+ ]).then(([saveRes, rateRes]) => {
+ proxy.$message.success(successMessage);
+ proxy.$message.success(rateSuccessMessage);
+
+ // 淇濆瓨鎴愬姛鍚庯紝璁剧疆hasQueryData涓簍rue锛屼笅娆′繚瀛樺皢璋冪敤淇敼鎺ュ彛
+ if (!hasQueryData.value) {
+ hasQueryData.value = true;
+ }
+
+ // 濡傛灉杩斿洖浜咺D锛屼繚瀛樿捣鏉�
+ if (rateRes && rateRes.data && rateRes.data.id) {
+ rateId.value = rateRes.data.id;
+ }
+
+ // 淇濆瓨鎴愬姛鍚庨噸鏂拌皟鐢ㄦ煡璇㈤〉闈�
+ getList();
+ }).catch(err => {
+ proxy.$message.error('淇濆瓨澶辫触');
+ console.error('淇濆瓨澶辫触:', err);
+ });
+}
+
+// 鑾峰彇鐐掓満鏌ヨ鏁版嵁
+const machineQueryData = ref([]);
+
+const getMachineQueryData = (machineId) => {
+ return machineQueryData.value.find(item => item.id === machineId);
+};
+
+const getMachineIndex = (item) => {
+ // 鍏煎澶氱瀛楁鍛藉悕锛岃繑鍥� 1-4 涔嬩竴锛屽惁鍒欒繑鍥� 0锛堟湭鐭ワ級
+ const candidates = [item.machineId, item.machineNo, item.machine, item.deviceNo, item.deviceId]
+ for (const v of candidates) {
+ if (v === undefined || v === null) continue
+ const n = Number(String(v).replace(/[^\d]/g, "")) // 鎶藉彇鏁板瓧
+ if ([1,2,3,4].includes(n)) return n
+ }
+ return 0
+}
+
+const computeTodaySummary = () => {
+ const todayStr = dayjs().format("YYYY-MM-DD")
+
+ // 閲嶇疆鎵�鏈夌倰鏈烘暟鎹�
+ machines.forEach(machine => {
+ machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
+ })
+
+ tableData.value.forEach(item => {
+ // 浠呯粺璁″綋澶�
+ const isToday = dayjs(item.entryDate).format("YYYY-MM-DD") === todayStr
+ if (!isToday) return
+
+ // 浣跨敤姝g‘鐨勫瓧娈靛悕锛歸orkLoad锛堢倰鏈哄伐浣滈噺锛�, currentWorkLoad锛堢倰鏈烘鍦ㄥ伐浣滈噺锛�
+ const workLoad = Number(item.workLoad) || 0
+ const currentWorkLoad = Number(item.currentWorkLoad) || 0
+ const machineName = item.speculativeTradingName || '鐐掓満1'
+
+ if (machineData[machineName]) {
+ machineData[machineName].workLoad += workLoad
+ machineData[machineName].currentWorkLoad += currentWorkLoad
+ machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad
+ }
+ })
+}
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
@@ -98,6 +339,56 @@
page.current = 1;
getList();
};
+
+// 鏄惁鏈夋煡璇㈡暟鎹�
+const hasQueryData = ref(false)
+// 鎹熻�楃巼
+const rate = ref(6)
+// 鎹熻�楃巼ID
+const rateId = ref(null)
+
+// 鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+const getMachineProductionData = () => {
+ schedulingList().then((res) => {
+ // 澶勭悊鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+ if (res.data && Array.isArray(res.data)) {
+ // 璁剧疆鏄惁鏈夋煡璇㈡暟鎹�
+ hasQueryData.value = res.data.length > 0
+
+ // 淇濆瓨鏌ヨ鏁版嵁鍒癿achineQueryData
+ machineQueryData.value = res.data;
+
+ // 閲嶇疆鎵�鏈夌倰鏈烘暟鎹�
+ machines.forEach(machine => {
+ machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
+ });
+
+ // 閬嶅巻鏁版嵁锛屾牴鎹煡璇㈣繑鍥炵殑鏁版嵁缁撴瀯澶勭悊
+ res.data.forEach(item => {
+ // 鏍规嵁name瀛楁纭畾鐐掓満
+ const machineName = item.name || '鐐掓満1';
+
+ if (machineData[machineName]) {
+ // 濡傛灉鏌ヨ鏁版嵁涓湁workLoad锛屽垯鍒濆鍖栫倰鏈烘�婚噺
+ if (item.workLoad !== null && item.workLoad !== undefined) {
+ machineData[machineName].workLoad = Number(item.workLoad) || 0;
+ }
+
+ // 濡傛灉鏌ヨ鏁版嵁涓湁currentWorkLoad锛屽垯璁剧疆姝e湪宸ヤ綔閲�
+ if (item.currentWorkLoad !== null && item.currentWorkLoad !== undefined) {
+ machineData[machineName].currentWorkLoad = Number(item.currentWorkLoad) || 0;
+ }
+
+ // 璁$畻绌轰綑宸ヤ綔閲�
+ machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad;
+ }
+ });
+ }
+ }).catch(err => {
+ console.error('鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹け璐�:', err);
+ });
+};
+
const changeDaterange = (value) => {
if (value) {
searchForm.value.entryDateStart = value[0];
@@ -126,9 +417,33 @@
pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
}));
page.total = res.data.total;
+ computeTodaySummary()
+
+ // 鍚屾椂鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+ getMachineProductionData();
+ // 鑾峰彇鎹熻�楃巼鏁版嵁
+ getLossRateData();
}).catch(() => {
tableLoading.value = false;
})
+};
+
+// 鑾峰彇鎹熻�楃巼鏁版嵁
+const getLossRateData = () => {
+ getLossRate().then((res) => {
+ const data = res.data || res;
+ if (data && data.rate !== undefined && data.rate !== null) {
+ rate.value = Number(data.rate); // 纭繚杞崲涓烘暟瀛�
+ rateId.value = data.id || null;
+ } else {
+ rate.value = 6;
+ rateId.value = null;
+ }
+ }).catch(err => {
+ console.error('鑾峰彇鎹熻�楃巼鏁版嵁澶辫触:', err);
+ rate.value = 6;
+ rateId.value = null;
+ });
};
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
@@ -150,15 +465,166 @@
})
};
+// 鎵撳紑鑷姩娲惧伐寮规
+const openAutoDispatch = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$message.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+
+ // 杩囨护鎺夊緟鎺掍骇鏁伴噺涓�0鐨勬暟鎹�
+ const validRows = selectedRows.value.filter(row => row.pendingQuantity > 0);
+
+ if (validRows.length === 0) {
+ proxy.$message.warning("閫変腑鐨勬暟鎹棤闇�娲惧伐");
+ return;
+ }
+
+ nextTick(() => {
+ autoDispatchDia.value?.openDialog('auto', validRows)
+ })
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/salesLedger/scheduling/exportOne", {}, "鐢熶骇娲惧伐.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
- searchForm.value.entryDate = [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ]
- searchForm.value.entryDateStart = dayjs().format("YYYY-MM-DD")
- searchForm.value.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
getList();
+ getLossRateData();
});
</script>
-<style scoped></style>
+<style scoped>
+.summary-bar{
+ display: flex;
+ gap: 16px;
+ margin: 10px 0 16px 0;
+}
+.summary-item{
+ background: #f5f7fa;
+ border: 1px solid #ebeef5;
+ border-radius: 6px;
+ padding: 10px 16px;
+ min-width: 160px;
+}
+.summary-label{
+ color: #909399;
+ font-size: 12px;
+ margin-bottom: 6px;
+}
+.summary-value{
+ color: #303133;
+ font-size: 20px;
+ font-weight: 600;
+}
+.summary-control{
+ display: flex;
+ align-items: center;
+ height: 28px;
+}
+.machines-grid{
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ margin-bottom: 20px;
+ padding: 16px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+}
+.machine-card{
+ border: 1px solid #dee2e6;
+ border-radius: 8px;
+ padding: 16px;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+ transition: all 0.3s ease;
+}
+.machine-card:hover{
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+}
+.machine-title{
+ font-weight: 600;
+ font-size: 16px;
+ margin-bottom: 12px;
+ color: #2c3e50;
+ text-align: center;
+ padding-bottom: 8px;
+ border-bottom: 2px solid #3498db;
+}
+.machine-metrics{
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ color: #495057;
+}
+.machine-control{
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f1f3f4;
+}
+.machine-control span{
+ font-size: 14px;
+ white-space: nowrap;
+ color: #6c757d;
+ font-weight: 500;
+}
+.machine-metrics > div:not(.machine-control) {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 4px 0;
+ font-size: 14px;
+}
+.machine-metrics > div:not(.machine-control) span:first-child {
+ color: #6c757d;
+}
+.machine-metrics > div:not(.machine-control) span:last-child {
+ font-weight: 600;
+ color: #2c3e50;
+}
+.save-button-container{
+ grid-column: 1 / -1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 16px;
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid #e9ecef;
+}
+.loss-rate-container{
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.loss-rate-label{
+ font-size: 14px;
+ color: #6c757d;
+ font-weight: 500;
+ white-space: nowrap;
+}
+</style>
+
+
+
+
+
+
+
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 5dda65a..51b42ac 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -1,372 +1,391 @@
<template>
- <div class="app-container">
- <div class="search_form">
- <div>
- <span class="search_title">浜у搧澶х被锛�</span>
- <el-input
- v-model="searchForm.productCategory"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- @change="handleQuery"
- clearable
- prefix-icon="Search"
- />
- <span class="search_title ml10">褰曞叆鏃ユ湡锛�</span>
- <el-date-picker v-model="searchForm.registerDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
- >鎼滅储</el-button
- >
- </div>
- <div>
- <el-button type="primary" @click="openDialog('create')">鏂板璁㈠崟</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- </div>
- </div>
- <div class="table_list">
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :tableLoading="tableLoading"
- @pagination="pagination"
- >
- <template #action="{ row }">
- <el-button type="primary" link @click="handleEdit(row)">缂栬緫</el-button>
- <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
- </template>
- </PIMTable>
- </div>
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="40%" @close="closeDialog">
- <el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
- <el-form-item label="褰曞叆鏃ユ湡" prop="registerDate">
- <el-date-picker v-model="form.registerDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="璇烽�夋嫨褰曞叆鏃ユ湡" style="width: 100%"/>
- </el-form-item>
- <el-form-item label="浜у搧澶х被" prop="productCategory">
- <el-input
- v-model="form.productCategory"
- placeholder="璇疯緭鍏ヤ骇鍝佸ぇ绫�"
- clearable
- />
- </el-form-item>
- <el-form-item label="瑙勬牸鍨嬪彿" prop="specificationModel">
- <el-input
- v-model="form.specificationModel"
- placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
- clearable
- />
- </el-form-item>
- <el-form-item label="鍗曚綅" prop="unit">
- <el-input
- v-model="form.unit"
- placeholder="璇疯緭鍏ュ崟浣�"
- clearable
- />
- </el-form-item>
- <el-form-item label="鏁伴噺" prop="quantity">
- <el-input-number v-model="form.quantity" :min="0" :step="0.1" style="width: 100%"/>
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDialog">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ :inline="true">
+ <el-form-item label="瀹㈡埛鍚嶇О:">
+ <el-input v-model="searchForm.customerName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 160px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="鍚堝悓鍙�:">
+ <el-input v-model="searchForm.salesContractNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 160px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="浜у搧鍚嶇О:">
+ <el-input v-model="searchForm.productCategory"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 160px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="瑙勬牸:">
+ <el-input v-model="searchForm.specificationModel"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 160px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination">
+ <template #completionStatus="{ row }">
+ <el-progress
+ :percentage="toProgressPercentage(row?.completionStatus)"
+ :color="progressColor(toProgressPercentage(row?.completionStatus))"
+ :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
+ />
+ </template>
+ </PIMTable>
+ </div>
+ <el-dialog v-model="bindRouteDialogVisible"
+ title="缁戝畾宸ヨ壓璺嚎"
+ width="500px">
+ <el-form label-width="90px">
+ <el-form-item label="宸ヨ壓璺嚎">
+ <el-select v-model="bindForm.routeId"
+ placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
+ style="width: 100%;"
+ :loading="bindRouteLoading">
+ <el-option v-for="item in routeOptions"
+ :key="item.id"
+ :label="`${item.processRouteCode || ''}`"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="bindRouteDialogVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary"
+ :loading="bindRouteSaving"
+ @click="handleBindRouteConfirm">纭� 璁�</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
</template>
<script setup>
-import {onMounted, ref, reactive, toRefs, getCurrentInstance} from "vue";
-import { ElMessageBox } from "element-plus";
-import dayjs from "dayjs";
-import {schedulingListPage, addProductionOrder, updateProductionOrder, deleteProductionOrder} from "@/api/productionManagement/productionOrder.js";
-import {productTreeList} from "@/api/basicData/product.js";
-const { proxy } = getCurrentInstance();
+ import { onMounted, ref } from "vue";
+ import { ElMessageBox } from "element-plus";
+ import dayjs from "dayjs";
+ import { useRouter } from "vue-router";
+ import {
+ productOrderListPage,
+ listProcessRoute,
+ bindingRoute,
+ listProcessBom,
+ } from "@/api/productionManagement/productionOrder.js";
+ import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
+ const { proxy } = getCurrentInstance();
-const tableColumn = ref([
- {
- label: "褰曞叆鏃ユ湡",
- prop: "registerDate",
- width: 120,
- },
- {
- label: "鐢熶骇璁㈠崟鍙�",
- prop: "orderNo",
- },
- {
- label: "浜у搧澶х被",
- prop: "productCategory",
- },
- {
- label: "瑙勬牸鍨嬪彿",
- prop: "specificationModel",
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- },
- {
- label: "鏁伴噺",
- prop: "quantity",
- },
- // {
- // label: "鎺掍骇鏁伴噺",
- // prop: "schedulingNum",
- // width: 100,
- // },
- // {
- // label: "瀹屽伐鏁伴噺",
- // prop: "successNum",
- // width: 100,
- // },
- {
- label: "鎿嶄綔",
- prop: "action",
- width: 120,
- fixed: "right",
- dataType: "slot",
- align: "center",
- slot: "action"
- }
-]);
-const tableData = ref([]);
-const tableLoading = ref(false);
-const page = ref({
- current: 1,
- size: 100,
- total: 0,
-});
-const dialogVisible = ref(false);
-const dialogTitle = ref("");
-const dialogMode = ref(""); // 'create' 鎴� 'edit'
-const formRef = ref();
-const productOptions = ref([]);
-const form = reactive({
- id: null,
- registerDate: dayjs().format("YYYY-MM-DD"),
- productCategory: "",
- specificationModel: "",
- unit: "",
- quantity: null,
-});
-const formRules = {
- registerDate: [{ required: true, message: "璇烽�夋嫨褰曞叆鏃ユ湡", trigger: "change" }],
- productCategory: [{ required: true, message: "璇疯緭鍏ヤ骇鍝佸ぇ绫�", trigger: "blur" }],
- specificationModel: [{ required: true, message: "璇疯緭鍏ヨ鏍煎瀷鍙�", trigger: "blur" }],
- unit: [{ required: true, message: "璇疯緭鍏ュ崟浣�", trigger: "blur" }],
- quantity: [{ required: true, message: "璇疯緭鍏ユ暟閲�", trigger: "blur" }],
-};
+ const router = useRouter();
-const data = reactive({
- searchForm: {
- productCategory: "",
- registerDate: null, // 褰曞叆鏃ユ湡
- entryDateStart: undefined,
- entryDateEnd: undefined,
- },
-});
-const { searchForm } = toRefs(data);
+ const tableColumn = ref([
+ {
+ label: "鐢熶骇璁㈠崟鍙�",
+ prop: "npsNo",
+ width: '120px',
+ },
+ {
+ label: "閿�鍞悎鍚屽彿",
+ prop: "salesContractNo",
+ width: '150px',
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: '200px',
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productCategory",
+ width: '120px',
+ },
+ {
+ label: "瑙勬牸",
+ prop: "specificationModel",
+ width: '120px',
+ },
+ {
+ label: "宸ヨ壓璺嚎缂栧彿",
+ prop: "processRouteCode",
+ width: '200px',
+ },
+ {
+ label: "闇�姹傛暟閲�",
+ prop: "quantity",
+ },
+ {
+ label: "瀹屾垚鏁伴噺",
+ prop: "completeQuantity",
+ },
+ {
+ dataType: "slot",
+ label: "瀹屾垚杩涘害",
+ prop: "completionStatus",
+ slot: "completionStatus",
+ width: 180,
+ },
+ {
+ label: "寮�濮嬫棩鏈�",
+ prop: "startTime",
+ formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
+ width: 120,
+ },
+ {
+ label: "缁撴潫鏃ユ湡",
+ prop: "endTime",
+ formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
+ width: 120,
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 200,
+ operation: [
+ {
+ name: "宸ヨ壓璺嚎",
+ type: "text",
+ clickFun: row => {
+ showRouteItemModal(row);
+ },
+ },
+ {
+ name: "缁戝畾宸ヨ壓璺嚎",
+ type: "text",
+ showHide: row => !row.processRouteCode,
+ clickFun: row => {
+ openBindRouteDialog(row);
+ },
+ },
+ {
+ name: "浜у搧缁撴瀯",
+ type: "text",
+ clickFun: row => {
+ showProductStructure(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
-const openDialog = (mode, row = null) => {
- dialogMode.value = mode;
- if (mode === 'create') {
- dialogTitle.value = "鏂板鐢熶骇璁㈠崟";
- resetForm();
- } else if (mode === 'edit') {
- dialogTitle.value = "缂栬緫鐢熶骇璁㈠崟";
- resetForm();
-
- console.log('缂栬緫鏁版嵁:', row);
-
- // 濉厖缂栬緫鏁版嵁
- form.id = row.id;
- form.registerDate = row.registerDate;
- form.productCategory = row.productCategory;
- form.specificationModel = row.specificationModel;
- form.unit = row.unit;
- form.quantity = row.quantity;
- }
-
- dialogVisible.value = true;
-};
+ const data = reactive({
+ searchForm: {
+ customerName: "",
+ salesContractNo: "",
+ projectName: "",
+ productCategory: "",
+ specificationModel: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
-const closeDialog = () => {
- dialogVisible.value = false;
-};
+ const toProgressPercentage = val => {
+ const n = Number(val);
+ if (!Number.isFinite(n)) return 0;
+ if (n <= 0) return 0;
+ if (n >= 100) return 100;
+ return Math.round(n);
+ };
-const resetForm = () => {
- form.id = null;
- form.registerDate = dayjs().format("YYYY-MM-DD");
- form.productCategory = "";
- form.specificationModel = "";
- form.unit = "";
- form.quantity = null;
-};
+ // 30/50/80/100 鍒嗘棰滆壊锛氱孩/姗�/钃�/缁�
+ const progressColor = percentage => {
+ const p = toProgressPercentage(percentage);
+ if (p < 30) return "#f56c6c";
+ if (p < 50) return "#e6a23c";
+ if (p < 80) return "#409eff";
+ return "#67c23a";
+ };
-const submitForm = () => {
- formRef.value?.validate(async (valid) => {
- if (!valid) return;
- try {
- const payload = {
- registerDate: form.registerDate,
- productCategory: form.productCategory,
- specificationModel: form.specificationModel,
- unit: form.unit,
- quantity: form.quantity,
- };
-
- if (dialogMode.value === 'create') {
- await addProductionOrder(payload);
- proxy.$modal.msgSuccess("鏂板鎴愬姛");
- } else if (dialogMode.value === 'edit') {
- payload.id = form.id;
- await updateProductionOrder(payload);
- proxy.$modal.msgSuccess("缂栬緫鎴愬姛");
- }
-
- closeDialog();
- getList();
- } catch (err) {
- console.error(`${dialogMode.value === 'create' ? '鏂板' : '缂栬緫'}澶辫触`, err);
- proxy.$modal.msgError(`${dialogMode.value === 'create' ? '鏂板' : '缂栬緫'}澶辫触锛岃閲嶈瘯`);
- }
- });
-};
+ // 缁戝畾宸ヨ壓璺嚎寮规
+ const bindRouteDialogVisible = ref(false);
+ const bindRouteLoading = ref(false);
+ const bindRouteSaving = ref(false);
+ const routeOptions = ref([]);
+ const bindForm = reactive({
+ orderId: null,
+ routeId: null,
+ });
-// 缂栬緫鏂规硶
-const handleEdit = (row) => {
- openDialog('edit', row);
-};
+ const openBindRouteDialog = async row => {
+ bindForm.orderId = row.id;
+ bindForm.routeId = null;
+ bindRouteDialogVisible.value = true;
+ routeOptions.value = [];
+ if (!row.productModelId) {
+ proxy.$modal.msgWarning("褰撳墠璁㈠崟缂哄皯浜у搧鍨嬪彿锛屾棤娉曟煡璇㈠伐鑹鸿矾绾�");
+ bindRouteDialogVisible.value = false;
+ return;
+ }
+ bindRouteLoading.value = true;
+ try {
+ const res = await listProcessRoute({ productModelId: row.productModelId });
+ routeOptions.value = res.data || [];
+ } catch (e) {
+ console.error("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触锛�", e);
+ proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触");
+ } finally {
+ bindRouteLoading.value = false;
+ }
+ };
-// 鍒犻櫎鏂规硶
-const handleDelete = (row) => {
- proxy.$modal.confirm(`纭畾瑕佸垹闄ょ敓浜ц鍗�"${row.orderNo}"鍚楋紵`, "鍒犻櫎纭", {
- confirmButtonText: "纭畾",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- }).then(async () => {
- try {
- await deleteProductionOrder([row.id]);
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList(); // 鍒锋柊鍒楄〃
- } catch (err) {
- console.error("鍒犻櫎澶辫触", err);
- proxy.$modal.msgError("鍒犻櫎澶辫触锛岃閲嶈瘯");
- }
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑堝垹闄�");
- });
-};
+ const handleBindRouteConfirm = async () => {
+ if (!bindForm.routeId) {
+ proxy.$modal.msgWarning("璇烽�夋嫨宸ヨ壓璺嚎");
+ return;
+ }
+ bindRouteSaving.value = true;
+ try {
+ await bindingRoute({
+ id: bindForm.orderId,
+ routeId: bindForm.routeId,
+ });
+ proxy.$modal.msgSuccess("缁戝畾鎴愬姛");
+ bindRouteDialogVisible.value = false;
+ getList();
+ } catch (e) {
+ console.error("缁戝畾宸ヨ壓璺嚎澶辫触锛�", e);
+ proxy.$modal.msgError("缁戝畾宸ヨ壓璺嚎澶辫触");
+ } finally {
+ bindRouteSaving.value = false;
+ }
+ };
-const getProductOptions = () => {
- return productTreeList().then((res) => {
- productOptions.value = convertIdToValue(res || []);
- });
-};
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const changeDaterange = value => {
+ if (value) {
+ searchForm.value.entryDateStart = value[0];
+ searchForm.value.entryDateEnd = value[1];
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ // 鏋勯�犱竴涓柊鐨勫璞★紝涓嶅寘鍚玡ntryDate瀛楁
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined;
+ productOrderListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+ };
-const 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 showRouteItemModal = async row => {
+ const orderId = row.id;
+ try {
+ const res = await getOrderProcessRouteMain(orderId);
+ const data = res.data || {};
+ if (!data || !data.id) {
+ proxy.$modal.msgWarning("鏈壘鍒板叧鑱旂殑宸ヨ壓璺嚎");
+ return;
+ }
+ router.push({
+ path: "/productionManagement/processRouteItem",
+ query: {
+ id: data.id,
+ processRouteCode: data.processRouteCode || "",
+ productName: data.productName || "",
+ model: data.model || "",
+ bomNo: data.bomNo || "",
+ description: data.description || "",
+ orderId,
+ type: "order",
+ },
+ });
+ } catch (e) {
+ console.error("鑾峰彇宸ヨ壓璺嚎涓讳俊鎭け璐ワ細", e);
+ proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎淇℃伅澶辫触");
+ }
+ };
-const findNodeById = (nodes, value) => {
- for (let i = 0; i < nodes.length; i++) {
- if (nodes[i].value === value) {
- return nodes[i].label;
- }
- if (nodes[i].children && nodes[i].children.length > 0) {
- const label = findNodeById(nodes[i].children, value);
- if (label) return label;
- }
- }
- return null;
-};
+ const showProductStructure = row => {
+ router.push({
+ path: "/productionManagement/productStructureDetail",
+ query: {
+ id: row.id,
+ bomNo: row.bomNo || "",
+ productName: row.productCategory || "",
+ productModelName: row.specificationModel || "",
+ orderId: row.id,
+ type: "order",
+ },
+ });
+ };
+ // 瀵煎嚭
+ const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/productOrder/export", {...searchForm.value}, "鐢熶骇璁㈠崟.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.value.current = 1;
- getList();
-};
-const pagination = (obj) => {
- page.value.current = obj.page;
- page.value.size = obj.limit;
- getList();
-};
-const changeDaterange = (value) => {
- if (value) {
- searchForm.value.entryDateStart = value[0];
- searchForm.value.entryDateEnd = value[1];
- } else {
- searchForm.value.entryDateStart = undefined;
- searchForm.value.entryDateEnd = undefined;
- }
- handleQuery();
-};
-const getList = () => {
- tableLoading.value = true;
- // 鏋勯�犱竴涓柊鐨勫璞★紝涓嶅寘鍚玡ntryDate瀛楁鍜� total 瀛楁
- const { total, ...pageParams } = page.value;
- const params = { ...searchForm.value, ...pageParams };
- params.registerDate = undefined;
- if (params.productCategory) {
- // 濡傛灉鏄璞$被鍨嬶紝鑾峰彇鍏秎abel锛堝悕绉帮級鑰屼笉鏄痸alue锛圛D锛�
- if (typeof params.productCategory === "object") {
- params.productCategory = findNodeById(productOptions.value, params.productCategory) || params.productCategory;
- }
- // 濡傛灉鏄疘D锛岃浆鎹负鍚嶇О
- else if (typeof params.productCategory === "string" || typeof params.productCategory === "number") {
- const categoryName = findNodeById(productOptions.value, params.productCategory);
- if (categoryName) {
- params.productCategory = categoryName;
- }
- }
- }
- schedulingListPage(params).then((res) => {
- tableLoading.value = false;
- tableData.value = res.data.records;
- page.value.total = res.data.total;
- }).catch(() => {
- tableLoading.value = false;
- })
-};
+ const handleConfirmRoute = () => {};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/productionOrder/export", {}, "鐢熶骇璁㈠崟.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-onMounted(() => {
- getProductOptions();
- // 涓嶈缃粯璁ゆ棩鏈燂紝鍏ㄩ儴鏉′欢涓虹┖鍔犺浇
- searchForm.value.registerDate = null;
- searchForm.value.entryDateStart = undefined;
- searchForm.value.entryDateEnd = undefined;
- getList();
-});
+ onMounted(() => {
+ getList();
+ });
</script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.search_form{
+ align-items: start;
+}</style>
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
new file mode 100644
index 0000000..f979d51
--- /dev/null
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -0,0 +1,132 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="缂栬緫宸ュ簭"
+ width="400"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="宸ュ簭鍚嶇О锛�"
+ prop="name"
+ :rules="[
+ {
+ required: true,
+ message: '璇疯緭鍏ュ伐搴忓悕绉�',
+ },
+ {
+ max: 100,
+ message: '鏈�澶�100涓瓧绗�',
+ }
+ ]">
+ <el-input v-model="formState.name" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿" prop="no">
+ <el-input v-model="formState.no" />
+ </el-form-item>
+ <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
+ <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+ <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, watch } from "vue";
+import {update} from "@/api/productionManagement/productionProcess.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ record: {
+ type: Object,
+ required: true,
+ }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ id: props.record.id,
+ name: props.record.name,
+ no: props.record.no,
+ remark: props.record.remark,
+ salaryQuota: props.record.salaryQuota,
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+// 鐩戝惉 record 鍙樺寲锛屾洿鏂拌〃鍗曟暟鎹�
+watch(() => props.record, (newRecord) => {
+ if (newRecord && isShow.value) {
+ formState.value = {
+ id: newRecord.id,
+ name: newRecord.name || '',
+ no: newRecord.no || '',
+ remark: newRecord.remark || '',
+ salaryQuota: newRecord.salaryQuota || '',
+ };
+ }
+}, { immediate: true, deep: true });
+
+// 鐩戝惉寮圭獥鎵撳紑锛岄噸鏂板垵濮嬪寲琛ㄥ崟鏁版嵁
+watch(() => props.visible, (visible) => {
+ if (visible && props.record) {
+ formState.value = {
+ id: props.record.id,
+ name: props.record.name || '',
+ no: props.record.no || '',
+ remark: props.record.remark || '',
+ salaryQuota: props.record.salaryQuota || '',
+ };
+ }
+});
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ update(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
new file mode 100644
index 0000000..7558ba7
--- /dev/null
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -0,0 +1,99 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="鏂板宸ュ簭"
+ width="400"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="宸ュ簭鍚嶇О锛�"
+ prop="name"
+ :rules="[
+ {
+ required: true,
+ message: '璇疯緭鍏ュ伐搴忓悕绉�',
+ },
+ {
+ max: 100,
+ message: '鏈�澶�100涓瓧绗�',
+ }
+ ]">
+ <el-input v-model="formState.name" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿" prop="no">
+ <el-input v-model="formState.no" />
+ </el-form-item>
+ <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
+ <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+ <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 {add} from "@/api/productionManagement/productionProcess.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ name: '',
+ remark: '',
+ salaryQuota: '',
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ add(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
new file mode 100644
index 0000000..7ab8c9a
--- /dev/null
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -0,0 +1,302 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ :inline="true">
+ <el-form-item label="宸ュ簭鍚嶇О:">
+ <el-input v-model="searchForm.name"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿:">
+ <el-input v-model="searchForm.no"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right"
+ class="mb10">
+ <el-button type="primary"
+ @click="showNewModal">鏂板宸ュ簭</el-button>
+ <el-button type="info" plain @click="handleImport">瀵煎叆</el-button>
+ <el-button type="danger"
+ @click="handleDelete"
+ :disabled="selectedRows.length === 0"
+ plain>鍒犻櫎宸ュ簭</el-button>
+ </div>
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"></PIMTable>
+ </div>
+ <new-process v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ @completed="getList" />
+ <edit-process v-if="isShowEditModal"
+ v-model:visible="isShowEditModal"
+ :record="record"
+ @completed="getList" />
+ <ImportDialog
+ ref="importDialogRef"
+ v-model="importDialogVisible"
+ title="瀵煎叆宸ュ簭"
+ :action="importAction"
+ :headers="importHeaders"
+ :auto-upload="false"
+ :on-success="handleImportSuccess"
+ :on-error="handleImportError"
+ @confirm="handleImportConfirm"
+ @download-template="handleDownloadTemplate"
+ @close="handleImportClose"
+ />
+ </div>
+</template>
+
+<script setup>
+ import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
+ import NewProcess from "@/views/productionManagement/productionProcess/New.vue";
+ import EditProcess from "@/views/productionManagement/productionProcess/Edit.vue";
+ import ImportDialog from "@/components/Dialog/ImportDialog.vue";
+ import { listPage, del, importData, downloadTemplate } from "@/api/productionManagement/productionProcess.js";
+ import { getToken } from "@/utils/auth";
+
+ const data = reactive({
+ searchForm: {
+ name: "",
+ no: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
+ const tableColumn = ref([
+ {
+ label: "宸ュ簭缂栧彿",
+ prop: "no",
+ },
+ {
+ label: "宸ュ簭鍚嶇О",
+ prop: "name",
+ },
+
+ {
+ label: "宸ヨ祫瀹氶",
+ prop: "salaryQuota",
+ },
+ {
+ label: "澶囨敞",
+ prop: "remark",
+ },
+ {
+ label: "鏇存柊鏃堕棿",
+ prop: "updateTime",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 280,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: row => {
+ showEditModal(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const selectedRows = ref([]);
+ const tableLoading = ref(false);
+ const isShowNewModal = ref(false);
+ const isShowEditModal = ref(false);
+ const record = ref({});
+ const importDialogVisible = ref(false);
+ const importDialogRef = ref(null);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const { proxy } = getCurrentInstance();
+
+ // 瀵煎叆鐩稿叧閰嶇疆
+ const importAction = import.meta.env.VITE_APP_BASE_API + "/productProcess/importData";
+ const importHeaders = { Authorization: "Bearer " + getToken() };
+
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined;
+ listPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ }));
+ page.total = res.data.total;
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ });
+ };
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ };
+
+ // 鎵撳紑鏂板寮规
+ const showNewModal = () => {
+ isShowNewModal.value = true;
+ };
+
+ const showEditModal = row => {
+ isShowEditModal.value = true;
+ record.value = row;
+ };
+
+ // 鍒犻櫎
+ function handleDelete() {
+ const no = selectedRows.value.map(item => item.no);
+ const ids = selectedRows.value.map(item => item.id);
+ if (no.length > 2) {
+ proxy.$modal
+ .confirm(
+ '鏄惁纭鍒犻櫎宸ュ簭缂栧彿涓�"' +
+ no[0] +
+ "銆�" +
+ no[1] +
+ '"绛�' +
+ no.length +
+ "鏉℃暟鎹」锛�"
+ )
+ .then(function () {
+ return del(ids);
+ })
+ .then(() => {
+ getList();
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+ } else {
+ proxy.$modal
+ .confirm('鏄惁纭鍒犻櫎宸ュ簭缂栧彿涓�"' + no + '"鐨勬暟鎹」锛�')
+ .then(function () {
+ return del(ids);
+ })
+ .then(() => {
+ getList();
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+ }
+ }
+
+ // 瀵煎叆
+ const handleImport = () => {
+ importDialogVisible.value = true;
+ };
+
+ // 纭瀵煎叆
+ const handleImportConfirm = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.submit();
+ }
+ };
+
+ // 瀵煎叆鎴愬姛
+ const handleImportSuccess = (response) => {
+ if (response.code === 200) {
+ proxy.$modal.msgSuccess("瀵煎叆鎴愬姛");
+ importDialogVisible.value = false;
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles();
+ }
+ getList();
+ } else {
+ proxy.$modal.msgError(response.msg || "瀵煎叆澶辫触");
+ }
+ };
+
+ // 瀵煎叆澶辫触
+ const handleImportError = (error) => {
+ proxy.$modal.msgError("瀵煎叆澶辫触锛�" + (error.message || "鏈煡閿欒"));
+ };
+
+ // 鍏抽棴瀵煎叆寮圭獥
+ const handleImportClose = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles();
+ }
+ };
+
+ // 涓嬭浇妯℃澘
+ const handleDownloadTemplate = async () => {
+ try {
+ const res = await downloadTemplate();
+ const blob = new Blob([res], {
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ });
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = "宸ュ簭瀵煎叆妯℃澘.xlsx";
+ link.click();
+ window.URL.revokeObjectURL(url);
+ proxy.$modal.msgSuccess("妯℃澘涓嬭浇鎴愬姛");
+ } catch (error) {
+ proxy.$modal.msgError("妯℃澘涓嬭浇澶辫触");
+ }
+ };
+
+ // 瀵煎嚭
+ // const handleOut = () => {
+ // ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ // confirmButtonText: "纭",
+ // cancelButtonText: "鍙栨秷",
+ // type: "warning",
+ // })
+ // .then(() => {
+ // proxy.download("/salesLedger/scheduling/exportTwo", {}, "宸ュ簭鎺掍骇.xlsx");
+ // })
+ // .catch(() => {
+ // proxy.$modal.msg("宸插彇娑�");
+ // });
+ // };
+
+ onMounted(() => {
+ getList();
+ });
+</script>
+
+<style scoped></style>
diff --git a/src/views/productionManagement/productionReporting/Input.vue b/src/views/productionManagement/productionReporting/Input.vue
new file mode 100644
index 0000000..3ba68f7
--- /dev/null
+++ b/src/views/productionManagement/productionReporting/Input.vue
@@ -0,0 +1,115 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="鎶曞叆"
+ @close="closeModal"
+ >
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="data"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ ></PIMTable>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="closeModal">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed, onMounted} from "vue";
+import { productionProductInputListPage } from "@/api/productionManagement/productionProductInput";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ productionProductMainId: {
+ type: Number,
+ required: true,
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0
+});
+
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ fetchData();
+};
+
+const tableLoading = ref(false);
+
+const tableColumn = [
+ {
+ label: '鎶ュ伐鍗曞彿',
+ prop: 'productNo',
+ },
+ {
+ label: '鎶曞叆浜у搧鍚嶇О',
+ prop: 'productName',
+ },
+ {
+ label: '鎶曞叆浜у搧鍨嬪彿',
+ prop: 'model',
+ },
+ {
+ label: '鎶曞叆鏁伴噺',
+ prop: 'quantity',
+ },
+ {
+ label: '鍗曚綅',
+ prop: 'unit',
+ },
+]
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const data = ref([])
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const fetchData = () => {
+ tableLoading.value = true;
+ const params = { productMainId: props.productionProductMainId, ...page };
+
+ productionProductInputListPage(params).then(res => {
+ tableLoading.value = false;
+ data.value = res.data.records;
+ page.total = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+
+defineExpose({
+ closeModal,
+ isShow,
+});
+
+onMounted(() => {
+ fetchData()
+})
+</script>
diff --git a/src/views/productionManagement/productionReporting/Output.vue b/src/views/productionManagement/productionReporting/Output.vue
new file mode 100644
index 0000000..4eeac43
--- /dev/null
+++ b/src/views/productionManagement/productionReporting/Output.vue
@@ -0,0 +1,106 @@
+<template>
+ <div>
+ <el-dialog v-model="isShow"
+ title="浜у嚭"
+ @close="closeModal">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="data"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"></PIMTable>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="closeModal">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { ref, computed, onMounted } from "vue";
+ import { productionProductOutputListPage } from "@/api/productionManagement/productionProductOutput.js";
+
+ const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ productionProductMainId: {
+ type: Number,
+ required: true,
+ },
+ });
+
+ const emit = defineEmits(["update:visible", "completed"]);
+
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ fetchData();
+ };
+
+ const tableLoading = ref(false);
+
+ const tableColumn = [
+ {
+ label: "鎶ュ伐鍗曞彿",
+ prop: "productNo",
+ },
+ {
+ label: "浜у搧鍨嬪彿",
+ prop: "model",
+ },
+ {
+ label: "浜у嚭鏁伴噺",
+ prop: "quantity",
+ },
+ ];
+
+ const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit("update:visible", val);
+ },
+ });
+
+ const data = ref([]);
+
+ const closeModal = () => {
+ isShow.value = false;
+ };
+
+ const fetchData = () => {
+ tableLoading.value = true;
+ const params = { productMainId: props.productionProductMainId, ...page };
+
+ productionProductOutputListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ data.value = res.data.records;
+ page.total = res.data.total;
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ });
+ };
+
+ defineExpose({
+ closeModal,
+ isShow,
+ });
+
+ onMounted(() => {
+ fetchData();
+ });
+</script>
diff --git a/src/views/productionManagement/productionReporting/components/formDia.vue b/src/views/productionManagement/productionReporting/components/formDia.vue
index 89f6c76..126c5b0 100644
--- a/src/views/productionManagement/productionReporting/components/formDia.vue
+++ b/src/views/productionManagement/productionReporting/components/formDia.vue
@@ -13,8 +13,15 @@
<el-input v-model="form.schedulingNum" placeholder="璇疯緭鍏�" clearable disabled/>
</el-form-item>
</el-col>
- <el-col :span="12">
- <el-form-item label="鏈鐢熶骇鏁伴噺锛�" prop="finishedNum">
+ <el-col :span="12">
+ <el-form-item label="寰呯敓浜ф暟閲忥細" prop="pendingNum">
+ <el-input v-model="form.pendingNum" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鏈鐢熶骇鏁伴噺锛�" prop="finishedNum">
<el-input-number
v-model="form.finishedNum"
placeholder="璇疯緭鍏�"
@@ -25,13 +32,18 @@
style="width: 100%"
@change="changeNum"
/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍗曚环(鍏�)锛�" prop="unitPrice">
+ <el-input v-model="form.unitPrice" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="寰呯敓浜ф暟閲忥細" prop="pendingNum">
- <el-input v-model="form.pendingNum" placeholder="璇疯緭鍏�" clearable disabled/>
+ <el-form-item label="鎬讳环(鍏�)锛�" prop="totalPrice">
+ <el-input v-model="form.totalPrice" placeholder="璇疯緭鍏�" clearable disabled/>
</el-form-item>
</el-col>
</el-row>
@@ -42,6 +54,9 @@
v-model="form.schedulingUserId"
placeholder="閫夋嫨浜哄憳"
style="width: 100%;"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
>
<el-option
v-for="user in userList"
@@ -79,7 +94,6 @@
<script setup>
import {ref} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js";
const { proxy } = getCurrentInstance()
@@ -95,6 +109,8 @@
finishedNum: "",
schedulingUserId: "",
schedulingDate: "",
+ unitPrice: "",
+ totalPrice: "",
},
rules: {
schedulingNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
@@ -118,6 +134,19 @@
proxy.$modal.msgWarning('鏈鐢熶骇鏁伴噺涓嶅彲澶т簬鎺掍骇鏁伴噺')
}
form.value.pendingNum = form.value.schedulingNum - form.value.finishedNum;
+ calculateTotalPrice();
+}
+
+// 璁$畻鎬讳环
+const calculateTotalPrice = () => {
+ const quantity = Number(form.value.finishedNum ?? 0);
+ const unitPrice = Number(form.value.unitPrice ?? 0);
+
+ if (quantity > 0 && unitPrice > 0) {
+ form.value.totalPrice = (quantity * unitPrice).toFixed(2);
+ } else {
+ form.value.totalPrice = '0.00';
+ }
}
// 鎻愪氦浜у搧琛ㄥ崟
const submitForm = () => {
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index 1e45199..08b515d 100644
--- a/src/views/productionManagement/productionReporting/index.vue
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -1,401 +1,415 @@
<template>
- <div class="app-container">
- <div class="search_form">
- <el-form :model="searchForm" :inline="true">
- <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-form-item>
- <el-form-item label="鐘舵��:">
- <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 140px" clearable>
- <el-option label="寰呯敓浜�" :value="1"></el-option>
- <el-option label="宸叉姤宸�" :value="3"></el-option>
- <el-option label="鐢熶骇涓�" :value="2"></el-option>
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
- </el-form-item>
- </el-form>
- </div>
- <div class="table_list">
- <div style="text-align: right" class="mb10">
- <el-button type="primary" @click="openForm('add')">鐢熶骇鎶ュ伐</el-button>
- </div>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- :expandRowKeys="expandedRowKeys"
- @expand-change="expandChange"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- :total="page.total"
- >
- <template #expand="{ row }">
- <el-table
- :data="expandData"
- border
- show-summary
- stripe
- :summary-method="summarizeMainTable"
- v-loading="childrenLoading"
- >
- <el-table-column
- align="center"
- label="搴忓彿"
- type="index"
- width="60"
- />
- <el-table-column label="鏈鐢熶骇鏁伴噺" prop="finishedNum" align="center" width="400">
- <template #default="scope">
- <el-input-number :step="0.01" :min="0" style="width: 100%"
- v-model="scope.row.finishedNum"
- :disabled="!scope.row.editType"
- :precision="2"
- placeholder="璇疯緭鍏�"
- clearable
- @change="changeNum(scope.row)"
- />
- </template>
- </el-table-column>
-<!-- <el-table-column label="寰呯敓浜ф暟閲�" prop="pendingNum" width="240" align="center"></el-table-column>-->
- <el-table-column label="鐢熶骇浜�" prop="schedulingUserId" width="400">
- <template #default="scope">
- <el-select
- v-model="scope.row.schedulingUserId"
- placeholder="閫夋嫨浜哄憳"
- :disabled="!scope.row.editType"
- style="width: 100%;"
- >
- <el-option
- v-for="user in userList"
- :key="user.userId"
- :label="user.nickName"
- :value="user.userId"
- />
- </el-select>
- </template>
- </el-table-column>
- <el-table-column label="鐢熶骇鏃ユ湡" prop="schedulingDate" width="400">
- <template #default="scope">
- <el-date-picker
- v-model="scope.row.schedulingDate"
- type="date"
- :disabled="!scope.row.editType"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- style="width: 100%"
- />
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔" width="60">
- <template #default="scope">
- <el-button
- link
- type="primary"
- size="small"
- @click="changeEditType(scope.row)"
- v-if="!scope.row.editType"
- :disabled="scope.row.parentStatus === 3"
- >缂栬緫</el-button
- >
- <el-button
- link
- type="primary"
- size="small"
- @click="saveReceiptPayment(scope.row)"
- v-if="scope.row.editType"
- >淇濆瓨</el-button
- >
- </template>
- </el-table-column>
- </el-table>
- </template>
- </PIMTable>
- </div>
- <form-dia ref="formDia" @close="handleQuery"></form-dia>
- </div>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ :inline="true">
+ <el-form-item label="鎶ュ伐浜哄憳鍚嶇О:">
+ <el-input v-model="searchForm.nickName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="宸ュ崟鍙�:">
+ <el-input v-model="searchForm.workOrderNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right"
+ class="mb10">
+ <!-- <el-button type="primary"
+ @click="openForm('add')">鐢熶骇鎶ュ伐</el-button> -->
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ :expandRowKeys="expandedRowKeys"
+ @expand-change="expandChange"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total">
+ <template #expand="{ row }">
+ <el-table :data="expandData"
+ border
+ show-summary
+ :summary-method="summarizeMainTable"
+ v-loading="childrenLoading">
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="鏈鐢熶骇鏁伴噺"
+ prop="finishedNum"
+ align="center"
+ width="400">
+ <template #default="scope">
+ <el-input-number :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="scope.row.finishedNum"
+ :disabled="!scope.row.editType"
+ :precision="2"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="changeNum(scope.row)" />
+ </template>
+ </el-table-column>
+ <!-- <el-table-column label="寰呯敓浜ф暟閲�" prop="pendingNum" width="240" align="center"></el-table-column>-->
+ <el-table-column label="鐢熶骇浜�"
+ prop="schedulingUserId"
+ width="400">
+ <template #default="scope">
+ <el-select v-model="scope.row.schedulingUserId"
+ placeholder="閫夋嫨浜哄憳"
+ :disabled="!scope.row.editType"
+ style="width: 100%;">
+ <el-option v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId" />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢熶骇鏃ユ湡"
+ prop="schedulingDate"
+ width="400">
+ <template #default="scope">
+ <el-date-picker v-model="scope.row.schedulingDate"
+ type="date"
+ :disabled="!scope.row.editType"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ style="width: 100%" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ >
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ size="small"
+ @click="changeEditType(scope.row)"
+ v-if="!scope.row.editType"
+ :disabled="scope.row.parentStatus === 3">缂栬緫</el-button>
+ <el-button link
+ type="primary"
+ size="small"
+ @click="saveReceiptPayment(scope.row)"
+ v-if="scope.row.editType">淇濆瓨</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </PIMTable>
+ </div>
+ <form-dia ref="formDia"
+ @close="handleQuery"></form-dia>
+ <input-modal v-if="isShowInput"
+ v-model:visible="isShowInput"
+ :production-product-main-id="isShowingId" />
+ </div>
</template>
<script setup>
-import {onMounted, ref} from "vue";
-import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
-import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
-import {ElMessageBox} from "element-plus";
-import dayjs from "dayjs";
-import {
- productionReportUpdate,
- workListPage,
- workListPageById
-} from "@/api/productionManagement/productionReporting.js";
-import {userListNoPageByTenantId} from "@/api/system/user.js";
+ import { onMounted, ref } from "vue";
+ import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
+ import { ElMessageBox } from "element-plus";
+ import {
+ productionReportUpdate,
+ workListPageById,
+ productionReportDelete,
+ } from "@/api/productionManagement/productionReporting.js";
+ import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
+ import { userListNoPageByTenantId } from "@/api/system/user.js";
+ import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
-const data = reactive({
- searchForm: {
- staffName: "",
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
- },
-});
-const { searchForm } = toRefs(data);
-const expandedRowKeys = ref([]);
-const expandData = ref([]);
-const userList = ref([])
-const tableColumn = ref([
- {
- type: "expand",
- dataType: "slot",
- slot: "expand",
- },
- {
- label: "鐘舵��",
- prop: "status",
- dataType: "tag",
- formatData: (params) => {
- if (params == 3) {
- return "宸叉姤宸�";
- } else if (params == 1) {
- return "寰呯敓浜�";
- } else {
- return '鐢熶骇涓�';
- }
- },
- formatType: (params) => {
- if (params == 3) {
- return "success";
- } else if (params == 1) {
- return "primary";
- } else {
- return 'warning';
- }
- },
- },
- {
- label: "鎺掍骇鏃ユ湡",
- prop: "schedulingDate",
- width: 120,
- },
- {
- label: "鎺掍骇浜�",
- prop: "schedulingUserName",
- },
- {
- label: "浜у搧澶х被",
- prop: "productCategory",
- width: 150,
- },
- {
- label: "瑙勬牸鍨嬪彿",
- prop: "specificationModel",
- width: 150,
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- },
- {
- label: "宸ュ簭",
- prop: "process",
- },
- {
- label: "鎺掍骇鏁伴噺",
- prop: "schedulingNum",
- width: 100,
- },
- {
- label: "鐢熶骇鏁伴噺",
- prop: "finishedNum",
- width: 100,
- },
- {
- label: "寰呯敓浜ф暟閲�",
- prop: "pendingFinishNum",
- width: 100,
- },
-]);
-const tableData = ref([]);
-const selectedRows = ref([]);
-const tableLoading = ref(false);
-const childrenLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
- total: 0,
-});
-const formDia = ref()
-const { proxy } = getCurrentInstance()
+ const data = reactive({
+ searchForm: {
+ nickName: "",
+ workOrderNo: "",
+ workOrderStatus: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
+ const expandedRowKeys = ref([]);
+ const expandData = ref([]);
+ const userList = ref([]);
+ const tableColumn = ref([
+ {
+ label: "鎶ュ伐鍗曞彿",
+ prop: "productNo",
+ width: 120,
+ },
+ {
+ label: "鎶ュ伐浜哄憳",
+ prop: "nickName",
+ width: 120,
+ },
+ {
+ label: "宸ュ崟缂栧彿",
+ prop: "workOrderNo",
+ width: 120,
+ },
+ {
+ label: "閿�鍞悎鍚屽彿",
+ prop: "salesContractNo",
+ width: 120,
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ width: 120,
+ },
+ {
+ label: "浜у搧瑙勬牸鍨嬪彿",
+ prop: "productModelName",
+ width: 120,
+ },
+ {
+ label: "浜у嚭鏁伴噺",
+ prop: "quantity",
+ width: 120,
+ },
+ {
+ label: "鎶ュ簾鏁伴噺",
+ prop: "scrapQty",
+ width: 120,
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ width: 120,
+ },
+
+ {
+ label: "鍒涘缓鏃堕棿",
+ prop: "createTime",
+ width: 120,
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ operation: [
+ {
+ name: "鏌ョ湅鎶曞叆",
+ type: "text",
+ clickFun: row => {
+ showInput(row);
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ clickFun: row => {
+ deleteReport(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const selectedRows = ref([]);
+ const tableLoading = ref(false);
+ const childrenLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const formDia = ref();
+ const { proxy } = getCurrentInstance();
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
-const changeDaterange = (value) => {
- if (value) {
- searchForm.value.entryDateStart = value[0];
- searchForm.value.entryDateEnd = value[1];
- } else {
- searchForm.value.entryDateStart = undefined;
- searchForm.value.entryDateEnd = undefined;
- }
- handleQuery();
-};
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-const getList = () => {
- tableLoading.value = true;
- const params = { ...searchForm.value, ...page };
- params.entryDate = undefined
- expandedRowKeys.value = []
- workListPage(params).then(res => {
- tableLoading.value = false;
- tableData.value = res.data.records.map(item => ({
- ...item,
- pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
- }));
- page.total = res.data.total;
- }).catch(err => {
- tableLoading.value = false;
- })
-};
-// 灞曞紑琛�
-const expandChange = (row, expandedRows) => {
- userListNoPageByTenantId().then((res) => {
- userList.value = res.data;
- });
- if (expandedRows.length > 0) {
- nextTick(() => {
- expandedRowKeys.value = [];
- try {
- childrenLoading.value = true;
- workListPageById({ id: row.id }).then((res) => {
- childrenLoading.value = false;
- const index = tableData.value.findIndex((item) => item.id === row.id);
- if (index > -1) {
- expandData.value = res.data.map(item => ({
- ...item,
- pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
- parentStatus: row.status // 鏂板鐖惰〃鐘舵��
- }));
- }
- expandedRowKeys.value.push(row.id);
- });
- } catch (error) {
- childrenLoading.value = false;
- console.log(error);
- }
- })
- } else {
- expandedRowKeys.value = [];
- }
-};
-const changeNum = (row) => {
- // 鎵惧埌鐖惰〃鏍兼暟鎹�
- const parentRow = tableData.value.find(item => item.id === expandedRowKeys.value[0]);
- // 璁$畻鎵�鏈夊瓙琛ㄦ牸 finishedNum 鐨勬�诲拰
- const totalFinishedNum = expandData.value.reduce((sum, item) => sum + (Number(item.finishedNum) || 0), 0);
- // 鐖惰〃鏍肩殑鎺掍骇鏁伴噺
- const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
-
- if (totalFinishedNum > schedulingNum) {
- // 鍥為��鏈杈撳叆
- row.finishedNum = schedulingNum - (totalFinishedNum - Number(row.finishedNum));
- proxy.$modal.msgWarning('鎵�鏈夋湰娆$敓浜ф暟閲忎箣鍜屼笉鍙ぇ浜庢帓浜ф暟閲�');
- }
- row.pendingNum = row.schedulingNum - row.finishedNum;
-}
-// 缂栬緫淇敼鐘舵��
-const changeEditType = (row) => {
- row.editType = !row.editType;
-};
-// 淇濆瓨璁板綍
-const saveReceiptPayment = (row) => {
- productionReportUpdate(row).then((res) => {
- row.editType = !row.editType;
- getList();
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- });
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-const summarizeMainTable = (param) => {
- return proxy.summarizeTable(param, [
- "finishedNum"
- ]);
-};
-// 鎵撳紑寮规
-const openForm = (type, row) => {
- if (selectedRows.value.length !== 1) {
- proxy.$message.error("璇烽�夋嫨涓�鏉℃暟鎹�");
- return;
- }
- if (selectedRows.value[0].pendingFinishNum == 0) {
- proxy.$message.warning("鏃犻渶鍐嶆姤宸�");
- return;
- }
- nextTick(() => {
- const rowInfo = type === 'add' ? selectedRows.value[0] : row
- formDia.value?.openDialog(type, rowInfo)
- })
-};
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+ const changeDaterange = value => {
+ if (value) {
+ searchForm.value.entryDateStart = value[0];
+ searchForm.value.entryDateEnd = value[1];
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+ };
+ const deleteReport = row => {
+ ElMessageBox.confirm("纭畾鍒犻櫎璇ユ姤宸ュ悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ productionReportDelete({ id: row.id }).then(res => {
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ } else {
+ ElMessageBox.alert(res.msg || "鍒犻櫎澶辫触", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ }
+ });
+ });
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined;
+ expandedRowKeys.value = [];
+ productionProductMainListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ pendingFinishNum:
+ (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
+ }));
+ page.total = res.data.total;
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ });
+ };
+ // 灞曞紑琛�
+ const expandChange = (row, expandedRows) => {
+ userListNoPageByTenantId().then(res => {
+ userList.value = res.data;
+ });
+ if (expandedRows.length > 0) {
+ nextTick(() => {
+ expandedRowKeys.value = [];
+ try {
+ childrenLoading.value = true;
+ workListPageById({ id: row.id }).then(res => {
+ childrenLoading.value = false;
+ const index = tableData.value.findIndex(item => item.id === row.id);
+ if (index > -1) {
+ expandData.value = res.data.map(item => ({
+ ...item,
+ pendingNum:
+ (Number(item.schedulingNum) || 0) -
+ (Number(item.finishedNum) || 0),
+ parentStatus: row.status, // 鏂板鐖惰〃鐘舵��
+ }));
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ childrenLoading.value = false;
+ console.log(error);
+ }
+ });
+ } else {
+ expandedRowKeys.value = [];
+ }
+ };
+ const changeNum = row => {
+ // 鎵惧埌鐖惰〃鏍兼暟鎹�
+ const parentRow = tableData.value.find(
+ item => item.id === expandedRowKeys.value[0]
+ );
+ // 璁$畻鎵�鏈夊瓙琛ㄦ牸 finishedNum 鐨勬�诲拰
+ const totalFinishedNum = expandData.value.reduce(
+ (sum, item) => sum + (Number(item.finishedNum) || 0),
+ 0
+ );
+ // 鐖惰〃鏍肩殑鎺掍骇鏁伴噺
+ const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
-// 鍒犻櫎
-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(() => {
- staffJoinDel(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "浜哄憳鍏ヨ亴.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-onMounted(() => {
- getList();
-});
+ if (totalFinishedNum > schedulingNum) {
+ // 鍥為��鏈杈撳叆
+ row.finishedNum =
+ schedulingNum - (totalFinishedNum - Number(row.finishedNum));
+ proxy.$modal.msgWarning("鎵�鏈夋湰娆$敓浜ф暟閲忎箣鍜屼笉鍙ぇ浜庢帓浜ф暟閲�");
+ }
+ row.pendingNum = row.schedulingNum - row.finishedNum;
+ };
+ // 缂栬緫淇敼鐘舵��
+ const changeEditType = row => {
+ row.editType = !row.editType;
+ };
+ // 淇濆瓨璁板綍
+ const saveReceiptPayment = row => {
+ productionReportUpdate(row).then(res => {
+ row.editType = !row.editType;
+ getList();
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
+ };
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ };
+ const summarizeMainTable = param => {
+ return proxy.summarizeTable(param, ["finishedNum"]);
+ };
+ // 鎵撳紑寮规
+ const openForm = (type, row) => {
+ if (selectedRows.value.length !== 1) {
+ proxy.$message.error("璇烽�夋嫨涓�鏉℃暟鎹�");
+ return;
+ }
+ if (selectedRows.value[0].pendingFinishNum == 0) {
+ proxy.$message.warning("鏃犻渶鍐嶆姤宸�");
+ return;
+ }
+ nextTick(() => {
+ const rowInfo = type === "add" ? selectedRows.value[0] : row;
+ formDia.value?.openDialog(type, rowInfo);
+ });
+ };
+
+ // 鎵撳紑鎶曞叆妯℃�佹
+ const isShowInput = ref(false);
+ const isShowingId = ref(0);
+ const showInput = row => {
+ isShowInput.value = true;
+ isShowingId.value = row.id;
+ };
+
+ // 瀵煎嚭
+ const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/productionProductMain/export", {}, "鐢熶骇鎶ュ伐.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
+ onMounted(() => {
+ getList();
+ });
</script>
<style scoped></style>
diff --git a/src/views/productionManagement/safetyMonitoring/index.vue b/src/views/productionManagement/safetyMonitoring/index.vue
index 56f65e7..30e19d6 100644
--- a/src/views/productionManagement/safetyMonitoring/index.vue
+++ b/src/views/productionManagement/safetyMonitoring/index.vue
@@ -295,7 +295,7 @@
emergencyRecords: [
{
id: 'EM001',
- time: '2024-01-15 14:35:12',
+ time: '2025-01-15 14:35:12',
location: '鍌ㄧ綈T-003',
type: '鐢茬兎瓒呮爣',
status: 'resolved',
@@ -303,7 +303,7 @@
},
{
id: 'EM002',
- time: '2024-01-15 14:35:15',
+ time: '2025-01-15 14:35:15',
location: '鍘嬬缉鏈篊-002',
type: '纭寲姘㈣秴鏍�',
status: 'processing',
diff --git a/src/views/productionManagement/workOrder/index.vue b/src/views/productionManagement/workOrder/index.vue
new file mode 100644
index 0000000..de91893
--- /dev/null
+++ b/src/views/productionManagement/workOrder/index.vue
@@ -0,0 +1,653 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div class="search-row">
+ <div class="search-item">
+ <span class="search_title">宸ュ崟缂栧彿锛�</span>
+ <el-input v-model="searchForm.workOrderNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search" />
+ </div>
+ <div class="search-item">
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </div>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination">
+ <template #completionStatus="{ row }">
+ <el-progress :percentage="toProgressPercentage(row?.completionStatus)" :color="progressColor(toProgressPercentage(row?.completionStatus))" :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
+ </template>
+ </PIMTable>
+ </div>
+ <el-dialog v-model="editDialogVisible"
+ title="缂栬緫鏃堕棿"
+ width="500px">
+ <el-form :model="editrow"
+ label-width="120px">
+ <el-form-item label="璁″垝寮�濮嬫椂闂�">
+ <el-date-picker v-model="editrow.planStartTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="璁″垝缁撴潫鏃堕棿">
+ <el-date-picker v-model="editrow.planEndTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="瀹為檯寮�濮嬫椂闂�">
+ <el-date-picker v-model="editrow.actualStartTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="瀹為檯缁撴潫鏃堕棿">
+ <el-date-picker v-model="editrow.actualEndTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ @click="handleUpdate">纭畾</el-button>
+ <el-button @click="editDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="transferCardVisible"
+ title="娴佽浆鍗�"
+ width="1000px">
+ <div class="transfer-card-title">宸ュ崟娴佽浆鍗�</div>
+ <div class="transfer-card-container">
+ <div class="transfer-card-info">
+ <div class="info-group">
+ <div class="info-item">
+ <span class="info-label">宸ュ崟缂栧彿</span>
+ <span class="info-value">{{ transferCardRowData.workOrderNo }}</span>
+ </div>
+ <!-- <div class="info-item">
+ <span class="info-label">浜у搧缂栧彿</span>
+ <span class="info-value">{{ transferCardRowData.productNo }}</span>
+ </div> -->
+ <div class="info-item">
+ <span class="info-label">浜у搧鍚嶇О</span>
+ <span class="info-value">{{ transferCardRowData.productName }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">浜у搧瑙勬牸</span>
+ <span class="info-value">{{ transferCardRowData.model }}</span>
+ </div>
+ <!-- <div class="info-item">
+ <span class="info-label">宸ュ崟鐘舵��</span>
+ <span class="info-value">{{
+ transferCardRowData.status === 1 ? '寰呯‘璁�' :
+ transferCardRowData.status === 2 ? '寰呯敓浜�' :
+ transferCardRowData.status === 3 ? '鐢熶骇涓�' :
+ transferCardRowData.status === 4 ? '宸茬敓浜�' :
+ transferCardRowData.status
+ }}</span>
+ </div> -->
+
+ <div class="info-item">
+ <span class="info-label">璁″垝寮�濮嬫椂闂�</span>
+ <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">璁″垝缁撴潫鏃堕棿</span>
+ <span class="info-value">{{ transferCardRowData.planEndTime }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">澶囨敞</span>
+ <span class="info-value">{{ transferCardRowData.remark }}</span>
+ </div>
+ </div>
+ <div class="info-group">
+ <div class="info-item">
+ <span class="info-label">闇�姹傛暟閲�</span>
+ <span class="info-value">{{ transferCardRowData.planQuantity }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">瀹屾垚鏁伴噺</span>
+ <span class="info-value">{{ transferCardRowData.completeQuantity }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">鑹搧鏁伴噺</span>
+ <span class="info-value">0</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">涓嶈壇鍝佹暟</span>
+ <span class="info-value">0</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">瀹為檯寮�濮嬫椂闂�</span>
+ <span class="info-value">{{ transferCardRowData.actualStartTime }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">瀹為檯缁撴潫鏃堕棿</span>
+ <span class="info-value">{{ transferCardRowData.actualEndTime }}</span>
+ </div>
+ </div>
+ </div>
+ <div class="transfer-card-qr">
+ <div class="qr-container">
+ <img :src="transferCardQrUrl"
+ alt="娴佽浆鍗′簩缁寸爜"
+ style="width: 200px; height: 200px;" />
+ <!-- <div class="qr-tip"
+ style="margin-top: 10px; text-align: center;">娴佽浆鍗′簩缁寸爜</div> -->
+ </div>
+ </div>
+ </div>
+ <div class="print-button-container"
+ style=" text-align: center;
+ margin-bottom: 40px;">
+ <el-button type="primary"
+ style="margin-top: 20px;"
+ @click="printTransferCard">鎵撳嵃娴佽浆鍗�</el-button>
+ </div>
+ </el-dialog>
+ <el-dialog v-model="reportDialogVisible"
+ title="鎶ュ伐"
+ width="500px">
+ <el-form :model="reportForm"
+ label-width="120px">
+ <el-form-item label="寰呯敓浜ф暟閲�">
+ <el-input v-model="reportForm.planQuantity"
+ readonly
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="鏈鐢熶骇鏁伴噺">
+ <el-input v-model.number="reportForm.quantity"
+ type="number"
+ min="1"
+ style="width: 300px"
+ placeholder="璇疯緭鍏ユ湰娆$敓浜ф暟閲�" />
+ </el-form-item>
+ <el-form-item label="鎶ュ簾鏁伴噺">
+ <el-input v-model.number="reportForm.scrapQty"
+ type="number"
+ min="1"
+ style="width: 300px"
+ placeholder="璇疯緭鍏ユ姤搴熸暟閲�" />
+ </el-form-item>
+ <el-form-item label="鐝粍淇℃伅">
+ <el-select v-model="reportForm.userId"
+ style="width: 300px"
+ placeholder="璇烽�夋嫨鐝粍淇℃伅"
+ clearable
+ filterable
+ @change="handleUserChange">
+ <el-option v-for="user in userOptions"
+ :key="user.userId"
+ :label="user.userName"
+ :value="user.userId" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ @click="handleReport">纭畾</el-button>
+ <el-button @click="reportDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { onMounted, ref } from "vue";
+ import { ElMessageBox } from "element-plus";
+ import dayjs from "dayjs";
+ import {
+ productWorkOrderPage,
+ updateProductWorkOrder,
+ addProductMain,
+ } from "@/api/productionManagement/workOrder.js";
+ import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
+ import QRCode from "qrcode";
+ import { getCurrentInstance, reactive, toRefs } from "vue";
+ const { proxy } = getCurrentInstance();
+
+ const tableColumn = ref([
+ {
+ label: "宸ュ崟缂栧彿",
+ prop: "workOrderNo",
+ width: "140",
+ },
+ {
+ label: "鐢熶骇璁㈠崟鍙�",
+ prop: "productOrderNpsNo",
+ width: "140",
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ width: "140",
+ },
+ {
+ label: "瑙勬牸",
+ prop: "model",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "宸ュ簭鍚嶇О",
+ prop: "processName",
+ },
+ {
+ label: "闇�姹傛暟閲�",
+ prop: "planQuantity",
+ width: "140",
+ },
+ {
+ label: "瀹屾垚鏁伴噺",
+ prop: "completeQuantity",
+ width: "140",
+ },
+ {
+ label: "瀹屾垚杩涘害",
+ prop: "completionStatus",
+ dataType: "slot",
+ slot: "completionStatus",
+ width: "140",
+ },
+ {
+ label: "璁″垝寮�濮嬫椂闂�",
+ prop: "planStartTime",
+ width: "140",
+ },
+ {
+ label: "璁″垝缁撴潫鏃堕棿",
+ prop: "planEndTime",
+ width: "140",
+ },
+ {
+ label: "瀹為檯寮�濮嬫椂闂�",
+ prop: "actualStartTime",
+ width: "140",
+ },
+ {
+ label: "瀹為檯缁撴潫鏃堕棿",
+ prop: "actualEndTime",
+ width: "140",
+ },
+ {
+ label: "鎿嶄綔",
+ width: "200",
+ align: "center",
+ dataType: "action",
+ fixed: "right",
+ operation: [
+ {
+ name: "缂栬緫",
+ clickFun: row => {
+ handleEdit(row);
+ },
+ },
+ {
+ name: "娴佽浆鍗�",
+ clickFun: row => {
+ showTransferCard(row);
+ },
+ },
+ {
+ name: "鎶ュ伐",
+ clickFun: row => {
+ showReportDialog(row);
+ },
+ disabled: row => row.planQuantity <= 0,
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const qrCodeUrl = ref("");
+ const qrRowData = ref(null);
+ const editDialogVisible = ref(false);
+ const transferCardVisible = ref(false);
+ const transferCardData = ref([]);
+ const transferCardQrUrl = ref("");
+ const transferCardRowData = ref(null);
+ const reportDialogVisible = ref(false);
+ const userOptions = ref([]);
+ const reportForm = reactive({
+ planQuantity: 0,
+ quantity: 0,
+ userName: "",
+ workOrderId: "",
+ reportWork: "",
+ productProcessRouteItemId: "",
+ userId: "",
+ productMainId: null,
+ });
+ const currentReportRowData = ref(null);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+
+ const data = reactive({
+ searchForm: {
+ workOrderNo: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
+ const toProgressPercentage = val => {
+ const n = Number(val);
+ if (!Number.isFinite(n)) return 0;
+ if (n <= 0) return 0;
+ if (n >= 100) return 100;
+ return Math.round(n);
+ };
+ const progressColor = percentage => {
+ const p = toProgressPercentage(percentage);
+ if (p < 30) return "#f56c6c";
+ if (p < 50) return "#e6a23c";
+ if (p < 80) return "#409eff";
+ return "#67c23a";
+ };
+ let editrow = ref(null);
+
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ productWorkOrderPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+ };
+
+ const showTransferCard = async row => {
+ transferCardRowData.value = row;
+ const qrContent = String(row.id);
+
+ transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
+ transferCardVisible.value = true;
+ };
+
+ const printTransferCard = () => {
+ window.print();
+ };
+
+ const handleEdit = row => {
+ editrow.value = JSON.parse(JSON.stringify(row));
+ editDialogVisible.value = true;
+ };
+
+ const handleUpdate = () => {
+ updateProductWorkOrder(editrow.value)
+ .then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ editDialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ ElMessageBox.alert("淇敼澶辫触", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ });
+ };
+
+ const showReportDialog = row => {
+ currentReportRowData.value = row;
+ reportForm.planQuantity = row.planQuantity;
+ reportForm.quantity = row.quantity;
+ reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
+ reportForm.workOrderId = row.id;
+ reportForm.reportWork = row.reportWork;
+ reportForm.productMainId = row.productMainId;
+ reportForm.scrapQty = row.scrapQty;
+ // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅锛岃缃负榛樿閫変腑
+ getUserProfile()
+ .then(res => {
+ if (res.code === 200) {
+ reportForm.userId = res.data.userId;
+ reportForm.userName = res.data.userName;
+ }
+ })
+ .catch(err => {
+ console.error("鑾峰彇鐢ㄦ埛淇℃伅澶辫触", err);
+ });
+
+ reportDialogVisible.value = true;
+ };
+
+ const handleReport = () => {
+ if (reportForm.planQuantity <= 0) {
+ ElMessageBox.alert("寰呯敓浜ф暟閲忎负0锛屾棤娉曟姤宸�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ return;
+ }
+ if (!reportForm.quantity || reportForm.quantity <= 0) {
+ ElMessageBox.alert("璇疯緭鍏ユ湁鏁堢殑鏈鐢熶骇鏁伴噺", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ return;
+ }
+ if (reportForm.quantity > reportForm.planQuantity) {
+ ElMessageBox.alert("鏈鐢熶骇鏁伴噺涓嶈兘瓒呰繃寰呯敓浜ф暟閲�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ return;
+ }
+ // console.log(reportForm);
+ addProductMain(reportForm).then(res => {
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鎶ュ伐鎴愬姛");
+ reportDialogVisible.value = false;
+ getList();
+ } else {
+ ElMessageBox.alert(res.msg || "鎶ュ伐澶辫触", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ }
+ });
+ };
+
+ // 鑾峰彇鐢ㄦ埛鍒楄〃
+ const getUserList = () => {
+ userListNoPageByTenantId()
+ .then(res => {
+ if (res.code === 200) {
+ userOptions.value = res.data || [];
+ }
+ })
+ .catch(err => {
+ console.error("鑾峰彇鐢ㄦ埛鍒楄〃澶辫触", err);
+ });
+ };
+
+ // 鐢ㄦ埛閫夋嫨鍙樺寲鏃舵洿鏂� userName
+ const handleUserChange = (userId) => {
+ if (userId) {
+ const selectedUser = userOptions.value.find(user => user.userId === userId);
+ if (selectedUser) {
+ reportForm.userName = selectedUser.userName;
+ }
+ } else {
+ reportForm.userName = "";
+ }
+ };
+
+ onMounted(() => {
+ getList();
+ getUserList();
+ });
+</script>
+
+<style scoped lang="scss">
+ .search_form {
+ margin-bottom: 20px;
+ .search-row {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+ .search-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+ }
+ }
+
+ .transfer-card-title {
+ font-size: 24px;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 20px;
+ }
+
+ .transfer-card-container {
+ display: flex;
+ gap: 20px;
+ height: 350px;
+ .transfer-card-info {
+ flex: 1;
+ overflow: auto;
+ .info-group {
+ width: 50%;
+ float: left;
+ }
+ .info-item {
+ display: flex;
+ margin-bottom: 15px;
+ .info-label {
+ width: 120px;
+ font-weight: bold;
+ margin-right: 20px;
+ }
+ .info-value {
+ flex: 1;
+ }
+ }
+ }
+ .transfer-card-qr {
+ width: 240px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ }
+ }
+</style>
+
+<style lang="scss">
+ @media print {
+ @page {
+ size: landscape;
+ }
+ body * {
+ visibility: hidden;
+ }
+ .el-dialog__wrapper,
+ .el-dialog,
+ .el-dialog__body,
+ .transfer-card-title,
+ .transfer-card-container,
+ .transfer-card-container *,
+ .info-item,
+ .info-label,
+ .info-value {
+ visibility: visible;
+ }
+ .print-button-container {
+ visibility: hidden;
+ }
+ .el-dialog__wrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ margin: 0;
+ }
+ .el-dialog {
+ width: 100% !important;
+ max-width: 800px;
+ margin: 0 auto !important;
+ }
+ .el-dialog__header,
+ .el-dialog__footer {
+ display: none;
+ }
+ .el-dialog__body {
+ padding: 20px;
+ }
+ .transfer-card-container {
+ height: auto;
+ display: flex;
+ gap: 20px;
+ }
+ .transfer-card-info {
+ flex: 1;
+ .info-group {
+ width: 100%;
+ float: none;
+ margin-bottom: 20px;
+ }
+ .info-item {
+ display: flex;
+ margin-bottom: 10px;
+ .info-label {
+ width: 100px;
+ font-weight: bold;
+ margin-right: 15px;
+ white-space: nowrap;
+ }
+ .info-value {
+ flex: 1;
+ word-break: break-word;
+ }
+ }
+ }
+ .transfer-card-qr {
+ width: 160px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ }
+ .qr-container img {
+ width: 140px !important;
+ height: 140px !important;
+ }
+ }
+</style>
diff --git a/src/views/qualityManagement/finalInspection/components/filesDia.vue b/src/views/qualityManagement/finalInspection/components/filesDia.vue
index 66392f3..51dd78f 100644
--- a/src/views/qualityManagement/finalInspection/components/filesDia.vue
+++ b/src/views/qualityManagement/finalInspection/components/filesDia.vue
@@ -51,7 +51,6 @@
<script setup>
import {ref} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
qualityInspectParamDel,
diff --git a/src/views/qualityManagement/finalInspection/components/formDia.vue b/src/views/qualityManagement/finalInspection/components/formDia.vue
index 6547e3f..ec0cb42 100644
--- a/src/views/qualityManagement/finalInspection/components/formDia.vue
+++ b/src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -27,6 +27,24 @@
<el-input v-model="form.model" placeholder="璇疯緭鍏�" clearable/>
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="鎸囨爣閫夋嫨锛�" prop="testStandardId">
+ <el-select
+ v-model="form.testStandardId"
+ placeholder="璇烽�夋嫨鎸囨爣"
+ clearable
+ @change="handleTestStandardChange"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in testStandardOptions"
+ :key="item.id"
+ :label="item.standardName || item.standardNo"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
@@ -101,12 +119,12 @@
</template>
<script setup>
-import {ref} from "vue";
+import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {userListNoPage} from "@/api/system/user.js";
-import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js";
+import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -121,17 +139,19 @@
productName: "",
productId: "",
model: "",
+ testStandardId: "",
unit: "",
quantity: "",
checkCompany: "",
checkResult: "",
},
rules: {
- checkTime: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" },],
+ checkTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
process: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
checkName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
productId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
model: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ testStandardId: [{required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change"}],
unit: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
checkCompany: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
@@ -169,6 +189,7 @@
const tableLoading = ref(false);
const userList = ref([]);
const currentProductId = ref(0);
+const testStandardOptions = ref([]); // 鎸囨爣閫夋嫨涓嬫媺妗嗘暟鎹�
// 鎵撳紑寮规
const openDialog = async (type, row) => {
@@ -180,11 +201,54 @@
let userLists = await userListNoPage();
userList.value = userLists.data;
form.value = {}
+ testStandardOptions.value = [];
+ tableData.value = [];
getProductOptions();
if (operationType.value === 'edit') {
- form.value = {...row}
+ // 鍏堜繚瀛� testStandardId锛岄伩鍏嶈娓呯┖
+ const savedTestStandardId = row.testStandardId;
+ // 鍏堣缃〃鍗曟暟鎹紝浣嗘殏鏃舵竻绌� testStandardId锛岀瓑閫夐」鍔犺浇瀹屾垚鍚庡啀璁剧疆
+ form.value = {...row, testStandardId: ''}
currentProductId.value = row.productId || 0
- getQualityInspectParamList(row.id)
+ // 缂栬緫妯″紡涓嬶紝鍏堝姞杞芥寚鏍囬�夐」锛岀劧鍚庡姞杞藉弬鏁板垪琛�
+ if (currentProductId.value) {
+ // 鍏堝姞杞芥寚鏍囬�夐」
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 2
+ }
+ qualityInspectDetailByProductId(params).then(res => {
+ testStandardOptions.value = res.data || [];
+ // 浣跨敤 nextTick 鍜� setTimeout 纭繚閫夐」宸茬粡娓叉煋鍒� DOM
+ nextTick(() => {
+ setTimeout(() => {
+ // 濡傛灉缂栬緫鏁版嵁涓湁 testStandardId锛屽垯璁剧疆骞跺姞杞藉搴旂殑鍙傛暟
+ if (savedTestStandardId) {
+ // 纭繚绫诲瀷鍖归厤锛坕tem.id 鍙兘鏄暟瀛楁垨瀛楃涓诧級
+ const matchedOption = testStandardOptions.value.find(item =>
+ item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
+ );
+ if (matchedOption) {
+ // 纭繚浣跨敤鍖归厤椤圭殑 id锛堜繚鎸佺被鍨嬩竴鑷达級
+ form.value.testStandardId = matchedOption.id;
+ // 缂栬緫鍦烘櫙淇濈暀宸叉湁妫�楠屽�硷紝鐩存帴鎷夊彇鍘熷弬鏁版暟鎹�
+ getQualityInspectParamList(row.id);
+ } else {
+ // 濡傛灉鎵句笉鍒板尮閰嶉」锛屽皾璇曠洿鎺ヤ娇鐢ㄥ師鍊�
+ console.warn('鏈壘鍒板尮閰嶇殑鎸囨爣閫夐」锛宼estStandardId:', savedTestStandardId, '鍙敤閫夐」:', testStandardOptions.value);
+ form.value.testStandardId = savedTestStandardId;
+ getQualityInspectParamList(row.id);
+ }
+ } else {
+ // 鍚﹀垯浣跨敤鏃х殑閫昏緫
+ getQualityInspectParamList(row.id);
+ }
+ }, 100);
+ });
+ });
+ } else {
+ getQualityInspectParamList(row.id);
+ }
}
}
const getProductOptions = () => {
@@ -195,7 +259,7 @@
const getModels = (value) => {
currentProductId.value = value
form.value.productName = findNodeById(productOptions.value, value);
- if (currentProductId) {
+ if (currentProductId.value) {
getList();
}
};
@@ -253,9 +317,40 @@
})
}
const getList = () => {
- qualityInspectDetailByProductId(currentProductId.value).then(res => {
- tableData.value = res.data;
+ if (!currentProductId.value) {
+ testStandardOptions.value = [];
+ tableData.value = [];
+ return;
+ }
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 2
+ }
+ qualityInspectDetailByProductId(params).then(res => {
+ // 淇濆瓨涓嬫媺妗嗛�夐」鏁版嵁
+ testStandardOptions.value = res.data || [];
+ // 娓呯┖琛ㄦ牸鏁版嵁锛岀瓑寰呯敤鎴烽�夋嫨鎸囨爣
+ tableData.value = [];
+ // 娓呯┖鎸囨爣閫夋嫨
+ form.value.testStandardId = '';
})
+}
+
+// 鎸囨爣閫夋嫨鍙樺寲澶勭悊
+const handleTestStandardChange = (testStandardId) => {
+ if (!testStandardId) {
+ tableData.value = [];
+ return;
+ }
+ tableLoading.value = true;
+ getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
+ tableData.value = res.data || [];
+ }).catch(error => {
+ console.error('鑾峰彇鏍囧噯鍙傛暟澶辫触:', error);
+ tableData.value = [];
+ }).finally(() => {
+ tableLoading.value = false;
+ })
}
const getQualityInspectParamList = (id) => {
qualityInspectParamInfo(id).then(res => {
@@ -265,6 +360,9 @@
// 鍏抽棴寮规
const closeDia = () => {
proxy.resetForm("formRef");
+ tableData.value = [];
+ testStandardOptions.value = [];
+ form.value.testStandardId = '';
dialogFormVisible.value = false;
emit('close')
};
diff --git a/src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue b/src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue
index 32a36fa..411856c 100644
--- a/src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue
+++ b/src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue
@@ -34,7 +34,6 @@
<script setup>
import {ref} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
qualityInspectParamDel,
diff --git a/src/views/qualityManagement/finalInspection/index.vue b/src/views/qualityManagement/finalInspection/index.vue
index 3f2595f..17e74b1 100644
--- a/src/views/qualityManagement/finalInspection/index.vue
+++ b/src/views/qualityManagement/finalInspection/index.vue
@@ -79,12 +79,9 @@
const data = reactive({
searchForm: {
productName: "",
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDate: undefined, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
},
rules: {
checkName: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
diff --git a/src/views/qualityManagement/metricBinding/index.vue b/src/views/qualityManagement/metricBinding/index.vue
new file mode 100644
index 0000000..ac67474
--- /dev/null
+++ b/src/views/qualityManagement/metricBinding/index.vue
@@ -0,0 +1,504 @@
+<template>
+ <div class="app-container metric-binding">
+ <!-- 宸︿晶锛氭娴嬫爣鍑嗗垪琛紙鍙锛� -->
+ <div class="left-panel">
+ <PIMTable
+ rowKey="id"
+ :column="standardColumns"
+ :tableData="standardTableData"
+ :page="page"
+ :isSelection="false"
+ :rowClassName="rowClassNameCenter"
+ :tableLoading="tableLoading"
+ :rowClick="handleTableRowClick"
+ @pagination="handlePagination"
+ :total="page.total"
+ >
+ <template #standardNoCell="{ row }">
+ <span class="clickable-link" @click="handleStandardRowClick(row)">
+ {{ row.standardNo }}
+ </span>
+ </template>
+
+ <!-- 琛ㄥご鎼滅储 -->
+ <template #standardNoHeader>
+ <el-input
+ v-model="searchForm.standardNo"
+ placeholder="鏍囧噯缂栧彿"
+ clearable
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #standardNameHeader>
+ <el-input
+ v-model="searchForm.standardName"
+ placeholder="鏍囧噯鍚嶇О"
+ clearable
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #inspectTypeHeader>
+ <el-select
+ v-model="searchForm.inspectType"
+ placeholder="绫诲埆"
+ clearable
+ size="small"
+ style="width: 120px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鍘熸潗鏂欐楠�" value="0" />
+ <el-option label="杩囩▼妫�楠�" value="1" />
+ <el-option label="鍑哄巶妫�楠�" value="2" />
+ </el-select>
+ </template>
+ <template #stateHeader>
+ <el-select
+ v-model="searchForm.state"
+ placeholder="鐘舵��"
+ clearable
+ size="small"
+ style="width: 110px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鑽夌" value="0" />
+ <el-option label="閫氳繃" value="1" />
+ <el-option label="鎾ら攢" value="2" />
+ </el-select>
+ </template>
+ </PIMTable>
+ </div>
+
+ <!-- 鍙充晶锛氱粦瀹氬垪琛� -->
+ <div class="right-panel">
+ <div class="right-header">
+ <div class="title">缁戝畾鍏崇郴</div>
+ <div class="desc" v-if="currentStandard">
+ 褰撳墠妫�娴嬫爣鍑嗙紪鍙凤細<span class="link-text">{{ currentStandard.standardNo }}</span>
+ </div>
+ <div class="desc" v-else>璇烽�夋嫨宸︿晶妫�娴嬫爣鍑�</div>
+ </div>
+
+ <div class="right-toolbar">
+ <el-button type="primary" :disabled="!currentStandard" @click="openBindingDialog">娣诲姞缁戝畾</el-button>
+ <el-button type="danger" plain :disabled="!currentStandard" @click="handleBatchUnbind">鍒犻櫎</el-button>
+ </div>
+
+ <el-table
+ v-loading="bindingLoading"
+ :data="bindingTableData"
+ border
+ :row-class-name="() => 'row-center'"
+ class="center-table"
+ style="width: 100%"
+ height="calc(100vh - 220px)"
+ @selection-change="handleBindingSelectionChange"
+ >
+ <el-table-column type="selection" width="48" align="center" />
+ <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="140" />
+ <el-table-column label="鎿嶄綔" width="120" fixed="right" align="center">
+ <template #default="{ row }">
+ <el-button link type="danger" size="small" @click="handleUnbind(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 娣诲姞缁戝畾寮规 -->
+ <el-dialog
+ v-model="bindingDialogVisible"
+ title="娣诲姞缁戝畾"
+ width="520px"
+ @close="closeBindingDialog"
+ >
+ <el-form label-width="100px">
+ <el-form-item label="浜у搧">
+ <el-tree-select
+ v-model="selectedProductIds"
+ multiple
+ collapse-tags
+ collapse-tags-tooltip
+ placeholder="璇烽�夋嫨浜у搧锛堝彲澶氶�夛級"
+ clearable
+ check-strictly
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="closeBindingDialog">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitBinding">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import { ElMessageBox } from 'element-plus'
+import PIMTable from '@/components/PIMTable/PIMTable.vue'
+import { productTreeList } from '@/api/basicData/product.js'
+import {
+ qualityTestStandardListPage
+} from '@/api/qualityManagement/metricMaintenance.js'
+import { productProcessListPage } from '@/api/basicData/productProcess.js'
+import {
+ qualityTestStandardBindingList,
+ qualityTestStandardBindingAdd,
+ qualityTestStandardBindingDel
+} from '@/api/qualityManagement/qualityTestStandardBinding.js'
+
+const { proxy } = getCurrentInstance()
+
+// 宸︿晶鏍囧噯鍒楄〃锛氭暣琛屽唴瀹瑰眳涓紙閰嶅悎鏍峰紡锛�
+const rowClassNameCenter = () => 'row-center'
+
+const data = reactive({
+ searchForm: {
+ standardNo: '',
+ standardName: '',
+ state: '',
+ inspectType: ''
+ }
+})
+const { searchForm } = toRefs(data)
+
+// 宸︿晶
+const standardTableData = ref([])
+const tableLoading = ref(false)
+const page = reactive({ current: 1, size: 10, total: 0 })
+
+// 宸ュ簭涓嬫媺锛堢敤浜庡垪琛ㄥ洖鏄撅級
+const processOptions = ref([])
+
+const getProcessList = async () => {
+ try {
+ const res = await productProcessListPage({ current: 1, size: 1000 })
+ if (res?.code === 200) {
+ const records = res?.data?.records || []
+ processOptions.value = records.map((item) => ({
+ label: item.processName || item.name || item.label,
+ value: item.id || item.processId || item.value
+ }))
+ }
+ } catch (error) {
+ console.error('鑾峰彇宸ュ簭鍒楄〃澶辫触:', error)
+ }
+}
+
+const standardColumns = ref([
+ { label: '鏍囧噯缂栧彿', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, align: 'center', headerSlot: 'standardNoHeader' },
+ { label: '鏍囧噯鍚嶇О', prop: 'standardName', minWidth: 180, align: 'center', headerSlot: 'standardNameHeader' },
+ {
+ label: '绫诲埆',
+ prop: 'inspectType',
+ headerSlot: 'inspectTypeHeader',
+ align: 'center',
+ dataType: 'tag',
+ formatData: (val) => {
+ const map = { 0: '鍘熸潗鏂欐楠�', 1: '杩囩▼妫�楠�', 2: '鍑哄巶妫�楠�' }
+ return map[val] || val
+ }
+ },
+ {
+ label: '宸ュ簭',
+ prop: 'processId',
+ align: 'center',
+ dataType: 'tag',
+ formatData: (val) => {
+ const target = processOptions.value.find(
+ (item) => String(item.value) === String(val)
+ )
+ return target?.label || val
+ }
+ },
+ {
+ label: '澶囨敞',
+ prop: 'remark',
+ minWidth: 160,
+ align: 'center'
+ }
+ // {
+ // label: '鐘舵��',
+ // prop: 'state',
+ // headerSlot: 'stateHeader',
+ // dataType: 'tag',
+ // formatData: (val) => {
+ // const map = { 0: '鑽夌', 1: '閫氳繃', 2: '鎾ら攢' }
+ // return map[val] || val
+ // },
+ // formatType: (val) => {
+ // if (val == 1) return 'success'
+ // if (val == 2) return 'warning'
+ // return 'info'
+ // }
+ // }
+])
+
+const currentStandard = ref(null)
+
+// 鍙充晶缁戝畾
+const bindingTableData = ref([])
+const bindingLoading = ref(false)
+const bindingSelectedRows = ref([])
+const bindingDialogVisible = ref(false)
+
+// 浜у搧鏍戯紙鐢ㄤ簬缁戝畾閫夋嫨锛�
+const productOptions = ref([])
+const selectedProductIds = ref([])
+
+const getProductOptions = async () => {
+ // 閬垮厤閲嶅璇锋眰
+ if (productOptions.value?.length) return
+ const res = await productTreeList()
+ productOptions.value = convertIdToValue(Array.isArray(res) ? res : [])
+}
+
+function convertIdToValue(data) {
+ return (data || []).map((item) => {
+ const { id, children, ...rest } = item
+ const newItem = {
+ ...rest,
+ value: id
+ }
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children)
+ }
+ return newItem
+ })
+}
+
+const handleQuery = () => {
+ page.current = 1
+ getStandardList()
+}
+
+const handlePagination = (obj) => {
+ page.current = obj.page
+ page.size = obj.limit
+ getStandardList()
+}
+
+const getStandardList = () => {
+ tableLoading.value = true
+ qualityTestStandardListPage({
+ ...searchForm.value,
+ current: page.current,
+ size: page.size,
+ state: 1
+ })
+ .then((res) => {
+ const records = res?.data?.records || []
+ standardTableData.value = records
+ page.total = res?.data?.total || records.length
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+}
+
+// 琛ㄦ牸琛岀偣鍑伙紝鍔犺浇鍙充晶缁戝畾鍒楄〃
+const handleTableRowClick = (row) => {
+ currentStandard.value = row
+ loadBindingList()
+}
+
+// 宸︿晶琛岀偣鍑伙紝鍔犺浇鍙充晶缁戝畾鍒楄〃锛堜繚鐣欑敤浜庢爣鍑嗙紪鍙峰垪鐨勭偣鍑伙級
+const handleStandardRowClick = (row) => {
+ currentStandard.value = row
+ loadBindingList()
+}
+
+const loadBindingList = () => {
+ if (!currentStandard.value?.id) {
+ bindingTableData.value = []
+ return
+ }
+ bindingLoading.value = true
+ qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
+ .then((res) => {
+ const base = res?.data || []
+ // 灏嗗綋鍓嶆爣鍑嗙殑宸ュ簭鍜屽娉ㄥ甫鍒扮粦瀹氬垪琛ㄤ腑灞曠ず
+ bindingTableData.value = base.map((item) => ({
+ ...item,
+ processId: currentStandard.value?.processId,
+ remark: currentStandard.value?.remark
+ }))
+ })
+ .finally(() => {
+ bindingLoading.value = false
+ })
+}
+
+const handleBindingSelectionChange = (selection) => {
+ bindingSelectedRows.value = selection
+}
+
+const openBindingDialog = () => {
+ if (!currentStandard.value?.id) return
+ selectedProductIds.value = []
+ getProductOptions()
+ bindingDialogVisible.value = true
+}
+
+const closeBindingDialog = () => {
+ bindingDialogVisible.value = false
+}
+
+const submitBinding = async () => {
+ const testStandardId = currentStandard.value?.id
+ if (!testStandardId) return
+ const ids = (selectedProductIds.value || []).filter(Boolean)
+ if (!ids.length) {
+ proxy.$message.warning('璇烽�夋嫨浜у搧')
+ return
+ }
+ const payload = ids.map((pid) => ({
+ productId: pid,
+ testStandardId
+ }))
+ await qualityTestStandardBindingAdd(payload)
+ proxy.$message.success('娣诲姞鎴愬姛')
+ bindingDialogVisible.value = false
+ loadBindingList()
+}
+
+const handleUnbind = async (row) => {
+ if (!row?.id) return
+ try {
+ await ElMessageBox.confirm('纭鍒犻櫎璇ョ粦瀹氾紵', '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardBindingDel([row.qualityTestStandardBindingId])
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadBindingList()
+}
+
+const handleBatchUnbind = async () => {
+ if (!bindingSelectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const ids = bindingSelectedRows.value.map((i) => i.qualityTestStandardBindingId)
+ try {
+ await ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardBindingDel(ids)
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadBindingList()
+}
+
+onMounted(() => {
+ getStandardList()
+ getProcessList()
+})
+</script>
+
+<style scoped>
+.metric-binding {
+ display: flex;
+ gap: 16px;
+}
+
+.left-panel,
+.right-panel {
+ flex: 1;
+ background: #ffffff;
+ padding: 16px;
+ box-sizing: border-box;
+}
+
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.toolbar-right {
+ flex-shrink: 0;
+}
+
+.right-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.right-header .title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.right-header .desc {
+ font-size: 13px;
+ color: #666;
+}
+
+.right-toolbar {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.link-text {
+ color: #409eff;
+ cursor: default;
+}
+
+.clickable-link {
+ color: #409eff;
+ cursor: pointer;
+}
+
+.clickable-link:hover {
+ text-decoration: underline;
+}
+
+:deep(.row-center td) {
+ text-align: center !important;
+}
+
+/* el-table 琛ㄥご/鍐呭缁熶竴灞呬腑锛坮ow-class-name 涓嶄綔鐢ㄤ簬琛ㄥご锛� */
+:deep(.center-table .el-table__header-wrapper th .cell) {
+ text-align: center !important;
+}
+:deep(.center-table .el-table__body-wrapper td .cell) {
+ text-align: center !important;
+}
+
+/* PIMTable 琛ㄥご灞呬腑 */
+:deep(.lims-table .pim-table-header-cell) {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+:deep(.lims-table .pim-table-header-title) {
+ text-align: center;
+ width: 100%;
+}
+
+:deep(.lims-table .pim-table-header-extra) {
+ width: 100%;
+ margin-top: 4px;
+}
+</style>
diff --git a/src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue b/src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue
new file mode 100644
index 0000000..4c958a0
--- /dev/null
+++ b/src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue
@@ -0,0 +1,78 @@
+<template>
+ <FormDialog
+ v-model="dialogVisible"
+ :title="computedTitle"
+ :operation-type="operationType"
+ width="520px"
+ @close="emit('close')"
+ @cancel="handleCancel"
+ @confirm="handleConfirm"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="100px"
+ >
+ <el-form-item label="鍙傛暟椤�" prop="parameterItem">
+ <el-input v-model="form.parameterItem" placeholder="璇疯緭鍏ュ弬鏁伴」" />
+ </el-form-item>
+ <el-form-item label="鍗曚綅" prop="unit">
+ <el-input v-model="form.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍊�" prop="standardValue">
+ <el-input v-model="form.standardValue" placeholder="璇疯緭鍏ユ爣鍑嗗��" />
+ </el-form-item>
+ <el-form-item label="鍐呮帶鍊�" prop="controlValue">
+ <el-input v-model="form.controlValue" placeholder="璇疯緭鍏ュ唴鎺у��" />
+ </el-form-item>
+ <el-form-item label="榛樿鍊�" prop="defaultValue">
+ <el-input v-model="form.defaultValue" placeholder="璇疯緭鍏ラ粯璁ゅ��" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+
+const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ operationType: { type: String, default: 'add' }, // add | edit
+ form: { type: Object, required: true }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'cancel', 'confirm'])
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit('update:modelValue', val)
+})
+
+const formRef = ref(null)
+
+const rules = {
+ parameterItem: [{ required: true, message: '璇疯緭鍏ュ弬鏁伴」', trigger: 'blur' }],
+ unit: [{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: 'blur' }]
+}
+
+const computedTitle = computed(() => (props.operationType === 'edit' ? '缂栬緫鏍囧噯鍙傛暟' : '鏂板鏍囧噯鍙傛暟'))
+
+const handleConfirm = () => {
+ formRef.value?.validate?.((valid) => {
+ if (valid) emit('confirm')
+ })
+}
+
+const handleCancel = () => {
+ emit('cancel')
+ dialogVisible.value = false
+}
+
+const resetFields = () => {
+ formRef.value?.resetFields?.()
+}
+
+defineExpose({ resetFields })
+</script>
diff --git a/src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue b/src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
new file mode 100644
index 0000000..38d535a
--- /dev/null
+++ b/src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
@@ -0,0 +1,129 @@
+<template>
+ <FormDialog
+ v-model="dialogVisible"
+ :title="computedTitle"
+ :operation-type="operationType"
+ :width="width"
+ @close="emit('close')"
+ @cancel="handleCancel"
+ @confirm="handleConfirm"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="100px"
+ >
+ <el-form-item label="鏍囧噯缂栧彿" prop="standardNo">
+ <el-input v-model="form.standardNo" placeholder="璇疯緭鍏ユ爣鍑嗙紪鍙�" />
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍚嶇О" prop="standardName">
+ <el-input v-model="form.standardName" placeholder="璇疯緭鍏ユ爣鍑嗗悕绉�" />
+ </el-form-item>
+ <el-form-item label="绫诲埆" prop="inspectType">
+ <el-select v-model="form.inspectType" placeholder="璇烽�夋嫨绫诲埆" style="width: 100%">
+ <el-option label="鍘熸潗鏂欐楠�" value="0" />
+ <el-option label="杩囩▼妫�楠�" value="1" />
+ <el-option label="鍑哄巶妫�楠�" value="2" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="宸ュ簭" prop="processId">
+ <el-select v-model="form.processId" placeholder="璇烽�夋嫨宸ュ簭" style="width: 100%">
+ <el-option
+ v-for="item in processOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="state">
+ <el-select v-model="form.state" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option label="鑽夌" value="0" />
+ <el-option label="閫氳繃" value="1" />
+ <el-option label="鎾ら攢" value="2" />
+ </el-select>
+ </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 { computed, ref } from 'vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+
+const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ operationType: {
+ type: String,
+ default: 'add'
+ },
+ form: {
+ type: Object,
+ required: true
+ },
+ rules: {
+ type: Object,
+ default: () => ({})
+ },
+ processOptions: {
+ type: Array,
+ default: () => []
+ },
+ width: {
+ type: String,
+ default: '500px'
+ }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'cancel', 'confirm'])
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit('update:modelValue', val)
+})
+
+const formRef = ref(null)
+
+const computedTitle = computed(() => {
+ if (props.operationType === 'edit') return '缂栬緫妫�娴嬫爣鍑�'
+ if (props.operationType === 'copy') return '澶嶅埗妫�娴嬫爣鍑�'
+ return '鏂板妫�娴嬫爣鍑�'
+})
+
+const handleConfirm = () => {
+ if (!formRef.value) {
+ emit('confirm')
+ return
+ }
+ formRef.value.validate((valid) => {
+ if (valid) {
+ emit('confirm')
+ }
+ })
+}
+
+const handleCancel = () => {
+ emit('cancel')
+ dialogVisible.value = false
+}
+
+const resetFields = () => {
+ formRef.value?.resetFields?.()
+}
+
+defineExpose({
+ resetFields
+})
+</script>
diff --git a/src/views/qualityManagement/metricMaintenance/index.vue b/src/views/qualityManagement/metricMaintenance/index.vue
index 016a4c1..44d3fae 100644
--- a/src/views/qualityManagement/metricMaintenance/index.vue
+++ b/src/views/qualityManagement/metricMaintenance/index.vue
@@ -1,415 +1,834 @@
<template>
- <div class="app-container product-view">
- <div class="left">
- <div>
- <el-input
- v-model="search"
- style="width: 210px"
- placeholder="杈撳叆鍏抽敭瀛楄繘琛屾悳绱�"
- @change="searchFilter"
- @clear="searchFilter"
+ <div class="app-container metric-maintenance">
+ <!-- 宸︿晶锛氭娴嬫爣鍑嗗垪琛� -->
+ <div class="left-panel">
+ <div class="toolbar">
+ <div class="toolbar-left"></div>
+ <div class="toolbar-right">
+ <el-button type="primary" @click="openStandardDialog('add')">鏂板</el-button>
+ <el-button type="success" plain @click="handleBatchAudit(1)">鎵瑰噯</el-button>
+ <el-button type="warning" plain @click="handleBatchAudit(2)">鎾ら攢</el-button>
+ <el-button type="danger" plain @click="handleBatchDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="standardColumns"
+ :tableData="standardTableData"
+ :page="page"
+ :isSelection="true"
+ :tableLoading="tableLoading"
+ :rowClassName="rowClassNameCenter"
+ :rowClick="handleTableRowClick"
+ @selection-change="handleSelectionChange"
+ @pagination="handlePagination"
+ :total="page.total"
+ >
+ <template #standardNoCell="{ row }">
+ <span class="clickable-link" @click="handleStandardRowClick(row)">
+ {{ row.standardNo }}
+ </span>
+ </template>
+
+ <!-- 琛ㄥご鎼滅储鎻掓Ы -->
+ <template #standardNoHeader>
+ <el-input
+ v-model="searchForm.standardNo"
+ placeholder="鏍囧噯缂栧彿"
clearable
- prefix-icon="Search"
- />
- </div>
- <div ref="containerRef">
- <el-tree
- ref="tree"
- v-loading="treeLoad"
- :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
- node-key="id"
- style="
- height: calc(100vh - 190px);
- overflow-y: scroll;
- scrollbar-width: none;
- "
- >
- <template #default="{ node, data }">
- <div class="custom-tree-node">
- <span class="tree-node-content">
- <el-icon class="orange-icon">
- <component :is="data.children && data.children.length > 0
- ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
- </el-icon>
- {{ data.label }}
- </span>
- </div>
- </template>
- </el-tree>
- </div>
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #standardNameHeader>
+ <el-input
+ v-model="searchForm.standardName"
+ placeholder="鏍囧噯鍚嶇О"
+ clearable
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #inspectTypeHeader>
+ <el-select
+ v-model="searchForm.inspectType"
+ placeholder="绫诲埆"
+ clearable
+ size="small"
+ style="width: 120px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鍘熸潗鏂欐楠�" value="0" />
+ <el-option label="杩囩▼妫�楠�" value="1" />
+ <el-option label="鍑哄巶妫�楠�" value="2" />
+ </el-select>
+ </template>
+ <template #stateHeader>
+ <el-select
+ v-model="searchForm.state"
+ placeholder="鐘舵��"
+ clearable
+ size="small"
+ style="width: 110px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鑽夌" value="0" />
+ <el-option label="閫氳繃" value="1" />
+ <el-option label="鎾ら攢" value="2" />
+ </el-select>
+ </template>
+ </PIMTable>
</div>
- <div class="right">
- <div style="margin-bottom: 10px">
- <el-button type="primary" @click="openModelDia('add')">
- 鏂板妫�娴嬫寚鏍�
+
+ <!-- 鍙充晶锛氭爣鍑嗗弬鏁板垪琛� -->
+ <div class="right-panel">
+ <div class="right-header">
+ <div class="title">鏍囧噯鍙傛暟</div>
+ <div class="desc" v-if="currentStandard">
+ 鎮ㄥ綋鍓嶉�夋嫨鐨勬娴嬫爣鍑嗙紪鍙锋槸锛�
+ <span class="link-text">{{ currentStandard.standardNo }}</span>
+ </div>
+ <div class="desc" v-else>璇峰厛鍦ㄥ乏渚ч�夋嫨涓�涓娴嬫爣鍑�</div>
+ </div>
+
+ <div class="right-toolbar">
+ <el-button type="primary" :disabled="!currentStandard || isStandardReadonly" @click="openParamDialog('add')">
+ 鏂板
</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button
- type="danger"
- @click="handleDelete"
- style="margin-left: 10px"
- plain
- >
+ <el-button type="danger" plain :disabled="!currentStandard || isStandardReadonly" @click="handleParamBatchDelete">
鍒犻櫎
</el-button>
</div>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- :total="page.total"
- ></PIMTable>
- </div>
- <el-dialog
- v-model="modelDia"
- title="妫�娴嬫寚鏍�"
- width="400px"
- @close="closeModelDia"
- >
- <el-form
- :model="modelForm"
- label-width="140px"
- label-position="top"
- :rules="modelRules"
- ref="modelFormRef"
+
+ <el-table
+ v-loading="detailLoading"
+ :data="detailTableData"
+ border
+ :row-class-name="() => 'row-center'"
+ class="center-table"
+ style="width: 100%"
+ height="calc(100vh - 220px)"
+ @selection-change="handleParamSelectionChange"
>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鎸囨爣锛�" prop="parameterItem">
- <el-input
- v-model="modelForm.parameterItem"
- placeholder="璇疯緭鍏ユ寚鏍�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鍗曚綅锛�" prop="unit">
- <el-input
- v-model="modelForm.unit"
- placeholder="璇疯緭鍏ュ崟浣�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鏍囧噯鍊硷細" prop="standardValue">
- <el-input
- v-model="modelForm.standardValue"
- placeholder="璇疯緭鍏ユ爣鍑嗗��"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鍐呮帶鍊硷細" prop="controlValue">
- <el-input
- v-model="modelForm.controlValue"
- placeholder="璇疯緭鍏ュ唴鎺у��"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitModelForm">纭</el-button>
- <el-button @click="closeModelDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
+ <el-table-column type="selection" width="48" align="center" />
+ <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+ <el-table-column prop="parameterItem" label="鍙傛暟椤�" min-width="120" />
+ <el-table-column prop="unit" label="鍗曚綅" width="80" />
+ <el-table-column prop="standardValue" label="鏍囧噯鍊�" min-width="120" />
+ <el-table-column prop="controlValue" label="鍐呮帶鍊�" min-width="120" />
+ <el-table-column prop="defaultValue" label="榛樿鍊�" min-width="120" />
+ <el-table-column label="鎿嶄綔" width="140" fixed="right" align="center">
+ <template #default="{ row }">
+ <el-button link type="primary" size="small" :disabled="isStandardReadonly" @click="openParamDialog('edit', row)">
+ 缂栬緫
+ </el-button>
+ <el-button link type="danger" size="small" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 鏂板 / 缂栬緫妫�娴嬫爣鍑� -->
+ <StandardFormDialog
+ ref="standardFormDialogRef"
+ v-model="standardDialogVisible"
+ :operation-type="standardOperationType"
+ :form="standardForm"
+ :rules="standardRules"
+ :process-options="processOptions"
+ @confirm="submitStandardForm"
+ @close="closeStandardDialog"
+ @cancel="closeStandardDialog"
+ />
+
+ <ParamFormDialog
+ ref="paramFormDialogRef"
+ v-model="paramDialogVisible"
+ :operation-type="paramOperationType"
+ :form="paramForm"
+ @confirm="submitParamForm"
+ @close="closeParamDialog"
+ @cancel="closeParamDialog"
+ />
</div>
</template>
<script setup>
-import {ref} from "vue";
-import {addOrEditProductModel, delProductModel, modelListPage, productTreeList} from "@/api/basicData/product.js";
-import ImportExcel from "@/views/basicData/product/ImportExcel/index.vue";
-import {ElMessageBox} from "element-plus";
+import { Search } from '@element-plus/icons-vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, computed } from 'vue'
+import { ElMessageBox } from 'element-plus'
import {
- qualityTestStandardAdd, qualityTestStandardDel,
qualityTestStandardListPage,
- qualityTestStandardUpdate
-} from "@/api/qualityManagement/metricMaintenance.js";
-const { proxy } = getCurrentInstance();
-// 鏍�
-const search = ref("");
-const treeLoad = ref(false);
-const list = ref([]);
-const expandedKeys = ref([]);
-const currentId = ref("");
-const currentParentId = ref("");
-// 鎸囨爣琛ㄦ牸
-const tableData = ref([]);
-const tableLoading = ref(false);
+ qualityTestStandardAdd,
+ qualityTestStandardUpdate,
+ qualityTestStandardDel,
+ qualityTestStandardCopyParam,
+ qualityTestStandardAudit,
+ qualityTestStandardParamList,
+ qualityTestStandardParamAdd,
+ qualityTestStandardParamUpdate,
+ qualityTestStandardParamDel
+} from '@/api/qualityManagement/metricMaintenance.js'
+import { productProcessListPage } from '@/api/basicData/productProcess.js'
+import StandardFormDialog from './StandardFormDialog.vue'
+import ParamFormDialog from './ParamFormDialog.vue'
+
+const { proxy } = getCurrentInstance()
+
+// 宸︿晶鏍囧噯鍒楄〃锛氭暣琛屽唴瀹瑰眳涓紙閰嶅悎鏍峰紡锛�
+const rowClassNameCenter = () => 'row-center'
+
+// 鏍囧噯鐘舵�佷负鈥滈�氳繃(1)鈥濇椂锛屽彸渚у弬鏁扮姝㈠鍒犳敼
+const isStandardReadonly = computed(() => {
+ const state = currentStandard.value?.state
+ return state === 1 || state === '1'
+})
+
+// 鎼滅储鏉′欢
+const data = reactive({
+ searchForm: {
+ standardNo: '',
+ standardName: '',
+ remark: '',
+ state: '',
+ inspectType: '',
+ processId: ''
+ },
+ standardForm: {
+ id: undefined,
+ standardNo: '',
+ standardName: '',
+ remark: '',
+ state: '0',
+ inspectType: '',
+ processId: ''
+ },
+ standardRules: {
+ standardNo: [{ required: true, message: '璇疯緭鍏ユ爣鍑嗙紪鍙�', trigger: 'blur' }],
+ standardName: [{ required: true, message: '璇疯緭鍏ユ爣鍑嗗悕绉�', trigger: 'blur' }],
+ inspectType: [{ required: true, message: '璇烽�夋嫨妫�娴嬬被鍨�', trigger: 'change' }],
+ processId: [{ required: false, message: '璇烽�夋嫨宸ュ簭', trigger: 'change' }]
+ }
+})
+
+const { searchForm, standardForm, standardRules } = toRefs(data)
+
+// 宸︿晶琛ㄦ牸
+const standardTableData = ref([])
+const selectedRows = ref([])
+const tableLoading = ref(false)
const page = reactive({
current: 1,
size: 10,
-});
-const tableColumn = ref([
+ total: 0
+})
+
+// 宸ュ簭涓嬫媺
+const processOptions = ref([])
+
+// 鑾峰彇宸ュ簭鍒楄〃
+const getProcessList = async () => {
+ try {
+ const res = await productProcessListPage({ current: 1, size: 1000 })
+ if (res?.code === 200) {
+ const records = res?.data?.records || []
+ processOptions.value = records.map(item => ({
+ label: item.processName || item.name || item.label,
+ value: item.id || item.processId || item.value
+ }))
+ }
+ } catch (error) {
+ console.error('鑾峰彇宸ュ簭鍒楄〃澶辫触:', error)
+ }
+}
+
+// 褰撳墠閫変腑鐨勬爣鍑� & 鍙充晶璇︽儏
+const currentStandard = ref(null)
+const detailTableData = ref([])
+const detailLoading = ref(false)
+const paramSelectedRows = ref([])
+const paramDialogVisible = ref(false)
+const paramOperationType = ref('add') // add | edit
+const paramFormDialogRef = ref(null)
+const paramForm = reactive({
+ id: undefined,
+ parameterItem: '',
+ unit: '',
+ standardValue: '',
+ controlValue: '',
+ defaultValue: ''
+})
+
+// 寮圭獥
+const standardDialogVisible = ref(false)
+const standardOperationType = ref('add') // add | edit | copy
+const standardFormDialogRef = ref(null)
+
+// 鍒楀畾涔�
+const standardColumns = ref([
{
- label: "鎸囨爣",
- prop: "parameterItem",
+ label: '鏍囧噯缂栧彿',
+ prop: 'standardNo',
+ dataType: 'slot',
+ slot: 'standardNoCell',
+ minWidth: 160,
+ align: 'center',
+ headerSlot: 'standardNoHeader'
},
{
- label: "鍗曚綅",
- prop: "unit",
+ label: '鏍囧噯鍚嶇О',
+ prop: 'standardName',
+ minWidth: 180,
+ align: 'center',
+ headerSlot: 'standardNameHeader'
},
{
- label: "鏍囧噯鍊�",
- prop: "standardValue",
+ label: '绫诲埆',
+ prop: 'inspectType',
+ headerSlot: 'inspectTypeHeader',
+ align: 'center',
+ dataType: 'tag',
+ formatData: (val) => {
+ const map = {
+ 0: '鍘熸潗鏂欐楠�',
+ 1: '杩囩▼妫�楠�',
+ 2: '鍑哄巶妫�楠�'
+ }
+ return map[val] || val
+ }
},
{
- label: "鍐呮帶鍊�",
- prop: "controlValue",
+ label: '宸ュ簭',
+ prop: 'processId',
+ align: 'center',
+ dataType: 'tag',
+ formatData: (val) => {
+ const target = processOptions.value.find(
+ (item) => String(item.value) === String(val)
+ )
+ return target?.label || val
+ }
},
{
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
+ label: '鐘舵��',
+ prop: 'state',
+ headerSlot: 'stateHeader',
+ align: 'center',
+ dataType: 'tag',
+ formatData: (val) => {
+ const map = {
+ 0: '鑽夌',
+ 1: '閫氳繃',
+ 2: '鎾ら攢'
+ }
+ return map[val] || val
+ },
+ formatType: (val) => {
+ if (val === '1' || val === 1) return 'success'
+ if (val === '2' || val === 2) return 'warning'
+ return 'info'
+ }
+ },
+ {
+ label: '澶囨敞',
+ prop: 'remark',
+ minWidth: 160,
+ align: 'center'
+ },
+ {
+ dataType: 'action',
+ label: '鎿嶄綔',
+ align: 'center',
+ fixed: 'right',
+ width: 220,
operation: [
{
- name: "缂栬緫",
- type: "text",
+ name: '缂栬緫',
+ type: 'text',
clickFun: (row) => {
- openModelDia("edit", row);
- },
+ openStandardDialog('edit', row)
+ }
},
- ],
- },
-]);
-const selectedRows = ref([]);
-// 鎸囨爣寮规
-const modelDia = ref(false);
-const modelOperationType = ref("");
-const data = reactive({
- modelForm: {
- parameterItem: "",
- unit: "",
- standardValue: "",
- controlValue: "",
- },
- modelRules: {
- parameterItem: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- standardValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- controlValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- },
-});
-const { modelForm, modelRules } = toRefs(data);
-
-// 鏌ヨ浜у搧鏍�
-const getProductTreeList = () => {
- treeLoad.value = true;
- productTreeList().then((res) => {
- list.value = res;
- list.value.forEach((a) => {
- expandedKeys.value.push(a.label);
- });
- treeLoad.value = false;
- }).catch((err) => {
- treeLoad.value = false;
- });
-};
-// 杩囨护浜у搧鏍�
-const searchFilter = () => {
- proxy.$refs.tree.filter(search.value);
-};
-// 閫夋嫨浜у搧
-const handleNodeClick = (val, node, el) => {
- // 鍙湁鍙跺瓙鑺傜偣鎵嶆墽琛屼互涓嬮�昏緫
- currentId.value = val.id;
- currentParentId.value = val.parentId;
- getModelList();
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-// 鏌ヨ鎸囨爣鏁版嵁
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getModelList();
-};
-const getModelList = () => {
- tableLoading.value = true;
- qualityTestStandardListPage({
- productId: currentId.value,
- current: page.current,
- size: page.size,
- }).then((res) => {
- tableData.value = res.data.records;
- page.total = res.data.total;
- tableLoading.value = false;
- });
-};
-// 璋冪敤tree杩囨护鏂规硶 涓枃鑻辫繃婊�
-const filterNode = (value, data, node) => {
- if (!value) {
- //濡傛灉鏁版嵁涓虹┖锛屽垯杩斿洖true,鏄剧ず鎵�鏈夌殑鏁版嵁椤�
- return true;
- }
- // 鏌ヨ鍒楄〃鏄惁鏈夊尮閰嶆暟鎹紝灏嗗�煎皬鍐欙紝鍖归厤鑻辨枃鏁版嵁
- let val = value.toLowerCase();
- return chooseNode(val, data, node); // 璋冪敤杩囨护浜屽眰鏂规硶
-};
-// 杩囨护鐖惰妭鐐� / 瀛愯妭鐐� (濡傛灉杈撳叆鐨勫弬鏁版槸鐖惰妭鐐逛笖鑳藉尮閰嶏紝鍒欒繑鍥炶鑺傜偣浠ュ強鍏朵笅鐨勬墍鏈夊瓙鑺傜偣锛涘鏋滃弬鏁版槸瀛愯妭鐐癸紝鍒欒繑鍥炶鑺傜偣鐨勭埗鑺傜偣銆俷ame鏄腑鏂囧瓧绗︼紝enName鏄嫳鏂囧瓧绗�.
-const chooseNode = (value, data, node) => {
- if (data.label.indexOf(value) !== -1) {
- return true;
- }
- const level = node.level;
- // 濡傛灉浼犲叆鐨勮妭鐐规湰韬氨鏄竴绾ц妭鐐瑰氨涓嶇敤鏍¢獙浜�
- if (level === 1) {
- return false;
- }
- // 鍏堝彇褰撳墠鑺傜偣鐨勭埗鑺傜偣
- let parentData = node.parent;
- // 閬嶅巻褰撳墠鑺傜偣鐨勭埗鑺傜偣
- let index = 0;
- while (index < level - 1) {
- // 濡傛灉鍖归厤鍒扮洿鎺ヨ繑鍥烇紝姝ゅname鍊兼槸涓枃瀛楃锛宔nName鏄嫳鏂囧瓧绗︺�傚垽鏂尮閰嶄腑鑻辨枃杩囨护
- if (parentData.data.label.indexOf(value) !== -1) {
- return true;
- }
- // 鍚﹀垯鐨勮瘽鍐嶅線涓婁竴灞傚仛鍖归厤
- parentData = parentData.parent;
- index++;
- }
- // 娌″尮閰嶅埌杩斿洖false
- return false;
-};
-// 鎵撳紑鎸囨爣寮规
-const openModelDia = (type, data) => {
- modelOperationType.value = type;
- modelDia.value = true;
- modelForm.value.model = "";
- modelForm.value.model = "";
- modelForm.value.id = "";
- if (type === "edit") {
- modelForm.value = { ...data };
- }
-};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- }).then(() => {
- proxy.download("/quality/qualityTestStandard/export", {}, "妫�娴嬫寚鏍�.xlsx");
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-// 鍒犻櫎鎸囨爣
-const handleDelete = () => {
- let ids = [];
- if (selectedRows.value.length > 0) {
- ids = selectedRows.value.map((item) => item.id);
- } else {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- }).then(() => {
- tableLoading.value = true;
- qualityTestStandardDel(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getModelList();
- }).finally(() => {
- tableLoading.value = false;
- });
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-// 鎻愪氦瑙勬牸鍨嬪彿淇敼
-const submitModelForm = () => {
- proxy.$refs.modelFormRef.validate((valid) => {
- if (valid) {
- modelForm.value.productId = Number(currentId.value);
- if(modelOperationType.value === 'add') {
- qualityTestStandardAdd(modelForm.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeModelDia();
- getModelList();
- });
- } else {
- qualityTestStandardUpdate(modelForm.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeModelDia();
- getModelList();
- });
+ {
+ name: '澶嶅埗',
+ type: 'text',
+ clickFun: async (row) => {
+ if (!row?.id) return
+ try {
+ await ElMessageBox.confirm('纭澶嶅埗璇ユ爣鍑嗗弬鏁帮紵', '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardCopyParam(row.id)
+ proxy.$message.success('澶嶅埗鎴愬姛')
+ getStandardList()
+ if (currentStandard.value?.id === row.id) {
+ loadDetail(row.id)
+ }
+ }
+ },
+ {
+ name: '鍒犻櫎',
+ type: 'text',
+ clickFun: (row) => {
+ handleDelete(row)
+ }
}
- }
- });
-};
-// 鍏抽棴鍨嬪彿寮规
-const closeModelDia = () => {
- proxy.$refs.modelFormRef.resetFields();
- modelDia.value = false;
-};
-getProductTreeList();
+ ]
+ }
+])
+
+// 鏌ヨ鍒楄〃
+const getStandardList = () => {
+ tableLoading.value = true
+ const params = {
+ ...searchForm.value,
+ current: page.current,
+ size: page.size
+ }
+ qualityTestStandardListPage(params)
+ .then((res) => {
+ const records = res?.data?.records || []
+ standardTableData.value = records
+ page.total = res?.data?.total || records.length
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+}
+
+const handleQuery = () => {
+ page.current = 1
+ getStandardList()
+}
+
+const resetQuery = () => {
+ searchForm.value.standardNo = ''
+ searchForm.value.standardName = ''
+ searchForm.value.remark = ''
+ searchForm.value.state = ''
+ searchForm.value.inspectType = ''
+ searchForm.value.processId = ''
+ handleQuery()
+}
+
+const handlePagination = (obj) => {
+ page.current = obj.page
+ page.size = obj.limit
+ getStandardList()
+}
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection
+}
+
+// 鎵归噺瀹℃牳锛氱姸鎬� 1=鎵瑰噯锛�2=鎾ら攢
+const handleBatchAudit = async (state) => {
+ if (!selectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const text = state === 1 ? '鎵瑰噯' : '鎾ら攢'
+ const payload = selectedRows.value
+ .filter(i => i?.id)
+ .map((item) => ({ id: item.id, state }))
+
+ if (!payload.length) {
+ proxy.$message.warning('璇烽�夋嫨鏈夋晥鏁版嵁')
+ return
+ }
+
+ try {
+ await ElMessageBox.confirm(`纭${text}閫変腑鐨勬爣鍑嗭紵`, '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardAudit(payload)
+ proxy.$message.success(`${text}鎴愬姛`)
+ getStandardList()
+}
+
+// 琛ㄦ牸琛岀偣鍑伙紝鍔犺浇鍙充晶鍙傛暟
+const handleTableRowClick = (row) => {
+ currentStandard.value = row
+ loadDetail(row.id)
+}
+
+// 宸︿晶琛岀偣鍑伙紝鍔犺浇鍙充晶鍙傛暟锛堜繚鐣欑敤浜庢爣鍑嗙紪鍙峰垪鐨勭偣鍑伙級
+const handleStandardRowClick = (row) => {
+ currentStandard.value = row
+ loadDetail(row.id)
+}
+
+const loadDetail = (standardId) => {
+ if (!standardId) {
+ detailTableData.value = []
+ return
+ }
+ detailLoading.value = true
+ qualityTestStandardParamList({ testStandardId: standardId }).then((res) => {
+ detailTableData.value = res?.data || []
+ })
+ .finally(() => {
+ detailLoading.value = false
+ })
+}
+
+const handleParamSelectionChange = (selection) => {
+ paramSelectedRows.value = selection
+}
+
+const openParamDialog = (type, row) => {
+ if (!currentStandard.value?.id) return
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ paramOperationType.value = type
+ if (type === 'add') {
+ Object.assign(paramForm, {
+ id: undefined,
+ parameterItem: '',
+ unit: '',
+ standardValue: '',
+ controlValue: '',
+ defaultValue: ''
+ })
+ } else if (type === 'edit' && row) {
+ Object.assign(paramForm, row)
+ }
+ paramDialogVisible.value = true
+}
+
+const closeParamDialog = () => {
+ paramDialogVisible.value = false
+ paramFormDialogRef.value?.resetFields?.()
+}
+
+const submitParamForm = async () => {
+ const testStandardId = currentStandard.value?.id
+ if (!testStandardId) return
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ const payload = { ...paramForm, testStandardId }
+ if (paramOperationType.value === 'edit') {
+ await qualityTestStandardParamUpdate(payload)
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ } else {
+ await qualityTestStandardParamAdd(payload)
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ }
+ closeParamDialog()
+ loadDetail(testStandardId)
+}
+
+const handleParamDelete = async (row) => {
+ if (!row?.id) return
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ try {
+ await ElMessageBox.confirm('纭鍒犻櫎璇ュ弬鏁帮紵', '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardParamDel([row.id])
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadDetail(currentStandard.value?.id)
+}
+
+const handleParamBatchDelete = async () => {
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ if (!paramSelectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const ids = paramSelectedRows.value.map((i) => i.id)
+ try {
+ await ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardParamDel(ids)
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadDetail(currentStandard.value?.id)
+}
+
+// 鏂板 / 缂栬緫 / 澶嶅埗
+const openStandardDialog = (type, row) => {
+ standardOperationType.value = type
+ if (type === 'add') {
+ Object.assign(standardForm.value, {
+ id: undefined,
+ standardNo: '',
+ standardName: '',
+ remark: '',
+ state: '0',
+ inspectType: '',
+ processId: ''
+ })
+ } else if (type === 'edit' && row) {
+ Object.assign(standardForm.value, {
+ ...row,
+ // 纭繚 inspectType 鍜� state 杞崲涓哄瓧绗︿覆锛屼互鍖归厤 el-select 鐨� value 绫诲瀷
+ inspectType: row.inspectType !== null && row.inspectType !== undefined ? String(row.inspectType) : '',
+ state: row.state !== null && row.state !== undefined ? String(row.state) : '0',
+ // 纭繚 processId 杞崲涓哄瓧绗︿覆鎴栨暟瀛楋紙鏍规嵁瀹為檯闇�瑕侊級
+ processId: row.processId !== null && row.processId !== undefined ? row.processId : ''
+ })
+ } else if (type === 'copy' && row) {
+ const { id, ...rest } = row
+ Object.assign(standardForm.value, {
+ ...rest,
+ id: undefined,
+ standardNo: '',
+ state: '0',
+ // 纭繚 inspectType 杞崲涓哄瓧绗︿覆
+ inspectType: rest.inspectType !== null && rest.inspectType !== undefined ? String(rest.inspectType) : ''
+ })
+ }
+ standardDialogVisible.value = true
+}
+
+const closeStandardDialog = () => {
+ standardDialogVisible.value = false
+ standardFormDialogRef.value?.resetFields?.()
+}
+
+const submitStandardForm = () => {
+ const payload = { ...standardForm.value }
+ const isEdit = standardOperationType.value === 'edit'
+ if (isEdit) {
+ qualityTestStandardUpdate(payload).then(() => {
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ standardDialogVisible.value = false
+ getStandardList()
+ })
+ } else {
+ qualityTestStandardAdd(payload).then(() => {
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ standardDialogVisible.value = false
+ getStandardList()
+ })
+ }
+}
+
+// 鍒犻櫎锛堝崟鏉★級
+const handleDelete = (row) => {
+ const ids = [row.id]
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ tableLoading.value = true
+ qualityTestStandardDel(ids)
+ .then(() => {
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ getStandardList()
+ if (currentStandard.value && currentStandard.value.id === row.id) {
+ currentStandard.value = null
+ detailTableData.value = []
+ }
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+ })
+ .catch(() => {
+ proxy.$modal?.msg('宸插彇娑�')
+ })
+}
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (!selectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const ids = selectedRows.value.map((item) => item.id)
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ tableLoading.value = true
+ qualityTestStandardDel(ids)
+ .then(() => {
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ getStandardList()
+ if (currentStandard.value && ids.includes(currentStandard.value.id)) {
+ currentStandard.value = null
+ detailTableData.value = []
+ }
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+ })
+ .catch(() => {
+ proxy.$modal?.msg('宸插彇娑�')
+ })
+}
+
+onMounted(() => {
+ getProcessList()
+ getStandardList()
+})
</script>
<style scoped>
-.product-view {
+.metric-maintenance {
display: flex;
+ gap: 16px;
+ min-width: 0; /* 鍏佽 flex 瀛愬厓绱犳敹缂� */
}
-.left {
- width: 380px;
- padding: 16px;
- background: #ffffff;
-}
-.right {
- width: calc(100% - 380px);
- padding: 16px;
- margin-left: 20px;
- background: #ffffff;
-}
-.custom-tree-node {
+
+.left-panel,
+.right-panel {
flex: 1;
+ min-width: 0; /* 鍏佽 flex 瀛愬厓绱犳敹缂� */
+ background: #ffffff;
+ padding: 16px;
+ box-sizing: border-box;
+ overflow: hidden; /* 闃叉鍐呭婧㈠嚭 */
+}
+
+/* 浣庡垎杈ㄧ巼閫傞厤 */
+@media (max-width: 1400px) {
+ .metric-maintenance {
+ flex-direction: column;
+ }
+
+ .left-panel,
+ .right-panel {
+ width: 100%;
+ min-width: 0;
+ }
+}
+
+@media (max-width: 768px) {
+ .metric-maintenance {
+ gap: 12px;
+ }
+
+ .left-panel,
+ .right-panel {
+ padding: 12px;
+ }
+}
+
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.toolbar-left {
display: flex;
align-items: center;
- justify-content: space-between;
- font-size: 14px;
- padding-right: 8px;
+ flex-wrap: wrap;
+ gap: 4px;
}
-.tree-node-content {
+
+.toolbar-right {
+ flex-shrink: 0;
display: flex;
- align-items: center; /* 鍨傜洿灞呬腑 */
- height: 100%;
+ flex-wrap: wrap;
+ gap: 8px;
}
-.orange-icon {
- color: orange;
- font-size: 18px;
- margin-right: 8px; /* 鍥炬爣涓庢枃瀛椾箣闂村姞鐐归棿璺� */
+
+.search-label {
+ margin: 0 4px 0 12px;
+}
+
+.search-label:first-of-type {
+ margin-left: 0;
+}
+
+.right-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.right-header .title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.right-header .desc {
+ font-size: 13px;
+ color: #666;
+}
+
+.right-toolbar {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.link-text {
+ color: #409eff;
+ cursor: default;
+}
+
+.clickable-link {
+ color: #409eff;
+ cursor: pointer;
+}
+
+.clickable-link:hover {
+ text-decoration: underline;
+}
+
+:deep(.row-center td) {
+ text-align: center !important;
+}
+
+/* el-table 琛ㄥご/鍐呭缁熶竴灞呬腑锛坮ow-class-name 涓嶄綔鐢ㄤ簬琛ㄥご锛� */
+:deep(.center-table .el-table__header-wrapper th .cell) {
+ text-align: center !important;
+}
+:deep(.center-table .el-table__body-wrapper td .cell) {
+ text-align: center !important;
+}
+
+/* PIMTable 琛ㄥご灞呬腑 */
+:deep(.lims-table .pim-table-header-cell) {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+:deep(.lims-table .pim-table-header-title) {
+ text-align: center;
+ width: 100%;
+}
+
+:deep(.lims-table .pim-table-header-extra) {
+ width: 100%;
+ margin-top: 4px;
}
</style>
\ No newline at end of file
diff --git a/src/views/qualityManagement/metricMaintenance/index0.vue b/src/views/qualityManagement/metricMaintenance/index0.vue
new file mode 100644
index 0000000..016a4c1
--- /dev/null
+++ b/src/views/qualityManagement/metricMaintenance/index0.vue
@@ -0,0 +1,415 @@
+<template>
+ <div class="app-container product-view">
+ <div class="left">
+ <div>
+ <el-input
+ v-model="search"
+ style="width: 210px"
+ placeholder="杈撳叆鍏抽敭瀛楄繘琛屾悳绱�"
+ @change="searchFilter"
+ @clear="searchFilter"
+ clearable
+ prefix-icon="Search"
+ />
+ </div>
+ <div ref="containerRef">
+ <el-tree
+ ref="tree"
+ v-loading="treeLoad"
+ :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
+ node-key="id"
+ style="
+ height: calc(100vh - 190px);
+ overflow-y: scroll;
+ scrollbar-width: none;
+ "
+ >
+ <template #default="{ node, data }">
+ <div class="custom-tree-node">
+ <span class="tree-node-content">
+ <el-icon class="orange-icon">
+ <component :is="data.children && data.children.length > 0
+ ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
+ </el-icon>
+ {{ data.label }}
+ </span>
+ </div>
+ </template>
+ </el-tree>
+ </div>
+ </div>
+ <div class="right">
+ <div style="margin-bottom: 10px">
+ <el-button type="primary" @click="openModelDia('add')">
+ 鏂板妫�娴嬫寚鏍�
+ </el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button
+ type="danger"
+ @click="handleDelete"
+ style="margin-left: 10px"
+ plain
+ >
+ 鍒犻櫎
+ </el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ ></PIMTable>
+ </div>
+ <el-dialog
+ v-model="modelDia"
+ title="妫�娴嬫寚鏍�"
+ width="400px"
+ @close="closeModelDia"
+ >
+ <el-form
+ :model="modelForm"
+ label-width="140px"
+ label-position="top"
+ :rules="modelRules"
+ ref="modelFormRef"
+ >
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鎸囨爣锛�" prop="parameterItem">
+ <el-input
+ v-model="modelForm.parameterItem"
+ placeholder="璇疯緭鍏ユ寚鏍�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input
+ v-model="modelForm.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鏍囧噯鍊硷細" prop="standardValue">
+ <el-input
+ v-model="modelForm.standardValue"
+ placeholder="璇疯緭鍏ユ爣鍑嗗��"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鍐呮帶鍊硷細" prop="controlValue">
+ <el-input
+ v-model="modelForm.controlValue"
+ placeholder="璇疯緭鍏ュ唴鎺у��"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitModelForm">纭</el-button>
+ <el-button @click="closeModelDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {addOrEditProductModel, delProductModel, modelListPage, productTreeList} from "@/api/basicData/product.js";
+import ImportExcel from "@/views/basicData/product/ImportExcel/index.vue";
+import {ElMessageBox} from "element-plus";
+import {
+ qualityTestStandardAdd, qualityTestStandardDel,
+ qualityTestStandardListPage,
+ qualityTestStandardUpdate
+} from "@/api/qualityManagement/metricMaintenance.js";
+const { proxy } = getCurrentInstance();
+// 鏍�
+const search = ref("");
+const treeLoad = ref(false);
+const list = ref([]);
+const expandedKeys = ref([]);
+const currentId = ref("");
+const currentParentId = ref("");
+// 鎸囨爣琛ㄦ牸
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 10,
+});
+const tableColumn = ref([
+ {
+ label: "鎸囨爣",
+ prop: "parameterItem",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "鏍囧噯鍊�",
+ prop: "standardValue",
+ },
+ {
+ label: "鍐呮帶鍊�",
+ prop: "controlValue",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ openModelDia("edit", row);
+ },
+ },
+ ],
+ },
+]);
+const selectedRows = ref([]);
+// 鎸囨爣寮规
+const modelDia = ref(false);
+const modelOperationType = ref("");
+const data = reactive({
+ modelForm: {
+ parameterItem: "",
+ unit: "",
+ standardValue: "",
+ controlValue: "",
+ },
+ modelRules: {
+ parameterItem: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ standardValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ controlValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ },
+});
+const { modelForm, modelRules } = toRefs(data);
+
+// 鏌ヨ浜у搧鏍�
+const getProductTreeList = () => {
+ treeLoad.value = true;
+ productTreeList().then((res) => {
+ list.value = res;
+ list.value.forEach((a) => {
+ expandedKeys.value.push(a.label);
+ });
+ treeLoad.value = false;
+ }).catch((err) => {
+ treeLoad.value = false;
+ });
+};
+// 杩囨护浜у搧鏍�
+const searchFilter = () => {
+ proxy.$refs.tree.filter(search.value);
+};
+// 閫夋嫨浜у搧
+const handleNodeClick = (val, node, el) => {
+ // 鍙湁鍙跺瓙鑺傜偣鎵嶆墽琛屼互涓嬮�昏緫
+ currentId.value = val.id;
+ currentParentId.value = val.parentId;
+ getModelList();
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+// 鏌ヨ鎸囨爣鏁版嵁
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getModelList();
+};
+const getModelList = () => {
+ tableLoading.value = true;
+ qualityTestStandardListPage({
+ productId: currentId.value,
+ current: page.current,
+ size: page.size,
+ }).then((res) => {
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ tableLoading.value = false;
+ });
+};
+// 璋冪敤tree杩囨护鏂规硶 涓枃鑻辫繃婊�
+const filterNode = (value, data, node) => {
+ if (!value) {
+ //濡傛灉鏁版嵁涓虹┖锛屽垯杩斿洖true,鏄剧ず鎵�鏈夌殑鏁版嵁椤�
+ return true;
+ }
+ // 鏌ヨ鍒楄〃鏄惁鏈夊尮閰嶆暟鎹紝灏嗗�煎皬鍐欙紝鍖归厤鑻辨枃鏁版嵁
+ let val = value.toLowerCase();
+ return chooseNode(val, data, node); // 璋冪敤杩囨护浜屽眰鏂规硶
+};
+// 杩囨护鐖惰妭鐐� / 瀛愯妭鐐� (濡傛灉杈撳叆鐨勫弬鏁版槸鐖惰妭鐐逛笖鑳藉尮閰嶏紝鍒欒繑鍥炶鑺傜偣浠ュ強鍏朵笅鐨勬墍鏈夊瓙鑺傜偣锛涘鏋滃弬鏁版槸瀛愯妭鐐癸紝鍒欒繑鍥炶鑺傜偣鐨勭埗鑺傜偣銆俷ame鏄腑鏂囧瓧绗︼紝enName鏄嫳鏂囧瓧绗�.
+const chooseNode = (value, data, node) => {
+ if (data.label.indexOf(value) !== -1) {
+ return true;
+ }
+ const level = node.level;
+ // 濡傛灉浼犲叆鐨勮妭鐐规湰韬氨鏄竴绾ц妭鐐瑰氨涓嶇敤鏍¢獙浜�
+ if (level === 1) {
+ return false;
+ }
+ // 鍏堝彇褰撳墠鑺傜偣鐨勭埗鑺傜偣
+ let parentData = node.parent;
+ // 閬嶅巻褰撳墠鑺傜偣鐨勭埗鑺傜偣
+ let index = 0;
+ while (index < level - 1) {
+ // 濡傛灉鍖归厤鍒扮洿鎺ヨ繑鍥烇紝姝ゅname鍊兼槸涓枃瀛楃锛宔nName鏄嫳鏂囧瓧绗︺�傚垽鏂尮閰嶄腑鑻辨枃杩囨护
+ if (parentData.data.label.indexOf(value) !== -1) {
+ return true;
+ }
+ // 鍚﹀垯鐨勮瘽鍐嶅線涓婁竴灞傚仛鍖归厤
+ parentData = parentData.parent;
+ index++;
+ }
+ // 娌″尮閰嶅埌杩斿洖false
+ return false;
+};
+// 鎵撳紑鎸囨爣寮规
+const openModelDia = (type, data) => {
+ modelOperationType.value = type;
+ modelDia.value = true;
+ modelForm.value.model = "";
+ modelForm.value.model = "";
+ modelForm.value.id = "";
+ if (type === "edit") {
+ modelForm.value = { ...data };
+ }
+};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ proxy.download("/quality/qualityTestStandard/export", {}, "妫�娴嬫寚鏍�.xlsx");
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鍒犻櫎鎸囨爣
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ tableLoading.value = true;
+ qualityTestStandardDel(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getModelList();
+ }).finally(() => {
+ tableLoading.value = false;
+ });
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鎻愪氦瑙勬牸鍨嬪彿淇敼
+const submitModelForm = () => {
+ proxy.$refs.modelFormRef.validate((valid) => {
+ if (valid) {
+ modelForm.value.productId = Number(currentId.value);
+ if(modelOperationType.value === 'add') {
+ qualityTestStandardAdd(modelForm.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeModelDia();
+ getModelList();
+ });
+ } else {
+ qualityTestStandardUpdate(modelForm.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeModelDia();
+ getModelList();
+ });
+ }
+ }
+ });
+};
+// 鍏抽棴鍨嬪彿寮规
+const closeModelDia = () => {
+ proxy.$refs.modelFormRef.resetFields();
+ modelDia.value = false;
+};
+getProductTreeList();
+</script>
+
+<style scoped>
+.product-view {
+ display: flex;
+}
+.left {
+ width: 380px;
+ padding: 16px;
+ background: #ffffff;
+}
+.right {
+ width: calc(100% - 380px);
+ padding: 16px;
+ margin-left: 20px;
+ background: #ffffff;
+}
+.custom-tree-node {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ padding-right: 8px;
+}
+.tree-node-content {
+ display: flex;
+ align-items: center; /* 鍨傜洿灞呬腑 */
+ height: 100%;
+}
+.orange-icon {
+ color: orange;
+ font-size: 18px;
+ margin-right: 8px; /* 鍥炬爣涓庢枃瀛椾箣闂村姞鐐归棿璺� */
+}
+</style>
\ No newline at end of file
diff --git a/src/views/qualityManagement/nearExpiryReturn/index.vue b/src/views/qualityManagement/nearExpiryReturn/index.vue
new file mode 100644
index 0000000..93e19c2
--- /dev/null
+++ b/src/views/qualityManagement/nearExpiryReturn/index.vue
@@ -0,0 +1,395 @@
+<template>
+ <div class="app-container">
+ <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+ <el-form-item label="浜у搧鍚嶇О" prop="productName">
+ <el-input
+ v-model="queryParams.productName"
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+ clearable
+ @keyup.enter.native="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="鎵规鍙�" prop="batchNumber">
+ <el-input
+ v-model="queryParams.batchNumber"
+ placeholder="璇疯緭鍏ユ壒娆″彿"
+ clearable
+ @keyup.enter.native="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="閫�鍥炴棩鏈�" prop="returnDate">
+ <el-date-picker
+ clearable
+ v-model="queryParams.returnDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨閫�鍥炴棩鏈�">
+ </el-date-picker>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button
+ type="primary"
+ plain
+ icon="Plus"
+ @click="handleAdd"
+ >鏂板</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="success"
+ plain
+ icon="Edit"
+ :disabled="single"
+ @click="handleUpdate"
+ >淇敼</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="danger"
+ plain
+ icon="Delete"
+ :disabled="multiple"
+ @click="handleDelete"
+ >鍒犻櫎</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="warning"
+ plain
+ icon="Download"
+ @click="handleExport"
+ >瀵煎嚭</el-button>
+ </el-col>
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+ </el-row>
+
+ <el-table v-loading="loading" :data="nearExpiryReturnList" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="搴忓彿" type="index" width="50" align="center" />
+ <el-table-column label="浜у搧鍚嶇О" prop="productName" />
+ <el-table-column label="浜у搧瑙勬牸" prop="productSpec" />
+ <el-table-column label="鎵规鍙�" prop="batchNumber" />
+ <el-table-column label="鐢熶骇鏃ユ湡" prop="productionDate" align="center">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒版湡鏃ユ湡" prop="expiryDate" align="center">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.expiryDate, '{y}-{m}-{d}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="閫�鍥炴暟閲�" prop="returnQuantity" />
+ <el-table-column label="閫�鍥炲師鍥�" prop="returnReason" />
+ <el-table-column label="閫�鍥炴棩鏈�" prop="returnDate" align="center">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.returnDate, '{y}-{m}-{d}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶勭悊鐘舵��" prop="status" align="center">
+ <template #default="scope">
+ <dict-tag :options="statusOptions" :value="scope.row.status"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+ <template #default="scope">
+ <el-button size="mini" type="text" icon="Edit" @click="handleUpdate(scope.row)">淇敼</el-button>
+ <el-button size="mini" type="text" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ v-show="total>0"
+ :total="total"
+ v-model:page="queryParams.pageNum"
+ v-model:limit="queryParams.pageSize"
+ @pagination="getList"
+ />
+
+ <!-- 娣诲姞鎴栦慨鏀逛复鏈熼��鍥炲彴璐﹀璇濇 -->
+ <el-dialog :title="title" v-model="open" width="800px" append-to-body>
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="浜у搧鍚嶇О" prop="productName">
+ <el-input v-model="form.productName" placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浜у搧瑙勬牸" prop="productSpec">
+ <el-input v-model="form.productSpec" placeholder="璇疯緭鍏ヤ骇鍝佽鏍�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="鎵规鍙�" prop="batchNumber">
+ <el-input v-model="form.batchNumber" placeholder="璇疯緭鍏ユ壒娆″彿" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閫�鍥炴暟閲�" prop="returnQuantity">
+ <el-input-number v-model="form.returnQuantity" controls-position="right" :min="1" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="鐢熶骇鏃ユ湡" prop="productionDate">
+ <el-date-picker
+ clearable
+ v-model="form.productionDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨鐢熶骇鏃ユ湡">
+ </el-date-picker>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍒版湡鏃ユ湡" prop="expiryDate">
+ <el-date-picker
+ clearable
+ v-model="form.expiryDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨鍒版湡鏃ユ湡">
+ </el-date-picker>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="閫�鍥炴棩鏈�" prop="returnDate">
+ <el-date-picker
+ clearable
+ v-model="form.returnDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨閫�鍥炴棩鏈�">
+ </el-date-picker>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="澶勭悊鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨澶勭悊鐘舵��">
+ <el-option
+ v-for="dict in statusOptions"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ ></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="閫�鍥炲師鍥�" prop="returnReason">
+ <el-input v-model="form.returnReason" type="textarea" placeholder="璇疯緭鍏ラ��鍥炲師鍥�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+ <el-button @click="cancel">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup name="NearExpiryReturn">
+import { ref, reactive, onMounted } from "vue";
+import { ElMessageBox } from "element-plus";
+// API鎺ュ彛宸茬Щ闄わ紝涓嶅啀璋冪敤鍚庣鎺ュ彛
+
+const { proxy } = getCurrentInstance();
+const { parseTime } = proxy;
+
+const nearExpiryReturnList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+
+// 鐘舵�佸瓧鍏�
+const statusOptions = ref([
+ { label: "寰呭鐞�", value: "0" },
+ { label: "澶勭悊涓�", value: "1" },
+ { label: "宸插畬鎴�", value: "2" }
+]);
+
+const data = reactive({
+ form: {},
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ productName: null,
+ batchNumber: null,
+ returnDate: null
+ },
+ rules: {
+ productName: [
+ { required: true, message: "浜у搧鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ productSpec: [
+ { required: true, message: "浜у搧瑙勬牸涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ batchNumber: [
+ { required: true, message: "鎵规鍙蜂笉鑳戒负绌�", trigger: "blur" }
+ ],
+ returnQuantity: [
+ { required: true, message: "閫�鍥炴暟閲忎笉鑳戒负绌�", trigger: "blur" }
+ ],
+ productionDate: [
+ { required: true, message: "鐢熶骇鏃ユ湡涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ expiryDate: [
+ { required: true, message: "鍒版湡鏃ユ湡涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ returnDate: [
+ { required: true, message: "閫�鍥炴棩鏈熶笉鑳戒负绌�", trigger: "blur" }
+ ],
+ returnReason: [
+ { required: true, message: "閫�鍥炲師鍥犱笉鑳戒负绌�", trigger: "blur" }
+ ],
+ status: [
+ { required: true, message: "澶勭悊鐘舵�佷笉鑳戒负绌�", trigger: "change" }
+ ]
+ }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ涓存湡閫�鍥炲彴璐﹀垪琛� */
+function getList() {
+ loading.value = true;
+ // 涓嶈皟鐢ㄦ帴鍙o紝杩斿洖绌烘暟鎹�
+ nearExpiryReturnList.value = [];
+ total.value = 0;
+ loading.value = false;
+}
+
+// 鍙栨秷鎸夐挳
+function cancel() {
+ open.value = false;
+ reset();
+}
+
+// 琛ㄥ崟閲嶇疆
+function reset() {
+ form.value = {
+ id: null,
+ productName: null,
+ productSpec: null,
+ batchNumber: null,
+ productionDate: null,
+ expiryDate: null,
+ returnQuantity: null,
+ returnReason: null,
+ returnDate: null,
+ status: null,
+ remark: null
+ };
+ proxy.resetForm("formRef");
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+ queryParams.value.pageNum = 1;
+ getList();
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+ proxy.resetForm("queryForm");
+ handleQuery();
+}
+
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+ ids.value = selection.map(item => item.id);
+ single.value = selection.length !== 1;
+ multiple.value = !selection.length;
+}
+
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+ reset();
+ open.value = true;
+ title.value = "娣诲姞涓存湡閫�鍥炲彴璐�";
+}
+
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+ reset();
+ // 涓嶈皟鐢ㄦ帴鍙o紝鐩存帴浣跨敤浼犲叆鐨勬暟鎹�
+ if (row) {
+ form.value = { ...row };
+ open.value = true;
+ title.value = "淇敼涓存湡閫�鍥炲彴璐�";
+ }
+}
+
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ // 涓嶈皟鐢ㄦ帴鍙o紝鍙樉绀烘垚鍔熸彁绀�
+ if (form.value.id != null) {
+ proxy.$modal.msgSuccess("淇敼鎴愬姛");
+ } else {
+ proxy.$modal.msgSuccess("鏂板鎴愬姛");
+ }
+ open.value = false;
+ getList();
+ }
+ });
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+ const deleteIds = row.id || ids.value;
+ ElMessageBox.confirm('鏄惁纭鍒犻櫎涓存湡閫�鍥炲彴璐︾紪鍙蜂负"' + deleteIds + '"鐨勬暟鎹」锛�', "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }).then(function() {
+ // 涓嶈皟鐢ㄦ帴鍙o紝鍙樉绀烘垚鍔熸彁绀�
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ }).catch(() => {});
+}
+
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+ // 涓嶈皟鐢ㄦ帴鍙o紝鍙樉绀烘彁绀�
+ proxy.$modal.msgSuccess("瀵煎嚭鍔熻兘鏆傛湭瀹炵幇");
+}
+
+onMounted(() => {
+ getList();
+});
+</script>
+
diff --git a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
index 13a231d..9451ef0 100644
--- a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
+++ b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
@@ -87,7 +87,9 @@
</el-col>
<el-col :span="12">
<el-form-item label="澶勭悊缁撴灉锛�" prop="dealResult">
- <el-input v-model="form.dealResult" placeholder="璇疯緭鍏�" clearable/>
+ <el-select v-model="form.dealResult" placeholder="璇烽�夋嫨" clearable>
+ <el-option :label="item.label" :value="item.value" v-for="item in rejection_handling" :key="item.value" />
+ </el-select>
</el-form-item>
</el-col>
</el-row>
@@ -135,6 +137,7 @@
const dialogFormVisible = ref(false);
const operationType = ref('')
+const { rejection_handling } = proxy.useDict("rejection_handling")
const data = reactive({
form: {
checkTime: "",
diff --git a/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue b/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue
index 308d960..a53c648 100644
--- a/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue
+++ b/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue
@@ -2,7 +2,7 @@
<div>
<el-dialog
v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板鍘熸潗鏂欐楠�' : '缂栬緫鍘熸潗鏂欐楠�'"
+ :title="operationType === 'add' ? '鏂板涓嶅悎鏍煎鐞�' : '澶勭悊涓嶅悎鏍�'"
width="70%"
@close="closeDia"
>
@@ -89,7 +89,9 @@
</el-col>
<el-col :span="12">
<el-form-item label="澶勭悊缁撴灉锛�" prop="dealResult">
- <el-input v-model="form.dealResult" placeholder="璇疯緭鍏�" clearable/>
+ <el-select v-model="form.dealResult" placeholder="璇烽�夋嫨" clearable>
+ <el-option :label="item.label" :value="item.value" v-for="item in rejection_handling" :key="item.value" />
+ </el-select>
</el-form-item>
</el-col>
</el-row>
@@ -135,6 +137,7 @@
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
+const { rejection_handling } = proxy.useDict("rejection_handling")
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
diff --git a/src/views/qualityManagement/nonconformingManagement/index.vue b/src/views/qualityManagement/nonconformingManagement/index.vue
index d3ac667..c0fd2ca 100644
--- a/src/views/qualityManagement/nonconformingManagement/index.vue
+++ b/src/views/qualityManagement/nonconformingManagement/index.vue
@@ -4,7 +4,7 @@
<div style="display: flex;flex-direction: row;align-items: center;">
<div>
<span class="search_title">绫诲瀷锛�</span>
- <el-select v-model="searchForm.inspectType" clearable style="width: 240px" @change="handleQuery">
+ <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery">
<el-option label="鍘熸潗鏂欐楠�" :value="0" />
<el-option label="杩囩▼妫�楠�" :value="1" />
<el-option label="鍑哄巶妫�楠�" :value="2" />
@@ -12,7 +12,7 @@
</div>
<div style="margin-left: 10px">
<span class="search_title">鐘舵�侊細</span>
- <el-select v-model="searchForm.inspectState" clearable style="width: 240px" @change="handleQuery">
+ <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery">
<el-option label="寰呭鐞�" :value="0" />
<el-option label="宸插鐞�" :value="1" />
</el-select>
@@ -21,7 +21,7 @@
<span class="search_title">浜у搧鍚嶇О锛�</span>
<el-input
v-model="searchForm.productName"
- style="width: 240px"
+ style="width: 200px"
placeholder="璇疯緭鍏ヤ骇鍝佸悕绉版悳绱�"
@change="handleQuery"
clearable
@@ -30,6 +30,7 @@
</div>
<span style="margin-left: 10px" class="search_title">妫�娴嬫棩鏈燂細</span>
<el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ style="width: 300px"
placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
<el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
</div>
@@ -71,12 +72,9 @@
inspectType: "",
inspectState: "",
productName: "",
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDate: undefined, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
},
});
const { searchForm } = toRefs(data);
diff --git a/src/views/qualityManagement/processInspection/components/formDia.vue b/src/views/qualityManagement/processInspection/components/formDia.vue
index c24d425..37b3914 100644
--- a/src/views/qualityManagement/processInspection/components/formDia.vue
+++ b/src/views/qualityManagement/processInspection/components/formDia.vue
@@ -10,7 +10,7 @@
<el-row :gutter="30">
<el-col :span="12">
<el-form-item label="宸ュ簭锛�" prop="process">
- <el-input v-model="form.process" placeholder="璇疯緭鍏�" clearable/>
+ <el-input v-model="form.process" placeholder="璇疯緭鍏ュ伐搴�" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -32,6 +32,24 @@
<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="testStandardId">
+ <el-select
+ v-model="form.testStandardId"
+ placeholder="璇烽�夋嫨鎸囨爣"
+ clearable
+ @change="handleTestStandardChange"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in testStandardOptions"
+ :key="item.id"
+ :label="item.standardName || item.standardNo"
+ :value="item.id"
+ />
+ </el-select>
</el-form-item>
</el-col>
</el-row>
@@ -108,11 +126,11 @@
</template>
<script setup>
-import {ref} from "vue";
+import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
-import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js";
+import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {userListNoPage} from "@/api/system/user.js";
import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
const { proxy } = getCurrentInstance()
@@ -128,17 +146,19 @@
productName: "",
productId: "",
model: "",
+ testStandardId: "",
unit: "",
quantity: "",
checkCompany: "",
checkResult: "",
},
rules: {
- checkTime: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" },],
- process: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ checkTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
+ process: [{ required: true, message: "璇疯緭鍏ュ伐搴�", trigger: "blur" }],
checkName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
productId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
model: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ testStandardId: [{required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change"}],
unit: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
checkCompany: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
@@ -176,6 +196,7 @@
const tableData = ref([]);
const tableLoading = ref(false);
const currentProductId = ref(0);
+const testStandardOptions = ref([]); // 鎸囨爣閫夋嫨涓嬫媺妗嗘暟鎹�
// 鎵撳紑寮规
const openDialog = async (type, row) => {
@@ -187,11 +208,55 @@
let userLists = await userListNoPage();
userList.value = userLists.data;
form.value = {}
+ testStandardOptions.value = [];
+ tableData.value = [];
getProductOptions();
if (operationType.value === 'edit') {
- form.value = {...row}
+ // 鍏堜繚瀛� testStandardId锛岄伩鍏嶈娓呯┖
+ const savedTestStandardId = row.testStandardId;
+ // 鍏堣缃〃鍗曟暟鎹紝浣嗘殏鏃舵竻绌� testStandardId锛岀瓑閫夐」鍔犺浇瀹屾垚鍚庡啀璁剧疆
+ form.value = {...row, testStandardId: ''}
currentProductId.value = row.productId || 0
- getQualityInspectParamList(row.id)
+ // 缂栬緫妯″紡涓嬶紝鍏堝姞杞芥寚鏍囬�夐」锛岀劧鍚庡姞杞藉弬鏁板垪琛�
+ if (currentProductId.value) {
+ // 鍏堝姞杞芥寚鏍囬�夐」
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 1,
+ process: form.value.process || ''
+ }
+ qualityInspectDetailByProductId(params).then(res => {
+ testStandardOptions.value = res.data || [];
+ // 浣跨敤 nextTick 鍜� setTimeout 纭繚閫夐」宸茬粡娓叉煋鍒� DOM
+ nextTick(() => {
+ setTimeout(() => {
+ // 濡傛灉缂栬緫鏁版嵁涓湁 testStandardId锛屽垯璁剧疆骞跺姞杞藉搴旂殑鍙傛暟
+ if (savedTestStandardId) {
+ // 纭繚绫诲瀷鍖归厤锛坕tem.id 鍙兘鏄暟瀛楁垨瀛楃涓诧級
+ const matchedOption = testStandardOptions.value.find(item =>
+ item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
+ );
+ if (matchedOption) {
+ // 纭繚浣跨敤鍖归厤椤圭殑 id锛堜繚鎸佺被鍨嬩竴鑷达級
+ form.value.testStandardId = matchedOption.id;
+ // 缂栬緫淇濈暀鍘熸楠屽�硷紝鐩存帴鎷夊彇鍘熷弬鏁版暟鎹�
+ getQualityInspectParamList(row.id);
+ } else {
+ // 濡傛灉鎵句笉鍒板尮閰嶉」锛屽皾璇曠洿鎺ヤ娇鐢ㄥ師鍊�
+ console.warn('鏈壘鍒板尮閰嶇殑鎸囨爣閫夐」锛宼estStandardId:', savedTestStandardId, '鍙敤閫夐」:', testStandardOptions.value);
+ form.value.testStandardId = savedTestStandardId;
+ getQualityInspectParamList(row.id);
+ }
+ } else {
+ // 鍚﹀垯浣跨敤鏃х殑閫昏緫
+ getQualityInspectParamList(row.id);
+ }
+ }, 100);
+ });
+ });
+ } else {
+ getQualityInspectParamList(row.id);
+ }
}
}
const getProductOptions = () => {
@@ -202,7 +267,7 @@
const getModels = (value) => {
currentProductId.value = value
form.value.productName = findNodeById(productOptions.value, value);
- if (currentProductId) {
+ if (currentProductId.value) {
getList();
}
};
@@ -234,17 +299,23 @@
return newItem;
});
}
+// 宸ュ簭鍙樺寲澶勭悊
// 鎻愪氦浜у搧琛ㄥ崟
const submitForm = () => {
proxy.$refs.formRef.validate(valid => {
if (valid) {
form.value.inspectType = 1
+ const processName = form.value.process || '';
if (operationType.value === "add") {
tableData.value.forEach((item) => {
delete item.id
})
}
- const data = {...form.value, qualityInspectParams: tableData.value}
+ const data = {
+ ...form.value,
+ process: processName, // 淇濈暀 process 瀛楁浠ュ吋瀹瑰悗绔�
+ qualityInspectParams: tableData.value
+ }
if (operationType.value === "add") {
qualityInspectAdd(data).then(res => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
@@ -260,8 +331,41 @@
})
}
const getList = () => {
- qualityInspectDetailByProductId(currentProductId.value).then(res => {
- tableData.value = res.data;
+ if (!currentProductId.value) {
+ testStandardOptions.value = [];
+ tableData.value = [];
+ return;
+ }
+ const processName = form.value.process || '';
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 1,
+ process: processName
+ }
+ qualityInspectDetailByProductId(params).then(res => {
+ // 淇濆瓨涓嬫媺妗嗛�夐」鏁版嵁
+ testStandardOptions.value = res.data || [];
+ // 娓呯┖琛ㄦ牸鏁版嵁锛岀瓑寰呯敤鎴烽�夋嫨鎸囨爣
+ tableData.value = [];
+ // 娓呯┖鎸囨爣閫夋嫨
+ form.value.testStandardId = '';
+ })
+}
+
+// 鎸囨爣閫夋嫨鍙樺寲澶勭悊
+const handleTestStandardChange = (testStandardId) => {
+ if (!testStandardId) {
+ tableData.value = [];
+ return;
+ }
+ tableLoading.value = true;
+ getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
+ tableData.value = res.data || [];
+ }).catch(error => {
+ console.error('鑾峰彇鏍囧噯鍙傛暟澶辫触:', error);
+ tableData.value = [];
+ }).finally(() => {
+ tableLoading.value = false;
})
}
const getQualityInspectParamList = (id) => {
@@ -272,6 +376,9 @@
// 鍏抽棴寮规
const closeDia = () => {
proxy.resetForm("formRef");
+ tableData.value = [];
+ testStandardOptions.value = [];
+ form.value.testStandardId = '';
dialogFormVisible.value = false;
emit('close')
};
diff --git a/src/views/qualityManagement/processInspection/components/inspectionFormDia.vue b/src/views/qualityManagement/processInspection/components/inspectionFormDia.vue
index 32a36fa..411856c 100644
--- a/src/views/qualityManagement/processInspection/components/inspectionFormDia.vue
+++ b/src/views/qualityManagement/processInspection/components/inspectionFormDia.vue
@@ -34,7 +34,6 @@
<script setup>
import {ref} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
qualityInspectParamDel,
diff --git a/src/views/qualityManagement/processInspection/index.vue b/src/views/qualityManagement/processInspection/index.vue
index 6f5ddec..cebda55 100644
--- a/src/views/qualityManagement/processInspection/index.vue
+++ b/src/views/qualityManagement/processInspection/index.vue
@@ -79,12 +79,9 @@
const data = reactive({
searchForm: {
process: "",
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDate: undefined, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
},
rules: {
checkName: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
diff --git a/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue b/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
index 977b420..442d7fd 100644
--- a/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -45,6 +45,24 @@
<el-input v-model="form.model" placeholder="璇疯緭鍏�" clearable/>
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="鎸囨爣閫夋嫨锛�" prop="testStandardId">
+ <el-select
+ v-model="form.testStandardId"
+ placeholder="璇烽�夋嫨鎸囨爣"
+ clearable
+ @change="handleTestStandardChange"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in testStandardOptions"
+ :key="item.id"
+ :label="item.standardName || item.standardNo"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
@@ -121,13 +139,13 @@
</template>
<script setup>
-import {ref} from "vue";
+import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {ElMessageBox} from "element-plus";
import {qualityInspectParamDel, qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
-import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js";
+import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
const {proxy} = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -142,17 +160,19 @@
productName: "",
productId: "",
model: "",
+ testStandardId: "",
unit: "",
quantity: "",
checkCompany: "",
checkResult: "",
},
rules: {
- checkTime: [{required: false, message: "璇疯緭鍏�", trigger: "blur"},],
+ checkTime: [{required: true, message: "璇疯緭鍏�", trigger: "blur"},],
supplier: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
checkName: [{required: false, message: "璇疯緭鍏�", trigger: "blur"}],
productId: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
model: [{required: false, message: "璇疯緭鍏�", trigger: "blur"}],
+ testStandardId: [{required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change"}],
unit: [{required: false, message: "璇疯緭鍏�", trigger: "blur"}],
quantity: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
checkCompany: [{required: false, message: "璇疯緭鍏�", trigger: "blur"}],
@@ -190,6 +210,7 @@
const supplierList = ref([]);
const productOptions = ref([]);
const currentProductId = ref(0);
+const testStandardOptions = ref([]); // 鎸囨爣閫夋嫨涓嬫媺妗嗘暟鎹�
// 鎵撳紑寮规
const openDialog = (type, row) => {
@@ -199,11 +220,53 @@
supplierList.value = res.data;
});
form.value = {}
+ testStandardOptions.value = [];
+ tableData.value = [];
getProductOptions();
if (operationType.value === 'edit') {
+ // 鍏堜繚瀛� testStandardId锛岄伩鍏嶈娓呯┖
+ const savedTestStandardId = row.testStandardId;
form.value = {...row}
currentProductId.value = row.productId || 0
- getQualityInspectParamList(row.id)
+ // 缂栬緫妯″紡涓嬶紝鍏堝姞杞芥寚鏍囬�夐」锛岀劧鍚庡姞杞藉弬鏁板垪琛�
+ if (currentProductId.value) {
+ // 鍏堝姞杞芥寚鏍囬�夐」
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 0
+ }
+ qualityInspectDetailByProductId(params).then(res => {
+ testStandardOptions.value = res.data || [];
+ // 浣跨敤 nextTick 鍜� setTimeout 纭繚閫夐」宸茬粡娓叉煋鍒� DOM
+ nextTick(() => {
+ setTimeout(() => {
+ // 濡傛灉缂栬緫鏁版嵁涓湁 testStandardId锛屽垯璁剧疆骞跺姞杞藉搴旂殑鍙傛暟
+ if (savedTestStandardId) {
+ // 纭繚绫诲瀷鍖归厤锛坕tem.id 鍙兘鏄暟瀛楁垨瀛楃涓诧級
+ const matchedOption = testStandardOptions.value.find(item =>
+ item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
+ );
+ if (matchedOption) {
+ // 纭繚浣跨敤鍖归厤椤圭殑 id锛堜繚鎸佺被鍨嬩竴鑷达級
+ form.value.testStandardId = matchedOption.id;
+ // 缂栬緫淇濈暀鍘熸楠屽�硷紝鐩存帴鎷夊彇鍘熷弬鏁版暟鎹�
+ getQualityInspectParamList(row.id);
+ } else {
+ // 濡傛灉鎵句笉鍒板尮閰嶉」锛屽皾璇曠洿鎺ヤ娇鐢ㄥ師鍊�
+ console.warn('鏈壘鍒板尮閰嶇殑鎸囨爣閫夐」锛宼estStandardId:', savedTestStandardId, '鍙敤閫夐」:', testStandardOptions.value);
+ form.value.testStandardId = savedTestStandardId;
+ getQualityInspectParamList(row.id);
+ }
+ } else {
+ // 鍚﹀垯浣跨敤鏃х殑閫昏緫
+ getQualityInspectParamList(row.id);
+ }
+ }, 100);
+ });
+ });
+ } else {
+ getQualityInspectParamList(row.id);
+ }
}
}
const getProductOptions = () => {
@@ -214,7 +277,7 @@
const getModels = (value) => {
currentProductId.value = value
form.value.productName = findNodeById(productOptions.value, value);
- if (currentProductId) {
+ if (currentProductId.value) {
getList();
}
};
@@ -275,8 +338,39 @@
}
const getList = () => {
- qualityInspectDetailByProductId(currentProductId.value).then(res => {
- tableData.value = res.data;
+ if (!currentProductId.value) {
+ testStandardOptions.value = [];
+ tableData.value = [];
+ return;
+ }
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 0
+ }
+ qualityInspectDetailByProductId(params).then(res => {
+ // 淇濆瓨涓嬫媺妗嗛�夐」鏁版嵁
+ testStandardOptions.value = res.data || [];
+ // 娓呯┖琛ㄦ牸鏁版嵁锛岀瓑寰呯敤鎴烽�夋嫨鎸囨爣
+ tableData.value = [];
+ // 娓呯┖鎸囨爣閫夋嫨
+ form.value.testStandardId = '';
+ })
+}
+
+// 鎸囨爣閫夋嫨鍙樺寲澶勭悊
+const handleTestStandardChange = (testStandardId) => {
+ if (!testStandardId) {
+ tableData.value = [];
+ return;
+ }
+ tableLoading.value = true;
+ getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
+ tableData.value = res.data || [];
+ }).catch(error => {
+ console.error('鑾峰彇鏍囧噯鍙傛暟澶辫触:', error);
+ tableData.value = [];
+ }).finally(() => {
+ tableLoading.value = false;
})
}
@@ -288,7 +382,9 @@
// 鍏抽棴寮规
const closeDia = () => {
proxy.resetForm("formRef");
- tableData.value = []
+ tableData.value = [];
+ testStandardOptions.value = [];
+ form.value.testStandardId = '';
dialogFormVisible.value = false;
emit('close')
};
diff --git a/src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue b/src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue
index 32a36fa..411856c 100644
--- a/src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue
@@ -34,7 +34,6 @@
<script setup>
import {ref} from "vue";
-import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
qualityInspectParamDel,
diff --git a/src/views/qualityManagement/rawMaterialInspection/index.vue b/src/views/qualityManagement/rawMaterialInspection/index.vue
index 7536274..c9ea3db 100644
--- a/src/views/qualityManagement/rawMaterialInspection/index.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -81,12 +81,9 @@
const data = reactive({
searchForm: {
supplier: "",
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ entryDate: undefined, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
},
rules: {
checkName: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
diff --git a/src/views/qualityManagement/visualization/qualityDashboard.vue b/src/views/qualityManagement/visualization/qualityDashboard.vue
new file mode 100644
index 0000000..57462b7
--- /dev/null
+++ b/src/views/qualityManagement/visualization/qualityDashboard.vue
@@ -0,0 +1,307 @@
+<template>
+ <div class="quality-dashboard">
+ <el-row :gutter="16">
+ <el-col :xs="24" :sm="12">
+ <el-card shadow="hover" class="panel">
+ <template #header>
+ <div class="panel-title">
+ 妫�娴嬫牱鍝佸姩鎬佺姸鎬�
+ <div class="actions">
+ <el-switch v-model="voiceEnabled" active-text="璇煶棰勮" inactive-text="闈欓煶" />
+ </div>
+ </div>
+ </template>
+ <div class="status-list">
+ <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status">
+ <div class="left">
+ <span class="dot" :class="item.status"></span>
+ <span class="name">{{ item.name }}</span>
+ </div>
+ <div class="right">
+ <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag>
+ <span class="time">{{ item.time }}</span>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :xs="24" :sm="12">
+ <el-card shadow="hover" class="panel">
+ <template #header>
+ <div class="panel-title">浠诲姟鎺掕锛圱op 10锛�</div>
+ </template>
+ <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" />
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16" style="margin-top: 16px;">
+ <el-col :xs="24" :sm="14">
+ <el-card shadow="hover" class="panel">
+ <template #header>
+ <div class="panel-title">鍘嗗彶瓒嬪娍</div>
+ </template>
+ <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: '鏁伴噺' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" />
+ </el-card>
+ </el-col>
+ <el-col :xs="24" :sm="10">
+ <el-card shadow="hover" class="panel">
+ <template #header>
+ <div class="panel-title">鍚堟牸鐜囧垎鏋�</div>
+ </template>
+ <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" />
+ <div class="passrate-text">
+ 褰撳墠鍚堟牸鐜囷細<b>{{ (passRate * 100).toFixed(1) }}%</b>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16" style="margin-top: 16px;">
+ <el-col :xs="24">
+ <el-card shadow="hover" class="panel">
+ <template #header>
+ <div class="panel-title">SPC 鎺у埗鍥撅紙Xbar锛�</div>
+ </template>
+ <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: '娴嬮噺鍊�' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" />
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+
+</template>
+
+<script setup>
+import { onMounted, onBeforeUnmount, reactive, ref } from 'vue'
+import EChart from '@/components/Echarts/echarts.vue'
+
+const voiceEnabled = ref(true)
+let dataTimer = null
+
+// 1) 鏍峰搧鍔ㄦ�佺姸鎬侊紙婊氬姩鏇存柊锛�
+const sampleStatus = ref([])
+const statusPool = ['processing', 'warning', 'error', 'success']
+function statusLabel(s) {
+ return s === 'processing' ? '妫�娴嬩腑' : s === 'warning' ? '棰勮' : s === 'error' ? '涓嶅悎鏍�' : '鍚堟牸'
+}
+function statusTagType(s) {
+ return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success'
+}
+function randomSample() {
+ const id = Math.random().toString(36).slice(2, 8)
+ const status = statusPool[Math.floor(Math.random() * statusPool.length)]
+ const name = `鏍峰搧-${Math.floor(Math.random() * 900 + 100)}`
+ const time = new Date().toLocaleTimeString('zh-CN', { hour12: false })
+ return { id, name, status, time }
+}
+
+// 2) 浠诲姟鎺掕锛堟煴鐘跺浘锛�
+const tasksXAxis = reactive([{ type: 'category', data: [] }])
+const tasksSeries = ref([
+ {
+ type: 'bar',
+ data: [],
+ label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' },
+ encode: undefined,
+ },
+])
+
+// 3) 鍘嗗彶瓒嬪娍锛堟姌绾匡級
+const trendXAxis = ref([])
+const trendSeries = ref([
+ { name: '鏉ユ牱鏁�', type: 'line', smooth: true, data: [] },
+ { name: '瀹屾垚鏁�', type: 'line', smooth: true, data: [] },
+])
+
+// 4) 鍚堟牸鐜囧垎鏋愶紙浠〃鐩橈級
+const passRate = ref(0.92)
+const passRateSeries = ref([
+ {
+ type: 'gauge',
+ progress: { show: true, width: 12 },
+ axisLine: { lineStyle: { width: 12 } },
+ pointer: { show: true },
+ detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` },
+ data: [{ value: passRate.value }],
+ },
+])
+
+// 5) SPC 鎺у埗鍥�
+const spcXAxis = ref([])
+const spcData = ref([]) // 瀹為檯娴嬮噺鍊�
+const CL = ref(50)
+const UCL = ref(55)
+const LCL = ref(45)
+const spcSeries = ref([
+ {
+ name: '娴嬮噺鍧囧��',
+ type: 'line',
+ smooth: false,
+ symbol: 'circle',
+ data: [],
+ markLine: {
+ symbol: 'none',
+ lineStyle: { type: 'dashed', color: '#999' },
+ data: [
+ { yAxis: () => UCL.value, name: 'UCL' },
+ { yAxis: () => CL.value, name: 'CL' },
+ { yAxis: () => LCL.value, name: 'LCL' },
+ ],
+ label: { formatter: ({ name }) => name },
+ },
+ },
+ { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } },
+ { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } },
+ { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } },
+])
+
+// 璇煶鎾姤
+function speak(text) {
+ if (!voiceEnabled.value) return
+ if (!('speechSynthesis' in window)) return
+ const utter = new SpeechSynthesisUtterance(text)
+ utter.lang = 'zh-CN'
+ try {
+ window.speechSynthesis.cancel()
+ window.speechSynthesis.speak(utter)
+ } catch (e) {
+ // ignore
+ }
+}
+
+function refreshFakeData() {
+ // 鏍峰搧鐘舵�佹粴鍔�
+ const next = randomSample()
+ sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8)
+
+ // 浠诲姟鎺掕
+ const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `浠诲姟-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) }))
+ tasks.sort((a, b) => a.count - b.count)
+ tasksXAxis.data = tasks.map(t => t.name)
+ tasksSeries.value[0].data = tasks.map(t => t.count)
+
+ // 鍘嗗彶瓒嬪娍锛堣拷鍔犵偣锛�
+ const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' })
+ if (trendXAxis.value.length > 15) {
+ trendXAxis.value.shift()
+ trendSeries.value[0].data.shift()
+ trendSeries.value[1].data.shift()
+ }
+ trendXAxis.value.push(nowLabel)
+ const incoming = Math.floor(Math.random() * 30 + 20)
+ const finished = Math.max(0, incoming - Math.floor(Math.random() * 10))
+ trendSeries.value[0].data.push(incoming)
+ trendSeries.value[1].data.push(finished)
+
+ // 鍚堟牸鐜囷紙杞诲井娉㈠姩锛�
+ const delta = (Math.random() - 0.5) * 0.02
+ passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta))
+ passRateSeries.value[0].data[0].value = passRate.value
+
+ // SPC 鏁版嵁锛堢獥鍙gЩ鍔級
+ const nextVal = CL.value + (Math.random() - 0.5) * 8 // 娉㈠姩
+ if (spcXAxis.value.length > 30) {
+ spcXAxis.value.shift()
+ spcData.value.shift()
+ }
+ spcXAxis.value.push(`${spcXAxis.value.length + 1}`)
+ spcData.value.push(parseFloat(nextVal.toFixed(2)))
+ spcSeries.value[0].data = [...spcData.value]
+ spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
+ spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
+ spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
+
+ // 瑙﹀彂鎾姤锛氬悎鏍肩巼杩囦綆鎴� SPC 瓒呴檺
+ if (passRate.value < 0.8) {
+ speak(`棰勮锛屽綋鍓嶅悎鏍肩巼涓� ${(passRate.value * 100).toFixed(0)}%锛屼綆浜� 80% 闃堝�糮)
+ }
+ const last = spcData.value[spcData.value.length - 1]
+ if (last > UCL.value) {
+ speak(`棰勮锛屾渶鏂版祴閲忓�� ${last.toFixed(2)} 瓒呰繃涓婇檺`)
+ }
+ if (last < LCL.value) {
+ speak(`棰勮锛屾渶鏂版祴閲忓�� ${last.toFixed(2)} 浣庝簬涓嬮檺`)
+ }
+}
+
+onMounted(() => {
+ // 鍒濆鍖栧嚑鏉″亣鏁版嵁
+ sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample())
+ for (let i = 0; i < 10; i++) {
+ trendXAxis.value.push(`T-${i}`)
+ trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20))
+ trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15))
+ }
+ for (let i = 0; i < 20; i++) {
+ spcXAxis.value.push(`${i + 1}`)
+ const v = CL.value + (Math.random() - 0.5) * 6
+ spcData.value.push(parseFloat(v.toFixed(2)))
+ }
+ spcSeries.value[0].data = [...spcData.value]
+ spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
+ spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
+ spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
+
+ dataTimer = setInterval(refreshFakeData, 10000)
+})
+
+onBeforeUnmount(() => {
+ if (dataTimer) clearInterval(dataTimer)
+ try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {}
+})
+</script>
+
+<style scoped>
+.quality-dashboard {
+ padding: 8px;
+}
+.panel-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-weight: 600;
+}
+.status-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-height: 320px;
+ overflow: auto;
+}
+.status-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 10px;
+ border-radius: 6px;
+ background: var(--el-fill-color-light);
+}
+.status-item .left {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.status-item .right {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.status-item .name { font-weight: 500; }
+.status-item .time { color: var(--el-text-color-secondary); font-size: 12px; }
+.dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ display: inline-block;
+}
+.dot.processing { background: #60a5fa; }
+.dot.warning { background: #f59e0b; }
+.dot.error { background: #ef4444; }
+.dot.success { background: #10b981; }
+.passrate-text {
+ text-align: center;
+ margin-top: 8px;
+}
+</style>
+
+
diff --git a/src/views/reportAnalysis/dataDashboard/index.vue b/src/views/reportAnalysis/dataDashboard/index.vue
new file mode 100644
index 0000000..5c318c8
--- /dev/null
+++ b/src/views/reportAnalysis/dataDashboard/index.vue
@@ -0,0 +1,2036 @@
+<template>
+ <div class="scale-container">
+ <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }">
+ <!-- 鍏ㄥ睆鎸夐挳 - 绉诲姩鍒板乏涓婅 -->
+ <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? '閫�鍑哄叏灞�' : '鍏ㄥ睆鏄剧ず'">
+ <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
+ </svg>
+ <svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
+ </svg>
+ </button>
+
+ <!-- 椤堕儴鏍囬鏍� -->
+ <div class="dashboard-header">
+ <div class="factory-name">{{ userStore.currentFactoryName }}</div>
+ </div>
+
+ <!-- 涓昏鍐呭鍖哄煙 -->
+ <div class="dashboard-content">
+ <!-- 宸︿晶鍖哄煙 -->
+ <div class="left-panel">
+ <!-- 瀹㈡埛淇℃伅缁熻鍒嗘瀽 -->
+ <div class="panel-header">
+ <span class="panel-title">鍦ㄥ埗鍝佺粺璁″垎鏋�</span>
+ </div>
+ <div class="panel-item-customers">
+ <div class="quality-cards">
+ <div class="quality-cardSec">
+ <div class="quality-card one"></div>
+ <div class="quality-cardTitle">
+ <div>鎬诲湪鍒舵暟閲�</div>
+ <div>{{workInProcessStatistics.totalQuantity}}浠�</div>
+ </div>
+ </div>
+ <div class="quality-cardSec">
+ <div class="quality-card two"></div>
+ <div class="quality-cardTitle">
+ <div>骞冲潎鍛ㄨ浆澶╂暟</div>
+ <div>{{workInProcessStatistics.avgTurnoverDays}}澶�</div>
+ </div>
+ </div>
+ <div class="quality-cardSec">
+ <div class="quality-card three"></div>
+ <div class="quality-cardTitle">
+ <div>鍛ㄨ浆鏁堢巼</div>
+ <div>{{workInProcessStatistics.turnoverEfficiency}}%</div>
+ </div>
+ </div>
+ </div>
+ <!-- 宸ュ簭鍦ㄥ埗鍝佹暟閲忔煴鐘跺浘 -->
+ <div style="height: 70%">
+ <Echarts ref="chart"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="workInProcessBarLegend"
+ :series="workInProcessBarSeries"
+ :tooltip="tooltip"
+ :xAxis="workInProcessXAxis"
+ :yAxis="workInProcessYAxis"
+ :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
+ style="height: 100%"></Echarts>
+ </div>
+ </div>
+
+ <!-- 璐ㄩ噺缁熻 -->
+ <div class="panel-header">
+ <span class="panel-title">杩�4鏈堣川閲忕粺璁�</span>
+ </div>
+ <div class="main-panel">
+ <div class="panel-item-customers">
+ <div class="quality-cards">
+ <div class="quality-cardSec">
+ <div class="quality-card one"></div>
+ <div class="quality-cardTitle">
+ <div>鍘熸潗鏂欐鏁�</div>
+ <div>{{qualityStatisticsObject.supplierNum}}浠�</div>
+ </div>
+ </div>
+ <div class="quality-cardSec">
+ <div class="quality-card two"></div>
+ <div class="quality-cardTitle">
+ <div>杩囩▼妫�鏁�</div>
+ <div>{{qualityStatisticsObject.processNum}}浠�</div>
+ </div>
+ </div>
+ <div class="quality-cardSec">
+ <div class="quality-card three"></div>
+ <div class="quality-cardTitle">
+ <div>鍑哄巶妫�鏁�</div>
+ <div>{{qualityStatisticsObject.factoryNum}}浠�</div>
+ </div>
+ </div>
+ </div>
+ <Echarts ref="chart"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="barLegend"
+ :series="barSeries1"
+ :tooltip="tooltip"
+ :xAxis="xAxis1"
+ :yAxis="yAxis1"
+ :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
+ style="height: 260px"></Echarts>
+ </div>
+ </div>
+ </div>
+
+ <!-- 涓棿鍖哄煙 -->
+ <div class="center-panel">
+ <!-- 椤堕儴缁熻鍗$墖 -->
+ <div class="stats-cards">
+ <div class="stat-card">
+ <img src="@/assets/BI/icon@2x.png" alt="鍥炬爣" class="card-icon" />
+ <div class="card-content">
+ <span class="card-label">鍛樺伐鎬绘暟</span>
+ <span class="card-value">{{totalStaff}}</span>
+ </div>
+ </div>
+ <div class="stat-card">
+ <img src="@/assets/BI/icon@2x.png" alt="鍥炬爣" class="card-icon" />
+ <div class="card-content">
+ <span class="card-label">瀹㈡埛鎬绘暟</span>
+ <span class="card-value">{{totalCustomers}}</span>
+ </div>
+ </div>
+ <div class="stat-card">
+ <img src="@/assets/BI/icon@2x.png" alt="鍥炬爣" class="card-icon" />
+ <div class="card-content">
+ <span class="card-label">渚涘簲鍟嗘�绘暟</span>
+ <span class="card-value">{{totalSuppliers}}</span>
+ </div>
+ </div>
+ </div>
+
+ <!-- 璁惧缁熻 -->
+ <div class="equipment-stats">
+ <div class="equipment-header">
+ <img src="@/assets/BI/shujutongjiicon@2x.png" alt="鍥炬爣" class="equipment-icon" />
+ <span class="equipment-title">璁惧缁熻</span>
+ </div>
+ <div class="equipment-items">
+ <div class="equipment-item">
+ <span class="equipment-value">{{equipmentNum}}</span>
+ <span class="equipment-label">璁惧鎬绘暟</span>
+ </div>
+ <div class="equipment-item">
+ <span class="equipment-value">{{equipmentRepair}}</span>
+ <span class="equipment-label">寰呯淮淇澶�</span>
+ </div>
+ <div class="equipment-item">
+ <span class="equipment-value">{{equipmentMaintain}}</span>
+ <span class="equipment-label">寰呬繚鍏昏澶�</span>
+ </div>
+ <div class="equipment-item">
+ <span class="equipment-value">{{totalMeasuring}}</span>
+ <span class="equipment-label">璁¢噺鍣ㄥ叿鎬绘暟</span>
+ </div>
+ </div>
+ </div>
+
+ <!-- 浜嬩欢鍚嶇О -->
+ <div class="event-info">
+ <div class="event-header">
+ <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="鍥炬爣" class="event-icon" />
+ <span class="event-title">浜嬩欢鍚嶇О</span>
+ </div>
+ <div class="event-content">
+ <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList">
+ <li v-for="item in todoList" :key="item.id">
+ <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">
+ <div style="display: flex;justify-content: space-between;align-items: center;">
+ <div class="todo-title">寰呭姙缂栧彿锛歿{item.approveId}}</div>
+ <div class="todo-division">閮ㄩ棬锛歿{item.approveDeptName}}</div>
+ <div class="todo-time">{{item.approveTime}}</div>
+ </div>
+ <div class="todo-division">寰呭姙浜嬬敱锛歿{item.approveReason}}</div>
+ </div>
+ </li>
+ </ul>
+ <div v-else style="text-align: center">
+ 鏆傛棤鏁版嵁
+ </div>
+ </div>
+ </div>
+
+ <div class="financial-header">
+ <span class="financial-title">鍚勭敓浜ц鍗曠殑瀹屾垚杩涘害缁熻</span>
+ </div>
+ <div class="main-panel">
+ <div class="panel-item-customers">
+ <div class="order-statistics-cards" style="margin-bottom: 0px;">
+ <div class="quality-cardSec">
+ <div class="quality-card four"></div>
+ <div class="quality-cardTitle">
+ <div>鎬昏鍗曟暟</div>
+ <div>{{orderStatisticsObject.totalOrderCount}}浠�</div>
+ </div>
+ </div>
+ <div class="quality-cardSec">
+ <div class="quality-card five"></div>
+ <div class="quality-cardTitle">
+ <div>鏈畬鎴愯鍗曟暟</div>
+ <div>{{orderStatisticsObject.uncompletedOrderCount}}浠�</div>
+ </div>
+ </div>
+ <div class="quality-cardSec">
+ <div class="quality-card six"></div>
+ <div class="quality-cardTitle">
+ <div>閮ㄥ垎瀹屾垚璁㈠崟鏁�</div>
+ <div>{{orderStatisticsObject.partialCompletedOrderCount}}浠�</div>
+ </div>
+ </div>
+ <div class="quality-cardSec">
+ <div class="quality-card seven"></div>
+ <div class="quality-cardTitle">
+ <div>宸插畬鎴愯鍗曟暟</div>
+ <div>{{orderStatisticsObject.completedOrderCount}}浠�</div>
+ </div>
+ </div>
+ </div>
+ <div class="progress-table-container" ref="progressTableRef" style="margin-top: 0px;" @scroll="handleTableScroll">
+ <table class="progress-table">
+ <thead>
+ <tr>
+ <th>鐢熶骇璁㈠崟鍙�</th>
+ <th>浜у搧鍚嶇О</th>
+ <th>瑙勬牸</th>
+ <th>闇�姹傛暟閲�</th>
+ <th>瀹屾垚鏁伴噺</th>
+ <th>瀹屾垚杩涘害</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr
+ v-for="(item, index) in progressTableData"
+ :key="index"
+ :ref="el => setRowRef(el, index)"
+ :class="{ 'row-under-header': isRowUnderHeader(index) }"
+ >
+ <td>{{ item.npsNo || '-' }}</td>
+ <td>{{ item.productCategory || '-' }}</td>
+ <td>{{ item.specificationModel || '-' }}</td>
+ <td>{{ item.quantity || 0 }}</td>
+ <td>{{ item.completeQuantity || 0 }}</td>
+ <td>
+ <el-progress
+ :percentage="calculateProgress(item)"
+ :color="progressColor(calculateProgress(item))"
+ :status="calculateProgress(item) >= 100 ? 'success' : ''"
+ :stroke-width="8"
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鍙充晶鍖哄煙 -->
+ <div class="right-panel">
+ <!-- 搴旀敹搴斾粯缁熻 -->
+ <div class="panel-header">
+ <span class="panel-title">搴旀敹搴斾粯缁熻</span>
+ </div>
+ <div class="panel-item-customers">
+ <div style="display: flex;justify-content: space-between;margin-bottom: 20px;">
+ <div class="section-title">搴旀敹搴斾粯缁熻</div>
+<!-- <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group">-->
+<!-- <el-radio-button label="鎸夊懆" :value="1" />-->
+<!-- <el-radio-button label="鎸夋湀" :value="2" />-->
+<!-- <el-radio-button label="鎸夊搴�" :value="3" />-->
+<!-- </el-radio-group>-->
+ </div>
+ <Echarts ref="chart"
+ :color="barColors2"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="barLegend2"
+ :series="barSeries"
+ :tooltip="tooltip"
+ :xAxis="xAxis"
+ :yAxis="yAxis"
+ :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
+ style="height: 260px"></Echarts>
+ </div>
+
+ <!-- 鍥炴涓庡紑绁ㄥ垎鏋� -->
+ <div class="panel-header">
+ <span class="panel-title">杩戜竴鏈堝洖娆句笌寮�绁ㄥ垎鏋�</span>
+ </div>
+ <div class="panel-item-customers" style="padding-top: 60px;">
+ <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"
+ :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" :options="{backgroundColor: 'transparent', textStyle: {color: '#FFFFFF'}}" style="height: 270px;"></Echarts>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import * as echarts from 'echarts'
+import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import autofit from 'autofit.js'
+import Echarts from "@/components/Echarts/echarts.vue";
+import useUserStore from '@/store/modules/user'
+import {
+ analysisCustomerContractAmounts, getAmountHalfYear,
+ homeTodos,
+ qualityStatistics,
+ statisticsReceivablePayable,
+ getProgressStatistics,
+ getWorkInProcessTurnover
+} from "@/api/viewIndex.js";
+import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js";
+import {listCustomer} from "@/api/basicData/customerFile.js";
+import {listSupplier} from "@/api/basicData/supplierManageFile.js";
+import {getLedgerPage} from "@/api/equipmentManagement/ledger.js";
+import {getRepairPage} from "@/api/equipmentManagement/repair.js";
+import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js";
+import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js";
+import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js";
+import {productOrderListPage} from "@/api/productionManagement/productionOrder.js";
+
+// 鍏ㄥ睆鐩稿叧鐘舵��
+const isFullscreen = ref(false);
+
+// 缂╂斁姣斾緥
+const scaleRatio = ref(1)
+// 璁捐灏哄锛堝熀鍑嗗昂瀵革級- 鏍规嵁瀹為檯璁捐绋胯皟鏁�
+const designWidth = 1920
+const designHeight = 1080
+
+// 鐢ㄦ埛store
+const userStore = useUserStore()
+
+// 鍝嶅簲寮忔暟鎹�
+const currentTime = ref('')
+const currentDate = ref('')
+const timer = ref(null)
+const charts = ref([])
+
+// 鍥捐〃寮曠敤
+const customerPieChartRef = ref(null)
+const salesBarChartRef = ref(null)
+const dataBarChartRef = ref(null)
+const financialAreaChartRef = ref(null)
+const realtimeLineChartRef = ref(null)
+const refContractList = ref(null)
+const refTodoList = ref(null)
+const progressTableRef = ref(null)
+const timerScroll = ref(null)
+const progressTableScrollTimer = ref(null)
+const isTableScrolling = ref(false)
+const tableScrollTimeout = ref(null)
+const tableRowRefs = ref([])
+const rowsUnderHeader = ref(new Set())
+
+const chartStylePie = {
+ width: '100%',
+ height: '100%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+}
+const materialPieSeries = ref([
+ {
+ type: 'pie',
+ radius: ['0%', '90%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderColor: '#fff',
+ borderWidth: 0
+ },
+ label: {
+ show: false
+ },
+ data: []
+ }
+])
+const pieLegend = reactive({
+ show: false,
+})
+const sum = ref(0)
+const totalStaff = ref(0)
+const totalCustomers = ref(0)
+const totalSuppliers = ref(0)
+const yny = ref(0)
+const chain = ref(0)
+const equipmentNum = ref(0)
+const equipmentRepair = ref(0)
+const equipmentMaintain = ref(0)
+const totalMeasuring = ref(0)
+const pieTooltip = reactive({
+ trigger: 'item',
+ formatter: function (params) {
+ // 鍔ㄦ�佺敓鎴愭彁绀轰俊鎭紝鍩轰簬鏁版嵁椤圭殑 name 灞炴��
+ const description = params.name === '鏈湀鍥炴閲戦' ? '鏈湀鍥炴閲戦' : '搴旀敹娆鹃噾棰�';
+ return `<div style="color: #B8C8E0">${description} ${params.value}鍏� ${params.percent}%</div>`;
+ },
+ position: 'right'
+})
+
+const qualityStatisticsObject = ref({
+ supplierNum: 0,
+ processNum: 0,
+ factoryNum: 0,
+})
+
+// 璁㈠崟缁熻瀵硅薄
+const orderStatisticsObject = ref({
+ totalOrderCount: 0,
+ uncompletedOrderCount: 0,
+ partialCompletedOrderCount: 0,
+ completedOrderCount: 0,
+})
+
+// 鍦ㄥ埗鍝佸懆杞粺璁″璞�
+const workInProcessStatistics = ref({
+ totalQuantity: 0,
+ avgTurnoverDays: 0,
+ turnoverEfficiency: 0,
+})
+const chartStyle = {
+ width: '100%',
+ height: '150%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+}
+const barSeries = ref([
+ {
+ name: '搴斾粯閲戦',
+ type: 'bar',
+ data: [],
+ label: {
+ show: true,
+ },
+ itemStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: '#00A4ED' },
+ { offset: 1, color: '#4EE4FF' }
+ ])
+ }
+ },
+ {
+ name: '搴旀敹閲戦',
+ type: 'bar',
+ data: [],
+ label: {
+ show: true,
+ },
+ itemStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: '#537EF5' },
+ { offset: 1, color: '#9061F8' }
+ ])
+ }
+ }
+])
+const radio1 = ref(1)
+const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8']
+const grid = {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+}
+const lineLegend = {
+ show: true,
+ textStyle: { color: '#B8C8E0' },
+ data: ['寮�绁�', '鍥炴']
+}
+const lineSeries = ref([
+ {
+ type: 'line',
+ data: [],
+ label: {
+ show: true
+ },
+ showSymbol: true, // 鏄剧ず鍦嗙偣
+ },
+])
+const tooltipLine = {
+ trigger: 'axis',
+}
+const yAxis2 = ref([
+ {
+ type: 'value',
+ }
+])
+const xAxis2 = ref([
+ {
+ type: 'category',
+ data: [],
+ axisLabel: {
+ interval: 0,
+ formatter: function(value) {
+ return value.replace(/~/g, '\n');
+ },
+ }
+ }
+])
+const barLegend2 = {
+ show: true,
+ textStyle: { color: '#B8C8E0' },
+ data: ['搴斾粯閲戦', '搴旀敹閲戦']
+}
+const barLegend = {
+ show: true,
+ textStyle: { color: '#B8C8E0' },
+ data: ['鍘熸潗鏂欏悎鏍兼暟', '杩囩▼鍚堟牸鏁�', '鍑轰笉鍚堟牸鏁�']
+}
+const barLegend1 = {
+ show: false,
+ textStyle: { color: '#B8C8E0' },
+ data: []
+}
+const barSeries11 = ref([
+ {
+ name: '鐢熶骇璁㈠崟缁熻',
+ type: 'bar',
+ barGap: 0,
+ emphasis: {
+ focus: 'series'
+ },
+ itemStyle: {
+ // 浣跨敤鍑芥暟鏍规嵁鏁版嵁绱㈠紩杩斿洖涓嶅悓棰滆壊
+ color: function(params) {
+ const colorStops = [
+ [
+ { offset: 1, color: '#00A4ED' },
+ { offset: 0, color: '#4EE4FF' }
+ ],
+ [
+ { offset: 1, color: '#3378FF' },
+ { offset: 0, color: '#4E8AFF' }
+ ],
+ [
+ { offset: 1, color: '#FF6B6B' },
+ { offset: 0, color: '#FF8E8E' }
+ ],
+ [
+ { offset: 1, color: '#537EF5' },
+ { offset: 0, color: '#9061F8' }
+ ]
+ ]
+ const stops = colorStops[params.dataIndex] || colorStops[0]
+ return {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: stops
+ }
+ }
+ },
+ data: []
+ }
+])
+const barSeries1 = ref([
+ {
+ name: '鍘熸潗鏂欏悎鏍兼暟',
+ type: 'bar',
+ barGap: 0,
+ emphasis: {
+ focus: 'series'
+ },
+ itemStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ { offset: 1, color: '#00A4ED' },
+ { offset: 0, color: '#4EE4FF' }
+ ]
+ }
+ },
+ data: []
+ },
+ {
+ name: '杩囩▼鍚堟牸鏁�',
+ type: 'bar',
+ emphasis: {
+ focus: 'series'
+ },
+ itemStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ { offset: 1, color: '#3378FF' },
+ { offset: 0, color: '#4E8AFF' }
+ ]
+ }
+ },
+ data: []
+ },
+ {
+ name: '鍑哄巶鍚堟牸鏁�',
+ type: 'bar',
+ emphasis: {
+ focus: 'series'
+ },
+ itemStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ { offset: 1, color: '#537EF5' },
+ { offset: 0, color: '#9061F8' }
+ ]
+ }
+ },
+ data: []
+ },
+])
+const tooltip = {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ },
+ formatter: function (params) {
+ let result = params[0].axisValueLabel + '<br/>';
+ params.forEach(item => {
+ result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>`;
+ });
+ return result;
+ }
+}
+const xAxis = [{
+ type: 'value',
+}]
+const yAxis = [{
+ type: 'category',
+ data: ['搴旀敹搴斾粯缁熻']
+}]
+const xAxis1 = ref([{
+ type: 'category',
+ axisTick: { show: false },
+ axisLabel: { color: '#B8C8E0' },
+ data: []
+}])
+const yAxis1 = [{
+ type: 'value',
+ axisLabel: { color: '#B8C8E0' }
+}]
+const xAxis3 = ref([{
+ type: 'category',
+ axisTick: { show: false },
+ axisLabel: { color: '#B8C8E0' },
+ data: []
+}])
+const yAxis3 = [{
+ type: 'value',
+ axisLabel: { color: '#B8C8E0' }
+}]
+
+// 鍦ㄥ埗鍝佸伐搴忔煴鐘跺浘閰嶇疆
+const workInProcessXAxis = ref([{
+ type: 'category',
+ axisTick: { show: false },
+ axisLabel: { color: '#B8C8E0' },
+ data: []
+}])
+const workInProcessYAxis = [{
+ type: 'value',
+ axisLabel: { color: '#B8C8E0' },
+ name: ''
+}]
+const workInProcessBarLegend = {
+ show: false,
+ textStyle: { color: '#B8C8E0' },
+ data: []
+}
+const workInProcessBarSeries = ref([
+ {
+ name: '鍦ㄥ埗鍝佹暟閲�',
+ type: 'bar',
+ barWidth: 25, // 鍥哄畾鏌辩姸鍥惧搴︿负40px
+ barGap: 0,
+ emphasis: {
+ focus: 'series'
+ },
+ itemStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ { offset: 0, color: '#4EE4FF' },
+ { offset: 1, color: '#00A4ED' }
+ ]
+ }
+ },
+ label: {
+ show: true,
+ position: 'top',
+ color: '#B8C8E0'
+ },
+ data: []
+ }
+])
+
+// 寰呭姙浜嬮」
+const todoList = ref([])
+
+// 鐢熶骇璁㈠崟瀹屾垚杩涘害琛ㄦ牸鏁版嵁
+const progressTableData = ref([])
+
+// 璁$畻瀹屾垚杩涘害鐧惧垎姣�
+const calculateProgress = (item) => {
+ if (!item) return 0
+ // 浼樺厛浣跨敤completionStatus瀛楁
+ if (item.completionStatus !== undefined && item.completionStatus !== null) {
+ const percentage = Number(item.completionStatus)
+ if (isNaN(percentage)) return 0
+ return Math.min(Math.max(Math.round(percentage), 0), 100)
+ }
+ // 濡傛灉娌℃湁completionStatus锛屽垯鏍规嵁瀹屾垚鏁伴噺鍜岄渶姹傛暟閲忚绠�
+ if (!item.quantity || item.quantity === 0) return 0
+ const percentage = (item.completeQuantity || 0) / item.quantity * 100
+ return Math.min(Math.max(Math.round(percentage), 0), 100)
+}
+
+// 鏍规嵁杩涘害鐧惧垎姣旇繑鍥為鑹�
+const progressColor = (percentage) => {
+ const p = percentage || 0
+ if (p < 30) return "#f56c6c"
+ if (p < 50) return "#e6a23c"
+ if (p < 80) return "#409eff"
+ return "#67c23a"
+}
+
+// 璁$畻缂╂斁姣斾緥
+const calculateScale = () => {
+ const container = document.querySelector('.scale-container')
+ if (!container) return
+
+ // 鑾峰彇瀹瑰櫒鐨勫疄闄呭昂瀵�
+ const rect = container.getBoundingClientRect?.()
+ const containerWidth = container.clientWidth || rect?.width || window.innerWidth
+ const containerHeight = container.clientHeight || rect?.height || window.innerHeight
+
+ // 璁$畻瀹介珮缂╂斁姣斾緥锛屽彇杈冨皬鍊间互淇濊瘉鍐呭瀹屾暣鏄剧ず锛堢瓑姣旂缉鏀撅級
+ const scaleX = containerWidth / designWidth
+ const scaleY = containerHeight / designHeight
+ scaleRatio.value = Math.min(scaleX, scaleY)
+
+ // 瑙﹀彂鍥捐〃resize
+ charts.value.forEach(chart => {
+ if (chart && chart.resize) {
+ chart.resize()
+ }
+ })
+}
+
+// 绐楀彛澶у皬鍙樺寲澶勭悊
+const handleResize = () => {
+ // 寤惰繜鎵ц锛岀‘淇滵OM鏇存柊瀹屾垚
+ setTimeout(() => {
+ calculateScale()
+ }, 100)
+}
+
+// 閿�姣佸浘琛ㄥ疄渚�
+const disposeCharts = () => {
+ charts.value.forEach(chart => {
+ if (chart && chart.dispose) {
+ chart.dispose()
+ }
+ })
+ charts.value = []
+}
+// 鍚堝悓閲戦
+const analysisCustomer = () => {
+ analysisCustomerContractAmounts().then((res) => {
+ sum.value = res.data.sum
+ yny.value = res.data.yny
+ chain.value = res.data.chain
+ // 涓烘瘡涓暟鎹」鍒嗛厤闅忔満棰滆壊
+ materialPieSeries.value[0].data = res.data.item.map(item => ({
+ ...item,
+ itemStyle: { color: getRandomColor() }
+ }))
+ })
+}
+// 鍦ㄥ埗鍝佸懆杞粺璁�
+const workInProcessTurnoverInfo = () => {
+ getWorkInProcessTurnover().then((res) => {
+ console.log("鍦ㄥ埗鍝佸懆杞粺璁℃暟鎹�:", res)
+
+ if (!res || !res.data) {
+ console.warn('鍦ㄥ埗鍝佸懆杞粺璁℃暟鎹负绌�')
+ return
+ }
+
+ // 浠庢帴鍙h幏鍙栫粺璁℃暟鎹�
+ workInProcessStatistics.value = {
+ totalQuantity: res.data.totalOrderCount || 0,
+ avgTurnoverDays: res.data.averageTurnoverDays || 0,
+ turnoverEfficiency: res.data.turnoverEfficiency || 0,
+ }
+
+ // 璁剧疆宸ュ簭鏌辩姸鍥炬暟鎹�
+ // X杞达細processDetails (宸ュ簭璇︽儏鏁扮粍)
+ // Y杞达細processQuantityDetails (宸ュ簭鏁伴噺璇︽儏鏁扮粍)
+ if (res.data.processDetails && Array.isArray(res.data.processDetails)) {
+ // 璁剧疆X杞存暟鎹紙宸ュ簭鍚嶇О锛�
+ workInProcessXAxis.value[0].data = res.data.processDetails
+ } else {
+ workInProcessXAxis.value[0].data = []
+ }
+
+ if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) {
+ // 璁剧疆Y杞存暟鎹紙鍦ㄥ埗鍝佹暟閲忥級
+ workInProcessBarSeries.value[0].data = res.data.processQuantityDetails
+ } else {
+ workInProcessBarSeries.value[0].data = []
+ }
+ }).catch((error) => {
+ console.error('鑾峰彇鍦ㄥ埗鍝佸懆杞粺璁″け璐�:', error)
+ })
+}
+// 璐ㄦ缁熻
+const qualityStatisticsInfo = () => {
+ qualityStatistics().then((res) => {
+ res.data.item.forEach(item => {
+ xAxis1.value[0].data.push(item.date)
+ barSeries1.value[0].data.push(item.supplierNum)
+ barSeries1.value[1].data.push(item.processNum)
+ barSeries1.value[2].data.push(item.factoryNum)
+ })
+ qualityStatisticsObject.value.supplierNum = res.data.supplierNum
+ qualityStatisticsObject.value.processNum = res.data.processNum
+ qualityStatisticsObject.value.factoryNum = res.data.factoryNum
+ })
+}
+// 鍚勭敓浜ц鍗曠殑瀹屾垚杩涘害缁熻
+const progressStatisticsInfo = () => {
+ // 浠庣粺璁℃帴鍙h幏鍙栫粺璁℃暟鎹�
+ getProgressStatistics().then((res) => {
+ console.log("鐢熶骇璁㈠崟瀹屾垚杩涘害缁熻鏁版嵁:", res)
+
+ if (!res || !res.data) {
+ console.warn('鐢熶骇璁㈠崟瀹屾垚杩涘害缁熻鏁版嵁涓虹┖')
+ return
+ }
+
+ // 浠庢帴鍙h幏鍙栫粺璁℃暟鎹�
+ orderStatisticsObject.value = {
+ totalOrderCount: res.data.totalOrderCount || 0,
+ uncompletedOrderCount: res.data.uncompletedOrderCount || 0,
+ partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0,
+ completedOrderCount: res.data.completedOrderCount || 0
+ }
+ progressTableData.value = res.data.completedOrderDetails || []
+ // 閲嶇疆琛屽紩鐢�
+ tableRowRefs.value = []
+ rowsUnderHeader.value.clear()
+
+ // 鍦ㄨ幏鍙栧埌鏁版嵁鍚庯紝鍒濆鍖栨粴鍔ㄥ姛鑳�
+ nextTick(() => {
+ initProgressTableScroll()
+ })
+ }).catch((error) => {
+ console.error('鑾峰彇鐢熶骇璁㈠崟瀹屾垚杩涘害缁熻澶辫触:', error)
+ })
+}
+// 璐㈠姟缁熻
+// const accountStatisticsInfo = () => {
+// listPageAnalysis().then((res) => {
+// xAxis3.value[0].data = res.data.days
+// barSeries11.value[0].data = res.data.totalIncome
+// })
+// }
+const getNum = () => {
+ const params = {
+ pageNum: -1,
+ pageSize: -1,
+ }
+ staffOnJobListPage({...params, staffState: 1}).then(res => {
+ totalStaff.value = res.data.total
+ })
+ listCustomer(params).then((res) => {
+ totalCustomers.value = res.total;
+ });
+ listSupplier(params).then((res) => {
+ totalSuppliers.value = res.data.total
+ });
+}
+const getLedgerNum = () => {
+ const params = {
+ pageNum: -1,
+ pageSize: -1,
+ }
+ getLedgerPage(params).then((res) => {
+ equipmentNum.value = res.data.total
+ });
+ getRepairPage({...params, status:0}).then((res) => {
+ equipmentRepair.value = res.data.total
+ });
+ getUpkeepPage({...params, status:0}).then((res) => {
+ equipmentMaintain.value = res.data.total
+ });
+ measuringInstrumentListPage(params).then((res) => {
+ totalMeasuring.value = res.data.total
+ });
+}
+// 寰呭姙浜嬮」
+const todoInfoS = () => {
+ homeTodos().then((res) => {
+ todoList.value = res.data
+ // 鍦ㄨ幏鍙栧埌寰呭姙浜嬮」鏁版嵁鍚庯紝鍒濆鍖栨粴鍔ㄥ姛鑳�
+ nextTick(() => {
+ initTodoListScroll()
+ })
+ })
+}
+// 搴斾粯搴旀敹缁熻
+const statisticsReceivable = (type) => {
+ statisticsReceivablePayable({type: radio1.value}).then((res) => {
+ // 璁剧疆搴斾粯閲戦鏁版嵁
+ barSeries.value[0].data = [
+ { value: res.data.payableMoney }
+ ]
+ // 璁剧疆搴旀敹閲戦鏁版嵁
+ barSeries.value[1].data = [
+ { value: res.data.receivableMoney }
+ ]
+ })
+}
+const getAmountHalfYearNum = async () => {
+ const res = await getAmountHalfYear()
+ console.log(res)
+ const monthName = []
+ const receiptAmount = []
+ const invoiceAmount = []
+ res.data.forEach(item => {
+ monthName.push(item.month)
+ receiptAmount.push(item.receiptAmount)
+ invoiceAmount.push(item.invoiceAmount)
+ })
+ // 姝g‘鍝嶅簲寮忚祴鍊硷細鍒涘缓鏂扮殑 xAxis 鍜� series 瀵硅薄
+ xAxis2.value[0].data = monthName
+ xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~'));
+ lineSeries.value = [
+ {
+ name: '寮�绁�',
+ type: 'line',
+ data: receiptAmount,
+ stack: 'Total',
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ {
+ offset: 0,
+ color: 'rgba(131, 207, 255, 1)'
+ },
+ {
+ offset: 1,
+ color: 'rgba(186, 228, 255, 1)'
+ }
+ ])
+ },
+ itemStyle: {
+ color: '#2D99FF',
+ borderColor: '#2D99FF'
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ lineStyle: {
+ width: 0
+ },
+ showSymbol: true,
+ },
+ {
+ name: '鍥炴',
+ type: 'line',
+ data: invoiceAmount,
+ stack: 'Total',
+ lineStyle: {
+ width: 0
+ },
+ itemStyle: {
+ color: '#83CFFF',
+ borderColor: '#83CFFF'
+ },
+ showSymbol: true,
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ {
+ offset: 0,
+ color: 'rgba(54, 153, 255, 1)'
+ },
+ {
+ offset: 1,
+ color: 'rgba(89, 169, 254, 1)'
+ }
+ ])
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ }
+ ]
+}
+
+// 鑷姩杞崲鍛ㄣ�佹湀銆佸搴︾殑瀹氭椂鍣�
+const autoSwitchTimer = ref(null)
+
+// 璁剧疆琛屽紩鐢�
+const setRowRef = (el, index) => {
+ if (el) {
+ tableRowRefs.value[index] = el
+ }
+}
+
+// 鍒ゆ柇琛屾槸鍚﹀湪琛ㄥご涓嬫柟
+const isRowUnderHeader = (index) => {
+ return rowsUnderHeader.value.has(index)
+}
+
+// 澶勭悊琛ㄦ牸婊氬姩浜嬩欢
+const handleTableScroll = () => {
+ const tableContainer = progressTableRef.value
+ if (!tableContainer) return
+
+ const thead = tableContainer.querySelector('thead')
+ if (!thead) return
+
+ const theadHeight = thead.offsetHeight
+ const containerRect = tableContainer.getBoundingClientRect()
+ const containerTop = containerRect.top
+ const theadBottom = containerTop + theadHeight
+
+ // 娓呯┖涔嬪墠鐨勮褰�
+ rowsUnderHeader.value.clear()
+
+ // 妫�鏌ユ瘡涓�琛屾槸鍚﹀湪琛ㄥご涓嬫柟锛堣琛ㄥご閬尅锛�
+ tableRowRefs.value.forEach((row, index) => {
+ if (row) {
+ const rowRect = row.getBoundingClientRect()
+ const rowTop = rowRect.top
+ const rowBottom = rowRect.bottom
+
+ // 濡傛灉琛屼笌琛ㄥご鏈夐噸鍙狅紙琛屽湪琛ㄥご涓嬫柟琚伄鎸★級
+ // 琛岀殑椤堕儴鍦ㄨ〃澶村簳閮ㄤ笅鏂癸紝浣嗚鐨勫簳閮ㄥ湪琛ㄥご搴曢儴涓婃柟锛岃鏄庤閬尅
+ if (rowTop < theadBottom && rowBottom > containerTop) {
+ rowsUnderHeader.value.add(index)
+ }
+ }
+ })
+
+ // 娓呴櫎涔嬪墠鐨勫畾鏃跺櫒
+ if (tableScrollTimeout.value) {
+ clearTimeout(tableScrollTimeout.value)
+ }
+
+ // 婊氬姩鍋滄鍚庢竻绌烘贰鍖栨爣璁�
+ tableScrollTimeout.value = setTimeout(() => {
+ rowsUnderHeader.value.clear()
+ }, 150)
+}
+
+// 鍒濆鍖栫敓浜ц鍗曡繘搴﹁〃鏍兼粴鍔ㄥ姛鑳�
+const initProgressTableScroll = () => {
+ const tableContainer = progressTableRef.value
+ if (!tableContainer) return
+
+ // 娓呯悊涔嬪墠鐨勬粴鍔ㄥ姩鐢诲拰瀹氭椂鍣�
+ if (progressTableScrollTimer.value) {
+ cancelAnimationFrame(progressTableScrollTimer.value)
+ progressTableScrollTimer.value = null
+ }
+ if (tableContainer._pauseTimer) {
+ clearInterval(tableContainer._pauseTimer)
+ tableContainer._pauseTimer = null
+ }
+
+ const tbody = tableContainer.querySelector('tbody')
+ if (!tbody) return
+
+ // 娓呯悊涔嬪墠鍙兘瀛樺湪鐨勫厠闅嗚锛堜繚鐣欏師濮嬫暟鎹锛�
+ // 鍘熷鏁版嵁琛岀殑鏁伴噺搴旇绛変簬 progressTableData.value.length
+ const originalCount = progressTableData.value.length
+ const allRows = Array.from(tbody.querySelectorAll('tr'))
+ if (allRows.length > originalCount) {
+ // 绉婚櫎鎵�鏈夎秴杩囧師濮嬫暟閲忕殑琛岋紙杩欎簺鏄厠闅嗙殑琛岋級
+ for (let i = originalCount; i < allRows.length; i++) {
+ allRows[i].remove()
+ }
+ }
+
+ const scrollItems = Array.from(tbody.querySelectorAll('tr'))
+ if (scrollItems.length === 0) return
+
+ // 鑾峰彇鍘熷鏁版嵁椤规暟閲�
+ const originalItemCount = scrollItems.length
+
+ // 璁$畻瀹瑰櫒楂樺害鍜岃〃澶撮珮搴�
+ const thead = tableContainer.querySelector('thead')
+ const theadHeight = thead ? thead.offsetHeight : 40
+ const containerHeight = tableContainer.clientHeight
+ const visibleHeight = containerHeight - theadHeight
+
+ // 璁$畻鍘熷鏁版嵁鐨勬�婚珮搴�
+ const itemHeight = scrollItems[0]?.offsetHeight || 40
+ const totalContentHeight = itemHeight * originalItemCount
+
+ // 濡傛灉鏁版嵁閲忎笉澶燂紝瀹瑰櫒鍙互瀹屽叏鏄剧ず鎵�鏈夋暟鎹紝灏变笉闇�瑕佹粴鍔ㄥ拰鍏嬮殕
+ if (totalContentHeight <= visibleHeight) {
+ // 鏁版嵁閲忓皯锛屼笉闇�瑕佹粴鍔紝鐩存帴杩斿洖
+ return
+ }
+
+ // 鏁版嵁閲忚冻澶燂紝闇�瑕佹粴鍔紝杩涜鍏嬮殕浠ュ疄鐜版棤缂濇粴鍔�
+ const cloneCount = Math.ceil(visibleHeight / itemHeight) + 2
+
+ // 鍏嬮殕鍓嶅嚑涓」鐩苟娣诲姞鍒板垪琛ㄦ湯灏撅紝瀹炵幇鏃犵紳婊氬姩
+ for (let i = 0; i < cloneCount; i++) {
+ const clone = scrollItems[i % originalItemCount].cloneNode(true)
+ tbody.appendChild(clone)
+ }
+
+ let scrollPosition = 0
+ const scrollSpeed = 1.5
+ const pauseTime = 3000
+ let isPaused = false
+ let lastTimestamp = 0
+
+ // 杩炵画婊氬姩鍔ㄧ敾鍑芥暟
+ function scrollAnimation(timestamp) {
+ if (!lastTimestamp) lastTimestamp = timestamp
+ const deltaTime = timestamp - lastTimestamp
+ lastTimestamp = timestamp
+
+ if (!isPaused) {
+ scrollPosition += scrollSpeed * (deltaTime / 16)
+
+ // 璁$畻鏈�澶ф粴鍔ㄤ綅缃紙鍘熷鍐呭鐨勯珮搴︼級
+ const maxScroll = itemHeight * originalItemCount
+
+ // 褰撴粴鍔ㄨ秴杩囧師濮嬪唴瀹归暱搴︽椂锛岄噸缃綅缃疄鐜版棤缂濇粴鍔�
+ if (scrollPosition >= maxScroll) {
+ scrollPosition = 0
+ tableContainer.scrollTop = 0
+ } else {
+ tableContainer.scrollTop = scrollPosition
+ }
+ }
+
+ progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation)
+ }
+
+ // 鍚姩婊氬姩鍔ㄧ敾
+ progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation)
+
+ // 璁剧疆婊氬姩-鏆傚仠-婊氬姩鐨勫惊鐜晥鏋�
+ const pauseTimer = setInterval(() => {
+ isPaused = !isPaused
+ }, pauseTime)
+
+ // 娓呯悊瀹氭椂鍣�
+ tableContainer._pauseTimer = pauseTimer
+}
+
+// 鍒濆鍖栧緟鍔炰簨椤瑰垪琛ㄦ粴鍔ㄥ姛鑳�
+const initTodoListScroll = () => {
+ const todoList = refTodoList.value
+ // 寮哄埗鍚敤婊氬姩锛屼笉妫�鏌ヤ换浣曟潯浠�
+ if (todoList) {
+ // 鍒涘缓涓�涓厠闅嗛」锛岀敤浜庡疄鐜版棤缂濇粴鍔�
+ const scrollItems = Array.from(todoList.querySelectorAll('li'))
+ if (scrollItems.length > 0) {
+ // 纭繚鏈夎冻澶熺殑椤圭洰鐢ㄤ簬婊氬姩
+ // 濡傛灉椤圭洰澶皯锛屽澶嶅埗鍑犳浠ョ‘淇濇粴鍔ㄦ晥鏋�
+ if (scrollItems.length < 4) {
+ const originalItems = [...scrollItems]
+ for (let i = 0; i < 4; i++) {
+ originalItems.forEach(item => {
+ const clone = item.cloneNode(true)
+ todoList.appendChild(clone)
+ })
+ }
+ // 閲嶆柊鑾峰彇鎵�鏈夐」鐩�
+ scrollItems.push(...Array.from(todoList.querySelectorAll('li')).slice(scrollItems.length));
+ }
+ const itemHeight = scrollItems[0]?.offsetHeight || 0
+ const containerHeight = todoList.clientHeight
+ const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
+
+ // 鍏嬮殕鍓嶅嚑涓」鐩苟娣诲姞鍒板垪琛ㄦ湯灏撅紝瀹炵幇鏃犵紳婊氬姩
+ for (let i = 0; i < cloneCount; i++) {
+ const clone = scrollItems[i % scrollItems.length].cloneNode(true)
+ todoList.appendChild(clone)
+ }
+
+ let scrollPosition = 0
+ const scrollSpeed = 1.5 // 澧炲姞婊氬姩閫熷害锛屼娇婊氬姩鏇村姞鏄庢樉
+ const pauseTime = 3000 // 婊氬姩鏆傚仠鏃堕棿
+ let isPaused = false
+ let lastTimestamp = 0
+
+ // 杩炵画婊氬姩鍔ㄧ敾鍑芥暟
+ function scrollAnimation(timestamp) {
+ if (!lastTimestamp) lastTimestamp = timestamp
+ const deltaTime = timestamp - lastTimestamp
+ lastTimestamp = timestamp
+
+ if (!isPaused) {
+ scrollPosition += scrollSpeed * (deltaTime / 16) // 鏍囧噯鍖栦负60fps鐨勯�熷害
+
+ // 褰撴粴鍔ㄨ秴杩囧師濮嬪唴瀹归暱搴︽椂锛岄噸缃綅缃疄鐜版棤缂濇粴鍔�
+ const maxScroll = Math.max(todoList.scrollHeight - containerHeight - cloneCount * itemHeight, itemHeight * scrollItems.length)
+ if (scrollPosition >= maxScroll) {
+ scrollPosition = 0
+ todoList.scrollTop = 0
+ } else {
+ todoList.scrollTop = scrollPosition
+ }
+ }
+
+ todoList._animationFrame = requestAnimationFrame(scrollAnimation)
+ }
+
+ // 鍚姩婊氬姩鍔ㄧ敾
+ todoList._animationFrame = requestAnimationFrame(scrollAnimation)
+
+ // 璁剧疆婊氬姩-鏆傚仠-婊氬姩鐨勫惊鐜晥鏋�
+ const pauseTimer = setInterval(() => {
+ isPaused = !isPaused
+ }, pauseTime)
+
+ // 娓呯悊瀹氭椂鍣�
+ todoList._pauseTimer = pauseTimer
+ }
+ }
+}
+const getRandomColor = () => {
+ // 鐢熸垚娴呰壊锛歊銆丟銆丅 鍒嗛噺閮藉湪 150-255 涔嬮棿
+ const r = Math.floor(Math.random() * 106) + 150; // 150-255
+ const g = Math.floor(Math.random() * 106) + 150; // 150-255
+ const b = Math.floor(Math.random() * 106) + 150; // 150-255
+ // 灏� RGB 杞崲涓哄崄鍏繘鍒堕鑹�
+ return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
+}
+
+// 鏇存柊鏃堕棿
+const updateTime = () => {
+ const now = new Date()
+ currentTime.value = now.toLocaleTimeString('zh-CN', { hour12: false })
+ currentDate.value = now.toLocaleDateString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ weekday: 'long'
+ })
+}
+
+// 鍒濆鍖栨椂闂�
+const initTime = () => {
+ updateTime()
+ timer.value = setInterval(updateTime, 1000)
+}
+// 鍏ㄥ睆鍔熻兘瀹炵幇 - 閽堝scale-container鍏冪礌
+const toggleFullscreen = () => {
+ const element = document.querySelector('.scale-container')
+
+ if (!element) return
+
+ if (!isFullscreen.value) {
+ if (element.requestFullscreen) {
+ element.requestFullscreen()
+ } else if (element.webkitRequestFullscreen) {
+ element.webkitRequestFullscreen()
+ } else if (element.msRequestFullscreen) {
+ element.msRequestFullscreen()
+ }
+ } else {
+ if (document.exitFullscreen) {
+ document.exitFullscreen()
+ } else if (document.webkitExitFullscreen) {
+ document.webkitExitFullscreen()
+ } else if (document.msExitFullscreen) {
+ document.msExitFullscreen()
+ }
+ }
+}
+
+// 鐩戝惉鍏ㄥ睆鍙樺寲浜嬩欢
+const handleFullscreenChange = () => {
+ const fullscreenElement = document.fullscreenElement ||
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement
+ isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('scale-container')
+
+ // 鍏ㄥ睆鐘舵�佸彉鍖栨椂锛屽欢杩熼噸鏂拌绠楃缉鏀炬瘮渚嬶紙纭繚DOM鏇存柊瀹屾垚锛�
+ setTimeout(() => {
+ calculateScale()
+ }, 200)
+}
+
+// 鐢熷懡鍛ㄦ湡閽╁瓙
+onMounted(() => {
+ initTime()
+ // 浣跨敤nextTick纭繚DOM瀹屽叏娓叉煋鍚庡啀鍒濆鍖栧浘琛�
+ nextTick(() => {
+ // 璁$畻鍒濆缂╂斁姣斾緥
+ calculateScale()
+
+ // 鍒濆鍖朼utofit鑷�傚簲锛堝鏋滈渶瑕佷繚鐣檃utofit锛屽彲浠ヤ繚鐣欙紝浣嗕富瑕佺缉鏀剧敱scale-container鎺у埗锛�
+ // autofit.init({ dh: 800, dw: 1280, el: '.data-dashboard', resize: true }, false)
+
+ // 娣诲姞鑷姩婊氬姩鍔ㄧ敾鏁堟灉 - 瀹㈡埛淇℃伅鍒楄〃
+ const contractList = refContractList.value
+ if (contractList && contractList.scrollHeight > contractList.clientHeight) {
+ // 鍒涘缓涓�涓厠闅嗛」锛岀敤浜庡疄鐜版棤缂濇粴鍔�
+ const scrollItems = Array.from(contractList.querySelectorAll('li'))
+ const itemHeight = scrollItems[0]?.offsetHeight || 0
+ const containerHeight = contractList.clientHeight
+ const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
+
+ // 鍏嬮殕鍓嶅嚑涓」鐩苟娣诲姞鍒板垪琛ㄦ湯灏撅紝瀹炵幇鏃犵紳婊氬姩
+ for (let i = 0; i < cloneCount; i++) {
+ const clone = scrollItems[i % scrollItems.length].cloneNode(true)
+ contractList.appendChild(clone)
+ }
+
+ let scrollPosition = 0
+ const scrollSpeed = 1.5 // 澧炲姞婊氬姩閫熷害锛屼娇婊氬姩鏇村姞鏄庢樉
+ const pauseTime = 3000 // 婊氬姩鏆傚仠鏃堕棿
+ let isPaused = false
+ let lastTimestamp = 0
+
+ // 杩炵画婊氬姩鍔ㄧ敾鍑芥暟
+ function scrollAnimation(timestamp) {
+ if (!lastTimestamp) lastTimestamp = timestamp
+ const deltaTime = timestamp - lastTimestamp
+ lastTimestamp = timestamp
+
+ if (!isPaused) {
+ scrollPosition += scrollSpeed * (deltaTime / 16) // 鏍囧噯鍖栦负60fps鐨勯�熷害
+
+ // 褰撴粴鍔ㄨ秴杩囧師濮嬪唴瀹归暱搴︽椂锛岄噸缃綅缃疄鐜版棤缂濇粴鍔�
+ if (scrollPosition >= contractList.scrollHeight - containerHeight - cloneCount * itemHeight) {
+ scrollPosition = 0
+ contractList.scrollTop = 0
+ } else {
+ contractList.scrollTop = scrollPosition
+ }
+ }
+
+ timerScroll.value = requestAnimationFrame(scrollAnimation)
+ }
+
+ // 鍚姩婊氬姩鍔ㄧ敾
+ timerScroll.value = requestAnimationFrame(scrollAnimation)
+
+ // 璁剧疆婊氬姩-鏆傚仠-婊氬姩鐨勫惊鐜晥鏋�
+ const pauseTimer = setInterval(() => {
+ isPaused = !isPaused
+ }, pauseTime)
+
+ // 娓呯悊瀹氭椂鍣�
+ contractList._pauseTimer = pauseTimer
+ }
+
+ // 寰呭姙浜嬮」鍒楄〃婊氬姩鍔熻兘宸茬Щ鑷硉odoInfoS鍑芥暟涓紝鍦ㄨ幏鍙栨暟鎹悗鍒濆鍖�
+ })
+
+ window.addEventListener('resize', handleResize)
+ window.addEventListener('fullscreenchange', handleFullscreenChange)
+ window.addEventListener('webkitfullscreenchange', handleFullscreenChange)
+ window.addEventListener('MSFullscreenChange', handleFullscreenChange)
+ analysisCustomer()
+ workInProcessTurnoverInfo()
+ qualityStatisticsInfo()
+ // accountStatisticsInfo()
+ progressStatisticsInfo()
+ getNum()
+ getLedgerNum()
+ todoInfoS()
+ statisticsReceivable()
+ getAmountHalfYearNum()
+
+ // 璁剧疆鑷姩杞崲鍛ㄣ�佹湀銆佸搴︾殑瀹氭椂鍣紝姣�10绉掑垏鎹竴娆�
+ autoSwitchTimer.value = setInterval(() => {
+ // 寰幆鍒囨崲锛�1(鍛�) -> 2(鏈�) -> 3(瀛e害) -> 1(鍛�)
+ radio1.value = radio1.value === 3 ? 1 : radio1.value + 1
+ statisticsReceivable()
+ }, 10000) // 10绉掑垏鎹竴娆�
+})
+
+onBeforeUnmount(() => {
+ if (timer.value) {
+ clearInterval(timer.value)
+ }
+ if (timerScroll.value) {
+ cancelAnimationFrame(timerScroll.value)
+ }
+ // 娓呯悊婊氬姩鍒楄〃鐨勬殏鍋滃畾鏃跺櫒
+ const contractList = refContractList.value
+ if (contractList && contractList._pauseTimer) {
+ clearInterval(contractList._pauseTimer)
+ }
+
+ // 娓呯悊寰呭姙浜嬮」鍒楄〃鐨勫姩鐢诲拰瀹氭椂鍣�
+ const todoList = refTodoList.value
+ if (todoList) {
+ if (todoList._animationFrame) {
+ cancelAnimationFrame(todoList._animationFrame)
+ todoList._animationFrame = null
+ }
+ if (todoList._pauseTimer) {
+ clearInterval(todoList._pauseTimer)
+ todoList._pauseTimer = null
+ }
+ }
+
+ // 娓呯悊鐢熶骇璁㈠崟杩涘害琛ㄦ牸鐨勫姩鐢诲拰瀹氭椂鍣�
+ const progressTable = progressTableRef.value
+ if (progressTable) {
+ if (progressTableScrollTimer.value) {
+ cancelAnimationFrame(progressTableScrollTimer.value)
+ progressTableScrollTimer.value = null
+ }
+ if (progressTable._pauseTimer) {
+ clearInterval(progressTable._pauseTimer)
+ progressTable._pauseTimer = null
+ }
+ }
+
+ // 娓呯悊琛ㄦ牸婊氬姩瀹氭椂鍣�
+ if (tableScrollTimeout.value) {
+ clearTimeout(tableScrollTimeout.value)
+ tableScrollTimeout.value = null
+ }
+
+ // 娓呯悊鑷姩杞崲鍛ㄣ�佹湀銆佸搴︾殑瀹氭椂鍣�
+ if (autoSwitchTimer.value) {
+ clearInterval(autoSwitchTimer.value)
+ autoSwitchTimer.value = null
+ }
+
+ window.removeEventListener('resize', handleResize)
+ window.removeEventListener('fullscreenchange', handleFullscreenChange)
+ window.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
+ window.removeEventListener('MSFullscreenChange', handleFullscreenChange)
+ // 绉婚櫎鎴戜滑娣诲姞鐨刟utofit鍔ㄦ�佽皟鏁寸洃鍚櫒
+ if (window._autofitUpdateHandler) {
+ window.removeEventListener('resize', window._autofitUpdateHandler)
+ delete window._autofitUpdateHandler
+ }
+ disposeCharts()
+ // 鍏抽棴autofit
+ autofit.off()
+})
+</script>
+
+<style scoped>
+/* 澶栭儴缂╂斁瀹瑰櫒 - 鍗犳嵁鏁翠釜瑙嗗彛 */
+.scale-container {
+ position: relative;
+ width: 100%;
+ /* 椤甸潰鍦ㄥ父瑙勫竷灞�涓嬶紙鏈夐《鏍忥級榛樿鍑忓幓 84px锛岄伩鍏嶅唴瀹硅瑁佸垏 */
+ height: calc(100vh - 84px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #000;
+ overflow: hidden;
+}
+
+/* 鍐呴儴鍐呭鍖哄煙 - 鍥哄畾璁捐灏哄 */
+.data-dashboard {
+ position: relative;
+ width: 1920px;
+ height: 1080px;
+ background-image: url("@/assets/BI/backImage@2x.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ transform-origin: center center;
+}
+
+/* 鍏ㄥ睆鐘舵�佺殑鏍峰紡 - 浣滅敤浜巗cale-container */
+.scale-container:fullscreen {
+ width: 100vw;
+ height: 100vh;
+ margin: 0;
+ padding: 0;
+ background-color: #000;
+ z-index: 9999;
+}
+
+/* Webkit娴忚鍣ㄥ墠缂� */
+.scale-container:-webkit-full-screen {
+ width: 100vw;
+ height: 100vh;
+ margin: 0;
+ padding: 0;
+ background-color: #000;
+ z-index: 9999;
+}
+
+/* MS娴忚鍣ㄥ墠缂� */
+.scale-container:-ms-fullscreen {
+ width: 100vw;
+ height: 100vh;
+ margin: 0;
+ padding: 0;
+ background-color: #000;
+ z-index: 9999;
+}
+
+
+.dashboard-header {
+ position: relative;
+ z-index: 1;
+ height: 86px;
+ background-image: url("@/assets/BI/biaoti.png");
+ background-size: cover;
+ background-repeat: no-repeat;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.factory-name {
+ font-weight: 600;
+font-size: 52px;
+color: #FFFFFF;
+top: 16px;
+position: absolute;
+}
+
+.fullscreen-btn {
+ position: absolute;
+ top: 10px;
+ left: 20px;
+ width: 40px;
+ height: 40px;
+ background: rgba(0, 20, 60, 0.8);
+ border: 1px solid rgba(0, 212, 255, 0.3);
+ border-radius: 6px;
+ color: #00d4ff;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s;
+ z-index: 10000;
+}
+
+.fullscreen-btn:hover {
+ background: rgba(0, 30, 90, 0.9);
+ border-color: rgba(0, 212, 255, 0.5);
+}
+
+.dashboard-content {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ gap: 30px;
+ padding: 0 30px;
+ height: calc(100% - 86px);
+ overflow: hidden;
+}
+
+/* 纭繚鍚勯潰鏉胯兘澶熸纭樉绀� */
+.left-panel, .center-panel, .right-panel {
+ overflow: hidden;
+}
+
+.left-panel,
+.right-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ width: 520px;
+}
+
+.center-panel {
+ flex: 1.5;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+.panel-item-customers {
+ border: 1px solid #1A58B0;
+ padding: 18px;
+ width: 100%;
+ height: 540px;
+}
+.panel-title-second {
+ height: 60px;
+ display: flex;
+ gap: 12px;
+ margin-bottom: 20px;
+ align-items: center;
+}
+.quality-cards {
+ display: flex;
+ gap: 12px;
+ width: 100%;
+ height: 54px;
+ justify-content: space-between;
+ align-items: center;
+}
+.quality-cardSec {
+ display: flex;
+}
+.quality-cardTitle {
+ font-weight: 400;
+ font-size: 14px;
+ color: #FFFFFF;
+ display: flex;
+ align-items: flex-start;
+ flex-direction: column;
+}
+.quality-card {
+ width: 80px;
+ height: 60px;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+.quality-card.one {
+ background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
+}
+.quality-card.two {
+ background-image: url("@/assets/BI/guochengyijianicon@2x.png");
+}
+.quality-card.three {
+ background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
+}
+
+/* 璁㈠崟缁熻鍗$墖鏍峰紡 */
+.order-statistics-cards {
+ display: flex;
+ gap: 12px;
+ width: 100%;
+ height: 94px;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.quality-card.four {
+ background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
+}
+
+.quality-card.five {
+ background-image: url("@/assets/BI/guochengyijianicon@2x.png");
+}
+
+.quality-card.six {
+ background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
+}
+
+.quality-card.seven {
+ background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
+}
+.panel-title-icon {
+ width: 60px;
+ height: 60px;
+ background-image: url("@/assets/BI/hetongicon.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.panel-header {
+ background-image: url("@/assets/BI/kehuhetongback@2x.png");
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.panel-title {
+ width: 100%;
+ font-weight: 500;
+ font-size: 16px;
+ color: #D9ECFF;
+ padding-left: 46px;
+ line-height: 36px;
+}
+.total-customers {
+ background-image: url("@/assets/BI/hetongjineback@2x.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 90%;
+ height: 60px;
+ display: flex;
+ align-items: center;
+ padding: 0 20px;
+ gap: 20px;
+}
+
+.total-customers .label {
+ font-weight: 500;
+ font-size: 16px;
+ color: #FFFFFF;
+}
+
+.total-customers .value {
+ font-weight: 500;
+ font-size: 40px;
+ background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.contract-list {
+ margin-top: 16px;
+ font-size: 14px;
+ color: #666;
+ list-style: none;
+ padding: 0;
+ height: 82%;
+ overflow-y: auto;
+ width: 460px;
+ /* 闅愯棌婊氬姩鏉′絾淇濈暀婊氬姩鍔熻兘 */
+ scrollbar-width: none; /* Firefox */
+ -ms-overflow-style: none; /* IE鍜孍dge */
+}
+
+/* Chrome銆丼afari鍜孫pera */
+.contract-list::-webkit-scrollbar {
+ display: none;
+}
+.line {
+ position: relative;
+ width: 230px;
+}
+.line::after {
+ content: '';
+ position: absolute;
+ right: 2px;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background-color: #C9C5C5;
+ border-radius: 2px;
+}
+.contract-list li {
+ margin-top: 10px;
+}
+.stats-cards {
+ display: flex;
+ gap: 30px;
+}
+
+.stat-card {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ background-image: url("@/assets/BI/border@2x.png");
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+ height: 142px;
+}
+
+.card-icon {
+ width: 100px;
+ height: 100px;
+ margin: 20px 20px 0 10px;
+}
+
+.card-content {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.card-value {
+ font-weight: 500;
+ font-size: 40px;
+ background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.card-label {
+ font-weight: 400;
+ font-size: 19px;
+ color: rgba(208,231,255,0.7);
+}
+
+.equipment-stats {
+ border: 1px solid #1A58B0;
+ padding: 18px;
+ height: 240px;
+}
+.equipment-header {
+ font-weight: 500;
+ font-size: 21px;
+ display: flex;
+ border-bottom: 1px solid;
+ border-image: linear-gradient( 270deg, rgba(0,126,255,0) 0%, rgba(0,126,255,0.4549) 35%, #007EFF 78%, #007EFF 100%) 1;
+ padding-bottom: 2px;
+}
+.equipment-title {
+ font-weight: 500;
+ font-size: 21px;
+ background: linear-gradient(360deg, #056DFF 0%, #43E8FC 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ line-height: 50px;
+}
+.equipment-icon {
+ width: 50px;
+ height: 50px;
+}
+.equipment-items {
+ display: flex;
+ justify-content: space-around;
+ gap: 30px;
+}
+
+.equipment-item {
+ text-align: center;
+}
+
+.equipment-value {
+ display: block;
+ font-weight: 500;
+ font-size: 40px;
+ color: #FFFFFF;
+ width: 120px;
+ height: 110px;
+ line-height: 110px;
+ background-image: url("@/assets/BI/shujutongji@2x.png");
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+ margin-bottom: 8px;
+}
+
+.equipment-label {
+ font-weight: 500;
+ font-size: 21px;
+ color: #FFFFFE;
+}
+
+.event-info {
+ background-image: url("@/assets/BI/shijianmingchengbeijing@2x.png");
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+ padding: 20px;
+ height: 186px;
+}
+.event-header {
+ display: flex;
+ align-items: center;
+}
+.event-icon {
+ width: 40px;
+ height: 40px;
+}
+.event-title {
+ font-weight: 500;
+ font-size: 24px;
+ color: #FFFFFE;
+ line-height: 30px;
+}
+.todo-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ height: 120px; /* 鎸夌敤鎴疯姹傝皟鏁撮珮搴� */
+ overflow: hidden;
+ font-size: 15px;
+}
+.todo-list li {
+ border-radius: 8px;
+ margin-bottom: 12px;
+ padding: 12px 40px;
+ height: 74px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.todo-title {
+ font-weight: 400;
+ font-size: 20px;
+ color: #FFFFFE;
+ position: relative;
+}
+.todo-title::before {
+ content: ''; /* 蹇呴渶锛岃〃绀鸿繖閲屾湁涓�涓唴瀹� */
+ position: absolute;
+ left: -10px; /* 瀹氫綅鍒板乏渚� */
+ top: 50%; /* 鍨傜洿灞呬腑 */
+ transform: translateY(-50%); /* 寰皟鍨傜洿灞呬腑 */
+ width: 6px; /* 鍦嗙殑鐩村緞 */
+ height: 6px; /* 鍦嗙殑鐩村緞 */
+ background: #498CEB;
+ border-radius: 50%; /* 璁╁叾鍙樻垚鍦嗗舰 */
+}
+.todo-division {
+ font-weight: 400;
+ font-size: 20px;
+ color: #FFFFFE;
+}
+.todo-time {
+ font-weight: 400;
+ font-size: 20px;
+ color: #FFFFFE;
+}
+.financial-header {
+ background-image: url("@/assets/BI/caiwufenxiback@2x.png");
+ background-size: 100% 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+.financial-title {
+ width: 100%;
+ font-weight: 500;
+ font-size: 16px;
+ color: #D9ECFF;
+ padding-left: 46px;
+ line-height: 36px;
+}
+
+/* 鑷畾涔夊崟閫夋寜閽粍鏍峰紡 */
+.custom-radio-group :deep(.el-radio-button__inner) {
+ background-color: transparent;
+ color: white;
+ border-color: rgba(255, 255, 255, 0.3);
+}
+
+.custom-radio-group :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
+ background-color: rgba(255, 255, 255, 0.2);
+ color: white;
+ border-color: rgba(255, 255, 255, 0.5);
+ box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5);
+}
+
+/* 鐢熶骇璁㈠崟杩涘害琛ㄦ牸鏍峰紡 */
+.progress-table-container {
+ height: 200px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ margin-top: 10px;
+ scrollbar-width: none; /* Firefox */
+ -ms-overflow-style: none; /* IE鍜孍dge */
+}
+
+.progress-table-container::-webkit-scrollbar {
+ display: none; /* Chrome銆丼afari鍜孫pera */
+}
+
+.progress-table {
+ width: 100%;
+ border-collapse: collapse;
+ color: #B8C8E0;
+ font-size: 12px;
+ table-layout: fixed;
+}
+
+.progress-table thead {
+ position: sticky;
+ top: 0;
+ background-color: rgba(26, 88, 176, 0.9);
+ z-index: 10;
+}
+
+.progress-table th {
+ padding: 8px 6px;
+ text-align: left;
+ font-weight: 500;
+ border-bottom: 1px solid rgba(184, 200, 224, 0.3);
+ color: #B8C8E0;
+ font-size: 12px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.progress-table th:nth-child(1) { width: 15%; } /* 鐢熶骇璁㈠崟鍙� */
+.progress-table th:nth-child(2) { width: 15%; } /* 浜у搧鍚嶇О */
+.progress-table th:nth-child(3) { width: 15%; } /* 瑙勬牸 */
+.progress-table th:nth-child(4) { width: 12%; } /* 闇�姹傛暟閲� */
+.progress-table th:nth-child(5) { width: 12%; } /* 瀹屾垚鏁伴噺 */
+.progress-table th:nth-child(6) { width: 31%; } /* 瀹屾垚杩涘害 */
+
+.progress-table td {
+ padding: 8px 6px;
+ border-bottom: 1px solid rgba(184, 200, 224, 0.1);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 12px;
+ transition: opacity 0.3s ease;
+}
+
+.progress-table tbody tr:hover {
+ background-color: rgba(184, 200, 224, 0.1);
+}
+
+.progress-table tbody tr.row-under-header {
+ opacity: 0.5;
+}
+
+/* el-progress 缁勪欢鏍峰紡璋冩暣 */
+.progress-table :deep(.el-progress) {
+ width: 100%;
+}
+
+.progress-table :deep(.el-progress-bar__outer) {
+ background-color: rgba(184, 200, 224, 0.2);
+}
+
+.progress-table :deep(.el-progress__text) {
+ color: #B8C8E0;
+ font-size: 11px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/reportAnalysis/projectProfit/index.vue b/src/views/reportAnalysis/projectProfit/index.vue
index 9584776..f61cbe5 100644
--- a/src/views/reportAnalysis/projectProfit/index.vue
+++ b/src/views/reportAnalysis/projectProfit/index.vue
@@ -1,120 +1,126 @@
<template>
- <div class="app-container">
- <el-form :model="filters" :inline="true" label-width="80px">
- <el-form-item label="瀹㈡埛鍚嶇О">
- <el-input v-model="filters.customerName" placeholder="璇疯緭鍏ュ鎴峰悕绉�" />
- </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">
- <PIMTable
- rowKey="id"
- :column="columns"
- :tableLoading="loading"
- :tableData="dataList"
- :page="{
+ <div class="app-container">
+ <el-form :model="filters" :inline="true" label-width="80px">
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <el-input v-model="filters.customerName" placeholder="璇疯緭鍏ュ鎴峰悕绉�" clearable style="width: 240px"/>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData"> 鎼滅储 </el-button>
+ <el-button @click="resetFilters"> 閲嶇疆 </el-button>
+ <el-button @click="handleOut"> 瀵煎嚭 </el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableLoading="loading"
+ :tableData="dataList"
+ :page="{
current: pagination.currentPage,
size: pagination.pageSize,
- total: pagination.total,
+ total: pagination.total
}"
- :isShowSummary="true"
- :summaryMethod="summaryMethod"
- @pagination="changePage"
- ></PIMTable>
- </div>
- </div>
+ :isShowSummary="true"
+ :summaryMethod="summarizeMainTable"
+ @pagination="changePage"
+ ></PIMTable>
+ </div>
+ </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { getPurchaseList } from "@/api/procurementManagement/projectProfit";
-import { onMounted } from "vue";
-import { summarizeTable } from "@/utils/summarizeTable";
+import { onMounted, getCurrentInstance } from "vue";
+import { ElMessageBox } from "element-plus";
+
+const { proxy } = getCurrentInstance();
defineOptions({
- name: "椤圭洰鍒╂鼎",
+ name: "椤圭洰鍒╂鼎",
});
const {
- loading,
- filters,
- columns,
- dataList,
- pagination,
- getTableData,
- resetFilters,
- onCurrentChange,
+ loading,
+ filters,
+ columns,
+ dataList,
+ pagination,
+ getTableData,
+ resetFilters,
+ onCurrentChange,
} = usePaginationApi(
- getPurchaseList,
- {
- customerName: undefined,
- },
- [
- {
- label: "閿�鍞悎鍚屽彿",
- align: "center",
- prop: "customerContractNo",
- },
- {
- label: "瀹㈡埛鍚嶇О",
- align: "center",
- prop: "customerName",
- },
- {
- label: "椤圭洰鍚嶇О",
- align: "center",
- prop: "projectName",
- },
- {
- label: "鍚堝悓閲戦",
- align: "center",
- prop: "contractAmount",
- },
- {
- label: "閲囪喘閲戦",
- align: "center",
- prop: "purchaseAmount",
- },
- {
- label: "鍒╂鼎",
- align: "center",
- prop: "balance",
- },
- {
- label: "鍒╂鼎鐜�",
- align: "center",
- prop: "balanceRatio",
- },
- {
- label: "澧炲�肩◣",
- align: "center",
- prop: "balanceAmount",
- },
- ]
+ getPurchaseList,
+ {
+ customerName: undefined,
+ },
+ [
+ {
+ label: "閿�鍞悎鍚屽彿",
+ align: "center",
+ prop: "customerContractNo",
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ align: "center",
+ prop: "customerName",
+ },
+ {
+ label: "鍚堝悓閲戦",
+ align: "center",
+ prop: "contractAmount",
+ },
+ {
+ label: "閲囪喘閲戦",
+ align: "center",
+ prop: "purchaseAmount",
+ },
+ {
+ label: "鍒╂鼎",
+ align: "center",
+ prop: "balance",
+ },
+ {
+ label: "鍒╂鼎鐜�",
+ align: "center",
+ prop: "balanceRatio",
+ },
+ ]
);
-const changePage = ({ page }) => {
- pagination.currentPage = page;
- onCurrentChange(page);
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ onCurrentChange(page);
};
-// 鍚堣鏂规硶
-const summaryMethod = (param) => {
- return summarizeTable(
- param,
- ['contractAmount', 'purchaseAmount', 'balance', 'balanceAmount', 'balanceRatio'],
- );
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, ["contractAmount", "purchaseAmount", "balance"]);
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/purchase/report/export", {}, "椤圭洰鍒╂鼎.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
onMounted(() => {
- getTableData();
+ getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
- margin-top: unset;
+ margin-top: unset;
}
</style>
diff --git a/src/views/reportAnalysis/reportManagement.vue b/src/views/reportAnalysis/reportManagement.vue
new file mode 100644
index 0000000..3c87550
--- /dev/null
+++ b/src/views/reportAnalysis/reportManagement.vue
@@ -0,0 +1,733 @@
+<template>
+ <div class="report-management">
+ <!-- 椤甸潰鏍囬 -->
+ <div class="page-header">
+ <h2>鎶ヨ〃绠$悊</h2>
+ <p>鎻愪緵鏍峰搧杩涘害銆佽澶囦娇鐢ㄣ�佹娴嬮」鐩�侀鐢ㄨ褰曠瓑鍚勭被鑷姩缁熻鎶ヨ〃</p>
+ </div>
+
+ <!-- 绛涢�夋潯浠� -->
+ <el-card class="filter-card" shadow="never">
+ <el-form :model="filterForm" inline>
+ <el-form-item label="鏃堕棿鑼冨洿">
+ <el-date-picker
+ v-model="filterForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ @change="handleFilterChange"
+ />
+ </el-form-item>
+ <el-form-item label="鎶ヨ〃绫诲瀷">
+ <el-select v-model="filterForm.reportType" placeholder="璇烽�夋嫨鎶ヨ〃绫诲瀷" @change="handleFilterChange">
+ <el-option label="鏍峰搧杩涘害鎶ヨ〃" value="sample" />
+ <el-option label="璁惧浣跨敤鎶ヨ〃" value="equipment" />
+ <el-option label="妫�娴嬮」鐩姤琛�" value="inspection" />
+ <el-option label="棰嗙敤璁板綍鎶ヨ〃" value="usage" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleFilterChange">鏌ヨ</el-button>
+ <el-button @click="resetFilter">閲嶇疆</el-button>
+ <el-button type="success" @click="exportReport">瀵煎嚭鎶ヨ〃</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 缁熻鍗$墖 -->
+ <div class="statistics-cards">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <el-card class="stat-card" shadow="hover">
+ <div class="stat-content">
+ <div class="stat-icon">
+ <el-icon><Box /></el-icon>
+ </div>
+ <div class="stat-info">
+ <div class="stat-number">{{ statistics.totalSamples }}</div>
+ <div class="stat-label">鎬绘牱鍝佹暟</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stat-card" shadow="hover">
+ <div class="stat-content">
+ <div class="stat-icon">
+ <el-icon><Tools /></el-icon>
+ </div>
+ <div class="stat-info">
+ <div class="stat-number">{{ statistics.activeEquipment }}</div>
+ <div class="stat-label">鍦ㄧ敤璁惧</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stat-card" shadow="hover">
+ <div class="stat-content">
+ <div class="stat-icon">
+ <el-icon><Document /></el-icon>
+ </div>
+ <div class="stat-info">
+ <div class="stat-number">{{ statistics.completedInspections }}</div>
+ <div class="stat-label">宸插畬鎴愭娴�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stat-card" shadow="hover">
+ <div class="stat-content">
+ <div class="stat-icon">
+ <el-icon><ShoppingCart /></el-icon>
+ </div>
+ <div class="stat-info">
+ <div class="stat-number">{{ statistics.totalUsage }}</div>
+ <div class="stat-label">鎬婚鐢ㄦ鏁�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <div class="charts-container">
+ <el-row :gutter="20">
+ <!-- 鏍峰搧杩涘害鍥捐〃 -->
+ <el-col :span="12">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span>鏍峰搧杩涘害缁熻</span>
+ <el-button type="text" @click="refreshSampleChart">鍒锋柊</el-button>
+ </div>
+ </template>
+ <div ref="sampleChartRef" class="chart-container"></div>
+ </el-card>
+ </el-col>
+
+ <!-- 璁惧浣跨敤鍥捐〃 -->
+ <el-col :span="12">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span>璁惧浣跨敤鐜囩粺璁�</span>
+ <el-button type="text" @click="refreshEquipmentChart">鍒锋柊</el-button>
+ </div>
+ </template>
+ <div ref="equipmentChartRef" class="chart-container"></div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <!-- 妫�娴嬮」鐩粺璁� -->
+ <el-col :span="12">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span>妫�娴嬮」鐩垎甯�</span>
+ <el-button type="text" @click="refreshInspectionChart">鍒锋柊</el-button>
+ </div>
+ </template>
+ <div ref="inspectionChartRef" class="chart-container"></div>
+ </el-card>
+ </el-col>
+
+ <!-- 棰嗙敤璁板綍瓒嬪娍 -->
+ <el-col :span="12">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span>棰嗙敤璁板綍瓒嬪娍</span>
+ <el-button type="text" @click="refreshUsageChart">鍒锋柊</el-button>
+ </div>
+ </template>
+ <div ref="usageChartRef" class="chart-container"></div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 璇︾粏鏁版嵁琛ㄦ牸 -->
+ <el-card class="table-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span>璇︾粏鏁版嵁</span>
+ <div>
+ <el-button type="primary" size="small" @click="refreshTable">鍒锋柊</el-button>
+ <el-button type="success" size="small" @click="exportTable">瀵煎嚭</el-button>
+ </div>
+ </div>
+ </template>
+
+ <el-table
+ :data="tableData"
+ style="width: 100%"
+ v-loading="tableLoading"
+ stripe
+ border
+ >
+ <el-table-column prop="id" label="缂栧彿" width="80" />
+ <el-table-column prop="name" label="鍚嶇О" />
+ <el-table-column prop="type" label="绫诲瀷" width="120" />
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="progress" label="杩涘害" width="120">
+ <template #default="scope">
+ <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="createTime" label="鍒涘缓鏃堕棿" width="180" />
+ <el-table-column prop="updateTime" label="鏇存柊鏃堕棿" width="180" />
+ <el-table-column label="鎿嶄綔" width="150" fixed="right">
+ <template #default="scope">
+ <el-button type="text" size="small" @click="viewDetail(scope.row)">鏌ョ湅</el-button>
+ <el-button type="text" size="small" @click="editItem(scope.row)">缂栬緫</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination-container">
+ <el-pagination
+ v-model:current-page="pagination.currentPage"
+ v-model:page-size="pagination.pageSize"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import * as echarts from 'echarts'
+import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const filterForm = reactive({
+ dateRange: [],
+ reportType: 'sample'
+})
+
+const statistics = reactive({
+ totalSamples: 1250,
+ activeEquipment: 45,
+ completedInspections: 890,
+ totalUsage: 2340
+})
+
+const tableData = ref([])
+const tableLoading = ref(false)
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 20,
+ total: 0
+})
+
+// 鍥捐〃寮曠敤
+const sampleChartRef = ref(null)
+const equipmentChartRef = ref(null)
+const inspectionChartRef = ref(null)
+const usageChartRef = ref(null)
+
+// 鍥捐〃瀹炰緥
+let sampleChart = null
+let equipmentChart = null
+let inspectionChart = null
+let usageChart = null
+
+// 鍒濆鍖栨暟鎹�
+const initData = () => {
+ // 妯℃嫙琛ㄦ牸鏁版嵁
+ tableData.value = [
+ {
+ id: 'SP001',
+ name: '鏍峰搧A-001',
+ type: '閲戝睘鏉愭枡',
+ status: '妫�娴嬩腑',
+ progress: 75,
+ createTime: '2025-01-15 09:30:00',
+ updateTime: '2025-01-20 14:20:00'
+ },
+ {
+ id: 'SP002',
+ name: '鏍峰搧B-002',
+ type: '濉戞枡鍒跺搧',
+ status: '宸插畬鎴�',
+ progress: 100,
+ createTime: '2025-01-10 10:15:00',
+ updateTime: '2025-01-18 16:45:00'
+ },
+ {
+ id: 'SP003',
+ name: '鏍峰搧C-003',
+ type: '鐢靛瓙鍏冧欢',
+ status: '寰呮娴�',
+ progress: 0,
+ createTime: '2025-01-22 08:45:00',
+ updateTime: '2025-01-22 08:45:00'
+ },
+ {
+ id: 'EQ001',
+ name: '妫�娴嬭澶嘇',
+ type: '鍏夎氨浠�',
+ status: '浣跨敤涓�',
+ progress: 60,
+ createTime: '2025-01-05 14:20:00',
+ updateTime: '2025-01-20 11:30:00'
+ },
+ {
+ id: 'EQ002',
+ name: '妫�娴嬭澶嘊',
+ type: '鏄惧井闀�',
+ status: '绌洪棽',
+ progress: 0,
+ createTime: '2025-01-08 16:10:00',
+ updateTime: '2025-01-19 09:15:00'
+ }
+ ]
+
+ pagination.total = tableData.value.length
+}
+
+// 鍒濆鍖栨牱鍝佽繘搴﹀浘琛�
+const initSampleChart = () => {
+ if (sampleChartRef.value) {
+ sampleChart = echarts.init(sampleChartRef.value)
+ const option = {
+ title: {
+ text: '鏍峰搧杩涘害鍒嗗竷',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b}: {c} ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'left'
+ },
+ series: [
+ {
+ name: '鏍峰搧鐘舵��',
+ type: 'pie',
+ radius: ['40%', '70%'],
+ avoidLabelOverlap: false,
+ label: {
+ show: false,
+ position: 'center'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: '18',
+ fontWeight: 'bold'
+ }
+ },
+ labelLine: {
+ show: false
+ },
+ data: [
+ { value: 450, name: '宸插畬鎴�' },
+ { value: 320, name: '妫�娴嬩腑' },
+ { value: 280, name: '寰呮娴�' },
+ { value: 200, name: '宸叉殏鍋�' }
+ ]
+ }
+ ]
+ }
+ sampleChart.setOption(option)
+ }
+}
+
+// 鍒濆鍖栬澶囦娇鐢ㄥ浘琛�
+const initEquipmentChart = () => {
+ if (equipmentChartRef.value) {
+ equipmentChart = echarts.init(equipmentChartRef.value)
+ const option = {
+ title: {
+ text: '璁惧浣跨敤鐜�',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ }
+ },
+ xAxis: {
+ type: 'category',
+ data: ['鍏夎氨浠�', '鏄惧井闀�', '纭害璁�', '鎷夊姏鏈�', '鍐插嚮鏈�', '閲戠浉浠�']
+ },
+ yAxis: {
+ type: 'value',
+ name: '浣跨敤鐜�(%)'
+ },
+ series: [
+ {
+ name: '浣跨敤鐜�',
+ type: 'bar',
+ data: [85, 60, 75, 90, 45, 70],
+ label: {
+ show: true,
+ position: 'inside',
+ align: 'center',
+ verticalAlign: 'middle',
+ formatter: '{c}%',
+ color: '#fff'
+ },
+ itemStyle: {
+ color: function(params) {
+ const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
+ return colors[params.dataIndex]
+ }
+ }
+ }
+ ]
+ }
+ equipmentChart.setOption(option)
+ }
+}
+
+// 鍒濆鍖栨娴嬮」鐩浘琛�
+const initInspectionChart = () => {
+ if (inspectionChartRef.value) {
+ inspectionChart = echarts.init(inspectionChartRef.value)
+ const option = {
+ title: {
+ text: '妫�娴嬮」鐩垎甯�',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'item'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'left'
+ },
+ series: [
+ {
+ name: '妫�娴嬮」鐩�',
+ type: 'pie',
+ radius: '50%',
+ data: [
+ { value: 335, name: '鐗╃悊鎬ц兘' },
+ { value: 310, name: '鍖栧鍒嗘瀽' },
+ { value: 234, name: '灏哄娴嬮噺' },
+ { value: 135, name: '澶栬妫�鏌�' },
+ { value: 148, name: '鍏朵粬妫�娴�' }
+ ],
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
+ }
+ }
+ }
+ ]
+ }
+ inspectionChart.setOption(option)
+ }
+}
+
+// 鍒濆鍖栭鐢ㄨ褰曞浘琛�
+const initUsageChart = () => {
+ if (usageChartRef.value) {
+ usageChart = echarts.init(usageChartRef.value)
+ const option = {
+ title: {
+ text: '棰嗙敤璁板綍瓒嬪娍',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'axis'
+ },
+ legend: {
+ data: ['棰嗙敤娆℃暟', '褰掕繕娆℃暟']
+ },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�']
+ },
+ yAxis: {
+ type: 'value'
+ },
+ series: [
+ {
+ name: '棰嗙敤娆℃暟',
+ type: 'line',
+ stack: 'Total',
+ data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
+ },
+ {
+ name: '褰掕繕娆℃暟',
+ type: 'line',
+ stack: 'Total',
+ data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
+ }
+ ]
+ }
+ usageChart.setOption(option)
+ }
+}
+
+// 浜嬩欢澶勭悊鍑芥暟
+const handleFilterChange = () => {
+ ElMessage.success('绛涢�夋潯浠跺凡鏇存柊')
+ // 杩欓噷鍙互鏍规嵁绛涢�夋潯浠堕噸鏂板姞杞芥暟鎹�
+}
+
+const resetFilter = () => {
+ filterForm.dateRange = []
+ filterForm.reportType = 'sample'
+ ElMessage.info('绛涢�夋潯浠跺凡閲嶇疆')
+}
+
+const exportReport = () => {
+ ElMessage.success('鎶ヨ〃瀵煎嚭鍔熻兘寮�鍙戜腑...')
+}
+
+const refreshSampleChart = () => {
+ initSampleChart()
+ ElMessage.success('鏍峰搧杩涘害鍥捐〃宸插埛鏂�')
+}
+
+const refreshEquipmentChart = () => {
+ initEquipmentChart()
+ ElMessage.success('璁惧浣跨敤鍥捐〃宸插埛鏂�')
+}
+
+const refreshInspectionChart = () => {
+ initInspectionChart()
+ ElMessage.success('妫�娴嬮」鐩浘琛ㄥ凡鍒锋柊')
+}
+
+const refreshUsageChart = () => {
+ initUsageChart()
+ ElMessage.success('棰嗙敤璁板綍鍥捐〃宸插埛鏂�')
+}
+
+const refreshTable = () => {
+ tableLoading.value = true
+ setTimeout(() => {
+ tableLoading.value = false
+ ElMessage.success('琛ㄦ牸鏁版嵁宸插埛鏂�')
+ }, 1000)
+}
+
+const exportTable = () => {
+ ElMessage.success('琛ㄦ牸瀵煎嚭鍔熻兘寮�鍙戜腑...')
+}
+
+const handleSizeChange = (val) => {
+ pagination.pageSize = val
+ // 閲嶆柊鍔犺浇鏁版嵁
+}
+
+const handleCurrentChange = (val) => {
+ pagination.currentPage = val
+ // 閲嶆柊鍔犺浇鏁版嵁
+}
+
+const getStatusType = (status) => {
+ const statusMap = {
+ '宸插畬鎴�': 'success',
+ '妫�娴嬩腑': 'warning',
+ '寰呮娴�': 'info',
+ '宸叉殏鍋�': 'danger',
+ '浣跨敤涓�': 'primary',
+ '绌洪棽': 'info'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getProgressStatus = (progress) => {
+ if (progress === 100) return 'success'
+ if (progress >= 80) return 'warning'
+ if (progress >= 50) return ''
+ return 'exception'
+}
+
+const viewDetail = (row) => {
+ ElMessage.info(`鏌ョ湅璇︽儏: ${row.name}`)
+}
+
+const editItem = (row) => {
+ ElMessage.info(`缂栬緫椤圭洰: ${row.name}`)
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ initData()
+ nextTick(() => {
+ initSampleChart()
+ initEquipmentChart()
+ initInspectionChart()
+ initUsageChart()
+ })
+
+ // 鐩戝惉绐楀彛澶у皬鍙樺寲锛岄噸鏂拌皟鏁村浘琛ㄥぇ灏�
+ window.addEventListener('resize', () => {
+ sampleChart?.resize()
+ equipmentChart?.resize()
+ inspectionChart?.resize()
+ usageChart?.resize()
+ })
+})
+</script>
+
+<style scoped>
+.report-management {
+ padding: 20px;
+ background-color: #f5f5f5;
+ min-height: 100vh;
+}
+
+.page-header {
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.page-header h2 {
+ color: #303133;
+ margin-bottom: 8px;
+ font-size: 24px;
+ font-weight: 600;
+}
+
+.page-header p {
+ color: #909399;
+ font-size: 14px;
+ margin: 0;
+}
+
+.filter-card {
+ margin-bottom: 20px;
+}
+
+.statistics-cards {
+ margin-bottom: 20px;
+}
+
+.stat-card {
+ height: 120px;
+}
+
+.stat-content {
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+
+.stat-icon {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 20px;
+ font-size: 24px;
+ color: white;
+}
+
+.stat-card:nth-child(1) .stat-icon {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.stat-card:nth-child(2) .stat-icon {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+}
+
+.stat-card:nth-child(3) .stat-icon {
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.stat-card:nth-child(4) .stat-icon {
+ background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+}
+
+.stat-info {
+ flex: 1;
+}
+
+.stat-number {
+ font-size: 28px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 8px;
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #909399;
+}
+
+.charts-container {
+ margin-bottom: 20px;
+}
+
+.chart-card {
+ margin-bottom: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.chart-container {
+ height: 300px;
+ width: 100%;
+}
+
+.table-card {
+ margin-bottom: 20px;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ text-align: right;
+}
+
+:deep(.el-card__header) {
+ padding: 15px 20px;
+ border-bottom: 1px solid #ebeef5;
+ background-color: #fafafa;
+}
+
+:deep(.el-card__body) {
+ padding: 20px;
+}
+
+:deep(.el-table) {
+ margin-bottom: 20px;
+}
+
+:deep(.el-progress) {
+ margin: 0;
+}
+
+:deep(.el-tag) {
+ margin: 0;
+}
+</style>
diff --git a/src/views/reportAnalysis/reportManagement/index.vue b/src/views/reportAnalysis/reportManagement/index.vue
new file mode 100644
index 0000000..343a2c2
--- /dev/null
+++ b/src/views/reportAnalysis/reportManagement/index.vue
@@ -0,0 +1,1471 @@
+<template>
+ <div class="report-management">
+ <!-- 鍥捐〃鍖哄煙 -->
+ <div class="charts-container">
+ <el-row :gutter="20">
+ <!-- 鍚勭被鍨嬪畬鎴愭暟閲� -->
+ <el-col :span="9">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <div class="chart-title-line"></div>
+ <span>鍚勭被鍨嬪畬鎴愭暟閲�</span>
+ </div>
+ </template>
+ <div class="top-container">
+ <div class="typeNum">
+ <div class="typeNum-left">
+ <img src="~@/assets/images/chartCard.svg" alt="鍥捐〃"
+ style="width: 40px; height: 40px; object-fit: contain;">
+ <div class="typeNum-left-text">鍘熸潗鏂�</div>
+ </div>
+ <div class="typeNum-center">
+ <div class="typeNum-leftLine">-</div>
+ <div class="typeNum-rightLine"></div>
+ </div>
+ <div class="typeNum-right">
+ <div class="typeNum-right-top">
+ <div class="typeNum-right-top-name">鎬绘暟閲�</div>
+ <div class="typeNum-right-top-text">{{ getInspectStatValue(0, 'totalCount') }} <span
+ class="unit">涓�</span></div>
+ </div>
+ <div class="typeNum-right-bottom">
+ <div class="typeNum-right-top-name">宸插畬鎴愭暟</div>
+ <div class="typeNum-right-top-text">{{ getInspectStatValue(0, 'completedCount') }} <span
+ class="unit">涓�</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="typeNum">
+ <div class="typeNum-left">
+ <img src="~@/assets/images/chartCard2.svg" alt="鍥捐〃"
+ style="width: 40px; height: 40px; object-fit: contain;">
+ <div class="typeNum-left-text" style="color: #5EB334;">鍗婃垚鍝�</div>
+ </div>
+ <div class="typeNum-center">
+ <div class="typeNum-leftLine2">-</div>
+ <div class="typeNum-rightLine2"></div>
+ </div>
+ <div class="typeNum-right">
+ <div class="typeNum-right-top">
+ <div class="typeNum-right-top-name">鎬绘暟閲�</div>
+ <div class="typeNum-right-top-text">{{ getInspectStatValue(1, 'totalCount') }} <span
+ class="unit">涓�</span></div>
+ </div>
+ <div class="typeNum-right-bottom">
+ <div class="typeNum-right-top-name">宸插畬鎴愭暟</div>
+ <div class="typeNum-right-top-text">{{ getInspectStatValue(1, 'completedCount') }} <span
+ class="unit">涓�</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="typeNum">
+ <div class="typeNum-left">
+ <img src="~@/assets/images/chartCard3.svg" alt="鍥捐〃"
+ style="width: 40px; height: 40px; object-fit: contain;">
+ <div class="typeNum-left-text" style="color: #8000FF;">鎴愬搧</div>
+ </div>
+ <div class="typeNum-center">
+ <div class="typeNum-leftLine3">-</div>
+ <div class="typeNum-rightLine3"></div>
+ </div>
+ <div class="typeNum-right">
+ <div class="typeNum-right-top">
+ <div class="typeNum-right-top-name">鎬绘暟閲�</div>
+ <div class="typeNum-right-top-text">{{ getInspectStatValue(2, 'totalCount') }} <span
+ class="unit">涓�</span></div>
+ </div>
+ <div class="typeNum-right-bottom">
+ <div class="typeNum-right-top-name">宸插畬鎴愭暟</div>
+ <div class="typeNum-right-top-text">{{ getInspectStatValue(2, 'completedCount') }} <span
+ class="unit">涓�</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <!-- 璐ㄦ鍚堟牸鐜� -->
+ <el-col :span="15">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <div class="chart-title-line"></div>
+ <span>璐ㄦ鍚堟牸鐜�</span>
+ </div>
+ </template>
+ <div class="top-container flex-center">
+ <div class="quality-card blue-card">
+ <div class="quality-card-title">
+ <img src="~@/assets/images/chartCard.svg" alt="鍘熸潗鏂�"
+ style="width: 24px; height: 24px; margin-right: 8px;">
+ 鍘熸潗鏂欏悎鏍肩巼
+ </div>
+ <div class="quality-card-content">
+ <div class="quality-item">
+ <div>
+ <div class="quality-item-label blue-label">瀹屾垚鐜�</div>
+ <div class="quality-item-tip">鍗犳瘮</div>
+ <div class="quality-item-value">{{ getPassRateStatValue(0, 'completionRate') }}</div>
+ </div>
+ <div class="quality-item-chart" ref="materialCompletionChart"></div>
+ </div>
+ <div class="quality-item">
+ <div>
+ <div class="quality-item-label green-label">鍚堟牸鐜�</div>
+ <div class="quality-item-tip">鍗犳瘮</div>
+ <div class="quality-item-value">{{ getPassRateStatValue(0, 'passRate') }}</div>
+ </div>
+ <div class="quality-item-chart" ref="materialQualityChart"></div>
+ </div>
+ </div>
+ </div>
+ <div class="quality-card green-card">
+ <div class="quality-card-title">
+ <img src="~@/assets/images/chartCard2.svg" alt="鍗婃垚鍝�"
+ style="width: 24px; height: 24px; margin-right: 8px;">
+ 鍗婃垚鍝佸悎鏍肩巼
+ </div>
+ <div class="quality-card-content">
+ <div class="quality-item">
+ <div>
+ <div class="quality-item-label blue-label">瀹屾垚鐜�</div>
+ <div class="quality-item-tip">鍗犳瘮</div>
+ <div class="quality-item-value">{{ getPassRateStatValue(1, 'completionRate') }}</div>
+ </div>
+ <div class="quality-item-chart" ref="semiCompletionChart"></div>
+ </div>
+ <div class="quality-item">
+ <div>
+ <div class="quality-item-label green-label">鍚堟牸鐜�</div>
+ <div class="quality-item-tip">鍗犳瘮</div>
+ <div class="quality-item-value">{{ getPassRateStatValue(1, 'passRate') }}</div>
+ </div>
+ <div class="quality-item-chart" ref="semiQualityChart"></div>
+ </div>
+ </div>
+ </div>
+ <div class="quality-card purple-card">
+ <div class="quality-card-title">
+ <img src="~@/assets/images/chartCard3.svg" alt="鎴愬搧"
+ style="width: 24px; height: 24px; margin-right: 8px;">
+ 鎴愬搧鍚堟牸鐜�
+ </div>
+ <div class="quality-card-content">
+ <div class="quality-item">
+ <div>
+ <div class="quality-item-label blue-label">瀹屾垚鐜�</div>
+ <div class="quality-item-tip">鍗犳瘮</div>
+ <div class="quality-item-value">{{ getPassRateStatValue(2, 'completionRate') }}</div>
+ </div>
+ <div class="quality-item-chart" ref="finalCompletionChart"></div>
+ </div>
+ <div class="quality-item">
+ <div>
+ <div class="quality-item-label green-label">鍚堟牸鐜�</div>
+ <div class="quality-item-tip">鍗犳瘮</div>
+ <div class="quality-item-value">{{ getPassRateStatValue(2, 'passRate') }}</div>
+ </div>
+ <div class="quality-item-chart" ref="finalQualityChart"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ <div class="charts-container">
+ <el-row :gutter="20">
+ <!-- 璐ㄦ鍚堟牸鐜� -->
+ <el-col :span="24">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <div class="chart-title-line"></div>
+ <span>璐ㄦ鍚堟牸鐜�</span>
+ </div>
+ </template>
+ <div class="chart-container-line">
+ <div class="container-line-left">
+ <div style="height: 100%; width: 100%;" ref="usageChartRef">
+ </div>
+ </div>
+ <div class="container-line-right">
+ <div style="height: 80%; width: 100%;" ref="inspectionChartRef">
+ </div>
+ <div class="container-line-right-bottom">
+ <div class="inspection-chart-box">
+ <div class="chart-box-title">鍘熸潗鏂欐�绘暟</div>
+ <div class="chart-box-num">{{ getYearlyStatValue(0, 'totalCount') }}</div>
+ </div>
+ <div class="inspection-chart-box">
+ <div class="chart-box-title">鍗婃垚鍝佹�绘暟</div>
+ <div class="chart-box-num">{{ getYearlyStatValue(1, 'totalCount') }}</div>
+ </div>
+ <div class="inspection-chart-box">
+ <div class="chart-box-title">鎴愬搧鎬绘暟</div>
+ <div class="chart-box-num">{{ getYearlyStatValue(2, 'totalCount') }}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- </div> -->
+ <!-- <div ref="sampleChartRef"
+ class="chart-container"></div> -->
+ <div class="yearchange">
+ <div style="margin-right: 8px;font-size: 14px;">骞翠唤:</div>
+ <el-date-picker v-model="value3" size="mini" :clearable="false" style="width: 60px;" type="year"
+ :disabled-date="disabledDate" placeholder="">
+ </el-date-picker>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ <div class="charts-container">
+ <el-row :gutter="20">
+ <!-- 鏍峰搧杩涘害鍥捐〃 -->
+ <el-col :span="12">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <div class="chart-title-line"></div>
+ <span>璐ㄩ噺瀹屾垚鏄庣粏</span>
+ </div>
+ </template>
+ <div ref="equipmentChartRef" class="chart-container"></div>
+ </el-card>
+ </el-col>
+ <!-- 璁惧浣跨敤鍥捐〃 -->
+ <el-col :span="12">
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <div class="chart-title-line"></div>
+ <span>妫�娴嬮」鐩垎绫�</span>
+ </div>
+ </template>
+ <div class="chart-container-line">
+ <div class="container-line2-left">
+ <div class="info-box">
+ <div class="info-box-header">椤圭洰鍒嗗竷</div>
+ <div class="info-line" v-for="(item, index) in topParametersData.list" :key="index">
+ <div class="info-icon" :style="{ backgroundColor: getParameterColor(index) }"></div>
+ <div class="info-line-title">{{ item.name }}</div>
+ <div class="info-line-value1">{{ item.percentage }}%</div>
+ <div class="info-line-value2">{{ item.count }}</div>
+ </div>
+ </div>
+ </div>
+ <div ref="sampleChartRef" style="height: 100%; width: 50%;" class="chart-container"></div>
+ </div>
+ <!-- Tab 閫夋嫨鍣� -->
+ <div class="tab-selector">
+ <div class="tab-item" :class="{ active: activeTab === 'raw' }" @click="activeTab = 'raw'">鍘熸潗鏂�</div>
+ <div class="tab-item" :class="{ active: activeTab === 'semi' }" @click="activeTab = 'semi'">鍗婃垚鍝�</div>
+ <div class="tab-item" :class="{ active: activeTab === 'final' }" @click="activeTab = 'final'">鎴愬搧</div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+import * as echarts from "echarts";
+import { getInspectStatistics, getPassRateStatistics, getMonthlyPassRateStatistics, getYearlyPassRateStatistics, getMonthlyCompletionDetails, getTopParameters } from "@/api/reportAnalysis/qualityReport";
+
+// 鍝嶅簲寮忔暟鎹�
+const filterForm = reactive({
+ dateRange: [],
+ reportType: "sample",
+});
+
+const inspectStatisticsData = ref([]);
+const passRateStatisticsData = ref([]);
+const monthlyPassRateData = ref([]);
+const yearlyPassRateData = ref([]);
+const monthlyCompletionDetailsData = ref([]);
+const topParametersData = ref({ totalCount: 0, list: [] });
+const activeTab = ref("raw");
+
+const getParameterColor = (index) => {
+ const colors = ['#165DFF', '#14C9C9', '#F7BA1E', '#722ED1', '#3491FA', '#FF7D00', '#F53F3F'];
+ return colors[index % colors.length];
+};
+
+const getYearlyStatValue = (type, field) => {
+ const stat = yearlyPassRateData.value.find(item => item.inspectType === type);
+ return stat ? stat[field] : 0;
+};
+
+const getInspectStatValue = (type, field) => {
+ const stat = inspectStatisticsData.value.find(item => item.inspectType === type);
+ return stat ? stat[field] : 0;
+};
+
+const getPassRateStatValue = (type, field) => {
+ const stat = passRateStatisticsData.value.find(item => item.inspectType === type);
+ if (stat) {
+ if (field === 'completionRate' || field === 'passRate') {
+ return stat[field] ? Number(stat[field]).toFixed(0) + '%' : '0%';
+ }
+ return stat[field];
+ }
+ return field === 'completionRate' || field === 'passRate' ? '0%' : 0;
+};
+
+const fetchInspectStatisticsData = async () => {
+ try {
+ const res = await getInspectStatistics();
+ if (res.code === 200) {
+ inspectStatisticsData.value = res.data;
+ }
+ } catch (error) {
+ console.error("Failed to fetch inspect statistics:", error);
+ }
+};
+
+const fetchPassRateStatisticsData = async () => {
+ try {
+ const res = await getPassRateStatistics();
+ if (res.code === 200) {
+ passRateStatisticsData.value = res.data;
+ // 鏁版嵁鑾峰彇鍚庨噸鏂板垵濮嬪寲鍥捐〃
+ initAllQualityCharts();
+ }
+ } catch (error) {
+ console.error("Failed to fetch pass rate statistics:", error);
+ }
+};
+
+const fetchMonthlyPassRateData = async () => {
+ try {
+ const year = value3.value.getFullYear().toString();
+ const res = await getMonthlyPassRateStatistics(year);
+ if (res.code === 200) {
+ monthlyPassRateData.value = res.data;
+ initUsageChart();
+ }
+ } catch (error) {
+ console.error("Failed to fetch monthly pass rate statistics:", error);
+ }
+};
+
+const fetchYearlyPassRateData = async () => {
+ try {
+ const year = value3.value.getFullYear().toString();
+ const res = await getYearlyPassRateStatistics(year);
+ if (res.code === 200) {
+ yearlyPassRateData.value = res.data;
+ initInspectionChart();
+ }
+ } catch (error) {
+ console.error("Failed to fetch yearly pass rate statistics:", error);
+ }
+};
+
+const fetchMonthlyCompletionDetailsData = async () => {
+ try {
+ const year = value3.value.getFullYear().toString();
+ const res = await getMonthlyCompletionDetails(year);
+ if (res.code === 200) {
+ monthlyCompletionDetailsData.value = res.data;
+ initEquipmentChart();
+ }
+ } catch (error) {
+ console.error("Failed to fetch monthly completion details:", error);
+ }
+};
+
+const fetchTopParametersData = async () => {
+ try {
+ const typeMap = { raw: 0, semi: 1, final: 2 };
+ const res = await getTopParameters(typeMap[activeTab.value]);
+ if (res.code === 200) {
+ topParametersData.value = res.data;
+ initSampleChart();
+ }
+ } catch (error) {
+ console.error("Failed to fetch top parameters:", error);
+ }
+};
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 20,
+ total: 0,
+});
+
+// 鍒濆鍖栧勾浠戒负褰撳墠骞翠唤锛堜娇鐢―ate瀵硅薄锛�
+const currentYear = new Date().getFullYear();
+const value3 = ref(new Date(currentYear, 0, 1));
+
+// 闄愬埗鏃ユ湡閫夋嫨锛屼笉鍏佽閫夋嫨浠婂勾涔嬪悗鐨勫勾浠�
+const disabledDate = time => {
+ const currentYear = new Date().getFullYear();
+ return time.getFullYear() > currentYear;
+};
+
+// 鐩戝惉骞翠唤鍙樺寲
+import { watch } from "vue";
+watch(value3, () => {
+ fetchMonthlyPassRateData();
+ fetchYearlyPassRateData();
+ fetchMonthlyCompletionDetailsData();
+});
+
+watch(activeTab, () => {
+ fetchTopParametersData();
+});
+
+
+// 鍥捐〃寮曠敤
+const sampleChartRef = ref(null);
+const equipmentChartRef = ref(null);
+const inspectionChartRef = ref(null);
+const usageChartRef = ref(null);
+
+// 璐ㄦ鍚堟牸鐜囧浘琛ㄥ紩鐢�
+const materialCompletionChart = ref(null);
+const materialQualityChart = ref(null);
+const semiCompletionChart = ref(null);
+const semiQualityChart = ref(null);
+const finalCompletionChart = ref(null);
+const finalQualityChart = ref(null);
+
+// 鍥捐〃瀹炰緥
+let sampleChart = null;
+let equipmentChart = null;
+let inspectionChart = null;
+let usageChart = null;
+
+// 璐ㄦ鍚堟牸鐜囧浘琛ㄥ疄渚�
+let materialCompletionChartInstance = null;
+let materialQualityChartInstance = null;
+let semiCompletionChartInstance = null;
+let semiQualityChartInstance = null;
+let finalCompletionChartInstance = null;
+let finalQualityChartInstance = null;
+
+// 鍒濆鍖栨牱鍝佽繘搴﹀浘琛�
+const initSampleChart = () => {
+ if (sampleChartRef.value) {
+ sampleChart = echarts.init(sampleChartRef.value);
+ const option = {
+ title: {
+ show: false,
+ },
+ tooltip: {
+ trigger: "item",
+ formatter: "{a} <br/>{b}: {c} ({d}%)",
+ },
+ // legend: {
+ // orient: "vertical",
+ // left: "left",
+ // },
+ series: [
+ {
+ name: "妫�娴嬮」鐩�",
+ type: "pie",
+ radius: ["40%", "80%"],
+ avoidLabelOverlap: false,
+ label: {
+ show: false,
+ position: "center",
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: "18",
+ fontWeight: "bold",
+ },
+ },
+ labelLine: {
+ show: false,
+ },
+ data: topParametersData.value.list.map((item, index) => ({
+ value: item.count,
+ name: item.name,
+ itemStyle: { color: getParameterColor(index) }
+ })),
+ },
+ ],
+ };
+ sampleChart.setOption(option);
+ }
+};
+
+// 鍒濆鍖栬澶囦娇鐢ㄥ浘琛�
+const initEquipmentChart = () => {
+ if (equipmentChartRef.value) {
+ equipmentChart = echarts.init(equipmentChartRef.value);
+ const option = {
+ title: {
+ show: false,
+ },
+ tooltip: {
+ trigger: "axis",
+ axisPointer: {
+ type: "shadow",
+ },
+ },
+ grid: {
+ left: "1%",
+ right: "1%",
+ bottom: "1%",
+ containLabel: true,
+ },
+ legend: {
+ data: ["鍘熸潗鏂�", "鍗婃垚鍝�", "鎴愬搧"], // 鍥句緥鏁版嵁
+ icon: ["circle", "circle", "circle"],
+ itemWidth: 10, // 璁剧疆鍥炬爣瀹藉害
+ itemHeight: 10,
+ itemGap: 30,
+ right: 10,
+ },
+ xAxis: {
+ type: "category",
+ data: [
+ value3.value.getFullYear() + "-1",
+ value3.value.getFullYear() + "-2",
+ value3.value.getFullYear() + "-3",
+ value3.value.getFullYear() + "-4",
+ value3.value.getFullYear() + "-5",
+ value3.value.getFullYear() + "-6",
+ value3.value.getFullYear() + "-7",
+ value3.value.getFullYear() + "-8",
+ value3.value.getFullYear() + "-9",
+ value3.value.getFullYear() + "-10",
+ value3.value.getFullYear() + "-11",
+ value3.value.getFullYear() + "-12",
+ ], // 鏀逛负鍗佷簩涓湀
+ },
+ yAxis: {
+ type: "value",
+ name: "鏁�(涓�)",
+ },
+ series: [
+ {
+ name: "鍘熸潗鏂�",
+ type: "bar",
+ barWidth: "15%",
+ data: monthlyCompletionDetailsData.value.map(item => item.rawMaterialCount),
+ itemStyle: {
+ color: "#409EFF",
+ },
+ },
+ {
+ name: "鍗婃垚鍝�",
+ type: "bar",
+ barWidth: "15%",
+
+ data: monthlyCompletionDetailsData.value.map(item => item.processCount),
+ itemStyle: {
+ color: "#67C23A",
+ },
+ },
+ {
+ name: "鎴愬搧",
+ type: "bar",
+ barWidth: "15%",
+
+ data: monthlyCompletionDetailsData.value.map(item => item.outgoingCount),
+ itemStyle: {
+ color: "#E6A23C",
+ },
+ },
+ ],
+ };
+ equipmentChart.setOption(option);
+ }
+};
+
+// 鍒濆鍖栨娴嬮」鐩浘琛�
+const initInspectionChart = () => {
+ if (inspectionChartRef.value) {
+ inspectionChart = echarts.init(inspectionChartRef.value);
+ const option = {
+ title: {
+ show: false,
+ },
+ tooltip: {
+ trigger: "item",
+ },
+ series: [
+ {
+ type: "pie",
+ radius: "70%",
+ data: [
+ { value: getYearlyStatValue(0, 'totalCount'), name: "鍘熸潗鏂�", itemStyle: { color: "#1890FF" } },
+ { value: getYearlyStatValue(1, 'totalCount'), name: "鍗婃垚鍝�", itemStyle: { color: "#F7BA1E" } },
+ { value: getYearlyStatValue(2, 'totalCount'), name: "鎴愬搧", itemStyle: { color: "#14C9C9" } },
+ ],
+ label: {
+ show: true,
+ formatter: '{b}: {c}'
+ },
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: "rgba(0, 0, 0, 0.5)",
+ },
+ },
+ },
+ ],
+ };
+ inspectionChart.setOption(option);
+ }
+};
+
+// 鍒濆鍖栭鐢ㄨ褰曞浘琛�
+const initUsageChart = () => {
+ // 妫�鏌ュ浘琛ㄥ鍣ㄦ槸鍚﹀瓨鍦�
+ if (usageChartRef.value) {
+ // 鍒濆鍖� ECharts 瀹炰緥
+ usageChart = echarts.init(usageChartRef.value);
+ // 閰嶇疆鍥捐〃閫夐」
+ const option = {
+ // 鏍囬閰嶇疆锛堥殣钘忥級
+ title: {
+ show: false,
+ },
+
+ // 缃戞牸閰嶇疆锛堣皟鏁磋竟璺濓級
+ grid: {
+ left: "1%",
+ right: "4%",
+ bottom: "3%",
+ top: "14%",
+ containLabel: true,
+ },
+ // 鎻愮ず妗嗛厤缃�
+ tooltip: {
+ trigger: "axis", // 瑙﹀彂绫诲瀷涓哄潗鏍囪酱瑙﹀彂
+ },
+ // 鍥句緥閰嶇疆
+ legend: {
+ data: ["鍘熸潗鏂�", "鍗婃垚鍝�", "鎴愬搧"], // 鍥句緥鏁版嵁
+ icon: ["circle", "circle", "circle"],
+ itemWidth: 10, // 璁剧疆鍥炬爣瀹藉害
+ itemHeight: 10,
+ itemGap: 30,
+ },
+ // X杞撮厤缃�
+ xAxis: {
+ type: "category", // 绫诲埆杞�
+ boundaryGap: false, // 鍧愭爣杞翠袱杈圭暀鐧界瓥鐣�
+ data: [
+ value3.value.getFullYear() + "-1",
+ value3.value.getFullYear() + "-2",
+ value3.value.getFullYear() + "-3",
+ value3.value.getFullYear() + "-4",
+ value3.value.getFullYear() + "-5",
+ value3.value.getFullYear() + "-6",
+ value3.value.getFullYear() + "-7",
+ value3.value.getFullYear() + "-8",
+ value3.value.getFullYear() + "-9",
+ value3.value.getFullYear() + "-10",
+ value3.value.getFullYear() + "-11",
+ value3.value.getFullYear() + "-12",
+ ], // X杞存暟鎹�
+ },
+ // Y杞撮厤缃�
+ yAxis: {
+ type: "value", // 鏁板�艰酱
+ name: "鍗曚綅锛�%",
+ },
+ // 绯诲垪鏁版嵁
+ series: [
+ {
+ name: "鍘熸潗鏂�", // 绯诲垪鍚嶇О
+ type: "line", // 鍥捐〃绫诲瀷涓烘姌绾垮浘
+ // stack: "Total", // 鍫嗗彔鍚嶇О
+ symbol: "circle",
+ itemStyle: {
+ color: "#1890FF", // 璁剧疆杩欐潯绾跨殑棰滆壊
+ },
+ data: monthlyPassRateData.value.map(item => item.rawMaterial.passRate),
+ },
+ {
+ name: "鍗婃垚鍝�", // 绯诲垪鍚嶇О
+ type: "line", // 鍥捐〃绫诲瀷涓烘姌绾垮浘
+ // stack: "Total", // 鍫嗗彔鍚嶇О
+ symbol: "circle",
+ itemStyle: {
+ color: "#F7BA1E", // 璁剧疆杩欐潯绾跨殑棰滆壊
+ },
+ data: monthlyPassRateData.value.map(item => item.process.passRate),
+ },
+ {
+ name: "鎴愬搧", // 绯诲垪鍚嶇О
+ type: "line", // 鍥捐〃绫诲瀷涓烘姌绾垮浘
+ // stack: "Total", // 鍫嗗彔鍚嶇О
+ symbol: "circle",
+ itemStyle: {
+ color: "#14C9C9", // 璁剧疆杩欐潯绾跨殑棰滆壊
+ },
+ data: monthlyPassRateData.value.map(item => item.outgoing.passRate),
+ },
+ ],
+ };
+ // 灏嗛厤缃簲鐢ㄥ埌鍥捐〃
+ usageChart.setOption(option);
+ }
+};
+
+// 鍒濆鍖栬川妫�鍚堟牸鐜囧浘琛�
+const initQualityChart = (chartRef, color, value = 0.8) => {
+ if (chartRef.value) {
+ let chart = echarts.getInstanceByDom(chartRef.value);
+ if (!chart) {
+ chart = echarts.init(chartRef.value);
+ }
+ const numericValue = typeof value === 'string' ? parseFloat(value) / 100 : value / 100;
+ const option = {
+ series: [
+ {
+ type: "pie",
+ radius: ["45%", "90%"],
+ itemStyle: {
+ borderColor: "#f5f5f5",
+ // borderWidth: 2,
+ },
+ labelLine: {
+ show: false,
+ },
+ data: [
+ { value: numericValue, itemStyle: { color: color } },
+ { value: 1 - numericValue, itemStyle: { color: "#f5f5f5" } },
+ ],
+ },
+ ],
+ };
+ chart.setOption(option);
+ return chart;
+ }
+ return null;
+};
+
+// 鍒濆鍖栨墍鏈夎川妫�鍚堟牸鐜囧浘琛�
+const initAllQualityCharts = () => {
+ materialCompletionChartInstance = initQualityChart(
+ materialCompletionChart,
+ "#1890ff",
+ getPassRateStatValue(0, 'completionRate')
+ );
+ materialQualityChartInstance = initQualityChart(
+ materialQualityChart,
+ "#52c41a",
+ getPassRateStatValue(0, 'passRate')
+ );
+ semiCompletionChartInstance = initQualityChart(
+ semiCompletionChart,
+ "#1890ff",
+ getPassRateStatValue(1, 'completionRate')
+ );
+ semiQualityChartInstance = initQualityChart(
+ semiQualityChart,
+ "#52c41a",
+ getPassRateStatValue(1, 'passRate')
+ );
+ finalCompletionChartInstance = initQualityChart(
+ finalCompletionChart,
+ "#1890ff",
+ getPassRateStatValue(2, 'completionRate')
+ );
+ finalQualityChartInstance = initQualityChart(
+ finalQualityChart,
+ "#722ed1",
+ getPassRateStatValue(2, 'passRate')
+ );
+};
+
+// 浜嬩欢澶勭悊鍑芥暟
+const handleFilterChange = () => {
+ ElMessage.success("绛涢�夋潯浠跺凡鏇存柊");
+ // 杩欓噷鍙互鏍规嵁绛涢�夋潯浠堕噸鏂板姞杞芥暟鎹�
+};
+
+const resetFilter = () => {
+ filterForm.dateRange = [];
+ filterForm.reportType = "sample";
+ ElMessage.info("绛涢�夋潯浠跺凡閲嶇疆");
+};
+
+const exportReport = () => {
+ ElMessage.success("鎶ヨ〃瀵煎嚭鍔熻兘寮�鍙戜腑...");
+};
+
+const refreshSampleChart = () => {
+ initSampleChart();
+ ElMessage.success("鏍峰搧杩涘害鍥捐〃宸插埛鏂�");
+};
+
+const refreshEquipmentChart = () => {
+ initEquipmentChart();
+ ElMessage.success("璁惧浣跨敤鍥捐〃宸插埛鏂�");
+};
+
+const refreshInspectionChart = () => {
+ initInspectionChart();
+ ElMessage.success("妫�娴嬮」鐩浘琛ㄥ凡鍒锋柊");
+};
+
+const refreshUsageChart = () => {
+ initUsageChart();
+ ElMessage.success("棰嗙敤璁板綍鍥捐〃宸插埛鏂�");
+};
+
+const refreshTable = () => {
+ tableLoading.value = true;
+ setTimeout(() => {
+ tableLoading.value = false;
+ ElMessage.success("琛ㄦ牸鏁版嵁宸插埛鏂�");
+ }, 1000);
+};
+
+const exportTable = () => {
+ ElMessage.success("琛ㄦ牸瀵煎嚭鍔熻兘寮�鍙戜腑...");
+};
+
+const handleSizeChange = val => {
+ pagination.pageSize = val;
+ // 閲嶆柊鍔犺浇鏁版嵁
+};
+
+const handleCurrentChange = val => {
+ pagination.currentPage = val;
+ // 閲嶆柊鍔犺浇鏁版嵁
+};
+
+const getStatusType = status => {
+ const statusMap = {
+ 宸插畬鎴�: "success",
+ 妫�娴嬩腑: "warning",
+ 寰呮娴�: "info",
+ 宸叉殏鍋�: "danger",
+ 浣跨敤涓�: "primary",
+ 绌洪棽: "info",
+ };
+ return statusMap[status] || "info";
+};
+
+const getProgressStatus = progress => {
+ if (progress === 100) return "success";
+ if (progress >= 80) return "warning";
+ if (progress >= 50) return "";
+ return "exception";
+};
+
+const viewDetail = row => {
+ ElMessage.info(`鏌ョ湅璇︽儏: ${row.name}`);
+};
+
+const editItem = row => {
+ ElMessage.info(`缂栬緫椤圭洰: ${row.name}`);
+};
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ fetchInspectStatisticsData();
+ fetchPassRateStatisticsData();
+ fetchMonthlyPassRateData();
+ fetchYearlyPassRateData();
+ fetchMonthlyCompletionDetailsData();
+ fetchTopParametersData();
+ nextTick(() => {
+ initSampleChart();
+ initEquipmentChart();
+ initInspectionChart();
+ initUsageChart();
+ initAllQualityCharts();
+ });
+ // 鐩戝惉绐楀彛澶у皬鍙樺寲锛岄噸鏂拌皟鏁村浘琛ㄥぇ灏�
+ window.addEventListener("resize", () => {
+ sampleChart?.resize();
+ equipmentChart?.resize();
+ inspectionChart?.resize();
+ usageChart?.resize();
+
+ // 璋冩暣璐ㄦ鍚堟牸鐜囧浘琛ㄥぇ灏�
+ materialCompletionChartInstance?.resize();
+ materialQualityChartInstance?.resize();
+ semiCompletionChartInstance?.resize();
+ semiQualityChartInstance?.resize();
+ finalCompletionChartInstance?.resize();
+ finalQualityChartInstance?.resize();
+ });
+});
+</script>
+
+<style scoped>
+.report-management {
+ padding: 20px;
+ background-color: #f5f5f5;
+ min-height: 100vh;
+ /* height: 87vh;
+ overflow: hidden; */
+}
+
+.page-header {
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.page-header h2 {
+ color: #303133;
+ margin-bottom: 8px;
+ font-size: 24px;
+ font-weight: 600;
+}
+
+.page-header p {
+ color: #909399;
+ font-size: 14px;
+ margin: 0;
+}
+
+.filter-card {
+ margin-bottom: 20px;
+}
+
+.statistics-cards {
+ margin-bottom: 20px;
+}
+
+.stat-card {
+ height: 120px;
+}
+
+.stat-content {
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+
+.stat-icon {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 20px;
+ font-size: 24px;
+ color: white;
+}
+
+.stat-card:nth-child(1) .stat-icon {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.stat-card:nth-child(2) .stat-icon {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+}
+
+.stat-card:nth-child(3) .stat-icon {
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.stat-card:nth-child(4) .stat-icon {
+ background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+}
+
+.stat-info {
+ flex: 1;
+}
+
+.stat-number {
+ font-size: 28px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 8px;
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #909399;
+}
+
+.charts-container {
+ /* margin-bottom: 20px; */
+ position: relative;
+}
+
+.chart-card {
+ margin-bottom: 20px;
+}
+
+.container-line-right-bottom {
+ height: 20%;
+ width: 100%;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ /* background-color: #5b3f3f; */
+}
+
+.card-header {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ font-family: Source Han Sans, Source Han Sans;
+ font-weight: 700;
+ font-size: 18px;
+ color: #000000;
+ /* line-height: 27px; */
+ text-align: left;
+ font-style: normal;
+ text-transform: none;
+}
+
+.chart-title-line {
+ width: 6px;
+ height: 22px;
+ background-color: #161a9a;
+ margin-right: 16px;
+ border-radius: 3px;
+}
+
+.chart-container {
+ height: 250px;
+ width: 100%;
+}
+
+.chart-container-line {
+ height: 250px;
+ width: 100%;
+ display: flex;
+ position: relative;
+}
+
+/* Tab 閫夋嫨鍣ㄦ牱寮� */
+.tab-selector {
+ position: absolute;
+ top: 20px;
+ right: 40px;
+ display: flex;
+ border: 1px solid #dcdfe6;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.tab-item {
+ padding: 4px 12px;
+ cursor: pointer;
+ font-size: 14px;
+ color: #606266;
+ background-color: #fff;
+ border-right: 1px solid #dcdfe6;
+ transition: all 0.3s;
+}
+
+.tab-item:last-child {
+ border-right: none;
+}
+
+.tab-item:hover {
+ color: #409eff;
+}
+
+.tab-item.active {
+ color: #fff;
+ background-color: #409eff;
+}
+
+.container-line-left {
+ height: 100%;
+ width: 66%;
+}
+
+.container-line-right {
+ height: 100%;
+ width: 34%;
+}
+
+.container-line2-left {
+ height: 100%;
+ width: 50%;
+}
+
+.info-box {
+ width: 92%;
+ margin-left: 4%;
+ height: 100%;
+ background-color: #f7f8fa;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-around;
+}
+
+.info-box-header {
+ width: 100%;
+ margin-left: 20px;
+ color: #1d2129;
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.info-line {
+ width: 100%;
+ display: flex;
+ padding-left: 20px;
+ align-items: center;
+}
+
+.info-icon {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ margin-right: 8px;
+}
+
+.info-line-title {
+ font-size: 12px;
+ color: #4e5969;
+ flex: 1;
+}
+
+.info-line-value1 {
+ font-size: 12px;
+ color: #3d3d3d;
+ color: #1d2129;
+ font-weight: 500;
+ margin-right: 15%;
+}
+
+.info-line-value2 {
+ font-size: 12px;
+ color: #3d3d3d;
+ color: #1d2129;
+ font-weight: 500;
+ margin-right: 10%;
+}
+
+.top-container {
+ height: 130px;
+ width: 100%;
+ display: flex;
+}
+
+.typeNum {
+ height: 100%;
+ width: 33.33%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.typeNum-left {
+ font-size: 12px;
+ color: #909399;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.typeNum-left-text {
+ font-size: 12px;
+ color: #3491fa;
+ font-weight: 500;
+ margin-top: 5px;
+}
+
+.table-card {
+ margin-bottom: 20px;
+}
+
+.typeNum-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: 10px;
+}
+
+.typeNum-leftLine {
+ color: #3491fa;
+ font-size: 12px;
+}
+
+.typeNum-rightLine {
+ border-top: 1px solid #3491fa;
+ border-left: 1px solid #3491fa;
+ border-bottom: 1px solid #3491fa;
+ height: 80px;
+ width: 8px;
+}
+
+.typeNum-leftLine2 {
+ color: #5eb334;
+ font-size: 12px;
+}
+
+.typeNum-rightLine2 {
+ border-top: 1px solid #3491fa;
+ border-left: 1px solid #5eb334;
+ border-bottom: 1px solid #5eb334;
+ height: 80px;
+ width: 8px;
+}
+
+.typeNum-leftLine3 {
+ color: #8000ff;
+ font-size: 12px;
+}
+
+.typeNum-rightLine3 {
+ border-top: 1px solid #8000ff;
+ border-left: 1px solid #8000ff;
+ border-bottom: 1px solid #8000ff;
+ height: 80px;
+ width: 8px;
+}
+
+.typeNum-right {
+ margin-left: 10px;
+ display: flex;
+ flex-direction: column;
+ height: 90%;
+ justify-content: space-between;
+}
+
+.typeNum-right-top-name {
+ font-weight: 400;
+ font-size: 12px;
+ color: #3d3d3d;
+}
+
+.typeNum-right-top-text {
+ font-weight: 400;
+ font-size: 16px;
+ color: rgba(0, 0, 0, 0.85);
+ margin-top: 5px;
+}
+
+.unit {
+ font-size: 12px;
+ color: #3d3d3d;
+}
+
+.inspection-chart-box {
+ height: 50px;
+ width: 30%;
+ background-color: #f7f8fa;
+ border-radius: 8px;
+ padding-left: 15px;
+}
+
+.chart-box-title {
+ font-size: 12px;
+ color: #4e5969;
+ margin-top: 5px;
+}
+
+.unit {
+ font-size: 12px;
+ color: #3d3d3d;
+}
+
+.chart-box-num {
+ font-size: 18px;
+ color: #000;
+ margin-top: 5px;
+ font-weight: 500;
+}
+
+/* 璐ㄦ鍚堟牸鐜囧崱鐗囨牱寮� */
+.top-container鍚堟牸鐜� {
+ height: 130px;
+ width: 100%;
+ display: flex;
+ gap: 15px;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.flex-center {
+ justify-content: space-evenly;
+}
+
+.quality-card {
+ /* flex: 1; */
+ width: 32%;
+ /* height: 100px; */
+ border-radius: 8px;
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+}
+
+.blue-card {
+ background-color: #e6f7ff;
+}
+
+.green-card {
+ background-color: #f6ffed;
+ color: #000000;
+}
+
+.purple-card {
+ background-color: #f9f0ff;
+}
+
+.quality-card-title {
+ font-size: 14px;
+ font-weight: 500;
+ margin-bottom: 10px;
+ display: flex;
+ align-items: center;
+}
+
+.quality-item-tip {
+ font-size: 12px;
+ color: #666666;
+ margin-bottom: 3px;
+}
+
+.blue-label {
+ color: #1890ff;
+}
+
+.green-label {
+ color: #52c41a;
+}
+
+.quality-card-title {
+ color: #000;
+ font-weight: bold;
+}
+
+.quality-card-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex: 1;
+}
+
+.quality-item {
+ display: flex;
+ /* flex-direction: column; */
+ align-items: center;
+ justify-content: center;
+ margin-top: 5px;
+ flex: 1;
+}
+
+.quality-item-label {
+ font-size: 12px;
+ /* color: #666; */
+ margin-bottom: 4px;
+}
+
+.quality-item-value {
+ font-size: 20px;
+ font-weight: 500;
+ margin-bottom: 4px;
+}
+
+.quality-item-chart {
+ width: 60px;
+ height: 60px;
+ margin-left: 10px;
+}
+
+/* .flex-center {
+justify-content: space-evenly;
+} */
+
+.blue-chart {
+ /* background-color: rgba(24, 144, 255, 0.1); */
+}
+
+.green-chart {
+ /* background-color: rgba(82, 196, 26, 0.1); */
+}
+
+.purple-chart {
+ /* background-color: rgba(114, 46, 209, 0.1); */
+}
+
+.chart-ring {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ border: 15px solid transparent;
+ position: relative;
+}
+
+.blue-chart .chart-ring {
+ border-top-color: #1890ff;
+ border-right-color: #1890ff;
+ border-bottom-color: #1890ff;
+ transform: rotate(45deg);
+}
+
+.green-chart .chart-ring {
+ border-top-color: #52c41a;
+ border-right-color: #52c41a;
+ border-bottom-color: #52c41a;
+ transform: rotate(45deg);
+}
+
+.purple-chart .chart-ring {
+ border-top-color: #722ed1;
+ border-right-color: #722ed1;
+ border-bottom-color: #722ed1;
+ transform: rotate(45deg);
+}
+
+.pagination-container {
+ margin-top: 20px;
+ text-align: right;
+}
+
+.yearchange {
+ position: absolute;
+ right: 40px;
+ top: 20px;
+ display: flex;
+ align-items: center;
+ /* width: 60px; */
+}
+
+:deep(.el-card__header) {
+ padding: 15px 20px;
+ border-bottom: 1px solid #ffffff;
+ background-color: #ffffff;
+}
+
+:deep(.el-card__body) {
+ padding: 20px;
+}
+
+:deep(.el-table) {
+ margin-bottom: 20px;
+}
+
+:deep(.el-progress) {
+ margin: 0;
+}
+
+:deep(.el-tag) {
+ margin: 0;
+}
+
+:deep(.el-input__prefix) {
+ display: none !important;
+}
+</style>
diff --git a/src/views/reportAnalysis/taxComparison/index.vue b/src/views/reportAnalysis/taxComparison/index.vue
index a97ca4f..d27ab07 100644
--- a/src/views/reportAnalysis/taxComparison/index.vue
+++ b/src/views/reportAnalysis/taxComparison/index.vue
@@ -16,6 +16,7 @@
<el-form-item>
<el-button type="primary" @click="getTableData"> 鎼滅储 </el-button>
<el-button @click="resetFilters"> 閲嶇疆 </el-button>
+ <el-button @click="handleOut"> 瀵煎嚭 </el-button>
</el-form-item>
</el-form>
<div class="table_list">
@@ -36,8 +37,11 @@
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { onMounted } from "vue";
+import { onMounted, getCurrentInstance } from "vue";
import { getTaxList } from "@/api/procurementManagement/taxComparison";
+import { ElMessageBox } from "element-plus";
+
+const { proxy } = getCurrentInstance();
defineOptions({
name: "澧炲�肩◣姣斿",
@@ -87,6 +91,21 @@
onCurrentChange(page);
};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/purchase/report/exportTwo", {}, "澧炲�肩◣姣斿.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
onMounted(() => {
getTableData();
});
diff --git a/src/views/salesManagement/customerManagement/index.vue b/src/views/salesManagement/customerManagement/index.vue
new file mode 100644
index 0000000..f66b5f4
--- /dev/null
+++ b/src/views/salesManagement/customerManagement/index.vue
@@ -0,0 +1,423 @@
+<template>
+ <div class="app-container">
+ <el-card class="box-card">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-input
+ v-model="searchForm.name"
+ placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+ clearable
+ @keyup.enter="handleSearch"
+ >
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.region" placeholder="璇烽�夋嫨鍖哄煙" clearable>
+ <el-option label="鍗庝笢鍖�" value="鍗庝笢鍖�"></el-option>
+ <el-option label="鍗庡崡鍖�" value="鍗庡崡鍖�"></el-option>
+ <el-option label="鍗庡寳鍖�" value="鍗庡寳鍖�"></el-option>
+ <el-option label="瑗垮崡鍖�" value="瑗垮崡鍖�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.level" placeholder="璇烽�夋嫨瀹㈡埛绛夌骇" clearable>
+ <el-option label="VIP瀹㈡埛" value="VIP瀹㈡埛"></el-option>
+ <el-option label="閲嶈瀹㈡埛" value="閲嶈瀹㈡埛"></el-option>
+ <el-option label="鏅�氬鎴�" value="鏅�氬鎴�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ <el-button style="float: right;" type="primary" @click="handleAdd">
+ 鏂板瀹㈡埛
+ </el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 瀹㈡埛鍒楄〃 -->
+ <el-table
+ :data="filteredList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)"
+ >
+ <el-table-column prop="id" label="ID" width="80" align="center"/>
+ <el-table-column prop="name" label="瀹㈡埛鍚嶇О" width="150" />
+ <el-table-column prop="contactPerson" label="鑱旂郴浜�" width="100" />
+ <el-table-column prop="phone" label="鑱旂郴鐢佃瘽" width="140" />
+ <el-table-column prop="email" label="閭" />
+ <el-table-column prop="region" label="鍖哄煙" width="100" />
+ <el-table-column prop="level" label="瀹㈡埛绛夌骇" width="100">
+ <template #default="scope">
+ <el-tag :type="getLevelType(scope.row.level)">
+ {{ scope.row.level }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="salesperson" label="璐熻矗涓氬姟鍛�" width="120" />
+ <el-table-column prop="status" label="鐘舵��" width="80">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button link type="primary" @click="handleAllocation(scope.row)">鍒嗛厤</el-button>
+ <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.currentPage"
+ :limit="pagination.pageSize"
+ @pagination="handleCurrentChange"
+ />
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="name">
+ <el-input v-model="form.name" placeholder="璇疯緭鍏ュ鎴峰悕绉�"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴浜�" prop="contactPerson">
+ <el-input v-model="form.contactPerson" placeholder="璇疯緭鍏ヨ仈绯讳汉"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴鐢佃瘽" prop="phone">
+ <el-input v-model="form.phone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閭" prop="email">
+ <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍖哄煙" prop="region">
+ <el-select v-model="form.region" placeholder="璇烽�夋嫨鍖哄煙" style="width: 100%">
+ <el-option label="鍗庝笢鍖�" value="鍗庝笢鍖�"></el-option>
+ <el-option label="鍗庡崡鍖�" value="鍗庡崡鍖�"></el-option>
+ <el-option label="鍗庡寳鍖�" value="鍗庡寳鍖�"></el-option>
+ <el-option label="瑗垮崡鍖�" value="瑗垮崡鍖�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛绛夌骇" prop="level">
+ <el-select v-model="form.level" placeholder="璇烽�夋嫨瀹㈡埛绛夌骇" style="width: 100%">
+ <el-option label="VIP瀹㈡埛" value="VIP瀹㈡埛"></el-option>
+ <el-option label="閲嶈瀹㈡埛" value="閲嶈瀹㈡埛"></el-option>
+ <el-option label="鏅�氬鎴�" value="鏅�氬鎴�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璐熻矗涓氬姟鍛�" prop="salesperson">
+ <el-select v-model="form.salesperson" placeholder="璇烽�夋嫨涓氬姟鍛�" style="width: 100%">
+ <el-option label="闄堝織寮�" value="闄堝織寮�"></el-option>
+ <el-option label="鍒橀泤濠�" value="鍒橀泤濠�"></el-option>
+ <el-option label="鐜嬪缓鍥�" value="鐜嬪缓鍥�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option label="娲昏穬" value="娲昏穬"></el-option>
+ <el-option label="娼滃湪" value="娼滃湪"></el-option>
+ <el-option label="娴佸け" value="娴佸け"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="handleSubmit">纭� 瀹�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 瀹㈡埛鍒嗛厤瀵硅瘽妗� -->
+ <el-dialog v-model="allocationDialogVisible" title="瀹㈡埛鍒嗛厤" width="500px">
+ <el-form label-width="100px">
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <span>{{ currentCustomer.name }}</span>
+ </el-form-item>
+ <el-form-item label="褰撳墠涓氬姟鍛�">
+ <span>{{ currentCustomer.salesperson }}</span>
+ </el-form-item>
+ <el-form-item label="閲嶆柊鍒嗛厤">
+ <el-select v-model="newSalesperson" placeholder="璇烽�夋嫨鏂颁笟鍔″憳" style="width: 100%">
+ <el-option label="闄堝織寮�" value="闄堝織寮�"></el-option>
+ <el-option label="鍒橀泤濠�" value="鍒橀泤濠�"></el-option>
+ <el-option label="鐜嬪缓鍥�" value="鐜嬪缓鍥�"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍒嗛厤鍘熷洜">
+ <el-input v-model="allocationReason" type="textarea" rows="3" placeholder="璇疯緭鍏ュ垎閰嶅師鍥�"></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="allocationDialogVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="saveAllocation">纭� 瀹�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus, Search } from '@element-plus/icons-vue'
+import Pagination from '@/components/PIMTable/Pagination.vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const searchForm = reactive({
+ name: '',
+ region: '',
+ level: ''
+})
+
+const customerList = ref([
+ {
+ id: 1,
+ name: '涓婃捣绉戞妧鏈夐檺鍏徃',
+ contactPerson: '闄堝織寮�',
+ phone: '021-12345678',
+ email: 'zhang@shanghai-tech.com',
+ region: '鍗庝笢鍖�',
+ level: 'VIP瀹㈡埛',
+ salesperson: '闄堝織寮�',
+ status: '娲昏穬'
+ },
+ {
+ id: 2,
+ name: '娣卞湷鐢靛瓙鏈夐檺鍏徃',
+ contactPerson: '鍒橀泤濠�',
+ phone: '0755-87654321',
+ email: 'li@shenzhen-elec.com',
+ region: '鍗庡崡鍖�',
+ level: '閲嶈瀹㈡埛',
+ salesperson: '鍒橀泤濠�',
+ status: '娲昏穬'
+ },
+ {
+ id: 3,
+ name: '鍖椾含璐告槗鍏徃',
+ contactPerson: '鐜嬪缓鍥�',
+ phone: '010-11223344',
+ email: 'wang@beijing-trade.com',
+ region: '鍗庡寳鍖�',
+ level: '鏅�氬鎴�',
+ salesperson: '鐜嬪缓鍥�',
+ status: '娼滃湪'
+ }
+])
+
+const pagination = reactive({
+ total: 3,
+ currentPage: 1,
+ pageSize: 10
+})
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('鏂板瀹㈡埛')
+const form = reactive({
+ name: '',
+ contactPerson: '',
+ phone: '',
+ email: '',
+ region: '',
+ level: '',
+ salesperson: '',
+ status: '娲昏穬'
+})
+
+const rules = {
+ name: [{ required: true, message: '璇疯緭鍏ュ鎴峰悕绉�', trigger: 'blur' }],
+ contactPerson: [{ required: true, message: '璇疯緭鍏ヨ仈绯讳汉', trigger: 'blur' }],
+ phone: [{ required: true, message: '璇疯緭鍏ヨ仈绯荤數璇�', trigger: 'blur' }],
+ email: [{ required: true, message: '璇疯緭鍏ラ偖绠�', trigger: 'blur' }],
+ region: [{ required: true, message: '璇烽�夋嫨鍖哄煙', trigger: 'change' }],
+ level: [{ required: true, message: '璇烽�夋嫨瀹㈡埛绛夌骇', trigger: 'change' }],
+ salesperson: [{ required: true, message: '璇烽�夋嫨涓氬姟鍛�', trigger: 'change' }],
+ status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+}
+
+const isEdit = ref(false)
+const editId = ref(null)
+const allocationDialogVisible = ref(false)
+const currentCustomer = ref({})
+const newSalesperson = ref('')
+const allocationReason = ref('')
+const formRef = ref()
+
+// 璁$畻灞炴��
+const filteredList = computed(() => {
+ let list = customerList.value
+ if (searchForm.name) {
+ list = list.filter(item => item.name.includes(searchForm.name))
+ }
+ if (searchForm.region) {
+ list = list.filter(item => item.region === searchForm.region)
+ }
+ if (searchForm.level) {
+ list = list.filter(item => item.level === searchForm.level)
+ }
+ return list
+})
+
+// 鏂规硶
+const getLevelType = (level) => {
+ const levelMap = {
+ 'VIP瀹㈡埛': 'danger',
+ '閲嶈瀹㈡埛': 'warning',
+ '鏅�氬鎴�': 'info'
+ }
+ return levelMap[level] || 'info'
+}
+
+const getStatusType = (status) => {
+ const statusMap = {
+ '娲昏穬': 'success',
+ '娼滃湪': 'warning',
+ '娴佸け': 'danger'
+ }
+ return statusMap[status] || 'info'
+}
+
+const handleSearch = () => {
+ // 鎼滅储閫昏緫宸插湪computed涓鐞�
+}
+
+const resetSearch = () => {
+ searchForm.name = ''
+ searchForm.region = ''
+ searchForm.level = ''
+}
+
+const handleAdd = () => {
+ dialogTitle.value = '鏂板瀹㈡埛'
+ isEdit.value = false
+ form.name = ''
+ form.contactPerson = ''
+ form.phone = ''
+ form.email = ''
+ form.region = ''
+ form.level = ''
+ form.salesperson = ''
+ form.status = '娲昏穬'
+ dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+ dialogTitle.value = '缂栬緫瀹㈡埛'
+ isEdit.value = true
+ editId.value = row.id
+ Object.assign(form, row)
+ dialogVisible.value = true
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ュ鎴峰悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = customerList.value.findIndex(item => item.id === row.id)
+ if (index > -1) {
+ customerList.value.splice(index, 1)
+ pagination.total--
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ }
+ })
+}
+
+const handleAllocation = (row) => {
+ currentCustomer.value = row
+ newSalesperson.value = ''
+ allocationReason.value = ''
+ allocationDialogVisible.value = true
+}
+
+const saveAllocation = () => {
+ if (!newSalesperson.value) {
+ ElMessage.warning('璇烽�夋嫨鏂颁笟鍔″憳')
+ return
+ }
+
+ const index = customerList.value.findIndex(item => item.id === currentCustomer.value.id)
+ if (index > -1) {
+ customerList.value[index].salesperson = newSalesperson.value
+ ElMessage.success('瀹㈡埛鍒嗛厤鎴愬姛')
+ allocationDialogVisible.value = false
+ }
+}
+
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (isEdit.value) {
+ // 缂栬緫
+ const index = customerList.value.findIndex(item => item.id === editId.value)
+ if (index > -1) {
+ customerList.value[index] = { ...form, id: editId.value }
+ ElMessage.success('缂栬緫鎴愬姛')
+ }
+ } else {
+ // 鏂板
+ const newId = Math.max(...customerList.value.map(item => item.id)) + 1
+ customerList.value.push({
+ ...form,
+ id: newId
+ })
+ pagination.total++
+ ElMessage.success('鏂板鎴愬姛')
+ }
+ dialogVisible.value = false
+ }
+ })
+}
+
+const handleCurrentChange = (val) => {
+ pagination.currentPage = val.page
+ pagination.pageSize = val.limit
+}
+</script>
+
+<style scoped>
+.search-row {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/src/views/salesManagement/deliveryLedger/index.vue b/src/views/salesManagement/deliveryLedger/index.vue
new file mode 100644
index 0000000..688741b
--- /dev/null
+++ b/src/views/salesManagement/deliveryLedger/index.vue
@@ -0,0 +1,661 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="閿�鍞鍗曞彿锛�">
+ <el-input v-model="searchForm.salesContractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" style="width: 200px"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="杞︾墝鍙凤細">
+ <el-input v-model="searchForm.shippingCarNumber" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" style="width: 200px"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="蹇�掑崟鍙凤細">
+ <el-input v-model="searchForm.expressNumber" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" style="width: 200px"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
+ :row-key="(row) => row.id" style="width: 100%" height="calc(100vh - 21.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="salesContractNo" show-overflow-tooltip />
+ <el-table-column label="鍙戣揣璁㈠崟鍙�" prop="shippingNo" show-overflow-tooltip />
+ <el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" show-overflow-tooltip />
+ <el-table-column label="鍙戣揣鏃堕棿" prop="shippingDate" show-overflow-tooltip />
+ <el-table-column label="鍙戣揣杞︾墝鍙�" prop="shippingCarNumber" show-overflow-tooltip />
+ <el-table-column label="蹇�掑叕鍙�" prop="expressCompany" show-overflow-tooltip />
+ <el-table-column label="蹇�掑崟鍙�" prop="expressNumber" show-overflow-tooltip />
+ <el-table-column label="瀹℃牳鐘舵��" prop="status" align="center" width="120">
+ <template #default="scope">
+ <el-tag :type="getApprovalStatusType(scope.row.status)">
+ {{ getApprovalStatusText(scope.row.status) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ :disabled="!isApproved(scope.row.status)"
+ @click="openForm('edit', scope.row)">缂栬緫</el-button>
+ <el-button
+ link
+ type="danger"
+ size="small"
+ :disabled="!isApproved(scope.row.status)"
+ @click="handleDeleteSingle(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" />
+ </div>
+ <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板鍙戣揣鍙拌处' : '缂栬緫鍙戣揣鍙拌处'" width="40%"
+ @close="closeDia">
+ <el-form :model="form" label-width="120px" label-position="top" :rules="rules" ref="formRef">
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="鍙戣揣绫诲瀷锛�" prop="type">
+ <el-select
+ v-model="form.type"
+ placeholder="璇烽�夋嫨鍙戣揣绫诲瀷"
+ style="width: 100%"
+ @change="handleShippingTypeChange"
+ >
+ <el-option label="璐ц溅" value="璐ц溅" />
+ <el-option label="蹇��" value="蹇��" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="鍙戣揣鏃ユ湡锛�" prop="shippingDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.shippingDate"
+ 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="24" v-if="form.type === '璐ц溅'">
+ <el-form-item label="鍙戣揣杞︾墝鍙凤細" prop="shippingCarNumber">
+ <el-input
+ v-model="form.shippingCarNumber"
+ placeholder="璇疯緭鍏ュ彂璐ц溅鐗屽彿"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24" v-else>
+ <el-form-item label="蹇�掑叕鍙革細" prop="expressCompany">
+ <el-input
+ v-model="form.expressCompany"
+ placeholder="璇疯緭鍏ュ揩閫掑叕鍙�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30" v-if="form.type === '蹇��'">
+ <el-col :span="24">
+ <el-form-item label="蹇�掑崟鍙凤細" prop="expressNumber">
+ <el-input
+ v-model="form.expressNumber"
+ placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="鍙戣揣鍥剧墖锛�">
+ <el-upload
+ v-model:file-list="deliveryFileList"
+ :action="upload.url"
+ multiple
+ ref="deliveryFileUpload"
+ auto-upload
+ :headers="upload.headers"
+ :data="{ type: 9 }"
+ :before-upload="handleDeliveryBeforeUpload"
+ :on-error="handleDeliveryUploadError"
+ :on-success="handleDeliveryUploadSuccess"
+ :on-remove="handleDeliveryRemove"
+ list-type="picture-card"
+ :limit="9"
+ accept="image/png,image/jpeg,image/jpg"
+ >
+ <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
+ <template #tip>
+ <div class="el-upload__tip">
+ 鏀寔 jpg銆乯peg銆乸ng 鏍煎紡锛屾渶澶氫笂浼� 9 寮狅紝鍗曞紶澶у皬涓嶈秴杩� 10MB
+ </div>
+ </template>
+ </el-upload>
+ </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>
+ </div>
+</template>
+
+<script setup>
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
+import { ElMessageBox } from "element-plus";
+import { Plus } from "@element-plus/icons-vue";
+import { getToken } from "@/utils/auth";
+import { getCurrentDate } from "@/utils/index.js";
+import {
+ deliveryLedgerListPage,
+ addOrUpdateDeliveryLedger,
+ delDeliveryLedger,
+} from "@/api/salesManagement/deliveryLedger.js";
+import { delLedgerFile } from "@/api/salesManagement/salesLedger.js";
+
+
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const salesOrderOptions = ref([]);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+const deliveryFileList = ref([]);
+const javaApi = proxy.javaApi;
+
+// 涓婁紶閰嶇疆
+const upload = reactive({
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+});
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref("");
+const dialogFormVisible = ref(false);
+const data = reactive({
+ searchForm: {
+ salesContractNo: "", // 閿�鍞鍗曞彿
+ shippingCarNumber: "", // 杞︾墝鍙�
+ expressNumber: "", // 蹇�掑崟鍙�
+ },
+ form: {
+ id: null,
+ salesContractNo: "",
+ customerName: "",
+ type: "璐ц溅", // 璐ц溅, 蹇��
+ shippingDate: "",
+ shippingCarNumber: "",
+ expressCompany: "",
+ expressNumber: "", // 蹇�掑崟鍙�
+ },
+ rules: {
+ salesContractNo: [{ required: true, message: "璇烽�夋嫨閿�鍞鍗�", trigger: "change" }],
+ customerName: [{ required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur" }],
+ type: [
+ { required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change" }
+ ],
+ shippingDate: [{ required: true, message: "璇烽�夋嫨鍙戣揣鏃堕棿", trigger: "change" }],
+ shippingCarNumber: [
+ { validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur" }
+ ],
+ expressCompany: [
+ { validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur" }
+ ],
+ },
+});
+const { form, rules } = toRefs(data);
+const { searchForm } = toRefs(data);
+
+
+
+// 鏌ヨ鍒楄〃
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+
+const getList = () => {
+ tableLoading.value = true;
+ deliveryLedgerListPage({ ...searchForm.value, ...page })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records || [];
+ total.value = res.data.total || 0;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
+
+// 閿�鍞鍗曞彉鍖栨椂鑷姩濉厖瀹㈡埛鍚嶇О
+const handleSalesOrderChange = (value) => {
+ const selectedOrder = salesOrderOptions.value.find(item => item.salesContractNo === value);
+ if (selectedOrder) {
+ form.value.customerName = selectedOrder.customerName;
+ }
+};
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鎵撳紑寮规
+const openForm = async (type, row) => {
+ // 缂栬緫鏃舵鏌ュ鏍哥姸鎬�
+ if (type === 'edit' && row && !isApproved(row.status)) {
+ proxy.$modal.msgWarning("鍙兘缂栬緫瀹℃牳閫氳繃鐨勬暟鎹�");
+ return;
+ }
+
+ operationType.value = type;
+ const baseUrl = import.meta.env.VITE_APP_BASE_API;
+
+ if (type === 'edit' && row) {
+ form.value = {
+ id: row.id ?? null,
+ salesContractNo: row.salesContractNo ?? "",
+ customerName: row.customerName ?? "",
+ type: row.type || "璐ц溅",
+ shippingDate: row.shippingDate || getCurrentDate(),
+ shippingCarNumber: row.shippingCarNumber ?? "",
+ expressCompany: row.expressCompany ?? "",
+ expressNumber: row.expressNumber ?? "",
+ };
+ // 濡傛灉鏈夊浘鐗囷紝灏� commonFileList 杞崲涓烘枃浠跺垪琛ㄦ牸寮�
+ if (row.commonFileList && Array.isArray(row.commonFileList) && row.commonFileList.length > 0) {
+ deliveryFileList.value = row.commonFileList.map((file, index) => {
+ // 澶勭悊 URL锛氬皢 Windows 璺緞杞崲涓哄彲璁块棶鐨� URL
+ let fileUrl = file.url || '';
+ console.log('鍘熷 URL:', fileUrl);
+
+ // 濡傛灉 URL 鏄� Windows 璺緞鏍煎紡锛堝寘鍚弽鏂滄潬锛夛紝闇�瑕佽浆鎹�
+ if (fileUrl && fileUrl.indexOf('\\') > -1) {
+ // 鏌ユ壘 uploads 鍏抽敭瀛楃殑浣嶇疆锛屼粠閭i噷寮�濮嬫彁鍙栫浉瀵硅矾寰�
+ const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
+ if (uploadsIndex > -1) {
+ // 浠� uploads 寮�濮嬫彁鍙栬矾寰勶紝骞跺皢鍙嶆枩鏉犳浛鎹负姝f枩鏉�
+ const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
+ fileUrl = '/' + relativePath;
+ console.log('杞崲鍚庣殑鐩稿璺緞:', fileUrl);
+ } else {
+ // 濡傛灉娌℃湁鎵惧埌 uploads锛屾彁鍙栨渶鍚庝竴涓洰褰曞拰鏂囦欢鍚�
+ const parts = fileUrl.split('\\');
+ const fileName = parts[parts.length - 1];
+ fileUrl = '/uploads/' + fileName;
+ console.log('鏈壘鍒� uploads锛屼娇鐢ㄦ枃浠跺悕:', fileUrl);
+ }
+ }
+
+ // 纭繚鎵�鏈夐潪 http 寮�澶寸殑 URL 閮芥嫾鎺� baseUrl
+ if (fileUrl && !fileUrl.startsWith('http')) {
+ // 纭繚璺緞浠� / 寮�澶�
+ if (!fileUrl.startsWith('/')) {
+ fileUrl = '/' + fileUrl;
+ }
+ // 鎷兼帴 baseUrl
+ fileUrl = javaApi + fileUrl;
+ console.log('鏈�缁堟嫾鎺ョ殑 URL:', fileUrl);
+ }
+
+ return {
+ uid: file.id || Date.now() + index,
+ name: file.name || `image_${index + 1}.jpg`,
+ url: fileUrl,
+ status: 'success',
+ response: {
+ code: 200,
+ data: {
+ tempId: file.id,
+ url: fileUrl
+ }
+ },
+ tempId: file.id // 淇濆瓨鏂囦欢ID锛岀敤浜庢彁浜ゆ椂浣跨敤
+ };
+ });
+ } else {
+ deliveryFileList.value = [];
+ }
+ } else {
+ form.value = {
+ id: null,
+ salesContractNo: "",
+ customerName: "",
+ type: "璐ц溅",
+ shippingDate: getCurrentDate(),
+ shippingCarNumber: "",
+ expressCompany: "",
+ expressNumber: "",
+ };
+ deliveryFileList.value = [];
+ }
+
+ dialogFormVisible.value = true;
+};
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ let tempFileIds = [];
+ if (deliveryFileList.value !== null && deliveryFileList.value.length > 0) {
+ tempFileIds = deliveryFileList.value.map((item) => item.tempId);
+ }
+ const payload = {
+ id: form.value.id,
+ type: form.value.type,
+ shippingDate: form.value.shippingDate,
+ shippingCarNumber: form.value.type === "璐ц溅" ? form.value.shippingCarNumber : "",
+ expressCompany: form.value.type === "蹇��" ? form.value.expressCompany : "",
+ expressNumber: form.value.type === "蹇��" ? form.value.expressNumber : "",
+ tempFileIds: tempFileIds,
+ };
+ addOrUpdateDeliveryLedger(payload).then((res) => {
+ proxy.$modal.msgSuccess("鎿嶄綔鎴愬姛");
+ closeDia();
+ getList();
+ });
+ }
+ });
+};
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ deliveryFileList.value = []; // 娓呯┖鏂囦欢鍒楄〃
+ dialogFormVisible.value = false;
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/shippingInfo/export", {}, "鍙戣揣鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鎵归噺鍒犻櫎
+const handleDelete = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+
+ // 妫�鏌ラ�変腑鐨勮鏄惁閮芥槸"瀹℃牳閫氳繃"鐘舵��
+ const notApprovedRows = selectedRows.value.filter(row => !isApproved(row.status));
+ if (notApprovedRows.length > 0) {
+ proxy.$modal.msgWarning("鍙兘鍒犻櫎瀹℃牳閫氳繃鐨勬暟鎹�");
+ return;
+ }
+
+ const ids = selectedRows.value.map((item) => item.id);
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delDeliveryLedger(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鍗曚釜鍒犻櫎
+const handleDeleteSingle = (row) => {
+ // 妫�鏌ユ槸鍚︿负"瀹℃牳閫氳繃"鐘舵��
+ if (!isApproved(row.deliveryLedger)) {
+ proxy.$modal.msgWarning("鍙兘鍒犻櫎瀹℃牳閫氳繃鐨勬暟鎹�");
+ return;
+ }
+
+ ElMessageBox.confirm("姝ゆ搷浣滃皢鍒犻櫎璇ヨ褰曪紝鏄惁纭锛�", "鍒犻櫎", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delDeliveryLedger([row.id]).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鍙戣揣绫诲瀷鏍¢獙锛氳揣杞︽椂瑕佹眰杞︾墝锛屽揩閫掓椂瑕佹眰蹇�掑叕鍙�
+const validateShippingCarNumber = (value, callback) => {
+ if (form.value.type === "璐ц溅") {
+ if (!value) return callback(new Error("璇疯緭鍏ュ彂璐ц溅鐗屽彿"));
+ }
+ callback();
+};
+const validateExpressCompany = (value, callback) => {
+ if (form.value.type === "蹇��") {
+ if (!value) return callback(new Error("璇疯緭鍏ュ揩閫掑叕鍙�"));
+ }
+ callback();
+};
+
+// 鍙戣揣鍥剧墖涓婁紶鍓嶆牎妫�
+function handleDeliveryBeforeUpload(file) {
+ // 鏍℃鏂囦欢绫诲瀷
+ const isImage = file.type === 'image/png' || file.type === 'image/jpeg' || file.type === 'image/jpg';
+ if (!isImage) {
+ proxy.$modal.msgError("鍙兘涓婁紶 jpg銆乯peg銆乸ng 鏍煎紡鐨勫浘鐗�!");
+ return false;
+ }
+ // 鏍℃鏂囦欢澶у皬
+ const isLt10M = file.size / 1024 / 1024 < 10;
+ if (!isLt10M) {
+ proxy.$modal.msgError("涓婁紶鍥剧墖澶у皬涓嶈兘瓒呰繃 10MB!");
+ return false;
+ }
+ proxy.$modal.loading("姝e湪涓婁紶鍥剧墖锛岃绋嶅��...");
+ return true;
+}
+// 鍙戣揣鍥剧墖涓婁紶澶辫触
+function handleDeliveryUploadError(err) {
+ proxy.$modal.msgError("涓婁紶鍥剧墖澶辫触");
+ proxy.$modal.closeLoading();
+}
+// 鍙戣揣鍥剧墖涓婁紶鎴愬姛鍥炶皟
+function handleDeliveryUploadSuccess(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.deliveryFileUpload.handleRemove(file);
+ }
+}
+// 绉婚櫎鍙戣揣鍥剧墖
+function handleDeliveryRemove(file) {
+ console.log('file--', file)
+ // 濡傛灉鏄紪杈戞ā寮忎笖鏂囦欢鏈� id锛岄渶瑕佽皟鐢ㄦ帴鍙e垹闄�
+ if (operationType.value === "edit") {
+ let ids = [];
+ ids.push(file.uid);
+ delLedgerFile(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ // 浠庢枃浠跺垪琛ㄤ腑绉婚櫎
+ const index = deliveryFileList.value.findIndex(item => item.uid === file.uid);
+ if (index > -1) {
+ deliveryFileList.value.splice(index, 1);
+ }
+ }).catch(() => {
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ });
+ } else {
+ // 鏂板妯″紡鎴栨病鏈� id 鐨勬枃浠讹紝鐩存帴浠庡垪琛ㄤ腑绉婚櫎
+ const index = deliveryFileList.value.findIndex(item => item.uid === file.uid);
+ if (index > -1) {
+ deliveryFileList.value.splice(index, 1);
+ }
+ }
+}
+
+// 鍙戣揣绫诲瀷鍒囨崲鏃舵竻绌哄搴斿瓧娈�
+const handleShippingTypeChange = (val) => {
+ if (val === "璐ц溅") {
+ form.value.expressCompany = "";
+ form.value.expressNumber = "";
+ } else {
+ form.value.shippingCarNumber = "";
+ }
+};
+
+// 鑾峰彇瀹℃牳鐘舵�佹枃鏈�
+const getApprovalStatusText = (status) => {
+ if (status === null || status === undefined || status === '') {
+ return '寰呭鏍�';
+ }
+ // 濡傛灉鏄暟瀛�
+ if (typeof status === 'number') {
+ const statusMap = {
+ 0: '寰呭鏍�',
+ 1: '瀹℃牳涓�',
+ 2: '瀹℃牳鎷掔粷',
+ 3: '瀹℃牳閫氳繃'
+ };
+ return statusMap[status] || '寰呭鏍�';
+ }
+ // 濡傛灉鏄瓧绗︿覆锛岀洿鎺ヨ繑鍥炴垨鏄犲皠
+ const statusStr = String(status).trim();
+ const statusTextMap = {
+ '寰呭鏍�': '寰呭鏍�',
+ '瀹℃牳涓�': '瀹℃牳涓�',
+ '瀹℃牳鎷掔粷': '瀹℃牳鎷掔粷',
+ '瀹℃牳閫氳繃': '瀹℃牳閫氳繃',
+ '0': '寰呭鏍�',
+ '1': '瀹℃牳涓�',
+ '2': '瀹℃牳鎷掔粷',
+ '3': '瀹℃牳閫氳繃'
+ };
+ return statusTextMap[statusStr] || statusStr || '寰呭鏍�';
+};
+
+// 鑾峰彇瀹℃牳鐘舵�佹爣绛剧被鍨嬶紙棰滆壊锛�
+const getApprovalStatusType = (status) => {
+ if (status === null || status === undefined || status === '') {
+ return 'info';
+ }
+ // 濡傛灉鏄暟瀛�
+ if (typeof status === 'number') {
+ const typeMap = {
+ 0: 'info', // 寰呭鏍� - 鐏拌壊
+ 1: 'warning', // 瀹℃牳涓� - 榛勮壊
+ 2: 'danger', // 瀹℃牳鎷掔粷 - 绾㈣壊
+ 3: 'success' // 瀹℃牳閫氳繃 - 缁胯壊
+ };
+ return typeMap[status] || 'info';
+ }
+ // 濡傛灉鏄瓧绗︿覆
+ const statusStr = String(status).trim();
+ const typeTextMap = {
+ '寰呭鏍�': 'info',
+ '瀹℃牳涓�': 'warning',
+ '瀹℃牳鎷掔粷': 'danger',
+ '瀹℃牳閫氳繃': 'success',
+ '0': 'info',
+ '1': 'warning',
+ '2': 'danger',
+ '3': 'success'
+ };
+ return typeTextMap[statusStr] || 'info';
+};
+
+// 妫�鏌ュ鏍哥姸鎬佹槸鍚︿负"瀹℃牳閫氳繃"
+const isApproved = (status) => {
+ if (status === null || status === undefined || status === '') {
+ return false;
+ }
+ // 濡傛灉鏄暟瀛楋紝3 琛ㄧず瀹℃牳閫氳繃
+ if (typeof status === 'number') {
+ return status === 3;
+ }
+ // 濡傛灉鏄瓧绗︿覆
+ const statusStr = String(status).trim();
+ return statusStr === '瀹℃牳閫氳繃' || statusStr === '3';
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+.table_list {
+ margin-top: unset;
+}
+
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+// 闅愯棌鍥剧墖涓婁紶缁勪欢鐨勯瑙堟寜閽紙鏀惧ぇ闀滐級
+:deep(.el-upload-list--picture-card .el-upload-list__item-actions) {
+ .el-upload-list__item-preview {
+ display: none;
+ }
+}
+</style>
+
diff --git a/src/views/salesManagement/indicatorStats/index.vue b/src/views/salesManagement/indicatorStats/index.vue
new file mode 100644
index 0000000..65dae96
--- /dev/null
+++ b/src/views/salesManagement/indicatorStats/index.vue
@@ -0,0 +1,399 @@
+<template>
+ <div class="app-container indicator-stats">
+ <el-card class="box-card">
+ <!-- KPI 姹囨�� -->
+ <el-row :gutter="20" class="stats-row">
+ <el-col :span="8">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #ecf5ff;">
+ <el-icon :size="30" color="#409eff"><Document /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div>
+ <div class="stat-label">璁㈠崟鏁伴噺</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #f0f9ff;">
+ <el-icon :size="30" color="#67c23a"><Tickets /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">楼{{ indicatorKpis.salesAmount.toLocaleString() }}</div>
+ <div class="stat-label">閿�鍞</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #fef0f0;">
+ <el-icon :size="30" color="#e6a23c"><Van /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ indicatorKpis.shipRate }}%</div>
+ <div class="stat-label">鍙戣揣鐜�</div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+
+ <!-- 缁村害绛涢�� -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-tree-select v-model="indicatorFilter.productCategory" placeholder="浜у搧绫诲埆" clearable check-strictly
+ :data="productOptions" :render-after-expand="false" style="width: 100%" />
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="indicatorFilter.customerName" placeholder="瀹㈡埛" clearable filterable>
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName" />
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-date-picker v-model="indicatorFilter.dateRange" type="daterange" range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%" />
+ </el-col>
+ <el-col :span="6" style="text-align: right;">
+ <el-button type="primary" @click="applyIndicatorFilter">鏌ヨ</el-button>
+ <el-button @click="resetIndicatorFilter">閲嶇疆</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鍥捐〃鍖� -->
+ <div class="chart-container">
+ <div ref="indicatorChartRef" class="chart-wrapper"></div>
+ </div>
+
+ <!-- 涓氱哗缁熻锛堝洟闃熺淮搴︼紝鏃犱釜浜哄鍚嶏級 -->
+ <el-table v-if="showTeamPerformance" :data="teamPerformanceList" border stripe style="margin-top: 20px;">
+ <el-table-column prop="team" label="閿�鍞洟闃�"/>
+ <el-table-column prop="orderCount" label="璁㈠崟鏁�"/>
+ <el-table-column prop="salesAmount" label="閿�鍞">
+ <template #default="scope">楼{{ scope.row.salesAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column prop="shipRate" label="鍙戣揣鐜�">
+ <template #default="scope">{{ scope.row.shipRate }}</template>
+ </el-table-column>
+ <el-table-column prop="attainment" label="鐩爣杈炬垚鐜�">
+ <template #default="scope">
+ <el-tag :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'">
+ {{ scope.row.attainment }}%
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
+import { Document, Van, Tickets } from '@element-plus/icons-vue'
+import * as echarts from 'echarts'
+import { getTotalStatistics, getStatisticsTable } from '@/api/salesManagement/indicatorStats'
+import { productTreeList } from '@/api/basicData/product.js'
+import { customerList } from '@/api/salesManagement/salesLedger.js'
+import { ElMessage } from 'element-plus'
+
+const indicatorKpis = reactive({
+ orderCount: 0,
+ salesAmount: 0,
+ shipRate: 0
+})
+
+// 鏄惁灞曠ず閿�鍞洟闃熸槑缁嗚〃锛屾寜闇�寮�鍚�
+const showTeamPerformance = ref(false)
+const loading = ref(false)
+
+const indicatorFilter = reactive({
+ productCategory: '',
+ customerName: '',
+ dateRange: []
+})
+
+const indicatorChartRef = ref(null)
+let indicatorChart = null
+
+const productOptions = ref([])
+const customerOption = ref([])
+
+const teamPerformanceList = ref([
+ { team: '鍗庝笢澶у尯', orderCount: 320, salesAmount: 2850000, shipRate: 90, attainment: 105 },
+ { team: '鍗庡寳澶у尯', orderCount: 280, salesAmount: 2150000, shipRate: 86, attainment: 92 },
+ { team: '鍗庡崡澶у尯', orderCount: 210, salesAmount: 1850000, shipRate: 88, attainment: 78 },
+ { team: '瑗垮崡澶у尯', orderCount: 180, salesAmount: 1500000, shipRate: 83, attainment: 74 }
+])
+
+// 杞崲浜у搧鏍戞暟鎹紝灏� id 鏀逛负 value
+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 getProductOptions = () => {
+ return productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res)
+ }).catch((error) => {
+ console.error('鑾峰彇浜у搧鏍戝け璐�:', error)
+ ElMessage.error('鑾峰彇浜у搧绫诲埆澶辫触')
+ })
+}
+
+// 鑾峰彇瀹㈡埛鍒楄〃
+const getCustomerList = () => {
+ return customerList().then((res) => {
+ customerOption.value = 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 fetchTotalStatistics = async () => {
+ try {
+ loading.value = true
+ const params = {}
+ if (indicatorFilter.customerName) {
+ params.customerName = indicatorFilter.customerName
+ }
+ if (indicatorFilter.productCategory) {
+ // 鏍规嵁 id 鏌ユ壘浜у搧绫诲埆鍚嶇О
+ const categoryName = findNodeLabelById(productOptions.value, indicatorFilter.productCategory)
+ if (categoryName) {
+ params.productCategory = categoryName
+ }
+ }
+ if (indicatorFilter.dateRange && indicatorFilter.dateRange.length === 2) {
+ params.entryDateStart = indicatorFilter.dateRange[0]
+ params.entryDateEnd = indicatorFilter.dateRange[1]
+ }
+ const res = await getTotalStatistics(params)
+ if (res && res.data) {
+ indicatorKpis.orderCount = res.data.total || 0
+ indicatorKpis.salesAmount = res.data.contractAmountTotal || 0
+ // 鍙戣揣鐜囧鏋滄帴鍙f病鏈夎繑鍥烇紝淇濇寔鍘熷�兼垨璁句负0
+ // indicatorKpis.shipRate = res.data.shipRate || 0
+ }
+ } catch (error) {
+ console.error('鑾峰彇澶撮儴缁熻澶辫触:', error)
+ ElMessage.error('鑾峰彇缁熻鏁版嵁澶辫触')
+ } finally {
+ loading.value = false
+ }
+}
+
+// 鑾峰彇鏌辩姸鍥炬暟鎹�
+const fetchStatisticsTable = async () => {
+ try {
+ loading.value = true
+ const params = {}
+ if (indicatorFilter.customerName) {
+ params.customerName = indicatorFilter.customerName
+ }
+ if (indicatorFilter.productCategory) {
+ // 鏍规嵁 id 鏌ユ壘浜у搧绫诲埆鍚嶇О
+ const categoryName = findNodeLabelById(productOptions.value, indicatorFilter.productCategory)
+ if (categoryName) {
+ params.productCategory = categoryName
+ }
+ }
+ if (indicatorFilter.dateRange && indicatorFilter.dateRange.length === 2) {
+ params.entryDateStart = indicatorFilter.dateRange[0]
+ params.entryDateEnd = indicatorFilter.dateRange[1]
+ }
+ const res = await getStatisticsTable(params)
+ if (res && res.data) {
+ updateChart(res.data)
+ }
+ } catch (error) {
+ console.error('鑾峰彇鍥捐〃鏁版嵁澶辫触:', error)
+ ElMessage.error('鑾峰彇鍥捐〃鏁版嵁澶辫触')
+ } finally {
+ loading.value = false
+ }
+}
+
+// 鏇存柊鍥捐〃
+const updateChart = (chartData) => {
+ if (!indicatorChartRef.value) return
+ if (indicatorChart) indicatorChart.dispose()
+ indicatorChart = echarts.init(indicatorChartRef.value)
+
+ // 鏍规嵁鎺ュ彛杩斿洖鐨勬暟鎹粨鏋勬洿鏂板浘琛�
+ // 鎺ュ彛杩斿洖: dateList, orderCountList, salesAmountList
+ const option = {
+ title: { text: '澶氱淮搴﹂攢鍞寚鏍囪秼鍔�', left: 'center' },
+ tooltip: { trigger: 'axis' },
+ legend: { data: ['璁㈠崟鏁�', '閿�鍞'], top: 30 },
+ grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
+ xAxis: {
+ type: 'category',
+ data: chartData.dateList || []
+ },
+ yAxis: [
+ { type: 'value', name: '閲戦', position: 'left', axisLabel: { formatter: '{value}' } },
+ {
+ type: 'value',
+ name: '鏁伴噺',
+ position: 'right',
+ minInterval: 1,
+ axisLabel: {
+ formatter: (value) => {
+ const intValue = Math.round(value)
+ return intValue.toString()
+ }
+ }
+ }
+ ],
+ series: [
+ {
+ name: '璁㈠崟鏁�',
+ type: 'line',
+ yAxisIndex: 1,
+ data: chartData.orderCountList || [],
+ itemStyle: { color: '#409eff' }
+ },
+ {
+ name: '閿�鍞',
+ type: 'bar',
+ yAxisIndex: 0,
+ data: chartData.salesAmountList || [],
+ itemStyle: { color: '#67c23a' }
+ }
+ ]
+ }
+ indicatorChart.setOption(option)
+}
+
+const initIndicatorChart = () => {
+ if (!indicatorChartRef.value) return
+ if (indicatorChart) indicatorChart.dispose()
+ indicatorChart = echarts.init(indicatorChartRef.value)
+ const option = {
+ title: { text: '澶氱淮搴﹂攢鍞寚鏍囪秼鍔�', left: 'center' },
+ tooltip: { trigger: 'axis' },
+ legend: { data: ['璁㈠崟鏁�', '閿�鍞'], top: 30 },
+ grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
+ xAxis: { type: 'category', data: [] },
+ yAxis: [
+ { type: 'value', name: '閲戦', position: 'left', axisLabel: { formatter: '{value}' } },
+ {
+ type: 'value',
+ name: '鏁伴噺',
+ position: 'right',
+ minInterval: 1,
+ axisLabel: {
+ formatter: (value) => {
+ const intValue = Math.round(value)
+ return intValue.toString()
+ }
+ }
+ }
+ ],
+ series: [
+ { name: '璁㈠崟鏁�', type: 'line', yAxisIndex: 1, data: [], itemStyle: { color: '#409eff' } },
+ { name: '閿�鍞', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } }
+ ]
+ }
+ indicatorChart.setOption(option)
+}
+
+const applyIndicatorFilter = async () => {
+ await Promise.all([
+ fetchTotalStatistics(),
+ fetchStatisticsTable()
+ ])
+}
+
+const resetIndicatorFilter = () => {
+ indicatorFilter.productCategory = ''
+ indicatorFilter.customerName = ''
+ indicatorFilter.dateRange = []
+ applyIndicatorFilter()
+}
+
+// 绐楀彛澶у皬鍙樺寲鏃惰皟鏁村浘琛ㄥぇ灏�
+const handleResize = () => {
+ if (indicatorChart) {
+ indicatorChart.resize()
+ }
+}
+
+onMounted(() => {
+ nextTick(() => {
+ initIndicatorChart()
+ getProductOptions()
+ getCustomerList()
+ fetchTotalStatistics()
+ fetchStatisticsTable()
+ })
+ // 鐩戝惉绐楀彛澶у皬鍙樺寲
+ window.addEventListener('resize', handleResize)
+})
+
+onUnmounted(() => {
+ // 绉婚櫎绐楀彛澶у皬鍙樺寲鐩戝惉鍣�
+ window.removeEventListener('resize', handleResize)
+ // 閿�姣佸浘琛ㄥ疄渚�
+ if (indicatorChart) {
+ indicatorChart.dispose()
+ indicatorChart = null
+ }
+})
+</script>
+
+<style scoped>
+.indicator-stats {
+ padding: 0;
+}
+.box-card { border: none; box-shadow: none; }
+.search-row { margin-bottom: 20px; }
+.stats-row { margin-bottom: 24px; }
+.stat-card { display: flex; align-items: center; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); }
+.stat-icon { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; border-radius: 8px; margin-right: 16px; }
+.stat-content { flex: 1; }
+.stat-value { font-size: 28px; font-weight: bold; color: #303133; margin-bottom: 4px; }
+.stat-label { font-size: 14px; color: #909399; }
+.chart-container {
+ margin: 20px 0;
+ padding: 20px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ width: 100%;
+ overflow: hidden;
+}
+.chart-wrapper {
+ width: 100%;
+ height: 360px;
+ min-width: 0;
+}
+</style>
+
+
diff --git a/src/views/salesManagement/invoiceLedger/index.vue b/src/views/salesManagement/invoiceLedger/index.vue
index 4e8c274..5cd2cc3 100644
--- a/src/views/salesManagement/invoiceLedger/index.vue
+++ b/src/views/salesManagement/invoiceLedger/index.vue
@@ -31,9 +31,7 @@
<el-table-column align="center" type="selection" width="55" />
<el-table-column align="center" label="搴忓彿" type="index" width="60" />
<el-table-column label="閿�鍞悎鍚屽彿" prop="salesContractNo" show-overflow-tooltip width="180" />
- <el-table-column label="瀹㈡埛鍚堝悓鍙�" prop="customerContractNo" show-overflow-tooltip width="180" />
<el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" show-overflow-tooltip width="240" />
- <el-table-column label="椤圭洰" prop="projectName" width="320" />
<el-table-column label="浜у搧澶х被" prop="productCategory" width="200" />
<el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" width="160" show-overflow-tooltip />
<el-table-column label="鍙戠エ鍙�" prop="invoiceNo" width="200" show-overflow-tooltip />
@@ -43,28 +41,18 @@
<el-table-column label="褰曞叆浜�" prop="invoicePerson" show-overflow-tooltip />
<el-table-column label="褰曞叆鏃ユ湡" prop="createTime" show-overflow-tooltip :formatter="formatDate" width="180" />
<el-table-column label="寮�绁ㄦ棩鏈�" prop="invoiceDate" show-overflow-tooltip width="120" />
- <el-table-column label="鍙戠エ" prop="invoiceFileName" width="120" align="center" show-overflow-tooltip fixed="right">
- <template #default="scope">
- <el-button v-if="scope.row.invoiceFileName" text bg type="primary"
- @click="handleFile(scope.row.commonFiles)">
- 鏌ョ湅闄勪欢
- </el-button>
- <el-button v-else link type="primary" @click="handleDownload(scope.row)" :disabled="scope.row.invoicePerson !== userStore.nickName">
- 涓婁紶
- </el-button>
- </template>
- </el-table-column>
<el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
<template #default="scope">
- <el-button link type="primary" size="small" @click="openForm(scope.row)" :disabled="scope.row.invoicePerson !== userStore.nickName">缂栬緫</el-button>
- <el-button link type="primary" size="small" @click="delInvoiceLedger(scope.row)" :disabled="scope.row.invoicePerson !== userStore.nickName">鍒犻櫎</el-button>
+ <el-button link type="primary" size="small" @click="openForm(scope.row)">缂栬緫</el-button>
+ <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">闄勪欢</el-button>
+ <el-button link type="primary" size="small" @click="delInvoiceLedger(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" />
</div>
- <el-dialog v-model="dialogFormVisible" title="寮�绁ㄥ彴璐﹂〉闈�" width="70%" @close="closeDia">
+ <FormDialog v-model="dialogFormVisible" title="寮�绁ㄥ彴璐﹂〉闈�" :width="'70%'" @close="closeDia" @confirm="submitForm" @cancel="closeDia">
<el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
<el-row :gutter="30">
<el-col :span="12">
@@ -108,7 +96,7 @@
<el-form-item label="闄勪欢鏉愭枡锛�" prop="remark">
<el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload
:headers="upload.headers" accept=".pdf" :limit="10" :before-upload="handleBeforeUpload"
- :on-error="handleUploadError" :on-success="handleUploadSuccess" :on-remove="handleRemove">
+ :on-error="handleUploadError" :on-success="handleUploadSuccess">
<el-button type="primary">涓婁紶</el-button>
<template #tip>
<!-- 鏂囦欢鏍煎紡鏀寔 doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z-->
@@ -119,13 +107,7 @@
</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>
+ </FormDialog>
<el-dialog title="涓婁紶寮圭獥" width="50%" v-model="uploadModal">
<el-row :gutter="30">
<el-col :span="24">
@@ -149,11 +131,13 @@
</div>
</template>
</el-dialog>
+ <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" />
</div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
+import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
@@ -164,10 +148,12 @@
commitFile,
registrationProductPage,
delInvoiceLedgerByRegProductId,
-} from "../../../api/salesManagement/invoiceLedger.js";
+} from "@/api/salesManagement/invoiceLedger.js";
import useUserStore from "@/store/modules/user.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
+import FileListDialog from '@/components/Dialog/FileListDialog.vue';
+import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
@@ -185,12 +171,9 @@
searchForm: {
searchText: "",
status: false,
- invoiceDate: [
- dayjs().startOf("month").format("YYYY-MM-DD"),
- dayjs().endOf("month").format("YYYY-MM-DD"),
- ],
- invoiceDateStart: dayjs().startOf("month").format("YYYY-MM-DD"),
- invoiceDateEnd: dayjs().endOf("month").format("YYYY-MM-DD"),
+ invoiceDate: null,
+ invoiceDateStart: undefined,
+ invoiceDateEnd: undefined,
createTimeStart: "", // 褰曞叆鏃ユ湡
},
form: {
@@ -248,7 +231,11 @@
const getList = () => {
tableLoading.value = true;
const { invoiceDate, ...rest } = searchForm;
- registrationProductPage({ ...rest, ...page }).then((res) => {
+ // 灏嗚寖鍥存棩鏈熷瓧娈典紶閫掔粰鍚庣
+ const params = { ...rest, ...page };
+ // 绉婚櫎寮�绁ㄦ棩鏈熺殑榛樿鍊艰缃紝鍙繚鐣欒寖鍥存棩鏈熷瓧娈�
+ delete params.invoiceDate;
+ registrationProductPage(params).then((res) => {
tableLoading.value = false;
tableData.value = res.data.records;
total.value = res.data.total;
@@ -275,12 +262,12 @@
invoiceLedgerProductInfo({ id: row.id }).then((res) => {
form.value = { ...res.data };
fileList.value = res.data.fileList;
+ // 淇濆瓨ticketRegistrationId鍒拌〃鍗曟暟鎹腑
+ if (row.ticketRegistrationId) {
+ form.value.ticketRegistrationId = row.ticketRegistrationId;
+ }
if (!form.value.invoicePerson) {
form.value.invoicePerson = userStore.nickName;
- form.value.entryDate = getCurrentDate();
- }
- if (!form.value.invoiceDate) {
- form.value.invoiceDate = getCurrentDate();
}
});
dialogFormVisible.value = true;
@@ -294,7 +281,6 @@
};
// 涓婁紶鍓嶆牎妫�
function handleBeforeUpload(file) {
- console.log("file", file);
// 鏍℃鏂囦欢澶у皬
if (file.size > 1024 * 1024 * 50) {
proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃50MB!");
@@ -318,19 +304,25 @@
function handleUploadSuccess(res, file, uploadFiles) {
proxy.$modal.closeLoading();
if (res.code === 200) {
- proxy.$refs["fileUpload"].handleRemove(file);
- fileList.value.push(res.data);
proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+ // 灏嗕笂浼犳垚鍔熺殑鏂囦欢淇℃伅娣诲姞鍒癴ileList涓�
+ const fileInfo = {
+ name: file.name,
+ url: res.data.url || file.response?.data?.url || file.url,
+ response: file.response
+ };
+ // 妫�鏌ユ槸鍚﹀凡瀛樺湪鐩稿悓鏂囦欢锛岄伩鍏嶉噸澶嶆坊鍔�
+ const existingFileIndex = fileList.value.findIndex(f => f.name === fileInfo.name);
+ if (existingFileIndex === -1) {
+ fileList.value.push(fileInfo);
+ } else {
+ fileList.value[existingFileIndex] = fileInfo;
+ }
+ // 纭繚琛ㄥ崟鏁版嵁涓殑fileList涔熸洿鏂�
+ form.value.fileList = fileList.value;
} else {
proxy.$modal.msgError(res.msg);
proxy.$refs.fileUpload.handleRemove(file);
- }
-}
-// 绉婚櫎鏂囦欢
-function handleRemove(file) {
- let index = fileList.value.findIndex((item) => item.url === file.url);
- if (index > -1) {
- fileList.value.splice(index, 1);
}
}
// 鎻愪氦琛ㄥ崟
@@ -364,13 +356,6 @@
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
});
-};
-
-// 鎵撳紑闄勪欢涓婁紶寮圭獥
-const handleDownload = (val) => {
- fileList.value = [];
- uploadModal.value = true;
- currentId.value = val.id;
};
// 纭鏂囦欢涓婁紶
@@ -407,14 +392,6 @@
});
};
-// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� 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 changeDateRange = (date) => {
if (date) {
searchForm.invoiceDateStart = date[0];
@@ -436,6 +413,19 @@
getList();
};
+//闄勪欢鐩稿叧
+const fileListRef = ref(null)
+const fileListDialogVisible = ref(false)
+//鏌ョ湅闄勪欢
+const downLoadFile = (row) => {
+ invoiceLedgerProductInfo({ id: row.id }).then((res) => {
+ if (fileListRef.value) {
+ fileListRef.value.open(res.data.fileList)
+ fileListDialogVisible.value = true
+ }
+ });
+}
+
onMounted(() => {
getList();
});
diff --git a/src/views/salesManagement/invoiceRegistration/index.vue b/src/views/salesManagement/invoiceRegistration/index.vue
index 9d39684..9cc1d66 100644
--- a/src/views/salesManagement/invoiceRegistration/index.vue
+++ b/src/views/salesManagement/invoiceRegistration/index.vue
@@ -1,424 +1,388 @@
<template>
- <div class="app-container">
- <div class="search_form">
- <el-form :inline="true" :model="searchForm">
- <el-form-item label="瀹㈡埛鍚嶇О">
- <el-input
- v-model="searchForm.customerName"
- style="width: 240px"
- placeholder="璇疯緭鍏ュ悕绉版悳绱�"
- clearable
- :prefix-icon="Search"
- @change="handleQuery"
- />
- </el-form-item>
- <el-form-item label="瀹㈡埛鍚堝悓鍙�">
- <el-input
- v-model="searchForm.customerContractNo"
- placeholder="璇疯緭鍏ュ鎴峰悎鍚屽彿"
- clearable
- />
- </el-form-item>
- <el-form-item label="椤圭洰鍚嶇О">
- <el-input
- v-model="searchForm.projectName"
- placeholder="璇疯緭鍏ラ」鐩悕绉�"
- clearable
- />
- </el-form-item>
- <el-form-item>
- <el-checkbox
- v-model="searchForm.status"
- label="涓嶆樉绀烘湭寮�绁ㄩ噾棰濅负0"
- @change="handleQuery"
- />
- </el-form-item>
- <br/>
- <el-form-item label="鍚堝悓褰曞叆鏃ユ湡">
- <el-date-picker style="width: 240px" v-model="searchForm.commonDate" value-format="YYYY-MM-DD"
- format="YYYY-MM-DD" type="daterange" start-placeholder="寮�濮嬫椂闂�" end-placeholder="缁撴潫鏃堕棿" clearable
- @change="changeDateRange" @clear="clearRange" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
- <el-button @click="resetForm"> 閲嶇疆 </el-button>
- </el-form-item>
- </el-form>
- </div>
- <div class="table_list">
- <div class="flex justify-between">
- <div></div>
- <el-button type="primary" @click="openForm" style="margin-bottom: 8px">
- 鏂板鐧昏
- </el-button>
- </div>
- <el-table
- :data="tableData"
- :border="true"
- height="calc(100vh - 21em)"
- v-loading="tableLoading"
- :expand-row-keys="expandedRowKeys"
- :row-key="(row) => row.id"
- show-summary
- :summary-method="summarizeMainTable"
- @expand-change="expandChange"
- @selection-change="handleSelectionChange"
- stripe
- >
- <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"
- stripe
- >
- <el-table-column
- align="center"
- label="搴忓彿"
- type="index"
- width="60"
- />
- <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="invoiceNum"
- :formatter="formattedNumber"
- />
- <el-table-column
- label="寮�绁ㄩ噾棰�(鍏�)"
- prop="invoiceAmount"
- :formatter="formattedNumber"
- />
- <el-table-column
- label="鏈紑绁ㄦ暟"
- prop="noInvoiceNum"
- :formatter="formattedNumber"
- />
- <el-table-column
- label="鏈紑绁ㄩ噾棰�(鍏�)"
- prop="noInvoiceAmount"
- :formatter="formattedNumber"
- />
- </el-table>
- </template>
- </el-table-column>
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
- <el-table-column label="鍚堝悓褰曞叆鏃ユ湡" prop="entryDate" width="120" />
- <el-table-column
- label="閿�鍞悎鍚屽彿"
- prop="salesContractNo"
- show-overflow-tooltip
- width="200"
- />
- <el-table-column
- label="瀹㈡埛鍚堝悓鍙�"
- prop="customerContractNo"
- width="200"
- show-overflow-tooltip
- />
- <el-table-column
- label="瀹㈡埛鍚嶇О"
- prop="customerName"
- show-overflow-tooltip
- width="240"
- />
- <el-table-column label="涓氬姟鍛�" prop="salesman" show-overflow-tooltip width="90"/>
- <el-table-column
- label="椤圭洰鍚嶇О"
- prop="projectName"
- show-overflow-tooltip
- width="200"
- />
- <el-table-column
- label="鍚堝悓閲戦(鍏�)"
- prop="contractAmount"
- show-overflow-tooltip
- :formatter="formattedNumber"
- width="220"
-
- />
- <el-table-column
- label="宸插紑绁ㄩ噾棰�(鍏�)"
- prop="invoiceTotal"
- show-overflow-tooltip
- :formatter="formattedNumber"
- width="120"
- />
- <el-table-column
- label="鏈紑绁ㄩ噾棰�(鍏�)"
- prop="noInvoiceAmountTotal"
- show-overflow-tooltip
- width="120"
- >
- <template #default="{ row, column }">
- <el-text type="danger">
- {{ formattedNumber(row, column, row.noInvoiceAmountTotal) }}
- </el-text>
- </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="鏂板寮�绁ㄧ櫥璁伴〉闈�"
- width="85%"
- @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="salesContractNo">
- <el-input v-model="form.salesContractNo" disabled></el-input>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
- <el-input
- v-model="form.customerName"
- placeholder="鑷姩濉厖"
- disabled
- ></el-input>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="涓氬姟鍛橈細" prop="salesman">
- <el-input
- v-model="form.salesman"
- placeholder="鑷姩濉厖"
- disabled
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="椤圭洰鍚嶇О锛�" prop="projectName">
- <el-input
- v-model="form.projectName"
- placeholder="鑷姩濉厖"
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="褰曞叆浜�" prop="createUer">
- <el-input v-model="form.createUer" placeholder="璇疯緭鍏ュ綍鍏ヤ汉" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="寮�绁ㄦ棩鏈�" prop="issueDate">
- <el-date-picker
- style="width: 100%"
- v-model="form.issueDate"
- type="date"
- placeholder="璇烽�夋嫨"
- clearable
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="褰曞叆鏃ユ湡锛�" prop="createTime">
- <el-date-picker
- style="width: 100%"
- v-model="form.createTime"
- type="date"
- placeholder="璇烽�夋嫨"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍙戠エ鍙风爜锛�" prop="invoiceNo">
- <el-input
- v-model="form.invoiceNo"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-form-item label="浜у搧淇℃伅锛�" prop="entryDate"> </el-form-item>
- </el-row>
- <el-table
- :data="productData"
- 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"
- width="150"
- />
- <el-table-column label="鍗曚綅" prop="unit" />
- <el-table-column label="鏁伴噺" prop="quantity" width="70" />
- <el-table-column label="绋庣巼(%)" prop="taxRate" width="80" />
- <el-table-column
- label="鍚◣鍗曚环(鍏�)"
- prop="taxInclusiveUnitPrice"
- :formatter="formattedNumber"
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :inline="true" :model="searchForm">
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <el-input
+ v-model="searchForm.customerName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ュ悕绉版悳绱�"
+ clearable
+ :prefix-icon="Search"
+ @change="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-checkbox
+ v-model="searchForm.status"
+ label="涓嶆樉绀烘湭寮�绁ㄩ噾棰濅负0"
+ @change="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
+ <el-button @click="resetForm"> 閲嶇疆 </el-button>
+ <el-button @click="handleExport" style="margin-right: 10px">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div class="flex justify-between">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="openForm" style="margin-bottom: 8px">
+ 寮�绁ㄧ櫥璁�
+ </el-button>
+ </div>
+ </div>
+ <el-table
+ :data="tableData"
+ :border="true"
+ height="calc(100vh - 21em)"
+ v-loading="tableLoading"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.id"
+ show-summary
+ :summary-method="summarizeMainTable"
+ @expand-change="expandChange"
+ @selection-change="handleSelectionChange"
+ >
+ <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"
+ 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="invoiceNum"
+ :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="寮�绁ㄩ噾棰�(鍏�)"
+ prop="invoiceAmount"
+ :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="鏈紑绁ㄦ暟"
+ prop="noInvoiceNum"
+ :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="鏈紑绁ㄩ噾棰�(鍏�)"
+ prop="noInvoiceAmount"
+ :formatter="formattedNumber"
+ />
+ </el-table>
+ </template>
+ </el-table-column>
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column
+ label="閿�鍞悎鍚屽彿"
+ prop="salesContractNo"
+ show-overflow-tooltip
+ />
+<!-- <el-table-column-->
+<!-- label="瀹㈡埛鍚堝悓鍙�"-->
+<!-- prop="customerContractNo"-->
+<!-- width="200"-->
+<!-- show-overflow-tooltip-->
+<!-- />-->
+ <el-table-column
+ label="瀹㈡埛鍚嶇О"
+ prop="customerName"
+ show-overflow-tooltip
+ />
+ <el-table-column label="涓氬姟鍛�" prop="salesman" show-overflow-tooltip/>
+ <el-table-column
+ label="鍚堝悓閲戦(鍏�)"
+ prop="contractAmount"
+ show-overflow-tooltip
+ :formatter="formattedNumber"
+
+ />
+ <el-table-column
+ label="宸插紑绁ㄩ噾棰�(鍏�)"
+ prop="invoiceTotal"
+ show-overflow-tooltip
+ :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="鏈紑绁ㄩ噾棰�(鍏�)"
+ prop="noInvoiceAmountTotal"
+ show-overflow-tooltip
+ width="120"
+ >
+ <template #default="{ row, column }">
+ <el-text type="danger">
+ {{ formattedNumber(row, column, row.noInvoiceAmountTotal) }}
+ </el-text>
+ </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>
+ <FormDialog
+ v-model="dialogFormVisible"
+ title="鏂板寮�绁ㄧ櫥璁伴〉闈�"
+ :width="'85%'"
+ @close="closeDia"
+ @confirm="submitForm"
+ @cancel="closeDia"
+ >
+ <el-form
+ :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�" prop="salesContractNo">
+ <el-input v-model="form.salesContractNo" disabled placeholder="澶氬悎鍚屾壒閲忓鐞嗭紙鍏蜂綋鍚堝悓鍙疯浜у搧鍒楄〃锛�"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
+ <el-input
+ v-model="form.customerName"
+ placeholder="鑷姩濉厖"
+ disabled
+ ></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="涓氬姟鍛橈細" prop="salesman">
+ <el-input
+ v-model="form.salesman"
+ placeholder="鑷姩濉厖"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙风爜锛�" prop="invoiceNo">
+ <el-input
+ v-model="form.invoiceNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="寮�绁ㄦ棩鏈�" prop="issueDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.issueDate"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="褰曞叆浜�" prop="createUer">
+ <el-input v-model="form.createUer" placeholder="璇疯緭鍏ュ綍鍏ヤ汉" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰曞叆鏃ユ湡锛�" prop="createTime">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.createTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+
+ </el-row>
+ <el-row>
+ <el-form-item label="浜у搧淇℃伅锛�" prop="entryDate"> </el-form-item>
+ </el-row>
+ <el-table
+ :data="productData"
+ border
+ show-summary
+ :summary-method="summarizeChildrenTable"
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="鎵�灞炲悎鍚�" prop="salesContractNo" width="200">
+ <template #default="{ row }">
+ <el-tag type="primary">{{ row.salesContractNo }}</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" />
+ <el-table-column label="鏁伴噺" prop="quantity" width="70" />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" width="80" />
+ <el-table-column
+ label="鍚◣鍗曚环(鍏�)"
+ prop="taxInclusiveUnitPrice"
+ :formatter="formattedNumber"
width="200"
- />
- <el-table-column
- label="鍚◣鎬讳环(鍏�)"
- prop="taxInclusiveTotalPrice"
- :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="鍚◣鎬讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ :formatter="formattedNumber"
width="200"
- />
- <el-table-column
- label="涓嶅惈绋庢�讳环(鍏�)"
- prop="taxExclusiveTotalPrice"
- :formatter="formattedNumber"
- width="150"
- />
- <el-table-column label="鏈寮�绁ㄦ暟" prop="currentInvoiceNum" width="180">
- <template #default="scope">
- <el-input-number :step="0.1" :min="0" style="width: 100%"
+ />
+ <el-table-column
+ label="涓嶅惈绋庢�讳环(鍏�)"
+ prop="taxExclusiveTotalPrice"
+ :formatter="formattedNumber"
+ width="150"
+ />
+ <el-table-column label="鏈寮�绁ㄦ暟" prop="currentInvoiceNum" width="180">
+ <template #default="scope">
+ <el-input-number :step="0.1" :min="0" style="width: 100%"
:precision="2"
- v-model="scope.row.currentInvoiceNum"
- @change="invoiceNumBlur(scope.row)"
- ></el-input-number>
- </template>
- </el-table-column>
- <el-table-column
- label="鏈寮�绁ㄩ噾棰�(鍏�)"
- prop="currentInvoiceAmount"
- width="180"
- >
- <template #default="scope">
- <el-input-number :step="0.01" :min="0" style="width: 100%"
+ v-model="scope.row.currentInvoiceNum"
+ @change="invoiceNumBlur(scope.row)"
+ ></el-input-number>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鏈寮�绁ㄩ噾棰�(鍏�)"
+ prop="currentInvoiceAmount"
+ width="180"
+ >
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
:precision="2"
- v-model="scope.row.currentInvoiceAmount"
- @change="invoiceAmountBlur(scope.row)"
- ></el-input-number>
- </template>
- </el-table-column>
- <el-table-column label="鏈紑绁ㄦ暟" prop="noInvoiceNum" width="120">
- <template #default="scope">
- <el-input
- type="number"
- min="0"
- disabled
- v-model="scope.row.noInvoiceNum"
- ></el-input>
- </template>
- </el-table-column>
- <el-table-column
- label="鏈紑绁ㄩ噾棰�(鍏�)"
- prop="noInvoiceAmount"
- width="200"
- >
- <template #default="scope">
- <el-input
- type="number"
- min="0"
- disabled
- v-model="scope.row.noInvoiceAmount"
- :formatter="formattedInputNumber"
- :precision="2"
- :step="0.01"
- ></el-input>
- </template>
- </el-table-column>
- <el-table-column label="鐧昏浜�" prop="register" width="100">
- <!-- <template #default="{ row }">
- <el-input
- v-model="row.register"
- placeholder="璇疯緭鍏ョ櫥璁颁汉"
- disabled
- />
- </template> -->
- </el-table-column>
- <el-table-column label="鐧昏鏃ユ湡" prop="registerDate" width="150">
- <!-- <template #default="{ row }">
- <el-date-picker
- style="width: 100%"
- v-model="row.registerDate"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- type="date"
- placeholder="璇烽�夋嫨"
- clearable
- disabled
- />
- </template> -->
- </el-table-column>
- </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>
+ v-model="scope.row.currentInvoiceAmount"
+ @change="invoiceAmountBlur(scope.row)"
+ ></el-input-number>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏈紑绁ㄦ暟" prop="noInvoiceNum" width="120">
+ <template #default="scope">
+ <el-input
+ type="number"
+ min="0"
+ disabled
+ v-model="scope.row.noInvoiceNum"
+ ></el-input>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鏈紑绁ㄩ噾棰�(鍏�)"
+ prop="noInvoiceAmount"
+ width="200"
+ >
+ <template #default="scope">
+ <el-input
+ type="number"
+ min="0"
+ disabled
+ v-model="scope.row.noInvoiceAmount"
+ :formatter="formattedInputNumber"
+ :precision="2"
+ :step="0.01"
+ ></el-input>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐧昏浜�" prop="register" width="100">
+ <!-- <template #default="{ row }">
+ <el-input
+ v-model="row.register"
+ placeholder="璇疯緭鍏ョ櫥璁颁汉"
+ disabled
+ />
+ </template> -->
+ </el-table-column>
+ <el-table-column label="鐧昏鏃ユ湡" prop="registerDate" width="150">
+ <!-- <template #default="{ row }">
+ <el-date-picker
+ style="width: 100%"
+ v-model="row.registerDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ disabled
+ />
+ </template> -->
+ </el-table-column>
+ </el-table>
+ </el-form>
+ </FormDialog>
+ </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
+import FormDialog from '@/components/Dialog/FormDialog.vue';
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
// import {userListNoPage} from "@/api/system/user.js";
import {
- getSalesLedgerWithProducts,
- ledgerListPage,
- productList,
+ getSalesLedgerWithProducts,
+ ledgerListPage,
+ productList,
} from "@/api/salesManagement/salesLedger.js";
import { invoiceRegistrationSave } from "@/api/salesManagement/invoiceRegistration.js";
import useFormData from "@/hooks/useFormData";
@@ -432,261 +396,377 @@
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
- current: 1,
- size: 100,
+ current: 1,
+ size: 100,
});
const total = ref(0);
// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
- searchForm: {
- customerName: "",
- status: false,
- customerContractNo: undefined, // 瀹㈡埛鍚堝悓鍙�
- projectName: undefined, // 椤圭洰鍚嶇О
- createUer: undefined, // 鐧昏浜�
- issueDate: undefined, // 寮�绁ㄦ棩鏈�
- createTime: undefined, // 褰曞叆鏃ユ湡锛�
- },
- form: {
- salesLedgerId: "",
- customerName: "",
- salesman: "",
- projectName: "",
- productData: [],
- invoiceNo: "",
- createUer: userStore.nickName,
- issueDate: dayjs().format("YYYY-MM-DD"),
- },
- rules: {
- salesLedgerId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- createUer: [{ required: true, message: "璇烽�夋嫨", trigger: "blur" }],
- issueDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- invoiceNo: [{ required: true, message: "璇疯緭鍏�", trigger: "change" }],
- createTime: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- },
+ searchForm: {
+ customerName: "",
+ status: false,
+ customerContractNo: undefined, // 瀹㈡埛鍚堝悓鍙�
+ projectName: undefined, // 椤圭洰鍚嶇О
+ createUer: undefined, // 鐧昏浜�
+ issueDate: undefined, // 寮�绁ㄦ棩鏈�
+ createTime: undefined, // 褰曞叆鏃ユ湡锛�
+ productCategory: "",
+ isInvoice: 1
+ },
+ form: {
+ salesLedgerId: "",
+ customerName: "",
+ salesman: "",
+ projectName: "",
+ productData: [],
+ invoiceNo: "",
+ createUer: userStore.nickName,
+ issueDate: dayjs().format("YYYY-MM-DD"),
+ selectedContractIds: [], // 鏂板锛氬瓨鍌ㄦ墍鏈夐�変腑鐨勫悎鍚孖D
+ isBatch: false // 鏂板锛氭爣璇嗘槸鍚︿负鎵归噺鎿嶄綔
+ },
+ rules: {
+ createUer: [{ required: true, message: "璇烽�夋嫨", trigger: "blur" }],
+ issueDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ invoiceNo: [{ required: true, message: "璇疯緭鍏�", trigger: "change" }],
+ createTime: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
});
const { form, rules } = toRefs(data);
const { form: searchForm, resetForm } = useFormData(data.searchForm);
-
-const changeDateRange = (date) => {
- if (date) {
- searchForm.entryDateStart = date[0];
- searchForm.entryDateEnd = date[1];
- getList();
- }
-};
-
-const clearRange = () => {
- searchForm.commonDate = [];
- searchForm.entryDateStart = undefined;
- searchForm.entryDateEnd = undefined;
- getList();
-};
const formattedNumber = (row, column, cellValue) => {
- if (cellValue == 0) {
- return parseFloat(cellValue).toFixed(2);
- }
- if (cellValue) {
- return parseFloat(cellValue).toFixed(2);
- } else {
- return cellValue;
- }
+ if (cellValue == 0) {
+ return parseFloat(cellValue).toFixed(2);
+ }
+ if (cellValue) {
+ return parseFloat(cellValue).toFixed(2);
+ } else {
+ return cellValue;
+ }
};
const formattedInputNumber = (value) => {
- return value ? parseFloat(value).toFixed(2) : 0;
+ return value ? parseFloat(value).toFixed(2) : 0;
};
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
const handleQuery = () => {
- page.current = 1;
- getList();
+ page.current = 1;
+ getList();
};
const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
};
const getList = () => {
- tableLoading.value = true;
- ledgerListPage({ ...searchForm, ...page }).then((res) => {
- tableLoading.value = false;
- tableData.value = res.records;
- total.value = res.total;
- expandedRowKeys.value = [];
- });
+ tableLoading.value = true;
+ ledgerListPage({ ...searchForm, ...page }).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.records;
+ total.value = res.total;
+ expandedRowKeys.value = [];
+ });
};
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
- console.log("selection", selection);
- selectedRows.value = selection.filter(
- (item) => item.salesContractNo !== undefined
- );
+ console.log("selection", selection);
+ selectedRows.value = selection.filter(
+ (item) => item.salesContractNo !== undefined
+ );
};
const expandedRowKeys = ref([]);
// 灞曞紑琛�
const expandChange = (row, expandedRows) => {
- if (expandedRows.length > 0) {
- expandedRowKeys.value = [];
- try {
- productList({ salesLedgerId: row.id, type: 1 }).then((res) => {
- 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.log(error);
- }
- } else {
- expandedRowKeys.value = [];
- }
+ if (expandedRows.length > 0) {
+ expandedRowKeys.value = [];
+ try {
+ productList({ salesLedgerId: row.id, type: 1 }).then((res) => {
+ 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.log(error);
+ }
+ } else {
+ expandedRowKeys.value = [];
+ }
};
// 涓昏〃鍚堣鏂规硶
const summarizeMainTable = (param) => {
- return proxy.summarizeTable(param, [
- "contractAmount",
- "invoiceTotal",
- "noInvoiceAmountTotal",
- ]);
+ return proxy.summarizeTable(param, [
+ "contractAmount",
+ "invoiceTotal",
+ "noInvoiceAmountTotal",
+ ]);
};
// 瀛愯〃鍚堣鏂规硶
const summarizeChildrenTable = (param) => {
- return proxy.summarizeTable(param, [
- "taxInclusiveUnitPrice",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- "invoiceNum",
- "invoiceAmount",
- "currentInvoiceAmount",
- "noInvoiceNum",
- "noInvoiceAmount",
- "currentInvoiceNum",
- ]);
+ return proxy.summarizeTable(param, [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ "invoiceNum",
+ "invoiceAmount",
+ "currentInvoiceAmount",
+ "noInvoiceNum",
+ "noInvoiceAmount",
+ "currentInvoiceNum",
+ ]);
};
// 鎵撳紑寮规
const openForm = () => {
- // 鍒ゆ柇鏄惁澶氶��
- if (selectedRows.value.length != 1) {
- proxy.$modal.msgError("璇烽�夋嫨涓�鏉″悎鍚�");
- return;
- }
- form.value = {};
- productData.value = [];
- getSalesLedgerWithProducts({ id: selectedRows.value[0].id }).then((res) => {
- form.value = { ...res };
- form.value.createTime = dayjs().format("YYYY-MM-DD");
- form.value.issueDate = dayjs().format("YYYY-MM-DD");
- form.value.createUer = userStore.nickName;
- productData.value = form.value.productData.map((item) => {
- return item;
- });
- dialogFormVisible.value = true;
- console.log("productData.value ", productData.value);
- });
+ // 鍒ゆ柇鏄惁閫夋嫨浜嗗悎鍚�
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgError("璇疯嚦灏戦�夋嫨涓�鏉″悎鍚�");
+ return;
+ }
+
+ // 妫�鏌ユ墍鏈夐�夋嫨鐨勫悎鍚屾槸鍚﹀叿鏈夌浉鍚岀殑瀹㈡埛鍚嶇О
+ const firstRow = selectedRows.value[0];
+ const isSameCustomer = selectedRows.value.every(row =>
+ row.customerName === firstRow.customerName
+ );
+
+ if (!isSameCustomer) {
+ proxy.$modal.msgError("璇烽�夋嫨鐩稿悓瀹㈡埛鍚嶇О鐨勫悎鍚�");
+ return;
+ }
+
+ // 鍏佽涓嶅悓鐨勯攢鍞悎鍚屽彿鎵归噺澶勭悊锛屾棤闇�妫�鏌ラ噸澶�
+
+ form.value = {};
+ productData.value = [];
+
+ // 鍔犺浇鎵�鏈夐�変腑鍚堝悓鐨勪骇鍝佹暟鎹�
+ const promises = selectedRows.value.map(row =>
+ getSalesLedgerWithProducts({ id: row.id })
+ );
+
+ Promise.all(promises).then(results => {
+ // 鍚堝苟鎵�鏈夊悎鍚岀殑浜у搧鏁版嵁锛屽苟涓烘瘡涓骇鍝佹坊鍔犲搴旂殑鍚堝悓淇℃伅
+ const allProductData = [];
+ results.forEach((result, index) => {
+ const contract = selectedRows.value[index];
+ // const contractId = contract.id;
+ if (result.productData) {
+ result.productData.forEach(item => {
+ allProductData.push({
+ ...item,
+ // id: contractId, // 鏄庣‘璁剧疆鍚堝悓ID
+ salesContractNo: contract.salesContractNo, // 娣诲姞閿�鍞悎鍚屽彿
+ customerName: contract.customerName, // 娣诲姞瀹㈡埛鍚嶇О
+ customerContractNo: contract.customerContractNo // 娣诲姞瀹㈡埛鍚堝悓鍙�
+ });
+ });
+ }
+ });
+
+ // 璁剧疆琛ㄥ崟鏁版嵁锛堜娇鐢ㄧ涓�涓悎鍚岀殑鍩烘湰淇℃伅锛岄攢鍞悎鍚屽彿鐣欑┖锛�
+ form.value = { ...results[0] };
+ form.value.createTime = dayjs().format("YYYY-MM-DD");
+ form.value.issueDate = dayjs().format("YYYY-MM-DD");
+ form.value.createUer = userStore.nickName;
+ form.value.selectedContractIds = selectedRows.value.map(row => row.id); // 瀛樺偍鎵�鏈夐�変腑鐨勫悎鍚孖D
+ form.value.salesContractNo = ""; // 閿�鍞悎鍚屽彿鐣欑┖锛屽洜涓轰細鍦ㄤ骇鍝佽〃鏍间腑鍒嗗埆鏄剧ず
+
+ productData.value = allProductData;
+
+ dialogFormVisible.value = true;
+ console.log("productData.value ", productData.value);
+ });
};
// 鎻愪氦琛ㄥ崟
const submitForm = () => {
- proxy.$refs["formRef"].validate((valid) => {
- if (valid) {
- form.value.productData = proxy.HaveJson(productData.value);
- invoiceRegistrationSave(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
- }
- });
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ // 濡傛灉鏄壒閲忔搷浣滐紝灏嗘墍鏈夊悎鍚岀殑鏁版嵁鏀惧湪涓�涓暟缁勯噷锛屽彧璋冪敤涓�娆℃帴鍙�
+ if (selectedRows.value.length > 1) {
+ // 鍒涘缓鍖呭惈鎵�鏈夊悎鍚屾暟鎹殑鏁扮粍
+ const batchData = selectedRows.value.map(contract => {
+ // 绛涢�夊嚭灞炰簬褰撳墠鍚堝悓鐨勪骇鍝佹暟鎹�
+ const contractProductData = productData.value.filter(item =>
+ item.salesLedgerId === contract.id
+ );
+
+ // 涓烘瘡涓攢鍞悎鍚屽彿鍒涘缓鐙珛鐨勫璞�
+ return {
+ // 鍩虹琛ㄥ崟鏁版嵁
+ issueDate: form.value.issueDate,
+ createTime: form.value.createTime,
+ createUer: form.value.createUer,
+ invoiceNo: form.value.invoiceNo,
+
+ // 鍚堝悓瀹為檯淇℃伅
+ id: contract.id, // 浣跨敤id浣滀负瀛楁鍚嶏紝鍊间负salesLedgerId
+ salesContractNo: contract.salesContractNo, // 浣跨敤瀹為檯鐨勯攢鍞悎鍚屽彿
+ customerName: contract.customerName, // 浣跨敤瀹為檯鐨勫鎴峰悕绉�
+ customerId: contract.customerId, // 娣诲姞瀹㈡埛ID
+ customerContractNo: contract.customerContractNo, // 浣跨敤瀹為檯鐨勫鎴峰悎鍚屽彿
+ projectName: contract.projectName, // 浣跨敤瀹為檯鐨勯」鐩悕绉�
+ salesman: contract.salesman, // 浣跨敤瀹為檯鐨勪笟鍔″憳
+
+ // 浜у搧鏁版嵁
+ productData: proxy.HaveJson(contractProductData),
+
+ // 鎵归噺鏍囪瘑
+ isBatch: true
+ };
+ });
+
+ // 鍙皟鐢ㄤ竴娆℃帴鍙o紝浼犻�掑寘鍚墍鏈夊悎鍚屾暟鎹殑鏁扮粍
+ invoiceRegistrationSave(batchData).then(() => {
+ proxy.$modal.msgSuccess("鎵归噺鏂板鎴愬姛");
+ closeDia();
+ getList();
+ });
+ } else {
+ // 鍗曚釜鍚堝悓鎻愪氦閫昏緫 - 涔熶互鏁扮粍褰㈠紡浼犻��
+ const singleContract = selectedRows.value[0];
+ const singleFormArray = [
+ {
+ // 鍩虹琛ㄥ崟鏁版嵁
+ issueDate: form.value.issueDate,
+ createTime: form.value.createTime,
+ createUer: form.value.createUer,
+ invoiceNo: form.value.invoiceNo,
+
+ // 鍚堝悓瀹為檯淇℃伅
+ id: singleContract.id, // 浣跨敤id浣滀负瀛楁鍚嶏紝鍊间负salesLedgerId
+ salesContractNo: singleContract.salesContractNo, // 浣跨敤瀹為檯鐨勯攢鍞悎鍚屽彿
+ customerName: singleContract.customerName, // 浣跨敤瀹為檯鐨勫鎴峰悕绉�
+ customerId: singleContract.customerId, // 娣诲姞瀹㈡埛ID
+ customerContractNo: singleContract.customerContractNo, // 浣跨敤瀹為檯鐨勫鎴峰悎鍚屽彿
+ projectName: singleContract.projectName, // 浣跨敤瀹為檯鐨勯」鐩悕绉�
+ salesman: singleContract.salesman, // 浣跨敤瀹為檯鐨勪笟鍔″憳
+
+ // 浜у搧鏁版嵁
+ productData: proxy.HaveJson(productData.value),
+
+ // 鎵归噺鏍囪瘑
+ isBatch: false
+ }
+ ];
+ invoiceRegistrationSave(singleFormArray).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+ }
+ }
+ });
};
// 鍏抽棴寮规
const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
};
// 瀵煎嚭
const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/invoiceRegistration/export", {}, "寮�绁ㄧ櫥璁颁俊鎭�.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/invoiceRegistration/export", {}, "寮�绁ㄧ櫥璁颁俊鎭�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 瀵煎嚭閿�鍞彴璐�
+const handleExport = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/sales/ledger/exportOne", { ...searchForm, ...page }, "寮�绁ㄧ櫥璁�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
//鏈寮�绁ㄥけ鐒︽搷浣�
const invoiceNumBlur = (row) => {
- if (!row.currentInvoiceNum) {
- row.currentInvoiceNum = 0;
- }
- if (row.currentInvoiceNum > row.tempNoInvoiceNum) {
- proxy.$modal.msgWarning("鏈寮�绁ㄦ暟涓嶅緱澶т簬鏈紑绁ㄦ暟");
- row.currentInvoiceNum = 0;
- }
- // 璁$畻鏈寮�绁ㄩ噾棰�
- row.currentInvoiceAmount = (
- row.currentInvoiceNum * row.taxInclusiveUnitPrice
- ).toFixed(2);
- // 璁$畻鏈紑绁ㄦ暟
- row.noInvoiceNum = (row.originalNoInvoiceNum - row.currentInvoiceNum).toFixed(
- 2
- );
- // 璁$畻鏈紑绁ㄩ噾棰�
- row.noInvoiceAmount = (
- row.tempnoInvoiceAmount - row.currentInvoiceAmount
- ).toFixed(2);
+ if (!row.currentInvoiceNum) {
+ row.currentInvoiceNum = 0;
+ }
+ if (row.currentInvoiceNum > row.tempNoInvoiceNum) {
+ proxy.$modal.msgWarning("鏈寮�绁ㄦ暟涓嶅緱澶т簬鏈紑绁ㄦ暟");
+ row.currentInvoiceNum = 0;
+ }
+ // 璁$畻鏈寮�绁ㄩ噾棰�
+ row.currentInvoiceAmount = (
+ row.currentInvoiceNum * row.taxInclusiveUnitPrice
+ ).toFixed(2);
+ // 璁$畻鏈紑绁ㄦ暟
+ row.noInvoiceNum = (row.originalNoInvoiceNum - row.currentInvoiceNum).toFixed(
+ 2
+ );
+ // 璁$畻鏈紑绁ㄩ噾棰�
+ row.noInvoiceAmount = (
+ row.tempnoInvoiceAmount - row.currentInvoiceAmount
+ ).toFixed(2);
};
// 鏈寮�绁ㄩ噾棰濆け鐒︽搷浣�
const invoiceAmountBlur = (row) => {
- if (!row.currentInvoiceAmount) {
- row.currentInvoiceAmount = 0;
- }
- // 璁$畻鏄惁瓒呰繃寮�绁ㄦ�婚噾棰�
- if (row.currentInvoiceAmount > row.tempnoInvoiceAmount) {
- proxy.$modal.msgWarning("鏈寮�绁ㄩ噾棰濅笉寰楀ぇ浜庢湭寮�绁ㄩ噾棰�");
- row.currentInvoiceAmount = 0;
- }
- // 璁$畻鏈寮�绁ㄦ暟
- row.currentInvoiceNum = (
- row.currentInvoiceAmount / row.taxInclusiveUnitPrice
- ).toFixed(2);
- console.log("row.currentInvoiceNum ", row.currentInvoiceNum);
- console.log(" row.originalNoInvoiceNum ", row.originalNoInvoiceNum);
- // 璁$畻鏈紑绁ㄦ暟
- row.noInvoiceNum = (row.originalNoInvoiceNum - row.currentInvoiceNum).toFixed(
- 2
- );
- // 璁$畻鏈紑绁ㄩ噾棰�
- row.noInvoiceAmount = (
- row.tempnoInvoiceAmount - row.currentInvoiceAmount
- ).toFixed(2);
+ if (!row.currentInvoiceAmount) {
+ row.currentInvoiceAmount = 0;
+ }
+ // 璁$畻鏄惁瓒呰繃寮�绁ㄦ�婚噾棰�
+ if (row.currentInvoiceAmount > row.tempnoInvoiceAmount) {
+ proxy.$modal.msgWarning("鏈寮�绁ㄩ噾棰濅笉寰楀ぇ浜庢湭寮�绁ㄩ噾棰�");
+ row.currentInvoiceAmount = 0;
+ }
+ // 璁$畻鏈寮�绁ㄦ暟
+ row.currentInvoiceNum = (
+ row.currentInvoiceAmount / row.taxInclusiveUnitPrice
+ ).toFixed(2);
+ console.log("row.currentInvoiceNum ", row.currentInvoiceNum);
+ console.log(" row.originalNoInvoiceNum ", row.originalNoInvoiceNum);
+ // 璁$畻鏈紑绁ㄦ暟
+ row.noInvoiceNum = (row.originalNoInvoiceNum - row.currentInvoiceNum).toFixed(
+ 2
+ );
+ // 璁$畻鏈紑绁ㄩ噾棰�
+ row.noInvoiceAmount = (
+ row.tempnoInvoiceAmount - row.currentInvoiceAmount
+ ).toFixed(2);
};
onMounted(() => {
- getList();
+ getList();
});
</script>
<style scoped lang="scss">
.table_list {
- margin-top: unset;
+ margin-top: unset;
}
.flex {
- display: flex;
+ display: flex;
}
.justify-between {
- justify-content: space-between;
+ justify-content: space-between;
}
::v-deep(.el-checkbox__label) {
- font-weight: bold;
+ font-weight: bold;
}
</style>
+
+
+
+
+
diff --git a/src/views/salesManagement/orderManagement/index.vue b/src/views/salesManagement/orderManagement/index.vue
new file mode 100644
index 0000000..6107966
--- /dev/null
+++ b/src/views/salesManagement/orderManagement/index.vue
@@ -0,0 +1,490 @@
+<template>
+ <div class="app-container">
+ <el-card class="box-card">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-input
+ v-model="searchForm.orderNo"
+ placeholder="璇疯緭鍏ヨ鍗曞彿"
+ clearable
+ @keyup.enter="handleSearch"
+ >
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.customer" placeholder="璇烽�夋嫨瀹㈡埛" clearable>
+ <el-option label="涓婃捣绉戞妧鏈夐檺鍏徃" value="涓婃捣绉戞妧鏈夐檺鍏徃"></el-option>
+ <el-option label="娣卞湷鐢靛瓙鏈夐檺鍏徃" value="娣卞湷鐢靛瓙鏈夐檺鍏徃"></el-option>
+ <el-option label="鍖椾含璐告槗鍏徃" value="鍖椾含璐告槗鍏徃"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨璁㈠崟鐘舵��" clearable>
+ <el-option label="寰呭鏍�" value="寰呭鏍�"></el-option>
+ <el-option label="宸插鏍�" value="宸插鏍�"></el-option>
+ <el-option label="宸插彂璐�" value="宸插彂璐�"></el-option>
+ <el-option label="宸插畬鎴�" value="宸插畬鎴�"></el-option>
+ <el-option label="宸插彇娑�" value="宸插彇娑�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ <el-button style="float: right;" type="primary" @click="handleAdd">
+ 鏂板璁㈠崟
+ </el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 璁㈠崟鍒楄〃 -->
+ <el-table
+ :data="filteredList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)"
+ >
+ <el-table-column prop="id" label="ID" width="80" align="center"/>
+ <el-table-column prop="orderNo" label="璁㈠崟鍙�" width="150" />
+ <el-table-column prop="customer" label="瀹㈡埛鍚嶇О" />
+ <el-table-column prop="salesperson" label="涓氬姟鍛�" width="100" />
+ <el-table-column prop="orderDate" label="涓嬪崟鏃ユ湡" width="120" />
+ <el-table-column prop="amount" label="璁㈠崟閲戦" width="120">
+ <template #default="scope">
+ 楼{{ scope.row.amount.toFixed(2) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="paymentMethod" label="浠樻鏂瑰紡" width="120" />
+ <el-table-column prop="status" label="璁㈠崟鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="250" fixed="right" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleView(scope.row)">鏌ョ湅</el-button>
+ <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === '寰呭鏍�'">缂栬緫</el-button>
+ <el-button link type="primary" @click="handleReview(scope.row)" v-if="scope.row.status === '寰呭鏍�'">瀹℃牳</el-button>
+ <el-button link type="primary" @click="handleTransfer(scope.row)" v-if="scope.row.status === '宸插鏍�'">杞崟</el-button>
+ <el-button link type="danger" @click="handleCancel(scope.row)" v-if="scope.row.status === '寰呭鏍�'">鍙栨秷</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.currentPage"
+ :limit="pagination.pageSize"
+ @pagination="handleCurrentChange"
+ />
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <FormDialog v-model="dialogVisible" :title="dialogTitle" :width="'700px'" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customer">
+ <el-select v-model="form.customer" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%">
+ <el-option label="涓婃捣绉戞妧鏈夐檺鍏徃" value="涓婃捣绉戞妧鏈夐檺鍏徃"></el-option>
+ <el-option label="娣卞湷鐢靛瓙鏈夐檺鍏徃" value="娣卞湷鐢靛瓙鏈夐檺鍏徃"></el-option>
+ <el-option label="鍖椾含璐告槗鍏徃" value="鍖椾含璐告槗鍏徃"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓氬姟鍛�" prop="salesperson">
+ <el-select v-model="form.salesperson" placeholder="璇烽�夋嫨涓氬姟鍛�" style="width: 100%">
+ <el-option label="闄堝織寮�" value="闄堝織寮�"></el-option>
+ <el-option label="鍒橀泤濠�" value="鍒橀泤濠�"></el-option>
+ <el-option label="鐜嬪缓鍥�" value="鐜嬪缓鍥�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璁㈠崟鏃ユ湡" prop="orderDate">
+ <el-date-picker
+ v-model="form.orderDate"
+ type="date"
+ placeholder="閫夋嫨璁㈠崟鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁㈠崟閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :precision="2" :min="0" 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="paymentMethod">
+ <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%">
+ <el-option label="鍏ㄦ鍒颁粯" value="鍏ㄦ鍒颁粯"></el-option>
+ <el-option label="鍒嗘湡浠樻" value="鍒嗘湡浠樻"></el-option>
+ <el-option label="鏈堢粨" value="鏈堢粨"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁㈠崟鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option label="寰呭鏍�" value="寰呭鏍�"></el-option>
+ <el-option label="宸插鏍�" value="宸插鏍�"></el-option>
+ <el-option label="宸插彂璐�" value="宸插彂璐�"></el-option>
+ <el-option label="宸插畬鎴�" value="宸插畬鎴�"></el-option>
+ <el-option label="宸插彇娑�" value="宸插彇娑�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input type="textarea" v-model="form.remark" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" rows="3"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="handleSubmit">纭� 瀹�</el-button>
+ </div>
+ </template>
+ </FormDialog>
+
+ <!-- 璁㈠崟瀹℃牳瀵硅瘽妗� -->
+ <FormDialog v-model="reviewDialogVisible" title="璁㈠崟瀹℃牳" :width="'500px'" @close="reviewDialogVisible = false" @confirm="saveReview" @cancel="reviewDialogVisible = false">
+ <el-form label-width="100px">
+ <el-form-item label="璁㈠崟鍙�">
+ <span>{{ currentOrder.orderNo }}</span>
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <span>{{ currentOrder.customer }}</span>
+ </el-form-item>
+ <el-form-item label="璁㈠崟閲戦">
+ <span>楼{{ currentOrder.amount.toFixed(2) }}</span>
+ </el-form-item>
+ <el-form-item label="瀹℃牳缁撴灉" prop="reviewResult">
+ <el-radio-group v-model="reviewResult">
+ <el-radio label="閫氳繃">閫氳繃</el-radio>
+ <el-radio label="鎷掔粷">鎷掔粷</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="瀹℃牳鎰忚" prop="reviewComment">
+ <el-input type="textarea" v-model="reviewComment" rows="3" placeholder="璇疯緭鍏ュ鏍告剰瑙�"></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="reviewDialogVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="saveReview">纭� 瀹�</el-button>
+ </div>
+ </template>
+ </FormDialog>
+
+ <!-- 璁㈠崟杞崟瀵硅瘽妗� -->
+ <FormDialog v-model="transferDialogVisible" title="璁㈠崟杞崟" :width="'500px'" @close="transferDialogVisible = false" @confirm="saveTransfer" @cancel="transferDialogVisible = false">
+ <el-form label-width="100px">
+ <el-form-item label="璁㈠崟鍙�">
+ <span>{{ currentOrder.orderNo }}</span>
+ </el-form-item>
+ <el-form-item label="褰撳墠涓氬姟鍛�">
+ <span>{{ currentOrder.salesperson }}</span>
+ </el-form-item>
+ <el-form-item label="杞崟缁�" prop="newSalesperson">
+ <el-select v-model="newSalesperson" placeholder="璇烽�夋嫨鏂颁笟鍔″憳" style="width: 100%">
+ <el-option label="闄堝織寮�" value="闄堝織寮�"></el-option>
+ <el-option label="鍒橀泤濠�" value="鍒橀泤濠�"></el-option>
+ <el-option label="鐜嬪缓鍥�" value="鐜嬪缓鍥�"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="杞崟鍘熷洜" prop="transferReason">
+ <el-input type="textarea" v-model="transferReason" rows="3" placeholder="璇疯緭鍏ヨ浆鍗曞師鍥�"></el-input>
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus, Search } from '@element-plus/icons-vue'
+import Pagination from '@/components/PIMTable/Pagination.vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const searchForm = reactive({
+ orderNo: '',
+ customer: '',
+ status: ''
+})
+
+const orderList = ref([
+ {
+ id: 1,
+ orderNo: 'ORD202312001',
+ customer: '涓婃捣绉戞妧鏈夐檺鍏徃',
+ salesperson: '闄堝織寮�',
+ orderDate: '2023-12-01',
+ amount: 50000.00,
+ paymentMethod: '鍏ㄦ鍒颁粯',
+ status: '寰呭鏍�',
+ remark: '閲嶈瀹㈡埛璁㈠崟'
+ },
+ {
+ id: 2,
+ orderNo: 'ORD202312002',
+ customer: '娣卞湷鐢靛瓙鏈夐檺鍏徃',
+ salesperson: '鍒橀泤濠�',
+ orderDate: '2023-12-02',
+ amount: 35000.00,
+ paymentMethod: '鍒嗘湡浠樻',
+ status: '宸插鏍�',
+ remark: '甯歌璁㈠崟'
+ },
+ {
+ id: 3,
+ orderNo: 'ORD202312003',
+ customer: '鍖椾含璐告槗鍏徃',
+ salesperson: '鐜嬪缓鍥�',
+ orderDate: '2023-12-03',
+ amount: 28000.00,
+ paymentMethod: '鏈堢粨',
+ status: '宸插彂璐�',
+ remark: '鏂板鎴疯鍗�'
+ }
+])
+
+const pagination = reactive({
+ total: 3,
+ currentPage: 1,
+ pageSize: 10
+})
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('鏂板璁㈠崟')
+const form = reactive({
+ customer: '',
+ salesperson: '',
+ orderDate: '',
+ amount: 0,
+ paymentMethod: '',
+ status: '寰呭鏍�',
+ remark: ''
+})
+
+const rules = {
+ customer: [{ required: true, message: '璇烽�夋嫨瀹㈡埛', trigger: 'change' }],
+ salesperson: [{ required: true, message: '璇烽�夋嫨涓氬姟鍛�', trigger: 'change' }],
+ orderDate: [{ required: true, message: '璇烽�夋嫨璁㈠崟鏃ユ湡', trigger: 'change' }],
+ amount: [{ required: true, message: '璇疯緭鍏ヨ鍗曢噾棰�', trigger: 'blur' }],
+ paymentMethod: [{ required: true, message: '璇烽�夋嫨浠樻鏂瑰紡', trigger: 'change' }],
+ status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+}
+
+const isEdit = ref(false)
+const editId = ref(null)
+const reviewDialogVisible = ref(false)
+const transferDialogVisible = ref(false)
+const currentOrder = ref({})
+const reviewResult = ref('')
+const reviewComment = ref('')
+const newSalesperson = ref('')
+const transferReason = ref('')
+const formRef = ref()
+
+// 璁$畻灞炴��
+const filteredList = computed(() => {
+ let list = orderList.value
+ if (searchForm.orderNo) {
+ list = list.filter(item => item.orderNo.includes(searchForm.orderNo))
+ }
+ if (searchForm.customer) {
+ list = list.filter(item => item.customer === searchForm.customer)
+ }
+ if (searchForm.status) {
+ list = list.filter(item => item.status === searchForm.status)
+ }
+ return list
+})
+
+// 鏂规硶
+const getStatusType = (status) => {
+ const statusMap = {
+ '寰呭鏍�': 'warning',
+ '宸插鏍�': 'primary',
+ '宸插彂璐�': 'success',
+ '宸插畬鎴�': 'success',
+ '宸插彇娑�': 'danger'
+ }
+ return statusMap[status] || 'info'
+}
+
+const handleSearch = () => {
+ // 鎼滅储閫昏緫宸插湪computed涓鐞�
+}
+
+const resetSearch = () => {
+ searchForm.orderNo = ''
+ searchForm.customer = ''
+ searchForm.status = ''
+}
+
+const handleAdd = () => {
+ dialogTitle.value = '鏂板璁㈠崟'
+ isEdit.value = false
+ form.customer = ''
+ form.salesperson = ''
+ form.orderDate = ''
+ form.amount = 0
+ form.paymentMethod = ''
+ form.status = '寰呭鏍�'
+ form.remark = ''
+ dialogVisible.value = true
+}
+
+const handleView = (row) => {
+ // 鏌ョ湅璁㈠崟璇︽儏
+ ElMessage.info('鏌ョ湅璁㈠崟璇︽儏鍔熻兘寰呭疄鐜�')
+}
+
+const handleEdit = (row) => {
+ dialogTitle.value = '缂栬緫璁㈠崟'
+ isEdit.value = true
+ editId.value = row.id
+ Object.assign(form, row)
+ dialogVisible.value = true
+}
+
+const handleReview = (row) => {
+ currentOrder.value = row
+ reviewResult.value = ''
+ reviewComment.value = ''
+ reviewDialogVisible.value = true
+}
+
+const handleTransfer = (row) => {
+ currentOrder.value = row
+ newSalesperson.value = ''
+ transferReason.value = ''
+ transferDialogVisible.value = true
+}
+
+const handleCancel = (row) => {
+ ElMessageBox.confirm('纭鍙栨秷璇ヨ鍗曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = orderList.value.findIndex(item => item.id === row.id)
+ if (index > -1) {
+ orderList.value[index].status = '宸插彇娑�'
+ ElMessage.success('璁㈠崟宸插彇娑�')
+ }
+ })
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ヨ鍗曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = orderList.value.findIndex(item => item.id === row.id)
+ if (index > -1) {
+ orderList.value.splice(index, 1)
+ pagination.total--
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ }
+ })
+}
+
+const saveReview = () => {
+ if (!reviewResult.value) {
+ ElMessage.warning('璇烽�夋嫨瀹℃牳缁撴灉')
+ return
+ }
+
+ const index = orderList.value.findIndex(item => item.id === currentOrder.value.id)
+ if (index > -1) {
+ if (reviewResult.value === '閫氳繃') {
+ orderList.value[index].status = '宸插鏍�'
+ ElMessage.success('璁㈠崟瀹℃牳閫氳繃')
+ } else {
+ orderList.value[index].status = '宸插彇娑�'
+ ElMessage.success('璁㈠崟瀹℃牳鎷掔粷')
+ }
+ reviewDialogVisible.value = false
+ }
+}
+
+const saveTransfer = () => {
+ if (!newSalesperson.value) {
+ ElMessage.warning('璇烽�夋嫨鏂颁笟鍔″憳')
+ return
+ }
+
+ const index = orderList.value.findIndex(item => item.id === currentOrder.value.id)
+ if (index > -1) {
+ orderList.value[index].salesperson = newSalesperson.value
+ ElMessage.success('璁㈠崟杞崟鎴愬姛')
+ transferDialogVisible.value = false
+ }
+}
+
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (isEdit.value) {
+ // 缂栬緫
+ const index = orderList.value.findIndex(item => item.id === editId.value)
+ if (index > -1) {
+ orderList.value[index] = { ...form, id: editId.value }
+ ElMessage.success('缂栬緫鎴愬姛')
+ }
+ } else {
+ // 鏂板
+ const newId = Math.max(...orderList.value.map(item => item.id)) + 1
+ const orderNo = `ORD${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(newId).padStart(3, '0')}`
+ orderList.value.push({
+ ...form,
+ id: newId,
+ orderNo: orderNo
+ })
+ pagination.total++
+ ElMessage.success('鏂板鎴愬姛')
+ }
+ dialogVisible.value = false
+ }
+ })
+}
+
+const handleCurrentChange = (val) => {
+ pagination.currentPage = val.page
+ pagination.pageSize = val.limit
+}
+</script>
+
+<style scoped>
+.search-row {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/src/views/salesManagement/paymentShipping/index.vue b/src/views/salesManagement/paymentShipping/index.vue
new file mode 100644
index 0000000..56caf3b
--- /dev/null
+++ b/src/views/salesManagement/paymentShipping/index.vue
@@ -0,0 +1,497 @@
+<template>
+ <div class="app-container">
+ <el-card class="box-card">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-input
+ v-model="searchForm.orderNo"
+ placeholder="璇疯緭鍏ヨ鍗曞彿"
+ clearable
+ @keyup.enter="handleSearch"
+ >
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.paymentStatus" placeholder="璇烽�夋嫨浠樻鐘舵��" clearable>
+ <el-option label="鏈粯娆�" value="鏈粯娆�"></el-option>
+ <el-option label="宸蹭粯娆�" value="宸蹭粯娆�"></el-option>
+ <el-option label="閮ㄥ垎浠樻" value="閮ㄥ垎浠樻"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.shippingStatus" placeholder="璇烽�夋嫨鍙戣揣鐘舵��" clearable>
+ <el-option label="寰呭彂璐�" value="寰呭彂璐�"></el-option>
+ <el-option label="宸插彂璐�" value="宸插彂璐�"></el-option>
+ <el-option label="宸茬鏀�" value="宸茬鏀�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ <el-button style="float: right;" type="primary" @click="handleAdd">
+ 鏂板璁板綍
+ </el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鏀粯涓庡彂璐у垪琛� -->
+ <el-table
+ :data="recordList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)"
+ >
+ <el-table-column prop="id" label="ID" width="80" align="center"/>
+ <el-table-column prop="orderNo" label="璁㈠崟鍙�" />
+ <el-table-column prop="customer" label="瀹㈡埛鍚嶇О" />
+ <el-table-column prop="orderAmount" label="璁㈠崟閲戦" width="120">
+ <template #default="scope">
+ 楼{{ scope.row.orderAmount }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="orderAmount" label="宸蹭粯娆鹃噾棰�" width="120">
+ <template #default="scope">
+ 楼{{ scope.row.paidAmount }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="paymentMethod" label="浠樻鏂瑰紡" width="120" />
+ <el-table-column prop="paymentStatus" label="浠樻鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getPaymentStatusType(scope.row.paymentStatus)">
+ {{ scope.row.paymentStatus }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="shippingStatus" label="鍙戣揣鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getShippingStatusType(scope.row.shippingStatus)">
+ {{ scope.row.shippingStatus }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="shippingDate" label="鍙戣揣鏃ユ湡" width="120" />
+ <el-table-column label="鎿嶄綔" width="250" fixed="right" align="center">
+ <template #default="scope">
+<!-- <el-button link type="primary" @click="handleView(scope.row)">鏌ョ湅</el-button>-->
+ <el-button link type="primary" @click="handlePayment(scope.row)" v-if="scope.row.paymentStatus !== '宸蹭粯娆�'">浠樻</el-button>
+ <el-button link type="primary" @click="handleShipping(scope.row)" v-if="scope.row.paymentStatus === '宸蹭粯娆�' && scope.row.shippingStatus === '寰呭彂璐�'">鍙戣揣</el-button>
+ <el-button link type="primary" @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.current"
+ :limit="pagination.size"
+ @pagination="handleCurrentChange"
+ />
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <FormDialog v-model="dialogVisible" :title="dialogTitle" :width="'700px'" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璁㈠崟鍙�" prop="orderNo">
+ <el-input v-model="form.orderNo" placeholder="璇疯緭鍏ヨ鍗曞彿" disabled></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customer">
+ <el-select v-model="form.customer" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%">
+ <el-option label="涓婃捣绉戞妧鏈夐檺鍏徃" value="涓婃捣绉戞妧鏈夐檺鍏徃"></el-option>
+ <el-option label="娣卞湷鐢靛瓙鏈夐檺鍏徃" value="娣卞湷鐢靛瓙鏈夐檺鍏徃"></el-option>
+ <el-option label="鍖椾含璐告槗鍏徃" value="鍖椾含璐告槗鍏徃"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璁㈠崟閲戦" prop="orderAmount">
+ <el-input-number v-model="form.orderAmount" :precision="2" :min="0" style="width: 100%"></el-input-number>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
+ <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%">
+ <el-option label="鍏ㄦ鍒颁粯" value="鍏ㄦ鍒颁粯"></el-option>
+ <el-option label="鍒嗘湡浠樻" value="鍒嗘湡浠樻"></el-option>
+ <el-option label="鏈堢粨" value="鏈堢粨"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻鐘舵��" prop="paymentStatus">
+ <el-select v-model="form.paymentStatus" placeholder="璇烽�夋嫨浠樻鐘舵��" style="width: 100%">
+ <el-option label="鏈粯娆�" value="鏈粯娆�"></el-option>
+ <el-option label="宸蹭粯娆�" value="宸蹭粯娆�"></el-option>
+ <el-option label="閮ㄥ垎浠樻" value="閮ㄥ垎浠樻"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戣揣鐘舵��" prop="shippingStatus">
+ <el-select v-model="form.shippingStatus" placeholder="璇烽�夋嫨鍙戣揣鐘舵��" style="width: 100%">
+ <el-option label="寰呭彂璐�" value="寰呭彂璐�"></el-option>
+ <el-option label="宸插彂璐�" value="宸插彂璐�"></el-option>
+ <el-option label="宸茬鏀�" value="宸茬鏀�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戣揣鏃ユ湡" prop="shippingDate">
+ <el-date-picker
+ v-model="form.shippingDate"
+ type="date"
+ placeholder="閫夋嫨鍙戣揣鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐗╂祦鍗曞彿" prop="trackingNo">
+ <el-input v-model="form.trackingNo" placeholder="璇疯緭鍏ョ墿娴佸崟鍙�"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input type="textarea" v-model="form.remark" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" rows="3"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="handleSubmit">纭� 瀹�</el-button>
+ </div>
+ </template>
+ </FormDialog>
+
+ <!-- 浠樻瀵硅瘽妗� -->
+ <FormDialog v-model="paymentDialogVisible" title="璁㈠崟浠樻" :width="'500px'" @close="paymentDialogVisible = false" @confirm="savePayment" @cancel="paymentDialogVisible = false">
+ <el-form label-width="100px">
+ <el-form-item label="璁㈠崟鍙�">
+ <span>{{ currentRecord.orderNo }}</span>
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <span>{{ currentRecord.customer }}</span>
+ </el-form-item>
+ <el-form-item label="璁㈠崟閲戦">
+ <span>楼{{ currentRecord.orderAmount }}</span>
+ </el-form-item>
+ <el-form-item label="浠樻閲戦" prop="paymentAmount">
+ <el-input-number v-model="paymentAmount" :precision="2" :min="0" :max="currentRecord.orderAmount" style="width: 100%"></el-input-number>
+ </el-form-item>
+ <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
+ <el-select v-model="paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%">
+ <el-option label="鐜伴噾" value="鐜伴噾"></el-option>
+ <el-option label="閾惰杞处" value="閾惰杞处"></el-option>
+ <el-option label="鏀粯瀹�" value="鏀粯瀹�"></el-option>
+ <el-option label="寰俊鏀粯" value="寰俊鏀粯"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠樻澶囨敞" prop="paymentRemark">
+ <el-input type="textarea" v-model="paymentRemark" rows="3" placeholder="璇疯緭鍏ヤ粯娆惧娉�"></el-input>
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+
+ <!-- 鍙戣揣瀵硅瘽妗� -->
+ <FormDialog v-model="shippingDialogVisible" title="璁㈠崟鍙戣揣" :width="'500px'" @close="shippingDialogVisible = false" @confirm="saveShipping" @cancel="shippingDialogVisible = false">
+ <el-form label-width="100px">
+ <el-form-item label="璁㈠崟鍙�">
+ <span>{{ currentRecord.orderNo }}</span>
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <span>{{ currentRecord.customer }}</span>
+ </el-form-item>
+ <el-form-item label="鍙戣揣鏃ユ湡" prop="shippingDate">
+ <el-date-picker
+ v-model="shippingDate"
+ type="date"
+ placeholder="閫夋嫨鍙戣揣鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ <el-form-item label="鐗╂祦鍏徃" prop="logisticsCompany">
+ <el-select v-model="logisticsCompany" placeholder="璇烽�夋嫨鐗╂祦鍏徃" style="width: 100%">
+ <el-option label="椤轰赴閫熻繍" value="椤轰赴閫熻繍"></el-option>
+ <el-option label="鍦嗛�氶�熼��" value="鍦嗛�氶�熼��"></el-option>
+ <el-option label="涓�氬揩閫�" value="涓�氬揩閫�"></el-option>
+ <el-option label="鐢抽�氬揩閫�" value="鐢抽�氬揩閫�"></el-option>
+ <el-option label="闊佃揪閫熼��" value="闊佃揪閫熼��"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐗╂祦鍗曞彿" prop="trackingNo">
+ <el-input v-model="trackingNo" placeholder="璇疯緭鍏ョ墿娴佸崟鍙�"></el-input>
+ </el-form-item>
+ <el-form-item label="鍙戣揣澶囨敞" prop="shippingRemark">
+ <el-input type="textarea" v-model="shippingRemark" rows="3" placeholder="璇疯緭鍏ュ彂璐у娉�"></el-input>
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed,onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus, Search } from '@element-plus/icons-vue'
+import {listPage,add,update,deletePaymentShipping} from "@/api/salesManagement/paymentShipping.js"
+import Pagination from '@/components/PIMTable/Pagination.vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+
+const total = ref(0)
+onMounted(() => {
+ getList()
+})
+
+const getList = () => {
+ loading.value = true
+ listPage({...searchForm,...pagination}).then(res => {
+ if(res.code === 200){
+ recordList.value = res.data.records
+ total.value = res.data.total
+ loading.value = false
+ console.log(recordList.value)
+ }
+ })
+}
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const searchForm = reactive({
+ orderNo: '',
+ paymentStatus: '',
+ shippingStatus: ''
+})
+
+const recordList = ref([])
+
+const pagination = reactive({
+ current: 1,
+ size: 10
+})
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('鏂板璁板綍')
+const form = reactive({
+ orderNo: '',
+ customer: '',
+ orderAmount: 0,
+ paymentMethod: '',
+ paymentStatus: '鏈粯娆�',
+ shippingStatus: '寰呭彂璐�',
+ shippingDate: '',
+ trackingNo: '',
+ remark: ''
+})
+
+const rules = {
+ // orderNo: [{ required: true, message: '璇疯緭鍏ヨ鍗曞彿', trigger: 'blur' }],
+ customer: [{ required: true, message: '璇烽�夋嫨瀹㈡埛', trigger: 'change' }],
+ orderAmount: [{ required: true, message: '璇疯緭鍏ヨ鍗曢噾棰�', trigger: 'blur' }],
+ paymentMethod: [{ required: true, message: '璇烽�夋嫨浠樻鏂瑰紡', trigger: 'change' }],
+ paymentStatus: [{ required: true, message: '璇烽�夋嫨浠樻鐘舵��', trigger: 'change' }],
+ shippingStatus: [{ required: true, message: '璇烽�夋嫨鍙戣揣鐘舵��', trigger: 'change' }]
+}
+
+const isEdit = ref(false)
+const editId = ref(null)
+const paymentDialogVisible = ref(false)
+const shippingDialogVisible = ref(false)
+const currentRecord = ref({})
+const paymentAmount = ref(0)
+const paymentMethod = ref('')
+const paymentRemark = ref('')
+const shippingDate = ref('')
+const logisticsCompany = ref('')
+const trackingNo = ref('')
+const shippingRemark = ref('')
+const formRef = ref()
+
+// 鏂规硶
+const getPaymentStatusType = (status) => {
+ const statusMap = {
+ '鏈粯娆�': 'danger',
+ '宸蹭粯娆�': 'success',
+ '閮ㄥ垎浠樻': 'warning'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getShippingStatusType = (status) => {
+ const statusMap = {
+ '寰呭彂璐�': 'warning',
+ '宸插彂璐�': 'primary',
+ '宸茬鏀�': 'success'
+ }
+ return statusMap[status] || 'info'
+}
+
+const handleSearch = () => {
+ // 鎼滅储閫昏緫宸插湪computed涓鐞�
+ getList()
+}
+
+const resetSearch = () => {
+ searchForm.orderNo = ''
+ searchForm.paymentStatus = ''
+ searchForm.shippingStatus = ''
+}
+
+const handleAdd = () => {
+ dialogTitle.value = '鏂板璁板綍'
+ isEdit.value = false
+ form.orderNo = ''
+ form.customer = ''
+ form.orderAmount = 0
+ form.paymentMethod = ''
+ form.paymentStatus = '鏈粯娆�'
+ form.shippingStatus = '寰呭彂璐�'
+ form.shippingDate = ''
+ form.trackingNo = ''
+ form.remark = ''
+ dialogVisible.value = true
+}
+
+const handleView = (row) => {
+ // 鏌ョ湅璁板綍璇︽儏
+ ElMessage.info('鏌ョ湅璁板綍璇︽儏鍔熻兘寰呭疄鐜�')
+}
+
+const handleEdit = (row) => {
+ dialogTitle.value = '缂栬緫璁板綍'
+ isEdit.value = true
+ editId.value = row.id
+ Object.assign(form, row)
+ dialogVisible.value = true
+}
+
+const handlePayment = (row) => {
+ currentRecord.value = row
+ paymentAmount.value = row.orderAmount - row.paidAmount
+ paymentMethod.value = ''
+ paymentRemark.value = ''
+ paymentDialogVisible.value = true
+}
+
+const handleShipping = (row) => {
+ currentRecord.value = row
+ shippingDate.value = ''
+ logisticsCompany.value = ''
+ trackingNo.value = ''
+ shippingRemark.value = ''
+ shippingDialogVisible.value = true
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ヨ褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ let ids = [row.id]
+ deletePaymentShipping(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
+ })
+}
+
+const savePayment = () => {
+ if (!paymentMethod.value) {
+ ElMessage.warning('璇烽�夋嫨浠樻鏂瑰紡')
+ return
+ }
+ currentRecord.value.paidAmount = Number(currentRecord.value.paidAmount) + paymentAmount.value
+ if(currentRecord.value.paidAmount == currentRecord.value.orderAmount){
+ currentRecord.value.paymentStatus = '宸蹭粯娆�'
+ }else{
+ currentRecord.value.paymentStatus = '閮ㄥ垎浠樻'
+ }
+ update(currentRecord.value).then(res => {
+ if(res.code === 200){
+ ElMessage.success('浠樻淇℃伅宸蹭繚瀛�')
+ paymentDialogVisible.value = false
+ getList()
+ }
+ })
+
+}
+
+const saveShipping = () => {
+ if (!shippingDate.value || !logisticsCompany.value || !trackingNo.value) {
+ ElMessage.warning('璇峰~鍐欏畬鏁寸殑鍙戣揣淇℃伅')
+ return
+ }
+ currentRecord.value.shippingStatus = '宸插彂璐�'
+ update(currentRecord.value).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍙戣揣淇℃伅宸蹭繚瀛�')
+ shippingDialogVisible.value = false
+ getList()
+ }
+ })
+}
+
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (isEdit.value) {
+ // 缂栬緫
+ update(form).then(res => {
+ if(res.code === 200){
+ ElMessage.success('缂栬緫鎴愬姛')
+ getList()
+ }
+ })
+ } else {
+ // 鏂板
+ add(form).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ getList()
+ }
+ })
+ }
+ dialogVisible.value = false
+ }
+ })
+}
+
+const handleCurrentChange = (val) => {
+ pagination.current = val.page
+ pagination.size = val.limit
+}
+</script>
+
+<style scoped>
+.search-row {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/src/views/salesManagement/receiptPayment/index.vue b/src/views/salesManagement/receiptPayment/index.vue
index fe285ba..66af76a 100644
--- a/src/views/salesManagement/receiptPayment/index.vue
+++ b/src/views/salesManagement/receiptPayment/index.vue
@@ -13,36 +13,12 @@
prefix-icon="Search"
/>
</el-form-item>
- <el-form-item label="瀹㈡埛鍚堝悓鍙�">
- <el-input
- v-model="searchForm.customerContractNo"
- placeholder="璇疯緭鍏�"
- @change="handleQuery"
- clearable
- prefix-icon="Search"
- />
- </el-form-item>
- <el-form-item label="椤圭洰鍚嶇О">
- <el-input
- v-model="searchForm.projectName"
- placeholder="璇疯緭鍏�"
- @change="handleQuery"
- clearable
- prefix-icon="Search"
- />
- </el-form-item>
<el-form-item>
<el-checkbox
v-model="searchForm.status"
label="涓嶆樉绀哄緟鍥炴涓�0"
@change="handleQuery"
/>
- </el-form-item>
- <br/>
- <el-form-item label="寮�绁ㄦ棩鏈�">
- <el-date-picker style="width: 240px" v-model="searchForm.commonDate" value-format="YYYY-MM-DD"
- format="YYYY-MM-DD" type="daterange" start-placeholder="寮�濮嬫椂闂�" end-placeholder="缁撴潫鏃堕棿" clearable
- @change="changeDateRange" @clear="clearRange" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
@@ -71,7 +47,7 @@
:summary-method="summarizeMainTable"
:expand-row-keys="expandedRowKeys"
@expand-change="expandChange"
- stripe
+
height="calc(100vh - 21.5em)"
>
<el-table-column align="center" type="selection" width="55" />
@@ -82,7 +58,6 @@
border
show-summary
:summary-method="summarizeChildrenTable"
- stripe
>
<el-table-column
align="center"
@@ -104,12 +79,19 @@
</el-table-column>
<el-table-column label="鍥炴鏂瑰紡" prop="receiptPaymentType">
<template #default="scope">
- <el-input
+ <el-select
v-model="scope.row.receiptPaymentType"
- placeholder="璇疯緭鍏�"
+ placeholder="璇烽�夋嫨"
clearable
:disabled="!scope.row.editType"
- />
+ >
+ <el-option
+ v-for="item in receipt_payment_type"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
</template>
</el-table-column>
<el-table-column label="鐧昏浜�" prop="registrant" width="90"/>
@@ -122,7 +104,6 @@
size="small"
@click="changeEditType(scope.row)"
v-if="!scope.row.editType"
- :disabled="scope.row.registrant !== userStore.nickName"
>缂栬緫</el-button
>
<el-button
@@ -131,7 +112,6 @@
size="small"
@click="saveReceiptPayment(scope.row)"
v-if="scope.row.editType"
- :disabled="scope.row.registrant !== userStore.nickName"
>淇濆瓨</el-button
>
<el-button
@@ -139,7 +119,6 @@
type="primary"
size="small"
@click="delReceiptRecord(scope.row)"
- :disabled="scope.row.registrant !== userStore.nickName"
>鍒犻櫎</el-button
>
</template>
@@ -149,23 +128,10 @@
</el-table-column>
<el-table-column align="center" label="搴忓彿" type="index" width="60" />
<el-table-column
- label="寮�绁ㄦ棩鏈�"
- prop="invoiceDate"
- show-overflow-tooltip
- width="240"
- />
- <el-table-column
label="閿�鍞悎鍚屽彿"
prop="salesContractNo"
show-overflow-tooltip
width="240"
- />
- <el-table-column
- label="瀹㈡埛鍚堝悓鍙�"
- prop="customerContractNo"
- show-overflow-tooltip
- width="240"
-
/>
<el-table-column
label="瀹㈡埛鍚嶇О"
@@ -174,11 +140,16 @@
width="240"
/>
<el-table-column
- label="椤圭洰鍚嶇О"
- prop="projectName"
- show-overflow-tooltip
- width="340"
- />
+ label="鍥炴鐘舵��"
+ prop="statusName"
+ width="120"
+ >
+ <template #default="{ row }">
+ <el-tag :type="getStatusTagType(row.statusName)" disable-transitions>
+ {{ row.statusName || "--" }}
+ </el-tag>
+ </template>
+ </el-table-column>
<el-table-column
label="浜у搧澶х被"
prop="productCategory"
@@ -186,35 +157,28 @@
width="100"
/>
<el-table-column
- label="鍙戠エ鍙�"
- prop="invoiceNo"
+ label="瑙勬牸鍨嬪彿"
+ prop="specificationModel"
show-overflow-tooltip
- width="200"
- />
- <el-table-column
- label="鍙戠エ閲戦(鍏�)"
- prop="invoiceTotal"
- show-overflow-tooltip
- :formatter="formattedNumber"
width="200"
/>
<el-table-column label="绋庣巼(%)" prop="taxRate" show-overflow-tooltip />
<el-table-column
- label="鍥炴閲戦(鍏�)"
- prop="receiptPaymentAmountTotal"
+ label="宸插洖娆鹃噾棰�(鍏�)"
+ prop="invoiceTotal"
show-overflow-tooltip
:formatter="formattedNumber"
width="200"
/>
<el-table-column
label="寰呭洖娆鹃噾棰�(鍏�)"
- prop="noReceiptAmount"
+ prop="pendingInvoiceTotal"
show-overflow-tooltip
width="200"
>
<template #default="{ row, column }">
<el-text type="danger">
- {{ formattedNumber(row, column, row.noReceiptAmount) }}
+ {{ formattedNumber(row, column, row.pendingInvoiceTotal) }}
</el-text>
</template>
</el-table-column>
@@ -228,138 +192,108 @@
@pagination="paginationChange"
/>
</div>
- <el-dialog
+ <FormDialog
v-model="dialogFormVisible"
- title="鏂板鍙戠エ鍙烽〉闈�"
- width="70%"
+ 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="salesContractNo">
- <el-input
- v-model="form.salesContractNo"
- placeholder="鑷姩濉厖"
- disabled
+ <el-table-column type="index" label="搴忓彿" width="50" align="center"/>
+ <el-table-column label="閿�鍞悎鍚屽彿" prop="salesContractNo" show-overflow-tooltip />
+ <el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" show-overflow-tooltip />
+ <el-table-column
+ label="浜у搧澶х被"
+ prop="productCategory"
+ show-overflow-tooltip
+ width="100"
+ />
+ <el-table-column
+ label="瑙勬牸鍨嬪彿"
+ prop="specificationModel"
+ show-overflow-tooltip
+ width="200"
+ />
+ <el-table-column label="绋庣巼(%)" width="110">
+ <template #default="{ row }">
+ <el-input v-model="row.taxRate" disabled />
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="寰呭洖娆鹃噾棰�(鍏�)"
+ prop="pendingInvoiceTotal"
+ show-overflow-tooltip
+ width="170"
+ >
+ <template #default="{ row, column }">
+ <el-text type="danger">
+ {{ formattedNumber(row, column, row.pendingInvoiceTotal) }}
+ </el-text>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏈鍥炴閲戦(鍏�)" width="180">
+ <template #default="{ row }">
+ <el-input-number
+ v-model="row.receiptPaymentAmount"
+ :step="0.01"
+ :min="0"
+ :max="Number(row.pendingInvoiceTotal || 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.receiptPaymentType" placeholder="璇烽�夋嫨" clearable>
+ <el-option
+ v-for="opt in receipt_payment_type"
+ :key="opt.value"
+ :label="opt.label"
+ :value="opt.value"
/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
- <el-input
- v-model="form.customerName"
- placeholder="鑷姩濉厖"
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鍙戠エ鍙凤細" prop="invoiceNo">
- <el-input
- v-model="form.invoiceNo"
- placeholder="鑷姩濉厖"
- disabled
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍙戠エ閲戦(鍏�)锛�" prop="invoiceTotal">
- <el-input
- type="number"
- v-model="form.invoiceTotal"
- placeholder="鑷姩濉厖"
- :step="0.01"
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="绋庣巼锛�" prop="taxRate">
- <el-input
- type="number"
- v-model="form.taxRate"
- placeholder="鑷姩濉厖"
- :step="0.01"
- disabled
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏈鍥炴閲戦锛�" prop="receiptPaymentAmount">
- <el-input-number :step="0.01" :min="0" style="width: 100%"
- :precision="2"
- v-model="form.receiptPaymentAmount"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鍥炴褰㈠紡锛�" prop="receiptPaymentType">
- <el-input
- v-model="form.receiptPaymentType"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- <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-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鏉ユ鏃ユ湡锛�" prop="receiptPaymentDate">
- <el-date-picker
- style="width: 100%"
- v-model="form.receiptPaymentDate"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- type="date"
- 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-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍥炴鏃ユ湡" width="170">
+ <template #default="{ row }">
+ <el-date-picker
+ v-model="row.receiptPaymentDate"
+ 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" />
+ </template>
+ </el-table-column>
+ </el-table>
+ <div v-else class="empty-tip">璇烽�夋嫨闇�瑕佸洖娆剧殑璁板綍</div>
+ </FormDialog>
</div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
-import { onMounted, ref } from "vue";
+import FormDialog from '@/components/Dialog/FormDialog.vue';
+import { onMounted, ref, reactive, getCurrentInstance } from "vue";
import {
receiptPaymentSaveOrUpdate,
bindInvoiceNoRegPage,
- invoiceInfo,
receiptPaymentHistoryListNoPage,
receiptPaymentDel,
} from "../../../api/salesManagement/receiptPayment.js";
@@ -372,6 +306,7 @@
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
+const forms = ref([]);
const page = reactive({
current: 1,
size: 100,
@@ -386,58 +321,21 @@
searchText: "",
status: true,
customerName: "",
- customerContractNo: "",
- projectName: "",
- },
- form: {
- salesContractNo: "",
- customerName: "",
- invoiceNo: "",
- invoiceTotal: "",
- taxRate: "",
- receiptPaymentAmount: "",
- receiptPaymentType: "",
- registrant: "",
- receiptPaymentDate: "",
- },
- rules: {
- salesContractNo: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- customerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- invoiceNo: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- invoiceTotal: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- receiptPaymentAmount: [
- { required: true, message: "璇烽�夋嫨", trigger: "change" },
- ],
- receiptPaymentType: [
- { required: true, message: "璇烽�夋嫨", trigger: "change" },
- ],
- registrant: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- receiptPaymentDate: [
- { required: true, message: "璇烽�夋嫨", trigger: "change" },
- ],
+ specificationModel: "",
},
});
-const { form, rules } = toRefs(data);
-const { form: searchForm, resetForm } = useFormData(data.searchForm);
+const { form: searchForm } = useFormData(data.searchForm);
+const { receipt_payment_type } = proxy.useDict("receipt_payment_type");
const formattedNumber = (row, column, cellValue) => {
- return parseFloat(cellValue).toFixed(2);
+ const val = Number(cellValue ?? 0);
+ return Number.isFinite(val) ? val.toFixed(2) : "0.00";
};
-const changeDateRange = (date) => {
- if (date) {
- searchForm.invoiceDateStart = date[0];
- searchForm.invoiceDateEnd = date[1];
- getList();
- }
-};
-
-const clearRange = () => {
- searchForm.commonDate = [];
- searchForm.invoiceDateStart = undefined;
- searchForm.invoiceDateEnd = undefined;
- getList();
+const getStatusTagType = (statusName = '') => {
+ const normalized = statusName.trim();
+ if (!normalized) return 'info';
+ return normalized === '鏈畬鎴愬洖娆�' ? 'danger' : 'success';
};
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
@@ -500,15 +398,13 @@
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
console.log("selection", selection);
- selectedRows.value = selection.filter(
- (item) => item.customerContractNo !== null
- );
+ selectedRows.value = selection;
};
// 涓昏〃鍚堣鏂规硶
const summarizeMainTable = (param) => {
return proxy.summarizeTable(
param,
- ["invoiceTotal", "receiptPaymentAmountTotal", "noReceiptAmount"],
+ ["receiptPaymentAmountTotal", "noReceiptAmount"],
{
ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
@@ -521,38 +417,72 @@
};
// 鎵撳紑寮规
const openForm = () => {
- form.value = {};
- if (selectedRows.value.length !== 1) {
- proxy.$modal.msgError("璇烽�夋嫨涓�鏉℃暟鎹�");
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgError("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
return;
}
- if (selectedRows.value[0].noReceiptAmount == 0) {
- proxy.$modal.msgWarning("鏃犻渶鍐嶅洖娆�");
+ const validRows = selectedRows.value.filter((item) => item.noReceiptAmount !== 0);
+ if (validRows.length === 0) {
+ proxy.$modal.msgWarning("鎵�閫夎褰曞潎鏃犻渶鍥炴");
return;
}
- invoiceInfo({ id: selectedRows.value[0].id }).then((res) => {
- form.value = { ...res.data };
- form.value.invoiceLedgerId = form.value.id;
- form.value.id = "";
- form.value.registrant = userStore.nickName;
- });
+ forms.value = validRows.map((row) => ({
+ salesContractNo: row.salesContractNo || "",
+ customerName: row.customerName || "",
+ productCategory: row.productCategory || "",
+ specificationModel: row.specificationModel || "",
+ pendingInvoiceTotal: Number(row.pendingInvoiceTotal || 0),
+ taxRate: row.taxRate ?? "",
+ receiptPaymentAmount: "",
+ receiptPaymentType: "",
+ registrant: userStore.nickName,
+ receiptPaymentDate: "",
+ invoiceLedgerId: row.id,
+ salesLedgerId: row.salesLedgerId,
+ salesLedgerProductId: row.id,
+ }));
dialogFormVisible.value = true;
};
// 鎻愪氦琛ㄥ崟
const submitForm = () => {
- proxy.$refs["formRef"].validate((valid) => {
- if (valid) {
- receiptPaymentSaveOrUpdate(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
+ 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.pendingInvoiceTotal || 0);
+ const currentAmount = Number(item.receiptPaymentAmount);
+ if (!item.receiptPaymentAmount && item.receiptPaymentAmount !== 0) {
+ proxy.$modal.msgError(`绗� ${i + 1} 鏉★細璇峰~鍐欏洖娆鹃噾棰漙);
+ return;
}
+ if (currentAmount > pendingAmount) {
+ proxy.$modal.msgError(
+ `绗� ${i + 1} 鏉★細鍥炴閲戦涓嶈兘瓒呰繃寰呭洖娆鹃噾棰濓紙寰呭洖娆撅細${pendingAmount.toFixed(
+ 2
+ )}锛塦
+ );
+ return;
+ }
+ if (!item.receiptPaymentType) {
+ proxy.$modal.msgError(`绗� ${i + 1} 鏉★細璇烽�夋嫨鍥炴褰㈠紡`);
+ return;
+ }
+ if (!item.receiptPaymentDate) {
+ proxy.$modal.msgError(`绗� ${i + 1} 鏉★細璇烽�夋嫨鍥炴鏃ユ湡`);
+ return;
+ }
+ }
+ receiptPaymentSaveOrUpdate(forms.value).then(() => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
});
};
// 鍏抽棴寮规
const closeDia = () => {
- proxy.resetForm("formRef");
+ forms.value = [];
dialogFormVisible.value = false;
};
@@ -593,7 +523,7 @@
receiptPaymentType: row.receiptPaymentType,
receiptPaymentAmount: row.receiptPaymentAmount,
};
- receiptPaymentSaveOrUpdate(updateData).then((res) => {
+ receiptPaymentSaveOrUpdate([updateData]).then((res) => {
row.editType = !row.editType;
getList();
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
@@ -637,4 +567,9 @@
justify-content: space-between;
margin-bottom: 10px;
}
+.empty-tip {
+ text-align: center;
+ padding: 20px 0;
+ color: #909399;
+}
</style>
diff --git a/src/views/salesManagement/receiptPaymentHistory/index.vue b/src/views/salesManagement/receiptPaymentHistory/index.vue
index d7ef8b2..8a53adf 100644
--- a/src/views/salesManagement/receiptPaymentHistory/index.vue
+++ b/src/views/salesManagement/receiptPaymentHistory/index.vue
@@ -10,24 +10,6 @@
:prefix-icon="Search"
/>
</el-form-item>
- <el-form-item label="瀹㈡埛鍚堝悓鍙�">
- <el-input
- v-model="searchForm.customerContractNo"
- placeholder="杈撳叆瀹㈡埛鍚堝悓鍙�"
- @change="handleQuery"
- clearable
- :prefix-icon="Search"
- />
- </el-form-item>
- <el-form-item label="椤圭洰鍚嶇О">
- <el-input
- v-model="searchForm.projectName"
- placeholder="杈撳叆椤圭洰鍚嶇О"
- @change="handleQuery"
- clearable
- :prefix-icon="Search"
- />
- </el-form-item>
<el-form-item label="鍥炴鏃ユ湡">
<el-date-picker
v-model="searchForm.receiptPaymentDate"
@@ -44,6 +26,14 @@
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"> 鎼滅储 </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">
@@ -59,15 +49,27 @@
:total="page.total"
@pagination="pagination"
@selection-change="handleSelectionChange"
- ></PIMTable>
+ >
+ <template #operation="{ row }">
+ <el-button
+ type="primary"
+ link
+ size="small"
+ @click="handleDelete(row)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </PIMTable>
</div>
</div>
</template>
<script setup>
-import { ref } from "vue";
+import { ref, reactive, getCurrentInstance, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
-import { receiptPaymentHistoryListPage } from "@/api/salesManagement/receiptPayment.js";
+import { ElMessageBox } from "element-plus";
+import { receiptPaymentHistoryListPage, receiptPaymentDel } from "@/api/salesManagement/receiptPayment.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
@@ -79,11 +81,6 @@
width:240
},
{
- label: "瀹㈡埛鍚堝悓鍙�",
- prop: "customerContractNo",
- width:240
- },
- {
label: "鍥炴鏃ユ湡",
prop: "receiptPaymentDate",
width:100
@@ -92,11 +89,6 @@
label: "瀹㈡埛鍚嶇О",
prop: "customerName",
width:240
- },
- {
- label: "椤圭洰鍚嶇О",
- prop: "projectName",
- width:200
},
{
label: "鍥炴閲戦锛堝厓锛�",
@@ -119,6 +111,14 @@
prop: "createTime",
width:100
},
+ {
+ label: "鎿嶄綔",
+ dataType: "slot",
+ fixed: "right",
+ slot: "operation",
+ width: 100,
+ align: "center",
+ },
]);
const tableData = ref([]);
const selectedRows = ref([]);
@@ -132,16 +132,9 @@
const { form: searchForm } = useFormData({
searchText: undefined,
- receiptPaymentDate: [
- dayjs().startOf("month").format("YYYY-MM-DD"),
- dayjs().endOf("month").format("YYYY-MM-DD"),
- ],
- receiptPaymentDateStart: dayjs()
- .startOf("month")
- .format("YYYY-MM-DD 00:00:00"),
- receiptPaymentDateEnd: dayjs().endOf("month").format("YYYY-MM-DD 23:59:59"),
- customerContractNo: undefined,
- projectName: undefined,
+ receiptPaymentDate: [],
+ receiptPaymentDateStart: undefined,
+ receiptPaymentDateEnd: undefined,
});
const { receipt_payment_type } = proxy.useDict("receipt_payment_type");
const isShowSummarySon = ref(true);
@@ -196,6 +189,72 @@
getList();
};
+// 鍒犻櫎
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ヨ褰曞悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(async () => {
+ try {
+ tableLoading.value = true;
+ await receiptPaymentDel([row.id]);
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ } catch (error) {
+ console.error("鍒犻櫎澶辫触:", error);
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ } 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(async () => {
+ try {
+ tableLoading.value = true;
+ const ids = selectedRows.value.map((item) => item.id);
+ await receiptPaymentDel(ids);
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ selectedRows.value = [];
+ getList();
+ } catch (error) {
+ console.error("鍒犻櫎澶辫触:", error);
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ } finally {
+ tableLoading.value = false;
+ }
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 瀵煎嚭
+const handleExport = () => {
+ const { receiptPaymentDate, ...rest } = searchForm;
+ proxy.download("/receiptPayment/exportOne", { ...rest, ...page }, "鍥炴娴佹按.xlsx");
+};
+
onMounted(() => {
getList();
});
diff --git a/src/views/salesManagement/receiptPaymentLedger/index.vue b/src/views/salesManagement/receiptPaymentLedger/index.vue
index 3a4af2e..ffeaee5 100644
--- a/src/views/salesManagement/receiptPaymentLedger/index.vue
+++ b/src/views/salesManagement/receiptPaymentLedger/index.vue
@@ -42,7 +42,7 @@
width="200"
/>
<el-table-column
- label="寮�绁ㄩ噾棰�(鍏�)"
+ label="鍚堝悓閲戦(鍏�)"
prop="invoiceTotal"
show-overflow-tooltip
:formatter="formattedNumber"
@@ -95,45 +95,43 @@
/>
<el-table-column
label="鍙戠敓鏃ユ湡"
- prop="happenTime"
+ prop="receiptPaymentDate"
show-overflow-tooltip
width="110"
/>
<el-table-column
- label="寮�绁ㄩ噾棰�(鍏�)"
- prop="invoiceAmount"
+ label="閿�鍞悎鍚屽彿"
+ prop="salesContractNo"
+ show-overflow-tooltip
+ width="200"
+ />
+ <el-table-column
+ label="鍚堝悓閲戦(鍏�)"
+ prop="invoiceTotal"
show-overflow-tooltip
:formatter="formattedNumber"
width="200"
/>
<el-table-column
label="鍥炴閲戦(鍏�)"
- prop="receiptAmount"
+ prop="receiptPaymentAmount"
show-overflow-tooltip
:formatter="formattedNumber"
width="200"
/>
<el-table-column
label="搴旀敹閲戦(鍏�)"
- prop="unReceiptAmount"
+ prop="unReceiptPaymentAmount"
show-overflow-tooltip
width="200"
>
<template #default="{ row, column }">
<el-text type="danger">
- {{ formattedNumber(row, column, row.unReceiptAmount) }}
+ {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }}
</el-text>
</template>
</el-table-column>
</el-table>
- <pagination
- v-show="recordTotal > 0"
- :total="recordTotal"
- layout="total, sizes, prev, pager, next, jumper"
- :page="recordPage.current"
- :limit="recordPage.size"
- @pagination="recordPaginationChange"
- />
</div>
</div>
</div>
@@ -174,7 +172,6 @@
getList();
};
const paginationChange = (obj) => {
- console.log("paginationChange", current, limit);
page.current = obj.page;
page.size = obj.limit;
getList();
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 51ff6a5..a07aa44 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -36,24 +36,70 @@
</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>
</div>
</div>
<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" @expand-change="expandChange" height="calc(100vh - 18.5em)" stripe>
- <el-table-column align="center" type="selection" width="55" />
- <el-table-column type="expand">
+ :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)">
+ <el-table-column align="center" type="selection" width="55" fixed="left"/>
+ <el-table-column type="expand" width="60" fixed="left">
<template #default="props">
- <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable" stripe>
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable">
+ <el-table-column align="center" label="搴忓彿" type="index"/>
<el-table-column label="浜у搧澶х被" prop="productCategory" />
<el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
<el-table-column label="鍗曚綅" prop="unit" />
+ <el-table-column label="浜у搧鐘舵��"
+ width="100px"
+ align="center">
+ <template #default="scope">
+ <el-tag v-if="scope.row.approveStatus === 1"
+ type="success">鍏呰冻</el-tag>
+ <el-tag v-else
+ type="danger">涓嶈冻</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙戣揣鐘舵��" prop="shippingStatus" width="140" align="center" show-overflow-tooltip />
+ <el-table-column label="蹇�掑叕鍙�" prop="expressCompany" show-overflow-tooltip />
+ <el-table-column label="蹇�掑崟鍙�" prop="expressNumber" show-overflow-tooltip />
+ <el-table-column label="鍙戣揣杞︾墝" minWidth="100px" align="center">
+ <template #default="scope">
+ <div>
+ <el-tag type="success" v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag>
+ <el-tag v-else type="info">鏈彂璐�</el-tag>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙戣揣鏃ユ湡"
+ minWidth="100px"
+ align="center">
+ <template #default="scope">
+ <div>
+ <div v-if="scope.row.shippingDate">{{ scope.row.shippingDate }}</div>
+ <el-tag v-else
+ type="info">-</el-tag>
+ </div>
+ </template>
+ </el-table-column>
<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-column Width="60px" label="鎿嶄綔" align="center">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ :disabled="scope.row.approveStatus !== 1 || !!scope.row.shippingDate || !!scope.row.shippingCarNumber"
+ @click="openDeliveryForm(scope.row)">
+ 鍙戣揣
+ </el-button>
+ </template>
+ </el-table-column>
</el-table>
</template>
</el-table-column>
@@ -66,30 +112,23 @@
<el-table-column label="浠樻鏂瑰紡" prop="paymentMethod" show-overflow-tooltip />
<el-table-column label="鍚堝悓閲戦(鍏�)" prop="contractAmount" width="220" show-overflow-tooltip
:formatter="formattedNumber" />
- <el-table-column label="宸插紑绁ㄩ噾棰�(鍏�)" prop="invoiceTotal" width="220" show-overflow-tooltip
- :formatter="formattedNumber" />
- <el-table-column label="鏈紑绁ㄩ噾棰�(鍏�)" prop="noInvoiceAmountTotal" width="220" show-overflow-tooltip
- :formatter="formattedNumber" />
- <el-table-column label="鍥炴閲戦(鍏�)" prop="receiptPaymentAmountTotal" width="220" show-overflow-tooltip
- :formatter="formattedNumber" />
- <el-table-column label="寰呭洖娆鹃噾棰�(鍏�)" prop="noReceiptAmount" width="220" show-overflow-tooltip
- :formatter="formattedNumber" />
<el-table-column label="褰曞叆浜�" prop="entryPersonName" width="100" show-overflow-tooltip />
<el-table-column label="褰曞叆鏃ユ湡" prop="entryDate" width="120" show-overflow-tooltip />
<el-table-column label="绛捐鏃ユ湡" prop="executionDate" width="120" show-overflow-tooltip />
- <el-table-column fixed="right" label="鎿嶄綔" min-width="140" align="center">
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="100" align="center">
<template #default="scope">
- <el-button link type="primary" size="small" :disabled="scope.row.invoiceTotal>0 || scope.row.entryPersonName !== userStore.nickName" @click="openForm('edit', scope.row)">缂栬緫</el-button>
+ <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">缂栬緫</el-button>
<!-- <el-button link type="primary" size="small" @click="openForm('view', scope.row)">璇︽儏</el-button>-->
<el-button link type="primary" size="small" @click="downLoadFile(scope.row)">闄勪欢</el-button>
+<!-- <el-button link type="primary" size="small" @click="openDeliveryForm(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" />
</div>
- <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板閿�鍞彴璐﹂〉闈�' : '缂栬緫閿�鍞彴璐﹂〉闈�'" width="70%"
- @close="closeDia">
+ <FormDialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板閿�鍞彴璐﹂〉闈�' : '缂栬緫閿�鍞彴璐﹂〉闈�'" :width="'70%'"
+ :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia">
<el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
<el-row :gutter="30">
<el-col :span="12">
@@ -99,12 +138,10 @@
</el-col>
<el-col :span="12">
<el-form-item label="涓氬姟鍛橈細" prop="salesman">
- <el-input
- v-model="form.salesman"
- placeholder="璇疯緭鍏�"
- clearable
- :disabled="operationType === 'view'"
- />
+ <el-select v-model="form.salesman" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'view'">
+ <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
+ :value="item.nickName" />
+ </el-select>
</el-form-item>
</el-col>
</el-row>
@@ -138,114 +175,167 @@
format="YYYY-MM-DD" type="date" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
- </el-row>
- <el-row :gutter="30">
+ </el-row>
+ <el-row :gutter="30">
<el-col :span="12">
<el-form-item label="褰曞叆浜猴細" prop="entryPerson">
- <el-select v-model="form.entryPerson" placeholder="璇烽�夋嫨" clearable @change="changs" disabled>
+ <el-select v-model="form.entryPerson"
+ filterable
+ default-first-option
+ :reserve-keyword="false" placeholder="璇烽�夋嫨" clearable @change="changs">
<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 disabled />
- </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 :disabled="operationType === 'view'" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-form-item label="浜у搧淇℃伅锛�" prop="entryDate">
- <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">娣诲姞</el-button>
- <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >鍒犻櫎</el-button>
- </el-form-item>
- </el-row>
- <el-table :data="productData" border @selection-change="productSelected" show-summary stripe
- :summary-method="summarizeMainTable">
- <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" />
- <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-column fixed="right" label="鎿嶄綔" min-width="60" align="center" v-if="operationType !== 'view'">
- <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" :disabled="operationType === 'view'" />
- </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-button type="primary" v-if="operationType !== 'view'">涓婁紶</el-button>
- <template #tip v-if="operationType !== 'view'">
- <div class="el-upload__tip">
- 鏂囦欢鏍煎紡鏀寔
- doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
- </div>
- </template>
- </el-upload>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- <el-dialog 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="productCategory">
- <!-- <el-select v-model="productForm.productCategory" placeholder="璇烽�夋嫨" clearable>
- <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
- </el-select> -->
- <el-tree-select v-model="productForm.productCategory" 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-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>
- </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>
+ </el-col>
+ </el-row>
+
+ <el-row>
+ <el-form-item label="浜у搧淇℃伅锛�" prop="entryDate">
+ <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">娣诲姞</el-button>
+ <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >鍒犻櫎</el-button>
+ </el-form-item>
+ </el-row>
+ <el-table :data="productData" border @selection-change="productSelected" show-summary
+ :summary-method="summarizeMainTable">
+ <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" />
+ <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-column fixed="right" label="鎿嶄綔" min-width="60" align="center" v-if="operationType !== 'view'">
+ <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" :disabled="operationType === 'view'" />
+ </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-button type="primary" v-if="operationType !== 'view'">涓婁紶</el-button>
+ <template #tip v-if="operationType !== 'view'">
+ <div class="el-upload__tip">
+ 鏂囦欢鏍煎紡鏀寔
+ doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
+ </div>
+ </template>
+ </el-upload>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
+
+ <!-- 浠庢姤浠峰崟瀵煎叆锛堜粎瀹℃壒閫氳繃锛� -->
+ <el-dialog
+ v-model="quotationDialogVisible"
+ title="閫夋嫨瀹℃壒閫氳繃鐨勯攢鍞姤浠峰崟"
+ width="80%"
+ :close-on-click-modal="false"
+ >
+ <div style="margin-bottom: 12px; display:flex; gap: 12px; align-items:center;">
+ <el-input
+ v-model="quotationSearchForm.quotationNo"
+ placeholder="璇疯緭鍏ユ姤浠峰崟鍙�"
+ clearable
+ style="max-width: 260px;"
+ @change="fetchQuotationList"
+ />
+ <el-input
+ v-model="quotationSearchForm.customer"
+ placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+ clearable
+ style="max-width: 260px;"
+ @change="fetchQuotationList"
+ />
+ <el-button type="primary" @click="fetchQuotationList">鎼滅储</el-button>
+ <el-button @click="resetQuotationSearch">閲嶇疆</el-button>
+ </div>
+
+ <el-table
+ :data="quotationList"
+ border
+ stripe
+ v-loading="quotationLoading"
+ height="420px"
+ >
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column prop="quotationNo" label="鎶ヤ环鍗曞彿" width="180" show-overflow-tooltip />
+ <el-table-column prop="customer" label="瀹㈡埛鍚嶇О" min-width="220" show-overflow-tooltip />
+ <el-table-column prop="salesperson" label="涓氬姟鍛�" width="120" show-overflow-tooltip />
+ <el-table-column prop="quotationDate" label="鎶ヤ环鏃ユ湡" width="140" />
+ <el-table-column prop="status" label="瀹℃壒鐘舵��" width="120" align="center" />
+ <el-table-column prop="totalAmount" label="鎶ヤ环閲戦(鍏�)" width="160" align="right">
+ <template #default="scope">
+ {{ Number(scope.row.totalAmount ?? 0).toFixed(2) }}
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔" width="120" align="center">
+ <template #default="scope">
+ <el-button type="primary" link @click="applyQuotation(scope.row)">閫夋嫨</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <template #footer>
+ <el-button @click="quotationDialogVisible = false">鍏抽棴</el-button>
+ </template>
+ </el-dialog>
+ <FormDialog
+ v-model="productFormVisible"
+ :title="productOperationType === 'add' ? '鏂板浜у搧' : '缂栬緫浜у搧'"
+ :width="'40%'"
+ :operation-type="productOperationType"
+ @close="closeProductDia"
+ @confirm="submitProduct"
+ @cancel="closeProductDia">
+ <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef">
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <!-- <el-select v-model="productForm.productCategory" placeholder="璇烽�夋嫨" clearable>
+ <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
+ </el-select> -->
+ <el-tree-select v-model="productForm.productCategory" 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" filterable>
+ <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>
+ </el-col>
<el-col :span="12">
<el-form-item label="绋庣巼(%)锛�" prop="taxRate">
<el-select v-model="productForm.taxRate" placeholder="璇烽�夋嫨" clearable @change="calculateFromTaxRate">
@@ -255,15 +345,15 @@
</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 :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%"
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�" prop="taxInclusiveUnitPrice">
+ <el-input-number :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%"
:precision="2"
placeholder="璇疯緭鍏�" clearable @change="calculateFromUnitPrice" />
- </el-form-item>
- </el-col>
+ </el-form-item>
+ </el-col>
<el-col :span="12">
<el-form-item label="鏁伴噺锛�" prop="quantity">
<el-input-number :step="0.1" :min="0" v-model="productForm.quantity" placeholder="璇疯緭鍏�" clearable
@@ -271,63 +361,259 @@
@change="calculateFromQuantity" style="width: 100%" />
</el-form-item>
</el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
- <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="璇疯緭鍏�" clearable @change="calculateFromTotalPrice" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="涓嶅惈绋庢�讳环(鍏�)锛�" prop="taxExclusiveTotalPrice">
- <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="璇疯緭鍏�" clearable @change="calculateFromExclusiveTotalPrice" />
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
+ <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="璇疯緭鍏�" clearable @change="calculateFromTotalPrice" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓嶅惈绋庢�讳环(鍏�)锛�" prop="taxExclusiveTotalPrice">
+ <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="璇疯緭鍏�" clearable @change="calculateFromExclusiveTotalPrice" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷锛�" prop="invoiceType">
+ <el-select v-model="productForm.invoiceType" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="澧炴櫘绁�" value="澧炴櫘绁�" />
+ <el-option label="澧炰笓绁�" value="澧炰笓绁�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
+ <!-- 闄勪欢鍒楄〃寮圭獥 -->
+ <FileListDialog
+ ref="fileListRef"
+ v-model="fileListDialogVisible"
+ title="闄勪欢鍒楄〃"
+ />
+ <!-- 鎵撳嵃棰勮寮圭獥 -->
+ <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.shippingCarNumber }}</span>
+ </div>
+ </div>
+ <div class="info-row">
+ <div>
+ <span class="label">瀹㈡埛鍚嶇О锛�</span>
+ <span class="value">{{ item.customerName || '寮犵埍鏈�' }}</span>
+ </div>
+ <span class="label">鍗曞彿锛�</span>
+ <span class="value">{{ item.salesContractNo }}</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 v-for="product in item.products" :key="product.id">
+ <td>{{ product.productCategory || '' }}</td>
+ <td>{{ product.specificationModel || '' }}</td>
+ <td>{{ product.unit || '' }}</td>
+ <td>{{ product.taxInclusiveUnitPrice || '0' }}</td>
+ <td>{{ product.quantity || '0' }}</td>
+ <td>{{ product.taxInclusiveTotalPrice || '0' }}</td>
+ </tr>
+ <tr v-if="!item.products || item.products.length === 0">
+ <td colspan="6" style="text-align: center; color: #999;">鏆傛棤浜у搧鏁版嵁</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">{{ getTotalQuantity(item.products) }}</td>
+ <td class="total-value">{{ getTotalAmount(item.products) }}</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>
+ <!-- 鍙戣揣寮规 -->
+ <el-dialog
+ v-model="deliveryFormVisible"
+ title="鍙戣揣淇℃伅"
+ width="40%"
+ @close="closeDeliveryDia"
+ >
+ <el-form :model="deliveryForm" label-width="120px" label-position="top" :rules="deliveryRules" ref="deliveryFormRef">
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="鍙戣揣绫诲瀷锛�" prop="type">
+ <el-select
+ v-model="deliveryForm.type"
+ placeholder="璇烽�夋嫨鍙戣揣绫诲瀷"
+ style="width: 100%"
+ >
+ <el-option label="璐ц溅" value="璐ц溅" />
+ <el-option label="蹇��" value="蹇��" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 瀹℃壒浜洪�夋嫨锛堜豢鍗忓悓瀹℃壒閲岀殑瀹℃壒浜鸿妭鐐归�夋嫨锛� -->
+ <el-row>
+ <el-col :span="24">
+ <el-form-item>
+ <template #label>
+ <span>瀹℃壒浜洪�夋嫨锛�</span>
+ <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">鏂板鑺傜偣</el-button>
+ </template>
+ <div style="display: flex; align-items: flex-end; flex-wrap: wrap;">
+ <div
+ v-for="(node, index) in approverNodes"
+ :key="node.id"
+ style="margin-right: 20px; text-align: center; margin-bottom: 10px;"
+ >
+ <div>
+ <span>瀹℃壒浜�</span>
+ 鈫�
+ </div>
+ <el-select
+ v-model="node.userId"
+ placeholder="閫夋嫨浜哄憳"
+ filterable
+ style="width: 140px; margin-bottom: 8px;"
+ >
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ <div>
+ <el-button
+ type="danger"
+ size="small"
+ @click="removeApproverNode(index)"
+ v-if="approverNodes.length > 1"
+ >鍒犻櫎</el-button>
+ </div>
+ </div>
+ </div>
</el-form-item>
</el-col>
</el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鍙戠エ绫诲瀷锛�" prop="invoiceType">
- <el-select v-model="productForm.invoiceType" placeholder="璇烽�夋嫨" clearable>
- <el-option label="澧炴櫘绁�" value="澧炴櫘绁�" />
- <el-option label="澧炰笓绁�" value="澧炰笓绁�" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitProduct">纭</el-button>
- <el-button @click="closeProductDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- <FileList ref="fileListRef" />
- </div>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitDelivery">纭鍙戣揣</el-button>
+ <el-button @click="closeDeliveryDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
-import {onMounted, ref} from "vue";
-import { ElMessageBox } from "element-plus";
+import {onMounted, ref, getCurrentInstance} from "vue";
+import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js";
+import { ElMessageBox, ElMessage } from "element-plus";
+import { UploadFilled, Download } from "@element-plus/icons-vue";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
-import FileList from "./fileList.vue";
+import FileListDialog from '@/components/Dialog/FileListDialog.vue';
+import FormDialog from '@/components/Dialog/FormDialog.vue';
+import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import {
- ledgerListPage,
- productList,
- customerList,
- addOrUpdateSalesLedger,
- getSalesLedgerWithProducts,
- delLedger,
- addOrUpdateSalesLedgerProduct,
- delProduct,
- delLedgerFile,
+ ledgerListPage,
+ productList,
+ customerList,
+ addOrUpdateSalesLedger,
+ getSalesLedgerWithProducts,
+ delLedger,
+ addOrUpdateSalesLedgerProduct,
+ delProduct,
+ delLedgerFile, getProductInventory,
} from "@/api/salesManagement/salesLedger.js";
import { modelList, productTreeList } from "@/api/basicData/product.js";
import useFormData from "@/hooks/useFormData.js";
import dayjs from "dayjs";
+import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
@@ -341,8 +627,8 @@
const modelOptions = ref([]);
const tableLoading = ref(false);
const page = reactive({
- current: 1,
- size: 100,
+ current: 1,
+ size: 100,
});
const total = ref(0);
const fileList = ref([]);
@@ -351,42 +637,30 @@
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
- searchForm: {
- customerName: "", // 瀹㈡埛鍚嶇О
- customerContractNo: "", // 瀹㈡埛鍚堝悓缂栧彿
- salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
- projectName: "", // 椤圭洰鍚嶇О
- entryDate: [
- dayjs().format("YYYY-MM-DD"),
- dayjs().add(1, "day").format("YYYY-MM-DD"),
- ], // 褰曞叆鏃ユ湡
- entryDateStart: dayjs().format("YYYY-MM-DD"),
- entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
- },
- form: {
- salesContractNo: "",
- salesman: "",
- customerContractNo: "",
- customerId: "",
- projectName: "",
- entryPerson: "",
- entryDate: "",
- maintenanceTime: "",
- productData: [],
- executionDate: "",
- paymentMethod: "",
- },
- rules: {
- salesman: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- customerContractNo: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- customerId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- projectName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- entryPerson: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- },
+ searchForm: {
+ customerName: "", // 瀹㈡埛鍚嶇О
+ salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
+ entryDate: null, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
+ },
+ form: {
+ salesContractNo: "",
+ salesman: "",
+ customerId: "",
+ entryPerson: "",
+ entryDate: "",
+ maintenanceTime: "",
+ productData: [],
+ executionDate: "",
+ },
+ rules: {
+ salesman: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ customerId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ entryPerson: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
});
const { form, rules } = toRefs(data);
const { form: searchForm } = useFormData(data.searchForm);
@@ -395,474 +669,1059 @@
const productOperationType = ref("");
const currentId = ref("");
const productFormData = reactive({
- productForm: {
- productCategory: "",
- specificationModel: "",
- unit: "",
- quantity: "",
- taxInclusiveUnitPrice: "",
- taxRate: "",
- taxInclusiveTotalPrice: "",
- taxExclusiveTotalPrice: "",
- invoiceType: "",
- },
- productRules: {
- productCategory: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ productForm: {
+ productCategory: "",
+ specificationModel: "",
+ unit: "",
+ quantity: "",
+ taxInclusiveUnitPrice: "",
+ taxRate: "",
+ taxInclusiveTotalPrice: "",
+ taxExclusiveTotalPrice: "",
+ invoiceType: "",
+ },
+ productRules: {
+ productCategory: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- specificationModel: [
- { 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" }],
- taxInclusiveTotalPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- taxExclusiveTotalPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- },
+ specificationModel: [
+ { 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" }],
+ taxInclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ taxExclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
});
const { productForm, productRules } = toRefs(productFormData);
// 闃叉寰幆璁$畻鐨勬爣蹇�
const isCalculating = ref(false);
const upload = reactive({
- // 涓婁紶鐨勫湴鍧�
- url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
- // 璁剧疆涓婁紶鐨勮姹傚ご閮�
- headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+});
+// 鎵撳嵃鐩稿叧
+const printPreviewVisible = ref(false);
+const printData = ref([]);
+
+// 鎶ヤ环鍗曞鍏ョ浉鍏�
+const quotationDialogVisible = ref(false);
+const quotationLoading = ref(false);
+const quotationList = ref([]);
+const quotationSearchForm = reactive({
+ quotationNo: "",
+ customer: "",
+});
+const selectedQuotation = ref(null);
+
+// 鍙戣揣鐩稿叧
+const deliveryFormVisible = ref(false);
+const currentDeliveryRow = ref(null);
+const deliveryFormData = reactive({
+ deliveryForm: {
+ type: "璐ц溅", // 璐ц溅, 蹇��
+ },
+ deliveryRules: {
+ type: [
+ { required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change" }
+ ]
+ },
+});
+const { deliveryForm, deliveryRules } = toRefs(deliveryFormData);
+
+// 鍙戣揣瀹℃壒浜鸿妭鐐癸紙浠垮崗鍚屽鎵� infoFormDia.vue锛�
+const approverNodes = ref([{ id: 1, userId: null }]);
+let nextApproverId = 2;
+const addApproverNode = () => {
+ approverNodes.value.push({ id: nextApproverId++, userId: null });
+};
+const removeApproverNode = (index) => {
+ approverNodes.value.splice(index, 1);
+};
+
+// 瀵煎叆鐩稿叧
+const importUploadRef = ref(null);
+const importUpload = reactive({
+ title: "瀵煎叆閿�鍞彴璐�",
+ open: false,
+ url: import.meta.env.VITE_APP_BASE_API + "/sales/ledger/import",
+ headers: { Authorization: "Bearer " + getToken() },
+ isUploading: false,
+ beforeUpload: (file) => {
+ const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
+ const isLt10M = file.size / 1024 / 1024 < 10;
+ if (!isExcel) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢鍙兘鏄� xlsx/xls 鏍煎紡!");
+ return false;
+ }
+ if (!isLt10M) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!");
+ return false;
+ }
+ return true;
+ },
+ onChange: (file, fileList) => {
+ console.log('鏂囦欢鐘舵�佹敼鍙�', file, fileList);
+ },
+ onProgress: (event, file, fileList) => {
+ console.log('涓婁紶涓�...', event.percent);
+ },
+ onSuccess: (response, file, fileList) => {
+ console.log('涓婁紶鎴愬姛', response, file, fileList);
+ importUpload.isUploading = false;
+ if (response.code === 200) {
+ proxy.$modal.msgSuccess("瀵煎叆鎴愬姛");
+ importUpload.open = false;
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ }
+ getList();
+ } else {
+ proxy.$modal.msgError(response.msg || "瀵煎叆澶辫触");
+ }
+ },
+ onError: (error, file, fileList) => {
+ console.error('涓婁紶澶辫触', error, file, fileList);
+ importUpload.isUploading = false;
+ proxy.$modal.msgError("瀵煎叆澶辫触锛岃閲嶈瘯");
+ },
});
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();
+ 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 handleQuery = () => {
- page.current = 1;
+ // 鍙湁鍦ㄧ偣鍑绘悳绱㈡寜閽椂鎵嶉噸缃〉鐮佸埌绗竴椤�
+ // 閬垮厤琛ㄥ崟瀛楁change浜嬩欢骞叉壈鍒嗛〉
+ if (arguments.length === 0) {
+ page.current = 1;
+ }
expandedRowKeys.value = [];
- getList();
+ getList();
};
const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
};
const getList = () => {
- tableLoading.value = true;
- const { entryDate, ...rest } = searchForm;
- ledgerListPage({ ...rest, ...page })
- .then((res) => {
- tableLoading.value = false;
- tableData.value = res.records;
- tableData.value.map((item) => {
- item.children = [];
- });
- total.value = res.total;
- })
- .catch(() => {
- tableLoading.value = false;
- });
+ tableLoading.value = true;
+ const { entryDate, ...rest } = searchForm;
+ // 灏嗚寖鍥存棩鏈熷瓧娈典紶閫掔粰鍚庣
+ const params = { ...rest, ...page };
+ // 绉婚櫎褰曞叆鏃ユ湡鐨勯粯璁ゅ�艰缃紝鍙繚鐣欒寖鍥存棩鏈熷瓧娈�
+ delete params.entryDate;
+ return ledgerListPage(params)
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.records;
+ tableData.value.map((item) => {
+ item.children = [];
+ });
+ total.value = res.total;
+ return res;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
};
// 鑾峰彇浜у搧澶х被tree鏁版嵁
const getProductOptions = () => {
- productTreeList().then((res) => {
- productOptions.value = convertIdToValue(res);
- });
+ // 杩斿洖 Promise锛屼究浜庡湪缂栬緫浜у搧鏃剁瓑寰呭姞杞藉畬鎴�
+ return productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res);
+ return productOptions.value;
+ });
};
const formattedNumber = (row, column, cellValue) => {
- return parseFloat(cellValue).toFixed(2);
+ return parseFloat(cellValue).toFixed(2);
};
// 鑾峰彇tree瀛愭暟鎹�
const getModels = (value) => {
- productForm.value.productCategory = findNodeById(productOptions.value, value);
- modelList({ id: value }).then((res) => {
- modelOptions.value = res;
- });
+ productForm.value.productCategory = findNodeById(productOptions.value, value);
+ modelList({ id: value }).then((res) => {
+ modelOptions.value = res;
+ });
};
const getProductModel = (value) => {
- console.log("value", 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 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; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
- }
- if (nodes[i].children && nodes[i].children.length > 0) {
- const foundNode = findNodeById(nodes[i].children, productId);
- if (foundNode) {
- return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖璇ヨ妭鐐�
- }
- }
- }
- return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === productId) {
+ return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖璇ヨ妭鐐�
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
};
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;
- });
+ 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;
+ });
+}
+// 鏍规嵁鍚嶇О鍙嶆煡浜у搧澶х被 id锛屼究浜庝粎瀛樺悕绉版椂鐨勫弽鏄�
+function findNodeIdByLabel(nodes, label) {
+ if (!label) return null;
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+ if (node.label === label) return node.value;
+ if (node.children && node.children.length > 0) {
+ const found = findNodeIdByLabel(node.children, label);
+ if (found !== null && found !== undefined) return found;
+ }
+ }
+ return null;
}
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
- // 杩囨护鎺夊瓙鏁版嵁
- selectedRows.value = selection.filter((item) => item.children !== undefined);
- console.log("selection", selectedRows.value);
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter((item) => item.children !== undefined);
+ console.log("selection", selectedRows.value);
};
const productSelected = (selectedRows) => {
- productSelectedRows.value = selectedRows;
+ productSelectedRows.value = selectedRows;
};
const expandedRowKeys = ref([]);
// 灞曞紑琛�
const expandChange = (row, expandedRows) => {
- if (expandedRows.length > 0) {
- expandedRowKeys.value = [];
- try {
- productList({ salesLedgerId: row.id, type: 1 }).then((res) => {
- 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.log(error);
- }
- } else {
- expandedRowKeys.value = [];
- }
+ if (expandedRows.length > 0) {
+ expandedRowKeys.value = [];
+ try {
+ productList({ salesLedgerId: row.id, type: 1 }).then((res) => {
+ 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.log(error);
+ }
+ } else {
+ expandedRowKeys.value = [];
+ }
};
// 涓昏〃鍚堣鏂规硶
const summarizeMainTable = (param) => {
- return proxy.summarizeTable(param, [
- "contractAmount",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- 'invoiceTotal',
- 'noInvoiceAmountTotal',
- 'receiptPaymentAmountTotal',
- 'noReceiptAmount',
- ]);
+ return proxy.summarizeTable(param, [
+ "contractAmount",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
};
// 瀛愯〃鍚堣鏂规硶
const summarizeChildrenTable = (param) => {
- return proxy.summarizeTable(param, [
- "taxInclusiveUnitPrice",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- ]);
+ return proxy.summarizeTable(param, [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
};
// 鎵撳紑寮规
const openForm = async (type, row) => {
- operationType.value = type;
- form.value = {};
- productData.value = [];
- let userLists = await userListNoPage();
- userList.value = userLists.data;
- customerList().then((res) => {
- customerOption.value = res;
- });
- form.value.entryPerson = userStore.id;
- if (type !== "add") {
- currentId.value = row.id;
- getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
- form.value = { ...res };
- form.value.entryPerson = Number(res.entryPerson);
- productData.value = form.value.productData;
- fileList.value = form.value.salesLedgerFiles;
- });
- }
- // let userAll = await userStore.getInfo()
- // userList.value.forEach(element => {
- // if(userAll.user.nickName === element.nickName && userAll.user.userName === element.userName) {
- // form.value.entryPerson = userAll.user.userId // 璁剧疆榛樿涓氬姟鍛樹负褰撳墠鐢ㄦ埛
- // }
- // });
- form.value.entryDate = getCurrentDate(); // 璁剧疆榛樿褰曞叆鏃ユ湡涓哄綋鍓嶆棩鏈�
- dialogFormVisible.value = true;
+ operationType.value = type;
+ form.value = {};
+ productData.value = [];
+ selectedQuotation.value = null;
+ let userLists = await userListNoPage();
+ userList.value = userLists.data;
+ customerList().then((res) => {
+ customerOption.value = res;
+ });
+ form.value.entryPerson = userStore.id;
+ if (type === "add") {
+ // 鏂板鏃惰缃綍鍏ユ棩鏈熶负褰撳ぉ
+ form.value.entryDate = getCurrentDate();
+ // 绛捐鏃ユ湡榛樿涓哄綋澶�
+ form.value.executionDate = getCurrentDate();
+ } else {
+ currentId.value = row.id;
+ getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
+ form.value = { ...res };
+ form.value.entryPerson = Number(res.entryPerson);
+ productData.value = form.value.productData;
+ fileList.value = form.value.salesLedgerFiles;
+ });
+ }
+ // let userAll = await userStore.getInfo()
+ // userList.value.forEach(element => {
+ // if(userAll.user.nickName === element.nickName && userAll.user.userName === element.userName) {
+ // form.value.entryPerson = userAll.user.userId // 璁剧疆榛樿涓氬姟鍛樹负褰撳墠鐢ㄦ埛
+ // }
+ // });
+ form.value.entryDate = getCurrentDate(); // 璁剧疆榛樿褰曞叆鏃ユ湡涓哄綋鍓嶆棩鏈�
+ dialogFormVisible.value = true;
+};
+
+// 鎵撳紑鎶ヤ环鍗曢�夋嫨寮圭獥锛堜粎瀹℃壒閫氳繃锛�
+const openQuotationDialog = async () => {
+ if (operationType.value === "view") return;
+ quotationDialogVisible.value = true;
+ // 鍏堢‘淇濆鎴峰垪琛ㄥ凡鍔犺浇锛屼究浜庡悗缁洖濉� customerId
+ if (!customerOption.value || customerOption.value.length === 0) {
+ try {
+ const res = await customerList();
+ customerOption.value = res;
+ } catch (e) {
+ // ignore锛屽厑璁哥敤鎴峰悗缁墜鍔ㄩ�夋嫨瀹㈡埛
+ }
+ }
+ await fetchQuotationList();
+};
+
+const fetchQuotationList = async () => {
+ quotationLoading.value = true;
+ try {
+ const params = {
+ // 鍏煎鍚庣鍒嗛〉瀛楁锛氳繖閲屾部鐢ㄦ姤浠烽〉闈㈠凡鏈夊彲鐢ㄧ殑瀛楁鍛藉悕
+ currentPage: 1,
+ pageSize: 100,
+ ...quotationSearchForm,
+ status: "閫氳繃",
+ };
+ const res = await getQuotationList(params);
+ quotationList.value = res?.data?.records || [];
+ } finally {
+ quotationLoading.value = false;
+ }
+};
+
+const resetQuotationSearch = async () => {
+ quotationSearchForm.quotationNo = "";
+ quotationSearchForm.customer = "";
+ await fetchQuotationList();
+};
+
+// 閫変腑鎶ヤ环鍗曞悗鍥炲~鍒板彴璐﹁〃鍗�
+const applyQuotation = (row) => {
+ if (!row) return;
+ selectedQuotation.value = row;
+
+ // 涓氬姟鍛�
+ form.value.salesman = row.salesperson || "";
+
+ // 瀹㈡埛鍚嶇О -> customerId
+ const customer = (customerOption.value || []).find((c) => c.customerName === row.customer);
+ if (customer?.id) {
+ form.value.customerId = customer.id;
+ } else {
+ // 濡傛灉鎵句笉鍒帮紝淇濈暀鍘熷�硷紙鍏佽鐢ㄦ埛鎵嬪姩閫夋嫨/涓嶆墦鏂凡鏈夎緭鍏ワ級
+ form.value.customerId = form.value.customerId || "";
+ }
+
+ // 浜у搧淇℃伅鏄犲皠锛氭姤浠� products -> 鍙拌处 productData
+ const products = Array.isArray(row.products) ? row.products : [];
+ productData.value = products.map((p) => {
+ const quantity = Number(p.quantity ?? 0) || 0;
+ const unitPrice = Number(p.unitPrice ?? 0) || 0;
+ const taxRate = "13"; // 榛樿 13%锛屼究浜庣洿鎺ユ彁浜わ紙濡傞渶鍙湪浜у搧涓嚜琛屼慨鏀癸級
+ const taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
+ const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(taxInclusiveTotalPrice, taxRate);
+ return {
+ // 鍙拌处瀛楁
+ productCategory: p.product || p.productName || "",
+ specificationModel: p.specification || "",
+ unit: p.unit || "",
+ quantity: quantity,
+ taxRate: taxRate,
+ taxInclusiveUnitPrice: unitPrice.toFixed(2),
+ taxInclusiveTotalPrice: taxInclusiveTotalPrice,
+ taxExclusiveTotalPrice: taxExclusiveTotalPrice,
+ invoiceType: "澧炴櫘绁�",
+ };
+ });
+
+ quotationDialogVisible.value = false;
};
function changs(val) {
- console.log(val);
+ console.log(val);
}
// 涓婁紶鍓嶆牎妫�
function handleBeforeUpload(file) {
- // 鏍℃鏂囦欢澶у皬
- // if (file.size > 1024 * 1024 * 10) {
- // proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃10MB!");
- // return false;
- // }
- proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
- return true;
+ // 鏍℃鏂囦欢澶у皬
+ // if (file.size > 1024 * 1024 * 10) {
+ // proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃10MB!");
+ // return false;
+ // }
+ proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
+ return true;
}
// 涓婁紶澶辫触
function handleUploadError(err) {
- proxy.$modal.msgError("涓婁紶鏂囦欢澶辫触");
- proxy.$modal.closeLoading();
+ 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);
- }
+ 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);
+ }
}
// 绉婚櫎鏂囦欢
function handleRemove(file) {
- if (operationType.value === "edit") {
- let ids = [];
- ids.push(file.id);
- delLedgerFile(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- });
- }
+ if (operationType.value === "edit") {
+ let ids = [];
+ ids.push(file.id);
+ delLedgerFile(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ });
+ }
}
// 鎻愪氦琛ㄥ崟
const submitForm = () => {
- proxy.$refs["formRef"].validate((valid) => {
- if (valid) {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
console.log('productData.value--', productData.value)
- if (productData.value !== null && productData.value.length > 0) {
- form.value.productData = proxy.HaveJson(productData.value);
- } else {
- proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
- return;
- }
- let tempFileIds = [];
- if (fileList.value !== null && fileList.value.length > 0) {
- tempFileIds = fileList.value.map((item) => item.tempId);
- }
- form.value.tempFileIds = tempFileIds;
- form.value.type = 1;
- addOrUpdateSalesLedger(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
- }
- });
+ if (productData.value !== null && productData.value.length > 0) {
+ form.value.productData = proxy.HaveJson(productData.value);
+ } else {
+ proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
+ return;
+ }
+ let tempFileIds = [];
+ if (fileList.value !== null && fileList.value.length > 0) {
+ tempFileIds = fileList.value.map((item) => item.tempId);
+ }
+ form.value.tempFileIds = tempFileIds;
+ form.value.type = 1;
+ addOrUpdateSalesLedger(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+ }
+ });
};
// 鍏抽棴寮规
const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
};
const productIndex = ref(0);
// 鎵撳紑浜у搧寮规
-const openProductForm = (type, row,index) => {
- productOperationType.value = type;
- productForm.value = {};
- proxy.resetForm("productFormRef");
- if (type === "edit") {
- productForm.value = { ...row };
- productIndex.value = index;
- }
- productFormVisible.value = true;
- getProductOptions();
+const openProductForm = async (type, row, index) => {
+ productOperationType.value = type;
+ productForm.value = {};
+ proxy.resetForm("productFormRef");
+ if (type === "edit") {
+ productForm.value = { ...row };
+ productIndex.value = index;
+ // 缂栬緫鏃舵牴鎹骇鍝佸ぇ绫诲悕绉板弽鏌� tree 鑺傜偣 id锛屽苟鍔犺浇瑙勬牸鍨嬪彿鍒楄〃
+ try {
+ const options = productOptions.value && productOptions.value.length > 0
+ ? productOptions.value
+ : await getProductOptions();
+ const categoryId = findNodeIdByLabel(options, productForm.value.productCategory);
+ if (categoryId) {
+ const models = await modelList({ id: categoryId });
+ modelOptions.value = models || [];
+ // 鏍规嵁褰撳墠瑙勬牸鍨嬪彿鍚嶇О鍙嶆煡骞惰缃� productModelId锛屼究浜庝笅鎷夋鏄剧ず宸查�夊��
+ const currentModel = (modelOptions.value || []).find(
+ (m) => m.model === productForm.value.specificationModel
+ );
+ if (currentModel) {
+ productForm.value.productModelId = currentModel.id;
+ }
+ }
+ } catch (e) {
+ // 鍔犺浇澶辫触鏃朵繚鎸佸彲缂栬緫锛屼笉涓柇寮圭獥
+ console.error("鍔犺浇浜у搧瑙勬牸鍨嬪彿澶辫触", e);
+ }
+ }
+ productFormVisible.value = true;
};
// 鎻愪氦浜у搧琛ㄥ崟
const submitProduct = () => {
- proxy.$refs["productFormRef"].validate((valid) => {
- if (valid) {
- if (operationType.value === "edit") {
- submitProductEdit();
- } else {
- if(productOperationType.value === "add"){
- productData.value.push({ ...productForm.value });
- }else{
- productData.value[productIndex.value] = { ...productForm.value }
- }
- closeProductDia();
- }
- }
- });
+ proxy.$refs["productFormRef"].validate((valid) => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitProductEdit();
+ } else {
+ if(productOperationType.value === "add"){
+ productData.value.push({ ...productForm.value });
+ }else{
+ productData.value[productIndex.value] = { ...productForm.value }
+ }
+ closeProductDia();
+ }
+ }
+ });
};
const submitProductEdit = () => {
- productForm.value.salesLedgerId = currentId.value;
- productForm.value.type = 1
- addOrUpdateSalesLedgerProduct(productForm.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeProductDia();
- getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => {
- productData.value = res.productData;
- });
- });
+ productForm.value.salesLedgerId = currentId.value;
+ productForm.value.type = 1
+ addOrUpdateSalesLedgerProduct(productForm.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeProductDia();
+ getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => {
+ productData.value = res.productData;
+ });
+ });
};
// 鍒犻櫎浜у搧
const deleteProduct = () => {
- 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
- );
- 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: 1 }).then(
- (res) => {
- productData.value = res.productData;
- }
- );
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
- }
+ 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
+ );
+ 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: 1 }).then(
+ (res) => {
+ productData.value = res.productData;
+ }
+ );
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ }
};
// 鍏抽棴浜у搧寮规
const closeProductDia = () => {
- proxy.resetForm("productFormRef");
- productFormVisible.value = false;
+ proxy.resetForm("productFormRef");
+ productFormVisible.value = false;
};
+// 瀵煎叆
+const handleImport = () => {
+ importUpload.title = "瀵煎叆閿�鍞彴璐�";
+ importUpload.open = true;
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ }
+};
+
+// 涓嬭浇瀵煎叆妯℃澘
+const downloadTemplate = () => {
+ proxy.download("/sales/ledger/exportTemplate", {}, "閿�鍞彴璐﹀鍏ユā鏉�.xlsx");
+};
+
+// 鎻愪氦瀵煎叆鏂囦欢
+const submitImportFile = () => {
+ importUpload.isUploading = true;
+ proxy.$refs["importUploadRef"].submit();
+};
+
// 瀵煎嚭
const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/sales/ledger/export", {}, "閿�鍞彴璐�.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/sales/ledger/export", {}, "閿�鍞彴璐�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鍒犻櫎
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(() => {
- delLedger(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ 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(() => {
+ delLedger(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 handlePrint = async () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
+ return;
+ }
+
+ // 鏄剧ず鍔犺浇鐘舵��
+ proxy.$modal.loading("姝e湪鑾峰彇浜у搧鏁版嵁锛岃绋嶅��...");
+
+ try {
+ // 涓烘瘡涓�変腑鐨勯攢鍞彴璐﹁褰曟煡璇㈠搴旂殑浜у搧鏁版嵁
+ const printDataWithProducts = [];
+
+ for (const row of selectedRows.value) {
+ try {
+ // 璋冪敤productList鎺ュ彛鏌ヨ浜у搧鏁版嵁
+ const productRes = await productList({ salesLedgerId: row.id, type: 1 });
+
+ // 灏嗕骇鍝佹暟鎹暣鍚堝埌閿�鍞彴璐﹁褰曚腑
+ const rowWithProducts = {
+ ...row,
+ products: productRes.data || []
+ };
+
+ printDataWithProducts.push(rowWithProducts);
+ } catch (error) {
+ console.error(`鑾峰彇閿�鍞彴璐� ${row.id} 鐨勪骇鍝佹暟鎹け璐�:`, error);
+ // 鍗充娇鏌愪釜璁板綍鐨勪骇鍝佹暟鎹幏鍙栧け璐ワ紝涔熻鍖呭惈璇ヨ褰�
+ printDataWithProducts.push({
+ ...row,
+ products: []
+ });
+ }
+ }
+
+ printData.value = printDataWithProducts;
+ console.log('鎵撳嵃鏁版嵁锛堝寘鍚骇鍝侊級:', printData.value);
+ printPreviewVisible.value = true;
+
+ } catch (error) {
+ console.error('鑾峰彇浜у搧鏁版嵁澶辫触:', error);
+ proxy.$modal.msgError("鑾峰彇浜у搧鏁版嵁澶辫触锛岃閲嶈瘯");
+ } finally {
+ proxy.$modal.closeLoading();
+ }
+};
+// 鎵ц鎵撳嵃
+const executePrint = () => {
+ console.log('寮�濮嬫墽琛屾墦鍗帮紝鏁版嵁鏉℃暟:', printData.value.length);
+ console.log('鎵撳嵃鏁版嵁:', printData.value);
+
+ // 鍒涘缓涓�涓柊鐨勬墦鍗扮獥鍙�
+ const printWindow = window.open('', '_blank', 'width=800,height=600');
+
+ // 鏋勫缓鎵撳嵃鍐呭
+ let printContent = `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="UTF-8">
+ <title>鎵撳嵃棰勮</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ font-family: "SimSun", serif;
+ background: white;
+ }
+ .print-page {
+ width: 200mm;
+ height: 75mm;
+ padding: 10mm;
+ padding-left: 20mm;
+ background: white;
+ box-sizing: border-box;
+ page-break-after: always;
+ page-break-inside: avoid;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+ .delivery-note {
+ width: 100%;
+ height: 100%;
+ font-size: 12px;
+ line-height: 1.2;
+ display: flex;
+ flex-direction: column;
+ color: #000;
+ }
+ .header {
+ text-align: center;
+ margin-bottom: 8px;
+ }
+ .company-name {
+ font-size: 18px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+ .document-title {
+ font-size: 16px;
+ font-weight: bold;
+ }
+ .info-section {
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+ .info-row {
+ line-height: 20px;
+ }
+ .label {
+ font-weight: bold;
+ width: 60px;
+ font-size: 12px;
+ }
+ .value {
+ margin-right: 20px;
+ min-width: 80px;
+ font-size: 12px;
+ }
+ .table-section {
+ margin-bottom: 40px;
+ // flex: 0.6;
+ }
+ .product-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #000;
+ }
+ .product-table th, .product-table td {
+ border: 1px solid #000;
+ padding: 6px;
+ text-align: center;
+ font-size: 12px;
+ line-height: 1.4;
+ }
+ .product-table th {
+ font-weight: bold;
+ }
+ .total-value {
+ font-weight: bold;
+ }
+ .footer-section {
+ margin-top: auto;
+ }
+ .footer-row {
+ display: flex;
+ margin-bottom: 3px;
+ line-height: 22px;
+ justify-content: space-between;
+ }
+ .footer-item {
+ display: flex;
+ margin-right: 20px;
+ }
+ .footer-item .label {
+ font-weight: bold;
+ width: 80px;
+ font-size: 12px;
+ }
+ .footer-item .value {
+ min-width: 80px;
+ font-size: 12px;
+ }
+ .address-item .address-value {
+ min-width: 200px;
+ }
+ @media print {
+ body {
+ margin: 0;
+ padding: 0;
+ }
+ .print-page {
+ margin: 0;
+ padding: 10mm;
+ /* padding-left: 20mm; */
+ page-break-inside: avoid;
+ page-break-after: always;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+ }
+ </style>
+ </head>
+ <body>
+ `;
+
+ // 涓烘瘡鏉℃暟鎹敓鎴愭墦鍗伴〉闈�
+ printData.value.forEach((item, index) => {
+ printContent += `
+ <div 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.customerName || '寮犵埍鏈�'}</span>
+ </div>
+ </div>
+ <div class="info-row">
+ <span class="label">鍗曞彿锛�</span>
+ <span class="value">${item.salesContractNo || ''}</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>
+ ${item.products && item.products.length > 0 ?
+ item.products.map(product => `
+ <tr>
+ <td>${product.productCategory || ''}</td>
+ <td>${product.specificationModel || ''}</td>
+ <td>${product.unit || ''}</td>
+ <td>${product.taxInclusiveUnitPrice || '0'}</td>
+ <td>${product.quantity || '0'}</td>
+ <td>${product.taxInclusiveTotalPrice || '0'}</td>
+ </tr>
+ `).join('') :
+ '<tr><td colspan="6" style="text-align: center; color: #999;">鏆傛棤浜у搧鏁版嵁</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">${getTotalQuantityForPrint(item.products)}</td>
+ <td class="total-value">${getTotalAmountForPrint(item.products)}</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>
+ `;
+ });
+
+ printContent += `
+ </body>
+ </html>
+ `;
+
+ // 鍐欏叆鍐呭鍒版柊绐楀彛
+ printWindow.document.write(printContent);
+ printWindow.document.close();
+
+ // 绛夊緟鍐呭鍔犺浇瀹屾垚鍚庢墦鍗�
+ printWindow.onload = () => {
+ setTimeout(() => {
+ printWindow.print();
+ printWindow.close();
+ printPreviewVisible.value = false;
+ }, 500);
+ };
+};
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (dateString) => {
+ if (!dateString) return getCurrentDate();
+ const date = new Date(dateString);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}/${month}/${day}`;
+};
+// 鏍煎紡鍖栨棩鏈熸椂闂�
+const formatDateTime = (date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ const hours = String(date.getHours()).padStart(2, "0");
+ const minutes = String(date.getMinutes()).padStart(2, "0");
+ const seconds = String(date.getSeconds()).padStart(2, "0");
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
+};
+// 璁$畻浜у搧鎬绘暟閲�
+const getTotalQuantity = (products) => {
+ if (!products || products.length === 0) return '0';
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.quantity) || 0);
+ }, 0);
+ return total.toFixed(2);
+};
+
+// 璁$畻浜у搧鎬婚噾棰�
+const getTotalAmount = (products) => {
+ if (!products || products.length === 0) return '0';
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
+ }, 0);
+ return total.toFixed(2);
+};
+
+// 鐢ㄤ簬鎵撳嵃鐨勮绠楀嚱鏁�
+const getTotalQuantityForPrint = (products) => {
+ if (!products || products.length === 0) return '0';
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.quantity) || 0);
+ }, 0);
+ return total.toFixed(2);
+};
+
+const getTotalAmountForPrint = (products) => {
+ if (!products || products.length === 0) return '0';
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
+ }, 0);
+ return total.toFixed(2);
+};
const mathNum = () => {
- console.log("productForm.value", productForm.value);
- 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
- );
- }
+ console.log("productForm.value", productForm.value);
+ 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 calculateFromTotalPrice = () => {
- if (isCalculating.value) return;
-
- const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
- const quantity = parseFloat(productForm.value.quantity);
-
- if (!totalPrice || !quantity || quantity <= 0) {
- return;
- }
-
- isCalculating.value = true;
-
- // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
- productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
-
- // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
- if (productForm.value.taxRate) {
- productForm.value.taxExclusiveTotalPrice =
- proxy.calculateTaxExclusiveTotalPrice(
- totalPrice,
- productForm.value.taxRate
- );
- }
-
- isCalculating.value = false;
+ if (isCalculating.value) return;
+
+ const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
+ const quantity = parseFloat(productForm.value.quantity);
+
+ if (!totalPrice || !quantity || quantity <= 0) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
+ productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
+
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ totalPrice,
+ productForm.value.taxRate
+ );
+ }
+
+ isCalculating.value = false;
};
// 鏍规嵁涓嶅惈绋庢�讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
@@ -871,27 +1730,27 @@
proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
return;
}
- if (isCalculating.value) return;
-
- const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice);
- const quantity = parseFloat(productForm.value.quantity);
- const taxRate = parseFloat(productForm.value.taxRate);
-
- if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
- return;
- }
-
- isCalculating.value = true;
-
- // 鍏堣绠楀惈绋庢�讳环 = 涓嶅惈绋庢�讳环 / (1 - 绋庣巼/100)
- const taxRateDecimal = taxRate / 100;
- const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
- productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
-
- // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
- productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2);
-
- isCalculating.value = false;
+ if (isCalculating.value) return;
+
+ const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice);
+ const quantity = parseFloat(productForm.value.quantity);
+ const taxRate = parseFloat(productForm.value.taxRate);
+
+ if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 鍏堣绠楀惈绋庢�讳环 = 涓嶅惈绋庢�讳环 / (1 - 绋庣巼/100)
+ const taxRateDecimal = taxRate / 100;
+ const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
+ productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
+
+ // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
+ productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2);
+
+ isCalculating.value = false;
};
// 鏍规嵁鏁伴噺鍙樺寲璁$畻鎬讳环
@@ -900,30 +1759,30 @@
proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
return;
}
- if (isCalculating.value) return;
-
- const quantity = parseFloat(productForm.value.quantity);
- const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
-
- if (!quantity || quantity <= 0 || !unitPrice) {
- return;
- }
-
- isCalculating.value = true;
-
- // 璁$畻鍚◣鎬讳环
- productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
-
- // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
- if (productForm.value.taxRate) {
- productForm.value.taxExclusiveTotalPrice =
- proxy.calculateTaxExclusiveTotalPrice(
- productForm.value.taxInclusiveTotalPrice,
- productForm.value.taxRate
- );
- }
-
- isCalculating.value = false;
+ if (isCalculating.value) return;
+
+ const quantity = parseFloat(productForm.value.quantity);
+ const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
+
+ if (!quantity || quantity <= 0 || !unitPrice) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻鍚◣鎬讳环
+ productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
+
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
+ );
+ }
+
+ isCalculating.value = false;
};
// 鏍规嵁鍚◣鍗曚环鍙樺寲璁$畻鎬讳环
@@ -932,30 +1791,30 @@
proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
return;
}
- if (isCalculating.value) return;
-
- const quantity = parseFloat(productForm.value.quantity);
- const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
-
- if (!quantity || quantity <= 0 || !unitPrice) {
- return;
- }
-
- isCalculating.value = true;
-
- // 璁$畻鍚◣鎬讳环
- productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
-
- // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
- if (productForm.value.taxRate) {
- productForm.value.taxExclusiveTotalPrice =
- proxy.calculateTaxExclusiveTotalPrice(
- productForm.value.taxInclusiveTotalPrice,
- productForm.value.taxRate
- );
- }
-
- isCalculating.value = false;
+ if (isCalculating.value) return;
+
+ const quantity = parseFloat(productForm.value.quantity);
+ const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
+
+ if (!quantity || quantity <= 0 || !unitPrice) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻鍚◣鎬讳环
+ productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
+
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
+ );
+ }
+
+ isCalculating.value = false;
};
// 鏍规嵁绋庣巼鍙樺寲璁$畻涓嶅惈绋庢�讳环
@@ -964,25 +1823,25 @@
proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
return;
}
- if (isCalculating.value) return;
-
- const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
- const taxRate = parseFloat(productForm.value.taxRate);
-
- if (!inclusiveTotalPrice || !taxRate) {
- return;
- }
-
- isCalculating.value = true;
-
- // 璁$畻涓嶅惈绋庢�讳环
- productForm.value.taxExclusiveTotalPrice =
- proxy.calculateTaxExclusiveTotalPrice(
- inclusiveTotalPrice,
- taxRate
- );
-
- isCalculating.value = false;
+ if (isCalculating.value) return;
+
+ const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
+ const taxRate = parseFloat(productForm.value.taxRate);
+
+ if (!inclusiveTotalPrice || !taxRate) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻涓嶅惈绋庢�讳环
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ inclusiveTotalPrice,
+ taxRate
+ );
+
+ isCalculating.value = false;
};
/**
* 涓嬭浇鏂囦欢
@@ -990,29 +1849,281 @@
* @param row 涓嬭浇鏂囦欢鐨勭浉鍏充俊鎭璞�
*/
const fileListRef = ref(null)
+const fileListDialogVisible = ref(false)
const downLoadFile = (row) => {
- getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
- fileListRef.value.open(res.salesLedgerFiles)
- });
-
+ getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
+ if (fileListRef.value) {
+ fileListRef.value.open(res.salesLedgerFiles)
+ }
+ });
}
+
+// 鎵撳紑鍙戣揣寮规
+const openDeliveryForm = (row) => {
+ // 鏍¢獙锛氬彧鏈変骇鍝佺姸鎬佷负鍏呰冻涓旀湭鍙戣揣鏃舵墠鑳藉彂璐�
+ if (row.approveStatus !== 1) {
+ proxy.$modal.msgWarning("浜у搧鐘舵�佷笉瓒筹紝鏃犳硶鍙戣揣");
+ return;
+ }
+ if (row.shippingDate || row.shippingCarNumber) {
+ proxy.$modal.msgWarning("璇ヤ骇鍝佸凡鍙戣揣锛屾棤娉曢噸澶嶅彂璐�");
+ return;
+ }
+ currentDeliveryRow.value = row;
+ deliveryForm.value = {
+ type: "璐ц溅",
+ };
+ // 閲嶇疆瀹℃壒浜鸿妭鐐癸紙榛樿涓�涓┖鑺傜偣锛�
+ approverNodes.value = [{ id: 1, userId: null }];
+ nextApproverId = 2;
+ deliveryFormVisible.value = true;
+};
+
+// 鎻愪氦鍙戣揣琛ㄥ崟
+const submitDelivery = () => {
+ proxy.$refs["deliveryFormRef"].validate((valid) => {
+ if (valid) {
+ // 瀹℃壒浜哄繀濉牎楠岋紙鎵�鏈夎妭鐐归兘瑕侀�変汉锛�
+ const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
+ if (hasEmptyApprover) {
+ proxy.$modal.msgError("璇蜂负鎵�鏈夊鎵硅妭鐐归�夋嫨瀹℃壒浜猴紒");
+ return;
+ }
+ const approveUserIds = approverNodes.value.map(node => node.userId).join(",");
+ // 淇濆瓨褰撳墠灞曞紑鐨勮ID锛屼互渚垮彂璐у悗閲嶆柊鍔犺浇瀛愯〃鏍兼暟鎹�
+ const currentExpandedKeys = [...expandedRowKeys.value];
+ const salesLedgerId = currentDeliveryRow.value.salesLedgerId;
+ addShippingInfo({
+ salesLedgerId: salesLedgerId,
+ salesLedgerProductId: currentDeliveryRow.value.id,
+ type: deliveryForm.value.type,
+ approveUserIds,
+ })
+ .then(() => {
+ proxy.$modal.msgSuccess("鍙戣揣鎴愬姛");
+ closeDeliveryDia();
+ // 鍒锋柊涓昏〃鏁版嵁
+ getList().then(() => {
+ // 濡傛灉涔嬪墠鏈夊睍寮�鐨勮锛岄噸鏂板姞杞借繖浜涜鐨勫瓙琛ㄦ牸鏁版嵁
+ if (currentExpandedKeys.length > 0) {
+ // 浣跨敤 Promise.all 骞惰鍔犺浇鎵�鏈夊睍寮�琛岀殑瀛愯〃鏍兼暟鎹�
+ const loadPromises = currentExpandedKeys.map(ledgerId => {
+ return productList({ salesLedgerId: ledgerId, type: 1 }).then((res) => {
+ const index = tableData.value.findIndex((item) => item.id === ledgerId);
+ if (index > -1) {
+ tableData.value[index].children = res.data;
+ }
+ });
+ });
+ Promise.all(loadPromises).then(() => {
+ // 鎭㈠灞曞紑鐘舵��
+ expandedRowKeys.value = currentExpandedKeys;
+ });
+ }
+ });
+ })
+ }
+ });
+};
+
+// 鍏抽棴鍙戣揣寮规
+const closeDeliveryDia = () => {
+ proxy.resetForm("deliveryFormRef");
+ deliveryFormVisible.value = false;
+ currentDeliveryRow.value = null;
+};
+const currentFactoryName = ref("");
+const getCurrentFactoryName = async () => {
+ let res = await userStore.getInfo();
+ currentFactoryName.value = res.user.currentFactoryName;
+};
onMounted(() => {
getList();
+ userListNoPage().then(res => {
+ userList.value = res.data;
+ })
+ getCurrentFactoryName();
});
</script>
<style scoped lang="scss">
.ml-10 {
- margin-left: 10px;
+ margin-left: 10px;
}
.table_list {
- margin-top: unset;
+ margin-top: unset;
}
.actions {
- display: flex;
- justify-content: space-between;
- margin-bottom: 10px;
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+.print-preview-dialog {
+ .el-dialog__body {
+ padding: 0;
+ max-height: 80vh;
+ overflow-y: auto;
+ }
+}
+
+.print-preview-container {
+ .print-preview-header {
+ padding: 15px;
+ border-bottom: 1px solid #e4e7ed;
+ text-align: center;
+
+ .el-button {
+ margin: 0 10px;
+ }
+ }
+
+ .print-preview-content {
+ padding: 20px;
+ background-color: #f5f5f5;
+ min-height: 400px;
+ }
+}
+
+.print-page {
+ width: 220mm;
+ height: 90mm;
+ padding: 10mm;
+ margin: 0 auto;
+ background: white;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.delivery-note {
+ width: 100%;
+ height: 100%;
+ font-family: "SimSun", serif;
+ font-size: 10px;
+ line-height: 1.2;
+ display: flex;
+ flex-direction: column;
+}
+
+.header {
+ text-align: center;
+ margin-bottom: 8px;
+
+ .company-name {
+ font-size: 18px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+
+ .document-title {
+ font-size: 16px;
+ font-weight: bold;
+ }
+}
+
+.info-section {
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .info-row {
+ line-height: 20px;
+
+ .label {
+ font-weight: bold;
+ width: 60px;
+ font-size: 14px;
+ }
+
+ .value {
+ margin-right: 20px;
+ min-width: 80px;
+ font-size: 14px;
+ }
+ }
+}
+
+.table-section {
+ margin-bottom: 4px;
+ flex: 1;
+
+ .product-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #000;
+
+ th, td {
+ border: 1px solid #000;
+ padding: 6px;
+ text-align: center;
+ font-size: 14px;
+ line-height: 1.4;
+ }
+
+ th {
+ font-weight: bold;
+ }
+
+ .total-label {
+ text-align: right;
+ font-weight: bold;
+ }
+
+ .total-value {
+ font-weight: bold;
+ }
+ }
+}
+
+.footer-section {
+ .footer-row {
+ display: flex;
+ margin-bottom: 3px;
+ line-height: 20px;
+ justify-content: space-between;
+
+ .footer-item {
+ display: flex;
+ margin-right: 20px;
+
+ .label {
+ font-weight: bold;
+ width: 80px;
+ font-size: 14px;
+ }
+
+ .value {
+ min-width: 80px;
+ font-size: 14px;
+ }
+
+ &.address-item {
+ .address-value {
+ min-width: 200px;
+ }
+ }
+ }
+ }
+}
+
+@media print {
+ .app-container {
+ display: none;
+ }
+
+ .print-page {
+ box-shadow: none;
+ margin: 0;
+ padding: 10mm;
+ padding-left: 20mm;
+ page-break-inside: avoid;
+ page-break-after: always;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
}
</style>
diff --git a/src/views/salesManagement/salesQuotation/index.vue b/src/views/salesManagement/salesQuotation/index.vue
new file mode 100644
index 0000000..cbbc4f8
--- /dev/null
+++ b/src/views/salesManagement/salesQuotation/index.vue
@@ -0,0 +1,1035 @@
+<template>
+ <div class="app-container">
+ <el-card class="box-card">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="8">
+ <el-input
+ v-model="searchForm.quotationNo"
+ placeholder="璇疯緭鍏ユ姤浠峰崟鍙�"
+ clearable
+ @keyup.enter="handleSearch"
+ >
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="8">
+ <el-select v-model="searchForm.customer" placeholder="璇烽�夋嫨瀹㈡埛" clearable>
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName">
+ {{
+ item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
+ }}
+ </el-option>
+ </el-select>
+ </el-col>
+<!-- <el-col :span="6">-->
+<!-- <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鎶ヤ环鐘舵��" clearable>-->
+<!-- <el-option label="鑽夌" value="鑽夌"></el-option>-->
+<!-- <el-option label="宸插彂閫�" value="宸插彂閫�"></el-option>-->
+<!-- <el-option label="瀹㈡埛纭" value="瀹㈡埛纭"></el-option>-->
+<!-- <el-option label="宸茶繃鏈�" value="宸茶繃鏈�"></el-option>-->
+<!-- </el-select>-->
+<!-- </el-col>-->
+ <el-col :span="8">
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ <el-button style="float: right;" type="primary" @click="handleAdd">
+ 鏂板鎶ヤ环
+ </el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鎶ヤ环鍒楄〃 -->
+ <el-table
+ :data="filteredList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)"
+ >
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column prop="quotationNo" label="鎶ヤ环鍗曞彿" width="150" />
+ <el-table-column prop="customer" label="瀹㈡埛鍚嶇О" />
+ <el-table-column prop="salesperson" label="涓氬姟鍛�" width="100" />
+ <el-table-column prop="quotationDate" label="鎶ヤ环鏃ユ湡" width="120" />
+ <el-table-column prop="validDate" label="鏈夋晥鏈熻嚦" width="120" />
+ <el-table-column prop="status" label="瀹℃壒鐘舵��" width="120" align="center">
+ <template #default="{ row }">
+ <el-tag :type="getStatusType(row.status)" disable-transitions>
+ {{ row.status || '--' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="totalAmount" label="鎶ヤ环閲戦" width="120">
+ <template #default="scope">
+ 楼{{ scope.row.totalAmount.toFixed(2) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleView(scope.row)">鏌ョ湅</el-button>
+ <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['寰呭鎵�','鎷掔粷'].includes(scope.row.status)">缂栬緫</el-button>
+ <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.currentPage"
+ :limit="pagination.pageSize"
+ @pagination="handleCurrentChange"
+ />
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
+ <div class="quotation-form-container">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form">
+ <!-- 鍩烘湰淇℃伅 -->
+ <el-card class="form-card" shadow="hover">
+ <template #header>
+ <div class="card-header-wrapper">
+ <el-icon class="card-icon"><Document /></el-icon>
+ <span class="card-title">鍩烘湰淇℃伅</span>
+ </div>
+ </template>
+ <div class="form-content">
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customer">
+ <el-select v-model="form.customer" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%" @change="handleCustomerChange" clearable>
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName">
+ {{
+ item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
+ }}
+ </el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓氬姟鍛�" prop="salesperson">
+ <el-select v-model="form.salesperson" placeholder="璇烽�夋嫨涓氬姟鍛�" style="width: 100%" clearable>
+ <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
+ :value="item.nickName" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="鎶ヤ环鏃ユ湡" prop="quotationDate">
+ <el-date-picker
+ v-model="form.quotationDate"
+ type="date"
+ placeholder="閫夋嫨鎶ヤ环鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈夋晥鏈熻嚦" prop="validDate">
+ <el-date-picker
+ v-model="form.validDate"
+ type="date"
+ placeholder="閫夋嫨鏈夋晥鏈�"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
+ <el-input v-model="form.paymentMethod" placeholder="璇疯緭鍏ヤ粯娆炬柟寮�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </div>
+ </el-card>
+
+ <!-- 瀹℃壒浜轰俊鎭� -->
+ <el-card class="form-card" shadow="hover">
+ <template #header>
+ <div class="card-header-wrapper">
+ <el-icon class="card-icon"><UserFilled /></el-icon>
+ <span class="card-title">瀹℃壒浜洪�夋嫨</span>
+ <el-button type="primary" size="small" @click="addApproverNode" class="header-btn">
+ <el-icon><Plus /></el-icon>
+ 鏂板鑺傜偣
+ </el-button>
+ </div>
+ </template>
+ <div class="form-content">
+ <el-row>
+ <el-col :span="24">
+ <el-form-item>
+ <div class="approver-nodes-container">
+ <div
+ v-for="(node, index) in approverNodes"
+ :key="node.id"
+ class="approver-node-item"
+ >
+ <div class="approver-node-label">
+ <span class="node-step">{{ index + 1 }}</span>
+ <span class="node-text">瀹℃壒浜�</span>
+ <el-icon class="arrow-icon"><ArrowRight /></el-icon>
+ </div>
+ <el-select
+ v-model="node.userId"
+ placeholder="閫夋嫨浜哄憳"
+ class="approver-select"
+ clearable
+ >
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ <el-button
+ type="danger"
+ size="small"
+ :icon="Delete"
+ @click="removeApproverNode(index)"
+ v-if="approverNodes.length > 1"
+ class="remove-btn"
+ >鍒犻櫎</el-button>
+ </div>
+ </div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </div>
+ </el-card>
+
+ <!-- 浜у搧淇℃伅 -->
+ <el-card class="form-card" shadow="hover">
+ <template #header>
+ <div class="card-header-wrapper">
+ <el-icon class="card-icon"><Box /></el-icon>
+ <span class="card-title">浜у搧淇℃伅</span>
+ <el-button type="primary" size="small" @click="addProduct" class="header-btn">
+ <el-icon><Plus /></el-icon>
+ 娣诲姞浜у搧
+ </el-button>
+ </div>
+ </template>
+ <div class="form-content">
+ <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0">
+ <el-table-column prop="product" label="浜у搧鍚嶇О" width="200">
+ <template #default="scope">
+ <el-tree-select
+ v-model="scope.row.productId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ check-strictly
+ @change="getModels($event, scope.row)"
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" width="150">
+ <template #default="scope">
+ <el-select
+ v-model="scope.row.specificationId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="getProductModel($event, scope.row)"
+ >
+ <el-option
+ v-for="item in modelOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unit" label="鍗曚綅">
+ <template #default="scope">
+ <el-input v-model="scope.row.unit" placeholder="鍗曚綅" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitPrice" label="鍗曚环">
+ <template #default="scope">
+ <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" align="center">
+ <template #default="scope">
+ <el-button link type="danger" @click="removeProduct(scope.$index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-empty v-else description="鏆傛棤浜у搧锛岃鐐瑰嚮娣诲姞浜у搧" :image-size="80" />
+ </div>
+ </el-card>
+
+ <!-- 澶囨敞淇℃伅 -->
+ <el-card class="form-card" shadow="hover">
+ <template #header>
+ <div class="card-header-wrapper">
+ <el-icon class="card-icon"><EditPen /></el-icon>
+ <span class="card-title">澶囨敞淇℃伅</span>
+ </div>
+ </template>
+ <div class="form-content">
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ type="textarea"
+ v-model="form.remark"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭紙閫夊~锛�"
+ :rows="4"
+ maxlength="500"
+ show-word-limit
+ ></el-input>
+ </el-form-item>
+ </div>
+ </el-card>
+ </el-form>
+ </div>
+ </FormDialog>
+
+ <!-- 鏌ョ湅璇︽儏瀵硅瘽妗� -->
+ <el-dialog v-model="viewDialogVisible" title="鎶ヤ环璇︽儏" width="800px">
+ <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="鎶ヤ环鐘舵��">-->
+<!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>-->
+<!-- </el-descriptions-item>-->
+ <el-descriptions-item label="鎶ヤ环鎬婚" :span="2">
+ <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">楼{{ currentQuotation.totalAmount?.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">
+ 楼{{ scope.row.unitPrice.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>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete } from '@element-plus/icons-vue'
+import Pagination from '@/components/PIMTable/Pagination.vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+import {getQuotationList,addQuotation,updateQuotation,deleteQuotation} from '@/api/salesManagement/salesQuotation.js'
+import {userListNoPage} from "@/api/system/user.js";
+import {customerList} from "@/api/salesManagement/salesLedger.js";
+import {modelList, productTreeList} from "@/api/basicData/product.js";
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const searchForm = reactive({
+ quotationNo: '',
+ customer: '',
+ status: ''
+})
+
+const quotationList = ref([])
+const productOptions = ref([]);
+const modelOptions = ref([]);
+const pagination = reactive({
+ total: 3,
+ currentPage: 1,
+ pageSize: 100
+})
+
+const dialogVisible = ref(false)
+const viewDialogVisible = ref(false)
+const dialogTitle = ref('鏂板鎶ヤ环')
+const form = reactive({
+ quotationNo: '',
+ customer: '',
+ salesperson: '',
+ quotationDate: '',
+ validDate: '',
+ paymentMethod: '',
+ status: '鑽夌',
+ remark: '',
+ products: [],
+ subtotal: 0,
+ freight: 0,
+ otherFee: 0,
+ discountRate: 0,
+ discountAmount: 0,
+ totalAmount: 0
+})
+
+const rules = {
+ customer: [{ required: true, message: '璇烽�夋嫨瀹㈡埛', trigger: 'change' }],
+ salesperson: [{ required: true, message: '璇烽�夋嫨涓氬姟鍛�', trigger: 'change' }],
+ quotationDate: [{ required: true, message: '璇烽�夋嫨鎶ヤ环鏃ユ湡', trigger: 'change' }],
+ validDate: [{ required: true, message: '璇烽�夋嫨鏈夋晥鏈�', trigger: 'change' }],
+ paymentMethod: [{ required: true, message: '璇疯緭鍏ヤ粯娆炬柟寮�', trigger: 'blur' }]
+}
+const userList = ref([]);
+const customerOption = ref([]);
+
+// 瀹℃壒浜鸿妭鐐圭浉鍏�
+const approverNodes = ref([
+ { id: 1, userId: null }
+])
+let nextApproverId = 2
+
+const isEdit = ref(false)
+const editId = ref(null)
+const currentQuotation = ref({})
+const formRef = ref()
+
+// 娣诲姞瀹℃壒浜鸿妭鐐�
+function addApproverNode() {
+ approverNodes.value.push({ id: nextApproverId++, userId: null })
+}
+
+// 鍒犻櫎瀹℃壒浜鸿妭鐐�
+function removeApproverNode(index) {
+ approverNodes.value.splice(index, 1)
+}
+
+// 璁$畻灞炴��
+const filteredList = computed(() => {
+ let list = quotationList.value
+ if (searchForm.quotationNo) {
+ list = list.filter(item => item.quotationNo.includes(searchForm.quotationNo))
+ }
+ if (searchForm.customer) {
+ list = list.filter(item => item.customer === searchForm.customer)
+ }
+ if (searchForm.status) {
+ list = list.filter(item => item.status === searchForm.status)
+ }
+ return list
+})
+
+// 鏂规硶
+const getStatusType = (status) => {
+ const statusMap = {
+ '寰呭鎵�': 'info',
+ '瀹℃牳涓�': 'primary',
+ '閫氳繃': 'success',
+ '鎷掔粷': 'danger'
+ }
+ return statusMap[status] || 'info'
+}
+
+const resetSearch = () => {
+ searchForm.quotationNo = ''
+ searchForm.customer = ''
+ searchForm.status = ''
+}
+
+const handleAdd = async () => {
+ dialogTitle.value = '鏂板鎶ヤ环'
+ isEdit.value = false
+ resetForm()
+ // 閲嶇疆瀹℃壒浜鸿妭鐐�
+ approverNodes.value = [{ id: 1, userId: null }]
+ nextApproverId = 2
+ dialogVisible.value = true
+ let userLists = await userListNoPage();
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ userList.value = (userLists.data || []).map(item => ({
+ userId: item.userId,
+ nickName: item.nickName || '',
+ userName: item.userName || ''
+ }));
+ getProductOptions();
+ customerList().then((res) => {
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
+ id: item.id,
+ customerName: item.customerName || '',
+ taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
+ }))
+ });
+}
+const getProductOptions = () => {
+ // 杩斿洖 Promise锛屼究浜庣紪杈戞椂 await 纭繚鑳藉弽鏄�
+ return productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res);
+ return productOptions.value
+ });
+};
+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;
+ });
+}
+// 鏍规嵁鍚嶇О鍙嶆煡鑺傜偣 id锛屼究浜庝粎瀛樺悕绉版椂鐨勫弽鏄�
+function findNodeIdByLabel(nodes, label) {
+ if (!label) return null;
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+ if (node.label === label) return node.value;
+ if (node.children && node.children.length > 0) {
+ const found = findNodeIdByLabel(node.children, label);
+ if (found !== null && found !== undefined) return found;
+ }
+ }
+ return null;
+}
+const getModels = (value, row) => {
+ if (!row) return;
+ // 濡傛灉娓呯┖閫夋嫨锛屽垯娓呯┖鐩稿叧瀛楁
+ if (!value) {
+ row.productId = '';
+ row.product = '';
+ modelOptions.value = [];
+ row.specificationId = '';
+ row.specification = '';
+ row.unit = '';
+ return;
+ }
+ // 鏇存柊 productId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
+ row.productId = value;
+ // 鎵惧埌瀵瑰簲鐨� label 骞惰祴鍊肩粰 row.product
+ const label = findNodeById(productOptions.value, value);
+ if (label) {
+ row.product = label;
+ }
+ // 鑾峰彇瑙勬牸鍨嬪彿鍒楄〃
+ modelList({ id: value }).then((res) => {
+ modelOptions.value = res || [];
+ });
+};
+const getProductModel = (value, row) => {
+ if (!row) return;
+ // 濡傛灉娓呯┖閫夋嫨锛屽垯娓呯┖鐩稿叧瀛楁
+ if (!value) {
+ row.specificationId = '';
+ row.specification = '';
+ row.unit = '';
+ return;
+ }
+ // 鏇存柊 specificationId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
+ row.specificationId = value;
+ const index = modelOptions.value.findIndex((item) => item.id === value);
+ if (index !== -1) {
+ row.specification = modelOptions.value[index].model;
+ row.unit = modelOptions.value[index].unit;
+ } else {
+ row.specification = '';
+ row.unit = '';
+ }
+};
+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 foundLabel = findNodeById(nodes[i].children, productId);
+ if (foundLabel) {
+ return foundLabel; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖 label
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+};
+const handleView = (row) => {
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ currentQuotation.value = {
+ quotationNo: row.quotationNo || '',
+ customer: row.customer || '',
+ salesperson: row.salesperson || '',
+ quotationDate: row.quotationDate || '',
+ validDate: row.validDate || '',
+ paymentMethod: row.paymentMethod || '',
+ status: row.status || '',
+ remark: row.remark || '',
+ products: row.products ? row.products.map(product => ({
+ productId: product.productId || '',
+ product: product.product || product.productName || '',
+ specificationId: product.specificationId || '',
+ specification: product.specification || '',
+ quantity: product.quantity || 0,
+ unit: product.unit || '',
+ unitPrice: product.unitPrice || 0,
+ amount: product.amount || 0
+ })) : [],
+ totalAmount: row.totalAmount || 0
+ }
+ viewDialogVisible.value = true
+}
+
+const handleEdit = async (row) => {
+ dialogTitle.value = '缂栬緫鎶ヤ环'
+ isEdit.value = true
+ editId.value = row.id
+ form.id = row.id || form.id || null
+ // 鍏堝姞杞戒骇鍝佹爲鏁版嵁锛屽惁鍒� el-tree-select 鏃犳硶鍙嶆樉浜у搧鍚嶇О
+ await getProductOptions()
+
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ form.quotationNo = row.quotationNo || ''
+ form.customer = row.customer || ''
+ form.salesperson = row.salesperson || ''
+ form.quotationDate = row.quotationDate || ''
+ form.validDate = row.validDate || ''
+ form.paymentMethod = row.paymentMethod || ''
+ form.status = row.status || '鑽夌'
+ form.remark = row.remark || ''
+ form.products = row.products ? row.products.map(product => {
+ const productName = product.product || product.productName || ''
+ // 浼樺厛鐢� productId锛涘鏋滃彧鏈夊悕绉帮紝灏濊瘯鍙嶆煡 id 浠ヤ究鏍戦�夋嫨鍣ㄥ弽鏄�
+ const resolvedId = product.productId
+ ? Number(product.productId)
+ : findNodeIdByLabel(productOptions.value, productName) || ''
+ return {
+ productId: resolvedId,
+ product: productName,
+ specificationId: product.specificationId || '',
+ specification: product.specification || '',
+ quantity: product.quantity || 0,
+ unit: product.unit || '',
+ unitPrice: product.unitPrice || 0,
+ amount: product.amount || 0
+ }
+ }) : []
+ form.subtotal = row.subtotal || 0
+ form.freight = row.freight || 0
+ form.otherFee = row.otherFee || 0
+ form.discountRate = row.discountRate || 0
+ form.discountAmount = row.discountAmount || 0
+ form.totalAmount = row.totalAmount || 0
+
+ // 鍙嶆樉瀹℃壒浜�
+ if (row.approveUserIds) {
+ const userIds = row.approveUserIds.split(',')
+ approverNodes.value = userIds.map((userId, idx) => ({
+ id: idx + 1,
+ userId: parseInt(userId.trim())
+ }))
+ nextApproverId = userIds.length + 1
+ } else {
+ approverNodes.value = [{ id: 1, userId: null }]
+ nextApproverId = 2
+ }
+
+ // 鍔犺浇鐢ㄦ埛鍒楄〃
+ let userLists = await userListNoPage();
+ userList.value = (userLists.data || []).map(item => ({
+ userId: item.userId,
+ nickName: item.nickName || '',
+ userName: item.userName || ''
+ }));
+
+ dialogVisible.value = true
+}
+
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ユ姤浠峰崟鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = quotationList.value.findIndex(item => item.id === row.id)
+ if (index > -1) {
+ deleteQuotation(row.id).then(res=>{
+ // console.log(res)
+ if(res.code===200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ handleSearch()
+ }
+ })
+ // quotationList.value.splice(index, 1)
+ // pagination.total--
+ // ElMessage.success('鍒犻櫎鎴愬姛')
+ }
+ })
+}
+
+const resetForm = () => {
+ form.customer = ''
+ form.salesperson = ''
+ form.quotationDate = ''
+ form.validDate = ''
+ form.paymentMethod = ''
+ form.status = '鑽夌'
+ form.remark = ''
+ form.products = []
+ form.subtotal = 0
+ form.freight = 0
+ form.otherFee = 0
+ form.discountRate = 0
+ form.discountAmount = 0
+ form.totalAmount = 0
+}
+
+const addProduct = () => {
+ form.products.push({
+ productId: '',
+ product: '',
+ productName: '',
+ specificationId: '',
+ specification: '',
+ quantity: 1,
+ unit: '',
+ unitPrice: 0,
+ amount: 0
+ })
+}
+
+const removeProduct = (index) => {
+ form.products.splice(index, 1)
+ calculateSubtotal()
+}
+
+const calculateAmount = (product) => {
+ product.amount = product.quantity * product.unitPrice
+ calculateSubtotal()
+}
+
+const calculateSubtotal = () => {
+ form.subtotal = form.products.reduce((sum, product) => sum + product.amount, 0)
+ calculateTotal()
+}
+
+const calculateTotal = () => {
+ form.discountAmount = form.subtotal * (form.discountRate / 100)
+ form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount
+}
+
+const handleCustomerChange = () => {
+ // 鍙互鏍规嵁瀹㈡埛淇℃伅鑷姩濉厖涓�浜涢粯璁ゅ��
+}
+
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (form.products.length === 0) {
+ ElMessage.warning('璇疯嚦灏戞坊鍔犱竴涓骇鍝�')
+ return
+ }
+
+ // 瀹℃壒浜哄繀濉牎楠�
+ const hasEmptyApprover = approverNodes.value.some(node => !node.userId)
+ if (hasEmptyApprover) {
+ ElMessage.error('璇蜂负鎵�鏈夊鎵硅妭鐐归�夋嫨瀹℃壒浜猴紒')
+ return
+ }
+
+ // 鏀堕泦鎵�鏈夎妭鐐圭殑瀹℃壒浜篿d
+ form.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
+
+ // 璁$畻鎵�鏈変骇鍝佺殑鍗曚环鎬诲拰
+ form.totalAmount = form.products.reduce((sum, product) => {
+ const price = Number(product.unitPrice) || 0
+ return sum + price
+ }, 0)
+
+ if (isEdit.value) {
+ // 缂栬緫
+ const index = quotationList.value.findIndex(item => item.id === editId.value)
+ if (index > -1) {
+ updateQuotation(form).then(res=>{
+ // console.log(res)
+ if(res.code===200){
+ ElMessage.success('缂栬緫鎴愬姛')
+ dialogVisible.value = false
+ handleSearch()
+ }
+ })
+ }
+ } else {
+ // 鏂板
+ addQuotation(form).then(res=>{
+ if(res.code===200){
+ ElMessage.success('鏂板鎴愬姛')
+ dialogVisible.value = false
+ handleSearch()
+ }
+ })
+ }
+
+ }
+ })
+}
+
+const handleCurrentChange = (val) => {
+ pagination.currentPage = val.page
+ pagination.pageSize = val.limit
+}
+const handleSearch = ()=>{
+ const params = {
+ ...pagination,
+ ...searchForm
+ }
+ getQuotationList(params).then(res=>{
+ // console.log(res)
+ if(res.code===200){
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鎴栧叾浠栧璞℃斁鍏ュ搷搴斿紡瀵硅薄
+ quotationList.value = (res.data.records || []).map(item => ({
+ id: item.id,
+ quotationNo: item.quotationNo || '',
+ customer: item.customer || '',
+ salesperson: item.salesperson || '',
+ quotationDate: item.quotationDate || '',
+ validDate: item.validDate || '',
+ paymentMethod: item.paymentMethod || '',
+ status: item.status || '鑽夌',
+ // 瀹℃壒浜猴紙鐢ㄤ簬缂栬緫鏃跺弽鏄撅級
+ approveUserIds: item.approveUserIds || '',
+ remark: item.remark || '',
+ products: item.products ? item.products.map(product => ({
+ productId: product.productId || '',
+ product: product.product || product.productName || '',
+ specificationId: product.specificationId || '',
+ specification: product.specification || '',
+ quantity: product.quantity || 0,
+ unit: product.unit || '',
+ unitPrice: product.unitPrice || 0,
+ amount: product.amount || 0
+ })) : [],
+ subtotal: item.subtotal || 0,
+ freight: item.freight || 0,
+ otherFee: item.otherFee || 0,
+ discountRate: item.discountRate || 0,
+ discountAmount: item.discountAmount || 0,
+ totalAmount: item.totalAmount || 0
+ }))
+ pagination.total = res.data.total
+ }
+ })
+ customerList().then((res) => {
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
+ id: item.id,
+ customerName: item.customerName || '',
+ taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
+ }))
+ });
+}
+
+onMounted(()=>{
+ handleSearch()
+})
+</script>
+
+<style scoped lang="scss">
+.search-row {
+ margin-bottom: 20px;
+}
+
+.quotation-form-container {
+ padding: 10px 0;
+ max-height: calc(100vh - 200px);
+ overflow-y: auto;
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #c1c1c1;
+ border-radius: 3px;
+
+ &:hover {
+ background: #a8a8a8;
+ }
+ }
+}
+
+.quotation-form {
+ .el-form-item {
+ margin-bottom: 22px;
+ }
+}
+
+.form-card {
+ margin-bottom: 24px;
+ border-radius: 8px;
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
+ }
+
+ :deep(.el-card__header) {
+ padding: 16px 20px;
+ background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
+ border-bottom: 1px solid #ebeef5;
+ }
+
+ :deep(.el-card__body) {
+ padding: 20px;
+ }
+}
+
+.card-header-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .card-icon {
+ font-size: 18px;
+ color: #409eff;
+ }
+
+ .card-title {
+ font-weight: 600;
+ font-size: 16px;
+ color: #303133;
+ flex: 1;
+ }
+
+ .header-btn {
+ margin-left: auto;
+ }
+}
+
+.form-content {
+ padding: 8px 0;
+}
+
+.approver-nodes-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 24px;
+ padding: 12px 0;
+}
+
+.approver-node-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ padding: 16px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e4e7ed;
+ transition: all 0.3s ease;
+ min-width: 180px;
+
+ &:hover {
+ border-color: #409eff;
+ background: #f0f7ff;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+ }
+}
+
+.approver-node-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: #606266;
+
+ .node-step {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ background: #409eff;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 12px;
+ font-weight: 600;
+ }
+
+ .node-text {
+ font-weight: 500;
+ }
+
+ .arrow-icon {
+ color: #909399;
+ font-size: 14px;
+ }
+}
+
+.approver-select {
+ width: 100%;
+ min-width: 150px;
+}
+
+.remove-btn {
+ margin-top: 4px;
+}
+
+.product-table {
+ :deep(.el-table__header) {
+ background-color: #f5f7fa;
+
+ th {
+ background-color: #f5f7fa !important;
+ color: #606266;
+ font-weight: 600;
+ }
+ }
+
+ :deep(.el-table__row) {
+ &:hover {
+ background-color: #f5f7fa;
+ }
+ }
+
+ :deep(.el-table__cell) {
+ padding: 12px 0;
+ }
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+// 鍝嶅簲寮忎紭鍖�
+@media (max-width: 1200px) {
+ .approver-nodes-container {
+ gap: 16px;
+ }
+
+ .approver-node-item {
+ min-width: 160px;
+ }
+}
+</style>
diff --git a/src/views/salesManagement/salespersonManagement/index.vue b/src/views/salesManagement/salespersonManagement/index.vue
new file mode 100644
index 0000000..e0094ec
--- /dev/null
+++ b/src/views/salesManagement/salespersonManagement/index.vue
@@ -0,0 +1,371 @@
+<template>
+ <div class="app-container">
+ <el-card class="box-card">
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-input
+ v-model="searchForm.name"
+ placeholder="璇疯緭鍏ヤ笟鍔″憳濮撳悕"
+ clearable
+ @keyup.enter="handleSearch"
+ >
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.department" placeholder="璇烽�夋嫨閮ㄩ棬" clearable>
+ <el-option label="閿�鍞儴" value="閿�鍞儴"></el-option>
+ <el-option label="甯傚満閮�" value="甯傚満閮�"></el-option>
+ <el-option label="瀹㈡湇閮�" value="瀹㈡湇閮�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" clearable>
+ <el-option label="鍦ㄨ亴" value="鍦ㄨ亴"></el-option>
+ <el-option label="绂昏亴" value="绂昏亴"></el-option>
+ <el-option label="璇曠敤鏈�" value="璇曠敤鏈�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ <el-button type="primary" style="float: right;" @click="handleAdd">鏂板涓氬姟鍛�</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 涓氬姟鍛樺垪琛� -->
+ <el-table
+ :data="filteredList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)"
+ >
+ <el-table-column prop="id" label="ID" width="80" align="center"/>
+ <el-table-column prop="name" label="濮撳悕" width="120" />
+ <el-table-column prop="phone" label="鑱旂郴鐢佃瘽" width="140" />
+ <el-table-column prop="email" label="閭" width="200" />
+ <el-table-column prop="department" label="閮ㄩ棬" width="100" />
+ <el-table-column prop="position" label="鑱屼綅" width="100" />
+ <el-table-column prop="hireDate" label="鍏ヨ亴鏃ユ湡" width="120" />
+ <el-table-column prop="status" label="鐘舵��" width="80">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="permissions" label="鏉冮檺">
+ <template #default="scope">
+ <el-tag v-for="perm in scope.row.permissionsList" :key="perm" size="small" style="margin-right: 5px;">
+ {{ perm }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button link type="primary" @click="handlePermissions(scope.row)">鏉冮檺</el-button>
+ <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.current"
+ :limit="pagination.size"
+ @pagination="handleCurrentChange"
+ />
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <FormDialog v-model="dialogVisible" :title="dialogTitle" :width="'600px'" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="濮撳悕" prop="name">
+ <el-input v-model="form.name" placeholder="璇疯緭鍏ュ鍚�"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴鐢佃瘽" prop="phone">
+ <el-input v-model="form.phone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閭" prop="email">
+ <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閮ㄩ棬" prop="department">
+ <el-select v-model="form.department" placeholder="璇烽�夋嫨閮ㄩ棬" style="width: 100%">
+ <el-option label="閿�鍞儴" value="閿�鍞儴"></el-option>
+ <el-option label="甯傚満閮�" value="甯傚満閮�"></el-option>
+ <el-option label="瀹㈡湇閮�" value="瀹㈡湇閮�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鑱屼綅" prop="position">
+ <el-input v-model="form.position" placeholder="璇疯緭鍏ヨ亴浣�"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏ヨ亴鏃ユ湡" prop="hireDate">
+ <el-date-picker
+ v-model="form.hireDate"
+ type="date"
+ placeholder="閫夋嫨鍏ヨ亴鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option label="鍦ㄨ亴" value="鍦ㄨ亴"></el-option>
+ <el-option label="绂昏亴" value="绂昏亴"></el-option>
+ <el-option label="璇曠敤鏈�" value="璇曠敤鏈�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
+
+ <!-- 鏉冮檺璁剧疆瀵硅瘽妗� -->
+ <FormDialog v-model="permissionDialogVisible" title="鏉冮檺璁剧疆" :width="'500px'" @close="permissionDialogVisible = false" @confirm="savePermissions" @cancel="permissionDialogVisible = false">
+ <el-form label-width="100px">
+ <el-form-item label="涓氬姟鍛樺鍚�">
+ <span>{{ currentSalesperson.name }}</span>
+ </el-form-item>
+ <el-form-item label="鏉冮檺璁剧疆">
+ <el-checkbox-group v-model="currentPermissions">
+ <el-checkbox label="璁㈠崟绠$悊">璁㈠崟绠$悊</el-checkbox>
+ <el-checkbox label="瀹㈡埛绠$悊">瀹㈡埛绠$悊</el-checkbox>
+ <el-checkbox label="璐㈠姟绠$悊">璐㈠姟绠$悊</el-checkbox>
+ <el-checkbox label="鍙戣揣绠$悊">鍙戣揣绠$悊</el-checkbox>
+ <el-checkbox label="鎶ヨ〃鏌ョ湅">鎶ヨ〃鏌ョ湅</el-checkbox>
+ <el-checkbox label="绯荤粺璁剧疆">绯荤粺璁剧疆</el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed,onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {listPage,add,update,deleteSalespersonManagement} from '@/api/salesManagement/salespersonManagement.js'
+import { Plus, Search } from '@element-plus/icons-vue'
+import Pagination from '@/components/PIMTable/Pagination.vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+
+const salespersonList = ref([])
+const total = ref(0)
+
+onMounted(() => {
+ getList()
+})
+const getList = () => {
+ loading.value = true
+ listPage({...pagination,...searchForm}).then(res => {
+ salespersonList.value = res.data.records
+ total.value = res.data.total
+ loading.value = false
+ })
+}
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false)
+const searchForm = reactive({
+ name: '',
+ department: '',
+ status: ''
+})
+
+
+
+const pagination = reactive({
+ current: 1,
+ size: 10
+})
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('鏂板涓氬姟鍛�')
+const form = reactive({
+ name: '',
+ phone: '',
+ email: '',
+ department: '',
+ position: '',
+ hireDate: '',
+ status: '鍦ㄨ亴'
+})
+
+const rules = {
+ name: [{ required: true, message: '璇疯緭鍏ュ鍚�', trigger: 'blur' }],
+ phone: [{ required: true, message: '璇疯緭鍏ヨ仈绯荤數璇�', trigger: 'blur' }],
+ email: [{ required: true, message: '璇疯緭鍏ラ偖绠�', trigger: 'blur' }],
+ department: [{ required: true, message: '璇烽�夋嫨閮ㄩ棬', trigger: 'change' }],
+ position: [{ required: true, message: '璇疯緭鍏ヨ亴浣�', trigger: 'blur' }],
+ hireDate: [{ required: true, message: '璇烽�夋嫨鍏ヨ亴鏃ユ湡', trigger: 'change' }],
+ status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+}
+
+const isEdit = ref(false)
+const editId = ref(null)
+const permissionDialogVisible = ref(false)
+const currentSalesperson = ref({})
+const currentPermissions = ref([])
+const formRef = ref()
+
+// 璁$畻灞炴��
+const filteredList = computed(() => {
+ let list = salespersonList.value
+ if (searchForm.name) {
+ list = list.filter(item => item.name.includes(searchForm.name))
+ }
+ if (searchForm.department) {
+ list = list.filter(item => item.department === searchForm.department)
+ }
+ if (searchForm.status) {
+ list = list.filter(item => item.status === searchForm.status)
+ }
+ return list
+})
+
+// 鏂规硶
+const getStatusType = (status) => {
+ const statusMap = {
+ '鍦ㄨ亴': 'success',
+ '绂昏亴': 'danger',
+ '璇曠敤鏈�': 'warning'
+ }
+ return statusMap[status] || 'info'
+}
+
+const handleSearch = () => {
+ getList()
+ // 鎼滅储閫昏緫宸插湪computed涓鐞�
+}
+
+const resetSearch = () => {
+ searchForm.name = ''
+ searchForm.department = ''
+ searchForm.status = ''
+}
+
+const handleAdd = () => {
+ dialogTitle.value = '鏂板涓氬姟鍛�'
+ isEdit.value = false
+ form.name = ''
+ form.phone = ''
+ form.email = ''
+ form.department = ''
+ form.position = ''
+ form.hireDate = ''
+ form.status = '鍦ㄨ亴'
+ dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+ dialogTitle.value = '缂栬緫涓氬姟鍛�'
+ isEdit.value = true
+ editId.value = row.id
+ Object.assign(form, row)
+ dialogVisible.value = true
+}
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ヤ笟鍔″憳鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ let ids = [row.id]
+ deleteSalespersonManagement(ids).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ getList()
+ }
+ })
+ })
+}
+
+const handlePermissions = (row) => {
+ currentSalesperson.value = row
+ currentPermissions.value = row.permissions.split(",")
+ permissionDialogVisible.value = true
+}
+
+const savePermissions = () => {
+ let splice = currentPermissions.value;
+ if(splice[0] === ''){
+ splice.splice(0,1)
+ }
+ currentSalesperson.value.permissions = splice.join(",")
+ update(currentSalesperson.value).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏉冮檺璁剧疆鎴愬姛')
+ permissionDialogVisible.value = false
+ getList()
+ }
+ })
+}
+
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (isEdit.value) {
+ // 缂栬緫
+ update(form).then(res => {
+ if(res.code === 200){
+ ElMessage.success('缂栬緫鎴愬姛')
+ dialogVisible.value = false
+ getList()
+ }
+ })
+ } else {
+ add(form).then(res => {
+ if(res.code === 200){
+ ElMessage.success('鏂板鎴愬姛')
+ dialogVisible.value = false
+ getList()
+ }
+ })
+
+ }
+
+ }
+ })
+}
+
+const handleCurrentChange = (val) => {
+ pagination.value.currentPage = val.page
+ pagination.value.pageSize = val.limit
+}
+</script>
+
+<style scoped>
+.search-row {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/src/views/salesManagement/strategyControl/index.vue b/src/views/salesManagement/strategyControl/index.vue
new file mode 100644
index 0000000..629d255
--- /dev/null
+++ b/src/views/salesManagement/strategyControl/index.vue
@@ -0,0 +1,1587 @@
+<template>
+ <div class="app-container strategy-control">
+ <el-tabs v-model="activeTab" type="border-card" class="main-tabs" @tab-change="handleTabChange">
+ <!-- 浠锋牸绛栫暐閰嶇疆 -->
+ <el-tab-pane label="浠锋牸绛栫暐閰嶇疆" name="priceStrategy">
+ <el-card class="box-card">
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-select v-model="priceSearchForm.customerName" placeholder="璇烽�夋嫨瀹㈡埛" clearable>
+ <el-option label="鍏ㄩ儴瀹㈡埛" value=""></el-option>
+ <el-option label="鍗庝笢寤烘潗闆嗗洟" value="鍗庝笢寤烘潗闆嗗洟"></el-option>
+ <el-option label="闀挎睙娣峰嚌鍦熷叕鍙�" value="闀挎睙娣峰嚌鍦熷叕鍙�"></el-option>
+ <el-option label="娴︽睙姘存偿鍒跺搧鍘�" value="娴︽睙姘存偿鍒跺搧鍘�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="priceSearchForm.productType" placeholder="璇烽�夋嫨姘存偿绫诲瀷" clearable>
+ <el-option label="鍏ㄩ儴绫诲瀷" value=""></el-option>
+ <el-option label="鏅�氱閰哥洂姘存偿" value="鏅�氱閰哥洂姘存偿"></el-option>
+ <el-option label="鐭挎福纭呴吀鐩愭按娉�" value="鐭挎福纭呴吀鐩愭按娉�"></el-option>
+ <el-option label="澶嶅悎纭呴吀鐩愭按娉�" value="澶嶅悎纭呴吀鐩愭按娉�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="priceSearchForm.strategyType" placeholder="绛栫暐绫诲瀷" clearable>
+ <el-option label="鍏ㄩ儴绛栫暐" value=""></el-option>
+ <el-option label="涓撳睘浠锋牸" value="涓撳睘浠锋牸"></el-option>
+ <el-option label="闃舵鎶ヤ环" value="闃舵鎶ヤ环"></el-option>
+ <el-option label="淇冮攢鎶樻墸" value="淇冮攢鎶樻墸"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary" @click="searchPriceStrategy">鏌ヨ</el-button>
+ <el-button @click="resetPriceSearch">閲嶇疆</el-button>
+ <el-button type="primary" @click="handleAddPriceStrategy">鏂板绛栫暐</el-button>
+ </el-col>
+ </el-row>
+
+ <el-table :data="priceStrategyList" border stripe v-loading="priceLoading" height="calc(100vh - 26em)">
+ <el-table-column prop="id" label="ID" width="60" align="center"/>
+ <el-table-column prop="strategyNo" label="绛栫暐缂栧彿" width="150"/>
+ <el-table-column prop="strategyType" label="绛栫暐绫诲瀷" width="100">
+ <template #default="scope">
+ <el-tag :type="getStrategyTypeColor(scope.row.strategyType)">
+ {{ scope.row.strategyType }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" width="180"/>
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" width="200"/>
+ <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" width="120"/>
+ <el-table-column prop="basePrice" label="鍩虹浠锋牸" width="100">
+ <template #default="scope">
+ 楼{{ scope.row.basePrice }}/鍚�
+ </template>
+ </el-table-column>
+ <el-table-column prop="strategyPrice" label="绛栫暐浠锋牸" width="120">
+ <template #default="scope">
+ <span style="color: #f56c6c; font-weight: bold;">
+ {{ scope.row.strategyPrice }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="validPeriod" label="鏈夋晥鏈�" width="200">
+ <template #default="scope">
+ {{ scope.row.startDate }} 鑷� {{ scope.row.endDate }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="80">
+ <template #default="scope">
+ <el-tag :type="scope.row.status === '鐢熸晥涓�' ? 'success' : 'info'">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleViewPriceStrategy(scope.row)">鏌ョ湅</el-button>
+ <el-button link type="primary" @click="handleEditPriceStrategy(scope.row)">缂栬緫</el-button>
+ <el-button link type="danger" @click="handleDeletePriceStrategy(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ :total="pricePagination.total"
+ :page="pricePagination.currentPage"
+ :limit="pricePagination.pageSize"
+ @pagination="handlePricePageChange"
+ />
+ </el-card>
+ </el-tab-pane>
+
+ <!-- 鍚堝悓鎵ц鐩戞帶 -->
+ <el-tab-pane label="鍚堝悓鎵ц鐩戞帶" name="contractMonitor">
+ <el-card class="box-card">
+ <!-- 缁熻姒傝 -->
+ <el-row :gutter="20" class="stats-row">
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #ecf5ff;">
+ <el-icon :size="30" color="#409eff"><Document /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ contractStats.totalContracts }}</div>
+ <div class="stat-label">鍚堝悓鎬绘暟</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #f0f9ff;">
+ <el-icon :size="30" color="#67c23a"><Van /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ contractStats.deliveryRate }}%</div>
+ <div class="stat-label">浜や粯瀹屾垚鐜�</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #fef0f0;">
+ <el-icon :size="30" color="#e6a23c"><Tickets /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ contractStats.invoiceRate }}%</div>
+ <div class="stat-label">鍙戠エ寮�鍏风巼</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #f4f4f5;">
+ <el-icon :size="30" color="#f56c6c"><Wallet /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ contractStats.paymentRate }}%</div>
+ <div class="stat-label">鍥炴瀹屾垚鐜�</div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-input v-model="contractSearchForm.contractNo" placeholder="璇疯緭鍏ュ悎鍚岀紪鍙�" clearable>
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="contractSearchForm.customerName" placeholder="璇烽�夋嫨瀹㈡埛" clearable>
+ <el-option label="鍗庝笢寤烘潗闆嗗洟" value="鍗庝笢寤烘潗闆嗗洟"></el-option>
+ <el-option label="闀挎睙娣峰嚌鍦熷叕鍙�" value="闀挎睙娣峰嚌鍦熷叕鍙�"></el-option>
+ <el-option label="娴︽睙姘存偿鍒跺搧鍘�" value="娴︽睙姘存偿鍒跺搧鍘�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="contractSearchForm.executionStatus" placeholder="鎵ц鐘舵��" clearable>
+ <el-option label="寰呮墽琛�" value="寰呮墽琛�"></el-option>
+ <el-option label="鎵ц涓�" value="鎵ц涓�"></el-option>
+ <el-option label="宸插畬鎴�" value="宸插畬鎴�"></el-option>
+ <el-option label="寮傚父" value="寮傚父"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary" @click="searchContract">鏌ヨ</el-button>
+ <el-button @click="resetContractSearch">閲嶇疆</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鍚堝悓鍒楄〃 -->
+ <el-table :data="contractList" border stripe v-loading="contractLoading" height="calc(100vh - 36em)">
+ <el-table-column type="expand">
+ <template #default="scope">
+ <div class="contract-detail-expand">
+ <el-steps :active="getContractStep(scope.row)" align-center>
+ <el-step title="璁㈠崟纭" :description="scope.row.orderDate">
+ <template #icon>
+ <el-icon :color="scope.row.orderStatus === '宸插畬鎴�' ? '#67c23a' : '#909399'">
+ <Check v-if="scope.row.orderStatus === '宸插畬鎴�'" />
+ <Clock v-else />
+ </el-icon>
+ </template>
+ </el-step>
+ <el-step title="璐х墿浜や粯" :description="`${scope.row.deliveryProgress}%`">
+ <template #icon>
+ <el-icon :color="scope.row.deliveryProgress === 100 ? '#67c23a' : '#409eff'">
+ <Check v-if="scope.row.deliveryProgress === 100" />
+ <Van v-else />
+ </el-icon>
+ </template>
+ </el-step>
+ <el-step title="鍙戠エ寮�鍏�" :description="`${scope.row.invoiceProgress}%`">
+ <template #icon>
+ <el-icon :color="scope.row.invoiceProgress === 100 ? '#67c23a' : '#e6a23c'">
+ <Check v-if="scope.row.invoiceProgress === 100" />
+ <Tickets v-else />
+ </el-icon>
+ </template>
+ </el-step>
+ <el-step title="娆鹃」鏀跺洖" :description="`${scope.row.paymentProgress}%`">
+ <template #icon>
+ <el-icon :color="scope.row.paymentProgress === 100 ? '#67c23a' : '#f56c6c'">
+ <Check v-if="scope.row.paymentProgress === 100" />
+ <Wallet v-else />
+ </el-icon>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column prop="contractNo" label="鍚堝悓缂栧彿" width="150"/>
+ <el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" width="180"/>
+ <el-table-column prop="contractAmount" label="鍚堝悓閲戦" width="120">
+ <template #default="scope">
+ 楼{{ scope.row.contractAmount.toLocaleString() }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="signDate" label="绛捐鏃ユ湡" width="120"/>
+ <el-table-column label="鎵ц杩涘害" width="150">
+ <template #default="scope">
+ <el-progress
+ :percentage="scope.row.executionProgress"
+ :color="getProgressColor(scope.row.executionProgress)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column prop="deliveryProgress" label="浜や粯杩涘害" width="100">
+ <template #default="scope">
+ {{ scope.row.deliveryProgress }}%
+ </template>
+ </el-table-column>
+ <el-table-column prop="invoiceProgress" label="寮�绁ㄨ繘搴�" width="100">
+ <template #default="scope">
+ {{ scope.row.invoiceProgress }}%
+ </template>
+ </el-table-column>
+ <el-table-column prop="paymentProgress" label="鍥炴杩涘害" width="100">
+ <template #default="scope">
+ {{ scope.row.paymentProgress }}%
+ </template>
+ </el-table-column>
+ <el-table-column prop="executionStatus" label="鎵ц鐘舵��" width="100">
+ <template #default="scope">
+ <el-tag :type="getExecutionStatusType(scope.row.executionStatus)">
+ {{ scope.row.executionStatus }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="120" fixed="right" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click="handleViewContract(scope.row)">鏌ョ湅璇︽儏</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ :total="contractPagination.total"
+ :page="contractPagination.currentPage"
+ :limit="contractPagination.pageSize"
+ @pagination="handleContractPageChange"
+ />
+ </el-card>
+ </el-tab-pane>
+
+ <!-- 鍘嗗彶姣斾环鍒嗘瀽 -->
+ <el-tab-pane label="鍘嗗彶姣斾环鍒嗘瀽" name="priceComparison">
+ <el-card class="box-card">
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-select v-model="compareSearchForm.productName" placeholder="璇烽�夋嫨浜у搧" clearable>
+ <el-option label="P.O 42.5鏅�氱閰哥洂姘存偿" value="P.O 42.5鏅�氱閰哥洂姘存偿"></el-option>
+ <el-option label="P.S 32.5鐭挎福纭呴吀鐩愭按娉�" value="P.S 32.5鐭挎福纭呴吀鐩愭按娉�"></el-option>
+ <el-option label="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�" value="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="8">
+ <el-date-picker
+ v-model="compareSearchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 100%"
+ />
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="compareSearchForm.region" placeholder="閿�鍞尯鍩�" clearable>
+ <el-option label="鍗庝笢鍦板尯" value="鍗庝笢鍦板尯"></el-option>
+ <el-option label="鍗庡崡鍦板尯" value="鍗庡崡鍦板尯"></el-option>
+ <el-option label="鍗庡寳鍦板尯" value="鍗庡寳鍦板尯"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="4">
+ <el-button type="primary" @click="searchPriceComparison">鏌ヨ</el-button>
+ <el-button @click="resetCompareSearch">閲嶇疆</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 浠锋牸瓒嬪娍鍥� -->
+ <div class="chart-container">
+ <div ref="priceChartRef" style="width: 100%; height: 350px;"></div>
+ </div>
+
+ <!-- 鍘嗗彶浠锋牸鍒楄〃 -->
+ <el-table :data="priceComparisonList" border stripe v-loading="compareLoading" style="margin-top: 20px;">
+ <el-table-column prop="date" label="鏃ユ湡" width="120"/>
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" width="200"/>
+ <el-table-column prop="specification" label="瑙勬牸" width="120"/>
+ <el-table-column prop="customerName" label="瀹㈡埛" width="180"/>
+ <el-table-column prop="region" label="鍖哄煙" width="100"/>
+ <el-table-column prop="quantity" label="鏁伴噺(鍚�)" width="100" align="right">
+ <template #default="scope">
+ {{ scope.row.quantity.toLocaleString() }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="price" label="鎴愪氦鍗曚环" width="100">
+ <template #default="scope">
+ 楼{{ scope.row.price }}/鍚�
+ </template>
+ </el-table-column>
+ <el-table-column prop="totalAmount" label="鎴愪氦閲戦" width="120">
+ <template #default="scope">
+ 楼{{ scope.row.totalAmount.toLocaleString() }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="priceChange" label="浠锋牸鍙樺姩" width="100">
+ <template #default="scope">
+ <span :style="{ color: scope.row.priceChange > 0 ? '#f56c6c' : scope.row.priceChange < 0 ? '#67c23a' : '#909399' }">
+ {{ scope.row.priceChange > 0 ? '+' : '' }}{{ scope.row.priceChange }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip/>
+ </el-table>
+ </el-card>
+ </el-tab-pane>
+
+ <!-- 鍒╂鼎鍒嗘瀽 -->
+ <el-tab-pane label="鍒╂鼎鍒嗘瀽" name="profitAnalysis">
+ <el-card class="box-card">
+ <!-- 鍒╂鼎缁熻鍗$墖 -->
+ <el-row :gutter="20" class="profit-stats-row">
+ <el-col :span="8">
+ <div class="profit-card">
+ <div class="profit-header">鎬婚攢鍞</div>
+ <div class="profit-value">楼{{ profitStats.totalSales.toLocaleString() }}</div>
+ <div class="profit-footer">
+ <span>杈冧笂鏈�</span>
+ <span :class="profitStats.salesGrowth > 0 ? 'growth-up' : 'growth-down'">
+ {{ profitStats.salesGrowth > 0 ? '+' : '' }}{{ profitStats.salesGrowth }}%
+ </span>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="profit-card">
+ <div class="profit-header">鎬绘垚鏈�</div>
+ <div class="profit-value">楼{{ profitStats.totalCost.toLocaleString() }}</div>
+ <div class="profit-footer">
+ <span>鎴愭湰鐜�</span>
+ <span class="cost-rate">{{ profitStats.costRate }}%</span>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="profit-card">
+ <div class="profit-header">姣涘埄娑�</div>
+ <div class="profit-value profit-highlight">楼{{ profitStats.grossProfit.toLocaleString() }}</div>
+ <div class="profit-footer">
+ <span>姣涘埄鐜�</span>
+ <span class="gross-profit-rate">{{ profitStats.grossProfitRate }}%</span>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-select v-model="profitSearchForm.productType" placeholder="浜у搧绫诲瀷" clearable>
+ <el-option label="鏅�氱閰哥洂姘存偿" value="鏅�氱閰哥洂姘存偿"></el-option>
+ <el-option label="鐭挎福纭呴吀鐩愭按娉�" value="鐭挎福纭呴吀鐩愭按娉�"></el-option>
+ <el-option label="澶嶅悎纭呴吀鐩愭按娉�" value="澶嶅悎纭呴吀鐩愭按娉�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="profitSearchForm.customerName" placeholder="瀹㈡埛鍚嶇О" clearable>
+ <el-option label="鍗庝笢寤烘潗闆嗗洟" value="鍗庝笢寤烘潗闆嗗洟"></el-option>
+ <el-option label="闀挎睙娣峰嚌鍦熷叕鍙�" value="闀挎睙娣峰嚌鍦熷叕鍙�"></el-option>
+ <el-option label="娴︽睙姘存偿鍒跺搧鍘�" value="娴︽睙姘存偿鍒跺搧鍘�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="8">
+ <el-date-picker
+ v-model="profitSearchForm.dateRange"
+ type="monthrange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫湀浠�"
+ end-placeholder="缁撴潫鏈堜唤"
+ value-format="YYYY-MM"
+ style="width: 100%"
+ />
+ </el-col>
+ <el-col :span="4">
+ <el-button type="primary" @click="searchProfit">鏌ヨ</el-button>
+ <el-button @click="resetProfitSearch">閲嶇疆</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鍒╂鼎鍒嗘瀽鍥捐〃 -->
+ <div class="chart-container">
+ <div ref="profitChartRef" style="width: 100%; height: 350px;"></div>
+ </div>
+
+ <!-- 鍒╂鼎鏄庣粏琛� -->
+ <el-table :data="profitAnalysisList" border stripe v-loading="profitLoading" style="margin-top: 20px;" show-summary :summary-method="getProfitSummary">
+ <el-table-column prop="orderNo" label="璁㈠崟缂栧彿" width="150"/>
+ <el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" width="180"/>
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" width="200"/>
+ <el-table-column prop="quantity" label="鏁伴噺(鍚�)" width="100" align="right">
+ <template #default="scope">
+ {{ scope.row.quantity.toLocaleString() }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="salesPrice" label="閿�鍞崟浠�" width="100">
+ <template #default="scope">
+ 楼{{ scope.row.salesPrice }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="costPrice" label="鎴愭湰鍗曚环" width="100">
+ <template #default="scope">
+ 楼{{ scope.row.costPrice }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="salesAmount" label="閿�鍞噾棰�" width="120" align="right">
+ <template #default="scope">
+ 楼{{ scope.row.salesAmount.toLocaleString() }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="costAmount" label="鎴愭湰閲戦" width="120" align="right">
+ <template #default="scope">
+ 楼{{ scope.row.costAmount.toLocaleString() }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="grossProfit" label="姣涘埄娑�" width="120" align="right">
+ <template #default="scope">
+ <span :style="{ color: scope.row.grossProfit > 0 ? '#67c23a' : '#f56c6c', fontWeight: 'bold' }">
+ 楼{{ scope.row.grossProfit.toLocaleString() }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="grossProfitRate" label="姣涘埄鐜�" width="100">
+ <template #default="scope">
+ <el-tag :type="getProfitRateType(scope.row.grossProfitRate)">
+ {{ scope.row.grossProfitRate }}%
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="orderDate" label="璁㈠崟鏃ユ湡" width="120"/>
+ </el-table>
+
+ <pagination
+ :total="profitPagination.total"
+ :page="profitPagination.currentPage"
+ :limit="profitPagination.pageSize"
+ @pagination="handleProfitPageChange"
+ />
+ </el-card>
+ </el-tab-pane>
+
+ <!-- 鎸囨爣缁熻锛堝缁村害閿�鍞垎鏋愶級 -->
+ <el-tab-pane label="鎸囨爣缁熻" name="indicatorStats">
+ <el-card class="box-card">
+ <!-- KPI 姹囨�� -->
+ <el-row :gutter="20" class="stats-row">
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #ecf5ff;">
+ <el-icon :size="30" color="#409eff"><Document /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div>
+ <div class="stat-label">璁㈠崟鏁伴噺</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #f0f9ff;">
+ <el-icon :size="30" color="#67c23a"><Tickets /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">楼{{ indicatorKpis.salesAmount.toLocaleString() }}</div>
+ <div class="stat-label">閿�鍞</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #fef0f0;">
+ <el-icon :size="30" color="#e6a23c"><Van /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ indicatorKpis.shipmentRate }}%</div>
+ <div class="stat-label">鍙戣揣鐜�</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-card">
+ <div class="stat-icon" style="background: #f4f4f5;">
+ <el-icon :size="30" color="#f56c6c"><Wallet /></el-icon>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ indicatorKpis.collectionRate }}%</div>
+ <div class="stat-label">鍥炴鐜�</div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+
+ <!-- 缁村害绛涢�� -->
+ <el-row :gutter="20" class="search-row">
+ <el-col :span="6">
+ <el-select v-model="indicatorFilter.product" placeholder="浜у搧" clearable>
+ <el-option label="鍏ㄩ儴浜у搧" value="" />
+ <el-option label="P.O 42.5鏅�氱閰哥洂姘存偿" value="P.O 42.5鏅�氱閰哥洂姘存偿" />
+ <el-option label="P.S 32.5鐭挎福纭呴吀鐩愭按娉�" value="P.S 32.5鐭挎福纭呴吀鐩愭按娉�" />
+ <el-option label="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�" value="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�" />
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="indicatorFilter.customer" placeholder="瀹㈡埛" clearable>
+ <el-option label="鍏ㄩ儴瀹㈡埛" value="" />
+ <el-option label="鍗庝笢寤烘潗闆嗗洟" value="鍗庝笢寤烘潗闆嗗洟" />
+ <el-option label="闀挎睙娣峰嚌鍦熷叕鍙�" value="闀挎睙娣峰嚌鍦熷叕鍙�" />
+ <el-option label="娴︽睙姘存偿鍒跺搧鍘�" value="娴︽睙姘存偿鍒跺搧鍘�" />
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="indicatorFilter.region" placeholder="鍖哄煙" clearable>
+ <el-option label="鍏ㄩ儴鍖哄煙" value="" />
+ <el-option label="鍗庝笢鍦板尯" value="鍗庝笢鍦板尯" />
+ <el-option label="鍗庡崡鍦板尯" value="鍗庡崡鍦板尯" />
+ <el-option label="鍗庡寳鍦板尯" value="鍗庡寳鍦板尯" />
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-date-picker v-model="indicatorFilter.dateRange" type="daterange" range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%" />
+ </el-col>
+ <el-col :span="24" style="text-align: right; margin-top: 10px;">
+ <el-button type="primary" @click="applyIndicatorFilter">鏌ヨ</el-button>
+ <el-button @click="resetIndicatorFilter">閲嶇疆</el-button>
+ <el-button @click="exportIndicatorTable">瀵煎嚭鎶ヨ〃</el-button>
+ <el-button @click="exportIndicatorChart">瀵煎嚭鍥捐〃</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鍥捐〃鍖� -->
+ <div class="chart-container">
+ <div ref="indicatorChartRef" style="width: 100%; height: 360px;"></div>
+ </div>
+
+ <!-- 涓氱哗缁熻锛堝洟闃熺淮搴︼紝鏃犱釜浜哄鍚嶏級 -->
+ <el-table :data="teamPerformanceList" border stripe style="margin-top: 20px;">
+ <el-table-column prop="team" label="閿�鍞洟闃�" width="140" />
+ <el-table-column prop="orderCount" label="璁㈠崟鏁�" width="100" />
+ <el-table-column prop="salesAmount" label="閿�鍞" width="140">
+ <template #default="scope">楼{{ scope.row.salesAmount.toLocaleString() }}</template>
+ </el-table-column>
+ <el-table-column prop="shipmentRate" label="鍙戣揣鐜�" width="100">
+ <template #default="scope">{{ scope.row.shipmentRate }}%</template>
+ </el-table-column>
+ <el-table-column prop="collectionRate" label="鍥炴鐜�" width="100">
+ <template #default="scope">{{ scope.row.collectionRate }}%</template>
+ </el-table-column>
+ <el-table-column prop="attainment" label="鐩爣杈炬垚鐜�" width="120">
+ <template #default="scope">
+ <el-tag :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'">
+ {{ scope.row.attainment }}%
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-tab-pane>
+ </el-tabs>
+
+ <!-- 浠锋牸绛栫暐瀵硅瘽妗� -->
+ <el-dialog v-model="priceStrategyDialogVisible" :title="priceStrategyDialogTitle" width="900px" :close-on-click-modal="false">
+ <el-form :model="priceStrategyForm" :rules="priceStrategyRules" ref="priceStrategyFormRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绛栫暐绫诲瀷" prop="strategyType">
+ <el-select v-model="priceStrategyForm.strategyType" placeholder="璇烽�夋嫨绛栫暐绫诲瀷" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'">
+ <el-option label="涓撳睘浠锋牸" value="涓撳睘浠锋牸"></el-option>
+ <el-option label="闃舵鎶ヤ环" value="闃舵鎶ヤ环"></el-option>
+ <el-option label="淇冮攢鎶樻墸" value="淇冮攢鎶樻墸"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customerName">
+ <el-select v-model="priceStrategyForm.customerName" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'">
+ <el-option label="鍗庝笢寤烘潗闆嗗洟" value="鍗庝笢寤烘潗闆嗗洟"></el-option>
+ <el-option label="闀挎睙娣峰嚌鍦熷叕鍙�" value="闀挎睙娣峰嚌鍦熷叕鍙�"></el-option>
+ <el-option label="娴︽睙姘存偿鍒跺搧鍘�" value="娴︽睙姘存偿鍒跺搧鍘�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浜у搧鍚嶇О" prop="productName">
+ <el-select v-model="priceStrategyForm.productName" placeholder="璇烽�夋嫨浜у搧" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'">
+ <el-option label="P.O 42.5鏅�氱閰哥洂姘存偿" value="P.O 42.5鏅�氱閰哥洂姘存偿"></el-option>
+ <el-option label="P.S 32.5鐭挎福纭呴吀鐩愭按娉�" value="P.S 32.5鐭挎福纭呴吀鐩愭按娉�"></el-option>
+ <el-option label="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�" value="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿" prop="specification">
+ <el-input v-model="priceStrategyForm.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" :disabled="priceStrategyDialogMode === 'view'" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍩虹浠锋牸(鍏�/鍚�)" prop="basePrice">
+ <el-input-number v-model="priceStrategyForm.basePrice" :min="0" :precision="2" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绛栫暐浠锋牸" prop="strategyPrice">
+ <el-input v-model="priceStrategyForm.strategyPrice" placeholder="濡�: 楼350/鍚� 鎴� 9鎶�" :disabled="priceStrategyDialogMode === 'view'" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢熸晥鏃ユ湡" prop="startDate">
+ <el-date-picker
+ v-model="priceStrategyForm.startDate"
+ type="date"
+ placeholder="閫夋嫨鐢熸晥鏃ユ湡"
+ style="width: 100%"
+ value-format="YYYY-MM-DD"
+ :disabled="priceStrategyDialogMode === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="澶辨晥鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="priceStrategyForm.endDate"
+ type="date"
+ placeholder="閫夋嫨澶辨晥鏃ユ湡"
+ style="width: 100%"
+ value-format="YYYY-MM-DD"
+ :disabled="priceStrategyDialogMode === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="绛栫暐璇存槑" prop="description">
+ <el-input type="textarea" v-model="priceStrategyForm.description" :rows="3" placeholder="璇疯緭鍏ョ瓥鐣ヨ鏄�" :disabled="priceStrategyDialogMode === 'view'" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="priceStrategyDialogVisible = false">{{ priceStrategyDialogMode === 'view' ? '鍏抽棴' : '鍙栨秷' }}</el-button>
+ <el-button v-if="priceStrategyDialogMode !== 'view'" type="primary" @click="handleSavePriceStrategy">淇濆瓨</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Document, Van, Tickets, Wallet, Check, Clock, Search } from '@element-plus/icons-vue'
+import * as echarts from 'echarts'
+import Pagination from '@/components/PIMTable/Pagination.vue'
+
+// 娲诲姩鏍囩椤�
+const activeTab = ref('priceStrategy')
+
+// ========== 浠锋牸绛栫暐閰嶇疆 ==========
+const priceLoading = ref(false)
+const priceSearchForm = reactive({
+ customerName: '',
+ productType: '',
+ strategyType: ''
+})
+
+const priceStrategyList = ref([
+ {
+ id: 1,
+ strategyNo: 'PS202501001',
+ strategyType: '涓撳睘浠锋牸',
+ customerName: '鍗庝笢寤烘潗闆嗗洟',
+ productName: 'P.O 42.5鏅�氱閰哥洂姘存偿',
+ specification: '50kg/琚�',
+ basePrice: 380,
+ strategyPrice: '楼350/鍚�',
+ startDate: '2025-01-01',
+ endDate: '2025-12-31',
+ status: '鐢熸晥涓�',
+ description: '鎴樼暐鍚堜綔瀹㈡埛涓撳睘浼樻儬浠锋牸'
+ },
+ {
+ id: 2,
+ strategyNo: 'PS202501002',
+ strategyType: '闃舵鎶ヤ环',
+ customerName: '闀挎睙娣峰嚌鍦熷叕鍙�',
+ productName: 'P.S 32.5鐭挎福纭呴吀鐩愭按娉�',
+ specification: '50kg/琚�',
+ basePrice: 320,
+ strategyPrice: '500鍚ㄤ互涓�9鎶�',
+ startDate: '2025-01-01',
+ endDate: '2025-06-30',
+ status: '鐢熸晥涓�',
+ description: '澶ф壒閲忛噰璐樁姊紭鎯�'
+ },
+ {
+ id: 3,
+ strategyNo: 'PS202501003',
+ strategyType: '淇冮攢鎶樻墸',
+ customerName: '娴︽睙姘存偿鍒跺搧鍘�',
+ productName: 'P.C 32.5澶嶅悎纭呴吀鐩愭按娉�',
+ specification: '50kg/琚�',
+ basePrice: 300,
+ strategyPrice: '8.5鎶�',
+ startDate: '2025-01-15',
+ endDate: '2025-02-28',
+ status: '鐢熸晥涓�',
+ description: '鏄ヨ妭淇冮攢娲诲姩'
+ },
+ {
+ id: 4,
+ strategyNo: 'PS202412015',
+ strategyType: '涓撳睘浠锋牸',
+ customerName: '鍗庝笢寤烘潗闆嗗洟',
+ productName: 'P.C 32.5澶嶅悎纭呴吀鐩愭按娉�',
+ specification: '50kg/琚�',
+ basePrice: 300,
+ strategyPrice: '楼285/鍚�',
+ startDate: '2024-10-01',
+ endDate: '2024-12-31',
+ status: '宸茶繃鏈�',
+ description: '绗洓瀛e害涓撳睘浠锋牸'
+ }
+])
+
+const pricePagination = reactive({
+ total: 4,
+ currentPage: 1,
+ pageSize: 10
+})
+
+const priceStrategyDialogVisible = ref(false)
+const priceStrategyDialogTitle = ref('鏂板浠锋牸绛栫暐')
+const priceStrategyDialogMode = ref('add') // add | edit | view
+const priceStrategyForm = reactive({
+ strategyType: '',
+ customerName: '',
+ productName: '',
+ specification: '',
+ basePrice: 0,
+ strategyPrice: '',
+ startDate: '',
+ endDate: '',
+ description: ''
+})
+
+const priceStrategyRules = {
+ strategyType: [{ required: true, message: '璇烽�夋嫨绛栫暐绫诲瀷', trigger: 'change' }],
+ customerName: [{ required: true, message: '璇烽�夋嫨瀹㈡埛', trigger: 'change' }],
+ productName: [{ required: true, message: '璇烽�夋嫨浜у搧', trigger: 'change' }],
+ basePrice: [{ required: true, message: '璇疯緭鍏ュ熀纭�浠锋牸', trigger: 'blur' }],
+ strategyPrice: [{ required: true, message: '璇疯緭鍏ョ瓥鐣ヤ环鏍�', trigger: 'blur' }],
+ startDate: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃ユ湡', trigger: 'change' }],
+ endDate: [{ required: true, message: '璇烽�夋嫨澶辨晥鏃ユ湡', trigger: 'change' }]
+}
+
+const priceStrategyFormRef = ref()
+
+// ========== 鍚堝悓鎵ц鐩戞帶 ==========
+const contractLoading = ref(false)
+const contractStats = reactive({
+ totalContracts: 48,
+ deliveryRate: 87.5,
+ invoiceRate: 82.3,
+ paymentRate: 75.6
+})
+
+const contractSearchForm = reactive({
+ contractNo: '',
+ customerName: '',
+ executionStatus: ''
+})
+
+const contractList = ref([
+ {
+ id: 1,
+ contractNo: 'CT202501001',
+ customerName: '鍗庝笢寤烘潗闆嗗洟',
+ contractAmount: 2850000,
+ signDate: '2025-01-05',
+ executionProgress: 85,
+ deliveryProgress: 90,
+ invoiceProgress: 85,
+ paymentProgress: 75,
+ executionStatus: '鎵ц涓�',
+ orderStatus: '宸插畬鎴�',
+ orderDate: '2025-01-05'
+ },
+ {
+ id: 2,
+ contractNo: 'CT202501002',
+ customerName: '闀挎睙娣峰嚌鍦熷叕鍙�',
+ contractAmount: 1650000,
+ signDate: '2025-01-08',
+ executionProgress: 95,
+ deliveryProgress: 100,
+ invoiceProgress: 100,
+ paymentProgress: 85,
+ executionStatus: '鎵ц涓�',
+ orderStatus: '宸插畬鎴�',
+ orderDate: '2025-01-08'
+ },
+ {
+ id: 3,
+ contractNo: 'CT202501003',
+ customerName: '娴︽睙姘存偿鍒跺搧鍘�',
+ contractAmount: 980000,
+ signDate: '2025-01-12',
+ executionProgress: 60,
+ deliveryProgress: 65,
+ invoiceProgress: 60,
+ paymentProgress: 50,
+ executionStatus: '鎵ц涓�',
+ orderStatus: '宸插畬鎴�',
+ orderDate: '2025-01-12'
+ },
+ {
+ id: 4,
+ contractNo: 'CT202412028',
+ customerName: '鍗庝笢寤烘潗闆嗗洟',
+ contractAmount: 3200000,
+ signDate: '2024-12-15',
+ executionProgress: 100,
+ deliveryProgress: 100,
+ invoiceProgress: 100,
+ paymentProgress: 100,
+ executionStatus: '宸插畬鎴�',
+ orderStatus: '宸插畬鎴�',
+ orderDate: '2024-12-15'
+ },
+ {
+ id: 5,
+ contractNo: 'CT202501004',
+ customerName: '闀挎睙娣峰嚌鍦熷叕鍙�',
+ contractAmount: 750000,
+ signDate: '2025-01-20',
+ executionProgress: 25,
+ deliveryProgress: 30,
+ invoiceProgress: 20,
+ paymentProgress: 0,
+ executionStatus: '寮傚父',
+ orderStatus: '宸插畬鎴�',
+ orderDate: '2025-01-20'
+ }
+])
+
+const contractPagination = reactive({
+ total: 5,
+ currentPage: 1,
+ pageSize: 10
+})
+
+// ========== 鍘嗗彶姣斾环鍒嗘瀽 ==========
+const compareLoading = ref(false)
+const compareSearchForm = reactive({
+ productName: '',
+ dateRange: [],
+ region: ''
+})
+
+const priceComparisonList = ref([
+ { date: '2025-01-20', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', specification: '50kg/琚�', customerName: '鍗庝笢寤烘潗闆嗗洟', region: '鍗庝笢鍦板尯', quantity: 5000, price: 350, totalAmount: 1750000, priceChange: 0, remark: '闀挎湡鍚堜綔瀹㈡埛' },
+ { date: '2025-01-15', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', specification: '50kg/琚�', customerName: '娴︿笢鏂板尯寤虹瓚鍏徃', region: '鍗庝笢鍦板尯', quantity: 3000, price: 365, totalAmount: 1095000, priceChange: +15, remark: '鐜版鐜拌揣' },
+ { date: '2025-01-10', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', specification: '50kg/琚�', customerName: '闀挎睙娣峰嚌鍦熷叕鍙�', region: '鍗庝笢鍦板尯', quantity: 8000, price: 345, totalAmount: 2760000, priceChange: -5, remark: '澶ф壒閲忎紭鎯�' },
+ { date: '2025-01-05', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', specification: '50kg/琚�', customerName: '姹熻嫃宸ョ▼闆嗗洟', region: '鍗庝笢鍦板尯', quantity: 4500, price: 360, totalAmount: 1620000, priceChange: +10, remark: '宸ョ▼椤圭洰涓撶敤' },
+ { date: '2024-12-28', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', specification: '50kg/琚�', customerName: '鍗庝笢寤烘潗闆嗗洟', region: '鍗庝笢鍦板尯', quantity: 6000, price: 355, totalAmount: 2130000, priceChange: +5, remark: '骞村簳澶囪揣' },
+ { date: '2024-12-20', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', specification: '50kg/琚�', customerName: '涓婃捣甯傛斂宸ョ▼', region: '鍗庝笢鍦板尯', quantity: 10000, price: 340, totalAmount: 3400000, priceChange: -10, remark: '鏀垮簻椤圭洰' }
+])
+
+const priceChartRef = ref(null)
+let priceChart = null
+
+// ========== 鍒╂鼎鍒嗘瀽 ==========
+const profitLoading = ref(false)
+const profitStats = reactive({
+ totalSales: 15680000,
+ totalCost: 11256000,
+ grossProfit: 4424000,
+ grossProfitRate: 28.2,
+ salesGrowth: 12.5,
+ costRate: 71.8
+})
+
+const profitSearchForm = reactive({
+ productType: '',
+ customerName: '',
+ dateRange: []
+})
+
+const profitAnalysisList = ref([
+ { orderNo: 'SO202501015', customerName: '鍗庝笢寤烘潗闆嗗洟', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', quantity: 5000, salesPrice: 350, costPrice: 245, salesAmount: 1750000, costAmount: 1225000, grossProfit: 525000, grossProfitRate: 30.0, orderDate: '2025-01-20' },
+ { orderNo: 'SO202501012', customerName: '闀挎睙娣峰嚌鍦熷叕鍙�', productName: 'P.S 32.5鐭挎福纭呴吀鐩愭按娉�', quantity: 3500, salesPrice: 288, costPrice: 210, salesAmount: 1008000, costAmount: 735000, grossProfit: 273000, grossProfitRate: 27.1, orderDate: '2025-01-18' },
+ { orderNo: 'SO202501008', customerName: '娴︽睙姘存偿鍒跺搧鍘�', productName: 'P.C 32.5澶嶅悎纭呴吀鐩愭按娉�', quantity: 2800, salesPrice: 255, costPrice: 185, salesAmount: 714000, costAmount: 518000, grossProfit: 196000, grossProfitRate: 27.5, orderDate: '2025-01-15' },
+ { orderNo: 'SO202501005', customerName: '鍗庝笢寤烘潗闆嗗洟', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', quantity: 6000, salesPrice: 350, costPrice: 248, salesAmount: 2100000, costAmount: 1488000, grossProfit: 612000, grossProfitRate: 29.1, orderDate: '2025-01-10' },
+ { orderNo: 'SO202501003', customerName: '姹熻嫃宸ョ▼闆嗗洟', productName: 'P.O 42.5鏅�氱閰哥洂姘存偿', quantity: 4500, salesPrice: 360, costPrice: 250, salesAmount: 1620000, costAmount: 1125000, grossProfit: 495000, grossProfitRate: 30.6, orderDate: '2025-01-08' },
+ { orderNo: 'SO202412025', customerName: '闀挎睙娣峰嚌鍦熷叕鍙�', productName: 'P.S 32.5鐭挎福纭呴吀鐩愭按娉�', quantity: 8000, salesPrice: 290, costPrice: 215, salesAmount: 2320000, costAmount: 1720000, grossProfit: 600000, grossProfitRate: 25.9, orderDate: '2024-12-28' }
+])
+
+const profitPagination = reactive({
+ total: 6,
+ currentPage: 1,
+ pageSize: 10
+})
+
+const profitChartRef = ref(null)
+let profitChart = null
+
+// ========== 鏂规硶 ==========
+
+// 浠锋牸绛栫暐鐩稿叧鏂规硶
+const getStrategyTypeColor = (type) => {
+ const colorMap = {
+ '涓撳睘浠锋牸': 'success',
+ '闃舵鎶ヤ环': 'primary',
+ '淇冮攢鎶樻墸': 'warning'
+ }
+ return colorMap[type] || 'info'
+}
+
+const searchPriceStrategy = () => {
+ priceLoading.value = true
+ setTimeout(() => {
+ priceLoading.value = false
+ }, 500)
+}
+
+const resetPriceSearch = () => {
+ priceSearchForm.customerName = ''
+ priceSearchForm.productType = ''
+ priceSearchForm.strategyType = ''
+}
+
+const handleAddPriceStrategy = () => {
+ priceStrategyDialogTitle.value = '鏂板浠锋牸绛栫暐'
+ resetPriceStrategyForm()
+ priceStrategyDialogMode.value = 'add'
+ priceStrategyDialogVisible.value = true
+}
+
+const handleViewPriceStrategy = (row) => {
+ priceStrategyDialogTitle.value = '鏌ョ湅浠锋牸绛栫暐'
+ Object.assign(priceStrategyForm, row)
+ priceStrategyDialogMode.value = 'view'
+ priceStrategyDialogVisible.value = true
+}
+
+const handleEditPriceStrategy = (row) => {
+ priceStrategyDialogTitle.value = '缂栬緫浠锋牸绛栫暐'
+ Object.assign(priceStrategyForm, row)
+ priceStrategyDialogMode.value = 'edit'
+ priceStrategyDialogVisible.value = true
+}
+
+const handleDeletePriceStrategy = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ヤ环鏍肩瓥鐣ュ悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ // 鏈湴鍋囨暟鎹垹闄わ細浠庡垪琛ㄤ腑绉婚櫎骞舵洿鏂版�绘暟
+ const index = priceStrategyList.value.findIndex(item => item.id === row.id)
+ if (index > -1) {
+ priceStrategyList.value.splice(index, 1)
+ if (pricePagination.total > 0) pricePagination.total -= 1
+ }
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ })
+}
+
+const resetPriceStrategyForm = () => {
+ Object.keys(priceStrategyForm).forEach(key => {
+ if (key === 'basePrice') {
+ priceStrategyForm[key] = 0
+ } else {
+ priceStrategyForm[key] = ''
+ }
+ })
+}
+
+const handleSavePriceStrategy = () => {
+ priceStrategyFormRef.value.validate((valid) => {
+ if (valid) {
+ ElMessage.success('淇濆瓨鎴愬姛')
+ priceStrategyDialogVisible.value = false
+ }
+ })
+}
+
+const handlePricePageChange = (val) => {
+ pricePagination.currentPage = val.page
+ pricePagination.pageSize = val.limit
+}
+
+// 鍚堝悓鎵ц鐩戞帶鐩稿叧鏂规硶
+const getExecutionStatusType = (status) => {
+ const statusMap = {
+ '寰呮墽琛�': 'info',
+ '鎵ц涓�': 'primary',
+ '宸插畬鎴�': 'success',
+ '寮傚父': 'danger'
+ }
+ return statusMap[status] || 'info'
+}
+
+const getProgressColor = (percentage) => {
+ if (percentage < 30) return '#f56c6c'
+ if (percentage < 70) return '#e6a23c'
+ return '#67c23a'
+}
+
+const getContractStep = (row) => {
+ if (row.paymentProgress === 100) return 4
+ if (row.invoiceProgress === 100) return 3
+ if (row.deliveryProgress === 100) return 2
+ if (row.orderStatus === '宸插畬鎴�') return 1
+ return 0
+}
+
+const searchContract = () => {
+ contractLoading.value = true
+ setTimeout(() => {
+ contractLoading.value = false
+ }, 500)
+}
+
+const resetContractSearch = () => {
+ contractSearchForm.contractNo = ''
+ contractSearchForm.customerName = ''
+ contractSearchForm.executionStatus = ''
+}
+
+const handleViewContract = (row) => {
+ ElMessage.info('鏌ョ湅鍚堝悓璇︽儏: ' + row.contractNo)
+}
+
+const handleContractPageChange = (val) => {
+ contractPagination.currentPage = val.page
+ contractPagination.pageSize = val.limit
+}
+
+// 鍘嗗彶姣斾环鍒嗘瀽鐩稿叧鏂规硶
+const searchPriceComparison = () => {
+ compareLoading.value = true
+ setTimeout(() => {
+ compareLoading.value = false
+ initPriceChart()
+ }, 500)
+}
+
+const resetCompareSearch = () => {
+ compareSearchForm.productName = ''
+ compareSearchForm.dateRange = []
+ compareSearchForm.region = ''
+}
+
+const initPriceChart = () => {
+ if (!priceChartRef.value) return
+
+ if (priceChart) {
+ priceChart.dispose()
+ }
+
+ priceChart = echarts.init(priceChartRef.value)
+
+ const option = {
+ title: {
+ text: '姘存偿浠锋牸瓒嬪娍鍒嗘瀽',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'axis',
+ formatter: '{b}<br/>{a}: 楼{c}/鍚�'
+ },
+ legend: {
+ data: ['P.O 42.5鏅�氱閰哥洂姘存偿'],
+ top: 30
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: ['2024-12-20', '2024-12-28', '2025-01-05', '2025-01-10', '2025-01-15', '2025-01-20']
+ },
+ yAxis: {
+ type: 'value',
+ name: '浠锋牸(鍏�/鍚�)',
+ min: 330,
+ max: 370
+ },
+ series: [
+ {
+ name: 'P.O 42.5鏅�氱閰哥洂姘存偿',
+ type: 'line',
+ data: [340, 355, 360, 345, 365, 350],
+ smooth: true,
+ itemStyle: {
+ color: '#409eff'
+ },
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
+ { offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
+ ])
+ }
+ }
+ ]
+ }
+
+ priceChart.setOption(option)
+}
+
+// 鍒╂鼎鍒嗘瀽鐩稿叧鏂规硶
+const searchProfit = () => {
+ profitLoading.value = true
+ setTimeout(() => {
+ profitLoading.value = false
+ initProfitChart()
+ }, 500)
+}
+
+const resetProfitSearch = () => {
+ profitSearchForm.productType = ''
+ profitSearchForm.customerName = ''
+ profitSearchForm.dateRange = []
+}
+
+const getProfitRateType = (rate) => {
+ if (rate >= 30) return 'success'
+ if (rate >= 25) return 'warning'
+ return 'danger'
+}
+
+const getProfitSummary = (param) => {
+ const { columns, data } = param
+ const sums = []
+ columns.forEach((column, index) => {
+ if (index === 0) {
+ sums[index] = '鍚堣'
+ return
+ }
+ if (['quantity', 'salesAmount', 'costAmount', 'grossProfit'].includes(column.property)) {
+ const values = data.map(item => Number(item[column.property]))
+ if (!values.every(value => isNaN(value))) {
+ const total = values.reduce((prev, curr) => {
+ const value = Number(curr)
+ if (!isNaN(value)) {
+ return prev + curr
+ } else {
+ return prev
+ }
+ }, 0)
+ sums[index] = column.property === 'quantity' ? total.toLocaleString() : '楼' + total.toLocaleString()
+ }
+ } else if (column.property === 'grossProfitRate') {
+ // 璁$畻骞冲潎姣涘埄鐜�
+ const totalSales = data.reduce((sum, item) => sum + item.salesAmount, 0)
+ const totalProfit = data.reduce((sum, item) => sum + item.grossProfit, 0)
+ sums[index] = ((totalProfit / totalSales) * 100).toFixed(1) + '%'
+ }
+ })
+ return sums
+}
+
+const initProfitChart = () => {
+ if (!profitChartRef.value) return
+
+ if (profitChart) {
+ profitChart.dispose()
+ }
+
+ profitChart = echarts.init(profitChartRef.value)
+
+ const option = {
+ title: {
+ text: '閿�鍞笌鍒╂鼎瓒嬪娍鍒嗘瀽',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross',
+ crossStyle: {
+ color: '#999'
+ }
+ }
+ },
+ legend: {
+ data: ['閿�鍞噾棰�', '鎴愭湰閲戦', '姣涘埄娑�', '姣涘埄鐜�'],
+ top: 30
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+ },
+ xAxis: [
+ {
+ type: 'category',
+ data: ['2024-12', '2025-01'],
+ axisPointer: {
+ type: 'shadow'
+ }
+ }
+ ],
+ yAxis: [
+ {
+ type: 'value',
+ name: '閲戦(涓囧厓)',
+ axisLabel: {
+ formatter: '{value}'
+ }
+ },
+ {
+ type: 'value',
+ name: '姣涘埄鐜�(%)',
+ min: 0,
+ max: 40,
+ axisLabel: {
+ formatter: '{value}%'
+ }
+ }
+ ],
+ series: [
+ {
+ name: '閿�鍞噾棰�',
+ type: 'bar',
+ data: [820, 950],
+ itemStyle: {
+ color: '#409eff'
+ }
+ },
+ {
+ name: '鎴愭湰閲戦',
+ type: 'bar',
+ data: [605, 670],
+ itemStyle: {
+ color: '#e6a23c'
+ }
+ },
+ {
+ name: '姣涘埄娑�',
+ type: 'bar',
+ data: [215, 280],
+ itemStyle: {
+ color: '#67c23a'
+ }
+ },
+ {
+ name: '姣涘埄鐜�',
+ type: 'line',
+ yAxisIndex: 1,
+ data: [26.2, 29.5],
+ itemStyle: {
+ color: '#f56c6c'
+ }
+ }
+ ]
+ }
+
+ profitChart.setOption(option)
+}
+
+const handleProfitPageChange = (val) => {
+ profitPagination.currentPage = val.page
+ profitPagination.pageSize = val.limit
+}
+
+// ========== 鎸囨爣缁熻锛堝缁村害鍒嗘瀽锛� ==========
+const indicatorKpis = reactive({
+ orderCount: 1280,
+ salesAmount: 9650000,
+ shipmentRate: 89.2,
+ collectionRate: 76.4
+})
+
+const indicatorFilter = reactive({
+ product: '',
+ customer: '',
+ region: '',
+ dateRange: []
+})
+
+const indicatorChartRef = ref(null)
+let indicatorChart = null
+
+const teamPerformanceList = ref([
+ { team: '鍗庝笢鍥㈤槦A', orderCount: 320, salesAmount: 2850000, shipmentRate: 90, collectionRate: 80, attainment: 105 },
+ { team: '鍗庡寳鍥㈤槦B', orderCount: 280, salesAmount: 2150000, shipmentRate: 86, collectionRate: 73, attainment: 92 },
+ { team: '鍗庡崡鍥㈤槦C', orderCount: 210, salesAmount: 1850000, shipmentRate: 88, collectionRate: 70, attainment: 78 },
+ { team: '瑗垮崡鍥㈤槦D', orderCount: 180, salesAmount: 1500000, shipmentRate: 83, collectionRate: 68, attainment: 74 }
+])
+
+const initIndicatorChart = () => {
+ if (!indicatorChartRef.value) return
+ if (indicatorChart) indicatorChart.dispose()
+ indicatorChart = echarts.init(indicatorChartRef.value)
+ const option = {
+ title: { text: '澶氱淮搴﹂攢鍞寚鏍囪秼鍔�', left: 'center' },
+ tooltip: { trigger: 'axis' },
+ legend: { data: ['璁㈠崟鏁�', '閿�鍞', '鍙戣揣鐜�', '鍥炴鐜�'], top: 30 },
+ grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
+ xAxis: { type: 'category', data: ['2024-12', '2025-01', '2025-02', '2025-03', '2025-04', '2025-05'] },
+ yAxis: [
+ { type: 'value', name: '鏁伴噺/閲戦', axisLabel: { formatter: '{value}' } },
+ { type: 'value', name: '姣斾緥(%)', min: 0, max: 100, axisLabel: { formatter: '{value}%' } }
+ ],
+ series: [
+ { name: '璁㈠崟鏁�', type: 'bar', data: [180, 220, 210, 260, 205, 225], itemStyle: { color: '#409eff' } },
+ { name: '閿�鍞', type: 'bar', data: [820, 950, 910, 1080, 980, 1020], itemStyle: { color: '#67c23a' } },
+ { name: '鍙戣揣鐜�', type: 'line', yAxisIndex: 1, data: [86, 89, 88, 91, 87, 90], itemStyle: { color: '#e6a23c' } },
+ { name: '鍥炴鐜�', type: 'line', yAxisIndex: 1, data: [72, 76, 74, 79, 75, 78], itemStyle: { color: '#f56c6c' } }
+ ]
+ }
+ indicatorChart.setOption(option)
+}
+
+const applyIndicatorFilter = () => {
+ // 浣跨敤鍋囨暟鎹ā鎷熸煡璇紝鍒锋柊KPI鍜屽浘琛�
+ // 浠呮紨绀猴細闅忔満寰皟浠ヤ綋鐜扮瓫閫夋晥鏋�
+ const random = (base, delta) => {
+ const v = base + Math.round((Math.random() - 0.5) * delta)
+ return v < 0 ? 0 : v
+ }
+ indicatorKpis.orderCount = random(1280, 120)
+ indicatorKpis.salesAmount = random(9650000, 350000)
+ indicatorKpis.shipmentRate = (85 + Math.random() * 10).toFixed(1) * 1
+ indicatorKpis.collectionRate = (70 + Math.random() * 12).toFixed(1) * 1
+
+ setTimeout(() => initIndicatorChart(), 200)
+}
+
+const resetIndicatorFilter = () => {
+ indicatorFilter.product = ''
+ indicatorFilter.customer = ''
+ indicatorFilter.region = ''
+ indicatorFilter.dateRange = []
+ applyIndicatorFilter()
+}
+
+const exportIndicatorTable = () => {
+ // 瀵煎嚭鍥㈤槦涓氱哗涓篊SV锛堝亣瀵煎嚭锛�
+ const header = ['閿�鍞洟闃�', '璁㈠崟鏁�', '閿�鍞', '鍙戣揣鐜�(%)', '鍥炴鐜�(%)', '鐩爣杈炬垚鐜�(%)']
+ const rows = teamPerformanceList.value.map(r => [
+ r.team,
+ r.orderCount,
+ r.salesAmount,
+ r.shipmentRate,
+ r.collectionRate,
+ r.attainment
+ ])
+ const csv = [header, ...rows].map(r => r.join(',')).join('\n')
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = '鎸囨爣缁熻-鍥㈤槦涓氱哗.csv'
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+}
+
+const exportIndicatorChart = () => {
+ if (!indicatorChart) return
+ const url = indicatorChart.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' })
+ const link = document.createElement('a')
+ link.href = url
+ link.download = '鎸囨爣缁熻-鍥捐〃.png'
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ // 缁勪欢鎸傝浇鍚庝笉绔嬪嵆鍒濆鍖栧浘琛紝绛夊緟鐢ㄦ埛鍒囨崲鍒板搴旀爣绛鹃〉
+})
+
+// 鐩戝惉鏍囩椤靛垏鎹�
+watch(activeTab, (newVal) => {
+ nextTick(() => {
+ if (newVal === 'priceComparison') {
+ initPriceChart()
+ } else if (newVal === 'profitAnalysis') {
+ initProfitChart()
+ } else if (newVal === 'indicatorStats') {
+ initIndicatorChart()
+ }
+ })
+})
+
+const handleTabChange = () => {
+ // 鏍囩椤靛垏鎹㈠鐞�
+}
+</script>
+
+<style scoped>
+.strategy-control {
+ padding: 0;
+}
+
+.main-tabs {
+ border: none;
+ box-shadow: none;
+}
+
+.main-tabs :deep(.el-tabs__content) {
+ padding: 0;
+}
+
+.box-card {
+ border: none;
+ box-shadow: none;
+}
+
+.search-row {
+ margin-bottom: 20px;
+}
+
+/* 缁熻鍗$墖鏍峰紡 */
+.stats-row {
+ margin-bottom: 24px;
+}
+
+.stat-card {
+ display: flex;
+ align-items: center;
+ padding: 20px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.stat-icon {
+ width: 60px;
+ height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ margin-right: 16px;
+}
+
+.stat-content {
+ flex: 1;
+}
+
+.stat-value {
+ font-size: 28px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 4px;
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #909399;
+}
+
+/* 鍚堝悓璇︽儏灞曞紑鏍峰紡 */
+.contract-detail-expand {
+ padding: 30px 60px;
+ background: #f5f7fa;
+}
+
+.contract-detail-expand :deep(.el-step__title) {
+ font-size: 14px;
+}
+
+.contract-detail-expand :deep(.el-step__description) {
+ font-size: 12px;
+ margin-top: 4px;
+}
+
+/* 鍒╂鼎缁熻鍗$墖 */
+.profit-stats-row {
+ margin-bottom: 24px;
+}
+
+.profit-card {
+ padding: 24px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 12px;
+ color: #fff;
+ box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
+}
+
+.profit-card:nth-child(2) .profit-card {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+}
+
+.profit-card:nth-child(3) .profit-card {
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.profit-header {
+ font-size: 14px;
+ opacity: 0.9;
+ margin-bottom: 12px;
+}
+
+.profit-value {
+ font-size: 32px;
+ font-weight: bold;
+ margin-bottom: 12px;
+}
+
+.profit-highlight {
+ color: #fff;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.profit-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 13px;
+ opacity: 0.9;
+}
+
+.growth-up {
+ color: #fff;
+ font-weight: bold;
+}
+
+.growth-down {
+ color: #ffd04b;
+ font-weight: bold;
+}
+
+.cost-rate, .gross-profit-rate {
+ font-weight: bold;
+}
+
+/* 鍥捐〃瀹瑰櫒 */
+.chart-container {
+ margin: 20px 0;
+ padding: 20px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+</style>
+
diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue
index 8d7a26b..a177314 100644
--- a/src/views/system/dept/index.vue
+++ b/src/views/system/dept/index.vue
@@ -1,149 +1,147 @@
<template>
- <div class="app-container">
- <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
- <el-form-item label="鍏徃鍚嶇О" prop="deptName">
- <el-input
- v-model="queryParams.deptName"
- placeholder="璇疯緭鍏ュ叕鍙稿悕绉�"
- clearable
- style="width: 200px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="鐘舵��" prop="status">
- <el-select v-model="queryParams.status" placeholder="鍏徃鐘舵��" clearable style="width: 200px">
- <el-option
- v-for="dict in sys_normal_disable"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
- <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
- </el-form-item>
- </el-form>
-
- <el-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button
- type="primary"
- plain
- icon="Plus"
- @click="handleAdd"
- v-hasPermi="['system:dept:add']"
- >鏂板</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button
- type="info"
- plain
- icon="Sort"
- @click="toggleExpandAll"
- >灞曞紑/鎶樺彔</el-button>
- </el-col>
- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
- </el-row>
-
- <el-table
- v-if="refreshTable"
- v-loading="loading"
- :data="deptList"
- row-key="deptId"
- :default-expand-all="isExpandAll"
- :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
- stripe
- >
- <el-table-column prop="deptName" label="鍏徃鍚嶇О" width="260"></el-table-column>
- <el-table-column prop="orderNum" label="鎺掑簭" width="200"></el-table-column>
- <el-table-column prop="status" label="鐘舵��" width="100">
- <template #default="scope">
- <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
- </template>
- </el-table-column>
- <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="200">
- <template #default="scope">
- <span>{{ parseTime(scope.row.createTime) }}</span>
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
- <template #default="scope">
- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']">淇敼</el-button>
- <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']">鏂板</el-button>
- <el-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">鍒犻櫎</el-button>
- </template>
- </el-table-column>
- </el-table>
-
- <!-- 娣诲姞鎴栦慨鏀瑰叕鍙稿璇濇 -->
- <el-dialog :title="title" v-model="open" width="600px" append-to-body>
- <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
- <el-row>
- <el-col :span="24" v-if="form.parentId !== 0">
- <el-form-item label="涓婄骇鍏徃" prop="parentId">
- <el-tree-select
- v-model="form.parentId"
- :data="deptOptions"
- :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
- value-key="deptId"
- placeholder="閫夋嫨涓婄骇鍏徃"
- check-strictly
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍏徃鍚嶇О" prop="deptName">
- <el-input v-model="form.deptName" placeholder="璇疯緭鍏ュ叕鍙稿悕绉�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏄剧ず鎺掑簭" prop="orderNum">
- <el-input-number v-model="form.orderNum" controls-position="right" :min="0"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="璐熻矗浜�" prop="leader">
- <el-input v-model="form.leader" placeholder="璇疯緭鍏ヨ礋璐d汉" maxlength="20" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鑱旂郴鐢佃瘽" prop="phone">
- <el-input v-model="form.phone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�" maxlength="11" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="閭" prop="email">
- <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�" maxlength="50" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍏徃鐘舵��">
- <el-radio-group v-model="form.status">
- <el-radio
- v-for="dict in sys_normal_disable"
- :key="dict.value"
- :value="dict.value"
- >{{ dict.label }}</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍏徃缂栧彿" prop="deptNick">
- <el-input v-model="form.deptNick" placeholder="璇疯緭鍏ュ叕鍙哥紪鍙�" maxlength="50" />
- </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="cancel">鍙� 娑�</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
+ <div class="app-container">
+ <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+ <el-form-item label="閮ㄩ棬鍚嶇О" prop="deptName">
+ <el-input
+ v-model="queryParams.deptName"
+ placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�"
+ clearable
+ style="width: 200px"
+ @keyup.enter="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="queryParams.status" placeholder="閮ㄩ棬鐘舵��" clearable style="width: 200px">
+ <el-option
+ v-for="dict in sys_normal_disable"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button
+ type="primary"
+ plain
+ icon="Plus"
+ @click="handleAdd"
+ v-hasPermi="['system:dept:add']"
+ >鏂板</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="info"
+ plain
+ icon="Sort"
+ @click="toggleExpandAll"
+ >灞曞紑/鎶樺彔</el-button>
+ </el-col>
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+ </el-row>
+ <el-table
+ v-if="refreshTable"
+ v-loading="loading"
+ :data="deptList"
+ row-key="deptId"
+ :default-expand-all="isExpandAll"
+ :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+ >
+ <el-table-column prop="deptName" label="閮ㄩ棬鍚嶇О" width="260"></el-table-column>
+ <el-table-column prop="orderNum" label="鎺掑簭" width="200"></el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="scope">
+ <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="200">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.createTime) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+ <template #default="scope">
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']">淇敼</el-button>
+ <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']">鏂板</el-button>
+ <el-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 娣诲姞鎴栦慨鏀归儴闂ㄥ璇濇 -->
+ <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+ <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
+ <el-row>
+ <el-col :span="24" v-if="form.parentId !== 0">
+ <el-form-item label="涓婄骇閮ㄩ棬" prop="parentId">
+ <el-tree-select
+ v-model="form.parentId"
+ :data="deptOptions"
+ :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
+ value-key="deptId"
+ placeholder="閫夋嫨涓婄骇閮ㄩ棬"
+ check-strictly
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閮ㄩ棬鍚嶇О" prop="deptName">
+ <el-input v-model="form.deptName" placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏄剧ず鎺掑簭" prop="orderNum">
+ <el-input-number v-model="form.orderNum" controls-position="right" :min="0"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璐熻矗浜�" prop="leader">
+ <el-input v-model="form.leader" placeholder="璇疯緭鍏ヨ礋璐d汉" maxlength="20" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴鐢佃瘽" prop="phone">
+ <el-input v-model="form.phone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�" maxlength="11" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閭" prop="email">
+ <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�" maxlength="50" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閮ㄩ棬鐘舵��">
+ <el-radio-group v-model="form.status">
+ <el-radio
+ v-for="dict in sys_normal_disable"
+ :key="dict.value"
+ :value="dict.value"
+ >{{ dict.label }}</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閮ㄩ棬缂栧彿" prop="deptNick">
+ <el-input v-model="form.deptNick" placeholder="璇疯緭鍏ラ儴闂ㄧ紪鍙�" maxlength="50" />
+ </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="cancel">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
</template>
<script setup name="Dept">
@@ -162,129 +160,129 @@
const refreshTable = ref(true)
const data = reactive({
- form: {},
- queryParams: {
- deptName: undefined,
- status: undefined
- },
- rules: {
- parentId: [{ required: true, message: "涓婄骇鍏徃涓嶈兘涓虹┖", trigger: "blur" }],
- deptName: [{ required: true, message: "鍏徃鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
- orderNum: [{ required: true, message: "鏄剧ず鎺掑簭涓嶈兘涓虹┖", trigger: "blur" }],
- email: [{ type: "email", message: "璇疯緭鍏ユ纭殑閭鍦板潃", trigger: ["blur", "change"] }],
- phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur" }],
- deptNick: [{ required: true, message: "鍏徃缂栧彿涓嶈兘涓虹┖", trigger: "blur" }],
- },
+ form: {},
+ queryParams: {
+ deptName: undefined,
+ status: undefined
+ },
+ rules: {
+ parentId: [{ required: true, message: "涓婄骇閮ㄩ棬涓嶈兘涓虹┖", trigger: "blur" }],
+ deptName: [{ required: true, message: "閮ㄩ棬鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+ orderNum: [{ required: true, message: "鏄剧ず鎺掑簭涓嶈兘涓虹┖", trigger: "blur" }],
+ email: [{ type: "email", message: "璇疯緭鍏ユ纭殑閭鍦板潃", trigger: ["blur", "change"] }],
+ phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur" }],
+ deptNick: [{ required: true, message: "閮ㄩ棬缂栧彿涓嶈兘涓虹┖", trigger: "blur" }],
+ },
})
const { queryParams, form, rules } = toRefs(data)
-/** 鏌ヨ鍏徃鍒楄〃 */
+/** 鏌ヨ閮ㄩ棬鍒楄〃 */
function getList() {
- loading.value = true
- listDept(queryParams.value).then(response => {
- deptList.value = proxy.handleTree(response.data, "deptId")
- loading.value = false
- })
+ loading.value = true
+ listDept(queryParams.value).then(response => {
+ deptList.value = proxy.handleTree(response.data, "deptId")
+ loading.value = false
+ })
}
/** 鍙栨秷鎸夐挳 */
function cancel() {
- open.value = false
- reset()
+ open.value = false
+ reset()
}
/** 琛ㄥ崟閲嶇疆 */
function reset() {
- form.value = {
- deptId: undefined,
- parentId: undefined,
- deptName: undefined,
- orderNum: 0,
- leader: undefined,
- phone: undefined,
- email: undefined,
- status: "0",
- deptNick: undefined,
- }
- proxy.resetForm("deptRef")
+ form.value = {
+ deptId: undefined,
+ parentId: undefined,
+ deptName: undefined,
+ orderNum: 0,
+ leader: undefined,
+ phone: undefined,
+ email: undefined,
+ status: "0",
+ deptNick: undefined,
+ }
+ proxy.resetForm("deptRef")
}
/** 鎼滅储鎸夐挳鎿嶄綔 */
function handleQuery() {
- getList()
+ getList()
}
/** 閲嶇疆鎸夐挳鎿嶄綔 */
function resetQuery() {
- proxy.resetForm("queryRef")
- handleQuery()
+ proxy.resetForm("queryRef")
+ handleQuery()
}
/** 鏂板鎸夐挳鎿嶄綔 */
function handleAdd(row) {
- reset()
- listDept().then(response => {
- deptOptions.value = proxy.handleTree(response.data, "deptId")
- })
- if (row != undefined) {
- form.value.parentId = row.deptId
- }
- open.value = true
- title.value = "娣诲姞鍏徃"
+ reset()
+ listDept().then(response => {
+ deptOptions.value = proxy.handleTree(response.data, "deptId")
+ })
+ if (row != undefined) {
+ form.value.parentId = row.deptId
+ }
+ open.value = true
+ title.value = "娣诲姞閮ㄩ棬"
}
/** 灞曞紑/鎶樺彔鎿嶄綔 */
function toggleExpandAll() {
- refreshTable.value = false
- isExpandAll.value = !isExpandAll.value
- nextTick(() => {
- refreshTable.value = true
- })
+ refreshTable.value = false
+ isExpandAll.value = !isExpandAll.value
+ nextTick(() => {
+ refreshTable.value = true
+ })
}
/** 淇敼鎸夐挳鎿嶄綔 */
function handleUpdate(row) {
- reset()
- listDeptExcludeChild(row.deptId).then(response => {
- deptOptions.value = proxy.handleTree(response.data, "deptId")
- })
- getDept(row.deptId).then(response => {
- form.value = response.data
- open.value = true
- title.value = "淇敼鍏徃"
- })
+ reset()
+ listDeptExcludeChild(row.deptId).then(response => {
+ deptOptions.value = proxy.handleTree(response.data, "deptId")
+ })
+ getDept(row.deptId).then(response => {
+ form.value = response.data
+ open.value = true
+ title.value = "淇敼閮ㄩ棬"
+ })
}
/** 鎻愪氦鎸夐挳 */
function submitForm() {
- proxy.$refs["deptRef"].validate(valid => {
- if (valid) {
- if (form.value.deptId != undefined) {
- updateDept(form.value).then(response => {
- proxy.$modal.msgSuccess("淇敼鎴愬姛")
- open.value = false
- getList()
- })
- } else {
- addDept(form.value).then(response => {
- proxy.$modal.msgSuccess("鏂板鎴愬姛")
- open.value = false
- getList()
- })
- }
- }
- })
+ proxy.$refs["deptRef"].validate(valid => {
+ if (valid) {
+ if (form.value.deptId != undefined) {
+ updateDept(form.value).then(response => {
+ proxy.$modal.msgSuccess("淇敼鎴愬姛")
+ open.value = false
+ getList()
+ })
+ } else {
+ addDept(form.value).then(response => {
+ proxy.$modal.msgSuccess("鏂板鎴愬姛")
+ open.value = false
+ getList()
+ })
+ }
+ }
+ })
}
/** 鍒犻櫎鎸夐挳鎿嶄綔 */
function handleDelete(row) {
- proxy.$modal.confirm('鏄惁纭鍒犻櫎鍚嶇О涓�"' + row.deptName + '"鐨勬暟鎹」?').then(function() {
- return delDept(row.deptId)
- }).then(() => {
- getList()
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
- }).catch(() => {})
+ proxy.$modal.confirm('鏄惁纭鍒犻櫎鍚嶇О涓�"' + row.deptName + '"鐨勬暟鎹」?').then(function() {
+ return delDept(row.deptId)
+ }).then(() => {
+ getList()
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
+ }).catch(() => {})
}
getList()
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
index 04adf59..f28463b 100644
--- a/src/views/system/user/index.vue
+++ b/src/views/system/user/index.vue
@@ -1,903 +1,546 @@
<template>
- <div class="app-container">
- <el-row :gutter="20" style="height: calc(100vh - 8em)">
- <splitpanes
- :horizontal="appStore.device === 'mobile'"
- class="default-theme"
- >
- <!--閮ㄩ棬鏁版嵁-->
- <pane size="16">
- <el-col style="padding: 10px">
- <div class="head-container">
- <el-input
- v-model="deptName"
- placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�"
- clearable
- prefix-icon="Search"
- style="margin-bottom: 20px"
- />
- </div>
- <div class="head-container">
- <el-tree
- :data="deptOptions"
- :props="{ label: 'label', children: 'children' }"
- :expand-on-click-node="false"
- :filter-node-method="filterNode"
- ref="deptTreeRef"
- node-key="id"
- highlight-current
- default-expand-all
- @node-click="handleNodeClick"
- />
- </div>
- </el-col>
- </pane>
- <!--鐢ㄦ埛鏁版嵁-->
- <pane size="84">
- <el-col style="padding: 10px">
- <el-form
- :model="queryParams"
- ref="queryRef"
- :inline="true"
- v-show="showSearch"
- label-width="68px"
- >
- <el-form-item label="鐧诲綍璐﹀彿" prop="userName">
- <el-input
- v-model="queryParams.userName"
- placeholder="璇疯緭鍏ョ櫥褰曡处鍙�"
- clearable
- style="width: 240px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber">
- <el-input
- v-model="queryParams.phonenumber"
- placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�"
- clearable
- style="width: 240px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="鐘舵��" prop="status">
- <el-select
- v-model="queryParams.status"
- placeholder="鐢ㄦ埛鐘舵��"
- clearable
- style="width: 240px"
- >
- <el-option
- v-for="dict in sys_normal_disable"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="鍒涘缓鏃堕棿" style="width: 308px">
- <el-date-picker
- v-model="dateRange"
- value-format="YYYY-MM-DD"
- type="daterange"
- range-separator="-"
- start-placeholder="寮�濮嬫棩鏈�"
- end-placeholder="缁撴潫鏃ユ湡"
- ></el-date-picker>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery"
- >鎼滅储</el-button
- >
- <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
- </el-form-item>
- </el-form>
-
- <el-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button
- type="primary"
- plain
- icon="Plus"
- @click="handleAdd"
- v-hasPermi="['system:user:add']"
- >鏂板</el-button
- >
- </el-col>
- <el-col :span="1.5">
- <el-button
- type="success"
- plain
- icon="Edit"
- :disabled="single"
- @click="handleUpdate"
- v-hasPermi="['system:user:edit']"
- >淇敼</el-button
- >
- </el-col>
- <el-col :span="1.5">
- <el-button
- type="danger"
- plain
- icon="Delete"
- :disabled="multiple"
- @click="handleDelete"
- v-hasPermi="['system:user:remove']"
- >鍒犻櫎</el-button
- >
- </el-col>
- <el-col :span="1.5">
- <el-button
- type="info"
- plain
- icon="Upload"
- @click="handleImport"
- v-hasPermi="['system:user:import']"
- >瀵煎叆</el-button
- >
- </el-col>
- <el-col :span="1.5">
- <el-button
- type="warning"
- plain
- icon="Download"
- @click="handleExport"
- v-hasPermi="['system:user:export']"
- >瀵煎嚭</el-button
- >
- </el-col>
- <right-toolbar
- v-model:showSearch="showSearch"
- @queryTable="getList"
- :columns="columns"
- ></right-toolbar>
- </el-row>
-
- <el-table
- v-loading="loading"
- :data="userList"
- @selection-change="handleSelectionChange"
- stripe
- >
- <el-table-column type="selection" width="50" align="center" />
- <el-table-column
- label="鐢ㄦ埛缂栧彿"
- align="center"
- key="userId"
- prop="userId"
- v-if="columns[0].visible"
- />
- <el-table-column
- label="鐧诲綍璐﹀彿"
- align="center"
- key="userName"
- prop="userName"
- v-if="columns[1].visible"
- :show-overflow-tooltip="true"
- />
- <el-table-column
- label="鐢ㄦ埛鏄电О"
- align="center"
- key="nickName"
- prop="nickName"
- v-if="columns[2].visible"
- :show-overflow-tooltip="true"
- />
- <el-table-column
- label="閮ㄩ棬"
- align="center"
- key="deptNames"
- prop="deptNames"
- v-if="columns[3].visible"
- :show-overflow-tooltip="true"
- />
- <el-table-column
- label="鎵嬫満鍙风爜"
- align="center"
- key="phonenumber"
- prop="phonenumber"
- v-if="columns[4].visible"
- width="120"
- />
- <el-table-column
- label="鐘舵��"
- align="center"
- key="status"
- v-if="columns[5].visible"
- >
- <template #default="scope">
- <el-switch
- v-model="scope.row.status"
- active-value="0"
- inactive-value="1"
- @change="handleStatusChange(scope.row)"
- ></el-switch>
- </template>
- </el-table-column>
- <el-table-column
- label="鍒涘缓鏃堕棿"
- align="center"
- prop="createTime"
- v-if="columns[6].visible"
- width="160"
- >
- <template #default="scope">
- <span>{{ parseTime(scope.row.createTime) }}</span>
- </template>
- </el-table-column>
- <el-table-column
- label="鎿嶄綔"
- align="center"
- width="150"
- class-name="small-padding fixed-width"
- >
- <template #default="scope">
- <el-tooltip
- content="淇敼"
- placement="top"
- v-if="scope.row.userId !== 1"
- >
- <el-button
- link
- type="primary"
- icon="Edit"
- @click="handleUpdate(scope.row)"
- v-hasPermi="['system:user:edit']"
- ></el-button>
- </el-tooltip>
- <el-tooltip
- content="鍒犻櫎"
- placement="top"
- v-if="scope.row.userId !== 1"
- >
- <el-button
- link
- type="primary"
- icon="Delete"
- @click="handleDelete(scope.row)"
- v-hasPermi="['system:user:remove']"
- ></el-button>
- </el-tooltip>
- <el-tooltip
- content="閲嶇疆瀵嗙爜"
- placement="top"
- v-if="scope.row.userId !== 1"
- >
- <el-button
- link
- type="primary"
- icon="Key"
- @click="handleResetPwd(scope.row)"
- v-hasPermi="['system:user:resetPwd']"
- ></el-button>
- </el-tooltip>
- <el-tooltip
- content="鍒嗛厤瑙掕壊"
- placement="top"
- v-if="scope.row.userId !== 1"
- >
- <el-button
- link
- type="primary"
- icon="CircleCheck"
- @click="handleAuthRole(scope.row)"
- v-hasPermi="['system:user:edit']"
- ></el-button>
- </el-tooltip>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="total > 0"
- :total="total"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- @pagination="getList"
- />
- </el-col>
- </pane>
- </splitpanes>
- </el-row>
-
- <!-- 娣诲姞鎴栦慨鏀圭敤鎴烽厤缃璇濇 -->
- <el-dialog :title="title" v-model="open" width="600px" append-to-body>
- <el-form :model="form" :rules="rules" ref="userRef" label-width="80px">
- <el-row>
- <el-col :span="12">
- <el-form-item label="鐢ㄦ埛鏄电О" prop="nickName">
- <el-input
- v-model="form.nickName"
- placeholder="璇疯緭鍏ョ敤鎴锋樀绉�"
- maxlength="30"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="褰掑睘鍏徃" prop="deptIds">
- <el-tree-select
- v-model="form.deptIds"
- :data="enabledDeptOptions"
- :render-after-expand="false"
- show-checkbox
- multiple
- placeholder="璇烽�夋嫨褰掑睘鍏徃"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber">
- <el-input
- v-model="form.phonenumber"
- placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�"
- maxlength="11"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="閭" prop="email">
- <el-input
- v-model="form.email"
- placeholder="璇疯緭鍏ラ偖绠�"
- maxlength="50"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item
- v-if="form.userId == undefined"
- label="鐧诲綍璐﹀彿"
- prop="userName"
- >
- <el-input
- v-model="form.userName"
- placeholder="璇疯緭鍏ョ櫥褰曡处鍙�"
- maxlength="30"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item
- v-if="form.userId == undefined"
- label="鐢ㄦ埛瀵嗙爜"
- prop="password"
- >
- <el-input
- v-model="form.password"
- placeholder="璇疯緭鍏ョ敤鎴峰瘑鐮�"
- type="password"
- maxlength="20"
- show-password
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item label="鐢ㄦ埛鎬у埆">
- <el-select v-model="form.sex" placeholder="璇烽�夋嫨">
- <el-option
- v-for="dict in sys_user_sex"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- ></el-option>
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鐘舵��">
- <el-radio-group v-model="form.status">
- <el-radio
- v-for="dict in sys_normal_disable"
- :key="dict.value"
- :value="dict.value"
- >{{ dict.label }}</el-radio
- >
- </el-radio-group>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item label="宀椾綅">
- <el-select v-model="form.postIds" multiple placeholder="璇烽�夋嫨">
- <el-option
- v-for="item in postOptions"
- :key="item.postId"
- :label="item.postName"
- :value="item.postId"
- :disabled="item.status == 1"
- ></el-option>
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="瑙掕壊" prop="roleIds">
- <el-select v-model="form.roleIds" multiple placeholder="璇烽�夋嫨">
- <el-option
- v-for="item in roleOptions"
- :key="item.roleId"
- :label="item.roleName"
- :value="item.roleId"
- :disabled="item.status == 1"
- ></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"
- type="textarea"
- placeholder="璇疯緭鍏ュ唴瀹�"
- ></el-input>
- </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="cancel">鍙� 娑�</el-button>
- </div>
- </template>
- </el-dialog>
-
- <!-- 鐢ㄦ埛瀵煎叆瀵硅瘽妗� -->
- <el-dialog
- :title="upload.title"
- v-model="upload.open"
- width="400px"
- append-to-body
- >
- <el-upload
- ref="uploadRef"
- :limit="1"
- accept=".xlsx, .xls"
- :headers="upload.headers"
- :action="upload.url + '?updateSupport=' + upload.updateSupport"
- :disabled="upload.isUploading"
- :on-progress="handleFileUploadProgress"
- :on-success="handleFileSuccess"
- :auto-upload="false"
- drag
- >
- <el-icon class="el-icon--upload"><upload-filled /></el-icon>
- <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
- <template #tip>
- <div class="el-upload__tip text-center">
- <div class="el-upload__tip">
- <el-checkbox
- v-model="upload.updateSupport"
- />鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
- </div>
- <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
- <el-link
- type="primary"
- :underline="false"
- style="font-size: 12px; vertical-align: baseline"
- @click="importTemplate"
- >涓嬭浇妯℃澘</el-link
- >
- </div>
- </template>
- </el-upload>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
- <el-button @click="upload.open = false">鍙� 娑�</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
+ <div class="app-container">
+ <el-row :gutter="20" style="height: calc(100vh - 8em)">
+ <splitpanes :horizontal="appStore.device === 'mobile'" class="default-theme">
+ <!--閮ㄩ棬鏁版嵁-->
+ <pane size="16">
+ <el-col style="padding: 10px">
+ <div class="head-container">
+ <el-input v-model="deptNames" placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�" clearable prefix-icon="Search" style="margin-bottom: 20px" />
+ </div>
+ <div class="head-container">
+ <el-tree :data="deptOptions" :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode" ref="deptTreeRef" node-key="id" highlight-current default-expand-all @node-click="handleNodeClick" />
+ </div>
+ </el-col>
+ </pane>
+ <!--鐢ㄦ埛鏁版嵁-->
+ <pane size="84">
+ <el-col style="padding: 10px">
+ <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+ <el-form-item label="鐧诲綍璐﹀彿" prop="userName">
+ <el-input v-model="queryParams.userName" placeholder="璇疯緭鍏ョ櫥褰曡处鍙�" clearable style="width: 240px" @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber">
+ <el-input v-model="queryParams.phonenumber" placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�" clearable style="width: 240px" @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="queryParams.status" placeholder="鐢ㄦ埛鐘舵��" clearable style="width: 240px">
+ <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍒涘缓鏃堕棿" style="width: 308px">
+ <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡"></el-date-picker>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:user:add']">鏂板</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:user:edit']">淇敼</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:user:remove']">鍒犻櫎</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:user:import']">瀵煎叆</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:user:export']">瀵煎嚭</el-button>
+ </el-col>
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
+ </el-row>
+
+ <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="50" align="center" />
+ <el-table-column label="鐢ㄦ埛缂栧彿" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
+ <el-table-column label="鐧诲綍璐﹀彿" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
+ <el-table-column label="鐢ㄦ埛鏄电О" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
+ <el-table-column label="閮ㄩ棬" align="center" key="deptNames" prop="deptNames" v-if="columns[3].visible" :show-overflow-tooltip="true" />
+ <el-table-column label="鎵嬫満鍙风爜" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
+ <el-table-column label="鐘舵��" align="center" key="status" v-if="columns[5].visible">
+ <template #default="scope">
+ <el-switch
+ v-model="scope.row.status"
+ active-value="0"
+ inactive-value="1"
+ @change="handleStatusChange(scope.row)"
+ ></el-switch>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" v-if="columns[6].visible" width="160">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.createTime) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" width="150" class-name="small-padding fixed-width">
+ <template #default="scope">
+ <el-tooltip content="淇敼" placement="top" v-if="scope.row.userId !== 1">
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
+ </el-tooltip>
+ <el-tooltip content="鍒犻櫎" placement="top" v-if="scope.row.userId !== 1">
+ <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
+ </el-tooltip>
+ <el-tooltip content="閲嶇疆瀵嗙爜" placement="top" v-if="scope.row.userId !== 1">
+ <el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button>
+ </el-tooltip>
+ <el-tooltip content="鍒嗛厤瑙掕壊" placement="top" v-if="scope.row.userId !== 1">
+ <el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
+ </el-tooltip>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+ </el-col>
+ </pane>
+ </splitpanes>
+ </el-row>
+
+ <!-- 娣诲姞鎴栦慨鏀圭敤鎴烽厤缃璇濇 -->
+ <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="userRef" label-width="80px">
+ <el-row>
+ <el-col :span="12">
+ <el-form-item v-if="form.userId == undefined" label="鐧诲綍璐﹀彿" prop="userName">
+ <el-input v-model="form.userName" placeholder="璇疯緭鍏ョ敤鎴峰悕绉�" maxlength="30" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item v-if="form.userId == undefined" label="鐢ㄦ埛瀵嗙爜" prop="password">
+ <el-input v-model="form.password" placeholder="璇疯緭鍏ョ敤鎴峰瘑鐮�" type="password" maxlength="20" show-password />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="鐢ㄦ埛鏄电О" prop="nickName">
+ <el-input v-model="form.nickName" placeholder="璇疯緭鍏ョ敤鎴锋樀绉�" maxlength="30" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰掑睘閮ㄩ棬" prop="deptId">
+ <el-tree-select v-model="form.deptId" :data="enabledDeptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="璇烽�夋嫨褰掑睘閮ㄩ棬" check-strictly />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="宀椾綅" prop="postIds">
+ <el-select v-model="form.postIds" multiple placeholder="璇烽�夋嫨">
+ <el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId" :disabled="item.status == 1"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙掕壊" prop="roleIds">
+ <el-select v-model="form.roleIds" multiple placeholder="璇烽�夋嫨">
+ <el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId" :disabled="item.status == 1"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber">
+ <el-input v-model="form.phonenumber" placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�" maxlength="11" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閭" prop="email">
+ <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�" maxlength="50" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="鐢ㄦ埛鎬у埆">
+ <el-select v-model="form.sex" placeholder="璇烽�夋嫨">
+ <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐘舵��">
+ <el-radio-group v-model="form.status">
+ <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="澶囨敞">
+ <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�"></el-input>
+ </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="cancel">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 鐢ㄦ埛瀵煎叆瀵硅瘽妗� -->
+ <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
+ <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+ <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+ <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <template #tip>
+ <div class="el-upload__tip text-center">
+ <div class="el-upload__tip">
+ <el-checkbox v-model="upload.updateSupport" />鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
+ </div>
+ <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
+ <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">涓嬭浇妯℃澘</el-link>
+ </div>
+ </template>
+ </el-upload>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+ <el-button @click="upload.open = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
</template>
<script setup name="User">
-import { getToken } from "@/utils/auth";
-import useAppStore from "@/store/modules/app";
-import {
- changeUserStatus,
- listUser,
- resetUserPwd,
- delUser,
- getUser,
- updateUser,
- addUser,
- deptTreeSelect,
-} from "@/api/system/user";
-import { Splitpanes, Pane } from "splitpanes";
-import "splitpanes/dist/splitpanes.css";
-import {onMounted} from "vue";
+import { getToken } from "@/utils/auth"
+import useAppStore from '@/store/modules/app'
+import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser, deptTreeSelect } from "@/api/system/user"
+import { Splitpanes, Pane } from "splitpanes"
+import "splitpanes/dist/splitpanes.css"
-const router = useRouter();
-const appStore = useAppStore();
-const { proxy } = getCurrentInstance();
-const { sys_normal_disable, sys_user_sex } = proxy.useDict(
- "sys_normal_disable",
- "sys_user_sex"
-);
+const router = useRouter()
+const appStore = useAppStore()
+const { proxy } = getCurrentInstance()
+const { sys_normal_disable, sys_user_sex } = proxy.useDict("sys_normal_disable", "sys_user_sex")
-const userList = ref([]);
-const open = ref(false);
-const loading = ref(true);
-const showSearch = ref(true);
-const ids = ref([]);
-const single = ref(true);
-const multiple = ref(true);
-const total = ref(0);
-const title = ref("");
-const dateRange = ref([]);
-const deptName = ref("");
-const deptOptions = ref(undefined);
-const enabledDeptOptions = ref(undefined);
-const initPassword = ref(undefined);
-const postOptions = ref([]);
-const roleOptions = ref([]);
+const userList = ref([])
+const open = ref(false)
+const loading = ref(true)
+const showSearch = ref(true)
+const ids = ref([])
+const single = ref(true)
+const multiple = ref(true)
+const total = ref(0)
+const title = ref("")
+const dateRange = ref([])
+const deptNames = ref("")
+const deptOptions = ref(undefined)
+const enabledDeptOptions = ref(undefined)
+const initPassword = ref(undefined)
+const postOptions = ref([])
+const roleOptions = ref([])
/*** 鐢ㄦ埛瀵煎叆鍙傛暟 */
const upload = reactive({
- // 鏄惁鏄剧ず寮瑰嚭灞傦紙鐢ㄦ埛瀵煎叆锛�
- open: false,
- // 寮瑰嚭灞傛爣棰橈紙鐢ㄦ埛瀵煎叆锛�
- title: "",
- // 鏄惁绂佺敤涓婁紶
- isUploading: false,
- // 鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
- updateSupport: 0,
- // 璁剧疆涓婁紶鐨勮姹傚ご閮�
- headers: { Authorization: "Bearer " + getToken() },
- // 涓婁紶鐨勫湴鍧�
- url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData",
-});
+ // 鏄惁鏄剧ず寮瑰嚭灞傦紙鐢ㄦ埛瀵煎叆锛�
+ open: false,
+ // 寮瑰嚭灞傛爣棰橈紙鐢ㄦ埛瀵煎叆锛�
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
+ updateSupport: 0,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
+})
// 鍒楁樉闅愪俊鎭�
const columns = ref([
- { key: 0, label: `鐢ㄦ埛缂栧彿`, visible: true },
- { key: 1, label: `鐧诲綍璐﹀彿`, visible: true },
- { key: 2, label: `鐢ㄦ埛鏄电О`, visible: true },
- { key: 3, label: `閮ㄩ棬`, visible: true },
- { key: 4, label: `鎵嬫満鍙风爜`, visible: true },
- { key: 5, label: `鐘舵�乣, visible: true },
- { key: 6, label: `鍒涘缓鏃堕棿`, visible: true },
-]);
+ { key: 0, label: `鐢ㄦ埛缂栧彿`, visible: true },
+ { key: 1, label: `鐧诲綍璐﹀彿`, visible: true },
+ { key: 2, label: `鐢ㄦ埛鏄电О`, visible: true },
+ { key: 3, label: `閮ㄩ棬`, visible: true },
+ { key: 4, label: `鎵嬫満鍙风爜`, visible: true },
+ { key: 5, label: `鐘舵�乣, visible: true },
+ { key: 6, label: `鍒涘缓鏃堕棿`, visible: true }
+])
const data = reactive({
- form: {},
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- userName: undefined,
- phonenumber: undefined,
- status: undefined,
- deptId: undefined,
- },
- rules: {
- userName: [
- { required: true, message: "鐧诲綍璐﹀彿涓嶈兘涓虹┖", trigger: "blur" },
- {
- min: 2,
- max: 20,
- message: "鐧诲綍璐﹀彿闀垮害蹇呴』浠嬩簬 2 鍜� 20 涔嬮棿",
- trigger: "blur",
- },
- ],
- nickName: [
- { required: true, message: "鐢ㄦ埛鏄电О涓嶈兘涓虹┖", trigger: "blur" },
- ],
- deptIds: [{ required: true, message: "鍏徃涓嶈兘涓虹┖", trigger: "change" }],
- roleIds: [{ required: true, message: "瑙掕壊涓嶈兘涓虹┖", trigger: "change" }],
- password: [
- { required: true, message: "鐢ㄦ埛瀵嗙爜涓嶈兘涓虹┖", trigger: "blur" },
- {
- min: 5,
- max: 20,
- message: "鐢ㄦ埛瀵嗙爜闀垮害蹇呴』浠嬩簬 5 鍜� 20 涔嬮棿",
- trigger: "blur",
- },
- {
- pattern: /^[^<>"'|\\]+$/,
- message: "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |",
- trigger: "blur",
- },
- ],
- email: [
- {
- type: "email",
- message: "璇疯緭鍏ユ纭殑閭鍦板潃",
- trigger: ["blur", "change"],
- },
- ],
- phonenumber: [
- {
- pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
- message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜",
- trigger: "blur",
- },
- ],
- },
-});
+ form: {},
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ userName: undefined,
+ phonenumber: undefined,
+ status: undefined,
+ deptId: undefined
+ },
+ rules: {
+ userName: [{ required: true, message: "鐢ㄦ埛鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }, { min: 2, max: 20, message: "鐢ㄦ埛鍚嶇О闀垮害蹇呴』浠嬩簬 2 鍜� 20 涔嬮棿", trigger: "blur" }],
+ nickName: [{ required: true, message: "鐢ㄦ埛鏄电О涓嶈兘涓虹┖", trigger: "blur" }],
+ password: [{ required: true, message: "鐢ㄦ埛瀵嗙爜涓嶈兘涓虹┖", trigger: "blur" }, { min: 5, max: 20, message: "鐢ㄦ埛瀵嗙爜闀垮害蹇呴』浠嬩簬 5 鍜� 20 涔嬮棿", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |", trigger: "blur" }],
+ email: [{ type: "email", message: "璇疯緭鍏ユ纭殑閭鍦板潃", trigger: ["blur", "change"] }],
+ phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur" }],
+ deptId: [{ required: true, message: "褰掑睘閮ㄩ棬涓嶈兘涓虹┖", trigger: "change" }],
+ postIds: [{ required: true, message: "宀椾綅涓嶈兘涓虹┖", trigger: "change" }],
+ roleIds: [{ required: true, message: "瑙掕壊涓嶈兘涓虹┖", trigger: "change" }]
+ }
+})
-const { queryParams, form, rules } = toRefs(data);
+const { queryParams, form, rules } = toRefs(data)
/** 閫氳繃鏉′欢杩囨护鑺傜偣 */
const filterNode = (value, data) => {
- if (!value) return true;
- return data.label.indexOf(value) !== -1;
-};
+ if (!value) return true
+ return data.label.indexOf(value) !== -1
+}
/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
-watch(deptName, (val) => {
- proxy.$refs["deptTreeRef"].filter(val);
-});
+watch(deptNames, val => {
+ proxy.$refs["deptTreeRef"].filter(val)
+})
/** 鏌ヨ鐢ㄦ埛鍒楄〃 */
function getList() {
- loading.value = true;
- listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(
- (res) => {
- loading.value = false;
- userList.value = res.rows;
- total.value = res.total;
- }
- );
+ loading.value = true
+ listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
+ loading.value = false
+ userList.value = res.rows
+ total.value = res.total
+ })
}
/** 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋� */
function getDeptTree() {
- deptTreeSelect().then((response) => {
- deptOptions.value = response.data;
- enabledDeptOptions.value = filterDisabledDept(
- JSON.parse(JSON.stringify(response.data))
- );
- });
+ deptTreeSelect().then(response => {
+ deptOptions.value = response.data
+ enabledDeptOptions.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;
- });
+ return deptList.filter(dept => {
+ if (dept.disabled) {
+ return false
+ }
+ if (dept.children && dept.children.length) {
+ dept.children = filterDisabledDept(dept.children)
+ }
+ return true
+ })
}
/** 鑺傜偣鍗曞嚮浜嬩欢 */
function handleNodeClick(data) {
- queryParams.value.deptId = data.id;
- handleQuery();
+ queryParams.value.deptId = data.id
+ handleQuery()
}
/** 鎼滅储鎸夐挳鎿嶄綔 */
function handleQuery() {
- queryParams.value.pageNum = 1;
- getList();
+ queryParams.value.pageNum = 1
+ getList()
}
/** 閲嶇疆鎸夐挳鎿嶄綔 */
function resetQuery() {
- dateRange.value = [];
- proxy.resetForm("queryRef");
- queryParams.value.deptId = undefined;
- proxy.$refs.deptTreeRef.setCurrentKey(null);
- handleQuery();
+ dateRange.value = []
+ proxy.resetForm("queryRef")
+ queryParams.value.deptId = undefined
+ proxy.$refs.deptTreeRef.setCurrentKey(null)
+ handleQuery()
}
/** 鍒犻櫎鎸夐挳鎿嶄綔 */
function handleDelete(row) {
- const userIds = row.userId || ids.value;
- proxy.$modal
- .confirm('鏄惁纭鍒犻櫎鐢ㄦ埛缂栧彿涓�"' + userIds + '"鐨勬暟鎹」锛�')
- .then(function () {
- return delUser(userIds);
- })
- .then(() => {
- getList();
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- })
- .catch(() => {});
+ const userIds = row.userId || ids.value
+ proxy.$modal.confirm('鏄惁纭鍒犻櫎鐢ㄦ埛缂栧彿涓�"' + userIds + '"鐨勬暟鎹」锛�').then(function () {
+ return delUser(userIds)
+ }).then(() => {
+ getList()
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
+ }).catch(() => {})
}
/** 瀵煎嚭鎸夐挳鎿嶄綔 */
function handleExport() {
- proxy.download(
- "system/user/export",
- {
- ...queryParams.value,
- },
- `user_${new Date().getTime()}.xlsx`
- );
+ proxy.download("system/user/export", {
+ ...queryParams.value,
+ },`user_${new Date().getTime()}.xlsx`)
}
/** 鐢ㄦ埛鐘舵�佷慨鏀� */
function handleStatusChange(row) {
- let text = row.status === "0" ? "鍚敤" : "鍋滅敤";
- proxy.$modal
- .confirm('纭瑕�"' + text + '""' + row.userName + '"鐢ㄦ埛鍚�?')
- .then(function () {
- return changeUserStatus(row.userId, row.status);
- })
- .then(() => {
- proxy.$modal.msgSuccess(text + "鎴愬姛");
- })
- .catch(function () {
- row.status = row.status === "0" ? "1" : "0";
- });
+ let text = row.status === "0" ? "鍚敤" : "鍋滅敤"
+ proxy.$modal.confirm('纭瑕�"' + text + '""' + row.userName + '"鐢ㄦ埛鍚�?').then(function () {
+ return changeUserStatus(row.userId, row.status)
+ }).then(() => {
+ proxy.$modal.msgSuccess(text + "鎴愬姛")
+ }).catch(function () {
+ row.status = row.status === "0" ? "1" : "0"
+ })
}
/** 鏇村鎿嶄綔 */
function handleCommand(command, row) {
- switch (command) {
- case "handleResetPwd":
- handleResetPwd(row);
- break;
- case "handleAuthRole":
- handleAuthRole(row);
- break;
- default:
- break;
- }
+ switch (command) {
+ case "handleResetPwd":
+ handleResetPwd(row)
+ break
+ case "handleAuthRole":
+ handleAuthRole(row)
+ break
+ default:
+ break
+ }
}
/** 璺宠浆瑙掕壊鍒嗛厤 */
function handleAuthRole(row) {
- const userId = row.userId;
- router.push("/system/user-auth/role/" + userId);
+ const userId = row.userId
+ router.push("/system/user-auth/role/" + userId)
}
/** 閲嶇疆瀵嗙爜鎸夐挳鎿嶄綔 */
function handleResetPwd(row) {
- proxy
- .$prompt('璇疯緭鍏�"' + row.userName + '"鐨勬柊瀵嗙爜', "鎻愮ず", {
- confirmButtonText: "纭畾",
- cancelButtonText: "鍙栨秷",
- closeOnClickModal: false,
- inputPattern: /^.{5,20}$/,
- inputErrorMessage: "鐢ㄦ埛瀵嗙爜闀垮害蹇呴』浠嬩簬 5 鍜� 20 涔嬮棿",
- inputValidator: (value) => {
- if (/<|>|"|'|\||\\/.test(value)) {
- return "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |";
- }
- },
- })
- .then(({ value }) => {
- resetUserPwd(row.userId, value).then((response) => {
- proxy.$modal.msgSuccess("淇敼鎴愬姛锛屾柊瀵嗙爜鏄細" + value);
- });
- })
- .catch(() => {});
+ proxy.$prompt('璇疯緭鍏�"' + row.userName + '"鐨勬柊瀵嗙爜', "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ closeOnClickModal: false,
+ inputPattern: /^.{5,20}$/,
+ inputErrorMessage: "鐢ㄦ埛瀵嗙爜闀垮害蹇呴』浠嬩簬 5 鍜� 20 涔嬮棿",
+ inputValidator: (value) => {
+ if (/<|>|"|'|\||\\/.test(value)) {
+ return "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |"
+ }
+ },
+ }).then(({ value }) => {
+ resetUserPwd(row.userId, value).then(response => {
+ proxy.$modal.msgSuccess("淇敼鎴愬姛锛屾柊瀵嗙爜鏄細" + value)
+ })
+ }).catch(() => {})
}
/** 閫夋嫨鏉℃暟 */
function handleSelectionChange(selection) {
- ids.value = selection.map((item) => item.userId);
- single.value = selection.length != 1;
- multiple.value = !selection.length;
+ ids.value = selection.map(item => item.userId)
+ single.value = selection.length != 1
+ multiple.value = !selection.length
}
/** 瀵煎叆鎸夐挳鎿嶄綔 */
function handleImport() {
- upload.title = "鐢ㄦ埛瀵煎叆";
- upload.open = true;
+ upload.title = "鐢ㄦ埛瀵煎叆"
+ upload.open = true
}
/** 涓嬭浇妯℃澘鎿嶄綔 */
function importTemplate() {
- proxy.download(
- "system/user/importTemplate",
- {},
- `user_template_${new Date().getTime()}.xlsx`
- );
+ proxy.download("system/user/importTemplate", {
+ }, `user_template_${new Date().getTime()}.xlsx`)
}
/**鏂囦欢涓婁紶涓鐞� */
const handleFileUploadProgress = (event, file, fileList) => {
- upload.isUploading = true;
-};
+ upload.isUploading = true
+}
/** 鏂囦欢涓婁紶鎴愬姛澶勭悊 */
const handleFileSuccess = (response, file, fileList) => {
- upload.open = false;
- upload.isUploading = false;
- proxy.$refs["uploadRef"].handleRemove(file);
- proxy.$alert(
- "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
- response.msg +
- "</div>",
- "瀵煎叆缁撴灉",
- { dangerouslyUseHTMLString: true }
- );
- getList();
-};
+ upload.open = false
+ upload.isUploading = false
+ proxy.$refs["uploadRef"].handleRemove(file)
+ proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "瀵煎叆缁撴灉", { dangerouslyUseHTMLString: true })
+ getList()
+}
/** 鎻愪氦涓婁紶鏂囦欢 */
function submitFileForm() {
- proxy.$refs["uploadRef"].submit();
+ proxy.$refs["uploadRef"].submit()
}
/** 閲嶇疆鎿嶄綔琛ㄥ崟 */
function reset() {
- form.value = {
- userId: undefined,
- deptId: undefined,
- userName: undefined,
- nickName: undefined,
- password: undefined,
- phonenumber: undefined,
- email: undefined,
- sex: undefined,
- status: "0",
- remark: undefined,
- postIds: [],
- roleIds: [],
- };
- proxy.resetForm("userRef");
+ form.value = {
+ userId: undefined,
+ deptId: undefined,
+ userName: undefined,
+ nickName: undefined,
+ password: undefined,
+ phonenumber: undefined,
+ email: undefined,
+ sex: undefined,
+ status: "0",
+ remark: undefined,
+ postIds: [],
+ roleIds: []
+ }
+ proxy.resetForm("userRef")
}
/** 鍙栨秷鎸夐挳 */
function cancel() {
- open.value = false;
- reset();
+ open.value = false
+ reset()
}
/** 鏂板鎸夐挳鎿嶄綔 */
function handleAdd() {
- reset();
- getUser().then((response) => {
- postOptions.value = response.posts;
- roleOptions.value = response.roles;
- open.value = true;
- title.value = "娣诲姞鐢ㄦ埛";
- form.value.password = initPassword.value;
- });
+ reset()
+ getUser().then(response => {
+ postOptions.value = response.posts
+ roleOptions.value = response.roles
+ open.value = true
+ title.value = "娣诲姞鐢ㄦ埛"
+ form.value.password = initPassword.value
+ })
}
/** 淇敼鎸夐挳鎿嶄綔 */
function handleUpdate(row) {
- reset();
- const userId = row.userId || ids.value;
- getUser(userId).then((response) => {
- form.value = response.data;
- postOptions.value = response.posts;
- roleOptions.value = response.roles;
- form.value.postIds = response.postIds;
- form.value.roleIds = response.roleIds;
- form.value.deptIds = response.deptIds;
- open.value = true;
- title.value = "淇敼鐢ㄦ埛";
- form.password = "";
- });
+ reset()
+ const userId = row.userId || ids.value
+ getUser(userId).then(response => {
+ form.value = response.data
+ postOptions.value = response.posts
+ roleOptions.value = response.roles
+ form.value.postIds = response.postIds
+ form.value.roleIds = response.roleIds
+ open.value = true
+ title.value = "淇敼鐢ㄦ埛"
+ form.password = ""
+ })
}
/** 鎻愪氦鎸夐挳 */
function submitForm() {
- proxy.$refs["userRef"].validate((valid) => {
- if (valid) {
- if (form.value.userId != undefined) {
- updateUser(form.value).then((response) => {
- proxy.$modal.msgSuccess("淇敼鎴愬姛");
- open.value = false;
- getList();
- });
- } else {
- addUser(form.value).then((response) => {
- proxy.$modal.msgSuccess("鏂板鎴愬姛");
- open.value = false;
- getList();
- });
- }
- }
- });
+ proxy.$refs["userRef"].validate(valid => {
+ if (valid) {
+ // 褰掑睘閮ㄩ棬铏界劧鏄崟閫夛紝浣嗗悗绔渶瑕佷紶鏁扮粍瀛楁 deptIds
+ const payload = {
+ ...form.value,
+ deptIds: form.value.deptId ? [form.value.deptId] : []
+ }
+ if (form.value.userId != undefined) {
+ updateUser(payload).then(response => {
+ proxy.$modal.msgSuccess("淇敼鎴愬姛")
+ open.value = false
+ getList()
+ })
+ } else {
+ addUser(payload).then(response => {
+ proxy.$modal.msgSuccess("鏂板鎴愬姛")
+ open.value = false
+ getList()
+ })
+ }
+ }
+ })
}
-onMounted(() => {
- getDeptTree();
- getList();
-});
+
+getDeptTree()
+getList()
</script>
--
Gitblit v1.9.3