From 07f9f8657d057a38792c3822acc9b08d83478967 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 07 五月 2026 14:23:10 +0800
Subject: [PATCH] 合并代码
---
src/views/financialManagement/accounting/index.vue | 1
src/assets/styles/index.scss | 49
src/api/inventoryManagement/stockInRecord.js | 17
src/layout/index.vue | 250
src/api/inventoryManagement/stockInventory.js | 35
src/views/collaborativeApproval/approvalManagement/index.vue | 881 +
src/views/collaborativeApproval/sealManagement/index.vue | 4
src/views/productionManagement/productStructure/index.vue | 865
src/views/tool/build/CodeTypeDialog.vue | 2
src/views/salesManagement/invoiceLedger/index.vue | 29
src/views/inventoryManagement/receiptManagement/index.vue | 59
src/views/equipmentManagement/calibration/index.vue | 2
src/views/financialManagement/voucher/generalLedger.vue | 230
src/views/personnelManagement/analytics/index.vue | 1
src/views/collaborativeApproval/meetingBoard/index.vue | 2
src/api/basicData/parameterMaintenance.js | 81
src/utils/generator/html.js | 4
src/api/basicData/customer.js | 75
src/views/productionManagement/workOrderEdit/index.vue | 266
src/components/Breadcrumb/index.vue | 43
src/views/financialManagement/receivable/salesReturn.vue | 305
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue | 284
src/views/reportAnalysis/reportManagement/index.vue | 1
src/api/system/appVersion.js | 19
src/views/customerService/feedbackRegistration/index.vue | 18
src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue | 30
src/views/procurementManagement/purchaseReturnOrder/New.vue | 278
src/views/salesManagement/orderManagement/index.vue | 2
src/views/reportAnalysis/dataDashboard/index0.vue | 2
src/views/collaborativeApproval/noticeManagement/index.vue | 6
src/views/system/role/index.vue | 2
src/views/equipmentManagement/upkeep/Form/PlanModal.vue | 26
jsconfig.json | 10
src/views/lavorissue/statistics/index.vue | 2
src/views/procurementManagement/paymentEntry/index.vue | 32
src/api/productionManagement/productionCosting.js | 4
src/assets/styles/variables.module.scss | 446
src/views/qualityManagement/metricBinding/index.vue | 4
src/views/safeProduction/accidentReportingRecord/index.vue | 2
src/components/AttachmentPreview/image/index.vue | 76
src/views/financialManagement/receivable/reconciliation.vue | 469
src/views/customerService/afterSalesHandling/index.vue | 167
src/views/collaborativeApproval/knowledgeBase/index.vue | 4
src/views/financialManagement/voucher/detailLedger.vue | 289
src/views/collaborativeApproval/attendanceManagement/index.vue | 2
src/views/fileManagement/document/index.vue | 10
src/views/procurementManagement/procurementReport/index.vue | 33
src/api/procurementManagement/procurementInvoiceLedger.js | 8
src/views/equipmentManagement/brand/index.vue | 2
src/views/collaborativeApproval/customerVisit/index.vue | 2
src/api/inventoryManagement/stockOut.js | 18
src/api/basicData/productModel.js | 8
src/views/inventoryManagement/stockManagement/Subtract.vue | 48
src/views/personnelManagement/monthlyStatistics/index.vue | 2
src/views/productionManagement/productionProcess/index.vue | 1306 +
src/views/basicData/product/ProductSelectDialog.vue | 29
src/views/financialManagement/receivable/salesOut.vue | 271
src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue | 320
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue | 89
src/api/procurementManagement/purchase_return_order.js | 18
src/api/productionManagement/productionOrder.js | 106
src/api/salesManagement/deliveryLedger.js | 7
src/views/productionManagement/productionProcess/Edit.vue | 12
src/views/salesManagement/returnOrder/index.vue | 57
src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue | 6
src/layout/components/Sidebar/Logo.vue | 271
src/views/safeProduction/safeWorkApproval/index.vue | 2
src/views/productionManagement/processStatistics/index.vue | 268
src/views/collaborativeApproval/planTemplate/index.vue | 2
src/views/inventoryManagement/receiptManagement/Record.vue | 91
src/views/equipmentManagement/upkeep/index.vue | 1108
src/views/salesManagement/deliveryLedger/index.vue | 468
src/views/equipmentManagement/repair/Modal/MaintainModal.vue | 115
src/views/personnelManagement/employeeRecord/index.vue | 141
src/views/productionPlan/productionPlan/components/PIMTable.vue | 470
src/views/safeProduction/hazardSourceLedger/index.vue | 2
src/router/index.js | 127
src/api/productionManagement/productProcessRoute.js | 43
src/views/inventoryManagement/transportTaskManagement/index.vue | 4
src/components/PurchaseAIChatSidebar/index.vue | 26
src/views/projectManagement/roles/index.vue | 79
src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue | 225
src/api/productionPlan/productionPlan.js | 79
src/views/customerService/feedbackRegistration/components/formDia.vue | 45
src/components/AttachmentUpload/image/index.vue | 335
src/views/productionManagement/workOrder/index.vue | 2
src/views/inventoryManagement/dispatchLog/Record.vue | 94
src/components/ProjectManagement/ProgressReportDialog.vue | 50
src/api/productionManagement/productStructure.js | 35
src/views/procurementManagement/purchaseReturnOrder/index.vue | 289
src/api/productionManagement/productBom.js | 24
src/components/Dialog/FileListDialog.vue | 1
src/views/basicData/supplierManage/components/HomeTab.vue | 6
src/views/collaborativeApproval/enterpriseBook/index.vue | 2
src/views/productionManagement/productionCosting/index.vue | 698
src/components/SvgIcon/index.vue | 2
src/views/financialManagement/receivable/invoiceApply.vue | 363
src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue | 33
src/views/productionManagement/productStructure/Detail/index.vue | 121
src/views/login.vue | 633
src/views/oaSystem/projectManagement/components/milestoneList.vue | 2
src/components/AttachmentUpload/file/index.vue | 309
src/views/personnelManagement/dimission/index.vue | 2
src/views/safeProduction/hazardousMaterialsControl/index.vue | 2
src/views/collaborativeApproval/notificationManagement/summary/index.vue | 6
src/api/productionManagement/workOrder.js | 68
src/api/collaborativeApproval/approvalManagement.js | 20
src/api/inventoryManagement/stockUninventory.js | 18
src/views/basicData/product/index.vue | 1016
src/views/financialManagement/payable/payment.vue | 377
src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue | 5
src/views/inventoryManagement/stockManagement/FrozenAndThaw.vue | 60
src/views/financialManagement/generalLedger/index.vue | 291
src/views/qualityManagement/rawMaterialInspection/index.vue | 2
src/views/inventoryManagement/stockWarning/index.vue | 4
src/views/qualityManagement/finalInspection/index.vue | 262
src/views/qualityManagement/processInspection/index.vue | 2
src/views/financialManagement/payable/input-invoice.vue | 409
src/views/productionManagement/workOrderManagement/index.vue | 309
src/api/personnelManagement/class.js | 3
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 391
src/views/productionManagement/processRoute/New.vue | 285
src/views/productionManagement/productionTraceability/index.vue | 706
src/views/inventoryManagement/stockManagement/index.vue | 57
src/components/Dialog/FileList.vue | 245
src/views/equipmentManagement/ledger/index.vue | 88
src/views/safeProduction/safetyTrainingAssessment/index.vue | 51
src/views/inventoryManagement/dispatchLog/index.vue | 61
src/store/modules/user.js | 53
src/api/basicData/customerFile.js | 72
src/views/financialManagement/receivable/receipt.vue | 356
src/views/financialManagement/revenueManagement/index.vue | 445
src/views/financialManagement/salesRefund/index.vue | 4
src/views/financialManagement/assets/fixedAssets.vue | 462
src/api/basicData/common.js | 25
src/views/collaborativeApproval/approvalProcess/fileList.vue | 6
src/views/qualityManagement/nonconformingManagement/index.vue | 36
src/views/salesManagement/receiptPaymentHistory/index.vue | 1
src/views/procurementManagement/procurementInvoiceLedger/index.vue | 159
src/views/productionManagement/processRoute/index.vue | 424
src/views/productionManagement/processRoute/processRouteItem/index.vue | 2162 +
src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue | 6
src/views/salesManagement/receiptPaymentLedger/index.vue | 2
src/views/collaborativeApproval/approvalProcess/index.vue | 474
src/views/safeProduction/emergencyPlanReview/index.vue | 2
src/views/financialManagement/voucher/index.vue | 836 +
src/views/salesManagement/salesLedger/index.vue | 4440 ++--
src/components/Editor/index.vue | 54
src/api/productionManagement/processRouteItem.js | 62
src/views/fileManagement/return/index.vue | 35
src/api/productionManagement/processRoute.js | 29
src/layout/components/Sidebar/index.vue | 236
src/views/financialManagement/assets/intangibleAssets.vue | 458
src/views/inventoryManagement/stockManagement/Record.vue | 223
src/views/inventoryManagement/stockManagement/Unqualified.vue | 8
src/views/salesManagement/salesLedger/fileList.vue | 4
src/views/safeProduction/safeQualifications/index.vue | 46
src/views/example/DynamicTableExample.vue | 2
src/views/personnelManagement/socialSecuritySet/index.vue | 2
src/views/equipmentManagement/spareParts/index.vue | 291
src/views/fileManagement/bookshelf/index.vue | 6
src/views/salesManagement/receiptPayment/index.vue | 5
src/assets/styles/sidebar.scss | 271
src/api/productionManagement/processRouteFile.js | 28
src/assets/styles/element-ui.scss | 193
src/views/equipmentManagement/defectManagement/index.vue | 2
src/components/PIMTable/PIMTable.vue | 785
src/views/basicData/customerFileOpenSea/index.vue | 1803 ++
src/layout/components/TagsView/index.vue | 178
src/views/inventoryManagement/stockManagement/Qualified.vue | 9
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue | 202
src/views/salesManagement/invoiceRegistration/index.vue | 2
src/views/collaborativeApproval/notificationManagement/index.vue | 2
src/views/customerService/expiryAfterSales/index.vue | 11
src/views/personnelManagement/classsSheduling/index.vue | 21
src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue | 2
src/api/basicData/storageAttachment.js | 29
src/layout/components/AppMain.vue | 11
src/api/basicData/productProcess.js | 10
src/views/financialManagement/receivable/outputInvoice.vue | 373
src/views/collaborativeApproval/rpaManagement/index.vue | 2
src/views/oaSystem/projectManagement/projectDetail.vue | 4
src/views/safeProduction/dangerInvestigation/index.vue | 178
src/views/index.vue | 3
src/views/personnelManagement/attendanceCheckin/index.vue | 5
src/views/basicData/parameterMaintenance/index.vue | 793
src/views/reportAnalysis/productionAnalysis/components/left-top.vue | 2
src/components/PageHeader/index.vue | 10
src/views/personnelManagement/contractManagement/index.vue | 2
src/views/equipmentManagement/ledger/Form.vue | 21
src/views/collaborativeApproval/purchaseApproval/index.vue | 1960 +-
src/views/inventoryManagement/vehicleFuelManagement/index.vue | 2
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue | 2
src/views/basicData/customerFile/index.vue | 125
src/views/fileManagement/borrow/index.vue | 35
src/views/qualityManagement/metricMaintenance/index.vue | 23
src/views/projectManagement/projectType/components/ProjectTypeDialog.vue | 58
vite.config.js | 4
src/views/inventoryManagement/stockManagement/New.vue | 330
src/views/equipmentManagement/repair/Modal/RepairModal.vue | 19
src/views/salesManagement/strategyControl/index.vue | 4
src/views/inventoryManagement/issueManagement/index.vue | 2
src/views/salesManagement/salesQuotation/index.vue | 274
src/views/financialManagement/payable/paymentApply.vue | 360
src/views/financialManagement/expenseManagement/index.vue | 176
src/views/oaSystem/projectManagement/components/taskTree.vue | 2
src/views/procurementManagement/procurementLedger/index.vue | 416
src/views/equipmentManagement/measurementEquipment/index.vue | 2
src/views/financialManagement/payable/reconciliation.vue | 469
src/views/collaborativeApproval/processTracking/index.vue | 2
src/views/productionManagement/productionReporting/index.vue | 61
src/views/financialManagement/payable/purchaseIn.vue | 331
src/main.js | 9
src/layout/components/TagsView/ScrollPane.vue | 2
src/views/productionManagement/productionOrder/index.vue | 743
FILE_UPLOAD_README.md | 480
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue | 115
src/views/productionManagement/productionProcess/New.vue | 8
src/views/procurementManagement/paymentHistory/index.vue | 1
src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue | 76
src/views/projectManagement/projectType/index.vue | 2
src/views/system/appVersion/index.vue | 270
src/layout/components/Navbar.vue | 116
src/views/equipmentManagement/inspectionManagement/components/formDia.vue | 2
src/components/ProcessParamListDialog.vue | 642
src/views/productManagement/productIdentifier/index.vue | 2
src/api/equipmentManagement/sparePartsUsage.js | 36
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue | 40
src/views/productionManagement/productionOrder/New.vue | 300
src/views/salesManagement/returnOrder/components/formDia.vue | 21
src/views/projectManagement/Management/components/formDia.vue | 11
src/views/oaSystem/projectManagement/index.vue | 2
src/api/productionManagement/productionProcess.js | 75
src/views/equipmentManagement/repair/index.vue | 25
src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue | 165
src/store/modules/permission.js | 49
src/components/AIChatSidebar/index.vue | 4423 +++++
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue | 63
src/views/procurementManagement/paymentLedger/index.vue | 2
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue | 2
/dev/null | 256
src/views/personnelManagement/dimission/components/formDia.vue | 1
src/views/salesManagement/indicatorStats/index.vue | 1
src/views/systemArchitecture/index.vue | 436
src/views/productionPlan/productionPlan/index.vue | 1332 +
src/views/projectManagement/Management/projectDetail.vue | 2
src/components/PIMTable/Pagination.vue | 1
src/views/projectManagement/Management/index.vue | 10
src/views/energyManagement/dynamicEnergySaving/index.vue | 6
249 files changed, 38,268 insertions(+), 11,527 deletions(-)
diff --git a/FILE_UPLOAD_README.md b/FILE_UPLOAD_README.md
new file mode 100644
index 0000000..ad5b42a
--- /dev/null
+++ b/FILE_UPLOAD_README.md
@@ -0,0 +1,480 @@
+# 鏈湴鏂囦欢涓婁紶 README
+
+鏈枃妗e熀浜庝互涓嬪疄鐜版暣鐞嗭細
+
+- `src/components/AttachmentUpload/file/index.vue`
+- `src/components/AttachmentUpload/image/index.vue`
+- `src/components/AttachmentPreview/image/index.vue`
+- `src/components/Dialog/FileList.vue`
+- `src/api/basicData/common.js`
+- `src/api/publicApi/commonFile.js`
+
+鐩稿叧缁勪欢宸插湪 `src/main.js` 涓敞鍐屼负鍏ㄥ眬缁勪欢锛屽彲鐩存帴鍦ㄩ〉闈腑浣跨敤锛�
+
+- `FileUpload`
+- `ImageUpload`
+- `ImagePreview`
+- `FileListDialog`
+
+## 1. 鍔熻兘姒傝
+
+褰撳墠杩欏涓婁紶鑳藉姏涓昏鍒嗕负 4 閮ㄥ垎锛�
+
+1. `FileUpload`锛氭櫘閫氭枃浠朵笂浼狅紝鏀寔鎷栨嫿銆佹壒閲忎笂浼犮�侀瑙堛�佸垹闄�
+2. `ImageUpload`锛氬浘鐗囦笂浼狅紝鏀寔鍥剧墖澧欏睍绀恒�侀瑙堛�佸垹闄�
+3. `ImagePreview`锛氬浘鐗囧垪琛ㄩ瑙堝睍绀�
+4. `FileListDialog`锛氫笟鍔¢檮浠跺脊绐楋紝鏀寔鏌ヨ銆佷笂浼犮�佸垹闄ゃ�佷笅杞�
+
+涓婁紶搴曞眰缁熶竴璧版帴鍙o細
+
+- `POST /common/upload`
+
+瀵瑰簲鏂规硶鍦� `src/api/basicData/common.js`锛�
+
+```js
+uploadFile(data)
+```
+
+## 2. 涓婁紶鎺ュ彛璇存槑
+
+### 2.1 閫氱敤涓婁紶鎺ュ彛
+
+鏂囦欢涓婁紶缁勪欢鍜屽浘鐗囦笂浼犵粍浠堕兘璋冪敤浜嗭細
+
+```js
+import { uploadFile } from '@/api/basicData/common'
+```
+
+鎺ュ彛鐗瑰緛锛�
+
+- 璇锋眰鏂瑰紡锛歚POST`
+- 鍦板潃锛歚/common/upload`
+- 璇锋眰绫诲瀷锛歚multipart/form-data`
+- 鏀寔 `FormData` 鎵归噺涓婁紶
+- 榛樿瀛楁鍚嶏細`files`
+
+缁勪欢鍐呴儴浼氳繖鏍风粍瑁呭弬鏁帮細
+
+```js
+const formData = new FormData()
+validFiles.forEach((file) => {
+ formData.append(props.uploadFieldName, file.raw)
+})
+```
+
+### 2.2 涓婁紶杩斿洖鍊艰姹�
+
+涓婁紶鎴愬姛鍚庯紝缁勪欢浼氬皾璇曚粠浠ヤ笅缁撴瀯涓彁鍙栨暟缁勶細
+
+- `response`
+- `response.data`
+- `response.data.data`
+- `response.payload`
+- `response.payload.data`
+- `response.rows`
+- `response.result`
+
+鍥犳鍚庣杩斿洖鏁扮粍鏃讹紝涓婇潰浠绘剰涓�绉嶇粨鏋勯兘鍙互琚瘑鍒��
+
+缁勪欢灞曠ず鏃跺父鐢ㄥ埌鐨勫瓧娈垫湁锛�
+
+- 鏂囦欢鍚嶏細`name` / `originalFilename` / `fileName` / `uidFilename`
+- 鏂囦欢鍦板潃锛歚url` / `downloadURL`
+- 鍥剧墖鍦板潃锛歚url` / `previewURL` / `previewUrl`
+- 涓婚敭锛歚id`
+
+寤鸿涓婁紶鎺ュ彛杩斿洖鐨勫崟椤瑰璞″敖閲忓寘鍚細
+
+```js
+{
+ id: 1,
+ originalFilename: 'demo.pdf',
+ downloadURL: 'https://xxx/demo.pdf',
+ previewURL: 'https://xxx/demo.png'
+}
+```
+
+## 3. FileUpload 鏂囦欢涓婁紶缁勪欢
+
+缁勪欢璺緞锛�
+
+`src/components/AttachmentUpload/file/index.vue`
+
+### 3.1 鍩虹鐢ㄦ硶
+
+```vue
+<template>
+ <FileUpload v-model:file-list="fileList" />
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const fileList = ref([])
+</script>
+```
+
+### 3.2 甯哥敤灞炴��
+
+| 灞炴�� | 璇存槑 | 绫诲瀷 | 榛樿鍊� |
+| --- | --- | --- | --- |
+| `fileList` | 缁戝畾鏂囦欢鍒楄〃 | `Array` | `[]` |
+| `limit` | 鏈�澶т笂浼犳暟閲� | `Number` | `10` |
+| `fileSize` | 鍗曚釜鏂囦欢澶у皬闄愬埗锛屽崟浣� MB | `Number` | `50` |
+| `fileType` | 鍏佽涓婁紶鐨勬枃浠剁被鍨嬶紝濡� `['pdf', 'docx']` | `Array` | `[]` |
+| `buttonText` | 涓婁紶鎻愮ず鏂囨 | `String` | `鍗曞嚮閫夋嫨鏂囦欢` |
+| `disabled` | 鏄惁绂佺敤 | `Boolean` | `false` |
+| `uploadFieldName` | `FormData` 瀛楁鍚� | `String` | `files` |
+| `index` | 琛ㄦ牸/鍒楄〃琛屾ā寮忎笅鐨勫綋鍓嶈绱㈠紩 | `Number` | `-1` |
+| `childrenKey` | 琛屽唴鎸傝浇瀛楁鍚� | `String` | `files` |
+
+### 3.3 浜嬩欢
+
+| 浜嬩欢 | 璇存槑 |
+| --- | --- |
+| `update:fileList` | 鏂囦欢鍒楄〃鍙樺寲鏃惰Е鍙� |
+| `change` | 鏂囦欢鍒楄〃鍙樺寲鏃惰Е鍙戯紝杩斿洖鏈�鏂板垪琛� |
+
+### 3.4 闄愬埗瑙勫垯
+
+缁勪欢鍐呭凡瀹炵幇锛�
+
+- 鏂囦欢鏁伴噺闄愬埗
+- 鏂囦欢澶у皬闄愬埗
+- 鏂囦欢绫诲瀷鏍¢獙
+- 涓婁紶涓姸鎬侀攣瀹�
+- 澶辫触鍚庤嚜鍔ㄦ竻绌哄綋鍓嶉�夋嫨闃熷垪
+
+渚嬪闄愬埗 PDF/Word锛�
+
+```vue
+<FileUpload
+ v-model:file-list="fileList"
+ :limit="5"
+ :file-size="20"
+ :file-type="['pdf', 'doc', 'docx']"
+/>
+```
+
+### 3.5 杩斿洖鏁版嵁鏍煎紡寤鸿
+
+`FileUpload` 鏇撮�傚悎鎺ユ敹杩欐牱鐨勫垪琛細
+
+```js
+[
+ {
+ id: 1,
+ originalFilename: '鍚堝悓.pdf',
+ downloadURL: 'https://xxx/contract.pdf'
+ }
+]
+```
+
+鍥犱负缁勪欢鎵撳紑鏂囦欢鏃朵細浼樺厛璇诲彇锛�
+
+```js
+url || downloadURL || previewURL || previewUrl
+```
+
+### 3.6 琛屽唴宓屽妯″紡
+
+濡傛灉涓婁紶缁勪欢鏀惧湪琛ㄦ牸鏌愪竴琛屼腑锛屽彲閰嶅悎 `index` 鍜� `childrenKey` 浣跨敤锛�
+
+```vue
+<FileUpload
+ v-model:file-list="tableData"
+ :index="scope.$index"
+ children-key="files"
+/>
+```
+
+姝ゆ椂缁勪欢浼氳嚜鍔ㄨ鍐欙細
+
+```js
+tableData[scope.$index].files
+```
+
+## 4. ImageUpload 鍥剧墖涓婁紶缁勪欢
+
+缁勪欢璺緞锛�
+
+`src/components/AttachmentUpload/image/index.vue`
+
+### 4.1 鍩虹鐢ㄦ硶
+
+```vue
+<template>
+ <ImageUpload v-model:file-list="imageList" />
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const imageList = ref([])
+</script>
+```
+
+### 4.2 榛樿琛屼负
+
+鍥剧墖涓婁紶缁勪欢榛樿锛�
+
+- 鏈�澶氫笂浼� `10` 寮�
+- 鍗曞紶涓嶈秴杩� `10MB`
+- 榛樿鏀寔鏍煎紡锛歚png / jpg / jpeg / webp`
+- 浣跨敤 `picture-card` 椋庢牸灞曠ず
+- 鐐瑰嚮缂╃暐鍥惧彲棰勮澶у浘
+
+### 4.3 甯哥敤绀轰緥
+
+```vue
+<ImageUpload
+ v-model:file-list="imageList"
+ :limit="9"
+ :file-size="5"
+ :file-type="['png', 'jpg', 'jpeg']"
+ button-text="涓婁紶鍥剧墖"
+/>
+```
+
+### 4.4 杩斿洖鏁版嵁鏍煎紡寤鸿
+
+`ImageUpload` 灞曠ず鍥剧墖鏃朵紭鍏堣鍙栵細
+
+```js
+url || previewURL || previewUrl
+```
+
+寤鸿鍚庣杩斿洖锛�
+
+```js
+[
+ {
+ id: 1,
+ originalFilename: '鐜板満鍥剧墖.jpg',
+ previewURL: 'https://xxx/image.jpg'
+ }
+]
+```
+
+### 4.5 琛屽唴宓屽妯″紡
+
+鍥剧墖缁勪欢鍚屾牱鏀寔琛屽唴瀛楁鍐欏洖锛�
+
+```vue
+<ImageUpload
+ v-model:file-list="tableData"
+ :index="scope.$index"
+ children-key="images"
+/>
+```
+
+榛樿鍐欏洖瀛楁涓� `images`銆�
+
+## 5. ImagePreview 鍥剧墖棰勮缁勪欢
+
+缁勪欢璺緞锛�
+
+`src/components/AttachmentPreview/image/index.vue`
+
+### 5.1 鍩虹鐢ㄦ硶
+
+```vue
+<ImagePreview :file-list="imageList" />
+```
+
+### 5.2 鍙厤缃」
+
+| 灞炴�� | 璇存槑 | 绫诲瀷 | 榛樿鍊� |
+| --- | --- | --- | --- |
+| `fileList` | 鍥剧墖鍒楄〃 | `Array` | `[]` |
+| `thumbSize` | 缂╃暐鍥惧ぇ灏� | `Number` | `72` |
+| `gap` | 缂╃暐鍥鹃棿璺� | `Number` | `10` |
+
+### 5.3 鏁版嵁瑕佹眰
+
+缁勪欢浼氳繃婊ゆ病鏈� `previewURL` 鐨勯」锛屽洜姝ゅ鏋滆姝e父鏄剧ず锛屽缓璁嚦灏戝寘鍚細
+
+```js
+[
+ {
+ previewURL: 'https://xxx/image.jpg',
+ originalFilename: '鍥剧墖1.jpg'
+ }
+]
+```
+
+濡傛灉鍒楄〃涓虹┖锛岀粍浠舵樉绀衡�滄殏鏃犲浘鐗団�濄��
+
+## 6. FileListDialog 闄勪欢寮圭獥缁勪欢
+
+缁勪欢璺緞锛�
+
+`src/components/Dialog/FileList.vue`
+
+杩欎釜缁勪欢閫傚悎涓氬姟琛ㄥ崟鎴栬鎯呴〉閲岀殑鈥滈檮浠剁鐞嗏�濆満鏅紝鑳藉姏鍖呮嫭锛�
+
+- 鏍规嵁涓氬姟涓婚敭鏌ヨ闄勪欢鍒楄〃
+- 鎵撳紑寮圭獥鏌ョ湅闄勪欢
+- 鍦ㄥ脊绐椾腑缁х画涓婁紶闄勪欢
+- 鍒犻櫎闄勪欢
+- 涓嬭浇闄勪欢
+
+### 6.1 缁勪欢灞炴��
+
+| 灞炴�� | 璇存槑 | 绫诲瀷 | 榛樿鍊� |
+| --- | --- | --- | --- |
+| `visible` | 鏄惁鏄剧ず寮圭獥 | `Boolean` | 蹇呬紶 |
+| `recordType` | 涓氬姟绫诲瀷 | `String` | `''` |
+| `recordId` | 涓氬姟涓婚敭 | `Number` | `0` |
+| `title` | 寮圭獥鏍囬 | `String` | `闄勪欢` |
+| `width` | 寮圭獥瀹藉害 | `String` | `50%` |
+| `showActions` | 鏄惁鏄剧ず涓嬭浇/鍒犻櫎鎿嶄綔鍒� | `Boolean` | `true` |
+
+### 6.2 鍩虹鐢ㄦ硶
+
+```vue
+<template>
+ <el-button @click="visible = true">鏌ョ湅闄勪欢</el-button>
+
+ <FileListDialog
+ v-model:visible="visible"
+ record-type="salesLedger"
+ :record-id="rowId"
+ title="闄勪欢鍒楄〃"
+ />
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const visible = ref(false)
+const rowId = ref(1001)
+</script>
+```
+
+### 6.3 缁勪欢鍐呴儴渚濊禆鐨勬帴鍙�
+
+`FileListDialog` 鏈韩涓嶇洿鎺ヨ皟鐢� `commonFile.js`锛岃�屾槸渚濊禆锛�
+
+- `attachmentList`
+- `createAttachment`
+- `deleteAttachment`
+
+澶勭悊閫昏緫涓猴細
+
+1. 鎵撳紑寮圭獥鍚庢牴鎹� `recordType + recordId` 鏌ヨ闄勪欢
+2. 鐐瑰嚮鈥滀笂浼犻檮浠垛�濆悗锛屽唴閮ㄤ娇鐢� `AttachmentUpload/file` 鍏堜笂浼犲埌 `/common/upload`
+3. 涓婁紶鎴愬姛鍚庯紝灏嗚繑鍥炵殑鏂囦欢瀵硅薄鍜屽凡鏈夊垪琛ㄤ竴璧锋彁浜ょ粰 `createAttachment`
+4. 鍒犻櫎鏃惰皟鐢� `deleteAttachment`
+5. 涓嬭浇鏃剁洿鎺� `window.open(downloadURL, '_blank')`
+
+鍥犳杩欓噷瑕佺壒鍒敞鎰忥細
+
+- `recordType` 鍜� `recordId` 蹇呴』鏄湁鏁堜笟鍔℃爣璇�
+- 涓婁紶鎴愬姛杩斿洖鐨勬暟鎹紝闇�瑕佽兘琚� `createAttachment` 鐩存帴鎺ユ敹
+- 鍒楄〃涓殑涓嬭浇鍦板潃瀛楁搴斾负 `downloadURL`
+
+## 7. commonFile.js 璇存槑
+
+鏂囦欢璺緞锛�
+
+`src/api/publicApi/commonFile.js`
+
+褰撳墠鏂囦欢鎻愪緵鐨勬槸鍏叡鏂囦欢鍒犻櫎鎺ュ彛锛�
+
+```js
+delCommonFile(ids)
+delCommonFileInvoiceLedger(ids)
+```
+
+瀵瑰簲鎺ュ彛锛�
+
+- `/commonFile/delCommonFile`
+- `/invoiceLedger/delFile`
+
+杩欎袱涓柟娉曟洿閫傚悎宸茬粡鍜屽叿浣撲笟鍔$粦瀹氬悗鐨勨�滃垹闄ゅ凡淇濆瓨闄勪欢鈥濆満鏅紝涓嶈礋璐d笂浼犳枃浠舵湰韬��
+
+绀轰緥锛�
+
+```js
+import { delCommonFile } from '@/api/publicApi/commonFile'
+
+await delCommonFile([1, 2, 3])
+```
+
+## 8. 鎺ㄨ崘浣跨敤鏂瑰紡
+
+### 8.1 鏅�氫笟鍔¤〃鍗曚笂浼犻檮浠�
+
+```vue
+<FileUpload v-model:file-list="form.storageBlobDTOs" />
+```
+
+鎻愪氦琛ㄥ崟鏃剁洿鎺ュ甫涓婏細
+
+```js
+{
+ ...form,
+ storageBlobDTOs: form.storageBlobDTOs
+}
+```
+
+### 8.2 鍥剧墖绫讳笟鍔�
+
+```vue
+<ImageUpload v-model:file-list="form.images" />
+<ImagePreview :file-list="form.images" />
+```
+
+### 8.3 宸茶惤搴撻檮浠剁鐞�
+
+```vue
+<FileListDialog
+ v-model:visible="dialogVisible"
+ :record-type="recordType"
+ :record-id="recordId"
+/>
+```
+
+閫傚悎璇︽儏椤点�佸鎵归〉銆佸彴璐﹂〉杩欑被鈥滄煡鐪嬪苟缁存姢褰撳墠涓氬姟闄勪欢鈥濈殑鍦烘櫙銆�
+
+## 9. 娉ㄦ剰浜嬮」
+
+1. `FileUpload` 鍜� `ImageUpload` 鍙槸璐熻矗鎶婃枃浠跺厛浼犲埌 `/common/upload`锛屼笉绛変簬宸茬粡鍜屼笟鍔℃暟鎹粦瀹氥��
+2. 濡傛灉涓氬姟闇�瑕佹寔涔呭寲闄勪欢鍏崇郴锛屼粛闇�瑕佸湪淇濆瓨琛ㄥ崟鏃舵妸杩斿洖鐨勬枃浠跺璞℃彁浜ょ粰涓氬姟鎺ュ彛銆�
+3. `ImagePreview` 褰撳墠鍙瘑鍒� `previewURL`锛屽鏋滃悗绔彧杩斿洖 `url`锛岄瑙堢粍浠跺皢涓嶄細灞曠ず锛屾渶濂界粺涓�琛ラ綈 `previewURL`銆�
+4. `FileListDialog` 渚濊禆 `recordType`銆乣recordId` 鏌ヨ鍜屼繚瀛橀檮浠跺叧绯伙紝鏂板涓氬姟鏃惰鍏堢‘璁ゅ悗绔叧鑱旀帴鍙e彲鐢ㄣ��
+5. 鍒犻櫎鏈湴鍒楄〃椤瑰拰鍒犻櫎宸蹭繚瀛橀檮浠舵槸涓や欢浜嬶細
+ - 涓婁紶缁勪欢閲岀殑鍒犻櫎锛氬彧浼氫粠褰撳墠鍓嶇缁戝畾鏁扮粍涓Щ闄�
+ - `commonFile.js` / `deleteAttachment`锛氭墠鏄湡姝h皟鐢ㄥ悗绔垹闄�
+
+## 10. 涓�濂楁渶甯歌鐨勯〉闈㈠啓娉�
+
+```vue
+<template>
+ <el-form :model="form">
+ <el-form-item label="闄勪欢">
+ <FileUpload v-model:file-list="form.storageBlobDTOs" :limit="5" />
+ </el-form-item>
+
+ <el-form-item label="鐜板満鍥剧墖">
+ <ImageUpload v-model:file-list="form.images" :limit="9" />
+ </el-form-item>
+
+ <el-form-item label="鍥剧墖棰勮">
+ <ImagePreview :file-list="form.images" />
+ </el-form-item>
+ </el-form>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const form = ref({
+ storageBlobDTOs: [],
+ images: [],
+})
+</script>
+```
+
+濡傛灉浣犵殑鐩爣鏄�滃厛涓婁紶锛屽啀璺熶笟鍔′竴璧蜂繚瀛樷�濓紝杩欏鍐欐硶鍙互鐩存帴浣滀负鍩虹妯℃澘浣跨敤銆�
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..365552f
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"],
+ "~/*": ["./*"]
+ }
+ },
+ "include": ["src/**/*.js", "src/**/*.vue", "vite.config.js"]
+}
diff --git a/src/api/basicData/common.js b/src/api/basicData/common.js
new file mode 100644
index 0000000..547c1e1
--- /dev/null
+++ b/src/api/basicData/common.js
@@ -0,0 +1,25 @@
+import request from '@/utils/request'
+
+// 閫氱敤涓婁紶鎺ュ彛锛屾敮鎸� FormData 鎵归噺浼犳枃浠�
+export function uploadFile(data) {
+ return request({
+ url: '/common/upload',
+ method: 'post',
+ data,
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ })
+}
+
+// 閫氱敤涓婁紶鎺ュ彛锛屾敮鎸� FormData 鎵归噺浼犳枃浠�,姘镐笉杩囨湡锛屾厧鐢�
+export function uploadPublicFile(data) {
+ return request({
+ url: '/common/public/upload',
+ method: 'post',
+ data,
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ })
+}
diff --git a/src/api/basicData/customer.js b/src/api/basicData/customer.js
new file mode 100644
index 0000000..28f2eab
--- /dev/null
+++ b/src/api/basicData/customer.js
@@ -0,0 +1,75 @@
+import request from '@/utils/request'
+
+export function listCustomer(query) {
+ return request({
+ url: '/basic/customer/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鍒嗛厤瀹㈡埛
+export function assignCustomer(data) {
+ return request({
+ url: '/basic/customer/assignCustomer',
+ method: 'post',
+ data
+ })
+}
+
+// 鍥炴敹瀹㈡埛
+export function recycleCustomer(data) {
+ return request({
+ url: '/basic/customer/recycleCustomer',
+ method: 'post',
+ data
+ })
+}
+
+export function shareCustomer(data) {
+ return request({
+ url: '/basic/customer/together',
+ method: 'post',
+ data: data
+ })
+}
+
+export function getCustomer(id) {
+ return request({
+ url: '/basic/customer/' + id,
+ method: 'get'
+ })
+}
+
+export function addCustomer(data) {
+ return request({
+ url: '/basic/customer/addCustomer',
+ method: 'post',
+ data: data
+ })
+}
+
+export function updateCustomer(data) {
+ return request({
+ url: '/basic/customer/updateCustomer',
+ method: 'post',
+ data: data
+ })
+}
+
+export function exportCustomer(query) {
+ return request({
+ url: '/basic/customer/export',
+ method: 'get',
+ params: query,
+ responseType: 'blob'
+ })
+}
+
+export function delCustomer(ids) {
+ return request({
+ url: '/basic/customer/delCustomer',
+ method: 'delete',
+ data: ids
+ })
+}
diff --git a/src/api/basicData/customerFile.js b/src/api/basicData/customerFile.js
index 6a5f049..6602ddc 100644
--- a/src/api/basicData/customerFile.js
+++ b/src/api/basicData/customerFile.js
@@ -1,57 +1,5 @@
-// 瀹㈡埛妗f椤甸潰鎺ュ彛
import request from '@/utils/request'
-// 鍒嗛〉鏌ヨ
-export function listCustomer(query) {
- return request({
- url: '/basic/customer/list',
- method: 'get',
- params: query
- })
-}
-// 鏌ヨ瀹㈡埛妗f璇︾粏
-export function getCustomer(id) {
- return request({
- url: '/basic/customer/' + id,
- method: 'get'
- })
-}
-// 鏂板瀹㈡埛妗f
-export function addCustomer(data) {
- return request({
- url: '/basic/customer/addCustomer',
- method: 'post',
- data: data
- })
-}
-// 淇敼瀹㈡埛妗f
-export function updateCustomer(data) {
- return request({
- url: '/basic/customer/updateCustomer',
- method: 'post',
- data: data
- })
-}
-// 瀵煎嚭瀹㈡埛妗f
-export function exportCustomer(query) {
- return request({
- url: '/basic/customer/export',
- method: 'get',
- params: query,
- responseType: 'blob'
- })
-}
-// 鍒犻櫎瀹㈡埛妗f
-export function delCustomer(ids) {
- return request({
- url: '/basic/customer/delCustomer',
- method: 'delete',
- data: ids
- })
-}
-
-
-// 鏂板瀹㈡埛璺熻繘
export function addCustomerFollow(data) {
return request({
url: '/basic/customer-follow/add',
@@ -60,23 +8,21 @@
})
}
-// 淇敼瀹㈡埛璺熻繘
export function updateCustomerFollow(data) {
- return request({
- url: '/basic/customer-follow/edit',
- method: 'put',
- data: data,
- })
+ return request({
+ url: '/basic/customer-follow/edit',
+ method: 'put',
+ data: data,
+ })
}
-// 鍒犻櫎瀹㈡埛璺熻繘
+
export function delCustomerFollow(id) {
return request({
- url: '/basic/customer-follow/'+id,
+ url: '/basic/customer-follow/' + id,
method: 'delete',
})
}
-// 鍥炶鎻愰啋-鏂板/鏇存柊
export function addReturnVisit(data) {
return request({
url: '/basic/customer-follow/return-visit',
@@ -84,10 +30,10 @@
data: data
})
}
-// 鑾峰彇鍥炶鎻愰啋璇︽儏
+
export function getReturnVisit(id) {
return request({
url: '/basic/customer-follow/return-visit/' + id,
method: 'get'
})
-}
\ No newline at end of file
+}
diff --git a/src/api/basicData/parameterMaintenance.js b/src/api/basicData/parameterMaintenance.js
new file mode 100644
index 0000000..86d889e
--- /dev/null
+++ b/src/api/basicData/parameterMaintenance.js
@@ -0,0 +1,81 @@
+// 鍙傛暟缁存姢椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鏌ヨ鍙傛暟鍒楄〃
+export function parameterListPage(query) {
+ return request({
+ url: "/basic/parameter/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板鍙傛暟
+export function addParameter(data) {
+ return request({
+ url: "/basic/parameter/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 缂栬緫鍙傛暟
+export function updateParameter(data) {
+ return request({
+ url: "/basic/parameter/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鍒犻櫎鍙傛暟
+export function delParameter(ids) {
+ return request({
+ url: "/basic/parameter/del",
+ method: "delete",
+ data: Array.isArray(ids) ? ids : [ids],
+ });
+}
+
+// 鑾峰彇浜у搧绫诲瀷鍒楄〃
+export function getProductTypes() {
+ return request({
+ url: "/basic/product/typeList",
+ method: "get",
+ });
+}
+
+// 鏂板鍩虹鍙傛暟
+export function addBaseParam(data) {
+ return request({
+ url: "/technologyParam/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 缂栬緫鍩虹鍙傛暟
+export function editBaseParam(data) {
+ return request({
+ url: "/technologyParam/edit",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鏌ヨ鍩虹鍙傛暟鍒楄〃
+export function getBaseParamList(query) {
+ return request({
+ url: "/technologyParam/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鍒犻櫎鍩虹鍙傛暟
+export function removeBaseParam(ids) {
+ return request({
+ url: `/technologyParam/remove/` + ids,
+ method: "delete",
+ });
+}
diff --git a/src/api/basicData/productModel.js b/src/api/basicData/productModel.js
index f048f9e..8acc2ef 100644
--- a/src/api/basicData/productModel.js
+++ b/src/api/basicData/productModel.js
@@ -6,4 +6,12 @@
method: 'get',
params: query
})
+}
+
+export function productModelListByUrl(url, query) {
+ return request({
+ url,
+ method: 'get',
+ params: query
+ })
}
\ No newline at end of file
diff --git a/src/api/basicData/productProcess.js b/src/api/basicData/productProcess.js
index e0208fa..46356fd 100644
--- a/src/api/basicData/productProcess.js
+++ b/src/api/basicData/productProcess.js
@@ -1,10 +1,10 @@
-import request from '@/utils/request'
+import request from "@/utils/request";
// 宸ュ簭鍒楄〃鍒嗛〉鏌ヨ
export function productProcessListPage(query) {
return request({
- url: '/productProcess/listPage',
- method: 'get',
- params: query
- })
+ url: "/technologyOperation/listPage",
+ method: "get",
+ params: query,
+ });
}
diff --git a/src/api/basicData/storageAttachment.js b/src/api/basicData/storageAttachment.js
new file mode 100644
index 0000000..3e241f6
--- /dev/null
+++ b/src/api/basicData/storageAttachment.js
@@ -0,0 +1,29 @@
+// 闄勪欢椤甸潰鎺ュ彛
+import request from '@/utils/request'
+
+// 闄勪欢鏌ヨ
+export function attachmentList(query) {
+ return request({
+ url: '/storageAttachment/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 闄勪欢鏂板
+export function createAttachment(data) {
+ return request({
+ url: '/storageAttachment/add',
+ method: 'post',
+ data
+ })
+}
+
+// 闄勪欢鍒犻櫎
+export function deleteAttachment(data) {
+ return request({
+ url: '/storageAttachment/delete',
+ method: 'delete',
+ data
+ })
+}
diff --git a/src/api/collaborativeApproval/approvalManagement.js b/src/api/collaborativeApproval/approvalManagement.js
new file mode 100644
index 0000000..c2ce4c7
--- /dev/null
+++ b/src/api/collaborativeApproval/approvalManagement.js
@@ -0,0 +1,20 @@
+// 瀹℃壒绠$悊閰嶇疆
+import request from "@/utils/request";
+
+// 鏌ヨ瀹℃壒娴佺▼閰嶇疆鑺傜偣鍒楄〃
+export function getApproveProcessConfigNodeList(type) {
+ return request({
+ url: '/approveProcessConfigNode/list',
+ method: 'get',
+ params: { type },
+ })
+}
+
+// 鏂板瀹℃壒娴佺▼閰嶇疆鑺傜偣
+export function addApproveProcessConfigNode(data) {
+ return request({
+ url: '/approveProcessConfigNode/add',
+ method: 'post',
+ data: data,
+ })
+}
diff --git a/src/api/equipmentManagement/sparePartsUsage.js b/src/api/equipmentManagement/sparePartsUsage.js
new file mode 100644
index 0000000..e9384aa
--- /dev/null
+++ b/src/api/equipmentManagement/sparePartsUsage.js
@@ -0,0 +1,36 @@
+import request from "@/utils/request";
+
+/**
+ * 澶囦欢棰嗙敤璁板綍 - 鍒嗛〉鏌ヨ
+ * params: { current, size, sparePartId?, sparePartName?, source?, deviceId?, startTime?, endTime? }
+ */
+export const getSparePartsUsagePage = (params) => {
+ return request({
+ url: "/sparePartsRequisitionRecord/listPage",
+ method: "get",
+ params,
+ });
+};
+
+/**
+ * 澶囦欢棰嗙敤璁板綍 - 鏂板
+ * data 绀轰緥锛�
+ * {
+ * source: "repair" | "upkeep" | "manual",
+ * sourceId?: number | string,
+ * deviceId?: number | string,
+ * deviceName?: string,
+ * operatorId?: number | string,
+ * operator?: string,
+ * useTime?: string, // YYYY-MM-DD HH:mm:ss
+ * items: [{ sparePartId: number|string, qty: number }]
+ * }
+ */
+export const addSparePartsUsage = (data) => {
+ return request({
+ url: "/sparePartsUsage/add",
+ method: "post",
+ data,
+ });
+};
+
diff --git a/src/api/inventoryManagement/stockInRecord.js b/src/api/inventoryManagement/stockInRecord.js
index 1746bfe..3142e09 100644
--- a/src/api/inventoryManagement/stockInRecord.js
+++ b/src/api/inventoryManagement/stockInRecord.js
@@ -24,4 +24,21 @@
method: "delete",
data: ids,
});
+};
+
+export const batchDeletePendingStockInRecords = (ids) => {
+ return request({
+ url: "/stockInRecord/pending",
+ method: "delete",
+ data: ids,
+ });
+};
+
+// 鎵归噺瀹℃壒鍏ュ簱璁板綍锛坅pprovalStatus: approved/rejected锛�
+export const batchApproveStockInRecords = (data) => {
+ return request({
+ url: "/stockInRecord/approve",
+ method: "post",
+ data,
+ });
};
\ No newline at end of file
diff --git a/src/api/inventoryManagement/stockInventory.js b/src/api/inventoryManagement/stockInventory.js
index aed71e2..0ba0943 100644
--- a/src/api/inventoryManagement/stockInventory.js
+++ b/src/api/inventoryManagement/stockInventory.js
@@ -8,6 +8,15 @@
});
};
+// 鍒嗛〉鏌ヨ鑱斿悎搴撳瓨璁板綍鍒楄〃锛堝寘鍚晢鍝佷俊鎭級
+export const getStockInventoryListPageCombined = (params) => {
+ return request({
+ url: "/stockInventory/pageListCombinedStockInventory",
+ method: "get",
+ params,
+ });
+};
+
// 鍒涘缓搴撳瓨璁板綍
export const createStockInventory = (params) => {
return request({
@@ -21,6 +30,24 @@
export const subtractStockInventory = (params) => {
return request({
url: "/stockInventory/subtractStockInventory",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鏂板鍏ュ簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�
+export const addStockInRecordOnly = (params) => {
+ return request({
+ url: "/stockInventory/addStockInRecordOnly",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鏂板鍑哄簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�
+export const addStockOutRecordOnly = (params) => {
+ return request({
+ url: "/stockInventory/addStockOutRecordOnly",
method: "post",
data: params,
});
@@ -60,3 +87,11 @@
});
};
+export const getStockInventoryByModelId = (productModelId) => {
+ return request({
+ url: "/stockInventory/getByModelId",
+ method: "get",
+ params: { productModelId },
+ });
+};
+
diff --git a/src/api/inventoryManagement/stockOut.js b/src/api/inventoryManagement/stockOut.js
index 3d260b3..004ba99 100644
--- a/src/api/inventoryManagement/stockOut.js
+++ b/src/api/inventoryManagement/stockOut.js
@@ -17,3 +17,21 @@
data: ids,
});
}
+
+//鍒犻櫎寰呭鎵瑰嚭搴撲俊鎭�
+export const delPendingStockOut = (ids) => {
+ return request({
+ url: "/stockOutRecord/pending",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 鎵归噺瀹℃壒鍑哄簱璁板綍锛坅pprovalStatus: approved/rejected锛�
+export const batchApproveStockOutRecords = (data) => {
+ return request({
+ url: "/stockOutRecord/approve",
+ method: "post",
+ data,
+ });
+}
diff --git a/src/api/inventoryManagement/stockUninventory.js b/src/api/inventoryManagement/stockUninventory.js
index 73907c7..a80474e 100644
--- a/src/api/inventoryManagement/stockUninventory.js
+++ b/src/api/inventoryManagement/stockUninventory.js
@@ -26,6 +26,24 @@
});
};
+// 鏂板鍏ュ簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�
+export const addUnqualifiedStockInRecordOnly = (params) => {
+ return request({
+ url: "/stockUninventory/addStockInRecordOnly",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鏂板鍑哄簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�
+export const addUnqualifiedStockOutRecordOnly = (params) => {
+ return request({
+ url: "/stockUninventory/addStockOutRecordOnly",
+ method: "post",
+ data: params,
+ });
+};
+
// 鍐荤粨搴撳瓨璁板綍
export const frozenStockUninventory = (params) => {
return request({
diff --git a/src/api/personnelManagement/class.js b/src/api/personnelManagement/class.js
index f5ae299..b254c4a 100644
--- a/src/api/personnelManagement/class.js
+++ b/src/api/personnelManagement/class.js
@@ -71,6 +71,7 @@
url: "/personalShift/export",
method: "get",
params: query,
+ responseType: "blob",
});
}
@@ -114,4 +115,4 @@
method: 'get',
params: query,
})
-}
\ No newline at end of file
+}
diff --git a/src/api/procurementManagement/procurementInvoiceLedger.js b/src/api/procurementManagement/procurementInvoiceLedger.js
index 038e94f..d0716f9 100644
--- a/src/api/procurementManagement/procurementInvoiceLedger.js
+++ b/src/api/procurementManagement/procurementInvoiceLedger.js
@@ -75,14 +75,6 @@
});
}
-export function productUploadFile(data) {
- return request({
- url: "/file/uploadFile",
- method: "post",
- data: data,
- });
-}
-
// export function getProductRecordById(params) {
// return request({
// url: "/purchase/registration/getProductRecordById",
diff --git a/src/api/procurementManagement/purchase_return_order.js b/src/api/procurementManagement/purchase_return_order.js
index 33ec33e..aeb380d 100644
--- a/src/api/procurementManagement/purchase_return_order.js
+++ b/src/api/procurementManagement/purchase_return_order.js
@@ -17,4 +17,22 @@
method: "post",
data
});
+}
+
+// 鏌ョ湅璇︽儏
+// purchaseReturnOrders/selectById/xxx
+export function getPurchaseReturnOrderDetail(id) {
+ return request({
+ url: "/purchaseReturnOrders/selectById/" + id,
+ method: "get",
+ });
+}
+
+// 閲囪喘閫�璐у崟鍒犻櫎
+// POST purchaseReturnOrders/deleteById/xxx
+export function deletePurchaseReturnOrder(id) {
+ return request({
+ url: "/purchaseReturnOrders/deleteById/" + id,
+ method: "post",
+ });
}
\ No newline at end of file
diff --git a/src/api/productionManagement/processRoute.js b/src/api/productionManagement/processRoute.js
index c13b2fc..a0cb6c8 100644
--- a/src/api/productionManagement/processRoute.js
+++ b/src/api/productionManagement/processRoute.js
@@ -4,7 +4,7 @@
// 鍒嗛〉鏌ヨ
export function listPage(query) {
return request({
- url: "/processRoute/page",
+ url: "/technologyRouting/page",
method: "get",
params: query,
});
@@ -12,31 +12,38 @@
export function add(data) {
return request({
- url: "/processRoute",
+ url: "/technologyRouting/addTechRoute",
method: "post",
data: data,
});
}
+// export function del(ids) {
+// return request({
+// url: "/processRoute/" + ids,
+// method: "delete",
+// });
+// }
export function del(ids) {
return request({
- url: '/processRoute/' + ids,
- method: 'delete',
- })
+ url: "/technologyRouting/delete",
+ method: "delete",
+ data: ids,
+ });
}
export function update(data) {
return request({
- url: '/processRoute',
- method: 'put',
+ url: "/technologyRouting/editTechRoute",
+ method: "put",
data: data,
- })
+ });
}
// 鑾峰彇璇︽儏
export function getById(id) {
return request({
url: `/processRoute/${id}`,
- method: 'get',
- })
-}
\ No newline at end of file
+ method: "get",
+ });
+}
diff --git a/src/api/productionManagement/processRouteFile.js b/src/api/productionManagement/processRouteFile.js
new file mode 100644
index 0000000..e3a9fb4
--- /dev/null
+++ b/src/api/productionManagement/processRouteFile.js
@@ -0,0 +1,28 @@
+import request from "@/utils/request";
+
+// 闄勪欢鍒楄〃
+export function listProcessRouteFiles(query) {
+ return request({
+ url: "/technologyRoutingFile/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板闄勪欢
+export function addProcessRouteFile(data) {
+ return request({
+ url: "/technologyRoutingFile/add",
+ method: "post",
+ data,
+ });
+}
+
+// 鍒犻櫎闄勪欢
+export function delProcessRouteFile(ids) {
+ return request({
+ url: "/technologyRoutingFile/del",
+ method: "delete",
+ data: ids,
+ });
+}
diff --git a/src/api/productionManagement/processRouteItem.js b/src/api/productionManagement/processRouteItem.js
index 9e81406..3bf4a6e 100644
--- a/src/api/productionManagement/processRouteItem.js
+++ b/src/api/productionManagement/processRouteItem.js
@@ -4,7 +4,7 @@
// 鍒楄〃鏌ヨ
export function findProcessRouteItemList(query) {
return request({
- url: "/processRouteItem/list",
+ url: "/technologyRoutingOperation/list",
method: "get",
params: query,
});
@@ -12,8 +12,15 @@
export function addOrUpdateProcessRouteItem(data) {
return request({
- url: "/processRouteItem",
+ url: "/technologyRoutingOperation/add",
method: "post",
+ data: data,
+ });
+}
+export function addOrUpdateProcessRouteItem1(data) {
+ return request({
+ url: "/technologyRoutingOperation",
+ method: "put",
data: data,
});
}
@@ -21,7 +28,7 @@
// 鎺掑簭鎺ュ彛
export function sortProcessRouteItem(data) {
return request({
- url: "/processRouteItem/sort",
+ url: "/technologyRoutingOperation/sort",
method: "post",
data: data,
});
@@ -32,7 +39,54 @@
// 灏唅d鏁扮粍杞崲涓洪�楀彿鍒嗛殧鐨勫瓧绗︿覆锛屾嫾鎺ュ埌URL鍚庨潰
const idsStr = Array.isArray(ids) ? ids.join(",") : ids;
return request({
- url: `/processRouteItem/batchDelete/${idsStr}`,
+ url: `/technologyRoutingOperation/${idsStr}`,
method: "delete",
});
}
+// 鑾峰彇宸ュ簭鍙傛暟鍒楄〃
+export function getProcessParamList(query) {
+ return request({
+ url: `/technologyRoutingOperationParam/list`,
+ method: "get",
+ params: query,
+ });
+}
+// 宸ヨ壓璺嚎鍙傛暟鏂板
+export function addProcessRouteItemParam(data) {
+ return request({
+ url: "/technologyRoutingOperationParam/add",
+ method: "post",
+ data: data,
+ });
+}
+// 宸ヨ壓璺嚎鍙傛暟淇敼
+export function editProcessRouteItemParam(data) {
+ return request({
+ url: "/technologyRoutingOperationParam",
+ method: "put",
+ data: data,
+ });
+}
+// 宸ヨ壓璺嚎鍙傛暟鍒犻櫎
+export function delProcessRouteItemParam(id) {
+ return request({
+ url: `/technologyRoutingOperationParam/${id}`,
+ method: "delete",
+ });
+}
+// 鎸夊伐鑹鸿矾绾垮伐搴忓悓姝ュ伐搴忓弬鏁�
+export function syncProcessParamItem(data) {
+ return request({
+ url: "/technologyRoutingOperationParam/sync",
+ method: "post",
+ data: data,
+ });
+}
+// 鎸夊伐鑹鸿矾绾垮伐搴忓悓姝ュ伐搴忓弬鏁�-鐢熶骇璁㈠崟
+export function syncProcessParamItemOrder(data) {
+ return request({
+ url: "/productionOrderRoutingOperationParam/sync",
+ method: "post",
+ data: data,
+ });
+}
diff --git a/src/api/productionManagement/productBom.js b/src/api/productionManagement/productBom.js
index 2badea8..517208b 100644
--- a/src/api/productionManagement/productBom.js
+++ b/src/api/productionManagement/productBom.js
@@ -4,7 +4,7 @@
// 鍒嗛〉鏌ヨ
export function listPage(query) {
return request({
- url: "/productBom/listPage",
+ url: "/technologyBom/listPage",
method: "get",
params: query,
});
@@ -13,16 +13,24 @@
// 鏂板
export function add(data) {
return request({
- url: "/productBom/add",
+ url: "/technologyBom/add",
method: "post",
data: data,
});
}
+// 澶嶅埗
+export function copy(data) {
+ return request({
+ url: "/technologyBom/copy",
+ method: "post",
+ data: data,
+ });
+}
// 淇敼
export function update(data) {
return request({
- url: "/productBom/update",
+ url: "/technologyBom/update",
method: "put",
data: data,
});
@@ -31,7 +39,7 @@
// 鎵归噺鍒犻櫎
export function batchDelete(ids) {
return request({
- url: "/productBom/batchDelete",
+ url: "/technologyBom/batchDelete",
method: "delete",
data: ids,
});
@@ -40,7 +48,7 @@
// 鏍规嵁浜у搧鍨嬪彿ID鏌ヨBOM
export function getByModel(productModelId) {
return request({
- url: "/productBom/getByModel",
+ url: "/technologyBom/getByModel",
method: "get",
params: { productModelId },
});
@@ -49,7 +57,7 @@
// 瀵煎嚭BOM
export function exportBom(bomId) {
return request({
- url: "/productBom/exportBom",
+ url: "/technologyBom/exportBom",
method: "post",
params: { bomId },
responseType: "blob",
@@ -59,8 +67,8 @@
// 涓嬭浇妯℃澘
export function downloadTemplate() {
return request({
- url: "/productBom/downloadTemplate",
+ url: "/technologyBom/downloadTemplate",
method: "get",
responseType: "blob",
});
-}
\ No newline at end of file
+}
diff --git a/src/api/productionManagement/productProcessRoute.js b/src/api/productionManagement/productProcessRoute.js
index e8d5da5..d75c239 100644
--- a/src/api/productionManagement/productProcessRoute.js
+++ b/src/api/productionManagement/productProcessRoute.js
@@ -4,7 +4,7 @@
// 鍒楄〃鏌ヨ
export function findProductProcessRouteItemList(query) {
return request({
- url: "/productProcessRoute/list",
+ url: "/productionOrderRouting/list",
method: "get",
params: query,
});
@@ -12,7 +12,7 @@
export function addOrUpdateProductProcessRouteItem(data) {
return request({
- url: "/productProcessRoute/updateRouteItem",
+ url: "/productionOrderRouting/updateRouteItem",
method: "post",
data: data,
});
@@ -21,7 +21,7 @@
// 鐢熶骇璁㈠崟涓嬶細鏂板宸ヨ壓璺嚎椤圭洰
export function addRouteItem(data) {
return request({
- url: "/productProcessRoute/addRouteItem",
+ url: "/productionOrderRouting/addRouteItem",
method: "post",
data,
});
@@ -30,7 +30,7 @@
// 鑾峰彇鐢熶骇璁㈠崟鍏宠仈鐨勫伐鑹鸿矾绾夸富淇℃伅
export function listMain(orderId) {
return request({
- url: "/productProcessRoute/listMain",
+ url: "/productionOrderRouting/listMain",
method: "get",
params: { orderId },
});
@@ -39,7 +39,7 @@
// 鍒犻櫎宸ヨ壓璺嚎椤圭洰锛堣矾鐢卞悗鎷兼帴 id锛�
export function deleteRouteItem(id) {
return request({
- url: `/productProcessRoute/deleteRouteItem/${id}`,
+ url: `/productionOrderRouting/deleteRouteItem/${id}`,
method: "delete",
});
}
@@ -47,8 +47,39 @@
// 鐢熶骇璁㈠崟涓嬶細鎺掑簭宸ヨ壓璺嚎椤圭洰
export function sortRouteItem(data) {
return request({
- url: "/productProcessRoute/sortRouteItem",
+ url: "/productionOrderRouting/sortRouteItem",
method: "post",
data,
});
}
+// 鑾峰彇宸ュ簭鍙傛暟鍒楄〃-鐢熶骇璁㈠崟
+export function findProcessParamListOrder(query) {
+ return request({
+ url: `/productionOrderRoutingOperationParam/list`,
+ method: "get",
+ params: query,
+ });
+}
+// 宸ヨ壓璺嚎鍙傛暟鏂板-鐢熶骇璁㈠崟
+export function addProcessRouteItemParamOrder(data) {
+ return request({
+ url: "/productionOrderRoutingOperationParam",
+ method: "post",
+ data: data,
+ });
+}
+// 宸ヨ壓璺嚎鍙傛暟淇敼-鐢熶骇璁㈠崟
+export function editProcessRouteItemParamOrder(data) {
+ return request({
+ url: "/productionOrderRoutingOperationParam",
+ method: "put",
+ data: data,
+ });
+}
+// 宸ヨ壓璺嚎鍙傛暟鍒犻櫎-鐢熶骇璁㈠崟
+export function delProcessRouteItemParamOrder(id) {
+ return request({
+ url: `/productionOrderRoutingOperationParam/${id}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/productionManagement/productStructure.js b/src/api/productionManagement/productStructure.js
index e69e14a..c55c93e 100644
--- a/src/api/productionManagement/productStructure.js
+++ b/src/api/productionManagement/productStructure.js
@@ -4,14 +4,43 @@
// 鍒嗛〉鏌ヨ
export function queryList(id) {
return request({
- url: "/productStructure/listBybomId/" + id,
+ url: "/technologyBomStructure/listByBomId/" + id,
method: "get",
});
}
-
+// 鍒嗛〉鏌ヨ-浜у搧璁㈠崟
+export function queryList2(id) {
+ return request({
+ url: "/productionBomStructure/listByBomId/" + id,
+ method: "get",
+ });
+}
export function add(data) {
return request({
- url: "/productStructure",
+ url: "/productStructure/" + data.bomId,
+ method: "post",
+ data: data.children,
+ });
+}
+
+export function addBomDetail(data) {
+ return request({
+ url: "/technologyBomStructure",
+ method: "post",
+ data: data,
+ });
+}
+// 鍒嗛〉鏌ヨ-浜у搧璁㈠崟
+// export function queryList2(id) {
+// return request({
+// url: "/productionOrderStructure/getBomStructs/" + id,
+// method: "get",
+// });
+// }
+
+export function add2(data) {
+ return request({
+ url: "/productionBomStructure/addOrUpdateBomStructs",
method: "post",
data: data,
});
diff --git a/src/api/productionManagement/productionCosting.js b/src/api/productionManagement/productionCosting.js
index ebf7daa..81e6b1b 100644
--- a/src/api/productionManagement/productionCosting.js
+++ b/src/api/productionManagement/productionCosting.js
@@ -14,7 +14,7 @@
// salesLedger/productionAccounting/page
export function salesLedgerProductionAccountingList(query) {
return request({
- url: "/salesLedger/productionAccounting/page",
+ url: "/productionAccount/listPage",
method: "get",
params: query,
});
@@ -24,7 +24,7 @@
//
export function salesLedgerProductionAccountingListProductionDetails(query) {
return request({
- url: "/salesLedger/productionAccounting/listProductionDetails",
+ url: "/productionAccount/listProductionDetails",
method: "get",
params: query,
});
diff --git a/src/api/productionManagement/productionOrder.js b/src/api/productionManagement/productionOrder.js
index 6c8dbe2..b84a188 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -12,7 +12,7 @@
export function productOrderListPage(query) {
return request({
- url: "/productOrder/page",
+ url: "/productionOrder/page",
method: "get",
params: query,
});
@@ -30,7 +30,7 @@
// 鐢熶骇璁㈠崟-缁戝畾宸ヨ壓璺嚎
export function bindingRoute(data) {
return request({
- url: "/productOrder/bindingRoute",
+ url: "/productionOrder/bindingRoute",
method: "post",
data,
});
@@ -39,7 +39,16 @@
// 鐢熶骇璁㈠崟-鏂板
export function addProductOrder(data) {
return request({
- url: "/productOrder/addProductOrder",
+ url: "/productionOrder/addOrder",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鐢熶骇璁㈠崟-淇敼
+export function updateProductOrder(data) {
+ return request({
+ url: "/productionOrder/updateOrder",
method: "post",
data: data,
});
@@ -47,8 +56,9 @@
export function delProductOrder(ids) {
return request({
- url: `/productOrder/${ids}`,
+ url: `/productionOrder/delete`,
method: "delete",
+ data: ids,
});
}
@@ -58,6 +68,92 @@
url: "/productOrder/listProcessBom",
method: "get",
params: query,
+ });
+}
+
+// 鐢熶骇璁㈠崟-棰嗘枡鍙拌处鍒楄〃
+export function listMaterialPickingLedger(query) {
+ return request({
+ url: "/productOrderMaterial/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鐢熶骇璁㈠崟-淇濆瓨棰嗘枡鍙拌处
+// export function saveMaterialPickingLedger(data) {
+// return request({
+// url: "/productOrderMaterial/save",
+// method: "post",
+// data,
+// });
+// }
+export function saveMaterialPickingLedger(data) {
+ return request({
+ url: "/productionOrderPick/savePick",
+ method: "post",
+ data,
+ });
+}
+export function updateMaterialPickingLedger(data) {
+ return request({
+ url: "/productionOrderPick/updatePick",
+ method: "post",
+ data,
+ });
+}
+
+// 鐢熶骇璁㈠崟婧簮璇︽儏
+export function getOrderDetail(npsNo) {
+ return request({
+ url: "/productionOrder/ordeDetail",
+ method: "get",
+ params: { npsNo },
+ });
+}
+// 鐢熶骇璁㈠崟-棰嗘枡璇︽儏鍒楄〃
+// export function listMaterialPickingDetail(query) {
+// return request({
+// url: "/productOrderMaterial/detailList",
+// method: "get",
+// params: query,
+// });
+// }
+export function listMaterialPickingBom(productionOrderId) {
+ return request({
+ url: "/productionOrder/pick/" + productionOrderId,
+ method: "get",
+ });
+}
+export function listMaterialPickingDetail(productionOrderId) {
+ return request({
+ url: "/productionOrderPick/detail/" + productionOrderId,
+ method: "get",
+ });
+}
+// 鐢熶骇璁㈠崟-琛ユ枡璁板綍鍒楄〃
+export function listMaterialSupplementRecord(query) {
+ return request({
+ url: "/productionOrderPickRecord/feeding",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鐢熶骇璁㈠崟-鑾峰彇鏉ユ簮鏁版嵁
+export function getProductOrderSource(id) {
+ return request({
+ url: `/productionOrder/source/${id}`,
+ method: "get",
+ });
+}
+
+// 鐢熶骇璁㈠崟-閫�鏂欑‘璁�
+export function confirmMaterialReturn(data) {
+ return request({
+ url: "/productOrderMaterial/confirmReturn",
+ method: "post",
+ data,
});
}
@@ -129,4 +225,4 @@
method: "post",
data: data,
});
-}
\ No newline at end of file
+}
diff --git a/src/api/productionManagement/productionProcess.js b/src/api/productionManagement/productionProcess.js
index d3a453c..783f584 100644
--- a/src/api/productionManagement/productionProcess.js
+++ b/src/api/productionManagement/productionProcess.js
@@ -4,7 +4,7 @@
// 鍒嗛〉鏌ヨ
export function listPage(query) {
return request({
- url: "/productProcess/listPage",
+ url: "/technologyOperation/listPage",
method: "get",
params: query,
});
@@ -12,15 +12,23 @@
export function processList(query) {
return request({
- url: "/productProcess/list",
+ url: "/technologyOperation/listPage",
method: "get",
params: query,
});
}
+// 宸ュ簭鏌ヨ
+export function list(query) {
+ return request({
+ url: "/technologyOperation/listPage",
+ method: "get",
+ params: query,
+ });
+}
export function add(data) {
return request({
- url: "/productProcess",
+ url: "/technologyOperation/add",
method: "post",
data: data,
});
@@ -28,32 +36,24 @@
export function del(data) {
return request({
- url: '/productProcess/batchDelete',
- method: 'delete',
+ url: "/technologyOperation/batchDelete",
+ method: "delete",
data: data,
- })
+ });
}
export function update(data) {
return request({
- url: '/productProcess/update',
- method: 'put',
+ url: "/technologyOperation/update",
+ method: "put",
data: data,
- })
-}
-
-// 宸ュ簭鏌ヨ
-export function list() {
- return request({
- url: "/productProcess/list",
- method: "get",
- });
+ });
}
// 瀵煎叆鏁版嵁
export function importData(data) {
return request({
- url: "/productProcess/importData",
+ url: "/technologyOperation/importData",
method: "post",
data: data,
});
@@ -62,8 +62,43 @@
// 涓嬭浇妯℃澘
export function downloadTemplate() {
return request({
- url: "/productProcess/downloadTemplate",
+ url: "/technologyOperation/downloadTemplate",
method: "post",
responseType: "blob",
});
-}
\ No newline at end of file
+}
+
+// 鑾峰彇宸ュ簭鍙傛暟鍒楄〃
+export function getProcessParamList(params) {
+ return request({
+ url: `/technologyOperationParam/list`,
+ method: "get",
+ params,
+ });
+}
+
+// 娣诲姞宸ュ簭鍙傛暟
+export function addProcessParam(data) {
+ return request({
+ url: "/technologyOperationParam/",
+ method: "post",
+ data: data,
+ });
+}
+
+// 缂栬緫宸ュ簭鍙傛暟
+export function editProcessParam(data) {
+ return request({
+ url: "/technologyOperationParam/",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鍒犻櫎宸ュ簭鍙傛暟
+export function deleteProcessParam(id) {
+ return request({
+ url: `/technologyOperationParam/batchDelete/${id}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/productionManagement/workOrder.js b/src/api/productionManagement/workOrder.js
index 7e8bd86..b3050c9 100644
--- a/src/api/productionManagement/workOrder.js
+++ b/src/api/productionManagement/workOrder.js
@@ -2,7 +2,7 @@
export function productWorkOrderPage(query) {
return request({
- url: "/productWorkOrder/page",
+ url: "/productionOperationTask/page",
method: "get",
params: query,
});
@@ -10,7 +10,7 @@
export function updateProductWorkOrder(data) {
return request({
- url: "/productWorkOrder/updateProductWorkOrder",
+ url: "/productionOperationTask/updateProductWorkOrder",
method: "post",
data: data,
});
@@ -24,12 +24,74 @@
});
}
+export function assignProductWorkOrder(data) {
+ return request({
+ url: "/productionOperationTask/assign",
+ method: "post",
+ data: data,
+ });
+}
+
// 涓嬭浇宸ュ崟娴佽浆鍗★紙杩斿洖鏂囦欢娴侊級
export function downProductWorkOrder(id) {
return request({
- url: "/productWorkOrder/down",
+ url: "/productionOperationTask/down",
method: "post",
data: { id },
responseType: "blob",
});
}
+
+// 宸ュ崟-褰撳墠宸ュ簭鐗╂枡鍙拌处
+export function listWorkOrderMaterialLedger(query) {
+ return request({
+ url: "/productOrderMaterial/reportMaterials",
+ method: "get",
+ params: query,
+ });
+}
+
+// 宸ュ崟-琛ユ枡
+export function addWorkOrderMaterialSupplement(data) {
+ return request({
+ url: "/productionOperationTask/material/supplement",
+ method: "post",
+ data,
+ });
+}
+
+// 宸ュ崟-閫�鏂�
+export function addWorkOrderMaterialReturn(data) {
+ return request({
+ url: "/productionOperationTask/material/return",
+ method: "post",
+ data,
+ });
+}
+
+// 宸ュ崟-琛ユ枡璁板綍
+export function listWorkOrderMaterialSupplementRecord(query) {
+ return request({
+ url: "/productionOperationTask/material/supplementRecord",
+ method: "get",
+ params: query,
+ });
+}
+
+// 宸ュ崟-棰嗙敤锛堟彁浜ゅ疄闄呴鐢ㄦ暟閲忥級
+export function pickWorkOrderMaterial(data) {
+ return request({
+ url: "/productionOperationTask/material/pick",
+ method: "post",
+ data,
+ });
+}
+
+// 鑾峰彇宸ュ簭缁熻鏁版嵁
+export function getOperationStatistics(query) {
+ return request({
+ url: "/productionOperationTask/getOperation",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/api/productionPlan/productionPlan.js b/src/api/productionPlan/productionPlan.js
new file mode 100644
index 0000000..5bcff27
--- /dev/null
+++ b/src/api/productionPlan/productionPlan.js
@@ -0,0 +1,79 @@
+// 鐢熶骇璁㈠崟椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+export function productionPlanListPage(query) {
+ return request({
+ url: "/productionPlan/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鎷夊彇鏁版嵁
+export function loadProdData(query) {
+ return request({
+ url: "/productionPlan/loadProdData",
+ method: "get",
+ params: query,
+ });
+}
+
+export function summaryByProductType(query) {
+ return request({
+ url: "/productionPlan/summaryByProductType",
+ method: "get",
+ params: query,
+ });
+}
+
+// 瀵煎嚭鐢熶骇璁″垝
+export function exportProductionPlan(bomId) {
+ return request({
+ url: "/productionPlan/export",
+ method: "post",
+ params: { bomId },
+ responseType: "blob",
+ });
+}
+
+// 鐢熶骇璁″垝-鏂板淇敼
+export function productionPlanAdd(query) {
+ return request({
+ url: "/productionPlan/addProductionPlan",
+ method: "post",
+ data: query,
+ });
+}
+export function productionPlanUpdate(query) {
+ return request({
+ url: "/productionPlan/updateProductionPlan",
+ method: "put",
+ data: query,
+ });
+}
+
+// 鐢熶骇璁″垝-鍒犻櫎
+export function productionPlanDelete(data) {
+ return request({
+ url: "/productionPlan/deleteProductionPlan",
+ method: "delete",
+ data,
+ });
+}
+// 鍚堝苟涓嬪彂
+export function productionPlanCombine(query) {
+ return request({
+ url: "/productionPlan/combine",
+ method: "post",
+ data: query,
+ });
+}
+
+// 杩借釜杩涘害
+export function trackProgressByNo(query) {
+ return request({
+ url: "/track/trackProgressByNo",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/api/salesManagement/deliveryLedger.js b/src/api/salesManagement/deliveryLedger.js
index 4be5829..b85582c 100644
--- a/src/api/salesManagement/deliveryLedger.js
+++ b/src/api/salesManagement/deliveryLedger.js
@@ -11,6 +11,13 @@
}
// 淇敼鍙戣揣鍙拌处
+export function getDeliveryDetail(id) {
+ return request({
+ url: `/shippingInfo/getDateil/${id}`,
+ method: "get",
+ });
+}
+
export function addOrUpdateDeliveryLedger(query) {
return request({
url: "/shippingInfo/update",
diff --git a/src/api/system/appVersion.js b/src/api/system/appVersion.js
new file mode 100644
index 0000000..fc7bdc5
--- /dev/null
+++ b/src/api/system/appVersion.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+// 鏌ヨ APP 鐗堟湰鍒嗛〉鍒楄〃
+export function listAppVersion(params) {
+ return request({
+ url: "/app/getAllVersion",
+ method: "get",
+ params,
+ });
+}
+
+// 涓婁紶 APK
+export function add(data) {
+ return request({
+ url: "/app/add",
+ method: "post",
+ data
+ });
+}
diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss
index 8c741af..f296f9d 100644
--- a/src/assets/styles/element-ui.scss
+++ b/src/assets/styles/element-ui.scss
@@ -47,50 +47,57 @@
}
// to fixed https://github.com/ElemeFE/element/issues/2461
-.el-dialog {
- transform: none;
- left: 0;
- position: relative;
- margin: 0 auto;
- border-radius: 8px;
- padding: 0 !important;
-}
-.el-dialog__header {
- background: #f5f6f7;
- padding: 12px 16px;
- border-radius: 8px 8px 0 0;
-}
-.el-dialog__title {
- font-weight: 400;
- font-size: 16px;
- color: #2e3033;
-}
-.el-dialog__body {
- padding: 16px 40px 0 40px;
- max-height: 74vh;
- overflow-y: auto;
-}
-.el-dialog__footer {
- text-align: center;
- padding: 16px;
-}
-.el-message-box {
- padding: 0 !important;
- border-radius: 8px;
-}
-.el-message-box__header {
- background: #f5f6f7;
- padding: 12px 16px;
- border-radius: 8px 8px 0 0;
-}
-.el-message-box__title {
- font-weight: 400;
- font-size: 16px;
- color: #2e3033;
-}
-.el-message-box__content {
- padding: 16px 40px 0 40px;
-}
+.el-dialog {
+ transform: none;
+ left: 0;
+ position: relative;
+ margin: 0 auto;
+ border-radius: 24px;
+ padding: 0 !important;
+ border: 1px solid var(--surface-border);
+ box-shadow: var(--shadow-md);
+ background: rgba(255, 255, 255, 0.96);
+}
+.el-dialog__header {
+ background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88));
+ padding: 18px 24px 14px;
+ border-bottom: 1px solid var(--surface-border);
+ border-radius: 24px 24px 0 0;
+}
+.el-dialog__title {
+ font-weight: 600;
+ font-size: 17px;
+ color: var(--text-primary);
+}
+.el-dialog__body {
+ padding: 24px 24px 0;
+ max-height: 74vh;
+ overflow-y: auto;
+}
+.el-dialog__footer {
+ text-align: center;
+ padding: 18px 24px 24px;
+}
+.el-message-box {
+ padding: 0 !important;
+ border-radius: 22px;
+ border: 1px solid var(--surface-border);
+ box-shadow: var(--shadow-md);
+}
+.el-message-box__header {
+ background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88));
+ padding: 18px 24px 14px;
+ border-bottom: 1px solid var(--surface-border);
+ border-radius: 22px 22px 0 0;
+}
+.el-message-box__title {
+ font-weight: 600;
+ font-size: 17px;
+ color: var(--text-primary);
+}
+.el-message-box__content {
+ padding: 24px 24px 0;
+}
.el-message-box__container {
justify-content: center;
}
@@ -105,12 +112,12 @@
margin-right: 12px;
}
}
-.el-table__expanded-cell {
- padding: 0 !important;
- .el-table__header-wrapper {
- background-color: #f5f8ff !important;
- }
-}
+.el-table__expanded-cell {
+ padding: 0 !important;
+ .el-table__header-wrapper {
+ background-color: var(--surface-soft) !important;
+ }
+}
// refine element ui upload
.upload-container {
@@ -149,6 +156,86 @@
display: none;
}
-.el-dropdown .el-dropdown-link {
- color: var(--el-color-primary) !important;
-}
+.el-dropdown .el-dropdown-link {
+ color: var(--el-color-primary) !important;
+}
+
+.el-button {
+ border-radius: 12px;
+ font-weight: 600;
+ box-shadow: none !important;
+}
+
+.el-button--primary {
+ --el-button-bg-color: var(--el-color-primary);
+ --el-button-border-color: var(--el-color-primary);
+ --el-button-hover-bg-color: var(--el-color-primary-light-3);
+ --el-button-hover-border-color: var(--el-color-primary-light-3);
+ --el-button-active-bg-color: var(--el-color-primary-dark-2);
+ --el-button-active-border-color: var(--el-color-primary-dark-2);
+}
+
+.el-input__wrapper,
+.el-textarea__inner,
+.el-select__wrapper,
+.el-date-editor.el-input__wrapper,
+.el-date-editor .el-input__wrapper {
+ border-radius: 12px;
+ box-shadow: 0 0 0 1px rgba(216, 225, 219, 0.92) inset !important;
+ background: rgba(255, 255, 255, 0.9);
+}
+
+.el-input__wrapper.is-focus,
+.el-select__wrapper.is-focused,
+.el-textarea__inner:focus {
+ box-shadow: 0 0 0 1px rgba(0, 47, 167, 0.28) inset !important;
+}
+
+.el-card {
+ border: 1px solid var(--surface-border);
+ box-shadow: var(--shadow-sm);
+ background: rgba(255, 255, 255, 0.88);
+}
+
+.el-table {
+ --el-table-border-color: var(--surface-border);
+ --el-table-header-bg-color: var(--surface-soft);
+ --el-table-row-hover-bg-color: #f1f6f4;
+ --el-table-current-row-bg-color: #e9f0ed;
+ border-radius: 18px;
+}
+
+.el-table th.el-table__cell {
+ background: var(--surface-soft) !important;
+ color: var(--text-secondary);
+ font-weight: 600;
+}
+
+.el-table tr,
+.el-table td.el-table__cell,
+.el-table__body tr > td.el-table__cell {
+ background: var(--surface-base) !important;
+}
+
+.el-table .el-table__body tr:hover > td.el-table__cell {
+ background: var(--el-table-row-hover-bg-color) !important;
+}
+
+.el-table .el-table__body tr.current-row > td.el-table__cell {
+ background: var(--el-table-current-row-bg-color) !important;
+}
+
+.el-table .el-table__footer-wrapper {
+ border-top: 1px solid var(--surface-border);
+}
+
+.el-table .el-table__footer-wrapper tbody td.el-table__cell,
+.el-table .el-table__footer-wrapper tfoot td.el-table__cell {
+ background: var(--surface-base) !important;
+ border-top: 1px solid var(--surface-border);
+ font-weight: 600;
+}
+
+.el-pagination {
+ margin-top: 18px;
+}
diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss
index 13d26ee..f905a36 100644
--- a/src/assets/styles/index.scss
+++ b/src/assets/styles/index.scss
@@ -12,11 +12,16 @@
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
- font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+ font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
+ background:
+ radial-gradient(circle at top left, rgba(214, 226, 219, 0.8), transparent 28%),
+ linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
+ color: var(--text-primary);
}
label {
- font-weight: 700;
+ font-weight: 600;
+ color: var(--text-secondary);
}
html {
@@ -26,6 +31,12 @@
#app {
height: 100%;
+}
+
+html,
+body,
+#app {
+ background-color: var(--app-bg);
}
*,
@@ -123,7 +134,7 @@
//main-container鍏ㄥ眬鏍峰紡
.app-container {
- padding: 20px;
+ padding: 20px 24px 24px;
}
.search_form {
display: flex;
@@ -131,15 +142,17 @@
justify-content: space-between;
.search_title {
font-size: 14px;
- font-weight: 700;
- color: #333333;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ color: var(--text-secondary);
}
}
.table_list {
- height: calc(100vh - 11em);
- margin-top: 20px;
- background: #fff;
- padding: 18px
+ background: rgba(255, 255, 255, 0.88);
+ border: 1px solid var(--surface-border);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-sm);
+ padding: 18px;
}
.components-container {
margin: 30px 50px;
@@ -176,11 +189,11 @@
.link-type,
.link-type:focus {
- color: #337ab7;
+ color: var(--el-color-primary);
cursor: pointer;
&:hover {
- color: rgb(32, 160, 255);
+ color: #165e57;
}
}
@@ -193,3 +206,17 @@
margin-bottom: 10px;
}
}
+
+.app-container,
+.table_list,
+.components-container {
+ .el-card,
+ .el-dialog,
+ .el-drawer,
+ .el-table,
+ .el-descriptions,
+ .el-collapse-item__wrap,
+ .el-tabs__content {
+ border-radius: var(--radius-md);
+ }
+}
diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss
index 8534448..be7b7a7 100644
--- a/src/assets/styles/sidebar.scss
+++ b/src/assets/styles/sidebar.scss
@@ -4,7 +4,7 @@
transition: margin-left 0.28s;
margin-left: $base-sidebar-width;
position: relative;
- background: #f5f7fb;
+ background: transparent;
}
.sidebarHide {
@@ -22,8 +22,9 @@
left: 0;
z-index: 1001;
overflow: hidden;
- -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
- box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
+ padding: 12px 0 16px 16px;
+ background: transparent;
+ box-shadow: none;
// reset element-ui css
.horizontal-collapse-transition {
@@ -45,7 +46,8 @@
&.has-logo {
.el-scrollbar {
- height: calc(100% - 50px);
+ height: calc(100% - 72px);
+ margin-top: 10px;
}
}
@@ -63,11 +65,16 @@
margin-right: 16px;
}
- .el-menu {
- border: none;
- height: 100%;
- width: 100% !important;
- }
+ .el-menu {
+ border: none;
+ height: 100%;
+ width: 100% !important;
+ padding: 10px 8px 18px;
+ border-radius: 22px;
+ background: var(--menu-surface);
+ backdrop-filter: blur(18px);
+ box-shadow: var(--shadow-sm);
+ }
.el-menu-item,
.menu-title {
@@ -80,80 +87,144 @@
display: inline-block !important;
}
- // menu hover
- .sub-menu-title-noDropdown,
- .el-sub-menu__title {
- &:hover {
- background-color: rgba(212, 221, 255, 0.8) !important;
- }
- }
+ // menu hover
+ .submenu-title-noDropdown,
+ .el-sub-menu__title {
+ &:hover {
+ background-color: var(--menu-hover) !important;
+ border-radius: 14px;
+ }
+ }
& .theme-light .is-active > .el-sub-menu__title {
- color: #fff !important;
+ color: var(--current-color) !important;
}
- & .nest-menu .el-sub-menu > .el-sub-menu__title,
- & .el-sub-menu .el-menu-item {
- min-width: $base-sidebar-width !important;
+ & .nest-menu .el-sub-menu > .el-sub-menu__title,
+ & .el-sub-menu .el-menu-item {
+ min-width: 0 !important;
+ margin: 0 12px 6px;
+ width: calc(100% - 24px);
+ padding-left: 8px !important;
+ padding-right: 8px !important;
+ box-sizing: border-box;
+
+ &:hover {
+ background-color: var(--menu-hover) !important;
+ }
+ &.is-active {
+ background-color: var(--menu-active-bg) !important;
+ border-radius: 14px;
+ }
+ }
- &:hover {
- background-color: rgba(212, 221, 255, 0.8) !important;
- }
- &.is-active {
- background-color: #fff !important;
- }
- }
+ & .theme-light .nest-menu .el-sub-menu > .el-sub-menu__title,
+ & .theme-light .el-sub-menu .el-menu-item {
+ //background-color: transparent;
+
+ &:hover {
+ background-color: var(--menu-hover) !important;
+ border-radius: 14px;
+ }
+ }
+ }
- & .theme-light .nest-menu .el-sub-menu > .el-sub-menu__title,
- & .theme-light .el-sub-menu .el-menu-item {
- //background-color: transparent;
+ .hideSidebar {
+ .sidebar-container {
+ width: 68px !important;
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .main-container {
+ margin-left: 84px;
+ }
- &:hover {
- background-color: rgba(212, 221, 255, 0.8) !important;
- }
- }
- }
-
- .hideSidebar {
- .sidebar-container {
- width: 54px !important;
- }
-
- .main-container {
- margin-left: 54px;
- }
-
- .sub-menu-title-noDropdown {
- padding: 0 !important;
- position: relative;
-
- .el-tooltip {
- padding: 0 !important;
-
- .svg-icon {
- margin-left: 20px;
- }
- }
- }
- .el-sub-menu {
- overflow: hidden;
-
- & > .el-sub-menu__title {
- padding: 0 !important;
-
- .svg-icon {
- margin-left: 20px;
- }
- }
- }
-
- .el-menu--collapse {
- .el-sub-menu {
- & > .el-sub-menu__title {
- & > span {
- height: 0;
- width: 0;
- overflow: hidden;
- visibility: hidden;
+ .submenu-title-noDropdown {
+ padding: 0 !important;
+ position: relative;
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+
+ .svg-icon {
+ margin-right: 0;
+ }
+
+ .el-tooltip {
+ padding: 0 !important;
+ display: inline-flex !important;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+
+ .svg-icon {
+ margin-left: 0;
+ }
+ }
+
+ .el-menu-tooltip__trigger {
+ width: 100%;
+ display: inline-flex !important;
+ align-items: center;
+ justify-content: center;
+
+ .svg-icon {
+ width: 18px;
+ height: 18px;
+ margin-right: 0;
+ flex-shrink: 0;
+ }
+ }
+ }
+ .el-sub-menu {
+ overflow: hidden;
+
+ & > .el-sub-menu__title {
+ padding: 0 !important;
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+
+ .svg-icon {
+ margin-left: 0;
+ margin-right: 0;
+ }
+ }
+ }
+
+ .el-menu--collapse {
+ width: 100% !important;
+ padding: 10px 6px 18px;
+
+ > .el-menu-item,
+ .el-sub-menu {
+ & > .el-sub-menu__title,
+ &.el-menu-item {
+ margin: 0 0 6px;
+ width: 100%;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ box-sizing: border-box;
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+
+ .svg-icon {
+ width: 18px;
+ height: 18px;
+ margin-right: 0;
+ flex-shrink: 0;
+ }
+
+ &:hover {
+ border-radius: 14px;
+ }
+
+ & > span {
+ height: 0;
+ width: 0;
+ overflow: hidden;
+ visibility: hidden;
display: inline-block;
}
& > i {
@@ -162,11 +233,11 @@
overflow: hidden;
visibility: hidden;
display: inline-block;
- }
- }
- }
- }
- }
+ }
+ }
+ }
+ }
+ }
.el-menu--collapse .el-menu .el-sub-menu {
min-width: $base-sidebar-width !important;
@@ -208,24 +279,36 @@
}
}
- .nest-menu .el-sub-menu > .el-sub-menu__title,
- .el-menu-item {
- &:hover {
- // you can use $sub-menuHover
- background-color: rgba(212, 221, 255, 0.56) !important;
- }
- &.is-active {
- background-color: rgba(212, 221, 255, 0.56) !important;
- }
- }
+ .nest-menu .el-sub-menu > .el-sub-menu__title,
+ .el-menu-item {
+ min-width: 0 !important;
+ margin: 0 12px 6px;
+ width: calc(100% - 24px);
+ padding-left: 8px !important;
+ padding-right: 8px !important;
+ box-sizing: border-box;
+
+ &:hover {
+ // you can use $sub-menuHover
+ background-color: var(--menu-hover) !important;
+ }
+ &.is-active {
+ background-color: var(--menu-active-bg) !important;
+ border-radius: 14px;
+ }
+ }
// the scroll bar appears when the sub-menu is too long
> .el-menu--popup {
max-height: 100vh;
overflow-y: auto;
+ padding: 8px;
+ border-radius: 18px;
+ border: 1px solid var(--surface-border);
+ box-shadow: var(--shadow-md);
&::-webkit-scrollbar-track-piece {
- background: #d3dce6;
+ background: #dfe7e1;
}
&::-webkit-scrollbar {
@@ -233,7 +316,7 @@
}
&::-webkit-scrollbar-thumb {
- background: #99a9bf;
+ background: #9aa79e;
border-radius: 20px;
}
}
diff --git a/src/assets/styles/variables.module.scss b/src/assets/styles/variables.module.scss
index 3194051..55b3822 100644
--- a/src/assets/styles/variables.module.scss
+++ b/src/assets/styles/variables.module.scss
@@ -1,221 +1,225 @@
-// base color
-$blue: #324157;
-$light-blue: #333c46;
-$red: #c03639;
-$pink: #e65d6e;
-$green: #30b08f;
-$tiffany: #4ab7bd;
-$yellow: #fec171;
-$panGreen: #30b08f;
-
-// 榛樿涓婚鍙橀噺
-$menuText: #bfcbd9;
-$menuActiveText: #409eff;
-$menuBg: #304156;
-$menuHover: #263445;
-
-// 娴呰壊涓婚theme-light
-$menuLightBg: #002fa7;
-$menuLightHover: #f0f1f5;
-$menuLightText: #fff;
-$menuLightActiveText: #002fa7;
-
-// 鍩虹鍙橀噺
-$base-sidebar-width: 200px;
-$sideBarWidth: 200px;
-
-// 鑿滃崟鏆楄壊鍙橀噺
-$base-menu-color: #bfcbd9;
-$base-menu-color-active: #f4f4f5;
-$base-menu-background: #304156;
-$base-sub-menu-background: #1f2d3d;
-$base-sub-menu-hover: #fff;
-
-// 缁勪欢鍙橀噺
-$--color-primary: #409eff;
-$--color-success: #67c23a;
-$--color-warning: #e6a23c;
-$--color-danger: #f56c6c;
-$--color-info: #909399;
-
-:export {
- menuText: $menuText;
- menuActiveText: $menuActiveText;
- menuBg: $menuBg;
- menuHover: $menuHover;
- menuLightBg: $menuLightBg;
- menuLightHover: $menuLightHover;
- menuLightText: $menuLightText;
- menuLightActiveText: $menuLightActiveText;
- sideBarWidth: $sideBarWidth;
- // 瀵煎嚭鍩虹棰滆壊
- blue: $blue;
- lightBlue: $light-blue;
- red: $red;
- pink: $pink;
- green: $green;
- tiffany: $tiffany;
- yellow: $yellow;
- panGreen: $panGreen;
- // 瀵煎嚭缁勪欢棰滆壊
- colorPrimary: $--color-primary;
- colorSuccess: $--color-success;
- colorWarning: $--color-warning;
- colorDanger: $--color-danger;
- colorInfo: $--color-info;
-}
-
-// CSS鍙橀噺瀹氫箟
-:root {
- /* 浜壊妯″紡鍙橀噺 */
- --sidebar-bg: #{$menuBg};
- --sidebar-text: #{$menuText};
- --menu-hover: #{$menuHover};
-
- --navbar-bg: #ffffff;
- --navbar-text: #303133;
-
- /* splitpanes default-theme 鍙橀噺 */
- --splitpanes-default-bg: #ffffff;
-}
-
-// 鏆楅粦妯″紡鍙橀噺
-html.dark {
- /* 榛樿閫氱敤 */
- --el-bg-color: #141414;
- --el-bg-color-overlay: #1d1e1f;
- --el-text-color-primary: #ffffff;
- --el-text-color-regular: #d0d0d0;
- --el-border-color: #434343;
- --el-border-color-light: #434343;
-
- /* 渚ц竟鏍� */
- --sidebar-bg: #141414;
- --sidebar-text: #ffffff;
- --menu-hover: #2d2d2d;
- --menu-active-text: #{$menuActiveText};
-
- /* 椤堕儴瀵艰埅鏍� */
- --navbar-bg: #141414;
- --navbar-text: #ffffff;
- --navbar-hover: #141414;
-
- /* 鏍囩鏍� */
- --tags-bg: #141414;
- --tags-item-bg: #1d1e1f;
- --tags-item-border: #303030;
- --tags-item-text: #d0d0d0;
- --tags-item-hover: #2d2d2d;
- --tags-close-hover: #64666a;
-
- /* splitpanes 缁勪欢鏆楅粦妯″紡鍙橀噺 */
- --splitpanes-bg: #141414;
- --splitpanes-border: #303030;
- --splitpanes-splitter-bg: #1d1e1f;
- --splitpanes-splitter-hover-bg: #2d2d2d;
-
- /* blockquote 鏆楅粦妯″紡鍙橀噺 */
- --blockquote-bg: #1d1e1f;
- --blockquote-border: #303030;
- --blockquote-text: #d0d0d0;
-
- /* Cron 鏃堕棿琛ㄨ揪寮� 妯″紡鍙橀噺 */
- --cron-border: #303030;
-
- /* splitpanes default-theme 鏆楅粦妯″紡鍙橀噺 */
- --splitpanes-default-bg: #141414;
-
- /* 渚ц竟鏍忚彍鍗曡鐩� */
- .sidebar-container {
- .el-menu-item,
- .menu-title {
- color: var(--el-text-color-regular);
- }
- & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
- & .theme-dark .el-sub-menu .el-menu-item {
- background-color: var(--el-bg-color) !important;
- }
- }
-
- /* 椤堕儴鏍忔爮鑿滃崟瑕嗙洊 */
- .el-menu--horizontal {
- .el-menu-item {
- &:not(.is-disabled) {
- &:hover,
- &:focus {
- background-color: var(--navbar-hover) !important;
- }
- }
- }
- }
-
- /* 鍒嗗壊绐楁牸瑕嗙洊 */
- .splitpanes {
- background-color: var(--splitpanes-bg);
-
- .splitpanes__pane {
- background-color: var(--splitpanes-bg);
- border-color: var(--splitpanes-border);
- }
-
- .splitpanes__splitter {
- background-color: var(--splitpanes-splitter-bg);
- border-color: var(--splitpanes-border);
-
- &:hover {
- background-color: var(--splitpanes-splitter-hover-bg);
- }
-
- &:before,
- &:after {
- background-color: var(--splitpanes-border);
- }
- }
- }
-
- /* 琛ㄦ牸鏍峰紡瑕嗙洊 */
- .el-table {
- --el-table-header-bg-color: var(--el-bg-color-overlay) !important;
- --el-table-header-text-color: var(--el-text-color-regular) !important;
- --el-table-border-color: var(--el-border-color-light) !important;
- --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
-
- .el-table__header-wrapper,
- .el-table__fixed-header-wrapper {
- th {
- background-color: var(--el-bg-color-overlay, #f0f1f5) !important;
- color: var(--el-text-color-regular, #515a6e);
- }
- }
- }
-
- /* 鏍戠粍浠堕珮浜牱寮忚鐩� */
- .el-tree {
- .el-tree-node.is-current > .el-tree-node__content {
- background-color: var(--el-bg-color-overlay) !important;
- color: var(--el-color-primary);
- }
-
- .el-tree-node__content:hover {
- background-color: var(--el-bg-color-overlay);
- }
- }
-
- /* 涓嬫媺鑿滃崟鏍峰紡瑕嗙洊 */
- .el-dropdown-menu__item:not(.is-disabled):focus,
- .el-dropdown-menu__item:not(.is-disabled):hover {
- background-color: var(--navbar-hover) !important;
- }
-
- /* blockquote鏍峰紡瑕嗙洊 */
- blockquote {
- background-color: var(--blockquote-bg) !important;
- border-left-color: var(--blockquote-border) !important;
- color: var(--blockquote-text) !important;
- }
-
- /* 鏃堕棿琛ㄨ揪寮忔爣棰樻牱寮忚鐩� */
- .popup-result .title {
- background: var(--cron-border);
- }
-}
+// base color
+$blue: #324157;
+$light-blue: #333c46;
+$red: #c03639;
+$pink: #e65d6e;
+$green: #30b08f;
+$tiffany: #4ab7bd;
+$yellow: #fec171;
+$panGreen: #30b08f;
+
+// menu palette
+$menuText: #677287;
+$menuActiveText: #1f7a72;
+$menuBg: #f4f7f4;
+$menuHover: #e7eeea;
+
+// light theme
+$menuLightBg: #f4f7f4;
+$menuLightHover: #e7eeea;
+$menuLightText: #3b4658;
+$menuLightActiveText: #1f7a72;
+
+// layout
+$base-sidebar-width: 216px;
+$sideBarWidth: 216px;
+
+// sidebar
+$base-menu-color: #677287;
+$base-menu-color-active: #1f7a72;
+$base-menu-background: #f4f7f4;
+$base-sub-menu-background: #eef3ef;
+$base-sub-menu-hover: #ffffff;
+
+// component
+$--color-primary: #1f7a72;
+$--color-success: #67c23a;
+$--color-warning: #d89b41;
+$--color-danger: #d25b52;
+$--color-info: #7d8797;
+
+:export {
+ menuText: $menuText;
+ menuActiveText: $menuActiveText;
+ menuBg: $menuBg;
+ menuHover: $menuHover;
+ menuLightBg: $menuLightBg;
+ menuLightHover: $menuLightHover;
+ menuLightText: $menuLightText;
+ menuLightActiveText: $menuLightActiveText;
+ sideBarWidth: $sideBarWidth;
+ blue: $blue;
+ lightBlue: $light-blue;
+ red: $red;
+ pink: $pink;
+ green: $green;
+ tiffany: $tiffany;
+ yellow: $yellow;
+ panGreen: $panGreen;
+ colorPrimary: $--color-primary;
+ colorSuccess: $--color-success;
+ colorWarning: $--color-warning;
+ colorDanger: $--color-danger;
+ colorInfo: $--color-info;
+}
+
+:root {
+ --sidebar-bg: #{$menuBg};
+ --sidebar-text: #{$menuText};
+ --sidebar-muted: #93a0b1;
+ --menu-hover: #{$menuHover};
+ --menu-active-bg: #dfe9e4;
+ --menu-surface: rgba(255, 255, 255, 0.72);
+
+ --app-bg: #eef2ee;
+ --app-bg-accent: #dfe8e2;
+ --surface-base: #ffffff;
+ --surface-soft: #f7faf8;
+ --surface-muted: #eff4f1;
+ --surface-border: #d8e1db;
+ --surface-border-strong: #c9d5ce;
+ --text-primary: #21313f;
+ --text-secondary: #5f6d7e;
+ --text-tertiary: #8a98a8;
+ --shadow-sm: 0 10px 30px rgba(31, 49, 38, 0.06);
+ --shadow-md: 0 18px 50px rgba(31, 49, 38, 0.1);
+ --radius-lg: 24px;
+ --radius-md: 18px;
+ --radius-sm: 12px;
+
+ --navbar-bg: rgba(255, 255, 255, 0.78);
+ --navbar-text: #21313f;
+ --navbar-hover: rgba(31, 122, 114, 0.08);
+
+ --tags-bg: transparent;
+ --tags-item-bg: rgba(255, 255, 255, 0.74);
+ --tags-item-border: rgba(201, 213, 206, 0.88);
+ --tags-item-text: #5f6d7e;
+ --tags-item-hover: rgba(31, 122, 114, 0.08);
+ --tags-close-hover: rgba(31, 122, 114, 0.18);
+
+ --splitpanes-default-bg: #ffffff;
+}
+
+html.dark {
+ --el-bg-color: #141414;
+ --el-bg-color-overlay: #1d1e1f;
+ --el-text-color-primary: #ffffff;
+ --el-text-color-regular: #d0d0d0;
+ --el-border-color: #434343;
+ --el-border-color-light: #434343;
+
+ --sidebar-bg: #141414;
+ --sidebar-text: #ffffff;
+ --menu-hover: #2d2d2d;
+ --menu-active-text: #{$menuActiveText};
+
+ --navbar-bg: #141414;
+ --navbar-text: #ffffff;
+ --navbar-hover: #141414;
+
+ --tags-bg: #141414;
+ --tags-item-bg: #1d1e1f;
+ --tags-item-border: #303030;
+ --tags-item-text: #d0d0d0;
+ --tags-item-hover: #2d2d2d;
+ --tags-close-hover: #64666a;
+
+ --splitpanes-bg: #141414;
+ --splitpanes-border: #303030;
+ --splitpanes-splitter-bg: #1d1e1f;
+ --splitpanes-splitter-hover-bg: #2d2d2d;
+
+ --blockquote-bg: #1d1e1f;
+ --blockquote-border: #303030;
+ --blockquote-text: #d0d0d0;
+ --cron-border: #303030;
+ --splitpanes-default-bg: #141414;
+
+ .sidebar-container {
+ .el-menu-item,
+ .menu-title {
+ color: var(--el-text-color-regular);
+ }
+
+ & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
+ & .theme-dark .el-sub-menu .el-menu-item {
+ background-color: var(--el-bg-color) !important;
+ }
+ }
+
+ .el-menu--horizontal {
+ .el-menu-item {
+ &:not(.is-disabled) {
+ &:hover,
+ &:focus {
+ background-color: var(--navbar-hover) !important;
+ }
+ }
+ }
+ }
+
+ .splitpanes {
+ background-color: var(--splitpanes-bg);
+
+ .splitpanes__pane {
+ background-color: var(--splitpanes-bg);
+ border-color: var(--splitpanes-border);
+ }
+
+ .splitpanes__splitter {
+ background-color: var(--splitpanes-splitter-bg);
+ border-color: var(--splitpanes-border);
+
+ &:hover {
+ background-color: var(--splitpanes-splitter-hover-bg);
+ }
+
+ &:before,
+ &:after {
+ background-color: var(--splitpanes-border);
+ }
+ }
+ }
+
+ .el-table {
+ --el-table-header-bg-color: var(--el-bg-color-overlay) !important;
+ --el-table-header-text-color: var(--el-text-color-regular) !important;
+ --el-table-border-color: var(--el-border-color-light) !important;
+ --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
+
+ .el-table__header-wrapper,
+ .el-table__fixed-header-wrapper {
+ th {
+ background-color: var(--el-bg-color-overlay, #f0f1f5) !important;
+ color: var(--el-text-color-regular, #515a6e);
+ }
+ }
+ }
+
+ .el-tree {
+ .el-tree-node.is-current > .el-tree-node__content {
+ background-color: var(--el-bg-color-overlay) !important;
+ color: var(--el-color-primary);
+ }
+
+ .el-tree-node__content:hover {
+ background-color: var(--el-bg-color-overlay);
+ }
+ }
+
+ .el-dropdown-menu__item:not(.is-disabled):focus,
+ .el-dropdown-menu__item:not(.is-disabled):hover {
+ background-color: var(--navbar-hover) !important;
+ }
+
+ blockquote {
+ background-color: var(--blockquote-bg) !important;
+ border-left-color: var(--blockquote-border) !important;
+ color: var(--blockquote-text) !important;
+ }
+
+ .popup-result .title {
+ background: var(--cron-border);
+ }
+}
diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
new file mode 100644
index 0000000..369d75d
--- /dev/null
+++ b/src/components/AIChatSidebar/index.vue
@@ -0,0 +1,4423 @@
+<template>
+ <div class="ai-chat-sidebar-wrapper">
+ <!-- 鎮诞鍥炬爣 -->
+ <div class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
+ <el-tooltip :content="currentAssistant.tooltip" placement="left">
+ <div class="trigger-icon">
+ <el-icon :size="30" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
+ </div>
+ </el-tooltip>
+ </div>
+
+ <!-- 渚ц竟鏍忓璇濇 -->
+ <el-drawer
+ v-model="visible"
+ :size="drawerSize"
+ direction="rtl"
+ :with-header="true"
+ class="ai-chat-drawer"
+ :modal="false"
+ modal-class="ai-chat-overlay"
+ :show-close="false"
+ :append-to-body="false"
+ @close="handleClose"
+ >
+ <template #header>
+ <div class="drawer-header">
+ <div class="header-left">
+ <el-icon :size="20" class="header-icon"><component :is="currentAssistant.icon" /></el-icon>
+ <span class="title">{{ currentAssistant.title }}</span>
+ </div>
+ <div v-if="showAssistantSwitch" class="assistant-switcher">
+ <el-radio-group v-model="selectedAssistantKey" size="small">
+ <el-radio-button
+ v-for="assistant in assistants"
+ :key="assistant.key"
+ :label="assistant.key"
+ >
+ {{ assistant.label }}
+ </el-radio-button>
+ </el-radio-group>
+ </div>
+ <div class="header-actions">
+ <el-tooltip content="浼氳瘽鍘嗗彶" placement="bottom">
+ <el-button link class="header-action-btn" @click="handleToggleHistory">
+ <el-icon :size="18"><Timer /></el-icon>
+ </el-button>
+ </el-tooltip>
+ <el-tooltip content="寮�鍚柊浼氳瘽" placement="bottom">
+ <el-button link class="header-action-btn" @click="handleNewChat">
+ <el-icon :size="18"><Plus /></el-icon>
+ </el-button>
+ </el-tooltip>
+ <div class="action-divider"></div>
+ <el-tooltip content="鍏抽棴" placement="bottom">
+ <el-button link class="header-action-btn close-btn" @click="handleManualClose">
+ <el-icon :size="18"><Close /></el-icon>
+ </el-button>
+ </el-tooltip>
+ </div>
+ </div>
+ </template>
+
+ <div class="chat-container">
+ <!-- 鍘嗗彶浼氳瘽鍒楄〃 -->
+ <div v-if="showHistory" class="history-panel">
+ <div class="history-header">
+ <span>鏈�杩戜細璇�</span>
+ <el-button link type="primary" @click="showHistory = false">杩斿洖瀵硅瘽</el-button>
+ </div>
+ <el-skeleton :loading="loadingSessions" animated>
+ <template #template>
+ <div v-for="i in 5" :key="i" style="padding: 10px">
+ <el-skeleton-item variant="p" style="width: 80%" />
+ </div>
+ </template>
+ <div class="session-list">
+ <div
+ v-for="session in sessions"
+ :key="session.memoryId"
+ :class="['session-item', { active: uuid === session.memoryId }]"
+ @click="selectSession(session)"
+ >
+ <el-icon><ChatDotSquare /></el-icon>
+ <span class="session-name" :title="session.lastMessage || '鏂颁細璇�'">
+ {{ session.lastMessage || '鏂颁細璇�' }}
+ </span>
+ <el-button
+ link
+ type="danger"
+ class="delete-btn"
+ @click.stop="handleDeleteSession(session.memoryId)"
+ >
+ <el-icon><Delete /></el-icon>
+ </el-button>
+ </div>
+ <el-empty v-if="sessions.length === 0" :description="currentAssistant.emptySessionText" />
+ </div>
+ </el-skeleton>
+ </div>
+
+ <div v-else class="chat-main">
+ <div :class="['chat-hero', { compact: hasMessages }]">
+ <div :class="['assistant-stand', { thinking: isSending, compact: hasMessages }]">
+ <div class="assistant-halo"></div>
+ <div class="assistant-scan-ring"></div>
+ <div class="assistant-orbit assistant-orbit-a"></div>
+ <div class="assistant-orbit assistant-orbit-b"></div>
+ <div class="assistant-bot">
+ <div class="assistant-bot-antenna assistant-bot-antenna-left"></div>
+ <div class="assistant-bot-antenna assistant-bot-antenna-right"></div>
+ <div class="assistant-bot-head">
+ <div class="assistant-bot-head-glow"></div>
+ <div class="assistant-bot-eye assistant-bot-eye-left"></div>
+ <div class="assistant-bot-eye assistant-bot-eye-right"></div>
+ <div class="assistant-bot-mouth"></div>
+ </div>
+ <div class="assistant-bot-neck"></div>
+ <div class="assistant-bot-body">
+ <div class="assistant-bot-core">
+ <div class="assistant-bot-core-ring"></div>
+ <el-icon :size="22"><component :is="currentAssistant.icon" /></el-icon>
+ </div>
+ <div class="assistant-bot-arm assistant-bot-arm-left"></div>
+ <div class="assistant-bot-arm assistant-bot-arm-right"></div>
+ </div>
+ </div>
+ <div class="assistant-status">
+ <span class="assistant-status-dot"></span>
+ {{ isSending ? '鎬濊�冧腑...' : currentAssistant.label }}
+ </div>
+ <div class="assistant-base assistant-base-lg"></div>
+ <div class="assistant-base assistant-base-md"></div>
+ <div class="assistant-base assistant-base-sm"></div>
+ </div>
+
+ <div :class="['welcome-card', { compact: hasMessages }]">
+ <div class="welcome-eyebrow">鏅鸿兘鍔╂墜</div>
+ <h3 class="welcome-title">
+ 鎮ㄥソ
+ <br />
+ 鎴戞槸{{ currentAssistant.label }}鍒嗘瀽瑙h鍔╂墜
+ </h3>
+ <p class="welcome-desc">
+ {{ currentAssistant.description || '鎴戝彲浠ュ洿缁曚笟鍔¢棶棰樻彁渚涜В璇汇�佹煡璇㈠缓璁拰鍒嗘瀽鏀寔锛屽府鍔╀綘鏇村揩瀹屾垚鍒ゆ柇涓庡鐞嗐��' }}
+ </p>
+
+ <div class="quick-prompt-list">
+ <button
+ v-for="prompt in displayedQuickPrompts"
+ :key="prompt"
+ type="button"
+ class="quick-prompt-btn"
+ :disabled="isSending"
+ @click="sendQuickPrompt(prompt)"
+ >
+ {{ prompt }}
+ </button>
+ </div>
+
+ <button
+ v-if="quickPrompts.length > quickPromptLimit"
+ type="button"
+ class="more-prompts-btn"
+ @click="refreshQuickPrompts"
+ >
+ <el-icon><RefreshRight /></el-icon>
+ <span>鎹竴鎹�</span>
+ </button>
+ </div>
+ </div>
+
+ <div v-show="!hasMessages" class="hero-dot-grid" aria-hidden="true">
+ <span v-for="dot in 28" :key="dot"></span>
+ </div>
+
+ <div class="message-list" ref="messageListRef">
+ <div
+ v-for="(message, index) in messages"
+ :key="index"
+ :class="['message-item', message.isUser ? 'user-message' : 'bot-message']"
+ >
+ <div class="avatar">
+ <el-icon v-if="message.isUser"><User /></el-icon>
+ <el-icon v-else><Cpu /></el-icon>
+ </div>
+ <div class="message-content">
+ <!-- 鏂囨湰鍐呭 -->
+ <div class="text-box" v-html="message.htmlContent"></div>
+
+ <!-- 鍥捐〃鍐呭 -->
+ <div v-if="message.chartOptions && message.chartRenderReady" class="charts-wrapper">
+ <div
+ v-for="(option, key) in message.chartOptions"
+ :key="key"
+ class="chart-item"
+ :id="`ai-chart-${index}-${key}`"
+ ></div>
+ </div>
+
+ <!-- 琛ㄦ牸鍐呭 -->
+ <div v-if="message.type === 'todo_list' && message.tableData" class="table-wrapper">
+ <el-table :data="message.tableData.items" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.tableData.columns"
+ :key="col"
+ :prop="col"
+ :label="columnLabelMap[col] || col"
+ min-width="100"
+ show-overflow-tooltip
+ />
+ </el-table>
+ </div>
+
+ <!-- 鎵撳瓧涓姩鐢� -->
+ <div v-if="message.purchaseAnalysisData" class="purchase-confirm-card">
+ <div class="purchase-confirm-header">
+ <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '閲囪喘涓氬姟' }}</span>
+ <el-tag size="small" type="success" v-if="message.purchaseAnalysisData.confidence !== undefined">
+ 缃俊搴� {{ formatPercent(message.purchaseAnalysisData.confidence) }}
+ </el-tag>
+ </div>
+ <div class="purchase-confirm-desc">
+ {{ getPurchaseConfirmDescription(message.purchaseAnalysisData) }}
+ </div>
+ <div v-if="isPurchasePayloadEmpty(message.purchaseAnalysisData.payload)" class="purchase-empty-state">
+ <div class="empty-title">娌℃湁璇嗗埆鍒板彲鐩存帴鎻愪氦鐨勯噰璐彴璐︿俊鎭�</div>
+ <div class="empty-desc">褰撳墠鏂囦欢閲岀己灏戦噰璐悎鍚屽彿銆佷緵搴斿晢銆侀」鐩�佹棩鏈熴�佺墿鏂欐槑缁嗙瓑鍏抽敭鍐呭銆傝涓婁紶鏇村畬鏁寸殑鍚堝悓銆佽鍗曟垨鏄庣粏琛紝鎴栧湪涓嬫柟琛ュ厖鏁版嵁鍚庡啀纭銆�</div>
+ </div>
+ <div v-if="message.purchaseAnalysisData.warnings?.length" class="purchase-alert warning">
+ <strong>椋庨櫓鎻愮ず</strong>
+ <ul>
+ <li v-for="(warning, warningIndex) in message.purchaseAnalysisData.warnings" :key="warningIndex">
+ {{ formatPreviewItem(warning) }}
+ </li>
+ </ul>
+ </div>
+ <div v-if="getVisiblePurchaseMissingFields(message.purchaseAnalysisData).length" class="purchase-alert missing">
+ <strong>闇�瑕佽ˉ鍏� {{ getVisiblePurchaseMissingFields(message.purchaseAnalysisData).length }} 椤�</strong>
+ <el-tag
+ v-for="field in getVisiblePurchaseMissingFields(message.purchaseAnalysisData)"
+ :key="field"
+ size="small"
+ type="danger"
+ >
+ {{ field }}
+ </el-tag>
+ </div>
+ <div v-if="message.purchaseAnalysisData.preview?.length" class="purchase-preview">
+ <div class="purchase-section-title">纭鎽樿</div>
+ <ul>
+ <li v-for="(item, previewIndex) in message.purchaseAnalysisData.preview" :key="previewIndex">
+ {{ formatPreviewItem(item) }}
+ </li>
+ </ul>
+ </div>
+ <div class="purchase-section-title">琛ュ厖鎴栫‘璁ゆ暟鎹�</div>
+ <div class="payload-toolbar">
+ <el-button
+ size="small"
+ plain
+ :disabled="message.confirming || message.confirmed"
+ @click="addPurchaseRootField(message)"
+ >
+ <el-icon><Plus /></el-icon>
+ 鏂板椤跺眰瀛楁
+ </el-button>
+ </div>
+ <div class="payload-tree-table-wrapper">
+ <el-table
+ :data="message.payloadTreeData || []"
+ row-key="id"
+ border
+ stripe
+ size="small"
+ default-expand-all
+ :tree-props="{ children: 'children' }"
+ empty-text="鏆傛棤寰呯‘璁ゆ暟鎹�"
+ >
+ <el-table-column label="瀛楁" min-width="240">
+ <template #default="{ row }">
+ <div class="payload-key-cell">
+ <template v-if="row.parentType === 'object'">
+ <el-input
+ v-if="row.keyEditable"
+ v-model="row.key"
+ size="small"
+ :disabled="message.confirming || message.confirmed"
+ placeholder="瀛楁鍚�"
+ />
+ <div v-else class="payload-fixed-key" :title="row.key">
+ <span>{{ getPurchaseFieldLabel(row.key) }}</span>
+ <small v-if="getPurchaseFieldLabel(row.key) !== row.key">{{ row.key }}</small>
+ </div>
+ </template>
+ <span v-else class="payload-array-index">{{ getPurchaseArrayItemLabel(row, message) }}</span>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="绫诲瀷" width="130" align="center">
+ <template #default="{ row }">
+ <el-select
+ v-model="row.valueType"
+ size="small"
+ :disabled="message.confirming || message.confirmed"
+ @change="handlePurchaseNodeTypeChange(message, row)"
+ >
+ <el-option
+ v-for="option in purchaseValueTypeOptions"
+ :key="option.value"
+ :label="option.label"
+ :value="option.value"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍊�" min-width="250">
+ <template #default="{ row }">
+ <div v-if="row.valueType === 'object'" class="payload-container-cell">
+ 瀵硅薄锛坽{ row.children?.length || 0 }}锛�
+ </div>
+ <div v-else-if="row.valueType === 'array'" class="payload-container-cell">
+ 鏁扮粍锛坽{ row.children?.length || 0 }}锛�
+ </div>
+ <el-switch
+ v-else-if="row.valueType === 'boolean'"
+ v-model="row.value"
+ size="small"
+ :disabled="message.confirming || message.confirmed"
+ />
+ <span v-else-if="row.valueType === 'null'" class="payload-null-value">null</span>
+ <el-input
+ v-else
+ v-model="row.value"
+ size="small"
+ :placeholder="row.valueType === 'number' ? '璇疯緭鍏ユ暟瀛�' : '璇疯緭鍏ュ唴瀹�'"
+ :disabled="message.confirming || message.confirmed"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="180" align="center">
+ <template #default="{ row }">
+ <div class="payload-row-actions">
+ <el-tooltip v-if="row.valueType === 'object'" content="鏂板瀛楁" placement="top">
+ <el-button
+ :icon="Plus"
+ circle
+ size="small"
+ text
+ type="primary"
+ :disabled="message.confirming || message.confirmed"
+ @click="addPurchaseChildNode(message, row)"
+ />
+ </el-tooltip>
+ <el-tooltip v-else-if="row.valueType === 'array'" content="鏂板鏁扮粍椤�" placement="top">
+ <el-button
+ :icon="Plus"
+ circle
+ size="small"
+ text
+ type="primary"
+ :disabled="message.confirming || message.confirmed"
+ @click="addPurchaseChildNode(message, row)"
+ />
+ </el-tooltip>
+ <el-tooltip v-if="row.parentType === 'array'" content="鏂板鍚岀骇椤�" placement="top">
+ <el-button
+ :icon="Plus"
+ circle
+ size="small"
+ text
+ type="primary"
+ :disabled="message.confirming || message.confirmed"
+ @click="addPurchaseSiblingNode(message, row)"
+ />
+ </el-tooltip>
+ <el-tooltip content="鍒犻櫎褰撳墠椤�" placement="top">
+ <el-button
+ :icon="Delete"
+ circle
+ size="small"
+ text
+ type="danger"
+ :disabled="message.confirming || message.confirmed"
+ @click="removePurchaseNode(message, row)"
+ />
+ </el-tooltip>
+ </div>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <div class="payload-editor-tip">
+ 鏃ユ湡璇峰~鍐� yyyy-MM-dd锛屼緥濡� 2026-04-30銆備骇鍝佹槑缁嗗缓璁斁鍦ㄦ瘡鏉¢噰璐彴璐︾殑 productData 涓紝纭鏃朵細鑷姩鍏煎鏃ф牸寮忓苟娓呯悊瀹℃壒瀛楁銆�
+ </div>
+ <div class="purchase-confirm-actions">
+ <span v-if="message.confirmResult" :class="['confirm-result', message.confirmed ? 'success' : 'error']">
+ {{ message.confirmResult }}
+ </span>
+ <el-button
+ type="primary"
+ size="small"
+ :loading="message.confirming"
+ :disabled="message.confirmed || isSending"
+ @click="confirmPurchaseAnalysisFromTable(message)"
+ >
+ 纭骞舵墽琛�
+ </el-button>
+ </div>
+ </div>
+
+ <div v-if="message.isTyping" class="typing-indicator">
+ <span class="dot"></span>
+ <span class="dot"></span>
+ <span class="dot"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="input-area">
+ <div class="input-actions">
+ <el-button link class="utility-action-btn" type="primary" size="small" @click="handleNewChat">
+ <el-icon><Plus /></el-icon>鏂颁細璇�
+ </el-button>
+ <el-button v-if="isSending" link class="utility-action-btn stop-action-btn" type="danger" size="small" @click="stopGeneration">
+ <el-icon><VideoPause /></el-icon>鍋滄鐢熸垚
+ </el-button>
+ <el-upload
+ v-if="currentAssistant.allowFileUpload"
+ class="file-upload-trigger"
+ action="#"
+ :auto-upload="false"
+ :show-file-list="false"
+ v-model:file-list="uploadFileList"
+ :multiple="currentAssistant.allowMultipleFileUpload"
+ :on-change="handleFileChange"
+ :disabled="isSending"
+ >
+ <el-button link class="utility-action-btn upload-action-btn" type="primary" size="small" :disabled="isSending">
+ <el-icon><Upload /></el-icon>鍒嗘瀽鏂囦欢
+ </el-button>
+ </el-upload>
+ </div>
+ <div class="input-box">
+ <div v-if="selectedFiles.length" class="selected-file-list">
+ <div v-for="(file, fileIndex) in selectedFiles" :key="`${file.name}-${fileIndex}`" class="selected-file-tag">
+ <el-icon><Document /></el-icon>
+ <span class="file-name">{{ file.name }}</span>
+ <el-icon class="remove-file" @click="removeSelectedFile(fileIndex)"><Close /></el-icon>
+ </div>
+ </div>
+ <el-input
+ v-model="inputMessage"
+ type="textarea"
+ :rows="selectedFiles.length ? 2 : 3"
+ :placeholder="currentAssistant.placeholder"
+ resize="none"
+ @keydown.enter.exact.prevent="sendMessage"
+ />
+ <el-button
+ type="primary"
+ class="send-btn"
+ :disabled="isSending || (!inputMessage.trim() && !selectedFiles.length)"
+ @click="sendMessage"
+ aria-label="鍙戦��"
+ >
+ <el-icon><Promotion /></el-icon>
+ </el-button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-drawer>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
+import request from '@/utils/request'
+import * as echarts from 'echarts'
+import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, ShoppingCart, Promotion, RefreshRight } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+ assistants: {
+ type: Array,
+ default: () => []
+ },
+ defaultAssistant: {
+ type: String,
+ default: ''
+ }
+})
+
+const builtInAssistants = [
+ {
+ key: 'general',
+ label: '寰呭姙鍔╃悊',
+ title: '寰呭姙鏅鸿兘鍔╃悊',
+ tooltip: '寰呭姙鍔╂墜',
+ icon: Cpu,
+ apiBase: '/xiaozhi',
+ storageKey: 'ai_chat_uuid',
+ placeholder: '璇疯緭鍏ユ偍鐨勯棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+ welcomeMessage: '浣犲ソ',
+ description: '鎴戝彲浠ュ洖绛斾綘鐨勯棶棰橈紝涓轰綘鎻愪緵涓氬姟鏁版嵁瑙h淇℃伅銆佸鐞嗗缓璁拰杈呭姪鍐崇瓥鏀寔銆�',
+ allowFileUpload: true,
+ emptySessionText: '鏆傛棤鍘嗗彶浼氳瘽'
+ },
+ {
+ key: 'purchase',
+ label: '閲囪喘鍔╃悊',
+ title: '閲囪喘鏅鸿兘鍔╃悊',
+ tooltip: '閲囪喘鏅鸿兘鍔╃悊',
+ icon: ShoppingCart,
+ apiBase: '/purchase-ai',
+ storageKey: 'purchase_ai_chat_uuid',
+ placeholder: '璇疯緭鍏ラ噰璐棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+ welcomeMessage: '浣犲ソ',
+ description: '鎴戝彲浠ュ崗鍔╀綘鍒嗘瀽閲囪喘璁㈠崟銆佸埌璐ц繘搴︺�佷緵搴斿晢琛ㄧ幇鍜屼粯娆炬儏鍐碉紝甯姪浣犲揩閫熷畾浣嶉噰璐紓甯搞��',
+ allowFileUpload: true,
+ allowMultipleFileUpload: true,
+ fileAnalyzeUrl: '/purchase-ai/analyze-files',
+ emptySessionText: '鏆傛棤閲囪喘浼氳瘽'
+ }
+]
+
+const assistants = computed(() => props.assistants?.length ? props.assistants : builtInAssistants)
+const selectedAssistantKey = ref(props.defaultAssistant || assistants.value[0]?.key || 'general')
+const currentAssistant = computed(() => assistants.value.find(item => item.key === selectedAssistantKey.value) || assistants.value[0] || builtInAssistants[0])
+const showAssistantSwitch = computed(() => assistants.value.length > 1)
+const assistantQuickPromptMap = {
+ general: [
+ '鎴戝綋鍓嶆湁鍝簺瀹℃壒寰呭姙闇�瑕佸鐞嗭紵',
+ '甯垜鍒楀嚭浠婂ぉ鏂板鐨勫鎵瑰緟鍔炪��',
+ '褰撳墠寰呮垜瀹℃壒鐨勫崟鎹紝鎸夋椂闂村�掑簭鍒楀嚭鏉ャ��',
+ '鎴戝彂璧风殑瀹℃壒閲岋紝鍝簺杩樺湪澶勭悊涓紵',
+ '鏌ヨ娴佺▼缂栧彿 XXX 鐨勫鎵硅鎯呫��',
+ '娴佺▼缂栧彿 XXX 鐜板湪鍗″湪鍝釜瀹℃壒鑺傜偣锛熷綋鍓嶅鎵逛汉鏄皝锛�',
+ '甯垜鏌ョ湅娴佺▼缂栧彿 XXX 鐨勫鎵规祦杞褰曘��',
+ '杩�7澶╂垜鐨勫鎵瑰緟鍔炵粺璁℃儏鍐垫�庝箞鏍凤紵',
+ '鏈湀鎴戠殑瀹℃壒涓紝閫氳繃銆侀┏鍥炪�佸鐞嗕腑鍚勬湁澶氬皯锛�',
+ '杩�30澶╁悇绫诲瀷瀹℃壒鏁伴噺鍒嗗竷鏄粈涔堬紵',
+ '甯垜瀹℃壒閫氳繃娴佺▼缂栧彿 XXX锛屽娉ㄢ�滃悓鎰忊�濄��',
+ '甯垜椹冲洖娴佺▼缂栧彿 XXX锛屽娉ㄢ�滆琛ュ厖璇存槑鈥濄��',
+ '鎾ら攢鎴戝垰鍒氬娴佺▼缂栧彿 XXX 鐨勫鎵规搷浣溿��',
+ '甯垜淇敼娴佺▼缂栧彿 XXX 鐨勫娉ㄤ负鈥滃凡琛ュ厖闄勪欢鈥濄��',
+ '鍒犻櫎鎴戝彂璧风殑娴佺▼缂栧彿 XXX銆�'
+ ],
+ purchase: [
+ '鏈湀閲囪喘閲戦鎺掑悕鍓嶅崄鐨勭墿鏂欐湁鍝簺锛�',
+ '鍝簺閲囪喘璁㈠崟杩樻湭鍏ュ簱锛�',
+ '鏈�杩�7澶╀緵搴斿晢鍒拌揣寮傚父鏈夊摢浜涳紵',
+ '甯垜缁熻寰呬粯娆鹃噰璐崟',
+ '鍒楀嚭鏈湀閲囪喘閫�璐ф儏鍐�'
+ ]
+}
+const quickPromptLimit = 3
+const quickPromptStart = ref(0)
+const quickPrompts = computed(() => {
+ const assistant = currentAssistant.value || {}
+ if (Array.isArray(assistant.quickPrompts) && assistant.quickPrompts.length) {
+ return assistant.quickPrompts
+ }
+ return assistantQuickPromptMap[assistant.key] || assistantQuickPromptMap.general
+})
+const displayedQuickPrompts = computed(() => {
+ const prompts = quickPrompts.value || []
+ if (prompts.length <= quickPromptLimit) return prompts
+
+ const result = []
+ for (let i = 0; i < quickPromptLimit; i++) {
+ result.push(prompts[(quickPromptStart.value + i) % prompts.length])
+ }
+ return result
+})
+const hasMessages = computed(() => messages.value.length > 0)
+
+const visible = ref(false)
+const windowWidth = ref(window.innerWidth)
+const drawerSize = computed(() => {
+ if (windowWidth.value < 768) return '100%'
+ if (windowWidth.value < 1200) return '50%'
+ return '50%'
+})
+const messageListRef = ref(null)
+const isSending = ref(false)
+const currentAbortController = ref(null)
+const inputMessage = ref('')
+const selectedFiles = ref([])
+const uploadFileList = ref([])
+const messages = ref([])
+const uuid = ref('')
+const chartInstances = ref({})
+const resizeHandlers = ref([])
+const outputState = ref({})
+const businessTypeLabelMap = {
+ purchase_ledger: '閲囪喘鍙拌处',
+ payment_registration: '浠樻鐧昏',
+ purchase_return_order: '閲囪喘閫�璐у崟',
+ unknown: '鏈煡閲囪喘涓氬姟'
+}
+const purchasePayloadFieldLabelMap = {
+ purchaseLedgers: '閲囪喘鍙拌处',
+ productData: '浜у搧鏄庣粏',
+ purchaseContractNumber: '閲囪喘鍚堝悓鍙�',
+ purchaseContractNo: '閲囪喘鍚堝悓鍙�',
+ purchaseOrderNumber: '閲囪喘鍚堝悓鍙�',
+ salesContractNo: '閿�鍞悎鍚屽彿',
+ salesContractNumber: '閿�鍞悎鍚屽彿',
+ salesOrderNumber: '閿�鍞悎鍚屽彿',
+ salesContractNoId: '閿�鍞悎鍚孖D',
+ approveUserIds: '瀹℃壒鐢ㄦ埛ID鍒楄〃',
+ entryDateStart: '褰曞叆寮�濮嬫棩鏈�',
+ entryDateEnd: '褰曞叆缁撴潫鏃ユ湡',
+ id: 'ID',
+ supplierId: '渚涘簲鍟咺D',
+ projectName: '椤圭洰鍚嶇О',
+ supplierName: '渚涘簲鍟嗗悕绉�',
+ isWhite: '鏄惁鐧藉悕鍗�',
+ recorderId: '褰曞叆浜篒D',
+ recorderName: '褰曞叆浜�',
+ contractDate: '鎵ц鏃ユ湡',
+ executionDate: '鎵ц鏃ユ湡',
+ inputPerson: '褰曞叆浜�',
+ inputDate: '褰曞叆鏃ユ湡',
+ entryDate: '褰曞叆鏃ユ湡',
+ paymentMethod: '浠樻鏂瑰紡',
+ auditors: '瀹℃壒浜�',
+ approverId: '瀹℃壒浜篒D',
+ approvalStatus: '瀹℃壒鐘舵��',
+ remark: '澶囨敞',
+ remarks: '澶囨敞',
+ attachmentMaterials: '闄勪欢鏉愭枡',
+ createdAt: '鍒涘缓鏃堕棿',
+ updatedAt: '鏇存柊鏃堕棿',
+ salesLedgerId: '閿�鍞彴璐D',
+ hasChildren: '鏄惁鏈夊瓙椤�',
+ Type: '绫诲瀷',
+ type: '绫诲瀷',
+ tempFileIds: '涓存椂鏂囦欢ID',
+ SalesLedgerFiles: '閿�鍞彴璐﹂檮浠�',
+ phoneNumber: '鑱旂郴鐢佃瘽',
+ businessPersonId: '涓氬姟鍛業D',
+ productId: '浜у搧ID',
+ productModelId: '浜у搧鍨嬪彿ID',
+ invoiceNumber: '鍙戠エ鍙风爜',
+ invoiceAmount: '鍙戠エ閲戦',
+ ticketRegistrationId: '寮�绁ㄧ櫥璁癐D',
+ contractAmount: '鍚堝悓閲戦',
+ receiptPaymentAmount: '宸叉敹浠樻閲戦',
+ unReceiptPaymentAmount: '鏈敹浠樻閲戦',
+ templateName: '妯℃澘鍚嶇О',
+ productCategory: '浜у搧绫诲埆',
+ specificationModel: '瑙勬牸鍨嬪彿',
+ unit: '鍗曚綅',
+ taxRate: '绋庣巼',
+ taxInclusiveUnitPrice: '鍚◣鍗曚环',
+ priceWithTax: '鍚◣鍗曚环',
+ quantity: '鏁伴噺',
+ taxInclusiveTotalPrice: '鍚◣鎬讳环',
+ totalPriceWithTax: '鍚◣鎬讳环',
+ invoiceType: '鍙戠エ绫诲瀷',
+ inventoryWarningQuantity: '搴撳瓨棰勮鏁伴噺',
+ isInspected: '鏄惁璐ㄦ',
+ isChecked: '鏄惁璐ㄦ'
+}
+const purchasePayloadFieldKeyMap = {
+ 閲囪喘鍙拌处: 'purchaseLedgers',
+ 浜у搧鏄庣粏: 'productData',
+ 閲囪喘鍚堝悓鍙�: 'purchaseContractNumber',
+ 閲囪喘鍗曞彿: 'purchaseContractNumber',
+ 閲囪喘璁㈠崟鍙�: 'purchaseContractNumber',
+ 閿�鍞悎鍚屽彿: 'salesContractNo',
+ 閿�鍞崟鍙�: 'salesContractNo',
+ 閿�鍞鍗曞彿: 'salesContractNo',
+ 閿�鍞悎鍚孖D: 'salesContractNoId',
+ 瀹℃壒鐢ㄦ埛ID鍒楄〃: 'approveUserIds',
+ 褰曞叆寮�濮嬫棩鏈�: 'entryDateStart',
+ 褰曞叆缁撴潫鏃ユ湡: 'entryDateEnd',
+ ID: 'id',
+ 椤圭洰鍚嶇О: 'projectName',
+ 渚涘簲鍟咺D: 'supplierId',
+ 渚涘簲鍟嗗悕绉�: 'supplierName',
+ 鏄惁鐧藉悕鍗�: 'isWhite',
+ 褰曞叆浜篒D: 'recorderId',
+ 褰曞叆浜�: 'recorderName',
+ 绛捐鏃ユ湡: 'executionDate',
+ 鎵ц鏃ユ湡: 'executionDate',
+ 褰曞叆鏃ユ湡: 'entryDate',
+ 浠樻鏂瑰紡: 'paymentMethod',
+ 瀹℃牳浜�: 'approverId',
+ 瀹℃壒浜�: 'approverId',
+ 瀹℃壒浜篒D: 'approverId',
+ 瀹℃壒鐘舵��: 'approvalStatus',
+ 澶囨敞: 'remarks',
+ 闄勪欢鏉愭枡: 'attachmentMaterials',
+ 鍒涘缓鏃堕棿: 'createdAt',
+ 鏇存柊鏃堕棿: 'updatedAt',
+ 閿�鍞彴璐D: 'salesLedgerId',
+ 鏄惁鏈夊瓙椤�: 'hasChildren',
+ 绫诲瀷: 'type',
+ 涓存椂鏂囦欢ID: 'tempFileIds',
+ 閿�鍞彴璐﹂檮浠�: 'SalesLedgerFiles',
+ 鑱旂郴鐢佃瘽: 'phoneNumber',
+ 涓氬姟鍛業D: 'businessPersonId',
+ 浜у搧ID: 'productId',
+ 浜у搧鍨嬪彿ID: 'productModelId',
+ 鍙戠エ鍙风爜: 'invoiceNumber',
+ 鍙戠エ閲戦: 'invoiceAmount',
+ 寮�绁ㄧ櫥璁癐D: 'ticketRegistrationId',
+ 鍚堝悓閲戦: 'contractAmount',
+ 宸叉敹浠樻閲戦: 'receiptPaymentAmount',
+ 鏈敹浠樻閲戦: 'unReceiptPaymentAmount',
+ 妯℃澘鍚嶇О: 'templateName',
+ 浜у搧绫诲埆: 'productCategory',
+ 浜у搧鍚嶇О: 'productCategory',
+ 瑙勬牸鍨嬪彿: 'specificationModel',
+ 鍗曚綅: 'unit',
+ 绋庣巼: 'taxRate',
+ 鍚◣鍗曚环: 'taxInclusiveUnitPrice',
+ 鏁伴噺: 'quantity',
+ 鍚◣鎬讳环: 'taxInclusiveTotalPrice',
+ 鍙戠エ绫诲瀷: 'invoiceType',
+ 搴撳瓨棰勮鏁伴噺: 'inventoryWarningQuantity',
+ 鏄惁璐ㄦ: 'isInspected',
+ purchaseLedgers: 'purchaseLedgers',
+ productData: 'productData',
+ purchaseContractNumber: 'purchaseContractNumber',
+ purchaseContractNo: 'purchaseContractNumber',
+ purchaseOrderNumber: 'purchaseContractNumber',
+ salesContractNo: 'salesContractNo',
+ salesContractNumber: 'salesContractNo',
+ salesOrderNumber: 'salesContractNo',
+ contractDate: 'executionDate',
+ inputPerson: 'recorderName',
+ inputDate: 'entryDate',
+ auditors: 'approverId',
+ remark: 'remarks',
+ productCategory: 'productCategory',
+ productName: 'productCategory',
+ specificationModel: 'specificationModel',
+ unit: 'unit',
+ taxRate: 'taxRate',
+ priceWithTax: 'taxInclusiveUnitPrice',
+ taxInclusiveUnitPrice: 'taxInclusiveUnitPrice',
+ quantity: 'quantity',
+ totalPriceWithTax: 'taxInclusiveTotalPrice',
+ taxInclusiveTotalPrice: 'taxInclusiveTotalPrice',
+ invoiceType: 'invoiceType',
+ inventoryWarningQuantity: 'inventoryWarningQuantity',
+ isInspected: 'isInspected',
+ isChecked: 'isInspected'
+}
+
+// 鍘嗗彶浼氳瘽鐩稿叧
+const purchaseValueTypeOptions = [
+ { label: '鏂囨湰', value: 'string' },
+ { label: '鏁板瓧', value: 'number' },
+ { label: '甯冨皵', value: 'boolean' },
+ { label: '绌哄��', value: 'null' },
+ { label: '瀵硅薄', value: 'object' },
+ { label: '鏁扮粍', value: 'array' }
+]
+const purchaseContainerValueTypes = new Set(['object', 'array'])
+const purchaseHiddenFieldKeySet = new Set(['templatename', 'approvalstatus', 'phonenumber', 'type'])
+const purchaseHiddenKeyWordList = [
+ 'attachment',
+ 'file',
+ 'invoice',
+ 'ticketregistration',
+ 'receiptpayment',
+ 'payment'
+]
+const purchaseHiddenChineseKeywordList = ['闄勪欢', '寮�绁�', '鏉ョエ', '鍥炴', '浠樻']
+let purchasePayloadTreeNodeSeed = 0
+
+const shouldHidePurchaseField = (fieldKey = '') => {
+ const rawKey = String(fieldKey || '')
+ if (!rawKey) return false
+ const normalizedFieldKey = purchasePayloadFieldKeyMap[rawKey] || rawKey
+ const lowerKey = String(normalizedFieldKey).toLowerCase()
+
+ if (lowerKey.endsWith('id') || lowerKey.endsWith('ids')) return true
+ if (purchaseHiddenFieldKeySet.has(lowerKey)) return true
+ if (purchaseHiddenKeyWordList.some(keyword => lowerKey.includes(keyword))) return true
+ if (purchaseHiddenChineseKeywordList.some(keyword => rawKey.includes(keyword))) return true
+ return false
+}
+
+const showHistory = ref(false)
+const sessions = ref([])
+const loadingSessions = ref(false)
+
+const abortCurrentRequest = () => {
+ if (!currentAbortController.value) return
+
+ currentAbortController.value.abort()
+ currentAbortController.value = null
+ isSending.value = false
+
+ const lastMsg = messages.value[messages.value.length - 1]
+ if (lastMsg && !lastMsg.isUser) {
+ lastMsg.isTyping = false
+ }
+}
+
+const toggleHistory = () => {
+ showHistory.value = !showHistory.value
+ if (showHistory.value) {
+ loadSessions()
+ }
+}
+
+const handleToggleHistory = () => {
+ if (isSending.value) {
+ abortCurrentRequest()
+ }
+ toggleHistory()
+}
+
+const loadSessions = async () => {
+ loadingSessions.value = true
+ try {
+ const res = await request.get(`${currentAssistant.value.apiBase}/history/sessions`)
+ if (res.code === 200) {
+ sessions.value = res.data || []
+ }
+ } catch (err) {
+ console.error('Failed to load sessions', err)
+ } finally {
+ loadingSessions.value = false
+ }
+}
+
+const selectSession = async (session) => {
+ showHistory.value = false
+ uuid.value = session.memoryId
+ localStorage.setItem(currentAssistant.value.storageKey, uuid.value)
+
+ // 鍔犺浇浼氳瘽娑堟伅
+ try {
+ const res = await request.get(`${currentAssistant.value.apiBase}/history/messages/${uuid.value}`)
+ if (res.code === 200) {
+ disposeCharts()
+ messages.value = []
+ const historyMsgs = res.data || []
+
+ // 閲嶆柊鏋勯�犳秷鎭垪琛ㄥ苟瑙f瀽
+ historyMsgs.forEach((msg, idx) => {
+ const isUser = msg.role === 'user'
+ const botMsgIndex = messages.value.length
+
+ const messageObj = {
+ isUser,
+ content: msg.content,
+ htmlContent: '',
+ isTyping: false,
+ chartOptions: null,
+ chartRenderReady: false,
+ type: '',
+ tableData: null,
+ payloadTreeData: null,
+ payloadHiddenData: null
+ }
+
+ messages.value.push(messageObj)
+
+ if (!isUser) {
+ outputState.value[botMsgIndex] = {
+ isPaused: false,
+ jsonBlockStartPos: -1,
+ jsBlockStartPos: -1,
+ blockEndPos: -1,
+ hasRenderedChart: false
+ }
+
+ // 瑙f瀽鍘嗗彶娑堟伅涓殑 JSON
+ const extracted = extractEmbeddedSuccessJson(msg.content)
+ if (extracted) {
+ applyStructuredMessageData(messageObj, extracted.data, botMsgIndex)
+ }
+
+ updateOutputState(msg.content, botMsgIndex)
+ messageObj.htmlContent = convertStreamOutput(msg.content, botMsgIndex)
+ } else {
+ messageObj.htmlContent = convertTextToHtml(msg.content)
+ }
+ })
+ scrollToBottom()
+ }
+ } catch (err) {
+ console.error('Failed to load messages', err)
+ }
+}
+
+const handleDeleteSession = async (memoryId) => {
+ try {
+ const res = await request.delete(`${currentAssistant.value.apiBase}/history/${memoryId}`)
+ if (res.code === 200) {
+ loadSessions()
+ if (uuid.value === memoryId) {
+ newChat()
+ }
+ }
+ } catch (err) {
+ console.error('Failed to delete session', err)
+ }
+}
+
+const columnLabelMap = {
+ approveId: '瀹℃壒缂栧彿',
+ approveType: '瀹℃壒绫诲瀷',
+ approveUserName: '瀹℃壒浜�',
+ approveUserCurrentName: '褰撳墠澶勭悊浜�',
+ approveReason: '瀹℃壒鍘熷洜',
+ approveStatus: '瀹℃壒鐘舵��',
+ createTime: '鍒涘缓鏃堕棿'
+}
+
+onMounted(() => {
+ initUUID()
+ // 鍒濆娆㈣繋
+ if (messages.value.length === 0) {
+ hello()
+ }
+ window.addEventListener('resize', handleWindowResize)
+})
+
+onUnmounted(() => {
+ disposeCharts()
+ window.removeEventListener('resize', handleWindowResize)
+})
+
+watch(selectedAssistantKey, (nextKey, prevKey) => {
+ if (!prevKey || nextKey === prevKey) return
+
+ abortCurrentRequest()
+ disposeCharts()
+ messages.value = []
+ outputState.value = {}
+ sessions.value = []
+ showHistory.value = false
+ selectedFiles.value = []
+ uploadFileList.value = []
+ inputMessage.value = ''
+ quickPromptStart.value = 0
+ initUUID()
+ hello()
+})
+
+const handleWindowResize = () => {
+ windowWidth.value = window.innerWidth
+}
+
+const toggleSidebar = () => {
+ visible.value = !visible.value
+ if (visible.value) {
+ scrollToBottom()
+ }
+}
+
+const handleClose = () => {
+ visible.value = false
+}
+
+const handleManualClose = () => {
+ if (isSending.value) {
+ abortCurrentRequest()
+ }
+ handleClose()
+}
+
+const initUUID = () => {
+ let storedUUID = localStorage.getItem(currentAssistant.value.storageKey)
+ if (!storedUUID) {
+ storedUUID = Math.random().toString(36).substring(2, 10) + Date.now().toString(36).substring(4)
+ localStorage.setItem(currentAssistant.value.storageKey, storedUUID)
+ }
+ uuid.value = storedUUID
+}
+
+const hello = () => {
+ sendRequest(currentAssistant.value.welcomeMessage || '浣犲ソ')
+}
+
+const newChat = () => {
+ disposeCharts()
+ messages.value = []
+ outputState.value = {}
+ sessions.value = []
+ showHistory.value = false
+ selectedFiles.value = []
+ uploadFileList.value = []
+ quickPromptStart.value = 0
+ localStorage.removeItem(currentAssistant.value.storageKey)
+ initUUID()
+ hello()
+}
+
+const handleNewChat = () => {
+ if (isSending.value) {
+ abortCurrentRequest()
+ }
+ newChat()
+}
+
+const sendQuickPrompt = (prompt) => {
+ if (!prompt || isSending.value) return
+ inputMessage.value = prompt
+ sendMessage()
+}
+
+const refreshQuickPrompts = () => {
+ const prompts = quickPrompts.value || []
+ if (prompts.length <= quickPromptLimit) return
+ quickPromptStart.value = (quickPromptStart.value + quickPromptLimit) % prompts.length
+}
+
+const disposeCharts = () => {
+ Object.values(chartInstances.value).forEach(chart => chart.dispose())
+ resizeHandlers.value.forEach(handler => window.removeEventListener('resize', handler))
+ chartInstances.value = {}
+ resizeHandlers.value = []
+}
+
+const extractEmbeddedSuccessJson = (text) => {
+ if (!text || typeof text !== 'string') return null
+
+ const startMatch = text.match(/\{\s*"success"\s*:/)
+ if (!startMatch) return null
+ const startIdx = startMatch.index ?? -1
+ if (startIdx < 0) return null
+
+ for (let i = startIdx; i < text.length; i++) {
+ if (text[i] !== '{') continue
+
+ let depth = 0
+ let inString = false
+ let escaped = false
+
+ for (let j = i; j < text.length; j++) {
+ const char = text[j]
+
+ if (inString) {
+ if (escaped) {
+ escaped = false
+ } else if (char === '\\') {
+ escaped = true
+ } else if (char === '"') {
+ inString = false
+ }
+ continue
+ }
+
+ if (char === '"') {
+ inString = true
+ continue
+ }
+
+ if (char === '{') {
+ depth++
+ } else if (char === '}') {
+ depth--
+ if (depth === 0) {
+ const candidate = text.slice(i, j + 1)
+ try {
+ const parsed = JSON.parse(candidate)
+ if (parsed?.success === true) {
+ return {
+ data: parsed,
+ startIdx: i,
+ endIdx: j + 1
+ }
+ }
+ } catch (err) {
+ continue
+ }
+ }
+ }
+ }
+ }
+
+ return null
+}
+
+const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
+ if (!messageObj || !parsedData?.success) return
+
+ messageObj.type = parsedData.type || ''
+
+ if (messageObj.type === 'todo_list' && parsedData.data) {
+ messageObj.tableData = parsedData.data
+ }
+
+ if (parsedData.action === 'confirm_required' && parsedData.businessType) {
+ messageObj.type = 'purchase_analysis_confirm'
+ messageObj.purchaseAnalysisData = parsedData
+ if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
+ initializePurchasePayloadTree(messageObj, parsedData.payload || {})
+ }
+ if (!messageObj.payloadText) {
+ const payloadFromTree = buildPurchasePayloadFromNodes(messageObj.payloadTreeData, 'object')
+ const payloadWithHidden = mergePurchasePayloadWithHidden(payloadFromTree, messageObj.payloadHiddenData)
+ messageObj.payloadText = JSON.stringify(localizePurchasePayload(payloadWithHidden), null, 2)
+ }
+ messageObj.confirmResult = ''
+ messageObj.confirmed = false
+ messageObj.confirming = false
+ }
+
+ const chartOptions = getStructuredChartOptions(parsedData)
+ if (chartOptions && Object.keys(chartOptions).length > 0) {
+ messageObj.chartOptions = chartOptions
+ messageObj.chartRenderReady = true
+
+ if (shouldRenderCharts) {
+ renderCharts(msgIndex, messageObj.chartOptions)
+ if (outputState.value[msgIndex]) {
+ outputState.value[msgIndex].hasRenderedChart = true
+ }
+ }
+ }
+}
+
+const getStructuredChartOptions = (parsedData) => {
+ if (!parsedData?.success) return null
+
+ if (parsedData.charts && Object.keys(parsedData.charts).length > 0) {
+ return parsedData.charts
+ }
+
+ if (parsedData.type === 'purchase_material_rank') {
+ return buildPurchaseMaterialRankCharts(parsedData)
+ }
+
+ return null
+}
+
+const buildPurchaseMaterialRankCharts = (parsedData) => {
+ const items = Array.isArray(parsedData?.data?.items) ? parsedData.data.items : []
+ if (!items.length) return null
+
+ const names = items.map(item => item.productCategory || '-')
+ const amounts = items.map(item => Number(item.amount) || 0)
+
+ return {
+ purchaseMaterialAmountRank: {
+ title: {
+ text: '\u91c7\u8d2d\u7269\u6599\u91d1\u989d\u6392\u884c',
+ left: 'center',
+ textStyle: {
+ fontSize: 14,
+ fontWeight: 600,
+ color: '#1a1a2e'
+ }
+ },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ },
+ formatter(params) {
+ const dataIndex = params?.[0]?.dataIndex ?? 0
+ const item = items[dataIndex] || {}
+ const amount = Number(item.amount) || 0
+ const quantity = Number(item.quantity) || 0
+ return [
+ `${item.productCategory || '-'}`,
+ `${params?.[0]?.marker || ''} \u91d1\u989d\uff1a${formatCurrency(amount)}`,
+ `\u89c4\u683c\u578b\u53f7\uff1a${item.specificationModel || '-'}`,
+ `\u6570\u91cf\uff1a${quantity}${item.unit || ''}`
+ ].join('<br/>')
+ }
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: names.some(name => String(name).length > 6) ? 72 : 48,
+ top: 48,
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: names,
+ axisLabel: {
+ interval: 0,
+ rotate: names.some(name => String(name).length > 6) ? 28 : 0,
+ color: '#4b5563'
+ }
+ },
+ yAxis: {
+ type: 'value',
+ name: '\u91d1\u989d(\u5143)',
+ axisLabel: {
+ color: '#4b5563',
+ formatter: value => formatCompactNumber(value)
+ }
+ },
+ series: [{
+ name: '\u91c7\u8d2d\u91d1\u989d',
+ type: 'bar',
+ barMaxWidth: 36,
+ data: amounts,
+ itemStyle: {
+ borderRadius: [6, 6, 0, 0],
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: '#2f7cf6' },
+ { offset: 1, color: '#55d7ff' }
+ ])
+ },
+ label: {
+ show: true,
+ position: 'top',
+ color: '#1f2937',
+ formatter: params => formatCompactNumber(params.value)
+ }
+ }]
+ }
+ }
+}
+
+const formatCurrency = (value) => {
+ const amount = Number(value) || 0
+ return `\u00a5${amount.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}`
+}
+
+const formatCompactNumber = (value) => {
+ const amount = Number(value) || 0
+ if (Math.abs(amount) >= 10000) {
+ return `${(amount / 10000).toFixed(2).replace(/\.?0+$/, '')}\u4e07`
+ }
+ return amount.toLocaleString('zh-CN', { maximumFractionDigits: 2 })
+}
+
+const formatPercent = (value) => {
+ const number = Number(value)
+ if (Number.isNaN(number)) return '-'
+ return `${Math.round(number * 100)}%`
+}
+
+const formatPreviewItem = (item) => {
+ if (item === null || item === undefined) return '-'
+ if (typeof item === 'string') return item
+ try {
+ return JSON.stringify(item)
+ } catch (err) {
+ return String(item)
+ }
+}
+
+const hasMeaningfulPayloadValue = (value) => {
+ if (value === null || value === undefined) return false
+ if (typeof value === 'string') return value.trim() !== ''
+ if (Array.isArray(value)) return value.some(item => hasMeaningfulPayloadValue(item))
+ if (typeof value === 'object') return Object.values(value).some(item => hasMeaningfulPayloadValue(item))
+ return true
+}
+
+const mergeMappedPayloadValue = (existingValue, incomingValue) => {
+ if (existingValue === undefined) return incomingValue
+
+ const existingHasValue = hasMeaningfulPayloadValue(existingValue)
+ const incomingHasValue = hasMeaningfulPayloadValue(incomingValue)
+
+ if (existingHasValue && !incomingHasValue) return existingValue
+ if (!existingHasValue && incomingHasValue) return incomingValue
+
+ if (
+ existingValue &&
+ incomingValue &&
+ typeof existingValue === 'object' &&
+ typeof incomingValue === 'object' &&
+ !Array.isArray(existingValue) &&
+ !Array.isArray(incomingValue)
+ ) {
+ return { ...existingValue, ...incomingValue }
+ }
+
+ return incomingValue
+}
+
+const mapPayloadKeys = (value, keyMap) => {
+ if (Array.isArray(value)) {
+ return value.map(item => mapPayloadKeys(item, keyMap))
+ }
+ if (value && typeof value === 'object') {
+ return Object.entries(value).reduce((result, [key, item]) => {
+ const mappedKey = keyMap[key] || key
+ const mappedValue = mapPayloadKeys(item, keyMap)
+ result[mappedKey] = mergeMappedPayloadValue(result[mappedKey], mappedValue)
+ return result
+ }, {})
+ }
+ return value
+}
+
+const clonePurchasePayloadValue = (value) => {
+ if (Array.isArray(value)) {
+ return value.map(item => clonePurchasePayloadValue(item))
+ }
+ if (value && typeof value === 'object') {
+ return Object.entries(value).reduce((result, [key, item]) => {
+ result[key] = clonePurchasePayloadValue(item)
+ return result
+ }, {})
+ }
+ return value
+}
+
+const splitPurchasePayloadByVisibility = (value) => {
+ if (Array.isArray(value)) {
+ const splitItems = value.map(item => splitPurchasePayloadByVisibility(item))
+ const visible = splitItems.map(item => item.visible)
+ const hidden = splitItems.map(item => item.hidden)
+ return { visible, hidden }
+ }
+
+ if (value && typeof value === 'object') {
+ const visible = {}
+ const hidden = {}
+
+ Object.entries(value).forEach(([key, item]) => {
+ if (shouldHidePurchaseField(key)) {
+ hidden[key] = clonePurchasePayloadValue(item)
+ return
+ }
+ const child = splitPurchasePayloadByVisibility(item)
+ visible[key] = child.visible
+ if (hasMeaningfulPayloadValue(child.hidden)) {
+ hidden[key] = child.hidden
+ }
+ })
+
+ return { visible, hidden }
+ }
+
+ return { visible: value, hidden: undefined }
+}
+
+const mergePurchasePayloadWithHidden = (visibleValue, hiddenValue) => {
+ if (hiddenValue === undefined || hiddenValue === null) return visibleValue
+ if (visibleValue === undefined || visibleValue === null) return clonePurchasePayloadValue(hiddenValue)
+
+ if (Array.isArray(visibleValue) && Array.isArray(hiddenValue)) {
+ const maxLength = Math.max(visibleValue.length, hiddenValue.length)
+ const merged = []
+ for (let i = 0; i < maxLength; i++) {
+ merged[i] = mergePurchasePayloadWithHidden(visibleValue[i], hiddenValue[i])
+ }
+ return merged
+ }
+
+ if (
+ visibleValue &&
+ hiddenValue &&
+ typeof visibleValue === 'object' &&
+ typeof hiddenValue === 'object' &&
+ !Array.isArray(visibleValue) &&
+ !Array.isArray(hiddenValue)
+ ) {
+ const merged = { ...clonePurchasePayloadValue(hiddenValue) }
+ Object.entries(visibleValue).forEach(([key, item]) => {
+ merged[key] = mergePurchasePayloadWithHidden(item, merged[key])
+ })
+ return merged
+ }
+
+ return visibleValue
+}
+
+const localizePurchasePayload = (payload) => mapPayloadKeys(payload, purchasePayloadFieldLabelMap)
+
+const normalizePurchasePayload = (payload) => mapPayloadKeys(payload, purchasePayloadFieldKeyMap)
+
+const createPurchasePayloadNodeId = () => `purchase-node-${Date.now()}-${purchasePayloadTreeNodeSeed++}`
+
+const detectPurchaseValueType = (value) => {
+ if (Array.isArray(value)) return 'array'
+ if (value === null) return 'null'
+ const valueType = typeof value
+ if (valueType === 'number') return 'number'
+ if (valueType === 'boolean') return 'boolean'
+ if (valueType === 'object') return 'object'
+ return 'string'
+}
+
+const normalizePurchaseNodeValueForEdit = (value, valueType) => {
+ if (valueType === 'number') return value === null || value === undefined ? '' : String(value)
+ if (valueType === 'boolean') return Boolean(value)
+ if (valueType === 'null') return ''
+ return value === null || value === undefined ? '' : String(value)
+}
+
+const createPurchaseTreeNode = ({
+ key = '',
+ parentType = 'object',
+ keyEditable = false,
+ valueType = 'string',
+ value = '',
+ children = []
+} = {}) => ({
+ id: createPurchasePayloadNodeId(),
+ key,
+ parentType,
+ keyEditable,
+ valueType,
+ value,
+ children
+})
+
+const reorderPurchaseObjectEntries = (value) => {
+ const entries = Object.entries(value || {})
+ const productDataIndex = entries.findIndex(([key]) => key === 'productData')
+ if (productDataIndex <= -1 || productDataIndex === entries.length - 1) {
+ return entries
+ }
+ const [productDataEntry] = entries.splice(productDataIndex, 1)
+ entries.push(productDataEntry)
+ return entries
+}
+
+const buildPurchasePayloadTreeNodes = (value, parentType = 'object') => {
+ if (Array.isArray(value)) {
+ return value.map(item => {
+ const itemType = detectPurchaseValueType(item)
+ const node = createPurchaseTreeNode({
+ key: '',
+ parentType: 'array',
+ keyEditable: false,
+ valueType: itemType,
+ value: normalizePurchaseNodeValueForEdit(item, itemType)
+ })
+ if (purchaseContainerValueTypes.has(itemType)) {
+ node.children = buildPurchasePayloadTreeNodes(item, itemType)
+ }
+ return node
+ })
+ }
+
+ if (value && typeof value === 'object') {
+ return reorderPurchaseObjectEntries(value).map(([key, item]) => {
+ const itemType = detectPurchaseValueType(item)
+ const node = createPurchaseTreeNode({
+ key,
+ parentType,
+ keyEditable: false,
+ valueType: itemType,
+ value: normalizePurchaseNodeValueForEdit(item, itemType)
+ })
+ if (purchaseContainerValueTypes.has(itemType)) {
+ node.children = buildPurchasePayloadTreeNodes(item, itemType)
+ }
+ return node
+ })
+ }
+
+ return []
+}
+
+const initializePurchasePayloadTree = (messageObj, payload = {}) => {
+ const sourcePayload = payload && typeof payload === 'object' && !Array.isArray(payload)
+ ? payload
+ : {}
+ const { visible, hidden } = splitPurchasePayloadByVisibility(sourcePayload)
+ const visiblePayload = visible && typeof visible === 'object' && !Array.isArray(visible) ? visible : {}
+ messageObj.payloadTreeData = buildPurchasePayloadTreeNodes(visiblePayload, 'object')
+ messageObj.payloadHiddenData = hidden && typeof hidden === 'object' ? hidden : {}
+}
+
+const getPurchaseFieldLabel = (fieldKey) => purchasePayloadFieldLabelMap[fieldKey] || fieldKey || '瀛楁'
+
+const createPurchaseDefaultNode = (parentType = 'object') => createPurchaseTreeNode({
+ key: parentType === 'object' ? 'newField' : '',
+ parentType,
+ keyEditable: parentType === 'object',
+ valueType: 'string',
+ value: ''
+})
+
+const getPurchaseScalarNodeValue = (node) => {
+ if (node.valueType === 'null') return null
+ if (node.valueType === 'boolean') return Boolean(node.value)
+ if (node.valueType === 'number') {
+ const text = String(node.value ?? '').trim()
+ if (!text) return null
+ const numberValue = Number(text)
+ return Number.isFinite(numberValue) ? numberValue : text
+ }
+ return node.value === null || node.value === undefined ? '' : String(node.value)
+}
+
+const buildPurchasePayloadFromNodes = (nodes, parentType = 'object') => {
+ if (!Array.isArray(nodes)) {
+ return parentType === 'array' ? [] : {}
+ }
+
+ if (parentType === 'array') {
+ return nodes.map(node => {
+ if (purchaseContainerValueTypes.has(node.valueType)) {
+ return buildPurchasePayloadFromNodes(node.children, node.valueType)
+ }
+ return getPurchaseScalarNodeValue(node)
+ })
+ }
+
+ return nodes.reduce((result, node, index) => {
+ const rawKey = String(node.key ?? '').trim()
+ const key = rawKey || `field_${index + 1}`
+ if (purchaseContainerValueTypes.has(node.valueType)) {
+ result[key] = buildPurchasePayloadFromNodes(node.children, node.valueType)
+ } else {
+ result[key] = getPurchaseScalarNodeValue(node)
+ }
+ return result
+ }, {})
+}
+
+const findPurchaseNodeLocation = (nodes, targetId, parentNode = null) => {
+ if (!Array.isArray(nodes)) return null
+ for (let index = 0; index < nodes.length; index++) {
+ const node = nodes[index]
+ if (node.id === targetId) {
+ return {
+ siblings: nodes,
+ index,
+ node,
+ parentNode
+ }
+ }
+ const next = findPurchaseNodeLocation(node.children, targetId, node)
+ if (next) return next
+ }
+ return null
+}
+
+const getPurchaseArrayItemLabel = (row, message) => {
+ const location = findPurchaseNodeLocation(message?.payloadTreeData, row.id)
+ return `[${(location?.index ?? 0) + 1}]`
+}
+
+const handlePurchaseNodeTypeChange = (message, row) => {
+ if (!message || !row) return
+ if (purchaseContainerValueTypes.has(row.valueType)) {
+ row.children = []
+ row.value = ''
+ return
+ }
+ row.children = []
+ if (row.valueType === 'boolean') {
+ row.value = false
+ } else if (row.valueType === 'null') {
+ row.value = ''
+ } else {
+ row.value = ''
+ }
+}
+
+const addPurchaseRootField = (message) => {
+ if (!message) return
+ if (!Array.isArray(message.payloadTreeData)) {
+ message.payloadTreeData = []
+ }
+ message.payloadTreeData.push(createPurchaseDefaultNode('object'))
+}
+
+const addPurchaseChildNode = (message, row) => {
+ if (!message || !row || !purchaseContainerValueTypes.has(row.valueType)) return
+ if (!Array.isArray(row.children)) {
+ row.children = []
+ }
+ row.children.push(createPurchaseDefaultNode(row.valueType))
+}
+
+const addPurchaseSiblingNode = (message, row) => {
+ if (!message || !row) return
+ const location = findPurchaseNodeLocation(message.payloadTreeData, row.id)
+ if (!location || location.node.parentType !== 'array') return
+ location.siblings.splice(location.index + 1, 0, createPurchaseDefaultNode('array'))
+}
+
+const removePurchaseNode = (message, row) => {
+ if (!message || !row) return
+ const location = findPurchaseNodeLocation(message.payloadTreeData, row.id)
+ if (!location) return
+ location.siblings.splice(location.index, 1)
+}
+
+const hasPurchaseNodeValidationError = (nodes, parentType = 'object') => {
+ if (!Array.isArray(nodes)) return false
+ return nodes.some((node) => {
+ if (parentType === 'object' && !String(node.key ?? '').trim()) {
+ return true
+ }
+ if (node.valueType === 'number') {
+ const text = String(node.value ?? '').trim()
+ if (text && !Number.isFinite(Number(text))) {
+ return true
+ }
+ }
+ if (purchaseContainerValueTypes.has(node.valueType)) {
+ return hasPurchaseNodeValidationError(node.children, node.valueType)
+ }
+ return false
+ })
+}
+
+const purchaseDateFieldKeys = new Set([
+ 'entryDateStart',
+ 'entryDateEnd',
+ 'entryDate',
+ 'executionDate',
+ 'contractDate',
+ 'inputDate',
+ 'createdAt',
+ 'updatedAt'
+])
+
+const purchaseLedgerAllowedFieldKeys = new Set([
+ 'entryDateStart',
+ 'entryDateEnd',
+ 'id',
+ 'purchaseContractNumber',
+ 'supplierId',
+ 'supplierName',
+ 'isWhite',
+ 'recorderId',
+ 'recorderName',
+ 'salesContractNo',
+ 'salesContractNoId',
+ 'projectName',
+ 'entryDate',
+ 'executionDate',
+ 'remarks',
+ 'attachmentMaterials',
+ 'createdAt',
+ 'updatedAt',
+ 'salesLedgerId',
+ 'hasChildren',
+ 'Type',
+ 'productData',
+ 'tempFileIds',
+ 'SalesLedgerFiles',
+ 'phoneNumber',
+ 'businessPersonId',
+ 'productId',
+ 'productModelId',
+ 'invoiceNumber',
+ 'invoiceAmount',
+ 'ticketRegistrationId',
+ 'contractAmount',
+ 'receiptPaymentAmount',
+ 'unReceiptPaymentAmount',
+ 'type',
+ 'paymentMethod',
+ 'approvalStatus',
+ 'templateName'
+])
+
+const purchaseApprovalFieldKeys = new Set([
+ 'approveUserIds',
+ 'approverId',
+ 'auditors',
+ '瀹℃牳浜�',
+ '瀹℃壒浜�',
+ '瀹℃壒浜篒D',
+ '瀹℃壒鐢ㄦ埛ID鍒楄〃'
+])
+
+const normalizePurchaseProductRecord = (record) => {
+ if (!record || typeof record !== 'object' || Array.isArray(record)) return record
+ return mapPayloadKeys(record, purchasePayloadFieldKeyMap)
+}
+
+const getPurchaseProductMatchKey = (record) => {
+ if (!record || typeof record !== 'object') return ''
+ return record.purchaseContractNumber ||
+ record.purchaseContractNo ||
+ record.salesContractNo ||
+ record.salesContractNumber ||
+ ''
+}
+
+const mergeLegacyProductDataIntoLedgers = (payload) => {
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return payload
+ if (!Array.isArray(payload.purchaseLedgers) || !Array.isArray(payload.productData) || !payload.productData.length) {
+ return payload
+ }
+
+ const ledgers = payload.purchaseLedgers.map(ledger => ({
+ ...ledger,
+ productData: Array.isArray(ledger.productData)
+ ? ledger.productData.map(normalizePurchaseProductRecord)
+ : []
+ }))
+ const unmatchedProducts = []
+
+ payload.productData.map(normalizePurchaseProductRecord).forEach(product => {
+ const productMatchKey = getPurchaseProductMatchKey(product)
+ const matchedLedger = ledgers.find(ledger => {
+ const ledgerKeys = [
+ ledger.purchaseContractNumber,
+ ledger.purchaseContractNo,
+ ledger.salesContractNo,
+ ledger.salesContractNumber
+ ].filter(Boolean)
+ return productMatchKey && ledgerKeys.includes(productMatchKey)
+ })
+
+ if (matchedLedger) {
+ matchedLedger.productData.push(product)
+ } else if (ledgers.length === 1) {
+ ledgers[0].productData.push(product)
+ } else {
+ unmatchedProducts.push(product)
+ }
+ })
+
+ const nextPayload = {
+ ...payload,
+ purchaseLedgers: ledgers
+ }
+
+ if (unmatchedProducts.length) {
+ nextPayload.productData = unmatchedProducts
+ } else {
+ delete nextPayload.productData
+ }
+
+ return nextPayload
+}
+
+const filterPurchaseLedgerRecord = (record) => {
+ if (!record || typeof record !== 'object' || Array.isArray(record)) return record
+ const normalizedRecord = {
+ ...record,
+ productData: Array.isArray(record.productData)
+ ? record.productData.map(normalizePurchaseProductRecord)
+ : record.productData
+ }
+ return Object.entries(normalizedRecord).reduce((result, [key, value]) => {
+ if (purchaseLedgerAllowedFieldKeys.has(key)) {
+ result[key] = value
+ }
+ return result
+ }, {})
+}
+
+const sanitizePurchasePayloadForSubmit = (payload, businessType) => {
+ if (businessType !== 'purchase_ledger' || !payload || typeof payload !== 'object') return payload
+
+ const sanitized = mergeLegacyProductDataIntoLedgers(Array.isArray(payload) ? [...payload] : { ...payload })
+ if (Array.isArray(sanitized.purchaseLedgers)) {
+ sanitized.purchaseLedgers = sanitized.purchaseLedgers.map(filterPurchaseLedgerRecord)
+ }
+
+ purchaseApprovalFieldKeys.forEach(key => {
+ if (!Array.isArray(sanitized)) {
+ delete sanitized[key]
+ }
+ })
+
+ return sanitized
+}
+
+const getVisiblePurchaseMissingFields = (analysisData) => {
+ const fields = Array.isArray(analysisData?.missingFields) ? analysisData.missingFields : []
+ const visibleFields = analysisData?.businessType === 'purchase_ledger'
+ ? fields.filter(field => {
+ if (purchaseApprovalFieldKeys.has(field)) return false
+ const normalizedField = purchasePayloadFieldKeyMap[field] || field
+ return !shouldHidePurchaseField(normalizedField) && !shouldHidePurchaseField(field)
+ })
+ : fields
+ return visibleFields.map(field => purchasePayloadFieldLabelMap[field] || field)
+}
+
+const formatDateParts = (year, month, day) => {
+ const normalizedYear = Number(year)
+ const normalizedMonth = Number(month)
+ const normalizedDay = Number(day)
+ if (!normalizedYear || !normalizedMonth || !normalizedDay) return ''
+
+ const date = new Date(normalizedYear, normalizedMonth - 1, normalizedDay)
+ if (
+ date.getFullYear() !== normalizedYear ||
+ date.getMonth() !== normalizedMonth - 1 ||
+ date.getDate() !== normalizedDay
+ ) {
+ return ''
+ }
+
+ return [
+ String(normalizedYear).padStart(4, '0'),
+ String(normalizedMonth).padStart(2, '0'),
+ String(normalizedDay).padStart(2, '0')
+ ].join('-')
+}
+
+const normalizeDateString = (value) => {
+ if (typeof value !== 'string') return value
+ const text = value.trim()
+ if (!text) return value
+
+ let match = text.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s].*)?$/)
+ if (match) return formatDateParts(match[1], match[2], match[3]) || value
+
+ match = text.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})(?:\s.*)?$/)
+ if (match) return formatDateParts(match[1], match[2], match[3]) || value
+
+ match = text.match(/^(\d{4})骞�(\d{1,2})鏈�(\d{1,2})鏃�?(?:\s.*)?$/)
+ if (match) return formatDateParts(match[1], match[2], match[3]) || value
+
+ match = text.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})(?:\s.*)?$/)
+ if (match) {
+ const year = match[3].length === 2 ? Number(`20${match[3]}`) : Number(match[3])
+ return formatDateParts(year, match[1], match[2]) || value
+ }
+
+ return value
+}
+
+const normalizePurchasePayloadDates = (value, key = '') => {
+ if (Array.isArray(value)) {
+ return value.map(item => normalizePurchasePayloadDates(item, key))
+ }
+ if (value && typeof value === 'object') {
+ return Object.entries(value).reduce((result, [itemKey, item]) => {
+ result[itemKey] = normalizePurchasePayloadDates(item, itemKey)
+ return result
+ }, {})
+ }
+ return purchaseDateFieldKeys.has(key) ? normalizeDateString(value) : value
+}
+
+const isEmptyValue = (value) => {
+ if (value === null || value === undefined || value === '') return true
+ if (Array.isArray(value)) return value.every(item => isEmptyValue(item))
+ if (typeof value === 'object') return Object.values(value).every(item => isEmptyValue(item))
+ return false
+}
+
+const isPurchasePayloadEmpty = (payload) => isEmptyValue(payload)
+
+const getPurchaseConfirmDescription = (analysisData) => {
+ if (!analysisData) return ''
+ if (isPurchasePayloadEmpty(analysisData.payload)) {
+ return '鎴戞病鏈変粠鏂囦欢涓彁鍙栧埌瀹屾暣鐨勯噰璐笟鍔℃暟鎹紝鏆傛椂涓嶈兘鐩存帴鐢熸垚閲囪喘鍙拌处銆�'
+ }
+ return analysisData.description || '宸叉暣鐞嗗嚭寰呯‘璁ょ殑閲囪喘涓氬姟鏁版嵁锛岃鏍稿鍚庢彁浜ゃ��'
+}
+
+const confirmPurchaseAnalysis = async (message) => {
+ if (!message?.purchaseAnalysisData || message.confirming || message.confirmed) return
+
+ let payload
+ try {
+ const parsedPayload = message.payloadText?.trim() ? JSON.parse(message.payloadText) : {}
+ payload = sanitizePurchasePayloadForSubmit(
+ normalizePurchasePayloadDates(normalizePurchasePayload(parsedPayload)),
+ message.purchaseAnalysisData.businessType
+ )
+ } catch (err) {
+ message.confirmResult = '寰呮彁浜ゆ暟鎹笉鏄悎娉� JSON锛岃淇敼鍚庡啀纭'
+ message.confirmed = false
+ return
+ }
+
+ message.confirming = true
+ message.confirmResult = ''
+
+ try {
+ const res = await request.post(`${currentAssistant.value.apiBase}/analyze-files/confirm`, {
+ businessType: message.purchaseAnalysisData.businessType,
+ payload
+ })
+ message.confirmed = true
+ message.confirmResult = res?.msg || '纭鎴愬姛锛屼笟鍔″鐞嗗凡鎻愪氦'
+ ElMessage.success(message.confirmResult)
+ } catch (err) {
+ message.confirmed = false
+ message.confirmResult = err?.message || '纭澶辫触锛岃妫�鏌ユ暟鎹悗閲嶈瘯'
+ } finally {
+ message.confirming = false
+ }
+}
+
+const confirmPurchaseAnalysisFromTable = async (message) => {
+ if (!message?.purchaseAnalysisData || message.confirming || message.confirmed) return
+
+ if (!Array.isArray(message.payloadTreeData)) {
+ initializePurchasePayloadTree(message, message.purchaseAnalysisData.payload || {})
+ }
+ if (hasPurchaseNodeValidationError(message.payloadTreeData, 'object')) {
+ message.confirmResult = '璇峰厛琛ュ叏瀛楁鍚嶏紝骞剁‘淇濇暟瀛楀瓧娈靛~鍐欏悎娉曟暟瀛�'
+ message.confirmed = false
+ return
+ }
+
+ let payload
+ try {
+ const draftPayload = buildPurchasePayloadFromNodes(message.payloadTreeData, 'object')
+ const mergedPayload = mergePurchasePayloadWithHidden(draftPayload, message.payloadHiddenData)
+ const normalizedPayload = normalizePurchasePayload(mergedPayload)
+ payload = sanitizePurchasePayloadForSubmit(
+ normalizePurchasePayloadDates(normalizedPayload),
+ message.purchaseAnalysisData.businessType
+ )
+ message.payloadText = JSON.stringify(localizePurchasePayload(normalizedPayload), null, 2)
+ } catch (err) {
+ message.confirmResult = '寰呮彁浜ゆ暟鎹牸寮忔湁璇紝璇锋鏌ュ悗鍐嶇‘璁�'
+ message.confirmed = false
+ return
+ }
+
+ message.confirming = true
+ message.confirmResult = ''
+
+ try {
+ const res = await request.post(`${currentAssistant.value.apiBase}/analyze-files/confirm`, {
+ businessType: message.purchaseAnalysisData.businessType,
+ payload
+ })
+ message.confirmed = true
+ message.confirmResult = res?.msg || '纭鎴愬姛锛屼笟鍔″鐞嗗凡鎻愪氦'
+ ElMessage.success(message.confirmResult)
+ } catch (err) {
+ message.confirmed = false
+ message.confirmResult = err?.message || '纭澶辫触锛岃妫�鏌ユ暟鎹悗閲嶈瘯'
+ } finally {
+ message.confirming = false
+ }
+}
+
+const scrollToBottom = () => {
+ nextTick(() => {
+ if (messageListRef.value) {
+ messageListRef.value.scrollTop = messageListRef.value.scrollHeight
+ }
+ })
+}
+
+const handleFileChange = (file, fileList = []) => {
+ if (!file) return
+ const nextFiles = currentAssistant.value.allowMultipleFileUpload
+ ? fileList.map(item => item.raw).filter(Boolean)
+ : [file.raw].filter(Boolean)
+
+ const validFiles = nextFiles.filter(rawFile => {
+ const isLt10M = rawFile.size / 1024 / 1024 < 10
+ if (!isLt10M) {
+ ElMessage.error(`${rawFile.name} 鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!`)
+ }
+ return isLt10M
+ })
+
+ selectedFiles.value = validFiles
+ uploadFileList.value = fileList.filter(item => item.raw && validFiles.includes(item.raw))
+}
+
+const removeSelectedFile = (index) => {
+ selectedFiles.value.splice(index, 1)
+ uploadFileList.value.splice(index, 1)
+}
+
+const analyzeFiles = async (files, message = '') => {
+ const uploadFiles = Array.isArray(files) ? files : [files].filter(Boolean)
+ if (!uploadFiles.length) return
+ if (isSending.value) return
+ isSending.value = true
+ currentAbortController.value = new AbortController()
+
+ const fileNames = uploadFiles.map(file => file.name).join('銆�')
+ const userMsg = message ? `${message}\n[涓婁紶鏂囦欢鍒嗘瀽] ${fileNames}` : `[涓婁紶鏂囦欢鍒嗘瀽] ${fileNames}`
+ messages.value.push({
+ isUser: true,
+ content: userMsg,
+ htmlContent: convertTextToHtml(userMsg),
+ isTyping: false
+ })
+
+ const botMsgIndex = messages.value.length
+ messages.value.push({
+ isUser: false,
+ content: '',
+ htmlContent: '',
+ isTyping: true,
+ chartOptions: null,
+ chartRenderReady: false,
+ type: '',
+ tableData: null,
+ payloadTreeData: null,
+ payloadHiddenData: null
+ })
+
+ outputState.value[botMsgIndex] = {
+ isPaused: false,
+ jsonBlockStartPos: -1,
+ jsBlockStartPos: -1,
+ blockEndPos: -1,
+ hasRenderedChart: false
+ }
+
+ scrollToBottom()
+
+ const formData = new FormData()
+ const fileFieldName = currentAssistant.value.allowMultipleFileUpload ? 'files' : 'file'
+ uploadFiles.forEach(file => formData.append(fileFieldName, file))
+ formData.append('memoryId', uuid.value)
+ if (message.trim()) {
+ formData.append('message', message.trim())
+ }
+
+ const analyzeUrl = currentAssistant.value.fileAnalyzeUrl || `${currentAssistant.value.apiBase}/analyze-file`
+ request.post(analyzeUrl, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ },
+ signal: currentAbortController.value.signal,
+ onDownloadProgress: (e) => {
+ const fullText = e.target ? e.target.responseText : (e.event ? e.event.target.responseText : '')
+ if (!fullText) return
+
+ const currentMsg = messages.value[botMsgIndex]
+ if (!currentMsg) return
+
+ currentMsg.content = fullText
+
+ // 瑙f瀽 JSON 鏁版嵁锛堥拡瀵瑰祵鍏ュ紡 JSON锛�
+ const extracted = extractEmbeddedSuccessJson(fullText)
+ if (extracted) {
+ applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex, !outputState.value[botMsgIndex].hasRenderedChart)
+ }
+
+ updateOutputState(fullText, botMsgIndex)
+ currentMsg.htmlContent = convertStreamOutput(fullText, botMsgIndex)
+ scrollToBottom()
+ }
+ }).then(() => {
+ const currentMsg = messages.value[botMsgIndex]
+ currentMsg.isTyping = false
+ isSending.value = false
+ currentAbortController.value = null
+
+ const extracted = extractEmbeddedSuccessJson(currentMsg.content)
+ if (extracted) {
+ applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex, !outputState.value[botMsgIndex].hasRenderedChart)
+ }
+
+ // 鏈�缁堣В鏋愮‘淇濆浘琛ㄦ覆鏌�
+ if (currentMsg.chartOptions && !outputState.value[botMsgIndex].hasRenderedChart) {
+ renderCharts(botMsgIndex, currentMsg.chartOptions)
+ outputState.value[botMsgIndex].hasRenderedChart = true
+ }
+ }).catch(err => {
+ if (err.name === 'CanceledError' || err.name === 'AbortError') {
+ console.log('Analysis aborted by user')
+ isSending.value = false
+ currentAbortController.value = null
+ return
+ }
+ console.error('File analysis error:', err)
+ const errorMsg = '鎶辨瓑锛屾枃浠跺垎鏋愯繃绋嬩腑閬囧埌浜嗕竴鐐归棶棰橈紝璇风◢鍚庡啀璇曘��'
+ if (messages.value[botMsgIndex]) {
+ messages.value[botMsgIndex].content = errorMsg
+ messages.value[botMsgIndex].htmlContent = convertTextToHtml(errorMsg)
+ messages.value[botMsgIndex].isTyping = false
+ }
+ isSending.value = false
+ currentAbortController.value = null
+ })
+}
+
+const sendMessage = () => {
+ const msg = inputMessage.value?.trim() || ''
+ if ((msg || selectedFiles.value.length) && !isSending.value) {
+ if (selectedFiles.value.length) {
+ analyzeFiles([...selectedFiles.value], msg)
+ selectedFiles.value = []
+ uploadFileList.value = []
+ } else {
+ sendRequest(msg)
+ }
+ inputMessage.value = ''
+ }
+}
+
+const stopGeneration = () => {
+ abortCurrentRequest()
+}
+
+const sendRequest = (message) => {
+ isSending.value = true
+ currentAbortController.value = new AbortController()
+
+ // 鐢ㄦ埛娑堟伅
+ messages.value.push({
+ isUser: true,
+ content: message,
+ htmlContent: convertTextToHtml(message),
+ isTyping: false
+ })
+
+ // 鏈哄櫒浜哄崰浣�
+ const botMsgIndex = messages.value.length
+ const botMsg = {
+ isUser: false,
+ content: '',
+ htmlContent: '',
+ isTyping: true,
+ chartOptions: null,
+ chartRenderReady: false,
+ type: '',
+ tableData: null,
+ payloadTreeData: null,
+ payloadHiddenData: null
+ }
+ messages.value.push(botMsg)
+
+ outputState.value[botMsgIndex] = {
+ isPaused: false,
+ jsonBlockStartPos: -1,
+ jsBlockStartPos: -1,
+ blockEndPos: -1,
+ hasRenderedChart: false
+ }
+
+ scrollToBottom()
+
+ request.post(`${currentAssistant.value.apiBase}/chat`,
+ { memoryId: uuid.value, message },
+ {
+ signal: currentAbortController.value.signal,
+ onDownloadProgress: (e) => {
+ // 鍏煎涓嶅悓鐗堟湰鐨� axios 鑾峰彇鍝嶅簲鏂囨湰鐨勬柟寮�
+ const fullText = e.target ? e.target.responseText : (e.event ? e.event.target.responseText : '')
+ if (!fullText) return
+
+ const currentMsg = messages.value[botMsgIndex]
+ if (!currentMsg) return
+
+ currentMsg.content = fullText
+
+ // 灏濊瘯鎻愬彇骞惰В鏋� JSON
+ const extracted = extractEmbeddedSuccessJson(fullText)
+ if (extracted) {
+ applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
+ } else {
+ const extractJson = (text) => {
+ const startIdx = text.indexOf('{"success": true')
+ if (startIdx === -1) return null
+
+ // 浠庡悗寰�鍓嶆壘鏈�鍚庝竴涓� '}'
+ const lastBraceIdx = text.lastIndexOf('}')
+ if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
+
+ const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
+ try {
+ return JSON.parse(potentialJson)
+ } catch (err) {
+ return null
+ }
+ }
+
+ const parsedData = extractJson(fullText)
+ if (parsedData) {
+ applyStructuredMessageData(currentMsg, parsedData, botMsgIndex, true)
+ }
+
+ }
+
+ updateOutputState(fullText, botMsgIndex)
+ currentMsg.htmlContent = convertStreamOutput(fullText, botMsgIndex)
+ scrollToBottom()
+ }
+ }
+ ).then(() => {
+ const currentMsg = messages.value[botMsgIndex]
+ currentMsg.isTyping = false
+ isSending.value = false
+ currentAbortController.value = null
+
+ // 鏈�缁堣В鏋�
+ const extracted = extractEmbeddedSuccessJson(currentMsg.content)
+ if (extracted) {
+ applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
+ } else {
+ const extractJson = (text) => {
+ const startIdx = text.indexOf('{"success": true')
+ if (startIdx === -1) return null
+ const lastBraceIdx = text.lastIndexOf('}')
+ if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
+ const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
+ try {
+ return JSON.parse(potentialJson)
+ } catch (err) {
+ return null
+ }
+ }
+
+ const finalParsed = extractJson(currentMsg.content)
+ if (finalParsed) {
+ applyStructuredMessageData(currentMsg, finalParsed, botMsgIndex)
+ }
+ }
+ }).catch(err => {
+ if (err.name === 'CanceledError' || err.name === 'AbortError') {
+ console.log('Request aborted by user')
+ return
+ }
+ console.error('AI Chat Error:', err)
+ const errorMsg = '鎶辨瓑锛屾垜鐜板湪閬囧埌浜嗕竴鐐归棶棰橈紝璇风◢鍚庡啀璇曘��'
+ if (messages.value[botMsgIndex]) {
+ messages.value[botMsgIndex].content = errorMsg
+ messages.value[botMsgIndex].htmlContent = convertTextToHtml(errorMsg)
+ messages.value[botMsgIndex].isTyping = false
+ }
+ isSending.value = false
+ currentAbortController.value = null
+ })
+}
+
+const updateOutputState = (text, msgIndex) => {
+ const state = outputState.value[msgIndex]
+ if (state.jsonBlockStartPos === -1) {
+ const pos = text.indexOf('```json')
+ if (pos !== -1) { state.jsonBlockStartPos = pos; state.isPaused = true }
+ }
+ if (state.jsBlockStartPos === -1) {
+ const pos = text.indexOf('```javascript') !== -1 ? text.indexOf('```javascript') : text.indexOf('```js')
+ if (pos !== -1) { state.jsBlockStartPos = pos; state.isPaused = true }
+ }
+ if ((state.jsonBlockStartPos !== -1 || state.jsBlockStartPos !== -1) && state.blockEndPos === -1) {
+ const startCheck = state.jsonBlockStartPos !== -1 ? state.jsonBlockStartPos + 7 : state.jsBlockStartPos + (text.includes('javascript') ? 13 : 5)
+ const endPos = text.indexOf('```', startCheck)
+ if (endPos !== -1) { state.blockEndPos = endPos + 3; state.isPaused = false }
+ }
+}
+
+const convertTextToHtml = (text) => {
+ if (!text) return ''
+ return text
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/\n/g, '<br>')
+}
+
+const convertStreamOutput = (output, msgIndex) => {
+ if (!output) return ''
+ const state = outputState.value[msgIndex]
+ let display = output
+
+ // 灏濊瘯鎻愬彇 JSON 閮ㄥ垎
+ const extracted = extractEmbeddedSuccessJson(output)
+ const startMatch = output.match(/\{\s*"success"\s*:/)
+ const startIdx = extracted ? extracted.startIdx : (startMatch?.index ?? -1)
+
+ // 濡傛灉杩樺湪浠g爜鍧椾腑涓旀湭缁撴潫锛屾樉绀烘彁绀烘枃瀛�
+ if (state && ((state.jsonBlockStartPos !== -1) || (state.jsBlockStartPos !== -1)) && state.blockEndPos === -1) {
+ const startPos = state.jsonBlockStartPos !== -1 ? state.jsonBlockStartPos : state.jsBlockStartPos
+ const textBeforeBlock = display.substring(0, startPos).trim()
+ display = textBeforeBlock || '姝e湪鍒嗘瀽鏁版嵁骞剁敓鎴愬浘琛�...'
+ return convertTextToHtml(display)
+ }
+
+ if (extracted) {
+ const parsed = extracted.data
+ display = (output.substring(0, extracted.startIdx) + output.substring(extracted.endIdx)).trim()
+
+ if (/^[\s}\],锛屻��.:锛�;锛沒+$/.test(display)) {
+ display = ''
+ }
+
+ if (parsed.description) {
+ display = parsed.action === 'confirm_required'
+ ? getPurchaseConfirmDescription(parsed)
+ : parsed.description
+ }
+
+ if (!display) {
+ if (parsed.type === 'todo_list') {
+ display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+ } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
+ display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
+ } else {
+ display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+ }
+ }
+ } else if (startIdx !== -1) {
+ const lastBraceIdx = output.lastIndexOf('}')
+ if (lastBraceIdx !== -1 && lastBraceIdx > startIdx) {
+ const potentialJson = output.substring(startIdx, lastBraceIdx + 1)
+ try {
+ const parsed = JSON.parse(potentialJson)
+ // 鎴愬姛瑙f瀽锛岀Щ闄� JSON 閮ㄥ垎鏄剧ず鏂囧瓧
+ display = (output.substring(0, startIdx) + output.substring(lastBraceIdx + 1)).trim()
+
+ if (/^[\s}\],锛屻��.:锛�;锛沒+$/.test(display)) {
+ display = ''
+ }
+
+ if (parsed.description) {
+ display = parsed.action === 'confirm_required'
+ ? getPurchaseConfirmDescription(parsed)
+ : parsed.description
+ }
+
+ if (!display) {
+ if (parsed.type === 'todo_list') {
+ display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁锛�'
+ } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
+ display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛細'
+ } else {
+ display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+ }
+ }
+ } catch (e) {
+ // 瑙f瀽澶辫触锛岃鏄� JSON 杩樺湪浼犺緭涓垨鏍煎紡涓嶆纭�
+ display = output.substring(0, startIdx).trim() || '姝e湪鍒嗘瀽鏁版嵁骞剁敓鎴愬浘琛�...'
+ }
+ } else {
+ // 鎵惧埌浜嗗紑濮嬩絾杩樻病鎵惧埌缁撴潫
+ display = output.substring(0, startIdx).trim() || '姝e湪鍒嗘瀽鏁版嵁骞剁敓鎴愬浘琛�...'
+ }
+ }
+
+ let html = convertTextToHtml(display)
+
+ // 杩樺師浠g爜鍧�
+ html = html.replace(/```(javascript|js)([\s\S]*?)```/g, '<pre class="code-block js-code">$2</pre>')
+ html = html.replace(/```([\s\S]*?)```/g, '<pre class="code-block">$1</pre>')
+
+ return html || '...'
+}
+
+const renderCharts = (msgIndex, chartOptions) => {
+ nextTick(() => {
+ Object.keys(chartOptions).forEach(key => {
+ const id = `ai-chart-${msgIndex}-${key}`
+ const tryInit = (count = 0) => {
+ const dom = document.getElementById(id)
+ if (dom) {
+ if (chartInstances.value[id]) {
+ // 濡傛灉宸茬粡鍒濆鍖栬繃锛岀洿鎺ユ洿鏂版暟鎹�
+ const chart = chartInstances.value[id]
+ const option = normalizeAiChartOption(chartOptions[key])
+ if (option) chart.setOption(option)
+ return
+ }
+
+ const chart = echarts.init(dom)
+ chartInstances.value[id] = chart
+ const option = normalizeAiChartOption(chartOptions[key])
+ if (option) {
+ chart.setOption(option)
+ } else {
+ console.warn('Invalid chart option for:', id, chartOptions[key])
+ }
+
+ const handler = () => chart.resize()
+ resizeHandlers.value.push(handler)
+ window.addEventListener('resize', handler)
+ } else if (count < 15) { // 绋嶅井澧炲姞閲嶈瘯娆℃暟
+ setTimeout(() => tryInit(count + 1), 200)
+ }
+ }
+ tryInit()
+ })
+ })
+}
+
+// 鏍煎紡鍖� AI 杩斿洖鐨勫浘琛ㄩ厤缃紝灏嗗叾杞崲涓烘爣鍑嗙殑 ECharts 閰嶇疆
+const formatChartOption = (rawOption) => {
+ if (!rawOption) return null
+
+ // 濡傛灉宸茬粡鏄爣鍑� ECharts 閰嶇疆锛堝寘鍚� series锛夛紝鍒欑洿鎺ヨ繑鍥�
+ const hasSeries = rawOption.series && Array.isArray(rawOption.series)
+
+ // 灏濊瘯杞崲绠�鏄撴牸寮�
+ try {
+ const isPie = rawOption.type === 'pie' || (rawOption.title && rawOption.title.includes('鍗犳瘮'))
+
+ const option = {
+ title: {
+ text: rawOption.title || '',
+ left: 'center',
+ textStyle: { fontSize: 14 }
+ },
+ tooltip: {
+ trigger: isPie ? 'item' : 'axis'
+ },
+ legend: {
+ bottom: '0'
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '15%',
+ containLabel: true
+ },
+ xAxis: isPie ? undefined : {
+ type: 'category',
+ data: rawOption.xAxisData || (Array.isArray(rawOption.xAxis) ? rawOption.xAxis : []),
+ name: typeof rawOption.xAxis === 'string' ? rawOption.xAxis : ''
+ },
+ yAxis: isPie ? undefined : {
+ type: 'value',
+ name: typeof rawOption.yAxis === 'string' ? rawOption.yAxis : ''
+ },
+ series: rawOption.series || [{
+ name: rawOption.title || '鏁板��',
+ type: rawOption.type || 'line',
+ data: rawOption.seriesData || (Array.isArray(rawOption.data) ? rawOption.data : []),
+ smooth: true,
+ radius: isPie ? '50%' : undefined
+ }]
+ }
+
+ // 閽堝楗煎浘鐨勭壒娈婂鐞�
+ if (isPie && !option.series[0].data && Array.isArray(rawOption.data)) {
+ option.series[0].data = rawOption.data
+ }
+
+ return option
+ } catch (err) {
+ console.error('Chart option conversion failed:', err)
+ return null
+ }
+}
+
+const normalizeAiChartOption = (rawOption) => {
+ if (!rawOption) return null
+
+ try {
+ const hasSeries = Array.isArray(rawOption.series) && rawOption.series.length > 0
+ const firstSeriesType = hasSeries ? rawOption.series[0]?.type : rawOption.type
+ const titleConfig = rawOption.title && typeof rawOption.title === 'object'
+ ? rawOption.title
+ : null
+ const tooltipConfig = rawOption.tooltip && typeof rawOption.tooltip === 'object'
+ ? rawOption.tooltip
+ : null
+ const legendConfig = rawOption.legend && typeof rawOption.legend === 'object'
+ ? rawOption.legend
+ : null
+ const rawXAxisConfig = rawOption.xAxis && typeof rawOption.xAxis === 'object' && !Array.isArray(rawOption.xAxis)
+ ? rawOption.xAxis
+ : null
+ const rawYAxisConfig = rawOption.yAxis && typeof rawOption.yAxis === 'object' && !Array.isArray(rawOption.yAxis)
+ ? rawOption.yAxis
+ : null
+ const titleText = typeof rawOption.title === 'string' ? rawOption.title : rawOption.title?.text || ''
+ const isPie = firstSeriesType === 'pie' || titleText.includes('鍗犳瘮')
+ const baseXAxisData = Array.isArray(rawOption.xAxisData)
+ ? rawOption.xAxisData
+ : (Array.isArray(rawOption.xAxis) ? rawOption.xAxis : (Array.isArray(rawXAxisConfig?.data) ? rawXAxisConfig.data : []))
+ const fallbackSeries = [{
+ name: titleText || '鏁版嵁',
+ type: rawOption.type || 'line',
+ data: Array.isArray(rawOption.seriesData) ? rawOption.seriesData : (Array.isArray(rawOption.data) ? rawOption.data : [])
+ }]
+ const normalizedSeries = (hasSeries ? rawOption.series : fallbackSeries).map((seriesItem, index) => {
+ const seriesType = seriesItem?.type || rawOption.type || 'line'
+ const nextSeries = {
+ ...seriesItem,
+ name: seriesItem?.name || titleText || `绯诲垪${index + 1}`,
+ type: seriesType
+ }
+
+ if (isPie) {
+ nextSeries.radius = nextSeries.radius || '55%'
+ nextSeries.data = Array.isArray(nextSeries.data) ? nextSeries.data : (Array.isArray(rawOption.data) ? rawOption.data : [])
+ } else {
+ nextSeries.smooth = typeof nextSeries.smooth === 'boolean' ? nextSeries.smooth : seriesType === 'line'
+ nextSeries.data = Array.isArray(nextSeries.data) ? nextSeries.data : []
+ }
+
+ return nextSeries
+ })
+ const categorySource = !isPie
+ ? normalizedSeries.find(seriesItem => Array.isArray(seriesItem.data) && seriesItem.data.every(item => item && typeof item === 'object' && 'name' in item && 'value' in item))
+ : null
+ const xAxisData = categorySource
+ ? categorySource.data.map(item => item.name)
+ : baseXAxisData
+ const finalSeries = !isPie && categorySource
+ ? normalizedSeries.map(seriesItem => ({
+ ...seriesItem,
+ data: Array.isArray(seriesItem.data)
+ ? seriesItem.data.map(item => (item && typeof item === 'object' && 'value' in item ? item.value : item))
+ : []
+ }))
+ : normalizedSeries
+
+ return {
+ title: titleConfig || {
+ text: titleText,
+ left: 'center',
+ textStyle: { fontSize: 14 }
+ },
+ tooltip: tooltipConfig || {
+ trigger: isPie ? 'item' : 'axis'
+ },
+ legend: legendConfig || {
+ bottom: '0'
+ },
+ grid: isPie ? undefined : {
+ left: '3%',
+ right: '4%',
+ bottom: '15%',
+ containLabel: true
+ },
+ xAxis: isPie ? undefined : {
+ ...(rawXAxisConfig || {}),
+ type: 'category',
+ data: xAxisData,
+ name: typeof rawOption.xAxis === 'string' ? rawOption.xAxis : (rawXAxisConfig?.name || '')
+ },
+ yAxis: isPie ? undefined : (rawYAxisConfig || {
+ type: 'value',
+ name: typeof rawOption.yAxis === 'string' ? rawOption.yAxis : ''
+ }),
+ series: finalSeries
+ }
+ } catch (err) {
+ console.error('AI chart normalization failed:', err, rawOption)
+ return formatChartOption(rawOption)
+ }
+}
+
+watch(messages, () => scrollToBottom(), { deep: true })
+</script>
+
+<style lang="scss">
+.ai-chat-overlay {
+ pointer-events: none !important;
+ background: transparent !important;
+}
+
+.ai-chat-overlay .el-drawer {
+ pointer-events: auto;
+}
+</style>
+
+<style scoped lang="scss">
+$primary-blue: #0055d4;
+$secondary-blue: #2e8ce0;
+$light-blue: #7ab8ff;
+$pale-blue: #c5dcff;
+$ice-white: #e8f2ff;
+$deep-blue: #003b8e;
+$deepest-blue: #002b66;
+$gradient-blue: linear-gradient(145deg, #004fc7 0%, #0066e0 40%, #2580e8 70%, #5a9fe0 100%);
+$gradient-dark: linear-gradient(145deg, #003b8e 0%, #0055d4 50%, #0077e8 100%);
+$gradient-ice: linear-gradient(180deg, #e0ecff 0%, #d4e5ff 50%, #e8f0ff 100%);
+$shadow-blue: 0 8px 40px rgba(0, 85, 212, 0.35);
+$shadow-deep: 0 12px 48px rgba(0, 40, 120, 0.4);
+$shadow-card: 0 6px 24px rgba(0, 51, 136, 0.12);
+
+.ai-chat-sidebar-wrapper {
+ position: static;
+ z-index: 2000;
+ pointer-events: auto;
+
+ :deep(.el-drawer) {
+ pointer-events: auto;
+ }
+}
+
+.ai-chat-trigger {
+ pointer-events: auto;
+ position: fixed;
+ right: 24px;
+ bottom: 100px;
+ width: 56px;
+ height: 56px;
+ background: $gradient-dark;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ box-shadow: $shadow-deep, 0 0 0 2px rgba(0, 85, 212, 0.3) inset, 0 0 30px rgba(0, 119, 232, 0.2);
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ z-index: 2001;
+ animation: triggerPulse 3s ease-in-out infinite;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: -6px;
+ background: linear-gradient(135deg, rgba(0, 85, 212, 0.4), rgba(0, 136, 232, 0.3), rgba(90, 159, 224, 0.2));
+ border-radius: 50%;
+ z-index: -1;
+ filter: blur(16px);
+ animation: glowPulse 2s ease-in-out infinite alternate;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border-radius: 50%;
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.3) 0%, transparent 50%);
+ pointer-events: none;
+ }
+
+ &:hover {
+ transform: scale(1.12) translateY(-4px);
+ box-shadow: $shadow-deep, 0 0 0 3px rgba(0, 136, 232, 0.4) inset, 0 0 50px rgba(0, 136, 232, 0.3);
+
+ &::before {
+ animation: glowPulse 1s ease-in-out infinite alternate;
+ }
+
+ .trigger-icon {
+ transform: rotate(-8deg) scale(1.05);
+ filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5));
+ }
+ }
+
+ .trigger-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ color: #fff;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
+ }
+}
+
+@keyframes triggerPulse {
+ 0%, 100% {
+ box-shadow: $shadow-blue, 0 0 0 2px rgba(0, 85, 212, 0.25) inset, 0 0 20px rgba(0, 119, 232, 0.15);
+ }
+ 50% {
+ box-shadow: $shadow-deep, 0 0 0 3px rgba(0, 136, 232, 0.35) inset, 0 0 40px rgba(0, 136, 232, 0.25);
+ }
+}
+
+@keyframes glowPulse {
+ 0% {
+ opacity: 0.6;
+ transform: scale(1);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1.1);
+ }
+}
+
+.ai-chat-drawer {
+ :deep(.el-drawer__body) {
+ padding: 0;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+ :deep(.el-drawer__header) {
+ margin-bottom: 0;
+ padding: 0;
+ background: $gradient-dark;
+ color: #fff;
+ }
+}
+
+.drawer-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ padding: 18px 20px;
+ background: $gradient-dark;
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: -60%;
+ right: -25%;
+ width: 250px;
+ height: 250px;
+ background: radial-gradient(circle, rgba(0, 136, 232, 0.4) 0%, transparent 70%);
+ pointer-events: none;
+ animation: headerGlow 4s ease-in-out infinite alternate;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: -40%;
+ left: -15%;
+ width: 200px;
+ height: 200px;
+ background: radial-gradient(circle, rgba(0, 85, 212, 0.3) 0%, transparent 70%);
+ pointer-events: none;
+ animation: headerGlow 5s ease-in-out infinite alternate-reverse;
+ }
+
+ .header-left {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ position: relative;
+ z-index: 1;
+
+ .header-icon {
+ color: rgba(255, 255, 255, 0.95);
+ filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.2));
+ animation: iconFloat 3s ease-in-out infinite;
+ }
+
+ .title {
+ font-size: 17px;
+ font-weight: 600;
+ color: #fff;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
+ letter-spacing: 0.5px;
+ }
+ }
+
+ .header-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ position: relative;
+ z-index: 1;
+
+ .action-divider {
+ width: 1px;
+ height: 16px;
+ background: rgba(255, 255, 255, 0.2);
+ margin: 0 2px;
+ }
+
+ :deep(.el-button) {
+ color: rgba(255, 255, 255, 0.85);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ background: rgba(255, 255, 255, 0.12);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+ padding: 8px;
+ height: 32px;
+ width: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ color: #fff;
+ background: rgba(255, 255, 255, 0.25);
+ border-color: rgba(255, 255, 255, 0.3);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ }
+
+ &:active {
+ transform: translateY(0);
+ }
+
+ &.close-btn {
+ background: rgba(255, 255, 255, 0.1);
+ &:hover {
+ background: rgba(245, 108, 108, 0.8);
+ border-color: rgba(245, 108, 108, 0.5);
+ }
+ }
+ }
+
+ :deep(.header-action-btn) {
+ position: relative;
+ overflow: hidden;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.08));
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 18px rgba(0, 0, 0, 0.12);
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), transparent 55%);
+ pointer-events: none;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: -120%;
+ left: -40%;
+ width: 60%;
+ height: 260%;
+ background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.28), transparent);
+ transform: rotate(24deg);
+ opacity: 0;
+ transition: all 0.35s ease;
+ }
+
+ &:hover::after {
+ left: 100%;
+ opacity: 1;
+ }
+ }
+ }
+
+ .assistant-switcher {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+ padding: 0 12px;
+ position: relative;
+ z-index: 1;
+
+ :deep(.el-radio-group) {
+ display: flex;
+ gap: 6px;
+ flex-wrap: wrap;
+ justify-content: center;
+ padding: 4px;
+ border-radius: 999px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.08));
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 18px rgba(0, 0, 0, 0.1);
+ }
+
+ :deep(.el-radio-button__inner) {
+ border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ background: rgba(255, 255, 255, 0.12);
+ color: rgba(255, 255, 255, 0.86);
+ box-shadow: none;
+ padding: 7px 14px;
+ font-weight: 500;
+ }
+
+ :deep(.el-radio-button:first-child .el-radio-button__inner),
+ :deep(.el-radio-button:last-child .el-radio-button__inner) {
+ border-radius: 999px;
+ }
+
+ :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
+ background: #fff;
+ color: $primary-blue;
+ border-color: #fff;
+ box-shadow: 0 6px 14px rgba(0, 40, 120, 0.16);
+ }
+ }
+}
+
+@keyframes headerGlow {
+ 0% {
+ opacity: 0.6;
+ transform: scale(1);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1.15);
+ }
+}
+
+@keyframes iconFloat {
+ 0%, 100% {
+ transform: translateY(0) rotate(0);
+ }
+ 50% {
+ transform: translateY(-2px) rotate(3deg);
+ }
+}
+
+.chat-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ background: $ice-white;
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 240px;
+ background: linear-gradient(180deg, rgba(0, 85, 212, 0.06) 0%, transparent 100%);
+ pointer-events: none;
+ }
+}
+
+.history-panel {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, #fff 0%, $ice-white 100%);
+ z-index: 10;
+ display: flex;
+ flex-direction: column;
+ box-shadow: -8px 0 32px rgba(0, 85, 212, 0.15);
+
+ .history-header {
+ padding: 18px 20px;
+ border-bottom: 1px solid rgba(0, 85, 212, 0.12);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: 600;
+ font-size: 14px;
+ color: $deep-blue;
+ background: linear-gradient(135deg, rgba(0, 85, 212, 0.08) 0%, rgba(0, 136, 232, 0.05) 100%);
+ position: relative;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(0, 85, 212, 0.2), transparent);
+ }
+ }
+
+ .session-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px 16px;
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: linear-gradient(180deg, $secondary-blue, $primary-blue);
+ border-radius: 4px;
+ box-shadow: 0 0 6px rgba(0, 85, 212, 0.25);
+ }
+
+ .session-item {
+ display: flex;
+ align-items: center;
+ padding: 14px 16px;
+ margin-bottom: 6px;
+ border-radius: 12px;
+ cursor: pointer;
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ gap: 12px;
+ position: relative;
+ border: 1px solid transparent;
+ background: #fff;
+ animation: sessionSlideIn 0.35s ease;
+
+ @keyframes sessionSlideIn {
+ from {
+ opacity: 0;
+ transform: translateX(-15px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+
+ &:hover {
+ background: linear-gradient(135deg, rgba(0, 85, 212, 0.06) 0%, rgba(0, 136, 232, 0.08) 100%);
+ border-color: rgba(0, 85, 212, 0.12);
+ box-shadow: 0 4px 16px rgba(0, 85, 212, 0.1);
+ transform: translateX(4px);
+
+ .delete-btn {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ &.active {
+ background: linear-gradient(135deg, rgba(0, 85, 212, 0.12) 0%, rgba(0, 136, 232, 0.15) 100%);
+ border-color: rgba(0, 85, 212, 0.25);
+ color: $primary-blue;
+ box-shadow: 0 4px 16px rgba(0, 85, 212, 0.15);
+
+ .el-icon {
+ color: $primary-blue;
+ }
+ }
+
+ .el-icon {
+ font-size: 18px;
+ flex-shrink: 0;
+ color: $secondary-blue;
+ transition: color 0.2s;
+ }
+
+ .session-name {
+ flex: 1;
+ font-size: 13px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: #1a1a2e;
+ font-weight: 500;
+ }
+
+ .delete-btn {
+ opacity: 0;
+ transform: scale(0.8);
+ transition: all 0.25s ease;
+ padding: 6px;
+ border-radius: 6px;
+ color: #c0c4cc;
+
+ &:hover {
+ color: #fff;
+ background: rgba(245, 108, 108, 0.85);
+ transform: scale(1.1) rotate(8deg);
+ }
+ }
+ }
+ }
+}
+
+.chat-main {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ flex: 1;
+ overflow: hidden;
+}
+
+.message-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 24px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ background: linear-gradient(180deg, transparent 0%, rgba(0, 85, 212, 0.02) 100%);
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: linear-gradient(180deg, $secondary-blue, $primary-blue);
+ border-radius: 4px;
+ box-shadow: 0 0 8px rgba(0, 85, 212, 0.3);
+ }
+}
+
+.message-item {
+ display: flex;
+ gap: 14px;
+ width: 100%;
+ animation: messageSlideIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+
+ @keyframes messageSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px) scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+ }
+
+ .avatar {
+ width: 42px;
+ height: 42px;
+ border-radius: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ font-size: 24px;
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: inherit;
+ filter: blur(10px);
+ opacity: 0.5;
+ z-index: -1;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: linear-gradient(45deg, transparent 40%, rgba(255, 255, 255, 0.2) 50%, transparent 60%);
+ animation: shimmer 3s infinite;
+ }
+ }
+
+ .message-content {
+ flex: 1;
+ overflow-x: hidden;
+ display: flex;
+ flex-direction: column;
+ max-width: calc(100% - 56px);
+
+ .text-box {
+ padding: 14px 20px;
+ border-radius: 18px;
+ font-size: 14px;
+ line-height: 1.7;
+ word-break: break-word;
+ max-width: 100%;
+ width: fit-content;
+ overflow-x: auto;
+ transition: all 0.3s ease;
+ position: relative;
+
+ &::-webkit-scrollbar {
+ height: 4px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: rgba(0, 85, 212, 0.25);
+ border-radius: 2px;
+ }
+ }
+ }
+
+ &.bot-message {
+ .message-content {
+ align-items: flex-start;
+ }
+ .avatar {
+ background: $gradient-dark;
+ color: #fff;
+ box-shadow: 0 6px 20px rgba(0, 85, 212, 0.35);
+ }
+ .text-box {
+ background: #fff;
+ color: #1a1a2e;
+ box-shadow: $shadow-card;
+ border: 1px solid rgba(0, 85, 212, 0.08);
+ border-top-left-radius: 6px;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, rgba(0, 85, 212, 0.15), transparent);
+ }
+ }
+ }
+
+ &.user-message {
+ flex-direction: row-reverse;
+ .message-content {
+ align-items: flex-end;
+ }
+ .avatar {
+ background: linear-gradient(145deg, #5a9fe0, #3d8bd4);
+ color: #fff;
+ box-shadow: 0 6px 20px rgba(0, 85, 212, 0.4);
+ }
+ .text-box {
+ background: $gradient-dark;
+ color: #fff;
+ border-top-right-radius: 6px;
+ box-shadow: 0 6px 24px rgba(0, 85, 212, 0.3);
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3));
+ }
+ }
+ }
+}
+
+@keyframes shimmer {
+ 0% {
+ transform: translateX(-100%) rotate(45deg);
+ }
+ 100% {
+ transform: translateX(100%) rotate(45deg);
+ }
+}
+
+.charts-wrapper {
+ margin-top: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ overflow-x: auto;
+ width: 100%;
+ padding-bottom: 8px;
+
+ &::-webkit-scrollbar {
+ height: 6px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: linear-gradient(90deg, $light-blue, $secondary-blue);
+ border-radius: 3px;
+ }
+}
+
+.chart-item {
+ width: 100%;
+ min-width: 300px;
+ height: 300px;
+ border-radius: 12px;
+ padding: 12px;
+ margin-bottom: 12px;
+}
+
+.table-wrapper {
+ margin-top: 12px;
+ background: #fff;
+ border-radius: 12px;
+ overflow: hidden;
+ overflow-x: auto;
+ width: 100%;
+ box-shadow: $shadow-card;
+ border: 1px solid rgba(0, 122, 255, 0.06);
+
+ &::-webkit-scrollbar {
+ height: 6px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: linear-gradient(90deg, $light-blue, $secondary-blue);
+ border-radius: 3px;
+ }
+
+ .el-table {
+ min-width: 300px;
+ --el-table-border-color: rgba(0, 122, 255, 0.08);
+ --el-table-header-bg-color: $ice-white;
+ }
+}
+
+.purchase-confirm-card {
+ margin-top: 12px;
+ width: 100%;
+ background: #fff;
+ border: 1px solid rgba(0, 85, 212, 0.12);
+ border-radius: 12px;
+ box-shadow: $shadow-card;
+ padding: 14px;
+ color: #1a1a2e;
+}
+
+.purchase-confirm-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ font-size: 15px;
+ font-weight: 700;
+ margin-bottom: 12px;
+}
+
+.purchase-confirm-desc {
+ margin-bottom: 12px;
+ color: #374151;
+ font-size: 13px;
+ line-height: 1.6;
+}
+
+.purchase-empty-state {
+ margin-bottom: 12px;
+ padding: 12px;
+ border-radius: 10px;
+ background: linear-gradient(135deg, rgba(255, 247, 237, 0.96), rgba(255, 255, 255, 0.98));
+ border: 1px solid rgba(245, 158, 11, 0.25);
+
+ .empty-title {
+ font-size: 14px;
+ font-weight: 700;
+ color: #92400e;
+ margin-bottom: 6px;
+ }
+
+ .empty-desc {
+ color: #78350f;
+ font-size: 13px;
+ line-height: 1.6;
+ }
+}
+
+.purchase-alert {
+ border-radius: 8px;
+ padding: 10px 12px;
+ margin-bottom: 10px;
+ font-size: 13px;
+
+ ul {
+ margin: 6px 0 0;
+ padding-left: 18px;
+ }
+
+ &.warning {
+ background: rgba(230, 162, 60, 0.12);
+ color: #9a5b00;
+ }
+
+ &.missing {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ background: rgba(245, 108, 108, 0.1);
+ color: #b42318;
+ }
+}
+
+.purchase-preview {
+ margin-bottom: 12px;
+
+ ul {
+ margin: 6px 0 0;
+ padding-left: 18px;
+ font-size: 13px;
+ line-height: 1.7;
+ }
+}
+
+.purchase-section-title {
+ margin: 10px 0 6px;
+ font-size: 13px;
+ font-weight: 700;
+ color: $deep-blue;
+}
+
+.payload-toolbar {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 8px;
+}
+
+.payload-tree-table-wrapper {
+ border: 1px solid rgba(0, 85, 212, 0.1);
+ border-radius: 10px;
+ overflow: auto;
+
+ :deep(.el-table) {
+ --el-table-header-bg-color: #f5f8ff;
+ --el-table-border-color: rgba(0, 85, 212, 0.08);
+ }
+}
+
+.payload-key-cell {
+ display: flex;
+ align-items: center;
+ min-height: 28px;
+}
+
+.payload-fixed-key {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ line-height: 1.3;
+ color: #1f2937;
+
+ small {
+ font-size: 11px;
+ color: #6b7280;
+ }
+}
+
+.payload-array-index {
+ font-size: 12px;
+ color: #475467;
+}
+
+.payload-container-cell {
+ color: #344054;
+ font-size: 12px;
+}
+
+.payload-null-value {
+ color: #6b7280;
+ font-size: 12px;
+}
+
+.payload-row-actions {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+}
+
+.payload-editor-tip {
+ margin-top: 6px;
+ font-size: 12px;
+ line-height: 1.5;
+ color: #6b7280;
+}
+
+.purchase-confirm-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 12px;
+ margin-top: 12px;
+
+ .confirm-result {
+ flex: 1;
+ font-size: 13px;
+
+ &.success {
+ color: #1f9d55;
+ }
+
+ &.error {
+ color: #d93025;
+ }
+ }
+}
+
+.input-area {
+ padding: 18px 20px;
+ background: linear-gradient(180deg, rgba(232, 242, 255, 0.95) 0%, #fff 100%);
+ border-top: 1px solid rgba(0, 85, 212, 0.1);
+ position: relative;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 20px;
+ right: 20px;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(0, 85, 212, 0.15), transparent);
+ }
+
+ .input-actions {
+ display: flex;
+ gap: 14px;
+ margin-bottom: 12px;
+ align-items: center;
+
+ .file-upload-trigger {
+ display: inline-flex;
+ align-items: center;
+ }
+
+ :deep(.utility-action-btn) {
+ position: relative;
+ height: 34px;
+ padding: 0 14px;
+ border-radius: 999px;
+ border: 1px solid rgba(92, 119, 255, 0.18);
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(236, 243, 255, 0.98));
+ color: $primary-blue;
+ font-weight: 600;
+ box-shadow: 0 10px 20px rgba(0, 85, 212, 0.08);
+ transition: all 0.25s ease;
+
+ .el-icon {
+ margin-right: 5px;
+ }
+
+ &:hover:not(.is-disabled) {
+ color: #fff;
+ border-color: transparent;
+ background: linear-gradient(135deg, #1f6dff 0%, #6b38ef 100%);
+ box-shadow: 0 14px 24px rgba(64, 90, 255, 0.2);
+ transform: translateY(-1px);
+ }
+ }
+
+ :deep(.stop-action-btn) {
+ border-color: rgba(255, 99, 123, 0.18);
+ color: #d33e5e;
+
+ &:hover:not(.is-disabled) {
+ background: linear-gradient(135deg, #f5536e 0%, #a33cff 100%);
+ }
+ }
+ }
+
+ .input-box {
+ padding: 16px;
+ position: relative;
+ background: #fff;
+ border: 2px solid rgba(0, 85, 212, 0.12);
+ border-radius: 16px;
+ margin: 0 4px;
+ transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+
+ &:focus-within {
+ border-color: $primary-blue;
+ box-shadow: 0 0 0 4px rgba(0, 85, 212, 0.12), $shadow-deep;
+ transform: translateY(-2px);
+ background: #fff;
+ }
+
+ .selected-file-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 12px;
+ }
+
+ .selected-file-tag {
+ display: flex;
+ align-items: center;
+ background: linear-gradient(135deg, rgba(0, 85, 212, 0.1) 0%, rgba(0, 136, 232, 0.15) 100%);
+ border: 1px solid rgba(0, 85, 212, 0.2);
+ border-radius: 10px;
+ padding: 8px 12px;
+ gap: 10px;
+ width: fit-content;
+ max-width: 100%;
+ animation: tagSlideIn 0.3s ease;
+
+ @keyframes tagSlideIn {
+ from {
+ opacity: 0;
+ transform: translateX(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+
+ .el-icon {
+ color: $primary-blue;
+ font-size: 18px;
+ }
+
+ .file-name {
+ font-size: 13px;
+ color: $deep-blue;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-weight: 600;
+ }
+
+ .remove-file {
+ cursor: pointer;
+ color: $secondary-blue;
+ transition: all 0.2s;
+ padding: 4px;
+ border-radius: 50%;
+
+ &:hover {
+ color: #fff;
+ background: rgba(245, 108, 108, 0.8);
+ transform: scale(1.1) rotate(90deg);
+ }
+ }
+ }
+
+ :deep(.el-textarea__inner) {
+ padding: 0;
+ padding-bottom: 35px;
+ border: none;
+ box-shadow: none;
+ background: transparent;
+ font-family: inherit;
+ font-size: 14px;
+ line-height: 1.6;
+ color: #1a1a2e;
+
+ &::placeholder {
+ color: #7ab8ff;
+ }
+
+ &:focus {
+ box-shadow: none;
+ }
+ }
+
+ .send-btn {
+ position: absolute;
+ right: 16px;
+ bottom: 16px;
+ padding: 10px 22px;
+ background: $gradient-dark;
+ border: none;
+ border-radius: 10px;
+ font-weight: 600;
+ font-size: 14px;
+ color: #fff;
+ box-shadow: 0 6px 20px rgba(0, 85, 212, 0.4);
+ transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ overflow: hidden;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ letter-spacing: 0.3px;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+ transition: left 0.5s;
+ }
+
+ &:hover:not(:disabled) {
+ transform: translateY(-3px) scale(1.02);
+ box-shadow: 0 10px 30px rgba(0, 85, 212, 0.5);
+
+ &::before {
+ left: 100%;
+ }
+ }
+
+ &:active:not(:disabled) {
+ transform: translateY(-1px) scale(0.98);
+ }
+
+ &:disabled {
+ background: linear-gradient(145deg, #b0b0b0, #c5c5c5);
+ box-shadow: none;
+ cursor: not-allowed;
+ }
+
+ .el-icon {
+ font-size: 15px;
+ transform: translateY(-1px);
+ }
+ }
+ }
+}
+
+.typing-indicator {
+ display: flex;
+ gap: 5px;
+ padding: 10px 14px;
+ background: #fff;
+ border-radius: 14px;
+ width: fit-content;
+ box-shadow: $shadow-card;
+ margin-top: 6px;
+ border: 1px solid rgba(0, 122, 255, 0.06);
+ border-top-left-radius: 4px;
+
+ .dot {
+ width: 7px;
+ height: 7px;
+ background: $secondary-blue;
+ border-radius: 50%;
+ animation: typing 1.4s infinite ease-in-out;
+
+ &:nth-child(2) {
+ animation-delay: 0.2s;
+ background: $primary-blue;
+ }
+ &:nth-child(3) {
+ animation-delay: 0.4s;
+ background: $deep-blue;
+ }
+ }
+}
+
+@keyframes typing {
+ 0%, 80%, 100% {
+ transform: scale(0.6);
+ opacity: 0.4;
+ }
+ 40% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.code-block {
+ background: linear-gradient(145deg, #1a1a2e, #16213e);
+ color: #a8d8ff;
+ padding: 14px;
+ border-radius: 10px;
+ font-family: 'Fira Code', 'Consolas', monospace;
+ margin: 10px 0;
+ overflow-x: auto;
+ border: 1px solid rgba(90, 200, 250, 0.15);
+ box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2);
+
+ &.js-code {
+ color: #5ac8fa;
+ }
+}
+
+.chat-main {
+ background:
+ radial-gradient(circle at top left, rgba(46, 140, 224, 0.12) 0%, transparent 34%),
+ linear-gradient(180deg, #fff 0%, #f7fbff 46%, #fff 100%);
+}
+
+.chat-hero {
+ display: grid;
+ grid-template-columns: 164px minmax(0, 1fr);
+ gap: 18px;
+ align-items: start;
+ padding: 14px 18px 6px;
+
+ &.compact {
+ grid-template-columns: 122px minmax(0, 1fr);
+ gap: 12px;
+ padding: 8px 18px 2px;
+ }
+}
+
+.assistant-stand {
+ position: relative;
+ min-height: 252px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ padding-top: 18px;
+ overflow: hidden;
+
+ &.compact {
+ min-height: 176px;
+ padding-top: 8px;
+ }
+
+ &.thinking {
+ .assistant-halo {
+ opacity: 1;
+ transform: scale(1.08);
+ filter: blur(8px);
+ }
+
+ .assistant-scan-ring {
+ opacity: 1;
+ animation-duration: 1.6s;
+ }
+
+ .assistant-orbit {
+ opacity: 1;
+ }
+
+ .assistant-bot {
+ transform: translateY(-4px) scale(1.02);
+ }
+
+ .assistant-bot-head {
+ box-shadow: 0 0 30px rgba(80, 157, 255, 0.36);
+ }
+
+ .assistant-bot-eye {
+ animation: robotBlinkFast 1.1s infinite;
+ box-shadow: 0 0 16px rgba(72, 186, 255, 0.95);
+ }
+
+ .assistant-bot-mouth {
+ width: 28px;
+ opacity: 1;
+ animation: robotTalk 1.2s ease-in-out infinite;
+ }
+
+ .assistant-bot-core {
+ animation: corePulse 1.4s ease-in-out infinite;
+ box-shadow: 0 0 24px rgba(78, 120, 255, 0.26);
+ }
+
+ .assistant-bot-core-ring {
+ animation: coreRotate 3s linear infinite;
+ }
+
+ .assistant-status {
+ color: #6a3bee;
+ box-shadow: 0 10px 22px rgba(106, 59, 238, 0.14);
+ }
+
+ .assistant-status-dot {
+ background: #6a3bee;
+ box-shadow: 0 0 12px rgba(106, 59, 238, 0.9);
+ animation: thinkingDot 1s ease-in-out infinite;
+ }
+
+ .assistant-base-sm {
+ box-shadow: 0 0 24px rgba(255, 93, 122, 0.48);
+ }
+ }
+}
+
+.assistant-halo {
+ position: absolute;
+ top: 22px;
+ width: 130px;
+ height: 130px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(46, 140, 224, 0.3) 0%, rgba(0, 85, 212, 0.18) 42%, rgba(113, 54, 244, 0.12) 60%, transparent 78%);
+ filter: blur(6px);
+ opacity: 0.82;
+ transition: all 0.35s ease;
+}
+
+.assistant-scan-ring {
+ position: absolute;
+ top: 40px;
+ width: 132px;
+ height: 132px;
+ border-radius: 50%;
+ border: 1px solid rgba(90, 159, 224, 0.22);
+ box-shadow: inset 0 0 16px rgba(255, 255, 255, 0.25);
+ opacity: 0.55;
+ animation: scanRing 4s linear infinite;
+}
+
+.assistant-orbit {
+ position: absolute;
+ top: 52px;
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ border: 1px dashed rgba(92, 135, 255, 0.22);
+ opacity: 0.45;
+}
+
+.assistant-orbit-a {
+ animation: orbitRotate 8s linear infinite;
+}
+
+.assistant-orbit-b {
+ width: 118px;
+ height: 118px;
+ top: 68px;
+ border-color: rgba(255, 108, 150, 0.22);
+ animation: orbitRotateReverse 5.6s linear infinite;
+}
+
+.assistant-bot {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin-top: 12px;
+ transition: transform 0.35s ease;
+}
+
+.assistant-bot-antenna {
+ position: absolute;
+ top: -4px;
+ width: 4px;
+ height: 20px;
+ border-radius: 999px;
+ background: linear-gradient(180deg, #fefefe, #aac9ff);
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: -6px;
+ left: 50%;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ transform: translateX(-50%);
+ background: linear-gradient(135deg, #54bfff, #7a41ff);
+ box-shadow: 0 0 14px rgba(84, 191, 255, 0.65);
+ }
+}
+
+.assistant-bot-antenna-left {
+ left: 36px;
+ transform: rotate(-14deg);
+}
+
+.assistant-bot-antenna-right {
+ right: 36px;
+ transform: rotate(14deg);
+}
+
+.assistant-bot-head {
+ position: relative;
+ width: 92px;
+ height: 78px;
+ border-radius: 28px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #e8f1ff 100%);
+ border: 1px solid rgba(0, 85, 212, 0.14);
+ box-shadow: 0 16px 32px rgba(0, 85, 212, 0.14);
+}
+
+.assistant-bot-head-glow {
+ position: absolute;
+ inset: 10px 16px auto;
+ height: 20px;
+ border-radius: 999px;
+ background: linear-gradient(180deg, rgba(0, 85, 212, 0.16), transparent);
+}
+
+.assistant-bot-eye {
+ position: absolute;
+ top: 30px;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: radial-gradient(circle, #8ef0ff 0%, #56c0ff 42%, #2869ff 100%);
+ box-shadow: 0 0 12px rgba(72, 186, 255, 0.72);
+ animation: robotBlink 3.2s infinite;
+}
+
+.assistant-bot-eye-left {
+ left: 22px;
+}
+
+.assistant-bot-eye-right {
+ right: 22px;
+}
+
+.assistant-bot-mouth {
+ position: absolute;
+ left: 50%;
+ bottom: 16px;
+ width: 22px;
+ height: 4px;
+ transform: translateX(-50%);
+ border-radius: 999px;
+ background: linear-gradient(90deg, rgba(72, 186, 255, 0.2), rgba(72, 186, 255, 0.9), rgba(72, 186, 255, 0.2));
+}
+
+.assistant-bot-neck {
+ width: 16px;
+ height: 10px;
+ border-radius: 0 0 10px 10px;
+ background: linear-gradient(180deg, #dceaff, #bdd5ff);
+ margin-top: -2px;
+}
+
+.assistant-bot-body {
+ position: relative;
+ width: 104px;
+ height: 92px;
+ margin-top: 2px;
+ border-radius: 28px 28px 34px 34px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #e3eeff 100%);
+ border: 1px solid rgba(0, 85, 212, 0.14);
+ box-shadow: 0 18px 36px rgba(0, 85, 212, 0.16);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.assistant-bot-arm {
+ position: absolute;
+ top: 18px;
+ width: 16px;
+ height: 44px;
+ border-radius: 999px;
+ background: linear-gradient(180deg, #eff5ff, #c7dbff);
+ border: 1px solid rgba(0, 85, 212, 0.12);
+}
+
+.assistant-bot-arm-left {
+ left: -10px;
+ transform: rotate(16deg);
+}
+
+.assistant-bot-arm-right {
+ right: -10px;
+ transform: rotate(-16deg);
+}
+
+.assistant-bot-core {
+ position: relative;
+ width: 46px;
+ height: 46px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: $primary-blue;
+ background: radial-gradient(circle, rgba(255, 255, 255, 1) 0%, #dae8ff 55%, #adc7ff 100%);
+}
+
+.assistant-bot-core-ring {
+ position: absolute;
+ inset: -6px;
+ border-radius: 50%;
+ border: 1px solid rgba(88, 135, 255, 0.3);
+ border-top-color: rgba(255, 96, 139, 0.85);
+ border-right-color: rgba(79, 145, 255, 0.9);
+}
+
+.assistant-status {
+ position: relative;
+ z-index: 1;
+ margin-top: 14px;
+ padding: 6px 12px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 600;
+ color: $deep-blue;
+ background: rgba(255, 255, 255, 0.95);
+ border: 1px solid rgba(0, 85, 212, 0.12);
+ box-shadow: 0 8px 20px rgba(0, 85, 212, 0.08);
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.assistant-status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #2e8ce0;
+ box-shadow: 0 0 10px rgba(46, 140, 224, 0.72);
+}
+
+.assistant-base {
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ border-radius: 50%;
+ border: 2px solid rgba(255, 93, 122, 0.22);
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.9) 0%, rgba(255, 111, 145, 0.1) 70%, transparent 100%);
+}
+
+.assistant-base-lg {
+ width: 118px;
+ height: 30px;
+}
+
+.assistant-base-md {
+ bottom: 6px;
+ width: 88px;
+ height: 20px;
+ border-color: rgba(255, 93, 122, 0.34);
+}
+
+.assistant-base-sm {
+ bottom: 11px;
+ width: 54px;
+ height: 10px;
+ background: linear-gradient(90deg, rgba(255, 93, 122, 0.95), rgba(255, 173, 188, 0.9));
+ border: none;
+ box-shadow: 0 0 18px rgba(255, 93, 122, 0.38);
+}
+
+@keyframes robotBlink {
+ 0%, 44%, 48%, 100% {
+ transform: scaleY(1);
+ }
+ 46% {
+ transform: scaleY(0.14);
+ }
+}
+
+@keyframes robotBlinkFast {
+ 0%, 100% {
+ transform: scaleY(1);
+ }
+ 50% {
+ transform: scaleY(0.3);
+ }
+}
+
+@keyframes robotTalk {
+ 0%, 100% {
+ transform: translateX(-50%) scaleX(1);
+ }
+ 50% {
+ transform: translateX(-50%) scaleX(1.35);
+ }
+}
+
+@keyframes corePulse {
+ 0%, 100% {
+ transform: scale(1);
+ filter: brightness(1);
+ }
+ 50% {
+ transform: scale(1.08);
+ filter: brightness(1.08);
+ }
+}
+
+@keyframes coreRotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes orbitRotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes orbitRotateReverse {
+ from {
+ transform: rotate(360deg);
+ }
+ to {
+ transform: rotate(0deg);
+ }
+}
+
+@keyframes scanRing {
+ 0%, 100% {
+ transform: scale(0.96);
+ opacity: 0.42;
+ }
+ 50% {
+ transform: scale(1.04);
+ opacity: 0.86;
+ }
+}
+
+@keyframes thinkingDot {
+ 0%, 100% {
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale(1.35);
+ }
+}
+
+.welcome-card {
+ position: relative;
+ padding: 14px 14px 12px;
+ border-radius: 16px;
+ background:
+ linear-gradient(#fff, #fff) padding-box,
+ linear-gradient(135deg, rgba(255, 64, 96, 0.85), rgba(117, 65, 255, 0.9)) border-box;
+ border: 1px solid transparent;
+ box-shadow: 0 16px 36px rgba(0, 85, 212, 0.12);
+
+ &.compact {
+ padding: 10px 12px;
+ border-radius: 12px;
+ box-shadow: 0 8px 16px rgba(0, 85, 212, 0.07);
+
+ .welcome-eyebrow {
+ margin-bottom: 4px;
+ }
+
+ .welcome-title {
+ font-size: 17px;
+ line-height: 1.3;
+
+ br {
+ display: none;
+ }
+ }
+
+ .welcome-desc {
+ margin-top: 6px;
+ font-size: 12px;
+ line-height: 1.55;
+ }
+
+ .quick-prompt-list {
+ margin-top: 10px;
+ gap: 6px;
+ }
+
+ .quick-prompt-btn {
+ padding: 8px 10px;
+ font-size: 12px;
+ border-radius: 7px;
+ }
+
+ .more-prompts-btn {
+ margin-top: 8px;
+ font-size: 12px;
+ }
+ }
+}
+
+.welcome-eyebrow {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 2px;
+ color: rgba(0, 85, 212, 0.58);
+ margin-bottom: 8px;
+}
+
+.welcome-title {
+ margin: 0;
+ font-size: 26px;
+ line-height: 1.2;
+ font-weight: 800;
+ color: #172033;
+}
+
+.welcome-desc {
+ margin: 10px 0 0;
+ font-size: 13px;
+ line-height: 1.7;
+ color: #5f6980;
+}
+
+.quick-prompt-list {
+ display: grid;
+ gap: 8px;
+ margin-top: 14px;
+}
+
+.quick-prompt-btn {
+ width: 100%;
+ border: none;
+ border-radius: 10px;
+ padding: 11px 14px;
+ text-align: left;
+ font-size: 13px;
+ font-weight: 600;
+ color: #fff;
+ cursor: pointer;
+ background: linear-gradient(90deg, #ff4c55 0%, #7c38ef 100%);
+ box-shadow: 0 12px 22px rgba(124, 56, 239, 0.18);
+ transition: transform 0.25s ease, box-shadow 0.25s ease, opacity 0.2s ease;
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), transparent 56%);
+ pointer-events: none;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: -120%;
+ left: -30%;
+ width: 45%;
+ height: 260%;
+ background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+ transform: rotate(22deg);
+ opacity: 0;
+ transition: all 0.35s ease;
+ }
+
+ &:hover:not(:disabled) {
+ transform: translateY(-2px) scale(1.01);
+ box-shadow: 0 16px 28px rgba(124, 56, 239, 0.24);
+
+ &::after {
+ left: 100%;
+ opacity: 1;
+ }
+ }
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.65;
+ }
+}
+
+.more-prompts-btn {
+ margin-top: 10px;
+ padding: 0 12px;
+ height: 32px;
+ border: 1px solid rgba(208, 65, 81, 0.12);
+ border-radius: 999px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(255, 241, 245, 0.96));
+ color: #d04151;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ box-shadow: 0 10px 18px rgba(208, 65, 81, 0.08);
+ transition: all 0.25s ease;
+
+ &:hover {
+ transform: translateY(-1px);
+ background: linear-gradient(135deg, #ff5570 0%, #8a3df6 100%);
+ border-color: transparent;
+ color: #fff;
+ box-shadow: 0 14px 24px rgba(138, 61, 246, 0.18);
+ }
+}
+
+.hero-dot-grid {
+ display: grid;
+ grid-template-columns: repeat(14, 1fr);
+ gap: 7px;
+ padding: 0 18px 14px;
+
+ span {
+ display: block;
+ width: 100%;
+ aspect-ratio: 1;
+ border-radius: 2px;
+ background: linear-gradient(135deg, rgba(255, 110, 138, 0.95), rgba(255, 190, 201, 0.55));
+ }
+}
+
+.message-list {
+ padding: 8px 18px 18px;
+ gap: 16px;
+ background: transparent;
+}
+
+.input-area {
+ padding: 12px 18px 16px;
+ background: #fff;
+ border-top: none;
+
+ &::before {
+ display: none;
+ }
+
+ .input-box {
+ padding: 14px 16px 16px;
+ border: 1px solid rgba(123, 56, 239, 0.9);
+ border-radius: 22px;
+ margin: 0;
+ transition: all 0.25s ease;
+ box-shadow: 0 14px 34px rgba(0, 85, 212, 0.08);
+
+ &:focus-within {
+ border-color: #7c38ef;
+ box-shadow: 0 0 0 3px rgba(124, 56, 239, 0.1), 0 18px 40px rgba(0, 85, 212, 0.12);
+ transform: none;
+ }
+
+ :deep(.el-textarea__inner) {
+ padding-right: 58px;
+ padding-bottom: 0;
+ min-height: 104px;
+
+ &::placeholder {
+ color: #a0a9bc;
+ }
+ }
+
+ .send-btn {
+ right: 25px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 36px;
+ min-width: 36px;
+ height: 36px;
+ padding: 0;
+ background: linear-gradient(135deg, #ff5570 0%, #7a36f2 58%, #2d79ff 100%);
+ border-radius: 50%;
+ box-shadow: 0 12px 24px rgba(109, 50, 236, 0.24);
+ transition: all 0.25s ease;
+ gap: 0;
+
+ &:hover:not(:disabled) {
+ transform: translateY(calc(-50% - 1px)) scale(1.04);
+ box-shadow: 0 16px 28px rgba(109, 50, 236, 0.3);
+ }
+
+ &:active:not(:disabled) {
+ transform: translateY(-50%) scale(0.96);
+ }
+
+ .el-icon {
+ margin: 0;
+ font-size: 16px;
+ transform: translate(0, -1px);
+ }
+ }
+ }
+}
+
+@media (max-width: 767px) {
+ .chat-hero {
+ grid-template-columns: 1fr;
+ gap: 10px;
+ padding: 14px 14px 6px;
+
+ &.compact {
+ padding: 8px 14px 4px;
+ }
+ }
+
+ .assistant-stand {
+ min-height: 184px;
+ }
+
+ .welcome-card {
+ padding: 12px 12px 10px;
+ }
+
+ .welcome-title {
+ font-size: 21px;
+ }
+
+ .hero-dot-grid {
+ grid-template-columns: repeat(12, 1fr);
+ gap: 6px;
+ padding: 0 14px 12px;
+ }
+
+ .message-list {
+ padding: 8px 14px 14px;
+ }
+
+ .input-area {
+ padding: 10px 14px 14px;
+ }
+
+ .input-area .input-actions {
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+}
+</style>
diff --git a/src/components/AttachmentPreview/image/index.vue b/src/components/AttachmentPreview/image/index.vue
new file mode 100644
index 0000000..1c6039b
--- /dev/null
+++ b/src/components/AttachmentPreview/image/index.vue
@@ -0,0 +1,76 @@
+<script setup>
+const props = defineProps({
+ fileList: {
+ type: Array,
+ default: () => [],
+ },
+ thumbSize: {
+ type: Number,
+ default: 72,
+ },
+ gap: {
+ type: Number,
+ default: 10,
+ },
+})
+
+const normalizedList = computed(() => {
+ return (props.fileList || [])
+ .filter((item) => item && item.previewURL)
+ .map((item, index) => ({
+ id: item.id ?? index,
+ name: item.originalFilename || `image-${index + 1}`,
+ url: item.previewURL,
+ }))
+})
+const previewUrls = computed(() => normalizedList.value.map((item) => item.url))
+</script>
+
+<template>
+ <div class="attachment-image-preview">
+ <div v-if="!normalizedList.length" class="empty">鏆傛棤鍥剧墖</div>
+
+ <div v-else class="thumbs" :style="{ gap: `${gap}px` }">
+ <el-image
+ v-for="(item, index) in normalizedList"
+ :key="item.id"
+ class="thumb"
+ :style="{ width: `${thumbSize}px`, height: `${thumbSize}px` }"
+ :src="item.url"
+ :preview-src-list="previewUrls"
+ :initial-index="index"
+ fit="cover"
+ preview-teleported
+ />
+ </div>
+ </div>
+</template>
+
+<style scoped lang="scss">
+.attachment-image-preview {
+ width: 100%;
+}
+
+.empty {
+ height: 120px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--el-text-color-secondary);
+ border: 1px dashed var(--el-border-color);
+ border-radius: 8px;
+}
+
+.thumbs {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.thumb {
+ border: 1px solid var(--el-border-color);
+ border-radius: 6px;
+ overflow: hidden;
+ cursor: pointer;
+ background: #fff;
+}
+</style>
diff --git a/src/components/AttachmentUpload/file/index.vue b/src/components/AttachmentUpload/file/index.vue
new file mode 100644
index 0000000..1e4508c
--- /dev/null
+++ b/src/components/AttachmentUpload/file/index.vue
@@ -0,0 +1,309 @@
+<script setup>
+import { UploadFilled } from '@element-plus/icons-vue'
+import { uploadFile } from '@/api/basicData/common'
+
+const props = defineProps({
+ fileList: {
+ type: Array,
+ default: () => [],
+ },
+ index: {
+ type: Number,
+ default: -1,
+ },
+ childrenKey: {
+ type: String,
+ default: 'files',
+ },
+ limit: {
+ type: Number,
+ default: 10,
+ },
+ fileSize: {
+ type: Number,
+ default: 50,
+ },
+ fileType: {
+ type: Array,
+ default: () => [],
+ },
+ buttonText: {
+ type: String,
+ default: '鍗曞嚮閫夋嫨鏂囦欢',
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ uploadFieldName: {
+ type: String,
+ default: 'files',
+ },
+})
+
+const emit = defineEmits(['update:fileList', 'change'])
+const { proxy } = getCurrentInstance()
+
+const uploadRef = ref()
+const uploadQueueTimer = ref(null)
+const uploading = ref(false)
+const queuedUidSet = ref(new Set())
+const innerList = ref([])
+
+function readListFromProps() {
+ if (props.index > -1) {
+ const row = props.fileList?.[props.index]
+ return Array.isArray(row?.[props.childrenKey]) ? row[props.childrenKey] : []
+ }
+ return Array.isArray(props.fileList) ? props.fileList : []
+}
+
+watch(
+ () => props.fileList,
+ () => {
+ innerList.value = [...readListFromProps()]
+ },
+ { deep: true, immediate: true },
+)
+
+const currentList = computed({
+ get() {
+ return innerList.value
+ },
+ set(value) {
+ const nextList = Array.isArray(value) ? value : []
+ innerList.value = nextList
+
+ if (props.index > -1) {
+ const nextModelValue = Array.isArray(props.fileList) ? [...props.fileList] : []
+ const currentRow = nextModelValue[props.index] || {}
+ nextModelValue[props.index] = {
+ ...currentRow,
+ [props.childrenKey]: nextList,
+ }
+ emit('update:fileList', nextModelValue)
+ emit('change', nextList, nextModelValue)
+ return
+ }
+
+ emit('update:fileList', nextList)
+ emit('change', nextList, nextList)
+ },
+})
+
+const displayFileList = computed(() => {
+ return currentList.value.map((item, index) => ({
+ uid: getItemUid(item, index),
+ name: getItemName(item, index),
+ url: getItemUrl(item),
+ status: 'success',
+ rawData: item,
+ }))
+})
+
+const uploadTip = computed(() => {
+ if (!props.fileType.length) return `鍗曚釜鏂囦欢涓嶈秴杩� ${props.fileSize}MB`
+ return `鏀寔 ${props.fileType.join('/')}锛屽崟涓枃浠朵笉瓒呰繃 ${props.fileSize}MB`
+})
+
+function getItemUid(item, index) {
+ if (item?.id !== undefined && item?.id !== null) return `${item.id}`
+ return `${getItemName(item, index)}-${getItemUrl(item) || index}`
+}
+
+function getItemUrl(item) {
+ if (!item) return ''
+ if (typeof item === 'string') return item
+ return item.url || item.downloadURL || item.previewURL || item.previewUrl || ''
+}
+
+function getItemName(item, index = 0) {
+ if (!item) return `file-${index + 1}`
+ if (typeof item === 'string') return `file-${index + 1}`
+ return item.name || item.originalFilename || item.fileName || item.uidFilename || `file-${index + 1}`
+}
+
+function normalizeResponseItem(item, index) {
+ if (typeof item === 'string') {
+ return {
+ name: `file-${currentList.value.length + index + 1}`,
+ url: item,
+ }
+ }
+ return Object.assign({}, item, {
+ url: item.url || item.downloadURL || item.previewURL || item.previewUrl || '',
+ name: item.name || item.originalFilename || item.fileName || item.uidFilename || `file-${currentList.value.length + index + 1}`,
+ })
+}
+
+function extractResponseArray(response) {
+ if (Array.isArray(response)) return response
+ if (Array.isArray(response?.data)) return response.data
+ if (Array.isArray(response?.data?.data)) return response.data.data
+ if (Array.isArray(response?.payload)) return response.payload
+ if (Array.isArray(response?.payload?.data)) return response.payload.data
+ if (Array.isArray(response?.rows)) return response.rows
+ if (Array.isArray(response?.result)) return response.result
+ return []
+}
+
+function validateFile(rawFile) {
+ const extension = rawFile.name.includes('.')
+ ? rawFile.name.slice(rawFile.name.lastIndexOf('.') + 1).toLowerCase()
+ : ''
+
+ if (props.fileType.length) {
+ const isValidType = props.fileType.some((type) => {
+ const normalizedType = String(type).toLowerCase()
+ return rawFile.type.toLowerCase().includes(normalizedType) || extension === normalizedType
+ })
+ if (!isValidType) {
+ proxy.$modal.msgError(`璇蜂笂浼� ${props.fileType.join('/')} 鏍煎紡鐨勬枃浠禶)
+ return false
+ }
+ }
+
+ const isWithinSize = rawFile.size / 1024 / 1024 <= props.fileSize
+ if (!isWithinSize) {
+ proxy.$modal.msgError(`鏂囦欢澶у皬涓嶈兘瓒呰繃 ${props.fileSize}MB`)
+ return false
+ }
+
+ return true
+}
+
+function scheduleUpload(uploadFiles) {
+ clearTimeout(uploadQueueTimer.value)
+ uploadQueueTimer.value = setTimeout(() => {
+ const readyFiles = uploadFiles.filter((file) => file.raw && !queuedUidSet.value.has(file.uid))
+ if (!readyFiles.length) return
+
+ const remainCount = props.limit - currentList.value.length
+ if (remainCount <= 0) {
+ proxy.$modal.msgError(`鏈�澶氫笂浼� ${props.limit} 涓枃浠禶)
+ uploadRef.value?.clearFiles()
+ return
+ }
+
+ const selectedFiles = readyFiles.slice(0, remainCount)
+ if (selectedFiles.length < readyFiles.length) {
+ proxy.$modal.msgWarning(`鏈�澶氫笂浼� ${props.limit} 涓枃浠讹紝瓒呭嚭閮ㄥ垎宸插拷鐣)
+ }
+
+ selectedFiles.forEach((file) => queuedUidSet.value.add(file.uid))
+ uploadSelectedFiles(selectedFiles)
+ }, 0)
+}
+
+async function uploadSelectedFiles(files) {
+ const validFiles = files.filter((file) => validateFile(file.raw))
+ const invalidFiles = files.filter((file) => !validFiles.includes(file))
+
+ invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid))
+
+ if (!validFiles.length) {
+ uploadRef.value?.clearFiles()
+ return
+ }
+
+ const formData = new FormData()
+ validFiles.forEach((file) => {
+ formData.append(props.uploadFieldName, file.raw)
+ })
+
+ uploading.value = true
+ proxy.$modal.loading('鏂囦欢涓婁紶涓紝璇风◢鍊�...')
+
+ try {
+ const response = await uploadFile(formData)
+ const responseList = extractResponseArray(response).map((item, index) => normalizeResponseItem(item, index))
+
+ if (!responseList.length) {
+ proxy.$modal.msgError('涓婁紶鎺ュ彛鏈繑鍥炴暟缁勬暟鎹�')
+ return
+ }
+
+ currentList.value = [...currentList.value, ...responseList]
+ proxy.$modal.msgSuccess('涓婁紶鎴愬姛')
+ } catch (error) {
+ proxy.$modal.msgError(error?.message || '涓婁紶澶辫触')
+ } finally {
+ validFiles.forEach((file) => queuedUidSet.value.delete(file.uid))
+ invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid))
+ uploadRef.value?.clearFiles()
+ uploading.value = false
+ proxy.$modal.closeLoading()
+ }
+}
+
+function handleChange(file, uploadFiles) {
+ if (props.disabled || uploading.value) return
+ scheduleUpload(uploadFiles)
+}
+
+function handleRemove(file) {
+ const targetUrl = file.url || getItemUrl(file.rawData)
+ const nextList = currentList.value.filter((item, index) => {
+ const itemUrl = getItemUrl(item)
+ const itemName = getItemName(item, index)
+ return !(itemUrl === targetUrl && itemName === file.name)
+ })
+ currentList.value = nextList
+}
+
+function handleExceed() {
+ proxy.$modal.msgError(`鏈�澶氫笂浼� ${props.limit} 涓枃浠禶)
+}
+
+function openFile(file) {
+ const fileUrl = file.url || getItemUrl(file.rawData)
+ if (!fileUrl) return
+ window.open(fileUrl, '_blank')
+}
+
+onBeforeUnmount(() => {
+ clearTimeout(uploadQueueTimer.value)
+})
+</script>
+
+<template>
+ <div class="attachment-upload-file">
+ <el-upload
+ ref="uploadRef"
+ drag
+ :auto-upload="false"
+ :multiple="true"
+ :show-file-list="true"
+ :file-list="displayFileList"
+ :disabled="disabled || uploading"
+ :limit="limit"
+ :on-change="handleChange"
+ :on-remove="handleRemove"
+ :on-exceed="handleExceed"
+ :on-preview="openFile"
+ >
+ <el-icon class="upload-drag-icon"><UploadFilled /></el-icon>
+ <div class="el-upload__text">
+ 灏嗘枃浠舵嫋鍒版澶勶紝鎴� <em>{{ buttonText }}</em>
+ </div>
+ <div class="upload-tip">{{ uploadTip }}</div>
+ </el-upload>
+ </div>
+</template>
+
+<style scoped lang="scss">
+.attachment-upload-file {
+ width: 100%;
+}
+
+.upload-drag-icon {
+ font-size: 40px;
+ color: var(--el-text-color-secondary);
+}
+
+.upload-tip {
+ margin-top: 8px;
+ color: var(--el-text-color-secondary);
+ font-size: 12px;
+}
+</style>
diff --git a/src/components/AttachmentUpload/image/index.vue b/src/components/AttachmentUpload/image/index.vue
new file mode 100644
index 0000000..8243f9c
--- /dev/null
+++ b/src/components/AttachmentUpload/image/index.vue
@@ -0,0 +1,335 @@
+<script setup>
+import {Plus} from '@element-plus/icons-vue'
+import {uploadFile} from '@/api/basicData/common'
+
+const props = defineProps({
+ fileList: {
+ type: Array,
+ default: () => [],
+ },
+ index: {
+ type: Number,
+ default: -1,
+ },
+ childrenKey: {
+ type: String,
+ default: 'images',
+ },
+ limit: {
+ type: Number,
+ default: 10,
+ },
+ fileSize: {
+ type: Number,
+ default: 10,
+ },
+ fileType: {
+ type: Array,
+ default: () => ['png', 'jpg', 'jpeg', 'webp'],
+ },
+ buttonText: {
+ type: String,
+ default: '涓婁紶鍥剧墖',
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ uploadFieldName: {
+ type: String,
+ default: 'files',
+ },
+})
+
+const emit = defineEmits(['update:fileList', 'change'])
+const {proxy} = getCurrentInstance()
+
+const uploadRef = ref()
+const previewVisible = ref(false)
+const previewUrl = ref('')
+const uploadQueueTimer = ref(null)
+const uploading = ref(false)
+const queuedUidSet = ref(new Set())
+
+const currentList = computed({
+ get() {
+ if (props.index > -1) {
+ const row = props.fileList?.[props.index]
+ return Array.isArray(row?.[props.childrenKey]) ? row[props.childrenKey] : []
+ }
+ return Array.isArray(props.fileList) ? props.fileList : []
+ },
+ set(value) {
+ const nextList = Array.isArray(value) ? value : []
+ if (props.index > -1) {
+ const nextModelValue = Array.isArray(props.fileList) ? [...props.fileList] : []
+ const currentRow = nextModelValue[props.index] || {}
+ nextModelValue[props.index] = {
+ ...currentRow,
+ [props.childrenKey]: nextList,
+ }
+ emit('update:fileList', nextModelValue)
+ emit('change', nextList, nextModelValue)
+ return
+ }
+ emit('update:fileList', nextList)
+ emit('change', nextList, nextList)
+ },
+})
+
+const displayFileList = computed(() => {
+ return currentList.value.map((item, index) => ({
+ uid: getItemUid(item, index),
+ name: getItemName(item, index),
+ url: getItemUrl(item),
+ status: 'success',
+ rawData: item,
+ }))
+})
+
+const uploadTip = computed(() => {
+ return `鏀寔 ${props.fileType.join('/')}锛屽崟寮犱笉瓒呰繃 ${props.fileSize}MB锛屾渶澶氫笂浼� ${props.limit} 寮犲浘鐗嘸
+})
+
+function getItemUid(item, index) {
+ if (item?.id !== undefined && item?.id !== null) return `${item.id}`
+ return `${getItemName(item, index)}-${getItemUrl(item) || index}`
+}
+
+function getItemUrl(item) {
+ if (!item) return ''
+ if (typeof item === 'string') return item
+ return item.url || item.previewURL || ''
+}
+
+function getItemName(item, index = 0) {
+ if (!item) return `image-${index + 1}`
+ if (typeof item === 'string') return `image-${index + 1}`
+ return item.name || item.fileName || item.originalFilename || `image-${index + 1}`
+}
+
+function normalizeResponseItem(item, index) {
+ if (typeof item === 'string') {
+ return {
+ name: `image-${currentList.value.length + index + 1}`,
+ url: item,
+ }
+ }
+ return Object.assign({}, item, {
+ url: item.url || item.previewURL || item.previewUrl || '',
+ name: item.name || item.originalFilename || item.fileName || `image-${currentList.value.length + index + 1}`,
+ })
+}
+
+function extractResponseArray(response) {
+ if (Array.isArray(response)) return response
+ if (Array.isArray(response?.data)) return response.data
+ if (Array.isArray(response?.data?.data)) return response.data.data
+ if (Array.isArray(response?.payload)) return response.payload
+ if (Array.isArray(response?.payload?.data)) return response.payload.data
+ if (Array.isArray(response?.rows)) return response.rows
+ if (Array.isArray(response?.result)) return response.result
+ return []
+}
+
+function validateFile(rawFile) {
+ let isValidType = false
+ const extension = rawFile.name.includes('.')
+ ? rawFile.name.slice(rawFile.name.lastIndexOf('.') + 1).toLowerCase()
+ : ''
+
+ if (props.fileType.length) {
+ isValidType = props.fileType.some((type) => {
+ const normalizedType = String(type).toLowerCase()
+ return rawFile.type.toLowerCase().includes(normalizedType) || extension === normalizedType
+ })
+ } else {
+ isValidType = rawFile.type.includes('image')
+ }
+
+ if (!isValidType) {
+ proxy.$modal.msgError(`璇蜂笂浼� ${props.fileType.join('/')} 鏍煎紡鐨勫浘鐗嘸)
+ return false
+ }
+
+ const isWithinSize = rawFile.size / 1024 / 1024 <= props.fileSize
+ if (!isWithinSize) {
+ proxy.$modal.msgError(`鍥剧墖澶у皬涓嶈兘瓒呰繃 ${props.fileSize}MB`)
+ return false
+ }
+
+ return true
+}
+
+function scheduleUpload(uploadFiles) {
+ clearTimeout(uploadQueueTimer.value)
+ uploadQueueTimer.value = setTimeout(() => {
+ const readyFiles = uploadFiles.filter((file) => file.raw && !queuedUidSet.value.has(file.uid))
+ if (!readyFiles.length) return
+
+ const remainCount = props.limit - currentList.value.length
+ if (remainCount <= 0) {
+ proxy.$modal.msgError(`鏈�澶氫笂浼� ${props.limit} 寮犲浘鐗嘸)
+ uploadRef.value?.clearFiles()
+ return
+ }
+
+ const selectedFiles = readyFiles.slice(0, remainCount)
+ if (selectedFiles.length < readyFiles.length) {
+ proxy.$modal.msgWarning(`鏈�澶氫笂浼� ${props.limit} 寮犲浘鐗囷紝瓒呭嚭閮ㄥ垎宸插拷鐣)
+ }
+
+ selectedFiles.forEach((file) => queuedUidSet.value.add(file.uid))
+ uploadSelectedFiles(selectedFiles)
+ }, 0)
+}
+
+async function uploadSelectedFiles(files) {
+ const validFiles = files.filter((file) => validateFile(file.raw))
+ const invalidFiles = files.filter((file) => !validFiles.includes(file))
+
+ invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid))
+
+ if (!validFiles.length) {
+ uploadRef.value?.clearFiles()
+ return
+ }
+
+ const formData = new FormData()
+ validFiles.forEach((file) => {
+ formData.append(props.uploadFieldName, file.raw)
+ })
+
+ uploading.value = true
+ proxy.$modal.loading('鍥剧墖涓婁紶涓紝璇风◢鍊�...')
+
+ try {
+ const response = await uploadFile(formData)
+ const responseList = extractResponseArray(response).map((item, index) => normalizeResponseItem(item, index))
+
+ if (!responseList.length) {
+ proxy.$modal.msgError('涓婁紶鎺ュ彛鏈繑鍥炴暟缁勬暟鎹�')
+ return
+ }
+ console.log('responseList', responseList)
+
+ currentList.value = [...currentList.value, ...responseList]
+ console.log('currentList.value', currentList.value)
+ proxy.$modal.msgSuccess('涓婁紶鎴愬姛')
+ } catch (error) {
+ proxy.$modal.msgError(error?.message || '涓婁紶澶辫触')
+ } finally {
+ validFiles.forEach((file) => queuedUidSet.value.delete(file.uid))
+ invalidFiles.forEach((file) => queuedUidSet.value.delete(file.uid))
+ uploadRef.value?.clearFiles()
+ uploading.value = false
+ proxy.$modal.closeLoading()
+ }
+}
+
+function handleChange(file, uploadFiles) {
+ if (props.disabled || uploading.value) return
+ scheduleUpload(uploadFiles)
+}
+
+function handleRemove(file) {
+ const targetUrl = file.url || getItemUrl(file.rawData)
+ const nextList = currentList.value.filter((item, index) => {
+ const itemUrl = getItemUrl(item)
+ const itemName = getItemName(item, index)
+ return !(itemUrl === targetUrl && itemName === file.name)
+ })
+ currentList.value = nextList
+}
+
+function handlePreview(file) {
+ previewUrl.value = file.url || getItemUrl(file.rawData)
+ previewVisible.value = true
+}
+
+function handleExceed() {
+ proxy.$modal.msgError(`鏈�澶氫笂浼� ${props.limit} 寮犲浘鐗嘸)
+}
+
+onBeforeUnmount(() => {
+ clearTimeout(uploadQueueTimer.value)
+})
+</script>
+
+<template>
+ <div class="attachment-upload-image">
+ <el-upload
+ ref="uploadRef"
+ :auto-upload="false"
+ :multiple="true"
+ :show-file-list="true"
+ :file-list="displayFileList"
+ list-type="picture-card"
+ accept="image/*"
+ :disabled="disabled || uploading"
+ :limit="limit"
+ :on-change="handleChange"
+ :on-remove="handleRemove"
+ :on-preview="handlePreview"
+ :on-exceed="handleExceed"
+ >
+ <div class="upload-trigger">
+ <el-icon>
+ <Plus/>
+ </el-icon>
+ <span>{{ buttonText }}</span>
+ </div>
+ </el-upload>
+
+ <div class="upload-tip">
+ {{ uploadTip }}
+ </div>
+
+ <el-dialog v-model="previewVisible" title="鍥剧墖棰勮" width="720px" append-to-body>
+ <img class="preview-image" :src="previewUrl" alt="preview"/>
+ </el-dialog>
+ </div>
+</template>
+
+<style scoped lang="scss">
+.attachment-upload-image {
+ width: 100%;
+}
+
+.upload-trigger {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ color: var(--el-text-color-secondary);
+ font-size: 12px;
+ line-height: 1.2;
+}
+
+.upload-tip {
+ margin-top: 8px;
+ color: var(--el-text-color-secondary);
+ font-size: 12px;
+}
+
+.preview-image {
+ display: block;
+ max-width: 100%;
+ margin: 0 auto;
+}
+
+:deep(.el-upload-list--picture-card) {
+ margin: 0;
+}
+
+:deep(.el-upload--picture-card) {
+ width: 132px;
+ height: 132px;
+}
+
+:deep(.el-upload-list--picture-card .el-upload-list__item) {
+ width: 132px;
+ height: 132px;
+}
+</style>
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
index 5385ec4..0ff79ef 100644
--- a/src/components/Breadcrumb/index.vue
+++ b/src/components/Breadcrumb/index.vue
@@ -85,15 +85,34 @@
</script>
<style lang='scss' scoped>
-.app-breadcrumb.el-breadcrumb {
- display: inline-block;
- font-size: 14px;
- line-height: 50px;
- margin-left: 8px;
-
- .no-redirect {
- color: #002FA7;
- cursor: text;
- }
-}
-</style>
\ No newline at end of file
+.app-breadcrumb.el-breadcrumb {
+ display: inline-block;
+ font-size: 14px;
+ line-height: 56px;
+ margin-left: 8px;
+
+ :deep(.el-breadcrumb__inner) {
+ color: var(--text-secondary);
+ font-weight: 500;
+ transition: color 0.2s ease;
+ }
+
+ :deep(.el-breadcrumb__separator) {
+ color: var(--text-tertiary);
+ }
+
+ a {
+ color: var(--text-secondary);
+
+ &:hover {
+ color: var(--current-color);
+ }
+ }
+
+ .no-redirect {
+ color: var(--current-color);
+ font-weight: 600;
+ cursor: text;
+ }
+}
+</style>
diff --git a/src/components/Dialog/FileList.vue b/src/components/Dialog/FileList.vue
new file mode 100644
index 0000000..6e0ca23
--- /dev/null
+++ b/src/components/Dialog/FileList.vue
@@ -0,0 +1,245 @@
+<template>
+ <el-dialog v-model="isShow"
+ :title="title"
+ :width="width"
+ @close="handleClose"
+ class="attachment-dialog">
+ <!-- 宸ュ叿鏍� -->
+ <div v-if="editable"
+ class="toolbar">
+ <el-button type="primary"
+ size="small"
+ @click="handleUpload">
+ 涓婁紶闄勪欢
+ </el-button>
+ </div>
+ <!-- 涓婁紶缁勪欢寮圭獥 -->
+ <el-dialog v-model="uploadDialogVisible"
+ title="涓婁紶闄勪欢"
+ width="50%"
+ @close="closeUpload">
+ <AttachmentUpload v-model:file-list="newFileList" />
+ <template #footer>
+ <el-button @click="saveUpload">淇濆瓨</el-button>
+ <el-button @click="closeUpload">鍏抽棴</el-button>
+ </template>
+ </el-dialog>
+ <!-- 鏂囦欢鍒楄〃琛ㄦ牸 -->
+ <div class="table-container">
+ <el-table :data="tableData"
+ border
+ class="attachment-table"
+ :height="tableData.length > 0 ? 'auto' : '120px'">
+ <el-table-column label="闄勪欢鍚嶇О"
+ prop="originalFilename"
+ show-overflow-tooltip />
+ <el-table-column v-if="showActions"
+ fixed="right"
+ label="鎿嶄綔"
+ :width="120"
+ align="center">
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ size="small"
+ class="download-link"
+ @click="downloadFile(scope.row.downloadURL)">
+ 涓嬭浇
+ </el-button>
+ <el-button v-if="editable"
+ link
+ type="danger"
+ size="small"
+ @click="handleDelete(scope.row)">
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </el-dialog>
+</template>
+
+<script setup>
+ import { ref, computed, getCurrentInstance, onMounted, watch } from "vue";
+ import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue";
+ import {
+ attachmentList,
+ deleteAttachment,
+ createAttachment,
+ } from "@/api/basicData/storageAttachment.js";
+
+ const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ recordType: {
+ type: String,
+ default: "",
+ required: true,
+ },
+ recordId: {
+ type: Number,
+ default: 0,
+ required: true,
+ },
+ title: {
+ type: String,
+ default: "闄勪欢",
+ },
+ width: {
+ type: String,
+ default: "50%",
+ },
+ showActions: {
+ type: Boolean,
+ default: true,
+ },
+ editable: {
+ type: Boolean,
+ default: true,
+ },
+ });
+
+ const emit = defineEmits(["close", "download", "upload", "delete"]);
+
+ const { proxy } = getCurrentInstance();
+ const tableData = ref([]);
+ const uploadDialogVisible = ref(false);
+ const newFileList = ref([]);
+
+ const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit("update:visible", val);
+ },
+ });
+
+ const handleClose = () => {
+ isShow.value = false;
+ };
+
+ const handleUpload = () => {
+ uploadDialogVisible.value = true;
+ };
+
+ const saveUpload = async () => {
+ // 妫�鏌ユ槸鍚︽湁鏂颁笂浼犵殑鏂囦欢
+ if (newFileList.value.length > 0) {
+ try {
+ await createAttachment({
+ application: "file",
+ recordType: props.recordType,
+ recordId: props.recordId,
+ storageBlobDTOs: [...newFileList.value, ...tableData.value],
+ });
+ newFileList.value = [];
+ // 鍒锋柊鍒楄〃
+ setList();
+ } catch (error) {
+ proxy?.$modal?.msgError("涓婁紶澶辫触");
+ }
+ }
+ uploadDialogVisible.value = false;
+ };
+
+ const closeUpload = () => {
+ newFileList.value = [];
+ uploadDialogVisible.value = false;
+ };
+
+ const handleDelete = async (row, index) => {
+ try {
+ await deleteAttachment([row.storageAttachmentId]);
+ proxy?.$modal?.msgSuccess("鍒犻櫎鎴愬姛");
+ setList();
+ } catch (error) {
+ proxy?.$modal?.msgError("鍒犻櫎澶辫触");
+ }
+ };
+
+ const setList = () => {
+ attachmentList({
+ recordType: props.recordType,
+ recordId: props.recordId,
+ }).then(res => {
+ if (res && res.data) {
+ tableData.value = res.data || [];
+ }
+ });
+ };
+
+ const downloadFile = url => {
+ window.open(url, "_blank");
+ };
+ onMounted(() => {
+ setList();
+ });
+</script>
+
+<style scoped>
+ .attachment-dialog {
+ border-radius: 12px;
+ }
+
+ .toolbar {
+ margin-bottom: 16px;
+ text-align: right;
+ }
+
+ .table-container {
+ max-height: 40vh;
+ overflow-y: auto;
+ min-height: 120px;
+ padding-bottom: 16px;
+ box-sizing: border-box;
+ will-change: scroll-position;
+ transform: translateZ(0);
+ -webkit-overflow-scrolling: touch;
+ }
+
+ :deep(.el-table) {
+ margin-bottom: 0;
+ }
+
+ :deep(.el-table__body-wrapper) {
+ overflow-y: auto;
+ will-change: transform;
+ transform: translateZ(0);
+ }
+
+ :deep(.el-table__body tr) {
+ transition: none;
+ }
+
+ :deep(.el-dialog__footer) {
+ padding-top: 12px;
+ border-top: 1px solid #e9ecef;
+ }
+
+ .attachment-table {
+ border-radius: 8px;
+ }
+
+ :deep(.el-dialog__header) {
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ padding: 16px 20px;
+ }
+
+ :deep(.el-dialog__title) {
+ font-size: 16px;
+ font-weight: 600;
+ }
+
+ :deep(.el-dialog__body) {
+ padding: 16px 20px;
+ }
+
+ :deep(.el-table__empty-text) {
+ color: #999;
+ }
+</style>
\ No newline at end of file
diff --git a/src/components/Dialog/FileListDialog.vue b/src/components/Dialog/FileListDialog.vue
index fc82411..6fea795 100644
--- a/src/components/Dialog/FileListDialog.vue
+++ b/src/components/Dialog/FileListDialog.vue
@@ -77,6 +77,7 @@
@pagination="paginationSearch"
@change="handleChange" />
</el-dialog>
+<!-- // todo 闄勪欢棰勮鐩稿叧 -->
<filePreview v-if="showPreview"
ref="filePreviewRef" />
</template>
diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue
index c283e42..8f0eebd 100644
--- a/src/components/Editor/index.vue
+++ b/src/components/Editor/index.vue
@@ -5,11 +5,11 @@
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
- name="file"
+ name="files"
:show-file-list="false"
:headers="headers"
class="editor-img-uploader"
- v-if="type == 'url'"
+ v-if="type === 'url'"
>
<i ref="uploadRef" class="editor-img-uploader"></i>
</el-upload>
@@ -27,15 +27,15 @@
</template>
<script setup>
-import axios from "axios";
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { getToken } from "@/utils/auth";
+import {uploadPublicFile} from "@/api/basicData/common.js";
const { proxy } = getCurrentInstance();
const quillEditorRef = ref();
-const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 涓婁紶鐨勫浘鐗囨湇鍔″櫒鍦板潃
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/public/upload"); // 涓婁紶鐨勫浘鐗囨湇鍔″櫒鍦板潃
const headers = ref({
Authorization: "Bearer " + getToken(),
});
@@ -157,21 +157,40 @@
function handleUploadSuccess(res, file) {
// 濡傛灉涓婁紶鎴愬姛
if (res.code == 200) {
+ const imageUrl = resolveImageUrl(res);
+ if (!imageUrl) {
+ proxy.$modal.msgError("鏈幏鍙栧埌鍥剧墖鍦板潃");
+ return;
+ }
// 鑾峰彇瀵屾枃鏈疄渚�
let quill = toRaw(quillEditorRef.value).getQuill();
// 鑾峰彇鍏夋爣浣嶇疆
- let length = quill.selection.savedRange.index;
+ const selection = quill.getSelection(true);
+ const length = selection ? selection.index : quill.getLength();
// 鎻掑叆鍥剧墖锛宺es.url涓烘湇鍔″櫒杩斿洖鐨勫浘鐗囬摼鎺ュ湴鍧�
- quill.insertEmbed(
- length,
- "image",
- import.meta.env.VITE_APP_BASE_API + res.fileName
- );
+ quill.insertEmbed(length, "image", imageUrl);
// 璋冩暣鍏夋爣鍒版渶鍚�
quill.setSelection(length + 1);
} else {
proxy.$modal.msgError("鍥剧墖鎻掑叆澶辫触");
}
+}
+
+function resolveImageUrl(res) {
+ if (!res) return "";
+ // 鍏煎鏂版帴鍙�: data[0].previewURL
+ const previewURL = res?.data?.[0]?.previewURL;
+ if (previewURL) {
+ return previewURL;
+ }
+ // 鍏煎鏃ф帴鍙�
+ if (res.url) {
+ return res.url;
+ }
+ if (res.fileName) {
+ return `${import.meta.env.VITE_APP_BASE_API}${res.fileName}`;
+ }
+ return "";
}
// 涓婁紶澶辫触澶勭悊
@@ -196,17 +215,10 @@
function insertImage(file) {
const formData = new FormData();
- formData.append("file", file);
- axios
- .post(uploadUrl.value, formData, {
- headers: {
- "Content-Type": "multipart/form-data",
- Authorization: headers.value.Authorization,
- },
- })
- .then((res) => {
- handleUploadSuccess(res.data);
- });
+ formData.append("files", file);
+ uploadPublicFile(formData).then((res) => {
+ handleUploadSuccess(res)
+ })
}
</script>
diff --git a/src/components/ImagePreview/index.vue b/src/components/ImagePreview/index.vue
deleted file mode 100644
index 00212c5..0000000
--- a/src/components/ImagePreview/index.vue
+++ /dev/null
@@ -1,92 +0,0 @@
-<template>
- <el-image
- :src="`${realSrc}`"
- fit="cover"
- :style="`width:${realWidth};height:${realHeight};`"
- :preview-src-list="realSrcList"
- preview-teleported
- >
- <template #error>
- <div class="image-slot">
- <el-icon><picture-filled /></el-icon>
- </div>
- </template>
- </el-image>
-</template>
-
-<script setup>
-import { isExternal } from "@/utils/validate"
-
-const props = defineProps({
- src: {
- type: String,
- default: ""
- },
- width: {
- type: [Number, String],
- default: ""
- },
- height: {
- type: [Number, String],
- default: ""
- }
-})
-
-const realSrc = computed(() => {
- if (!props.src) {
- return
- }
- let real_src = props.src.split(",")[0]
- if (isExternal(real_src)) {
- return real_src
- }
- return import.meta.env.VITE_APP_BASE_API + real_src
-})
-
-const realSrcList = computed(() => {
- if (!props.src) {
- return
- }
- let real_src_list = props.src.split(",")
- let srcList = []
- real_src_list.forEach(item => {
- if (isExternal(item)) {
- return srcList.push(item)
- }
- return srcList.push(import.meta.env.VITE_APP_BASE_API + item)
- })
- return srcList
-})
-
-const realWidth = computed(() =>
- typeof props.width == "string" ? props.width : `${props.width}px`
-)
-
-const realHeight = computed(() =>
- typeof props.height == "string" ? props.height : `${props.height}px`
-)
-</script>
-
-<style lang="scss" scoped>
-.el-image {
- border-radius: 5px;
- background-color: #ebeef5;
- box-shadow: 0 0 5px 1px #ccc;
- :deep(.el-image__inner) {
- transition: all 0.3s;
- cursor: pointer;
- &:hover {
- transform: scale(1.2);
- }
- }
- :deep(.image-slot) {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 100%;
- color: #909399;
- font-size: 30px;
- }
-}
-</style>
diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue
deleted file mode 100644
index 9670c72..0000000
--- a/src/components/ImageUpload/index.vue
+++ /dev/null
@@ -1,256 +0,0 @@
-<template>
- <div class="component-upload-image">
- <el-upload
- multiple
- :action="uploadImgUrl"
- list-type="picture-card"
- :on-success="handleUploadSuccess"
- :before-upload="handleBeforeUpload"
- :data="data"
- :limit="limit"
- :on-error="handleUploadError"
- :on-exceed="handleExceed"
- ref="imageUpload"
- :before-remove="handleDelete"
- :show-file-list="true"
- :headers="headers"
- :file-list="fileList"
- :on-preview="handlePictureCardPreview"
- :class="{ hide: fileList.length >= limit }"
- >
- <el-icon class="avatar-uploader-icon"><plus /></el-icon>
- </el-upload>
- <!-- 涓婁紶鎻愮ず -->
- <div class="el-upload__tip" v-if="showTip">
- 璇蜂笂浼�
- <template v-if="fileSize">
- 澶у皬涓嶈秴杩� <b style="color: #f56c6c">{{ fileSize }}MB</b>
- </template>
- <template v-if="fileType">
- 鏍煎紡涓� <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
- </template>
- 鐨勬枃浠�
- </div>
-
- <el-dialog
- v-model="dialogVisible"
- title="棰勮"
- width="800px"
- append-to-body
- >
- <img
- :src="dialogImageUrl"
- style="display: block; max-width: 100%; margin: 0 auto"
- />
- </el-dialog>
- </div>
-</template>
-
-<script setup>
-import { getToken } from "@/utils/auth";
-import { isExternal } from "@/utils/validate";
-import Sortable from "sortablejs";
-
-const props = defineProps({
- modelValue: [String, Object, Array],
- // 涓婁紶鎺ュ彛鍦板潃
- action: {
- type: String,
- default: "/common/upload",
- },
- // 涓婁紶鎼哄甫鐨勫弬鏁�
- data: {
- type: Object,
- },
- // 鍥剧墖鏁伴噺闄愬埗
- limit: {
- type: Number,
- default: 5,
- },
- // 澶у皬闄愬埗(MB)
- fileSize: {
- type: Number,
- default: 5,
- },
- // 鏂囦欢绫诲瀷, 渚嬪['png', 'jpg', 'jpeg']
- fileType: {
- type: Array,
- default: () => ["png", "jpg", "jpeg"],
- },
- // 鏄惁鏄剧ず鎻愮ず
- isShowTip: {
- type: Boolean,
- default: true,
- },
- // 鎷栧姩鎺掑簭
- drag: {
- type: Boolean,
- default: true,
- },
-});
-
-const { proxy } = getCurrentInstance();
-const emit = defineEmits();
-const number = ref(0);
-const uploadList = ref([]);
-const dialogImageUrl = ref("");
-const dialogVisible = ref(false);
-const baseUrl = import.meta.env.VITE_APP_BASE_API;
-const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action); // 涓婁紶鐨勫浘鐗囨湇鍔″櫒鍦板潃
-const headers = ref({ Authorization: "Bearer " + getToken() });
-const fileList = ref([]);
-const showTip = computed(
- () => props.isShowTip && (props.fileType || props.fileSize)
-);
-
-watch(
- () => props.modelValue,
- (val) => {
- if (val) {
- // 棣栧厛灏嗗�艰浆涓烘暟缁�
- const list = Array.isArray(val) ? val : props.modelValue.split(",");
- // 鐒跺悗灏嗘暟缁勮浆涓哄璞℃暟缁�
- fileList.value = list.map((item) => {
- if (typeof item === "string") {
- if (item.indexOf(baseUrl) === -1 && !isExternal(item)) {
- item = { name: baseUrl + item, url: baseUrl + item };
- } else {
- item = { name: item, url: item };
- }
- }
- return item;
- });
- } else {
- fileList.value = [];
- return [];
- }
- },
- { deep: true, immediate: true }
-);
-
-// 涓婁紶鍓峫oading鍔犺浇
-function handleBeforeUpload(file) {
- let isImg = false;
- if (props.fileType.length) {
- let fileExtension = "";
- if (file.name.lastIndexOf(".") > -1) {
- fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
- }
- isImg = props.fileType.some((type) => {
- if (file.type.indexOf(type) > -1) return true;
- if (fileExtension && fileExtension.indexOf(type) > -1) return true;
- return false;
- });
- } else {
- isImg = file.type.indexOf("image") > -1;
- }
- if (!isImg) {
- proxy.$modal.msgError(
- `鏂囦欢鏍煎紡涓嶆纭紝璇蜂笂浼�${props.fileType.join("/")}鍥剧墖鏍煎紡鏂囦欢!`
- );
- return false;
- }
- if (file.name.includes(",")) {
- proxy.$modal.msgError("鏂囦欢鍚嶄笉姝g‘锛屼笉鑳藉寘鍚嫳鏂囬�楀彿!");
- return false;
- }
- if (props.fileSize) {
- const isLt = file.size / 1024 / 1024 < props.fileSize;
- if (!isLt) {
- proxy.$modal.msgError(`涓婁紶澶村儚鍥剧墖澶у皬涓嶈兘瓒呰繃 ${props.fileSize} MB!`);
- return false;
- }
- }
- proxy.$modal.loading("姝e湪涓婁紶鍥剧墖锛岃绋嶅��...");
- number.value++;
-}
-
-// 鏂囦欢涓暟瓒呭嚭
-function handleExceed() {
- proxy.$modal.msgError(`涓婁紶鏂囦欢鏁伴噺涓嶈兘瓒呰繃 ${props.limit} 涓�!`);
-}
-
-// 涓婁紶鎴愬姛鍥炶皟
-function handleUploadSuccess(res, file) {
- if (res.code === 200) {
- uploadList.value.push({ name: res.fileName, url: res.fileName });
- uploadedSuccessfully();
- } else {
- number.value--;
- proxy.$modal.closeLoading();
- proxy.$modal.msgError(res.msg);
- proxy.$refs.imageUpload.handleRemove(file);
- uploadedSuccessfully();
- }
-}
-
-// 鍒犻櫎鍥剧墖
-function handleDelete(file) {
- const findex = fileList.value.map((f) => f.name).indexOf(file.name);
- if (findex > -1 && uploadList.value.length === number.value) {
- fileList.value.splice(findex, 1);
- emit("update:modelValue", listToString(fileList.value));
- return false;
- }
-}
-
-// 涓婁紶缁撴潫澶勭悊
-function uploadedSuccessfully() {
- if (number.value > 0 && uploadList.value.length === number.value) {
- fileList.value = fileList.value
- .filter((f) => f.url !== undefined)
- .concat(uploadList.value);
- uploadList.value = [];
- number.value = 0;
- emit("update:modelValue", listToString(fileList.value));
- proxy.$modal.closeLoading();
- }
-}
-
-// 涓婁紶澶辫触
-function handleUploadError() {
- proxy.$modal.msgError("涓婁紶鍥剧墖澶辫触");
- proxy.$modal.closeLoading();
-}
-
-// 棰勮
-function handlePictureCardPreview(file) {
- dialogImageUrl.value = file.url;
- dialogVisible.value = true;
-}
-
-// 瀵硅薄杞垚鎸囧畾瀛楃涓插垎闅�
-function listToString(list, separator) {
- let strs = "";
- separator = separator || ",";
- for (let i in list) {
- if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
- strs += list[i].url.replace(baseUrl, "") + separator;
- }
- }
- return strs != "" ? strs.substr(0, strs.length - 1) : "";
-}
-
-// 鍒濆鍖栨嫋鎷芥帓搴�
-onMounted(() => {
- if (props.drag) {
- nextTick(() => {
- const element = document.querySelector(".el-upload-list");
- Sortable.create(element, {
- onEnd: (evt) => {
- const movedItem = fileList.value.splice(evt.oldIndex, 1)[0];
- fileList.value.splice(evt.newIndex, 0, movedItem);
- emit("update:modelValue", listToString(fileList.value));
- },
- });
- });
- }
-});
-</script>
-
-<style scoped lang="scss">
-// .el-upload--picture-card 鎺у埗鍔犲彿閮ㄥ垎
-:deep(.hide .el-upload--picture-card) {
- display: none;
-}
-</style>
diff --git a/src/components/PIMTable/PIMTable.vue b/src/components/PIMTable/PIMTable.vue
index a418280..f99b1eb 100644
--- a/src/components/PIMTable/PIMTable.vue
+++ b/src/components/PIMTable/PIMTable.vue
@@ -1,82 +1,76 @@
<template>
- <el-table
- ref="multipleTable"
- v-loading="tableLoading"
- :border="border"
- :data="tableData"
- :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
- :height="height"
- :highlight-current-row="highlightCurrentRow"
- :row-class-name="rowClassName"
- :row-style="rowStyle"
- :row-key="rowKey"
- :style="tableStyle"
- tooltip-effect="dark"
- :expand-row-keys="expandRowKeys"
- :show-summary="isShowSummary"
- :summary-method="summaryMethod"
- @row-click="rowClick"
- @current-change="currentChange"
- @selection-change="handleSelectionChange"
- @expand-change="expandChange"
- class="lims-table"
- >
- <el-table-column
- align="center"
- type="selection"
- width="55"
- v-if="isSelection"
- />
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
-
- <el-table-column
- v-for="(item, index) in column"
- :key="index"
- :column-key="item.columnKey"
- :filter-method="item.filterHandler"
- :filter-multiple="item.filterMultiple"
- :filtered-value="item.filteredValue"
- :filters="item.filters"
- :fixed="item.fixed"
- :label="item.label"
- :prop="item.prop"
- :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
- :align="item.align"
- :sortable="!!item.sortable"
- :type="item.type"
- :width="item.width"
- >
+ <el-table ref="multipleTable"
+ v-loading="tableLoading"
+ :border="border"
+ :data="tableData"
+ :header-cell-style="mergedHeaderCellStyle"
+ :height="height"
+ :highlight-current-row="highlightCurrentRow"
+ :row-class-name="rowClassName"
+ :row-style="rowStyle"
+ :row-key="rowKey"
+ :style="tableStyle"
+ tooltip-effect="dark"
+ :expand-row-keys="expandRowKeys"
+ :show-summary="isShowSummary"
+ :summary-method="summaryMethod"
+ @row-click="rowClick"
+ @current-change="currentChange"
+ @selection-change="handleSelectionChange"
+ @expand-change="expandChange"
+ class="lims-table">
+ <el-table-column align="center"
+ type="selection"
+ :selectable="selectable"
+ width="55"
+ v-if="isSelection" />
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column v-for="(item, index) in column"
+ :key="index"
+ :column-key="item.columnKey"
+ :filter-method="item.filterHandler"
+ :filter-multiple="item.filterMultiple"
+ :filtered-value="item.filteredValue"
+ :filters="item.filters"
+ :fixed="item.fixed"
+ :label="item.label"
+ :prop="item.prop"
+ :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
+ :align="item.align"
+ :sortable="!!item.sortable"
+ :type="item.type"
+ :width="item.width"
+ :minWidth="item.minWidth">
<template #header="scope">
- <div class="pim-table-header-cell">
+ <div class="pim-table-header-cell"
+ :class="{ 'has-extra': item.headerSlot }">
<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 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"
- >
- <slot
- v-if="item.theadSlot"
- :name="item.theadSlot"
- :index="scope.$index"
- :row="scope.row"
- />
+ <template v-if="item.hasOwnProperty('colunmTemplate')"
+ #[item.colunmTemplate]="scope">
+ <slot v-if="item.theadSlot"
+ :name="item.theadSlot"
+ :index="scope.$index"
+ :row="scope.row" />
</template>
-
<template #default="scope">
<!-- 鎻掓Ы -->
<div v-if="item.dataType == 'slot'">
- <slot
- v-if="item.slot"
- :index="scope.$index"
- :name="item.slot"
- :row="scope.row"
- />
+ <slot v-if="item.slot"
+ :index="scope.$index"
+ :name="item.slot"
+ :row="scope.row" />
</div>
<!-- 杩涘害鏉� -->
<div v-else-if="item.dataType == 'progress'">
@@ -84,127 +78,111 @@
</div>
<!-- 鍥剧墖 -->
<div v-else-if="item.dataType == 'image'">
- <img
- :src="javaApi + '/img/' + scope.row[item.prop]"
- alt=""
- style="width: 40px; height: 40px; margin-top: 10px"
- />
+ <img :src="javaApi + '/img/' + scope.row[item.prop]"
+ alt=""
+ style="width: 40px; height: 40px; margin-top: 10px" />
</div>
-
<!-- tag -->
<div v-else-if="item.dataType == 'tag'">
- <el-tag
- v-if="
+ <el-tag v-if="
typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
'string'
"
- :title="formatters(scope.row[item.prop], item.formatData)"
- :type="formatType(scope.row[item.prop], item.formatType)"
- >
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(scope.row[item.prop], item.formatType)">
{{ formatters(scope.row[item.prop], item.formatData) }}
</el-tag>
-
- <el-tag
- v-for="(tag, index) in dataTypeFn(
+ <el-tag v-for="(tag, index) in dataTypeFn(
scope.row[item.prop],
item.formatData
)"
- v-else-if="
+ v-else-if="
typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
'object'
"
- :key="index"
- :title="formatters(scope.row[item.prop], item.formatData)"
- :type="formatType(tag, item.formatType)"
- >
+ :key="index"
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(tag, item.formatType)">
{{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }}
</el-tag>
-
- <el-tag
- v-else
- :title="formatters(scope.row[item.prop], item.formatData)"
- :type="formatType(scope.row[item.prop], item.formatType)"
- >
+ <el-tag v-else
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(scope.row[item.prop], item.formatType)">
{{ formatters(scope.row[item.prop], item.formatData) }}
</el-tag>
</div>
-
<!-- 鎸夐挳 -->
- <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'"
- v-if="o.showHide ? o.showHide(scope.row) : true"
- :disabled="o.disabled ? o.disabled(scope.row) : false"
- :plain="o.plain"
- type="primary"
- :style="{
- color:
- o.name === '鍒犻櫎' || o.name === 'delete'
- ? '#f56c6c'
- : o.color,
+ <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'"
+ v-if="o.showHide ? o.showHide(scope.row) : true"
+ :disabled="isOperationDisabled(o, scope.row)"
+ :plain="o.plain"
+ type="primary"
+ :style="{
+ color: getOperationColor(o, scope.row),
+ fontWeight: 'bold',
}"
- link
- @click.stop="o.clickFun(scope.row)"
- :key="key"
- >
+ link
+ @click.stop="o.clickFun(scope.row)"
+ :key="key">
{{ o.name }}
</el-button>
- <el-upload
- :action="
+ <el-upload :action="
javaApi +
o.url +
'?id=' +
(o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
"
- ref="uploadRef"
- :multiple="o.multiple ? o.multiple : false"
- :limit="1"
- :disabled="o.disabled ? o.disabled(scope.row) : false"
- :accept="
+ ref="uploadRef"
+ :multiple="o.multiple ? o.multiple : false"
+ :limit="1"
+ :disabled="isOperationDisabled(o, scope.row)"
+ :accept="
o.accept
? o.accept
: '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
"
- v-if="o.type == 'upload'"
- style="display: inline-block; width: 50px"
- v-show="o.showHide ? o.showHide(scope.row) : true"
- :headers="uploadHeader"
- :before-upload="(file) => beforeUpload(file, scope.$index)"
- :on-change="
+ v-if="o.type == 'upload'"
+ style="display: inline-block; width: 50px"
+ v-show="o.showHide ? o.showHide(scope.row) : true"
+ :headers="uploadHeader"
+ :before-upload="(file) => beforeUpload(file, scope.$index)"
+ :on-change="
(file, fileList) => handleChange(file, fileList, scope.$index)
"
- :on-error="
+ :on-error="
(error, file, fileList) =>
onError(error, file, fileList, scope.$index)
"
- :on-success="
+ :on-success="
(response, file, fileList) =>
handleSuccessUp(response, file, fileList, scope.$index)
"
- :on-exceed="onExceed"
- :show-file-list="false"
- >
- <el-button
- link
- type="primary"
- :disabled="o.disabled ? o.disabled(scope.row) : false"
- >{{ o.name }}</el-button
- >
+ :on-exceed="onExceed"
+ :show-file-list="false">
+ <el-button link
+ type="primary"
+ :disabled="isOperationDisabled(o, scope.row)"
+ :style="{
+ color: getOperationColor(o, scope.row),
+ }">{{ o.name }}</el-button>
</el-upload>
</template>
</div>
<!-- 鍙偣鍑荤殑鏂囧瓧 -->
- <div
- v-else-if="item.dataType == 'link'"
- class="cell link"
- style="width: 100%"
- @click="goLink(scope.row, item.linkMethod)"
- >
+ <div v-else-if="item.dataType == 'link'"
+ class="cell link"
+ style="width: 100%"
+ @click="goLink(scope.row, item.linkMethod)">
<span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
</div>
<!-- 榛樿绾睍绀烘暟鎹� -->
- <div v-else class="cell" style="width: 100%">
+ <div v-else
+ class="cell"
+ style="width: 100%">
<span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
<span v-else>{{
formatters(scope.row[item.prop], item.formatData)
@@ -213,244 +191,337 @@
</template>
</el-table-column>
</el-table>
- <pagination
- v-if="isShowPagination"
- :total="page.total"
- :layout="page.layout"
- :page="page.current"
- :limit="page.size"
- @pagination="paginationSearch"
- />
+ <pagination v-if="isShowPagination"
+ :total="page.total"
+ :layout="page.layout"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationSearch" />
</template>
<script setup>
-import pagination from "./Pagination.vue";
-import { ref, inject, getCurrentInstance } from "vue";
-import { ElMessage } from "element-plus";
+ import pagination from "./Pagination.vue";
+ import { computed, ref, inject, getCurrentInstance } from "vue";
+ import { ElMessage } from "element-plus";
-// 鑾峰彇鍏ㄥ眬鐨� uploadHeader
-const { proxy } = getCurrentInstance();
-const uploadHeader = proxy.uploadHeader;
-const javaApi = proxy.javaApi;
+ // 鑾峰彇鍏ㄥ眬鐨� uploadHeader
+ const { proxy } = getCurrentInstance();
+ const uploadHeader = proxy.uploadHeader;
+ const javaApi = proxy.javaApi;
-const emit = defineEmits(["pagination", "expand-change", "selection-change", "row-click"]);
+ const emit = defineEmits([
+ "pagination",
+ "expand-change",
+ "selection-change",
+ "row-click",
+ ]);
-// Filters
-const typeFn = (val, row) => {
- return typeof val === "function" ? val(row) : val;
-};
+ // Filters
+ const typeFn = (val, row) => {
+ return typeof val === "function" ? val(row) : val;
+ };
-const formatters = (val, format) => {
- return typeof format === "function" ? format(val) : val;
-};
+ const formatters = (val, format) => {
+ return typeof format === "function" ? format(val) : val;
+ };
-// Props锛堜娇鐢� defineProps 鐨勯潪 TS 褰㈠紡锛�
-const props = defineProps({
- tableLoading: {
- type: Boolean,
- default: false,
- },
- height: {
- type: [Number, String],
- default: "calc(100vh - 22em)",
- },
- expandRowKeys: {
- type: Array,
- default: () => [],
- },
- summaryMethod: {
- type: Function,
- default: () => {},
- },
- rowClick: {
- type: Function,
- default: () => {},
- },
- currentChange: {
- type: Function,
- default: () => {},
- },
- border: {
- type: Boolean,
- default: true,
- },
- isSelection: {
- type: Boolean,
- default: false,
- },
- isShowPagination: {
- type: Boolean,
- default: true,
- },
- isShowSummary: {
- type: Boolean,
- default: false,
- },
- highlightCurrentRow: {
- type: Boolean,
- default: false,
- },
- headerCellStyle: {
- type: Object,
- default: () => ({}),
- },
- column: {
- type: Array,
- default: () => [],
- },
- rowClassName: {
- type: Function,
- default: () => "",
- },
- rowStyle: {
- type: [Object, Function],
- default: () => ({}),
- },
- tableData: {
- type: Array,
- default: () => [],
- },
- rowKey: {
- type: String,
- default: 'id',
- },
- page: {
- type: Object,
- default: () => ({
- total: 0,
- current: 0,
- size: 10,
- layout: "total, sizes, prev, pager, next, jumper",
- }),
- },
- total: {
- type: Number,
- default: 0,
- },
- tableStyle: {
- type: [String, Object],
- default: () => ({ width: "100%" }),
- },
-});
+ // Props锛堜娇鐢� defineProps 鐨勯潪 TS 褰㈠紡锛�
+ const props = defineProps({
+ tableLoading: {
+ type: Boolean,
+ default: false,
+ },
+ height: {
+ type: [Number, String],
+ default: "calc(100vh - 22em)",
+ },
+ expandRowKeys: {
+ type: Array,
+ default: () => [],
+ },
+ summaryMethod: {
+ type: Function,
+ default: () => {},
+ },
+ rowClick: {
+ type: Function,
+ default: () => {},
+ },
+ currentChange: {
+ type: Function,
+ default: () => {},
+ },
+ border: {
+ type: Boolean,
+ default: true,
+ },
+ isSelection: {
+ type: Boolean,
+ default: false,
+ },
+ selectable: {
+ type: Function,
+ default: () => true,
+ },
+ isShowPagination: {
+ type: Boolean,
+ default: true,
+ },
+ isShowSummary: {
+ type: Boolean,
+ default: false,
+ },
+ highlightCurrentRow: {
+ type: Boolean,
+ default: false,
+ },
+ headerCellStyle: {
+ type: Object,
+ default: () => ({}),
+ },
+ column: {
+ type: Array,
+ default: () => [],
+ },
+ rowClassName: {
+ type: Function,
+ default: () => "",
+ },
+ rowStyle: {
+ type: [Object, Function],
+ default: () => ({}),
+ },
+ tableData: {
+ type: Array,
+ default: () => [],
+ },
+ rowKey: {
+ type: String,
+ default: "id",
+ },
+ page: {
+ type: Object,
+ default: () => ({
+ total: 0,
+ current: 0,
+ size: 10,
+ layout: "total, sizes, prev, pager, next, jumper",
+ }),
+ },
+ total: {
+ type: Number,
+ default: 0,
+ },
+ tableStyle: {
+ type: [String, Object],
+ default: () => ({ width: "100%" }),
+ },
+ });
-// Data
-const uploadRefs = ref([]);
-const currentFiles = ref({});
-const uploadKeys = ref({});
+ const mergedHeaderCellStyle = computed(() => ({
+ background: "var(--surface-soft)",
+ color: "var(--text-secondary)",
+ fontWeight: 600,
+ ...props.headerCellStyle,
+ }));
-const indexMethod = (index) => {
- return (props.page.current - 1) * props.page.size + index + 1;
-};
+ // Data
+ const uploadRefs = ref([]);
+ const currentFiles = ref({});
+ const uploadKeys = ref({});
-// 鐐瑰嚮 link 浜嬩欢
-const goLink = (row, linkMethod) => {
- if (!linkMethod) {
- return ElMessage.warning("璇烽厤缃� link 浜嬩欢");
- }
- const parentMethod = getParentMethod(linkMethod);
- if (typeof parentMethod === "function") {
- parentMethod(row);
- } else {
- console.warn(`鐖剁粍浠朵腑鏈壘鍒版柟娉�: ${linkMethod}`);
- }
-};
+ const indexMethod = index => {
+ return (props.page.current - 1) * props.page.size + index + 1;
+ };
-// 鑾峰彇鐖剁粍浠舵柟娉曪紙绀轰緥瀹炵幇锛�
-const getParentMethod = (methodName) => {
- const parentMethods = inject("parentMethods", {});
- return parentMethods[methodName];
-};
-
-const dataTypeFn = (val, format) => {
- if (typeof format === "function") {
- return format(val);
- } else return val;
-};
-
-const formatType = (val, format) => {
- if (typeof format === "function") {
- return format(val);
- } else return "";
-};
-
-// 鏂囦欢鍙樺寲澶勭悊
-const handleChange = (file, fileList, index) => {
- if (fileList.length > 1) {
- const earliestFile = fileList[0];
- uploadRefs.value[index]?.handleRemove(earliestFile);
- }
- currentFiles.value[index] = file;
-};
-
-// 鏂囦欢涓婁紶鍓嶆牎楠�
-const beforeUpload = (rawFile, index) => {
- currentFiles.value[index] = {};
- if (rawfile.size > 1024 * 1024 * 10 * 10) {
- ElMessage.error("涓婁紶鏂囦欢涓嶈秴杩�10M");
- return false;
- }
- return true;
-};
-
-// 涓婁紶鎴愬姛
-const handleSuccessUp = (response, file, fileList, index) => {
- if (response.code == 200) {
- if (uploadRefs[index]) {
- uploadRefs[index].clearFiles();
+ // 鐐瑰嚮 link 浜嬩欢
+ const goLink = (row, linkMethod) => {
+ if (!linkMethod) {
+ return ElMessage.warning("璇烽厤缃� link 浜嬩欢");
}
- currentFiles[index] = file;
- ElMessage.success("涓婁紶鎴愬姛");
- resetUploadComponent(index);
- } else {
- ElMessage.error(response.message);
- }
-};
+ const parentMethod = getParentMethod(linkMethod);
+ if (typeof parentMethod === "function") {
+ parentMethod(row);
+ } else {
+ console.warn(`鐖剁粍浠朵腑鏈壘鍒版柟娉�: ${linkMethod}`);
+ }
+ };
-const resetUploadComponent = (index) => {
- uploadKeys[index] = Date.now();
-};
+ // 鑾峰彇鐖剁粍浠舵柟娉曪紙绀轰緥瀹炵幇锛�
+ const getParentMethod = methodName => {
+ const parentMethods = inject("parentMethods", {});
+ return parentMethods[methodName];
+ };
-// 涓婁紶澶辫触
-const onError = (error, file, fileList, index) => {
- ElMessage.error("鏂囦欢涓婁紶澶辫触锛岃閲嶈瘯");
- if (uploadRefs.value[index]) {
- uploadRefs.value[index].clearFiles();
- }
-};
+ const dataTypeFn = (val, format) => {
+ if (typeof format === "function") {
+ return format(val);
+ } else return val;
+ };
+ const validTagTypes = ["primary", "success", "info", "warning", "danger"];
-// 鏂囦欢鏁伴噺瓒呴檺鎻愮ず
-const onExceed = () => {
- ElMessage.warning("瓒呭嚭鏂囦欢涓暟");
-};
+ const formatType = (val, format) => {
+ const type = typeof format === "function" ? format(val) : undefined;
+ return validTagTypes.includes(type) ? type : undefined;
+ };
-const paginationSearch = ({ page, limit }) => {
- emit("pagination", { page: page, limit: limit });
-};
+ const isOperationDisabled = (operation, row) => {
+ if (!operation?.disabled) return false;
+ return typeof operation.disabled === "function"
+ ? !!operation.disabled(row)
+ : !!operation.disabled;
+ };
-const rowClick = (row) => {
- emit("row-click", row);
-};
+ const parseHexToRgb = hex => {
+ const normalized = String(hex || "")
+ .trim()
+ .replace("#", "");
+ if (normalized.length === 3) {
+ const r = parseInt(normalized[0] + normalized[0], 16);
+ const g = parseInt(normalized[1] + normalized[1], 16);
+ const b = parseInt(normalized[2] + normalized[2], 16);
+ if ([r, g, b].some(n => Number.isNaN(n))) return null;
+ return { r, g, b };
+ }
+ if (normalized.length === 6 || normalized.length === 8) {
+ const r = parseInt(normalized.slice(0, 2), 16);
+ const g = parseInt(normalized.slice(2, 4), 16);
+ const b = parseInt(normalized.slice(4, 6), 16);
+ if ([r, g, b].some(n => Number.isNaN(n))) return null;
+ return { r, g, b };
+ }
+ return null;
+ };
-const expandChange = (row, expandedRows) => {
- emit("expand-change", row, expandedRows);
-};
+ const fadeColor = (color, alpha = 0.35) => {
+ const c = String(color || "").trim();
+ if (!c) return undefined;
+ if (c.startsWith("#")) {
+ const rgb = parseHexToRgb(c);
+ if (!rgb) return c;
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
+ }
+ const rgbMatch = c.match(
+ /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+\s*)?\)$/i
+ );
+ if (rgbMatch) {
+ const r = Number(rgbMatch[1]);
+ const g = Number(rgbMatch[2]);
+ const b = Number(rgbMatch[3]);
+ if ([r, g, b].some(n => Number.isNaN(n))) return c;
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+ }
+ if (c.includes("--el-color-primary")) {
+ return "var(--el-color-primary-light-5)";
+ }
+ if (c.includes("--el-color-danger")) {
+ return "var(--el-color-danger-light-5)";
+ }
+ return "var(--el-text-color-disabled)";
+ };
-const handleSelectionChange = (newSelection) => {
- emit("selection-change", newSelection);
-};
+ const getOperationColor = (operation, row) => {
+ const baseColor =
+ operation?.name === "鍒犻櫎" || operation?.name === "delete"
+ ? "#D93025"
+ : operation?.name === "璇︽儏"
+ ? "#67C23A"
+ : operation?.color || "var(--el-color-primary)";
+
+ if (isOperationDisabled(operation, row)) {
+ return fadeColor(baseColor, 0.35);
+ }
+ return baseColor;
+ };
+
+ // 鏂囦欢鍙樺寲澶勭悊
+ const handleChange = (file, fileList, index) => {
+ if (fileList.length > 1) {
+ const earliestFile = fileList[0];
+ uploadRefs.value[index]?.handleRemove(earliestFile);
+ }
+ currentFiles.value[index] = file;
+ };
+
+ // 鏂囦欢涓婁紶鍓嶆牎楠�
+ const beforeUpload = (rawFile, index) => {
+ currentFiles.value[index] = {};
+ if (rawfile.size > 1024 * 1024 * 10 * 10) {
+ ElMessage.error("涓婁紶鏂囦欢涓嶈秴杩�10M");
+ return false;
+ }
+ return true;
+ };
+
+ // 涓婁紶鎴愬姛
+ const handleSuccessUp = (response, file, fileList, index) => {
+ if (response.code == 200) {
+ if (uploadRefs[index]) {
+ uploadRefs[index].clearFiles();
+ }
+ currentFiles[index] = file;
+ ElMessage.success("涓婁紶鎴愬姛");
+ resetUploadComponent(index);
+ } else {
+ ElMessage.error(response.message);
+ }
+ };
+
+ const resetUploadComponent = index => {
+ uploadKeys[index] = Date.now();
+ };
+
+ // 涓婁紶澶辫触
+ const onError = (error, file, fileList, index) => {
+ ElMessage.error("鏂囦欢涓婁紶澶辫触锛岃閲嶈瘯");
+ if (uploadRefs.value[index]) {
+ uploadRefs.value[index].clearFiles();
+ }
+ };
+
+ // 鏂囦欢鏁伴噺瓒呴檺鎻愮ず
+ const onExceed = () => {
+ ElMessage.warning("瓒呭嚭鏂囦欢涓暟");
+ };
+
+ const paginationSearch = ({ page, limit }) => {
+ emit("pagination", { page: page, limit: limit });
+ };
+
+ const rowClick = row => {
+ emit("row-click", row);
+ };
+
+ const expandChange = (row, expandedRows) => {
+ emit("expand-change", row, expandedRows);
+ };
+
+ const handleSelectionChange = newSelection => {
+ emit("selection-change", newSelection);
+ };
</script>
<style scoped lang="scss">
-.cell {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- padding-right: 0 !important;
- padding-left: 0 !important;
-}
+ .lims-table {
+ border: 1px solid var(--surface-border);
+ border-radius: 18px;
+ background: rgba(255, 255, 255, 0.9);
+ }
-.pim-table-header-extra :deep(.el-input),
-.pim-table-header-extra :deep(.el-select) {
- width: 100%;
-}
+ .cell {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .pim-table-header-extra :deep(.el-input),
+ .pim-table-header-extra :deep(.el-select) {
+ width: 100%;
+ }
+
+ .pim-table-header-title {
+ font-weight: 600;
+ }
</style>
diff --git a/src/components/PIMTable/Pagination.vue b/src/components/PIMTable/Pagination.vue
index 7639e64..001f19a 100644
--- a/src/components/PIMTable/Pagination.vue
+++ b/src/components/PIMTable/Pagination.vue
@@ -91,7 +91,6 @@
<style scoped>
.pagination-container {
background: #fff;
- padding: 16px 0;
margin-top: 0;
}
.pagination-container.hidden {
diff --git a/src/components/PageHeader/index.vue b/src/components/PageHeader/index.vue
index d8fc6fa..60d3961 100644
--- a/src/components/PageHeader/index.vue
+++ b/src/components/PageHeader/index.vue
@@ -43,6 +43,11 @@
<style scoped>
.page-header-wrapper {
margin-bottom: 16px;
+ padding: 16px 18px;
+ border: 1px solid var(--surface-border);
+ border-radius: var(--radius-md);
+ background: rgba(255, 255, 255, 0.82);
+ box-shadow: var(--shadow-sm);
}
.page-header-wrapper :deep(.el-page-header__extra) {
@@ -50,4 +55,9 @@
align-items: center;
gap: 8px;
}
+
+.page-header-wrapper :deep(.el-page-header__content) {
+ font-weight: 600;
+ color: var(--text-primary);
+}
</style>
diff --git a/src/components/ProcessParamListDialog.vue b/src/components/ProcessParamListDialog.vue
new file mode 100644
index 0000000..38e892d
--- /dev/null
+++ b/src/components/ProcessParamListDialog.vue
@@ -0,0 +1,642 @@
+<template>
+ <el-dialog v-model="visible"
+ :title="title"
+ width="800px"
+ destroy-on-close>
+ <div class="param-list-container">
+ <div class="params-header">
+ <span>鍙傛暟鍒楄〃</span>
+ <div>
+ <el-button v-if="editable"
+ type="primary"
+ link
+ size="small"
+ @click="handleAddParam">
+ <el-icon>
+ <Plus />
+ </el-icon>鏂板
+ </el-button>
+ <!-- <el-button v-if="editable"
+ type="primary"
+ link
+ size="small"
+ @click="getsyncProcessParamItem">
+ <el-icon>
+ <Refresh />
+ </el-icon>鍚屾宸ュ簭鍙傛暟
+ </el-button> -->
+ </div>
+ </div>
+ <div class="params-list">
+ <div v-for="param in paramList"
+ :key="param.id"
+ class="param-item">
+ <div class="param-info">
+ <span class="param-code">{{ param.paramName }}</span>
+ <span class="param-value">
+ 鏍囧噯鍊硷細{{ param.standardValue || "-" }} {{ param.unit }}
+ </span>
+ </div>
+ <div class="param-actions">
+ <el-button v-if="editable"
+ link
+ type="primary"
+ size="small"
+ @click="handleEditParam(param)">
+ 缂栬緫
+ </el-button>
+ <el-button v-if="editable"
+ link
+ type="danger"
+ size="small"
+ @click="handleDeleteParam(param)">
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <el-empty v-if="!paramList || paramList.length === 0"
+ description="鏆傛棤鍙傛暟"
+ :image-size="50" />
+ </div>
+ </div>
+ <!-- 閫夋嫨鍙傛暟瀵硅瘽妗� -->
+ <el-dialog v-model="selectParamDialogVisible"
+ title="閫夋嫨鍙傛暟"
+ width="1000px">
+ <div class="param-select-container">
+ <!-- 宸︿晶鍙傛暟鍒楄〃 -->
+ <div class="param-list-area">
+ <div class="area-title">鍙�夊弬鏁�</div>
+ <div class="search-box">
+ <el-input v-model="paramSearchKeyword"
+ placeholder="璇疯緭鍏ュ弬鏁板悕绉版悳绱�"
+ clearable
+ size="small"
+ @input="getBaseParamListData">
+ <template #prefix>
+ <el-icon>
+ <Search />
+ </el-icon>
+ </template>
+ </el-input>
+ </div>
+ <el-table :data="filteredParamList"
+ height="400"
+ border
+ highlight-current-row
+ @current-change="handleSelectParam">
+ <el-table-column prop="paramName"
+ label="鍙傛暟鍚嶇О" />
+ <el-table-column prop="paramType"
+ label="鍙傛暟绫诲瀷">
+ <template #default="scope">
+ <el-tag size="small"
+ :type="getParamTypeTag(scope.row.paramType)">{{ getParamTypeText(scope.row.paramType) }}</el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- 鍒嗛〉鎺т欢 -->
+ <div class="pagination-container"
+ style="margin-top: 10px;">
+ <el-pagination :current-page="paramPage.current"
+ :page-size="paramPage.size"
+ :page-sizes="[10, 20, 50, 100]"
+ layout="total, sizes, prev, pager, next, jumper"
+ :total="paramPage.total"
+ @size-change="getBaseParamListData"
+ @current-change="getBaseParamListData"
+ size="small" />
+ </div>
+ </div>
+ <!-- 鍙充晶鍙傛暟璇︽儏 -->
+ <div class="param-detail-area">
+ <div class="area-title">鍙傛暟璇︽儏</div>
+ <el-form v-if="selectedParam"
+ :model="selectedParam"
+ label-width="100px"
+ class="param-detail-form">
+ <el-form-item label="鍙傛暟鍚嶇О">
+ <span class="detail-text">{{ selectedParam.paramName }}</span>
+ </el-form-item>
+ <el-form-item label="鍙傛暟绫诲瀷">
+ <el-tag size="small"
+ :type="getParamTypeTag(selectedParam.paramType)">{{ getParamTypeText(selectedParam.paramType) }}</el-tag>
+ </el-form-item>
+ <el-form-item label="鍙傛暟鏍煎紡">
+ <span class="detail-text">{{ selectedParam.paramFormat || '-' }}</span>
+ </el-form-item>
+ <el-form-item label="鍗曚綅">
+ <span class="detail-text">{{ selectedParam.unit || '-' }}</span>
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍊�">
+ <el-input v-model="selectedParam.standardValue"
+ placeholder="璇疯緭鍏ラ粯璁ゅ��" />
+ </el-form-item>
+ <el-form-item label="鏄惁蹇呭~">
+ <el-switch :active-value="1"
+ :inactive-value="0"
+ v-model="selectedParam.isRequired" />
+ </el-form-item>
+ </el-form>
+ <el-empty v-else
+ description="璇蜂粠宸︿晶閫夋嫨鍙傛暟"
+ :image-size="100" />
+ </div>
+ </div>
+ <template #footer>
+ <el-button type="primary" @click="handleParamSelectSubmit">纭畾</el-button>
+ <el-button @click="selectParamDialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
+ <!-- 缂栬緫鍙傛暟瀵硅瘽妗� -->
+ <el-dialog v-model="editParamDialogVisible"
+ title="缂栬緫鍙傛暟"
+ width="600px">
+ <el-form :model="editParamForm"
+ :rules="editParamRules"
+ ref="editParamFormRef"
+ label-width="120px">
+ <el-form-item label="鍙傛暟鍚嶇О">
+ <span class="detail-text">{{ editParamForm.paramName }}</span>
+ </el-form-item>
+ <el-form-item label="鍙傛暟绫诲瀷">
+ <el-tag size="small"
+ :type="getParamTypeTag(editParamForm.paramType)">
+ {{ getParamTypeText(editParamForm.paramType) }}
+ </el-tag>
+ </el-form-item>
+ <el-form-item label="鍙傛暟鏍煎紡">
+ <span class="detail-text">{{ editParamForm.paramFormat || '-' }}</span>
+ </el-form-item>
+ <el-form-item label="鍗曚綅">
+ <span class="detail-text">{{ editParamForm.unit || '-' }}</span>
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍊�"
+ prop="standardValue">
+ <el-input v-model="editParamForm.standardValue"
+ placeholder="璇疯緭鍏ユ爣鍑嗗��" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="handleEditParamSubmit">纭畾</el-button>
+ <el-button @click="editParamDialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
+ </el-dialog>
+</template>
+
+<script setup>
+ import { ref, computed, watch } from "vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import { Plus, Search } from "@element-plus/icons-vue";
+ import {
+ delProcessRouteItemParam,
+ editProcessRouteItemParam,
+ addProcessRouteItemParam,
+ } from "@/api/productionManagement/processRouteItem.js";
+ import {
+ addProcessRouteItemParamOrder,
+ delProcessRouteItemParamOrder,
+ editProcessRouteItemParamOrder,
+ } from "@/api/productionManagement/productProcessRoute.js";
+
+ import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js";
+
+ const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
+ title: {
+ type: String,
+ default: "鍙傛暟鍒楄〃",
+ },
+ routeId: {
+ type: Number,
+ default: 0,
+ },
+ process: {
+ type: Object,
+ default: () => ({}),
+ },
+ paramList: {
+ type: Array,
+ default: () => [],
+ },
+ editable: {
+ type: Boolean,
+ default: true,
+ },
+ orderId: {
+ type: Number,
+ default: 0,
+ },
+ pageType: {
+ type: String,
+ default: "route",
+ },
+ });
+
+ const emit = defineEmits(["update:modelValue", "refresh"]);
+
+ const visible = computed({
+ get: () => props.modelValue,
+ set: value => emit("update:modelValue", value),
+ });
+
+ // 鍝嶅簲寮忔暟鎹�
+ const selectParamDialogVisible = ref(false);
+ const editParamDialogVisible = ref(false);
+ const paramSearchKeyword = ref("");
+ const selectedParam = ref(null);
+ const filteredParamList = ref([]);
+ const paramPage = ref({
+ current: 1,
+ size: 10,
+ total: 0,
+ });
+ const editParamForm = ref({
+ id: null,
+ processId: null,
+ paramId: null,
+ paramName: "",
+ standardValue: null,
+ isRequired: 0,
+ paramType: null,
+ paramFormat: "",
+ unit: "",
+ });
+ const editParamRules = ref({
+ // standardValue: [{ required: true, message: "璇疯緭鍏ユ爣鍑嗗��", trigger: "blur" }],
+ });
+ const editParamFormRef = ref(null);
+
+ // 鏂板鍙傛暟
+ const handleAddParam = () => {
+ selectedParam.value = null;
+ paramSearchKeyword.value = "";
+ paramPage.value.current = 1;
+ // 鑾峰彇鍙�夊弬鏁板垪琛�
+ getBaseParamListData();
+ selectParamDialogVisible.value = true;
+ };
+
+ // 缂栬緫鍙傛暟
+ const handleEditParam = param => {
+ editParamForm.value = {
+ id: param.id,
+ processId: props.process.id,
+ paramId: param.paramId,
+ paramName: param.parameterName || param.paramName,
+ standardValue: param.standardValue,
+ isRequired: param.isRequired || 0,
+ paramType: param.parameterType || param.paramType,
+ paramFormat: param.parameterFormat || param.paramFormat,
+ unit: param.unit || param.unit,
+ };
+ editParamDialogVisible.value = true;
+ };
+
+ // 鍒犻櫎鍙傛暟
+ const handleDeleteParam = param => {
+ ElMessageBox.confirm("纭畾瑕佸垹闄よ鍙傛暟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ // 璋冪敤API鍒犻櫎鍙傛暟
+ if (props.pageType === "order") {
+ delProcessRouteItemParamOrder(param.id)
+ .then(res => {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ emit("refresh");
+ })
+ .catch(err => {
+ ElMessage.error("鍒犻櫎鍙傛暟澶辫触");
+ console.error("鍒犻櫎鍙傛暟澶辫触锛�", err);
+ });
+ } else {
+ delProcessRouteItemParam(param.id)
+ .then(res => {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ emit("refresh");
+ })
+ .catch(err => {
+ ElMessage.error("鍒犻櫎鍙傛暟澶辫触");
+ console.error("鍒犻櫎鍙傛暟澶辫触锛�", err);
+ });
+ }
+ })
+ .catch(() => {});
+ };
+ const getsyncProcessParamItem = () => {
+ emit("getsyncProcessParamItem");
+ };
+
+ // 鑾峰彇鍙�夊弬鏁板垪琛�
+ const getBaseParamListData = () => {
+ console.log(paramPage, "paramPage.size");
+
+ getBaseParamList({
+ paramName: paramSearchKeyword.value,
+ current: paramPage.value.current,
+ size: paramPage.value.size,
+ }).then(res => {
+ if (res.code === 200) {
+ filteredParamList.value = res.data?.records || [];
+ paramPage.value.total = res.data.total || 0;
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ });
+ };
+
+ // 閫夋嫨鍙傛暟
+ const handleSelectParam = param => {
+ selectedParam.value = param;
+ };
+
+ // 鎻愪氦閫夋嫨鍙傛暟
+ const handleParamSelectSubmit = () => {
+ if (!selectedParam.value) {
+ ElMessage.warning("璇峰厛閫夋嫨涓�涓弬鏁�");
+ return;
+ }
+
+ if (!props.process || !props.process.id) {
+ ElMessage.error("宸ヨ壓璺嚎椤圭洰淇℃伅涓嶅畬鏁�");
+ return;
+ }
+
+ // 璋冪敤API鏂板鍙傛暟
+ if (props.pageType === "order") {
+ addProcessRouteItemParamOrder({
+ productionOrderId: Number(props.orderId),
+ productionOrderRoutingOperationId: props.process.id,
+ technologyRoutingOperationParamId: props.process.id,
+ paramId: selectedParam.value.id,
+ standardValue: selectedParam.value.standardValue || "",
+ isRequired: selectedParam.value.isRequired || 0,
+ })
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success("娣诲姞鍙傛暟鎴愬姛");
+ selectParamDialogVisible.value = false;
+ emit("refresh");
+ } else {
+ ElMessage.error(res.msg || "娣诲姞鍙傛暟澶辫触");
+ }
+ })
+ .catch(err => {
+ ElMessage.error("娣诲姞鍙傛暟澶辫触");
+ console.error("娣诲姞鍙傛暟澶辫触锛�", err);
+ });
+ } else {
+ console.log(selectedParam.value, "selectedParam");
+
+ addProcessRouteItemParam({
+ technologyRoutingOperationId: props.process.id,
+ paramId: selectedParam.value.id,
+ standardValue: selectedParam.value.standardValue || "",
+ isRequired: selectedParam.value.isRequired || 0,
+ })
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success("娣诲姞鍙傛暟鎴愬姛");
+ selectParamDialogVisible.value = false;
+ emit("refresh");
+ } else {
+ ElMessage.error(res.msg || "娣诲姞鍙傛暟澶辫触");
+ }
+ })
+ .catch(err => {
+ ElMessage.error("娣诲姞鍙傛暟澶辫触");
+ console.error("娣诲姞鍙傛暟澶辫触锛�", err);
+ });
+ }
+ };
+
+ // 鎻愪氦缂栬緫鍙傛暟
+ const handleEditParamSubmit = () => {
+ if (!editParamFormRef.value) return;
+ editParamFormRef.value.validate(valid => {
+ if (valid) {
+ if (props.pageType === "order") {
+ editProcessRouteItemParamOrder({
+ id: editParamForm.value.id,
+ standardValue: editParamForm.value.standardValue || "",
+ isRequired: editParamForm.value.isRequired || 0,
+ // productionOrderRoutingOperationId: props.process.id,
+ })
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success("缂栬緫鎴愬姛");
+ editParamDialogVisible.value = false;
+ emit("refresh");
+ } else {
+ ElMessage.error(res.msg || "缂栬緫澶辫触");
+ }
+ })
+ .catch(err => {
+ ElMessage.error("缂栬緫鍙傛暟澶辫触");
+ console.error("缂栬緫鍙傛暟澶辫触锛�", err);
+ });
+ } else {
+ // 璋冪敤API淇敼鍙傛暟
+ editProcessRouteItemParam({
+ id: editParamForm.value.id,
+ technologyRoutingOperationId: props.process.id,
+ paramId: editParamForm.value.paramId,
+ standardValue: editParamForm.value.standardValue || "",
+ isRequired: editParamForm.value.isRequired || 0,
+ })
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success("缂栬緫鎴愬姛");
+ editParamDialogVisible.value = false;
+ emit("refresh");
+ } else {
+ ElMessage.error(res.msg || "缂栬緫澶辫触");
+ }
+ })
+ .catch(err => {
+ ElMessage.error("缂栬緫鍙傛暟澶辫触");
+ console.error("缂栬緫鍙傛暟澶辫触锛�", err);
+ });
+ }
+ }
+ });
+ };
+
+ // 鑾峰彇鍙傛暟绫诲瀷鏍囩
+ const getParamTypeTag = type => {
+ const typeMap = {
+ 1: "primary",
+ 2: "info",
+ 3: "warning",
+ 4: "success",
+ };
+ return typeMap[type] || "default";
+ };
+
+ // 鑾峰彇鍙傛暟绫诲瀷鏂囨湰
+ const getParamTypeText = type => {
+ const typeMap = {
+ 1: "鏁板�兼牸寮�",
+ 2: "鏂囨湰鏍煎紡",
+ 3: "涓嬫媺閫夐」",
+ 4: "鏃堕棿鏍煎紡",
+ };
+ return typeMap[type] || type;
+ };
+
+ watch(
+ () => props.modelValue,
+ newVal => {
+ if (!newVal) {
+ // 寮圭獥鍏抽棴鏃堕噸缃暟鎹�
+ selectParamDialogVisible.value = false;
+ editParamDialogVisible.value = false;
+ selectedParam.value = null;
+ paramSearchKeyword.value = "";
+ paramPage.value.current = 1;
+ filteredParamList.value = [];
+ editParamForm.value = {
+ id: null,
+ processId: null,
+ paramId: null,
+ paramName: "",
+ standardValue: null,
+ isRequired: 0,
+ paramType: null,
+ paramFormat: "",
+ unit: "",
+ };
+ }
+ }
+ );
+</script>
+
+<style scoped>
+ .param-list-container {
+ padding: 10px 0;
+ }
+
+ .params-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #e4e7ed;
+ }
+
+ .params-header span {
+ font-size: 16px;
+ font-weight: 500;
+ color: #303133;
+ }
+
+ .params-list {
+ max-height: 400px;
+ overflow-y: auto;
+ }
+
+ .param-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ margin-bottom: 8px;
+ background-color: #f9f9f9;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+ }
+
+ .param-item:hover {
+ background-color: #ecf5ff;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ }
+
+ .param-info {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ flex: 1;
+ }
+
+ .param-code {
+ font-weight: 500;
+ color: #303133;
+ min-width: 120px;
+ }
+
+ .param-value {
+ color: #606266;
+ font-size: 14px;
+ }
+
+ .param-actions {
+ display: flex;
+ gap: 10px;
+ }
+
+ /* 婊氬姩鏉℃牱寮� */
+ .params-list::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ .params-list::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 3px;
+ }
+
+ .params-list::-webkit-scrollbar-thumb {
+ background: #c1c1c1;
+ border-radius: 3px;
+ }
+
+ .params-list::-webkit-scrollbar-thumb:hover {
+ background: #a8a8a8;
+ }
+
+ /* 閫夋嫨鍙傛暟瀵硅瘽妗嗘牱寮� */
+ .param-select-container {
+ display: flex;
+ gap: 20px;
+ }
+
+ .param-list-area {
+ flex: 1;
+ min-width: 400px;
+ }
+
+ .param-detail-area {
+ flex: 1;
+ min-width: 300px;
+ }
+
+ .area-title {
+ font-size: 14px;
+ font-weight: 500;
+ margin-bottom: 10px;
+ color: #303133;
+ }
+
+ .search-box {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 10px;
+ }
+
+ .param-detail-form {
+ background: #f9f9f9;
+ padding: 15px;
+ border-radius: 4px;
+ }
+
+ .detail-text {
+ font-weight: 500;
+ }
+</style>
\ No newline at end of file
diff --git a/src/components/ProjectManagement/ProgressReportDialog.vue b/src/components/ProjectManagement/ProgressReportDialog.vue
index d9402c2..dc6afe7 100644
--- a/src/components/ProjectManagement/ProgressReportDialog.vue
+++ b/src/components/ProjectManagement/ProgressReportDialog.vue
@@ -116,25 +116,14 @@
</el-row>
<el-form-item label="闄勪欢" prop="attachmentIds">
- <el-upload
- v-model:file-list="fileList"
- :action="upload.url"
- :headers="upload.headers"
- multiple
- name="files"
- :on-success="handleUploadSuccess"
- :on-error="handleUploadError"
- :on-remove="handleRemove"
- >
- <el-button type="primary">涓婁紶鏂囦欢</el-button>
- </el-upload>
+ <FileUpload v-model:file-list="form.storageBlobDTOs" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button @click="visible = false">鍙栨秷</el-button>
<el-button type="primary" @click="submit">纭畾</el-button>
+ <el-button @click="visible = false">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
@@ -144,6 +133,7 @@
import { computed, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { getToken } from '@/utils/auth'
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
const props = defineProps({
modelValue: { type: Boolean, default: false },
@@ -161,11 +151,6 @@
})
const formRef = ref()
-const fileList = ref([])
-const upload = reactive({
- url: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload',
- headers: { Authorization: 'Bearer ' + getToken() }
-})
const form = ref({
planNodeId: undefined,
@@ -180,7 +165,7 @@
managerName: '',
departmentName: '',
remark: '',
- attachmentIds: []
+ storageBlobDTOs: []
})
const rules = {
@@ -217,9 +202,8 @@
managerName: info.managerName || '',
departmentName: info.departmentName || '',
remark: '',
- attachmentIds: []
+ storageBlobDTOs: []
}
- fileList.value = []
}
watch(
@@ -237,30 +221,6 @@
form.value.completionProgress = 100
form.value.totalProgress = 100
if (!form.value.actualEndTime) form.value.actualEndTime = form.value.reportDate || ''
-}
-
-function handleUploadError() {
- ElMessage.error('涓婁紶鏂囦欢澶辫触')
-}
-
-function handleUploadSuccess(res, file) {
- if (res?.code !== 200) {
- ElMessage.error(res?.msg || '涓婁紶澶辫触')
- return
- }
- const attachmentId = res?.data?.id ?? res?.data?.tempId ?? ''
- if (!attachmentId) return
- form.value.attachmentIds.push(attachmentId)
- try {
- file.attachmentId = attachmentId
- } catch (e) {}
- ElMessage.success('涓婁紶鎴愬姛')
-}
-
-function handleRemove(file) {
- const attachmentId = file?.attachmentId
- if (!attachmentId) return
- form.value.attachmentIds = (form.value.attachmentIds || []).filter(id => id !== attachmentId)
}
async function submit() {
diff --git a/src/components/PurchaseAIChatSidebar/index.vue b/src/components/PurchaseAIChatSidebar/index.vue
new file mode 100644
index 0000000..ecc46be
--- /dev/null
+++ b/src/components/PurchaseAIChatSidebar/index.vue
@@ -0,0 +1,26 @@
+<template>
+ <AIChatSidebar :assistants="assistants" default-assistant="purchase" />
+</template>
+
+<script setup>
+import { ShoppingCart } from '@element-plus/icons-vue'
+import AIChatSidebar from '@/components/AIChatSidebar/index.vue'
+
+const assistants = [
+ {
+ key: 'purchase',
+ label: '閲囪喘鍔╃悊',
+ title: '閲囪喘鏅鸿兘鍔╃悊',
+ tooltip: '閲囪喘鏅鸿兘鍔╃悊',
+ icon: ShoppingCart,
+ apiBase: '/purchase-ai',
+ storageKey: 'purchase_ai_chat_uuid',
+ placeholder: '璇疯緭鍏ラ噰璐棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+ welcomeMessage: '浣犲ソ',
+ allowFileUpload: true,
+ allowMultipleFileUpload: true,
+ fileAnalyzeUrl: '/purchase-ai/analyze-files',
+ emptySessionText: '鏆傛棤閲囪喘浼氳瘽'
+ }
+]
+</script>
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
index 41f64c9..2b8368f 100644
--- a/src/components/SvgIcon/index.vue
+++ b/src/components/SvgIcon/index.vue
@@ -9,7 +9,7 @@
props: {
iconClass: {
type: String,
- required: true
+ default: ''
},
className: {
type: String,
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
index 1e7a78b..011e118 100644
--- a/src/layout/components/AppMain.vue
+++ b/src/layout/components/AppMain.vue
@@ -43,16 +43,17 @@
width: 100%;
position: relative;
overflow: hidden;
- background: #F5F7FB;
+ background: transparent;
}
.route-view-wrapper {
width: 100%;
height: 100%;
+ padding: 120px 16px 24px 0;
}
.fixed-header + .app-main {
- padding-top: 50px;
+ padding-top: 0;
}
.hasTagsView {
@@ -62,7 +63,7 @@
}
.fixed-header + .app-main {
- padding-top: 84px;
+ padding-top: 0;
}
}
</style>
@@ -81,11 +82,11 @@
}
::-webkit-scrollbar-track {
- background-color: #f1f1f1;
+ background-color: rgba(218, 225, 220, 0.8);
}
::-webkit-scrollbar-thumb {
- background-color: #c0c0c0;
+ background-color: #b2bdb5;
border-radius: 3px;
}
</style>
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
index d4e938d..cb08221 100644
--- a/src/layout/components/Navbar.vue
+++ b/src/layout/components/Navbar.vue
@@ -158,15 +158,19 @@
</script>
<style lang='scss' scoped>
-.navbar {
- height: 50px;
- overflow: hidden;
- position: relative;
- background: var(--navbar-bg);
- box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+.navbar {
+ height: 56px;
+ overflow: hidden;
+ position: relative;
+ background: var(--navbar-bg);
+ border: 1px solid rgba(216, 225, 219, 0.9);
+ border-radius: 22px;
+ backdrop-filter: blur(18px);
+ box-shadow: var(--shadow-sm);
+ padding: 0 18px;
.hamburger-container {
- line-height: 46px;
+ line-height: 52px;
height: 100%;
float: left;
cursor: pointer;
@@ -174,7 +178,7 @@
-webkit-tap-highlight-color: transparent;
&:hover {
- background: rgba(0, 0, 0, 0.025);
+ background: var(--navbar-hover);
}
}
@@ -192,30 +196,32 @@
vertical-align: top;
}
- .right-menu {
- float: right;
- height: 100%;
- line-height: 50px;
- display: flex;
+ .right-menu {
+ float: right;
+ height: 100%;
+ align-items: center;
+ display: flex;
&:focus {
outline: none;
}
- .right-menu-item {
- display: inline-block;
- padding: 0 8px;
- height: 100%;
- font-size: 18px;
- color: var(--navbar-text);
- vertical-align: text-bottom;
+ .right-menu-item {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 8px;
+ height: 100%;
+ font-size: 18px;
+ color: var(--navbar-text);
+ border-radius: 14px;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
- background: rgba(0, 0, 0, 0.025);
+ background: var(--navbar-hover);
}
}
@@ -234,7 +240,7 @@
}
.notification-container {
- margin-right: 20px;
+ margin-right: 12px;
display: flex;
align-items: center;
cursor: pointer;
@@ -246,28 +252,43 @@
}
}
- .avatar-container {
- margin-right: 40px;
-
- .avatar-wrapper {
- margin-top: 5px;
- position: relative;
-
- .user-avatar {
- cursor: pointer;
- width: 40px;
- height: 40px;
- border-radius: 50px;
- }
-
- i {
- cursor: pointer;
- position: absolute;
- right: -20px;
- top: 14px;
- font-size: 12px;
- }
- }
+ .avatar-container {
+ margin-right: 4px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+
+ :deep(.el-dropdown) {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ }
+
+ .avatar-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ padding: 6px 10px 6px 6px;
+ height: 44px;
+ border-radius: 999px;
+ background: rgba(247, 250, 248, 0.92);
+ border: 1px solid var(--surface-border);
+
+ .user-avatar {
+ cursor: pointer;
+ width: 34px;
+ height: 34px;
+ border-radius: 50px;
+ }
+
+ i {
+ cursor: pointer;
+ position: static;
+ font-size: 12px;
+ }
+ }
}
}
}
@@ -275,8 +296,11 @@
</style>
<style lang="scss">
-.notification-popover {
- padding: 0 !important;
+.notification-popover {
+ padding: 0 !important;
+ border-radius: 20px !important;
+ border: 1px solid var(--surface-border) !important;
+ box-shadow: var(--shadow-md) !important;
.el-popover__title {
display: none;
diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue
index f8f1da3..57c2b4b 100644
--- a/src/layout/components/Sidebar/Logo.vue
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -1,129 +1,142 @@
-<template>
- <div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
- <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 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 class="sidebar-title">{{ title }}</h1>
- </router-link>
- </transition>
- </div>
-</template>
-
-<script setup>
-import { ref, computed, onMounted, watch } from 'vue'
-import useUserStore from '@/store/modules/user'
-import defaultLogo from '@/assets/logo/logo.png' // 瀵煎叆榛樿logo
-
-defineProps({
- collapse: {
- type: Boolean,
- required: true
- }
-})
-
-const title = import.meta.env.VITE_APP_TITLE
-const userStore = useUserStore()
-
-// 澶勭悊宸ュ巶鍚嶇О锛岀敓鎴愬悎娉曠殑鏂囦欢鍚�
-const cleanFactoryName = computed(() => {
- if (!userStore.currentFactoryName) return ''
- return userStore.currentFactoryName.trim()
-})
-
-// 鍔ㄦ�乴ogo璺緞
-const logoUrl = ref('')
-
-// 妫�鏌ogo鏄惁瀛樺湪骞惰缃畊rl
-const updateLogoUrl = () => {
- if (!cleanFactoryName.value) {
- logoUrl.value = defaultLogo
- return
- }
-
- // 浣跨敤Vite鐨勫姩鎬佸鍏�
- try {
- const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true })
- const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png`
-
- if (dynamicLogo[logoPath]) {
- logoUrl.value = dynamicLogo[logoPath].default
- } else {
- logoUrl.value = defaultLogo
- }
- } catch (error) {
- console.error('鍔犺浇宸ュ巶Logo澶辫触:', error)
- logoUrl.value = defaultLogo
- }
-}
-
-// 鍒濆鍖栧拰鐩戝惉鍙樺寲
-onMounted(() => {
- updateLogoUrl()
-
- // 鐩戝惉宸ュ巶鍚嶇О鍙樺寲
- watch(() => userStore.currentFactoryName, updateLogoUrl)
-})
-
-// 鍥剧墖鍔犺浇閿欒澶勭悊
-const handleImageError = (event) => {
- console.warn('Logo鍔犺浇澶辫触锛屼娇鐢ㄩ粯璁ogo')
- logoUrl.value = defaultLogo
-}
-</script>
-
-<style lang="scss" scoped>
-@import '@/assets/styles/variables.module.scss';
-
-.sidebarLogoFade-enter-active {
- transition: opacity 1.5s;
-}
-
-.sidebarLogoFade-enter,
-.sidebarLogoFade-leave-to {
- opacity: 0;
-}
-
-.sidebar-logo-container {
- position: relative;
- width: 100% !important;
- height: 50px !important;
- line-height: 50px;
- background: #fff;
- text-align: center;
- overflow: hidden;
-
- & .sidebar-logo-link {
- height: 100%;
- width: 100%;
-
- & .sidebar-logo {
- width: 100%;
- height: 100%;
- // height: 32px;
- vertical-align: middle;
- margin-right: 12px;
- }
-
- & .sidebar-title {
- display: inline-block;
- margin: 0;
- color: v-bind(getLogoTextColor);
- font-weight: 600;
- line-height: 50px;
- font-size: 14px;
- font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
- vertical-align: middle;
- }
- }
-
- &.collapse {
- .sidebar-logo {
- margin-right: 0px;
- }
- }
-}
-</style>
+<template>
+ <div class="sidebar-logo-container" :class="{ collapse }">
+ <transition name="sidebarLogoFade">
+ <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
+ <img :src="faviconUrl" class="sidebar-logo sidebar-favicon" alt="绔欑偣鍥炬爣" />
+ </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-if="!logoUrl" class="sidebar-title">{{ title }}</h1>
+ </router-link>
+ </transition>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, watch } from 'vue'
+import useUserStore from '@/store/modules/user'
+import defaultLogo from '@/assets/logo/logo.png'
+
+defineProps({
+ collapse: {
+ type: Boolean,
+ required: true
+ }
+})
+
+const title = import.meta.env.VITE_APP_TITLE
+const userStore = useUserStore()
+const baseUrl = import.meta.env.BASE_URL || '/'
+const faviconUrl = `${baseUrl.replace(/\/?$/, '/') }favicon.ico`.replace(/([^:]\/)\/+/g, '$1')
+
+const cleanFactoryName = computed(() => {
+ if (!userStore.currentFactoryName) return ''
+ return userStore.currentFactoryName.trim()
+})
+
+const logoUrl = ref('')
+
+const updateLogoUrl = () => {
+ if (!cleanFactoryName.value) {
+ logoUrl.value = defaultLogo
+ return
+ }
+
+ try {
+ const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true })
+ const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png`
+
+ if (dynamicLogo[logoPath]) {
+ logoUrl.value = dynamicLogo[logoPath].default
+ } else {
+ logoUrl.value = defaultLogo
+ }
+ } catch (error) {
+ console.error('鍔犺浇宸ュ巶 Logo 澶辫触:', error)
+ logoUrl.value = defaultLogo
+ }
+}
+
+onMounted(() => {
+ updateLogoUrl()
+ watch(() => userStore.currentFactoryName, updateLogoUrl)
+})
+
+const handleImageError = () => {
+ logoUrl.value = defaultLogo
+}
+</script>
+
+<style lang="scss" scoped>
+@import '@/assets/styles/variables.module.scss';
+
+.sidebarLogoFade-enter-active {
+ transition: opacity 1.5s;
+}
+
+.sidebarLogoFade-enter,
+.sidebarLogoFade-leave-to {
+ opacity: 0;
+}
+
+.sidebar-logo-container {
+ position: relative;
+ width: 100% !important;
+ height: 56px !important;
+ line-height: 56px;
+ background: rgba(255, 255, 255, 0.78);
+ border: 1px solid var(--surface-border);
+ border-radius: 22px;
+ text-align: center;
+ overflow: hidden;
+ box-shadow: var(--shadow-sm);
+
+ .sidebar-logo-link {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 18px 0 14px;
+ }
+
+ .sidebar-logo {
+ width: auto;
+ max-width: 250px;
+ max-height: 50px;
+ height: auto;
+ vertical-align: middle;
+ object-fit: contain;
+ object-position: center;
+ }
+
+ .sidebar-title {
+ display: inline-block;
+ margin: 0;
+ color: var(--text-primary);
+ font-weight: 600;
+ line-height: 1.2;
+ font-size: 14px;
+ font-family: "Segoe UI", "PingFang SC", sans-serif;
+ vertical-align: middle;
+ }
+
+ &.collapse {
+ .sidebar-logo-link {
+ padding: 0;
+ }
+
+ .sidebar-logo {
+ max-width: 30px;
+ max-height: 30px;
+ }
+
+ .sidebar-favicon {
+ width: 24px;
+ height: 24px;
+ max-width: 24px;
+ max-height: 24px;
+ }
+ }
+}
+</style>
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
index 9044945..0692dda 100644
--- a/src/layout/components/Sidebar/index.vue
+++ b/src/layout/components/Sidebar/index.vue
@@ -1,94 +1,142 @@
-<template>
- <div :class="{ 'has-logo': showLogo }" class="sidebar-container">
- <logo v-if="showLogo" :collapse="isCollapse" />
- <el-scrollbar wrap-class="scrollbar-wrapper">
- <el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="getMenuBackground"
- :text-color="getMenuTextColor" :unique-opened="true" :active-text-color="theme" :collapse-transition="false"
- mode="vertical" :class="sideTheme">
- <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route"
- :base-path="route.path" />
- </el-menu>
- </el-scrollbar>
- </div>
-</template>
-
-<script setup>
-import Logo from './Logo'
-import SidebarItem from './SidebarItem'
-import variables from '@/assets/styles/variables.module.scss'
-import useAppStore from '@/store/modules/app'
-import useSettingsStore from '@/store/modules/settings'
-import usePermissionStore from '@/store/modules/permission'
-
-const route = useRoute()
-const appStore = useAppStore()
-const settingsStore = useSettingsStore()
-const permissionStore = usePermissionStore()
-
-const sidebarRouters = computed(() => permissionStore.sidebarRouters)
-const showLogo = computed(() => settingsStore.sidebarLogo)
-const sideTheme = computed(() => settingsStore.sideTheme)
-const theme = computed(() => settingsStore.theme)
-const isCollapse = computed(() => !appStore.sidebar.opened)
-
-// 鑾峰彇鑿滃崟鑳屾櫙鑹�
-const getMenuBackground = computed(() => {
- if (settingsStore.isDark) {
- return 'var(--sidebar-bg)'
- }
- // 娴呰壊涓婚鏃讹紝鐩存帴鐢ㄤ富棰樿壊
- return sideTheme.value === 'theme-dark' ? variables.menuBg : settingsStore.theme
-})
-
-// 鑾峰彇鑿滃崟鏂囧瓧棰滆壊
-const getMenuTextColor = computed(() => {
- if (settingsStore.isDark) {
- return 'var(--sidebar-text)'
- }
- return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText
-})
-
-const activeMenu = computed(() => {
- const { meta, path } = route
- if (meta.activeMenu) {
- return meta.activeMenu
- }
- return path
-})
-</script>
-
-<style lang="scss" scoped>
-.sidebar-container {
- background-color: v-bind(getMenuBackground);
-
- .scrollbar-wrapper {
- background-color: v-bind(getMenuBackground);
- }
-
- .el-menu {
- border: none;
- height: 100%;
- width: 100% !important;
-
- .el-menu-item,
- .el-sub-menu__title {
- &:hover {
- background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
- }
- }
-
- .el-menu-item {
- color: v-bind(getMenuTextColor);
-
- &.is-active {
- color: var(--menu-active-text, #409eff);
- background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
- }
- }
-
- .el-sub-menu__title {
- color: v-bind(getMenuTextColor);
- }
- }
-}
-</style>
+<template>
+ <div :class="{ 'has-logo': showLogo }"
+ class="sidebar-container">
+ <logo v-if="showLogo"
+ :collapse="isCollapse" />
+ <el-scrollbar wrap-class="scrollbar-wrapper">
+ <el-menu :default-active="activeMenu"
+ :collapse="isCollapse"
+ :background-color="getMenuBackground"
+ :text-color="getMenuTextColor"
+ :unique-opened="true"
+ :active-text-color="theme"
+ :collapse-transition="false"
+ mode="vertical"
+ :class="sideTheme">
+ <sidebar-item v-for="(route, index) in sidebarRouters"
+ :key="route.path + index"
+ :item="route"
+ :base-path="route.path" />
+ </el-menu>
+ </el-scrollbar>
+ </div>
+</template>
+
+<script setup>
+ import Logo from "./Logo";
+ import SidebarItem from "./SidebarItem";
+ import variables from "@/assets/styles/variables.module.scss";
+ import useAppStore from "@/store/modules/app";
+ import useSettingsStore from "@/store/modules/settings";
+ import usePermissionStore from "@/store/modules/permission";
+
+ const route = useRoute();
+ const appStore = useAppStore();
+ const settingsStore = useSettingsStore();
+ const permissionStore = usePermissionStore();
+
+ const sidebarRouters = computed(() => permissionStore.sidebarRouters);
+ const showLogo = computed(() => settingsStore.sidebarLogo);
+ const sideTheme = computed(() => settingsStore.sideTheme);
+ const theme = computed(() => settingsStore.theme);
+ const isCollapse = computed(() => !appStore.sidebar.opened);
+
+ const getMenuBackground = computed(() => "var(--sidebar-bg)");
+
+ const getMenuTextColor = computed(() => {
+ if (settingsStore.isDark) {
+ return "var(--sidebar-text)";
+ }
+ return sideTheme.value === "theme-dark"
+ ? variables.menuText
+ : variables.menuLightText;
+ });
+
+ const activeMenu = computed(() => {
+ const { meta, path } = route;
+ if (meta.activeMenu) {
+ return meta.activeMenu;
+ }
+ return path;
+ });
+</script>
+
+<style lang="scss" scoped>
+ .sidebar-container {
+ background-color: v-bind(getMenuBackground);
+ border-radius: 22px;
+ overflow: hidden;
+
+ .scrollbar-wrapper {
+ background-color: v-bind(getMenuBackground);
+ }
+
+ .el-menu {
+ border: none;
+ height: 100%;
+ width: 100% !important;
+ border-radius: 22px;
+
+ .el-menu-item,
+ .el-sub-menu__title {
+ margin-bottom: 6px;
+ border-radius: 14px;
+ color: v-bind(getMenuTextColor);
+
+ &:hover {
+ background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
+ border-radius: 14px;
+ }
+ }
+
+ .el-menu-item {
+ &.is-active {
+ color: v-bind(theme);
+ background-color: var(--menu-active-bg, rgba(0, 0, 0, 0.06)) !important;
+ font-weight: 600;
+ }
+ }
+
+ .el-sub-menu__title {
+ color: v-bind(getMenuTextColor);
+ }
+
+ :deep(.el-sub-menu.is-active > .el-sub-menu__title) {
+ color: v-bind(theme) !important;
+ font-weight: 600;
+ background-color: var(--menu-active-bg, rgba(0, 0, 0, 0.06)) !important;
+ border-radius: 14px;
+ margin: 0 10px 6px !important;
+ // width: calc(100% - 20px) !important;
+ padding-left: 10px !important;
+ padding-right: 10px !important;
+ box-sizing: border-box;
+ overflow: hidden;
+ background-clip: padding-box;
+ }
+
+ :deep(.el-menu-item.is-active) {
+ margin: 0 10px 6px !important;
+ width: calc(100% - 20px) !important;
+ padding-left: 10px !important;
+ padding-right: 10px !important;
+ box-sizing: border-box;
+ overflow: hidden;
+ background-clip: padding-box;
+ border-radius: 14px;
+ }
+
+ :deep(.el-sub-menu.is-active > .el-sub-menu__title .menu-title),
+ :deep(.el-sub-menu.is-active > .el-sub-menu__title .svg-icon),
+ :deep(.el-menu-item.is-active .menu-title),
+ :deep(.el-menu-item.is-active .svg-icon) {
+ color: v-bind(theme) !important;
+ }
+
+ :deep(.el-sub-menu__title:hover),
+ :deep(.el-menu-item:hover) {
+ border-radius: 14px;
+ }
+ }
+ }
+</style>
diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue
index 0189a17..9067108 100644
--- a/src/layout/components/TagsView/ScrollPane.vue
+++ b/src/layout/components/TagsView/ScrollPane.vue
@@ -101,7 +101,7 @@
bottom: 0px;
}
:deep(.el-scrollbar__wrap) {
- height: 39px;
+ height: 42px;
}
}
</style>
\ No newline at end of file
diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue
index d3aa736..8fe064a 100644
--- a/src/layout/components/TagsView/index.vue
+++ b/src/layout/components/TagsView/index.vue
@@ -13,9 +13,9 @@
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ tag.title }}
- <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
- <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
- </span>
+ <span v-if="!isAffix(tag)" class="tags-view-close" @click.prevent.stop="closeSelectedTag(tag)">
+ <close class="el-icon-close" />
+ </span>
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
@@ -258,44 +258,54 @@
</script>
<style lang="scss" scoped>
-.tags-view-container {
- height: 34px;
- width: 100%;
- background: transparent;
-
- .tags-view-wrapper {
- .tags-view-item {
- display: inline-block;
- position: relative;
- cursor: pointer;
- height: 32px;
- line-height: 32px;
- //border: 1px solid var(--tags-item-border, #d8dce5);
- color: #4E5463;
- background: #E5E7EA;
- padding: 0 16px;
- font-size: 12px;
- //margin-left: 5px;
- //margin-top: 4px;
-
- //&:first-of-type {
- // margin-left: 8px;
- //}
- //
- //&:last-of-type {
- // margin-right: 15px;
- //}
-
- &.active {
- background-color: #FFFFFF !important;
- color: #2C51D9;
- }
- }
- //.tags-view-item div {
- // transform: skew(12deg);
- // display: inline-block;
- //}
- }
+.tags-view-container {
+ height: 42px;
+ width: 100%;
+ margin-top: 10px;
+ padding: 4px 10px;
+ background: rgba(255, 255, 255, 0.9);
+ border: 1px solid rgba(216, 225, 219, 0.92);
+ border-radius: 20px;
+ backdrop-filter: blur(18px);
+ box-shadow: var(--shadow-sm);
+
+ .tags-view-wrapper {
+ display: flex;
+ align-items: center;
+ min-height: 42px;
+
+ .tags-view-item {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ cursor: pointer;
+ height: 34px;
+ line-height: 1;
+ color: var(--tags-item-text, #4E5463);
+ background: var(--tags-item-bg, #E5E7EA);
+ border: 1px solid var(--tags-item-border, #d8dce5);
+ border-radius: 999px;
+ padding: 0 16px;
+ font-size: 12px;
+ margin-right: 8px;
+ flex-shrink: 0;
+ gap: 6px;
+ transition: all 0.24s ease;
+
+ &:hover {
+ background: var(--tags-item-hover, #eee);
+ border-color: rgba(31, 122, 114, 0.18);
+ }
+
+ &.active {
+ background-color: #FFFFFF !important;
+ color: var(--el-color-primary);
+ box-shadow: 0 10px 24px rgba(31, 122, 114, 0.12);
+ border-color: rgba(31, 122, 114, 0.2) !important;
+ }
+ }
+ }
.contextmenu {
margin: 0;
@@ -304,12 +314,12 @@
position: absolute;
list-style-type: none;
padding: 5px 0;
- border-radius: 4px;
+ border-radius: 16px;
font-size: 12px;
font-weight: 400;
color: var(--tags-item-text, #333);
- box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
- border: 1px solid var(--el-border-color-light, #e4e7ed);
+ box-shadow: var(--shadow-md);
+ border: 1px solid var(--surface-border, #e4e7ed);
li {
margin: 0;
@@ -326,30 +336,56 @@
<style lang="scss">
//reset element css of el-icon-close
-.tags-view-wrapper {
- .tags-view-item {
- .el-icon-close {
- width: 16px;
- height: 16px;
- vertical-align: 2px;
- border-radius: 50%;
- text-align: center;
- transition: all .3s cubic-bezier(.645, .045, .355, 1);
- transform-origin: 100% 50%;
-
- &:before {
- transform: scale(.6);
- display: inline-block;
- vertical-align: -3px;
- }
-
- &:hover {
- background-color: var(--tags-close-hover, #b4bccc);
- color: #fff;
- width: 12px !important;
- height: 12px !important;
- }
- }
- }
-}
-</style>
\ No newline at end of file
+.tags-view-wrapper {
+ .el-scrollbar__view {
+ display: flex;
+ align-items: center;
+ }
+
+ .tags-view-item {
+ .tags-view-close {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ line-height: 1;
+ align-self: center;
+ transform: translateY(1px);
+ }
+
+ .el-icon-close {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ line-height: 1;
+ vertical-align: initial !important;
+ border-radius: 50%;
+ text-align: center;
+ transition: all .3s cubic-bezier(.645, .045, .355, 1);
+ transform-origin: 100% 50%;
+ align-self: center;
+
+ &:before {
+ transform: scale(.6);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ svg {
+ display: block;
+ width: 10px;
+ height: 10px;
+ }
+
+ &:hover {
+ background-color: var(--tags-close-hover, #b4bccc);
+ color: #fff;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/layout/index.vue b/src/layout/index.vue
index 67ac803..03c13ba 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -1,114 +1,136 @@
-<template>
- <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
- <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
- <sidebar v-if="!sidebar.hide" class="sidebar-container" />
- <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
- <div :class="{ 'fixed-header': fixedHeader }">
- <navbar @setLayout="setLayout" />
- <tags-view v-if="needTagsView" />
- </div>
- <app-main />
- <settings ref="settingRef" />
- </div>
- </div>
-</template>
-
-<script setup>
-import { useWindowSize } from '@vueuse/core'
-import Sidebar from './components/Sidebar/index.vue'
-import { AppMain, Navbar, Settings, TagsView } from './components'
-import defaultSettings from '@/settings'
-
-import useAppStore from '@/store/modules/app'
-import useSettingsStore from '@/store/modules/settings'
-
-const settingsStore = useSettingsStore()
-const theme = computed(() => settingsStore.theme)
-const sideTheme = computed(() => settingsStore.sideTheme)
-const sidebar = computed(() => useAppStore().sidebar)
-const device = computed(() => useAppStore().device)
-const needTagsView = computed(() => settingsStore.tagsView)
-const fixedHeader = computed(() => settingsStore.fixedHeader)
-
-const classObj = computed(() => ({
- hideSidebar: !sidebar.value.opened,
- openSidebar: sidebar.value.opened,
- withoutAnimation: sidebar.value.withoutAnimation,
- mobile: device.value === 'mobile'
-}))
-
-const { width, height } = useWindowSize()
-const WIDTH = 992 // refer to Bootstrap's responsive design
-
-watch(() => device.value, () => {
- if (device.value === 'mobile' && sidebar.value.opened) {
- useAppStore().closeSideBar({ withoutAnimation: false })
- }
-})
-
-watchEffect(() => {
- if (width.value - 1 < WIDTH) {
- useAppStore().toggleDevice('mobile')
- useAppStore().closeSideBar({ withoutAnimation: true })
- } else {
- useAppStore().toggleDevice('desktop')
- }
-})
-
-function handleClickOutside() {
- useAppStore().closeSideBar({ withoutAnimation: false })
-}
-
-const settingRef = ref(null)
-function setLayout() {
- settingRef.value.openSetting()
-}
-</script>
-
-<style lang="scss" scoped>
- @import "@/assets/styles/mixin.scss";
- @import "@/assets/styles/variables.module.scss";
-
-.app-wrapper {
- @include clearfix;
- position: relative;
- height: 100%;
- width: 100%;
-
- &.mobile.openSidebar {
- position: fixed;
- top: 0;
- }
-}
-
-.drawer-bg {
- background: #000;
- opacity: 0.3;
- width: 100%;
- top: 0;
- height: 100%;
- position: absolute;
- z-index: 999;
-}
-
-.fixed-header {
- position: fixed;
- top: 0;
- right: 0;
- z-index: 9;
- width: calc(100% - #{$base-sidebar-width});
- transition: width 0.28s;
-}
-
-.hideSidebar .fixed-header {
- width: calc(100% - 54px);
-}
-
-.sidebarHide .fixed-header {
- width: 100%;
-}
-
-.mobile .fixed-header {
- width: 100%;
-}
-</style>
\ No newline at end of file
+<template>
+ <div :class="classObj"
+ class="app-wrapper"
+ :style="{ '--current-color': theme }">
+ <div v-if="device === 'mobile' && sidebar.opened"
+ class="drawer-bg"
+ @click="handleClickOutside" />
+ <sidebar v-if="!sidebar.hide"
+ class="sidebar-container" />
+ <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }"
+ class="main-container">
+ <div :class="{ 'fixed-header': fixedHeader }">
+ <navbar @setLayout="setLayout" />
+ <tags-view v-if="needTagsView" />
+ </div>
+ <app-main />
+ <settings ref="settingRef" />
+ </div>
+ <AIChatSidebar v-if="aiEnabled" />
+ </div>
+</template>
+
+<script setup>
+ import { useWindowSize } from "@vueuse/core";
+ import Sidebar from "./components/Sidebar/index.vue";
+ import { AppMain, Navbar, Settings, TagsView } from "./components";
+ import AIChatSidebar from "@/components/AIChatSidebar/index.vue";
+ import defaultSettings from "@/settings";
+
+ import useAppStore from "@/store/modules/app";
+ import useUserStore from "@/store/modules/user";
+ import useSettingsStore from "@/store/modules/settings";
+
+ const settingsStore = useSettingsStore();
+ const userStore = useUserStore();
+ const theme = computed(() => settingsStore.theme);
+ const sideTheme = computed(() => settingsStore.sideTheme);
+ const sidebar = computed(() => useAppStore().sidebar);
+ const device = computed(() => useAppStore().device);
+ const needTagsView = computed(() => settingsStore.tagsView);
+ const fixedHeader = computed(() => settingsStore.fixedHeader);
+ const aiEnabled = computed(() => Number(userStore.aiEnabled) === 1);
+
+ const classObj = computed(() => ({
+ hideSidebar: !sidebar.value.opened,
+ openSidebar: sidebar.value.opened,
+ withoutAnimation: sidebar.value.withoutAnimation,
+ mobile: device.value === "mobile",
+ }));
+
+ const { width, height } = useWindowSize();
+ const WIDTH = 992; // refer to Bootstrap's responsive design
+
+ watch(
+ () => device.value,
+ () => {
+ if (device.value === "mobile" && sidebar.value.opened) {
+ useAppStore().closeSideBar({ withoutAnimation: false });
+ }
+ }
+ );
+
+ watchEffect(() => {
+ if (width.value - 1 < WIDTH) {
+ useAppStore().toggleDevice("mobile");
+ useAppStore().closeSideBar({ withoutAnimation: true });
+ } else {
+ useAppStore().toggleDevice("desktop");
+ }
+ });
+
+ function handleClickOutside() {
+ useAppStore().closeSideBar({ withoutAnimation: false });
+ }
+
+ const settingRef = ref(null);
+ function setLayout() {
+ settingRef.value.openSetting();
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import "@/assets/styles/mixin.scss";
+ @import "@/assets/styles/variables.module.scss";
+
+ .app-wrapper {
+ @include clearfix;
+ position: relative;
+ height: 100%;
+ width: 100%;
+ background: radial-gradient(
+ circle at top,
+ rgba(223, 232, 226, 0.95),
+ transparent 32%
+ ),
+ linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
+
+ &.mobile.openSidebar {
+ position: fixed;
+ top: 0;
+ }
+ }
+
+ .drawer-bg {
+ background: #000;
+ opacity: 0.3;
+ width: 100%;
+ top: 0;
+ height: 100%;
+ position: absolute;
+ z-index: 999;
+ }
+
+ .fixed-header {
+ position: fixed;
+ top: 0px;
+ padding-top: 12px;
+ right: 16px;
+ z-index: 9;
+ width: calc(100% - #{$base-sidebar-width} - 32px);
+ transition: width 0.28s, right 0.28s;
+ padding-bottom: 8px;
+ background-color: #f3f6f4;
+ }
+ .hideSidebar .fixed-header {
+ width: calc(100% - 100px);
+ }
+
+ .sidebarHide .fixed-header {
+ width: calc(100% - 32px);
+ }
+
+ .mobile .fixed-header {
+ width: 100%;
+ }
+</style>
diff --git a/src/main.js b/src/main.js
index 0b3f714..025ff14 100644
--- a/src/main.js
+++ b/src/main.js
@@ -43,11 +43,13 @@
// 瀵屾枃鏈粍浠�
import Editor from "@/components/Editor";
// 鏂囦欢涓婁紶缁勪欢
-import FileUpload from "@/components/FileUpload";
+import FileUpload from "@/components/AttachmentUpload/file";
// 鍥剧墖涓婁紶缁勪欢
-import ImageUpload from "@/components/ImageUpload";
+import ImageUpload from "@/components/AttachmentUpload/image";
// 鍥剧墖棰勮缁勪欢
-import ImagePreview from "@/components/ImagePreview";
+import ImagePreview from "@/components/AttachmentPreview/image";
+// 闄勪欢寮圭獥缁勪欢
+import FileListDialog from "@/components/Dialog/FileList.vue";
// 瀛楀吀鏍囩缁勪欢
import DictTag from "@/components/DictTag";
// 琛ㄦ牸缁勪欢
@@ -92,6 +94,7 @@
app.component("FileUpload", FileUpload);
app.component("ImageUpload", ImageUpload);
app.component("ImagePreview", ImagePreview);
+app.component("FileListDialog", FileListDialog);
app.component("RightToolbar", RightToolbar);
app.component("Editor", Editor);
app.component("PIMTable", PIMTable);
diff --git a/src/router/index.js b/src/router/index.js
index f342004..2bb5586 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -47,6 +47,20 @@
component: () => import("@/views/register"),
hidden: true,
},
+ // 绯荤粺鏋舵瀯鍥�
+ // {
+ // path: "/system-architecture",
+ // component: Layout,
+ // redirect: "/system-architecture/index",
+ // children: [
+ // {
+ // path: "index",
+ // component: () => import("@/views/systemArchitecture/index.vue"),
+ // name: "SystemArchitecture",
+ // meta: { title: "绯荤粺鏋舵瀯鍥�", icon: "tree" },
+ // },
+ // ],
+ // },
{
path: "/:pathMatch(.*)*",
component: () => import("@/views/error/404"),
@@ -119,6 +133,119 @@
},
],
},
+ // 璐㈠姟绠$悊妯″潡璺敱
+ // {
+ // path: "/financial",
+ // component: Layout,
+ // hidden: false,
+ // redirect: "/financial/general-ledger",
+ // alwaysShow: true,
+ // meta: { title: "璐㈠姟绠$悊", icon: "money" },
+ // children: [
+ // {
+ // path: "general-ledger",
+ // component: () => import("@/views/financialManagement/generalLedger/index.vue"),
+ // name: "GeneralLedger",
+ // meta: { title: "鎬诲笎绉戠洰" },
+ // },
+ // {
+ // path: "sales-out",
+ // component: () => import("@/views/financialManagement/receivable/salesOut.vue"),
+ // name: "SalesOut",
+ // meta: { title: "閿�鍞嚭搴�" },
+ // },
+ // {
+ // path: "sales-return",
+ // component: () => import("@/views/financialManagement/receivable/salesReturn.vue"),
+ // name: "SalesReturn",
+ // meta: { title: "閿�鍞��璐�" },
+ // },
+ // {
+ // path: "receivable-reconciliation",
+ // component: () => import("@/views/financialManagement/receivable/reconciliation.vue"),
+ // name: "ReceivableReconciliation",
+ // meta: { title: "搴旀敹瀵硅处" },
+ // },
+ // {
+ // path: "invoice-apply",
+ // component: () => import("@/views/financialManagement/receivable/invoiceApply.vue"),
+ // name: "InvoiceApply",
+ // meta: { title: "寮�绁ㄧ敵璇�" },
+ // },
+ // {
+ // path: "output-invoice",
+ // component: () => import("@/views/financialManagement/receivable/outputInvoice.vue"),
+ // name: "OutputInvoice",
+ // meta: { title: "閿�椤瑰彂绁�" },
+ // },
+ // {
+ // path: "receipt",
+ // component: () => import("@/views/financialManagement/receivable/receipt.vue"),
+ // name: "Receipt",
+ // meta: { title: "鏀舵鍗�" },
+ // },
+ // {
+ // path: "purchase-in",
+ // component: () => import("@/views/financialManagement/payable/purchaseIn.vue"),
+ // name: "PurchaseIn",
+ // meta: { title: "閲囪喘鍏ュ簱" },
+ // },
+ // {
+ // path: "payable-reconciliation",
+ // component: () => import("@/views/financialManagement/payable/reconciliation.vue"),
+ // name: "PayableReconciliation",
+ // meta: { title: "搴斾粯瀵硅处" },
+ // },
+ // {
+ // path: "input-invoice",
+ // component: () => import("@/views/financialManagement/payable/input-invoice.vue"),
+ // name: "InputInvoice",
+ // meta: { title: "杩涢」鍙戠エ" },
+ // },
+ // {
+ // path: "payment-apply",
+ // component: () => import("@/views/financialManagement/payable/paymentApply.vue"),
+ // name: "PaymentApply",
+ // meta: { title: "浠樻鐢宠" },
+ // },
+ // {
+ // path: "payment",
+ // component: () => import("@/views/financialManagement/payable/payment.vue"),
+ // name: "Payment",
+ // meta: { title: "浠樻鍗�" },
+ // },
+ // {
+ // path: "fixed-assets",
+ // component: () => import("@/views/financialManagement/assets/fixedAssets.vue"),
+ // name: "FixedAssets",
+ // meta: { title: "鍥哄畾璧勪骇" },
+ // },
+ // {
+ // path: "intangible-assets",
+ // component: () => import("@/views/financialManagement/assets/intangibleAssets.vue"),
+ // name: "IntangibleAssets",
+ // meta: { title: "鏃犲舰璧勪骇" },
+ // },
+ // {
+ // path: "voucher",
+ // component: () => import("@/views/financialManagement/voucher/index.vue"),
+ // name: "Voucher",
+ // meta: { title: "鍑瘉" },
+ // },
+ // {
+ // path: "voucher-general-ledger",
+ // component: () => import("@/views/financialManagement/voucher/generalLedger.vue"),
+ // name: "VoucherGeneralLedger",
+ // meta: { title: "绉戠洰鎬诲笎" },
+ // },
+ // {
+ // path: "voucher-detail-ledger",
+ // component: () => import("@/views/financialManagement/voucher/detailLedger.vue"),
+ // name: "VoucherDetailLedger",
+ // meta: { title: "绉戠洰鏄庣粏甯�" },
+ // },
+ // ],
+ // },
];
// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇
diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js
index 6a16d9d..d3f0594 100644
--- a/src/store/modules/permission.js
+++ b/src/store/modules/permission.js
@@ -1,9 +1,10 @@
import auth from '@/plugins/auth'
import router, { constantRoutes, dynamicRoutes } from '@/router'
import { getRouters } from '@/api/menu'
-import Layout from '@/layout/index'
-import ParentView from '@/components/ParentView'
-import InnerLink from '@/layout/components/InnerLink'
+import Layout from '@/layout/index'
+import ParentView from '@/components/ParentView'
+import InnerLink from '@/layout/components/InnerLink'
+import useUserStore from '@/store/modules/user'
// 鍖归厤views閲岄潰鎵�鏈夌殑.vue鏂囦欢
const modules = import.meta.glob('./../../views/**/*.vue')
@@ -36,15 +37,18 @@
return new Promise(resolve => {
// 鍚戝悗绔姹傝矾鐢辨暟鎹�
getRouters().then(res => {
- const sdata = JSON.parse(JSON.stringify(res.data))
- const rdata = JSON.parse(JSON.stringify(res.data))
- const defaultData = JSON.parse(JSON.stringify(res.data))
+ const aiEnabled = Number(useUserStore().aiEnabled) === 1
+ const rawRoutes = filterAiFeatureRoutes(res.data, aiEnabled)
+ const sdata = JSON.parse(JSON.stringify(rawRoutes))
+ const rdata = JSON.parse(JSON.stringify(rawRoutes))
+ const defaultData = JSON.parse(JSON.stringify(rawRoutes))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const defaultRoutes = filterAsyncRouter(defaultData)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
asyncRoutes.forEach(route => { router.addRoute(route) })
this.setRoutes(rewriteRoutes)
+ // 灏嗚储鍔$鐞嗚矾鐢卞悎骞跺埌渚ц竟鏍�
this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
this.setDefaultRoutes(sidebarRoutes)
this.setTopbarRoutes(defaultRoutes)
@@ -56,7 +60,38 @@
})
// 閬嶅巻鍚庡彴浼犳潵鐨勮矾鐢卞瓧绗︿覆锛岃浆鎹负缁勪欢瀵硅薄
-function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
+function filterAiFeatureRoutes(routes = [], aiEnabled = false) {
+ if (aiEnabled) {
+ return routes
+ }
+ return routes.reduce((acc, route) => {
+ if (!route || isAiFeatureRoute(route)) {
+ return acc
+ }
+ const nextRoute = { ...route }
+ if (Array.isArray(nextRoute.children) && nextRoute.children.length > 0) {
+ nextRoute.children = filterAiFeatureRoutes(nextRoute.children, aiEnabled)
+ }
+ acc.push(nextRoute)
+ return acc
+ }, [])
+}
+
+function isAiFeatureRoute(route = {}) {
+ const path = String(route.path || '').toLowerCase()
+ const component = String(route.component || '').toLowerCase()
+ const name = String(route.name || '').toLowerCase()
+ const title = String(route?.meta?.title ?? route?.title ?? '')
+
+ return (
+ path.includes('chathome') ||
+ component.includes('chathome') ||
+ name.includes('chathome') ||
+ title.includes('AI')
+ )
+}
+
+function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {
if (type && route.children) {
route.children = filterChildren(route.children)
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index 4f3eab4..ea358d1 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -7,13 +7,14 @@
const useUserStore = defineStore(
'user',
{
- state: () => ({
- token: getToken(),
- id: '',
- name: '',
- avatar: '',
- roles: [],
- permissions: []
+ state: () => ({
+ token: getToken(),
+ id: '',
+ name: '',
+ avatar: '',
+ roles: [],
+ permissions: [],
+ aiEnabled: 0
}),
actions: {
// 鐧诲綍
@@ -58,29 +59,31 @@
this.id = user.userId
this.name = user.userName
this.avatar = avatar
- this.currentFactoryName = user.currentFactoryName
- this.nickName = user.nickName
- this.roleName = user.roles[0].roleName
- this.currentDeptId = user.tenantId
- this.currentLoginTime = this.getCurrentTime()
- resolve(res)
- }).catch(error => {
- reject(error)
- })
- })
+ this.currentFactoryName = user.currentFactoryName
+ this.nickName = user.nickName
+ this.roleName = user.roles[0].roleName
+ this.currentDeptId = user.tenantId
+ this.currentLoginTime = this.getCurrentTime()
+ this.aiEnabled = Number(res.aiEnabled) === 1 ? 1 : 0
+ resolve(res)
+ }).catch(error => {
+ reject(error)
+ })
+ })
},
// 閫�鍑虹郴缁�
logOut() {
return new Promise((resolve, reject) => {
logout(this.token).then(() => {
- this.token = ''
- this.roles = []
- this.permissions = []
- removeToken()
- resolve()
- }).catch(error => {
- reject(error)
- })
+ this.token = ''
+ this.roles = []
+ this.permissions = []
+ this.aiEnabled = 0
+ removeToken()
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
})
},
// 鐧诲綍鏍¢獙
diff --git a/src/utils/generator/html.js b/src/utils/generator/html.js
index 4b29841..eacb48e 100644
--- a/src/utils/generator/html.js
+++ b/src/utils/generator/html.js
@@ -8,8 +8,8 @@
return `<el-dialog v-model="dialogVisible" @open="onOpen" @close="onClose" title="Dialog Titile">
${str}
<template #footer>
- <el-button @click="close">鍙栨秷</el-button>
- <el-button type="primary" @click="handelConfirm">纭畾</el-button>
+ <el-button type="primary" @click="handelConfirm">纭畾</el-button>
+ <el-button @click="close">鍙栨秷</el-button>
</template>
</el-dialog>`
}
diff --git a/src/views/basicData/customerFile/index.vue b/src/views/basicData/customerFile/index.vue
index ad1c5bb..a080bd9 100644
--- a/src/views/basicData/customerFile/index.vue
+++ b/src/views/basicData/customerFile/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form" style="margin-bottom: 20px;">
<div>
<span class="search_title">瀹㈡埛鍚嶇О锛�</span>
<el-input v-model="searchForm.customerName"
@@ -211,7 +211,8 @@
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
- :action="upload.url + '?updateSupport=' + upload.updateSupport"
+ :action="upload.url"
+ :data="upload.data"
:disabled="upload.isUploading"
:before-upload="upload.beforeUpload"
:on-progress="upload.onProgress"
@@ -276,9 +277,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
+ <el-button type="primary" @click="submitReminderForm">纭</el-button>
<el-button @click="closeReminderDialog">鍙栨秷</el-button>
- <el-button type="primary"
- @click="submitReminderForm">鎻愪氦</el-button>
</div>
</template>
</el-dialog>
@@ -360,9 +360,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
+ <el-button type="primary" @click="submitNegotiationForm">纭</el-button>
<el-button @click="closeNegotiationDialog">鍙栨秷</el-button>
- <el-button type="primary"
- @click="submitNegotiationForm">鎻愪氦</el-button>
</div>
</template>
</el-dialog>
@@ -494,7 +493,6 @@
<template #default="{ row }">
<el-button type="info"
link
- size="small"
@click="openAttachmentDialog(row)">
<el-icon>
<Paperclip />
@@ -510,13 +508,11 @@
<template #default="{ row, $index }">
<el-button type="primary"
link
- size="small"
@click="editNegotiationRecord(row, $index)">
淇敼
</el-button>
<el-button type="danger"
link
- size="small"
@click="deleteNegotiationRecord(row, $index)">
鍒犻櫎
</el-button>
@@ -587,13 +583,11 @@
<template #default="{ row, $index }">
<el-button type="primary"
link
- size="small"
@click="downloadAttachment(row)">
涓嬭浇
</el-button>
<el-button type="danger"
link
- size="small"
@click="deleteAttachment(row, $index)">
鍒犻櫎
</el-button>
@@ -619,17 +613,13 @@
import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue";
import { Search, Paperclip, Upload } from "@element-plus/icons-vue";
import {
- addCustomer,
- delCustomer,
- getCustomer,
- listCustomer,
- updateCustomer,
addCustomerFollow,
updateCustomerFollow,
delCustomerFollow,
addReturnVisit,
getReturnVisit,
} from "@/api/basicData/customerFile.js";
+ import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer} from "@/api/basicData/customer.js";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import useUserStore from "@/store/modules/user";
@@ -661,7 +651,7 @@
const negotiationFormRef = ref();
const negotiationForm = reactive({
customerName: "",
- customerId: "",
+ customerId: "",
followUpMethod: "",
followUpLevel: "",
followUpTime: "",
@@ -733,7 +723,7 @@
},
{
label: "鍦板潃鍙婅仈绯绘柟寮�",
- prop: "addressPhone",
+ prop: "companyAddress",
width: 250,
},
{
@@ -775,6 +765,24 @@
prop: "maintainer",
},
{
+ label: "瀹㈡埛鏉ユ簮",
+ prop: "type",
+ dataType: "tag",
+ width: 100,
+ formatData: value => {
+ if (value === 1 || value === "1") {
+ return "鍏捣";
+ }
+ return "绉佹捣";
+ },
+ formatType: value => {
+ if (value === 1 || value === "1") {
+ return "warning";
+ }
+ return "success";
+ },
+ },
+ {
label: "缁存姢鏃堕棿",
prop: "maintenanceTime",
width: 100,
@@ -784,7 +792,7 @@
label: "鎿嶄綔",
align: "center",
fixed: "right",
- width: 250,
+ width: 290,
operation: [
{
name: "缂栬緫",
@@ -793,13 +801,13 @@
openForm("edit", row);
},
},
- {
- name: "璇︽儏",
- type: "text",
- clickFun: row => {
- openDetailDialog(row);
- },
- },
+ {
+ name: "娣诲姞娲借皥杩涘害",
+ type: "text",
+ clickFun: row => {
+ openNegotiationDialog(row);
+ },
+ },
{
name: "鍥炶鎻愰啋",
type: "text",
@@ -807,13 +815,13 @@
openReminderDialog(row);
},
},
- {
- name: "娣诲姞娲借皥杩涘害",
- type: "text",
- clickFun: row => {
- openNegotiationDialog(row);
- },
- },
+ {
+ name: "璇︽儏",
+ type: "text",
+ clickFun: row => {
+ openDetailDialog(row);
+ },
+ },
],
},
]);
@@ -844,6 +852,7 @@
searchForm: {
customerName: "",
customerType: "",
+ type: 0
},
form: {
customerName: "",
@@ -858,6 +867,7 @@
bankAccount: "",
bankCode: "",
customerType: "",
+ type: 0
},
rules: {
customerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
@@ -889,6 +899,9 @@
headers: { Authorization: "Bearer " + getToken() },
// 涓婁紶鐨勫湴鍧�
url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData",
+ data: {
+ type: 0
+ },
// 鏂囦欢涓婁紶鍓嶇殑鍥炶皟
beforeUpload: file => {
console.log("鏂囦欢鍗冲皢涓婁紶", file);
@@ -961,8 +974,8 @@
tableLoading.value = true;
listCustomer({ ...searchForm.value, ...page }).then(res => {
tableLoading.value = false;
- tableData.value = res.records;
- page.total = res.total;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
});
};
// 琛ㄦ牸閫夋嫨鏁版嵁
@@ -994,6 +1007,7 @@
contactPhone: "",
},
];
+ form.value.type = 0;
form.value.maintenanceTime = getCurrentDate();
userListNoPage().then(res => {
userList.value = res.data;
@@ -1069,7 +1083,7 @@
type: "warning",
})
.then(() => {
- proxy.download("/basic/customer/export", {}, "瀹㈡埛妗f.xlsx");
+ proxy.download("/basic/customer/export", {type: 0}, "瀹㈡埛妗f.xlsx");
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
@@ -1079,12 +1093,11 @@
const handleDelete = () => {
let ids = [];
if (selectedRows.value.length > 0) {
- // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
const unauthorizedData = selectedRows.value.filter(
- item => item.maintainer !== userStore.nickName
+ item => item.type === 1 || item.type === "1"
);
if (unauthorizedData.length > 0) {
- proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
+ proxy.$modal.msgWarning("鍏捣鍒嗛厤鐨勫鎴蜂笉鑳藉垹闄�");
return;
}
ids = selectedRows.value.map(item => item.id);
@@ -1100,7 +1113,7 @@
.then(() => {
tableLoading.value = true;
delCustomer(ids)
- .then(res => {
+ .then(() => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
getList();
})
@@ -1153,7 +1166,7 @@
if (reminderForm.id) {
submitvalue.value = {
id: reminderForm.id,
- customerId: currentCustomerId.value,
+ customerId: currentCustomerId.value,
isEnabled: reminderForm.reminderSwitch ? 1 : 0,
content: reminderForm.reminderContent,
reminderTime: reminderForm.reminderTime,
@@ -1168,8 +1181,6 @@
remindUserId: userStore.id,
};
}
-
- console.log("鎻愪氦鍥炶鎻愰啋鏁版嵁:", submitvalue.value);
// 璋冪敤鎺ュ彛
addReturnVisit(submitvalue.value)
@@ -1198,14 +1209,6 @@
negotiationForm.followUpTime = "";
negotiationForm.followerUserName = userStore.nickName; // 榛樿褰撳墠鐧诲綍浜�
negotiationForm.content = "";
- // {
- // "customerId": 152,
- // "followUpMethod": "鐢佃瘽娌熼��",
- // "followUpLevel": "娌℃湁鎰忓悜",
- // "followUpTime": "2026-03-04T15:30:00",
- // "followerUserName": "绠$悊鍛樿处鍙�",
- // "content": "111"
- // }
negotiationDialogVisible.value = true;
};
@@ -1227,23 +1230,6 @@
if (isEdit) {
// 淇敼鎿嶄綔
- console.log("淇敼娲借皥杩涘害鏁版嵁:", negotiationForm);
- // 杩欓噷鍙互璋冪敤鏇存柊鎺ュ彛
- // 瀹為檯椤圭洰涓渶瑕佹牴鎹悗绔帴鍙h繘琛岃皟鏁�
- // 绀轰緥锛歶pdateCustomerFollow(negotiationForm).then(res => {
- // // 鏇存柊鏈湴鏁版嵁
- // const index = negotiationForm.editIndex;
- // negotiationRecords.value[index] = {
- // followUpTime: negotiationForm.followUpTime,
- // followUpMethod: negotiationForm.followUpMethod,
- // followUpLevel: negotiationForm.followUpLevel,
- // followerUserName: negotiationForm.followerUserName,
- // content: negotiationForm.content,
- // id: negotiationForm.id,
- // };
- // proxy.$modal.msgSuccess("淇敼鎴愬姛");
- // closeNegotiationDialog();
- // });
updateCustomerFollow(negotiationForm).then(res => {
// 鏇存柊鏈湴鏁版嵁
getCustomer(negotiationForm.customerId).then(res => {
@@ -1278,7 +1264,6 @@
// 鎵撳紑璇︽儏寮圭獥
const openDetailDialog = row => {
- // 璋冪敤getCustomer鎺ュ彛鑾峰彇瀹㈡埛璇︽儏
getCustomer(row.id).then(res => {
// 濉厖瀹㈡埛鍩烘湰淇℃伅
Object.assign(detailForm, res.data);
@@ -1300,7 +1285,7 @@
// 灏嗗綋鍓嶈褰曟暟鎹~鍏呭埌琛ㄥ崟
Object.assign(negotiationForm, {
customerName: row.customerName,
- customerId: row.customerId,
+ customerId: row.customerId,
followUpMethod: row.followUpMethod,
followUpLevel: row.followUpLevel,
followUpTime: row.followUpTime,
diff --git a/src/views/basicData/customerFileOpenSea/index.vue b/src/views/basicData/customerFileOpenSea/index.vue
new file mode 100644
index 0000000..2598f48
--- /dev/null
+++ b/src/views/basicData/customerFileOpenSea/index.vue
@@ -0,0 +1,1803 @@
+<template>
+ <div class="app-container">
+ <div class="search_form" style="margin-bottom: 20px;">
+ <div>
+ <span class="search_title">瀹㈡埛鍚嶇О锛�</span>
+ <el-input v-model="searchForm.customerName"
+ 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>
+ </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="customerName">
+ <el-input v-model="form.customerName"
+ placeholder="璇疯緭鍏�"
+ clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绾崇◣浜鸿瘑鍒彿锛�"
+ prop="taxpayerIdentificationNumber">
+ <el-input v-model="form.taxpayerIdentificationNumber"
+ 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="basicBankAccount">
+ <el-input v-model="form.basicBankAccount"
+ placeholder="璇疯緭鍏�"
+ clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閾惰璐﹀彿锛�"
+ prop="bankAccount">
+ <el-input v-model="form.bankAccount"
+ placeholder="璇疯緭鍏�"
+ clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯鍙凤細"
+ prop="bankCode">
+ <el-input v-model="form.bankCode"
+ placeholder="璇疯緭鍏�"
+ clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :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">
+ <el-form-item label="鑱旂郴浜猴細"
+ prop="contactPerson">
+ <el-input v-model="contact.contactPerson"
+ placeholder="璇疯緭鍏�"
+ clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑱旂郴鐢佃瘽锛�"
+ prop="contactPhone">
+ <div style="display: flex; align-items: center;width: 100%;">
+ <el-input v-model="contact.contactPhone"
+ placeholder="璇疯緭鍏�"
+ clearable />
+ <el-button @click="removeContact(index)"
+ type="danger"
+ circle
+ style="margin-left: 5px;">
+ <el-icon>
+ <Close />
+ </el-icon>
+ </el-button>
+ </div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-button @click="addNewContact"
+ style="margin-bottom: 10px;">+ 鏂板鑱旂郴浜�</el-button>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="缁存姢浜猴細"
+ prop="maintainer">
+ <el-select v-model="form.maintainer"
+ placeholder="璇烽�夋嫨"
+ clearable
+ disabled>
+ <el-option v-for="item in userList"
+ :key="item.nickName"
+ :label="item.nickName"
+ :value="item.nickName" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁存姢鏃堕棿锛�"
+ prop="maintenanceTime">
+ <el-date-picker style="width: 100%"
+ v-model="form.maintenanceTime"
+ 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-dialog v-model="assignDialogVisible"
+ title="鍒嗛厤瀹㈡埛"
+ width="500px"
+ @close="closeAssignDialog">
+ <el-form :model="assignForm"
+ :rules="assignRules"
+ ref="assignFormRef"
+ label-width="100px">
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <el-input v-model="assignForm.customerName"
+ disabled />
+ </el-form-item>
+ <el-form-item label="鍒嗛厤浜哄憳"
+ prop="boundId">
+ <el-select v-model="assignForm.boundId"
+ placeholder="璇烽�夋嫨鍒嗛厤浜哄憳"
+ style="width: 100%"
+ filterable>
+ <el-option v-for="item in userList"
+ :key="item.userId || item.nickName"
+ :label="item.nickName"
+ :value="item.userId" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="submitAssignForm">纭</el-button>
+ <el-button @click="closeAssignDialog">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="shareDialogVisible"
+ title="鍏变韩瀹㈡埛"
+ width="500px"
+ @close="closeShareDialog">
+ <el-form :model="shareForm"
+ :rules="shareRules"
+ ref="shareFormRef"
+ label-width="100px">
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <el-input v-model="shareForm.customerName"
+ disabled />
+ </el-form-item>
+ <el-form-item label="鍏变韩浜哄憳"
+ prop="boundIds">
+ <el-select v-model="shareForm.boundIds"
+ placeholder="璇烽�夋嫨鍏变韩浜哄憳"
+ style="width: 100%"
+ filterable
+ multiple
+ collapse-tags
+ collapse-tags-tooltip>
+ <el-option v-for="item in userList"
+ :key="item.userId || item.nickName"
+ :label="item.nickName"
+ :value="item.userId" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="submitShareForm">纭</el-button>
+ <el-button @click="closeShareDialog">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 鐢ㄦ埛瀵煎叆瀵硅瘽妗� -->
+ <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"
+ :data="upload.data"
+ :disabled="upload.isUploading"
+ :before-upload="upload.beforeUpload"
+ :on-progress="upload.onProgress"
+ :on-success="upload.onSuccess"
+ :on-error="upload.onError"
+ :on-change="upload.onChange"
+ :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-dialog title="鍥炶鎻愰啋"
+ v-model="reminderDialogVisible"
+ width="500px"
+ @close="closeReminderDialog">
+ <el-form :model="reminderForm"
+ label-width="100px"
+ :rules="reminderRules"
+ ref="reminderFormRef">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�">
+ <el-input v-model="reminderForm.customerName"
+ disabled />
+ </el-form-item>
+ <el-form-item label="鎻愰啋寮�鍏筹細">
+ <el-switch v-model="reminderForm.reminderSwitch" />
+ </el-form-item>
+ <el-form-item label="鎻愰啋鍐呭锛�"
+ prop="reminderContent">
+ <el-input v-model="reminderForm.reminderContent"
+ type="textarea"
+ :maxlength="100"
+ show-word-limit
+ placeholder="璇疯緭鍏ユ彁閱掑唴瀹�" />
+ </el-form-item>
+ <el-form-item label="鎻愰啋鏃堕棿锛�"
+ prop="reminderTime">
+ <el-date-picker v-model="reminderForm.reminderTime"
+ type="datetime"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ format="YYYY-MM-DD HH:mm:ss"
+ placeholder="璇烽�夋嫨鎻愰啋鏃堕棿"
+ style="width: 100%" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitReminderForm">纭</el-button>
+ <el-button @click="closeReminderDialog">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 娣诲姞/淇敼娲借皥杩涘害瀵硅瘽妗� -->
+ <el-dialog :title="negotiationForm.editIndex !== undefined ? '淇敼杩涘害' : '娣诲姞杩涘害'"
+ v-model="negotiationDialogVisible"
+ width="600px"
+ @close="closeNegotiationDialog">
+ <el-form :model="negotiationForm"
+ label-width="100px"
+ :rules="negotiationRules"
+ ref="negotiationFormRef">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璺熻繘鏂瑰紡锛�"
+ prop="followUpMethod">
+ <el-select v-model="negotiationForm.followUpMethod"
+ placeholder="璇烽�夋嫨"
+ style="width: 100%">
+ <el-option label="鐢佃瘽"
+ value="鐢佃瘽" />
+ <el-option label="閭欢"
+ value="閭欢" />
+ <el-option label="涓婇棬"
+ value="涓婇棬" />
+ <el-option label="寰俊"
+ value="寰俊" />
+ <el-option label="鍏朵粬"
+ value="鍏朵粬" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璺熻繘绋嬪害锛�"
+ prop="followUpLevel">
+ <el-select v-model="negotiationForm.followUpLevel"
+ placeholder="璇烽�夋嫨"
+ style="width: 100%">
+ <el-option label="娼滃湪瀹㈡埛"
+ value="娼滃湪瀹㈡埛" />
+ <el-option label="鍒濇鎷滆"
+ value="鍒濇鎷滆" />
+ <el-option label="澶氭鎷滆"
+ value="澶氭鎷滆" />
+ <el-option label="鎰忓悜瀹㈡埛"
+ value="鎰忓悜瀹㈡埛" />
+ <el-option label="宸茬绾﹀鎴�"
+ value="宸茬绾﹀鎴�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璺熻繘鏃堕棿锛�"
+ prop="followUpTime">
+ <el-date-picker v-model="negotiationForm.followUpTime"
+ type="datetime"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ format="YYYY-MM-DD HH:mm:ss"
+ placeholder="璇烽�夋嫨"
+ style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璺熻繘浜猴細">
+ <el-input v-model="negotiationForm.followerUserName"
+ disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍐呭锛�"
+ prop="content">
+ <el-input v-model="negotiationForm.content"
+ type="textarea"
+ :rows="4"
+ placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitNegotiationForm">纭</el-button>
+ <el-button @click="closeNegotiationDialog">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 瀹㈡埛璇︽儏瀵硅瘽妗� -->
+ <el-dialog title="瀹㈡埛璇︽儏"
+ v-model="detailDialogVisible"
+ width="1000px"
+ @close="closeDetailDialog">
+ <!-- 瀹㈡埛鍩烘湰淇℃伅 -->
+ <div class="detail-section">
+ <h3 class="section-title">瀹㈡埛鍩烘湰淇℃伅</h3>
+ <div class="info-display">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">瀹㈡埛鍚嶇О锛�</span>
+ <span class="info-value">{{ detailForm.customerName }}</span>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">瀹㈡埛鍒嗙被锛�</span>
+ <span class="info-value">{{ detailForm.customerType }}</span>
+ </div>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">绾崇◣浜鸿瘑鍒彿锛�</span>
+ <span class="info-value">{{ detailForm.taxpayerIdentificationNumber }}</span>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">鍏徃鐢佃瘽锛�</span>
+ <span class="info-value">{{ detailForm.companyPhone }}</span>
+ </div>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">鍏徃鍦板潃锛�</span>
+ <span class="info-value">{{ detailForm.companyAddress }}</span>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">閾惰鍩烘湰鎴凤細</span>
+ <span class="info-value">{{ detailForm.basicBankAccount }}</span>
+ </div>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">閾惰璐﹀彿锛�</span>
+ <span class="info-value">{{ detailForm.bankAccount }}</span>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">寮�鎴疯鍙凤細</span>
+ <span class="info-value">{{ detailForm.bankCode }}</span>
+ </div>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">鑱旂郴浜猴細</span>
+ <span class="info-value">{{ detailForm.contactPerson }}</span>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">鑱旂郴鐢佃瘽锛�</span>
+ <span class="info-value">{{ detailForm.contactPhone }}</span>
+ </div>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">缁存姢浜猴細</span>
+ <span class="info-value">{{ detailForm.maintainer }}</span>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="info-item">
+ <span class="info-label">缁存姢鏃堕棿锛�</span>
+ <span class="info-value">{{ detailForm.maintenanceTime }}</span>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ </div>
+ <!-- 娲借皥杩涘害璁板綍 -->
+ <div class="detail-section">
+ <div class="section-header">
+ <h3 class="section-title">娲借皥杩涘害璁板綍</h3>
+ <el-button type="primary"
+ size="small"
+ @click="openNegotiationDialog(detailForm)">
+ 娣诲姞杩涘害
+ </el-button>
+ </div>
+ <el-table :data="negotiationRecords"
+ border
+ style="width: 100%">
+ <el-table-column prop="followUpTime"
+ label="璺熻繘鏃堕棿"
+ width="160" />
+ <el-table-column prop="followUpMethod"
+ label="璺熻繘鏂瑰紡"
+ width="100" />
+ <el-table-column prop="followUpLevel"
+ label="璺熻繘绋嬪害" />
+ <el-table-column prop="followerUserName"
+ label="璺熻繘浜�"
+ width="100" />
+ <el-table-column prop="content"
+ label="鍐呭"
+ show-overflow-tooltip />
+ <el-table-column label="闄勪欢"
+ width="100"
+ align="center">
+ <template #default="{ row }">
+ <el-button type="info"
+ link
+ @click="openAttachmentDialog(row)">
+ <el-icon>
+ <Paperclip />
+ </el-icon>
+ 闄勪欢
+ <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : '涓婁紶' }} -->
+ </el-button>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ width="150"
+ align="center">
+ <template #default="{ row, $index }">
+ <el-button type="primary"
+ link
+ @click="editNegotiationRecord(row, $index)">
+ 淇敼
+ </el-button>
+ <el-button type="danger"
+ link
+ @click="deleteNegotiationRecord(row, $index)">
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div v-if="negotiationRecords.length === 0"
+ class="no-records">
+ 鏆傛棤娲借皥杩涘害璁板綍
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="closeDetailDialog">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 闄勪欢涓婁紶寮圭獥 -->
+ <el-dialog title="闄勪欢绠$悊"
+ v-model="attachmentDialogVisible"
+ width="600px"
+ @close="closeAttachmentDialog">
+ <div class="attachment-section">
+ <div class="upload-area">
+ <el-upload ref="attachmentUploadRef"
+ :action="getAttachmentUploadUrl()"
+ :headers="attachmentUploadHeaders"
+ :file-list="currentAttachmentList"
+ :on-success="handleAttachmentSuccess"
+ :on-error="handleAttachmentError"
+ :on-remove="handleAttachmentRemove"
+ :before-upload="beforeAttachmentUpload"
+ multiple
+ :limit="10"
+ name="files">
+ <el-button type="primary">
+ <el-icon>
+ <Upload />
+ </el-icon>
+ 涓婁紶闄勪欢
+ </el-button>
+ <template #tip>
+ <div class="el-upload__tip">
+ 鏀寔涓婁紶鍥剧墖銆佹枃妗g瓑鏂囦欢锛屽崟涓枃浠朵笉瓒呰繃50MB
+ </div>
+ </template>
+ </el-upload>
+ </div>
+ <div v-if="currentAttachmentList.length > 0"
+ class="attachment-list">
+ <h4>宸蹭笂浼犻檮浠讹細</h4>
+ <el-table :data="currentAttachmentList"
+ border
+ size="small">
+ <el-table-column prop="name"
+ label="鏂囦欢鍚�"
+ show-overflow-tooltip />
+ <el-table-column prop="size"
+ label="澶у皬"
+ width="100">
+ <template #default="{ row }">
+ {{ formatFileSize(row.size) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ width="120"
+ align="center">
+ <template #default="{ row, $index }">
+ <el-button type="primary"
+ link
+ @click="downloadAttachment(row)">
+ 涓嬭浇
+ </el-button>
+ <el-button type="danger"
+ link
+ @click="deleteAttachment(row, $index)">
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <div v-else
+ class="no-attachment">
+ 鏆傛棤闄勪欢
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="closeAttachmentDialog">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue";
+ import { Search, Paperclip, Upload } from "@element-plus/icons-vue";
+ import {
+ addCustomerFollow,
+ updateCustomerFollow,
+ delCustomerFollow,
+ addReturnVisit,
+ getReturnVisit,
+ } from "@/api/basicData/customerFile.js";
+ import {
+ listCustomer,
+ addCustomer,
+ delCustomer,
+ updateCustomer,
+ getCustomer,
+ assignCustomer,
+ recycleCustomer,
+ shareCustomer,
+ } from "@/api/basicData/customer.js";
+
+ import { ElMessageBox } from "element-plus";
+ import { userListNoPage } from "@/api/system/user.js";
+ import useUserStore from "@/store/modules/user";
+ import { getToken } from "@/utils/auth.js";
+ const { proxy } = getCurrentInstance();
+ const userStore = useUserStore();
+ const assignDialogVisible = ref(false);
+ const assignFormRef = ref();
+ const assignForm = reactive({
+ id: undefined,
+ customerName: "",
+ boundId: undefined,
+ });
+ const assignRules = {
+ boundId: [{ required: true, message: "璇烽�夋嫨鍒嗛厤浜哄憳", trigger: "change" }],
+ };
+ const shareDialogVisible = ref(false);
+ const shareFormRef = ref();
+ const shareForm = reactive({
+ id: undefined,
+ customerName: "",
+ boundIds: [],
+ });
+ const shareRules = {
+ boundIds: [{ required: true, message: "璇烽�夋嫨鍏变韩浜哄憳", trigger: "change" }],
+ };
+
+ // 鍥炶鎻愰啋鐩稿叧
+ const reminderDialogVisible = ref(false);
+ const reminderFormRef = ref();
+ const currentCustomerId = ref();
+ const reminderForm = reactive({
+ customerName: "",
+ reminderSwitch: false,
+ reminderContent: "",
+ reminderTime: "",
+ });
+ const reminderRules = {
+ reminderContent: [
+ { required: true, message: "璇疯緭鍏ユ彁閱掑唴瀹�", trigger: "blur" },
+ ],
+ reminderTime: [
+ { required: true, message: "璇烽�夋嫨鎻愰啋鏃堕棿", trigger: "change" },
+ ],
+ };
+
+ // 娲借皥杩涘害鐩稿叧
+ const negotiationDialogVisible = ref(false);
+ const negotiationFormRef = ref();
+ const negotiationForm = reactive({
+ customerName: "",
+ customerId: "",
+ followUpMethod: "",
+ followUpLevel: "",
+ followUpTime: "",
+ followerUserName: "",
+ content: "",
+ });
+ const negotiationRules = {
+ followUpMethod: [
+ { required: true, message: "璇烽�夋嫨璺熻繘鏂瑰紡", trigger: "change" },
+ ],
+ followUpLevel: [
+ { required: true, message: "璇烽�夋嫨璺熻繘绋嬪害", trigger: "change" },
+ ],
+ followUpTime: [
+ { required: true, message: "璇烽�夋嫨璺熻繘鏃堕棿", trigger: "change" },
+ ],
+ content: [{ required: true, message: "璇疯緭鍏ュ唴瀹�", trigger: "blur" }],
+ };
+
+ // 璇︽儏鐩稿叧
+ const detailDialogVisible = ref(false);
+ const detailForm = reactive({
+ customerName: "",
+ customerType: "",
+ taxpayerIdentificationNumber: "",
+ companyPhone: "",
+ companyAddress: "",
+ basicBankAccount: "",
+ bankAccount: "",
+ bankCode: "",
+ contactPerson: "",
+ contactPhone: "",
+ maintainer: "",
+ maintenanceTime: "",
+ });
+ const negotiationRecords = ref([]);
+
+ // 闄勪欢鐩稿叧
+ const attachmentDialogVisible = ref(false);
+ const attachmentUploadRef = ref();
+ const currentAttachmentList = ref([]);
+ const currentFollowRecord = ref({});
+ const attachmentUploadHeaders = { Authorization: "Bearer " + getToken() };
+
+ // 鍔ㄦ�佹瀯寤轰笂浼燯RL
+ const getAttachmentUploadUrl = () => {
+ const baseUrl =
+ import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/upload";
+ return currentFollowRecord.value.id
+ ? `${baseUrl}/${currentFollowRecord.value.id}`
+ : baseUrl;
+ };
+
+ const tableColumn = ref([
+ {
+ label: "瀹㈡埛鍒嗙被",
+ prop: "customerType",
+ width: 120,
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: 220,
+ },
+ {
+ label: "绾崇◣浜鸿瘑鍒爜",
+ prop: "taxpayerIdentificationNumber",
+ width: 220,
+ },
+ {
+ label: "鍦板潃鍙婅仈绯绘柟寮�",
+ prop: "addressPhone",
+ width: 250,
+ },
+ {
+ label: "鑱旂郴浜�",
+ prop: "contactPerson",
+ },
+ {
+ label: "鑱旂郴鐢佃瘽",
+ prop: "contactPhone",
+ width: 150,
+ },
+ // {
+ // label: "璺熻繘杩涘害",
+ // prop: "followUpLevel",
+ // width: 120,
+ // },
+ // {
+ // label: "璺熻繘鏃堕棿",
+ // prop: "followUpTime",
+ // width: 120,
+ // },
+ {
+ label: "閾惰鍩烘湰鎴�",
+ prop: "basicBankAccount",
+ width: 220,
+ },
+ {
+ label: "閾惰璐﹀彿",
+ prop: "bankAccount",
+ width: 220,
+ },
+ {
+ label: "寮�鎴疯鍙�",
+ prop: "bankCode",
+ width: 220,
+ },
+ {
+ label: "缁存姢浜�",
+ prop: "maintainer",
+ },
+ {
+ label: "缁存姢鏃堕棿",
+ prop: "maintenanceTime",
+ width: 100,
+ },
+ {
+ label: "棰嗙敤浜�",
+ prop: "usageUserName",
+ width: 120,
+ fixed: "right",
+ },
+ {
+ label: "棰嗙敤鐘舵��",
+ prop: "usageStatus",
+ dataType: "tag",
+ width: 100,
+ fixed: "right",
+ formatData: value => {
+ if (value === 1 || value === "1") {
+ return "宸查鐢�";
+ }
+ return "鏈鐢�";
+ },
+ formatType: value => {
+ if (value === 1 || value === "1") {
+ return "success";
+ }
+ return "info";
+ },
+ },
+ {
+ label: "鍏变韩浜�",
+ prop: "togetherUserNames",
+ width: 120,
+ fixed: "right",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 200,
+ operation: [
+ {
+ name: "鍒嗛厤",
+ type: "text",
+ showHide: row => row.usageStatus != 1,
+ clickFun: row => {
+ openAssignDialog(row);
+ },
+ },
+ {
+ name: "鍥炴敹",
+ type: "text",
+ showHide: row => row.usageStatus == 1,
+ clickFun: row => {
+ recycle(row);
+ },
+ },
+ {
+ name: "鍏变韩",
+ type: "text",
+ showHide: row => row.usageStatus == 1,
+ clickFun: row => {
+ openShareDialog(row);
+ },
+ },
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: row => {
+ openForm("edit", row);
+ },
+ },
+ // {
+ // name: "璇︽儏",
+ // type: "text",
+ // clickFun: row => {
+ // openDetailDialog(row);
+ // },
+ // },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const selectedRows = ref([]);
+ const userList = ref([]);
+ const tableLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const total = ref(0);
+
+ // 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+ const operationType = ref("");
+ const dialogFormVisible = ref(false);
+ const formYYs = ref({
+ // 鍏朵粬瀛楁...
+ contactList: [
+ {
+ contactPerson: "",
+ contactPhone: "",
+ },
+ ],
+ });
+ const data = reactive({
+ searchForm: {
+ customerName: "",
+ customerType: "",
+ type: 1
+ },
+ form: {
+ customerName: "",
+ taxpayerIdentificationNumber: "",
+ companyAddress: "",
+ companyPhone: "",
+ contactPerson: "",
+ contactPhone: "",
+ maintainer: "",
+ maintenanceTime: "",
+ basicBankAccount: "",
+ bankAccount: "",
+ bankCode: "",
+ customerType: "",
+ type: 1
+ },
+ rules: {
+ customerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxpayerIdentificationNumber: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ companyAddress: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ companyPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ // contactPerson: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ // contactPhone: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ maintainer: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
+ maintenanceTime: [
+ { required: false, message: "璇烽�夋嫨", trigger: "change" },
+ ],
+ 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({
+ // 鏄惁鏄剧ず寮瑰嚭灞傦紙瀹㈡埛瀵煎叆锛�
+ open: false,
+ // 寮瑰嚭灞傛爣棰橈紙瀹㈡埛瀵煎叆锛�
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData",
+ data: {
+ type: 1
+ },
+ // 鏂囦欢涓婁紶鍓嶇殑鍥炶皟
+ beforeUpload: file => {
+ console.log("鏂囦欢鍗冲皢涓婁紶", file);
+ // 鍙互鍦ㄦ澶勫仛鏂囦欢绫诲瀷鎴栧ぇ灏忔牎楠�
+ const isValid =
+ file.type ===
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
+ file.name.endsWith(".xlsx") ||
+ file.name.endsWith(".xls");
+ if (!isValid) {
+ proxy.$modal.msgError("鍙兘涓婁紶 Excel 鏂囦欢");
+ }
+ return isValid;
+ },
+ // 鏂囦欢鐘舵�佹敼鍙樻椂鐨勫洖璋�
+ onChange: (file, fileList) => {
+ console.log("鏂囦欢鐘舵�佹敼鍙�", file, fileList);
+ },
+ // 鏂囦欢涓婁紶鎴愬姛鏃剁殑鍥炶皟
+ 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 {
+ proxy.$modal.msgWarning(response.msg);
+ }
+ },
+ // 鏂囦欢涓婁紶澶辫触鏃剁殑鍥炶皟
+ onError: (error, file, fileList) => {
+ console.error("涓婁紶澶辫触", error, file, fileList);
+ upload.isUploading = false;
+ proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+ },
+ // 鏂囦欢涓婁紶杩涘害鍥炶皟
+ onProgress: (event, file, fileList) => {
+ console.log("涓婁紶涓�...", event.percent);
+ },
+ });
+ const { searchForm, form, rules } = toRefs(data);
+ const addNewContact = () => {
+ formYYs.value.contactList.push({
+ contactPerson: "",
+ contactPhone: "",
+ });
+ };
+
+ const removeContact = index => {
+ if (formYYs.value.contactList.length > 1) {
+ formYYs.value.contactList.splice(index, 1);
+ }
+ };
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ const { total, ...queryPage } = page;
+ listCustomer({ ...searchForm.value, ...queryPage }).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ });
+ };
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ };
+ /** 鎻愪氦涓婁紶鏂囦欢 */
+ function submitFileForm() {
+ upload.isUploading = true;
+ proxy.$refs["uploadRef"].submit();
+ }
+ /** 瀵煎叆鎸夐挳鎿嶄綔 */
+ function handleImport() {
+ upload.title = "瀹㈡埛瀵煎叆";
+ upload.open = true;
+ }
+ /** 涓嬭浇妯℃澘 */
+ function importTemplate() {
+ proxy.download("/basic/customer/downloadTemplate", {}, "瀹㈡埛瀵煎叆妯℃澘.xlsx");
+ }
+ // 鎵撳紑寮规
+ const openForm = (type, row) => {
+ operationType.value = type;
+ form.value = {};
+ form.value.maintainer = userStore.nickName;
+ formYYs.value.contactList = [
+ {
+ contactPerson: "",
+ contactPhone: "",
+ },
+ ];
+ form.value.maintenanceTime = getCurrentDate();
+ form.value.type = 1;
+ userListNoPage().then(res => {
+ userList.value = res.data;
+ });
+ if (type === "edit") {
+ getCustomer(row.id).then(res => {
+ form.value = { ...res.data };
+ formYYs.value.contactList = res.data.contactPerson
+ .split(",")
+ .map((item, index) => {
+ return {
+ contactPerson: item,
+ contactPhone: res.data.contactPhone.split(",")[index],
+ };
+ });
+ });
+ }
+ dialogFormVisible.value = true;
+ };
+ // 鎻愪氦琛ㄥ崟
+ const submitForm = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitEdit();
+ } else {
+ submitAdd();
+ }
+ }
+ });
+ };
+ // 鎻愪氦鏂板
+ const submitAdd = () => {
+ if (formYYs.value.contactList.length < 1) {
+ return proxy.$modal.msgWarning("璇疯嚦灏戞坊鍔犱竴涓仈绯讳汉");
+ }
+ form.value.contactPerson = formYYs.value.contactList
+ .map(item => item.contactPerson)
+ .join(",");
+ form.value.contactPhone = formYYs.value.contactList
+ .map(item => item.contactPhone)
+ .join(",");
+ addCustomer(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+ };
+ // 鎻愪氦淇敼
+ const submitEdit = () => {
+ form.value.contactPerson = formYYs.value.contactList
+ .map(item => item.contactPerson)
+ .join(",");
+ form.value.contactPhone = formYYs.value.contactList
+ .map(item => item.contactPhone)
+ .join(",");
+ updateCustomer(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+ };
+ // 鍏抽棴寮规
+ const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ };
+ const ensureUserList = () => {
+ if (userList.value.length) {
+ return Promise.resolve();
+ }
+ return userListNoPage().then(res => {
+ userList.value = res.data || [];
+ });
+ };
+ const openAssignDialog = row => {
+ assignForm.id = row.id;
+ assignForm.customerName = row.customerName;
+ assignForm.boundId = undefined;
+ ensureUserList().then(() => {
+ assignDialogVisible.value = true;
+ });
+ };
+ const closeAssignDialog = () => {
+ proxy.resetForm("assignFormRef");
+ assignForm.id = undefined;
+ assignForm.customerName = "";
+ assignForm.boundId = undefined;
+ assignDialogVisible.value = false;
+ };
+ const openShareDialog = row => {
+ shareForm.id = row.id;
+ shareForm.customerName = row.customerName;
+ shareForm.boundIds = row.userIds || [];
+ ensureUserList().then(() => {
+ shareDialogVisible.value = true;
+ });
+ };
+ const closeShareDialog = () => {
+ proxy.resetForm("shareFormRef");
+ shareForm.id = undefined;
+ shareForm.customerName = "";
+ shareForm.boundIds = [];
+ shareDialogVisible.value = false;
+ };
+ const submitAssignForm = () => {
+ proxy.$refs.assignFormRef.validate(valid => {
+ if (!valid) {
+ return;
+ }
+ assignCustomer({
+ id: assignForm.id,
+ usageUser: assignForm.boundId,
+ }).then(() => {
+ proxy.$modal.msgSuccess("鍒嗛厤鎴愬姛");
+ closeAssignDialog();
+ getList();
+ });
+ });
+ };
+ const submitShareForm = () => {
+ proxy.$refs.shareFormRef.validate(valid => {
+ if (!valid) {
+ return;
+ }
+ shareCustomer({
+ id: shareForm.id,
+ userIds: shareForm.boundIds,
+ }).then(() => {
+ proxy.$modal.msgSuccess("鍏变韩鎴愬姛");
+ closeShareDialog();
+ getList();
+ });
+ });
+ };
+ const recycle = row => {
+ ElMessageBox.confirm("纭鍥炴敹瀹㈡埛鈥�" + row.customerName + "鈥濆悧锛�", "鍥炴敹鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ return recycleCustomer({id: row.id}).then(() => {
+ proxy.$modal.msgSuccess("鍥炴敹鎴愬姛");
+ getList();
+ })
+ })
+ .catch(error => {
+ if (error === "cancel" || error === "close") {
+ proxy.$modal.msg("宸插彇娑�");
+ }
+ });
+ };
+ // 瀵煎嚭
+ const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/basic/customer/export", {type: 1}, "瀹㈡埛妗f.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
+ // 鍒犻櫎
+ const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
+ const unauthorizedData = selectedRows.value.filter(
+ item => item.maintainer !== userStore.nickName
+ );
+ if (unauthorizedData.length > 0) {
+ proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
+ return;
+ }
+ ids = selectedRows.value.map(item => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ delCustomer(ids)
+ .then(res => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
+
+ // 鎵撳紑鍥炶鎻愰啋寮圭獥
+ const openReminderDialog = row => {
+ currentCustomerId.value = row.id;
+ reminderForm.customerName = row.customerName;
+ reminderForm.reminderSwitch = false;
+ reminderForm.reminderContent = "";
+ reminderForm.reminderTime = "";
+
+ // 灏濊瘯鑾峰彇宸叉湁鐨勫洖璁挎彁閱�
+ getReturnVisit(row.id)
+ .then(res => {
+ if (res.code === 200 && res.data) {
+ reminderForm.reminderSwitch = res.data.isEnabled === 1;
+ reminderForm.reminderContent = res.data.content;
+ reminderForm.reminderTime = res.data.reminderTime;
+ reminderForm.id = res.data.id;
+ }
+ })
+ .catch(error => {
+ console.error("鑾峰彇鍥炶鎻愰啋澶辫触:", error);
+ });
+
+ reminderDialogVisible.value = true;
+ };
+
+ // 鍏抽棴鍥炶鎻愰啋寮圭獥
+ const closeReminderDialog = () => {
+ proxy.resetForm("reminderFormRef");
+ reminderDialogVisible.value = false;
+ };
+ const submitvalue = ref({});
+
+ // 鎻愪氦鍥炶鎻愰啋
+ const submitReminderForm = () => {
+ console.log("鎻愪氦鍥炶鎻愰啋鏁版嵁:", userStore.id, userStore);
+ proxy.$refs.reminderFormRef.validate(valid => {
+ if (valid) {
+ if (reminderForm.id) {
+ submitvalue.value = {
+ id: reminderForm.id,
+ customerId: currentCustomerId.value,
+ isEnabled: reminderForm.reminderSwitch ? 1 : 0,
+ content: reminderForm.reminderContent,
+ reminderTime: reminderForm.reminderTime,
+ remindUserId: userStore.id,
+ };
+ } else {
+ submitvalue.value = {
+ customerId: currentCustomerId.value,
+ isEnabled: reminderForm.reminderSwitch ? 1 : 0,
+ content: reminderForm.reminderContent,
+ reminderTime: reminderForm.reminderTime,
+ remindUserId: userStore.id,
+ };
+ }
+
+ console.log("鎻愪氦鍥炶鎻愰啋鏁版嵁:", submitvalue.value);
+
+ // 璋冪敤鎺ュ彛
+ addReturnVisit(submitvalue.value)
+ .then(res => {
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鍥炶鎻愰啋璁剧疆鎴愬姛");
+ closeReminderDialog();
+ } else {
+ proxy.$modal.msgError(res.msg || "璁剧疆澶辫触");
+ }
+ })
+ .catch(error => {
+ console.error("璁剧疆鍥炶鎻愰啋澶辫触:", error);
+ proxy.$modal.msgError("璁剧疆澶辫触");
+ });
+ }
+ });
+ };
+
+ // 鎵撳紑娲借皥杩涘害寮圭獥
+ const openNegotiationDialog = row => {
+ negotiationForm.customerName = row.customerName;
+ negotiationForm.customerId = row.id;
+ negotiationForm.followUpMethod = "";
+ negotiationForm.followUpLevel = "";
+ negotiationForm.followUpTime = "";
+ negotiationForm.followerUserName = userStore.nickName; // 榛樿褰撳墠鐧诲綍浜�
+ negotiationForm.content = "";
+ // {
+ // "customerId": 152,
+ // "followUpMethod": "鐢佃瘽娌熼��",
+ // "followUpLevel": "娌℃湁鎰忓悜",
+ // "followUpTime": "2026-03-04T15:30:00",
+ // "followerUserName": "绠$悊鍛樿处鍙�",
+ // "content": "111"
+ // }
+ negotiationDialogVisible.value = true;
+ };
+
+ // 鍏抽棴娲借皥杩涘害寮圭獥
+ const closeNegotiationDialog = () => {
+ proxy.resetForm("negotiationFormRef");
+ // 娓呴櫎缂栬緫鐘舵��
+ delete negotiationForm.editIndex;
+ delete negotiationForm.id;
+ negotiationDialogVisible.value = false;
+ };
+
+ // 鎻愪氦娲借皥杩涘害
+ const submitNegotiationForm = () => {
+ proxy.$refs.negotiationFormRef.validate(valid => {
+ if (valid) {
+ // 鍒ゆ柇鏄柊澧炶繕鏄慨鏀�
+ const isEdit = negotiationForm.editIndex !== undefined;
+
+ if (isEdit) {
+ // 淇敼鎿嶄綔
+ console.log("淇敼娲借皥杩涘害鏁版嵁:", negotiationForm);
+ // 杩欓噷鍙互璋冪敤鏇存柊鎺ュ彛
+ // 瀹為檯椤圭洰涓渶瑕佹牴鎹悗绔帴鍙h繘琛岃皟鏁�
+ // 绀轰緥锛歶pdateCustomerFollow(negotiationForm).then(res => {
+ // // 鏇存柊鏈湴鏁版嵁
+ // const index = negotiationForm.editIndex;
+ // negotiationRecords.value[index] = {
+ // followUpTime: negotiationForm.followUpTime,
+ // followUpMethod: negotiationForm.followUpMethod,
+ // followUpLevel: negotiationForm.followUpLevel,
+ // followerUserName: negotiationForm.followerUserName,
+ // content: negotiationForm.content,
+ // id: negotiationForm.id,
+ // };
+ // proxy.$modal.msgSuccess("淇敼鎴愬姛");
+ // closeNegotiationDialog();
+ // });
+ updateCustomerFollow(negotiationForm).then(res => {
+ // 鏇存柊鏈湴鏁版嵁
+ getCustomer(negotiationForm.customerId).then(res => {
+ // 鏇存柊鏈湴鏁版嵁
+ negotiationRecords.value = res.data.followUpList || [];
+ });
+ });
+ proxy.$modal.msgSuccess("淇敼鎴愬姛");
+ closeNegotiationDialog();
+ } else {
+ // 鏂板鎿嶄綔
+ console.log("鎻愪氦娲借皥杩涘害鏁版嵁:", negotiationForm);
+ addCustomerFollow(negotiationForm).then(res => {
+ // 娣诲姞鎴愬姛鍚庢洿鏂拌鎯呴〉闈㈢殑杩涘害璁板綍
+ const newRecord = {
+ followUpTime: negotiationForm.followUpTime,
+ followUpMethod: negotiationForm.followUpMethod,
+ followUpLevel: negotiationForm.followUpLevel,
+ followerUserName: negotiationForm.followerUserName,
+ content: negotiationForm.content,
+ };
+ negotiationRecords.value.unshift(newRecord);
+
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeNegotiationDialog();
+ getList();
+ });
+ }
+ }
+ });
+ };
+
+ // 鎵撳紑璇︽儏寮圭獥
+ const openDetailDialog = row => {
+ // 璋冪敤getCustomer鎺ュ彛鑾峰彇瀹㈡埛璇︽儏
+ getCustomer(row.id).then(res => {
+ // 濉厖瀹㈡埛鍩烘湰淇℃伅
+ Object.assign(detailForm, res.data);
+
+ // 鑾峰彇娲借皥杩涘害璁板綍
+ negotiationRecords.value = res.data.followUpList || [];
+
+ detailDialogVisible.value = true;
+ });
+ };
+
+ // 鍏抽棴璇︽儏寮圭獥
+ const closeDetailDialog = () => {
+ detailDialogVisible.value = false;
+ };
+
+ // 淇敼娲借皥璁板綍
+ const editNegotiationRecord = (row, index) => {
+ // 灏嗗綋鍓嶈褰曟暟鎹~鍏呭埌琛ㄥ崟
+ Object.assign(negotiationForm, {
+ customerName: row.customerName,
+ customerId: row.customerId,
+ followUpMethod: row.followUpMethod,
+ followUpLevel: row.followUpLevel,
+ followUpTime: row.followUpTime,
+ followerUserName: row.followerUserName,
+ content: row.content,
+ id: row.id, // 璁板綍ID鐢ㄤ簬鏇存柊
+ editIndex: index, // 璁板綍绱㈠紩鐢ㄤ簬鏈湴鏇存柊
+ });
+ negotiationDialogVisible.value = true;
+ };
+
+ // 鍒犻櫎娲借皥璁板綍
+ const deleteNegotiationRecord = (row, index) => {
+ ElMessageBox.confirm("纭畾瑕佸垹闄よ繖鏉℃唇璋堣褰曞悧锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ // 杩欓噷鍙互璋冪敤鍒犻櫎鎺ュ彛
+ // 瀹為檯椤圭洰涓渶瑕佹牴鎹悗绔帴鍙h繘琛岃皟鏁�
+ // 绀轰緥锛歞eleteCustomerFollow(row.id).then(() => {
+ // negotiationRecords.value.splice(index, 1);
+ // proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ // });
+ delCustomerFollow(row.id).then(() => {
+ // 鍒犻櫎鎴愬姛鍚庢洿鏂版湰鍦版暟鎹�
+ getCustomer(row.customerId).then(res => {
+ // 鏇存柊鏈湴鏁版嵁
+ negotiationRecords.value = res.data.followUpList || [];
+ });
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ });
+ // 鏈湴鍒犻櫎锛堟ā鎷燂級
+ negotiationRecords.value.splice(index, 1);
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑堝垹闄�");
+ });
+ };
+
+ // 鎵撳紑闄勪欢寮圭獥
+ const openAttachmentDialog = row => {
+ currentFollowRecord.value = row;
+ // 杞崲涓虹鍚圗lement Plus fileList鏍煎紡鐨勬暟缁�
+ currentAttachmentList.value = (row.fileList || []).map((file, index) => ({
+ name: file.fileName,
+ url: file.fileUrl,
+ size: file.fileSize,
+ id: file.id,
+ uid: file.id || index,
+ status: "success",
+ }));
+
+ attachmentDialogVisible.value = true;
+ };
+
+ // 鍏抽棴闄勪欢寮圭獥
+ const closeAttachmentDialog = () => {
+ attachmentDialogVisible.value = false;
+ currentFollowRecord.value = {};
+ currentAttachmentList.value = [];
+ };
+
+ // 闄勪欢涓婁紶鎴愬姛
+ const handleAttachmentSuccess = (response, file, fileList) => {
+ if (response.code === 200) {
+ proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+ // 鏇存柊褰撳墠璁板綍鐨勯檮浠跺垪琛�
+ currentAttachmentList.value = fileList.map(item => ({
+ name: item.name,
+ size: item.size,
+ url: item.response?.data?.url || item.url,
+ id: item.response?.data?.id,
+ uid: item.uid,
+ status: "success",
+ }));
+ // 鏇存柊鍘熻褰曚腑鐨刦iles瀛楁
+ if (currentFollowRecord.value) {
+ currentFollowRecord.value.files = [...currentAttachmentList.value];
+ }
+ } else {
+ proxy.$modal.msgError(response.msg || "涓婁紶澶辫触");
+ }
+ };
+
+ // 闄勪欢涓婁紶澶辫触
+ const handleAttachmentError = (error, file, fileList) => {
+ console.error("涓婁紶澶辫触:", error);
+ proxy.$modal.msgError("涓婁紶澶辫触");
+ };
+
+ // 闄勪欢绉婚櫎
+ const handleAttachmentRemove = (file, fileList) => {
+ currentAttachmentList.value = fileList;
+ // 鏇存柊鍘熻褰曚腑鐨刦iles瀛楁
+ if (currentFollowRecord.value) {
+ currentFollowRecord.value.files = [...fileList];
+ }
+ };
+
+ // 闄勪欢涓婁紶鍓嶆牎楠�
+ const beforeAttachmentUpload = file => {
+ const maxSize = 50 * 1024 * 1024; // 50MB
+ if (file.size > maxSize) {
+ proxy.$modal.msgError("鏂囦欢澶у皬涓嶈兘瓒呰繃50MB");
+ return false;
+ }
+ return true;
+ };
+
+ // 鏍煎紡鍖栨枃浠跺ぇ灏�
+ const formatFileSize = size => {
+ if (size < 1024) {
+ return size + " B";
+ } else if (size < 1024 * 1024) {
+ return (size / 1024).toFixed(2) + " KB";
+ } else {
+ return (size / (1024 * 1024)).toFixed(2) + " MB";
+ }
+ };
+
+ // 涓嬭浇闄勪欢
+ const downloadAttachment = row => {
+ if (row.url) {
+ // proxy.download(row.url, {}, row.name);
+ proxy.$download.name(row.url);
+ } else {
+ proxy.$modal.msgError("涓嬭浇閾炬帴涓嶅瓨鍦�");
+ }
+ };
+
+ // 鍒犻櫎闄勪欢
+ const deleteAttachment = (row, index) => {
+ ElMessageBox.confirm("纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ // 璋冪敤鍚庣鎺ュ彛鍒犻櫎闄勪欢
+ const deleteUrl =
+ import.meta.env.VITE_APP_BASE_API +
+ "/basic/customer-follow/file/" +
+ row.id;
+ fetch(deleteUrl, {
+ method: "DELETE",
+ headers: {
+ Authorization: "Bearer " + getToken(),
+ "Content-Type": "application/json",
+ },
+ })
+ .then(response => response.json())
+ .then(res => {
+ if (res.code === 200) {
+ // 鍒犻櫎鎴愬姛鍚庢洿鏂版湰鍦版枃浠跺垪琛�
+ currentAttachmentList.value.splice(index, 1);
+ // 鏇存柊鍘熻褰曚腑鐨刦iles瀛楁
+ if (currentFollowRecord.value) {
+ currentFollowRecord.value.files = [
+ ...currentAttachmentList.value,
+ ];
+ }
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ } else {
+ proxy.$modal.msgError(res.msg || "鍒犻櫎澶辫触");
+ }
+ })
+ .catch(error => {
+ console.error("鍒犻櫎闄勪欢澶辫触:", error);
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ });
+ })
+ .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}`;
+ }
+
+ onMounted(() => {
+ getList();
+ });
+</script>
+
+<style scoped lang="scss">
+ .detail-section {
+ margin-bottom: 20px;
+ padding: 15px;
+ background-color: #f9f9f9;
+ border-radius: 4px;
+ }
+
+ .section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ }
+
+ .section-title {
+ font-size: 16px;
+ font-weight: bold;
+ margin: 0 0 15px 0;
+ color: #333;
+ }
+
+ .info-display {
+ background-color: #fff;
+ padding: 15px;
+ border-radius: 4px;
+ }
+
+ .info-item {
+ margin-bottom: 12px;
+ display: flex;
+ align-items: flex-start;
+ }
+
+ .info-label {
+ width: 120px;
+ font-weight: 500;
+ color: #606266;
+ margin-right: 10px;
+ }
+
+ .info-value {
+ flex: 1;
+ color: #303133;
+ word-break: break-word;
+ }
+
+ .no-records {
+ text-align: center;
+ padding: 30px;
+ color: #999;
+ font-size: 14px;
+ }
+
+ .attachment-section {
+ .upload-area {
+ margin-bottom: 20px;
+ padding: 20px;
+ background-color: #f9f9f9;
+ border-radius: 4px;
+ border: 1px dashed #d9d9d9;
+
+ .el-upload__tip {
+ margin-top: 10px;
+ color: #909399;
+ }
+ }
+
+ .attachment-list {
+ h4 {
+ margin: 0 0 10px 0;
+ font-size: 14px;
+ color: #606266;
+ }
+ }
+
+ .no-attachment {
+ text-align: center;
+ padding: 40px;
+ color: #909399;
+ font-size: 14px;
+ }
+ }
+</style>
diff --git a/src/views/basicData/parameterMaintenance/index.vue b/src/views/basicData/parameterMaintenance/index.vue
new file mode 100644
index 0000000..2dbf5df
--- /dev/null
+++ b/src/views/basicData/parameterMaintenance/index.vue
@@ -0,0 +1,793 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title ml10">鍙傛暟鍚嶇О锛�</span>
+ <el-input v-model="searchForm.paramName"
+ style="width: 200px"
+ placeholder="璇疯緭鍏ュ弬鏁板悕绉�"
+ clearable />
+ <!-- 鍏宠仈浜у搧绫诲瀷鎼滅储 -->
+ <!-- <span class="search_title ml10">鍏宠仈浜у搧绫诲瀷锛�</span>
+ <el-input v-model="searchForm.productName"
+ style="width: 200px"d
+ placeholder="璇疯緭鍏ュ叧鑱斾骇鍝佺被鍨�"
+ clearable /> -->
+ <el-button type="primary"
+ @click="handleQuery"
+ style="margin-left: 10px">鎼滅储</el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ <el-button type="primary"
+ @click="handleAdd"
+ style="margin-left: 10px">鏂板鍙傛暟</el-button>
+ <!-- 浜у搧绫诲瀷缁存姢鎸夐挳 -->
+ <!-- <el-button type="primary"
+ @click="handleProductTypeMaintenance"
+ style="margin-left: 10px">浜у搧绫诲瀷缁存姢</el-button> -->
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable rowKey="paramName"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ height="calc(100vh - 320px)"
+ :tableLoading="tableLoading"
+ :isSelection="false"
+ :isShowPagination="true"
+ @pagination="pagination">
+ </PIMTable>
+ </div>
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog v-model="dialogVisible"
+ :title="dialogTitle"
+ width="500px">
+ <el-form :model="formData"
+ :rules="rules"
+ ref="formRef"
+ label-width="120px">
+ <el-form-item label="鍙傛暟缂栫爜"
+ prop="paramCode">
+ <el-input v-model="formData.paramCode"
+ disabled
+ placeholder="鑷姩鐢熸垚" />
+ </el-form-item>
+ <el-form-item label="鍙傛暟鍚嶇О"
+ prop="paramName">
+ <el-input v-model="formData.paramName"
+ placeholder="璇疯緭鍏ュ弬鏁板悕绉�" />
+ </el-form-item>
+ <el-form-item label="鍙傛暟绫诲瀷"
+ prop="paramType">
+ <el-select v-model="formData.paramType"
+ @change="handleParamTypeChange"
+ placeholder="璇烽�夋嫨鍙傛暟绫诲瀷">
+ <el-option label="鏁板�兼牸寮�"
+ :value="1" />
+ <el-option label="鏂囨湰鏍煎紡"
+ :value="2" />
+ <el-option label="涓嬫媺閫夐」"
+ :value="3" />
+ <el-option label="鏃堕棿鏍煎紡"
+ :value="4" />
+ </el-select>
+ </el-form-item>
+ <!-- <el-form-item label="鍙栧�兼ā寮�"
+ prop="valueMode">
+ <el-select v-model="formData.valueMode"
+ placeholder="璇烽�夋嫨鍙栧�兼ā寮�">
+ <el-option label="鍗曞��"
+ value="1" />
+ <el-option label="鍖洪棿"
+ value="2" />
+ </el-select>
+ </el-form-item> -->
+ <el-form-item label="鍗曚綅"
+ prop="unit">
+ <el-input v-model="formData.unit"
+ placeholder="璇疯緭鍏ュ崟浣�" />
+ </el-form-item>
+ <el-form-item label="鍙栧�兼牸寮�"
+ v-if="formData.paramType == 1 || formData.paramType == 2"
+ prop="paramFormat">
+ <el-input v-model="formData.paramFormat"
+ placeholder="璇疯緭鍏ュ彇鍊兼牸寮�" />
+ <!-- <el-select v-model="formData.paramFormat"
+ placeholder="璇烽�夋嫨鍙栧�兼ā寮�">
+ <el-option label="#.00000"
+ value="#.00000" />
+ <el-option label="#.0000"
+ value="#.0000" />
+ <el-option label="#.000"
+ value="#.000" />
+ <el-option label="#.00"
+ value="#.00" />
+ </el-select> -->
+ </el-form-item>
+ <el-form-item label="涓嬫媺瀛楀吀"
+ v-else-if="formData.paramType == 3"
+ prop="paramFormat">
+ <el-select v-model="formData.paramFormat"
+ placeholder="璇烽�夋嫨鍙栧�兼ā寮�">
+ <el-option v-for="item in dictTypes"
+ :key="item.dictType"
+ :label="item.dictName"
+ :value="item.dictType" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏃堕棿鏍煎紡"
+ v-else-if="formData.paramType == 4"
+ prop="paramFormat">
+ <el-select v-model="formData.paramFormat"
+ placeholder="璇烽�夋嫨鍙栧�兼ā寮�">
+ <el-option label="YYYY-MM-DD"
+ value="YYYY-MM-DD" />
+ <el-option label="YYYY-MM-DD HH:mm:ss"
+ value="YYYY-MM-DD HH:mm:ss" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏄惁蹇呭~"
+ prop="isRequired">
+ <el-switch v-model="formData.isRequired"
+ :active-value="1"
+ :inactive-value="0" />
+ </el-form-item>
+ <el-form-item label="澶囨敞"
+ prop="remark">
+ <el-input v-model="formData.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <!-- 浜у搧绫诲瀷缁存姢瀵硅瘽妗� -->
+ <!-- <el-dialog v-model="productTypeDialogVisible"
+ title="浜у搧绫诲瀷缁存姢"
+ width="600px">
+ <div class="product-type-header">
+ <el-button type="primary"
+ @click="handleAddProductType">鏂板浜у搧绫诲瀷</el-button>
+ </div>
+ <el-table :data="productTypeList"
+ border
+ style="width: 100%; margin-top: 10px; margin-bottom: 20px">
+ <el-table-column prop="typeCode"
+ label="绫诲瀷缂栫爜"
+ width="150" />
+ <el-table-column prop="typeName"
+ label="绫诲瀷鍚嶇О" />
+ <el-table-column label="鎿嶄綔"
+ width="150">
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ @click="handleEditProductType(scope.row)">缂栬緫</el-button>
+ <el-button link
+ type="danger"
+ @click="handleDeleteProductType(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-dialog> -->
+ <!-- 鏂板/缂栬緫浜у搧绫诲瀷瀵硅瘽妗� -->
+ <!-- <el-dialog v-model="productTypeFormVisible"
+ :title="productTypeDialogTitle"
+ width="400px">
+ <el-form :model="productTypeForm"
+ :rules="productTypeRules"
+ ref="productTypeFormRef"
+ label-width="100px">
+ <el-form-item label="绫诲瀷缂栫爜"
+ prop="typeCode">
+ <el-input v-model="productTypeForm.typeCode"
+ placeholder="璇疯緭鍏ョ被鍨嬬紪鐮�" />
+ </el-form-item>
+ <el-form-item label="绫诲瀷鍚嶇О"
+ prop="typeName">
+ <el-input v-model="productTypeForm.typeName"
+ placeholder="璇疯緭鍏ョ被鍨嬪悕绉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="productTypeFormVisible = false">鍙栨秷</el-button>
+ <el-button type="primary"
+ @click="handleProductTypeSubmit">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog> -->
+ </div>
+</template>
+
+<script setup>
+ import { onMounted, ref, reactive } from "vue";
+ import {
+ parameterListPage,
+ addParameter,
+ updateParameter,
+ delParameter,
+ addBaseParam,
+ editBaseParam,
+ getBaseParamList,
+ removeBaseParam,
+ // getProductTypes as getProductTypesApi,
+ } from "@/api/basicData/parameterMaintenance.js";
+ import { listType } from "@/api/system/dict/type";
+ import { deptTreeSelect } from "@/api/system/user.js";
+ import PIMTable from "@/components/PIMTable/PIMTable.vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+
+ const tableColumn = ref([
+ {
+ label: "鍙傛暟缂栫爜",
+ prop: "paramCode",
+ },
+ {
+ label: "鍙傛暟鍚嶇О",
+ prop: "paramName",
+ },
+ {
+ label: "鍙傛暟绫诲瀷",
+ prop: "paramType",
+ dataType: "tag",
+ formatType: params => {
+ const typeMap = {
+ 1: "primary",
+ 2: "info",
+ 3: "warning",
+ 4: "success",
+ };
+ return typeMap[params] || "default";
+ },
+ formatData: val => {
+ const labelMap = {
+ 1: "鏁板�兼牸寮�",
+ 2: "鏂囨湰鏍煎紡",
+ 3: "涓嬫媺閫夐」",
+ 4: "鏃堕棿鏍煎紡",
+ };
+ return labelMap[val] || val;
+ },
+ },
+ // {
+ // label: "鍙栧�兼ā寮�",
+ // prop: "valueMode",
+ // dataType: "tag",
+ // formatType: params => {
+ // return params === 2 ? "warning" : "success";
+ // },
+ // formatData: val => {
+ // return val === 2 ? "鍖洪棿" : "鍗曞��";
+ // },
+ // },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "鍙栧�兼牸寮�",
+ prop: "paramFormat",
+ },
+ {
+ label: "鏄惁蹇呭~",
+ prop: "isRequired",
+ dataType: "tag",
+ formatType: val => {
+ return val === 1 ? "success" : "info";
+ },
+ formatData: val => {
+ return val === 1 ? "鏄�" : "鍚�";
+ },
+ },
+ {
+ label: "澶囨敞",
+ prop: "remark",
+ },
+ {
+ label: "鍒涘缓鏃堕棿",
+ prop: "createTime",
+ },
+ {
+ label: "鎿嶄綔",
+ dataType: "action",
+ width: "150",
+ operation: [
+ {
+ name: "缂栬緫",
+ clickFun: row => {
+ handleEdit(row);
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ clickFun: row => {
+ handleDelete(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+ });
+ // 鎼滅储琛ㄥ崟
+ const searchForm = reactive({
+ paramName: "",
+ productName: "",
+ });
+
+ // 瀵硅瘽妗嗙浉鍏�
+ const dialogVisible = ref(false);
+ const dialogTitle = ref("");
+ const formRef = ref(null);
+ const formData = reactive({
+ id: null,
+ paramCode: "",
+ paramName: "",
+ paramType: "",
+ // valueMode: "1",
+ unit: "",
+ remark: "",
+ isRequired: 0,
+ paramFormat: "",
+ });
+ const rules = reactive({
+ paramName: [{ required: true, message: "璇疯緭鍏ュ弬鏁板悕绉�", trigger: "blur" }],
+ paramType: [{ required: true, message: "璇烽�夋嫨鍙傛暟绫诲瀷", trigger: "change" }],
+ // valueMode: [{ required: true, message: "璇烽�夋嫨鍙栧�兼ā寮�", trigger: "change" }],
+ unit: [
+ {
+ required: false,
+ message: "璇疯緭鍏ュ崟浣�",
+ trigger: "blur",
+ validator: (rule, value, callback) => {
+ if (formData.paramType === 1 && !value) {
+ callback(new Error("鏁板�肩被鍨嬪繀椤诲~鍐欏崟浣�"));
+ } else {
+ callback();
+ }
+ },
+ },
+ ],
+ });
+ // const productTypes = ref([]);
+ const isEdit = ref(false);
+
+ // 浜у搧绫诲瀷缁存姢鐩稿叧 - 宸叉敞閲�
+ // const productTypeDialogVisible = ref(false);
+ // const productTypeFormVisible = ref(false);
+ // const productTypeDialogTitle = ref("");
+ // const productTypeFormRef = ref(null);
+ // const productTypeList = ref([]);
+ // const productTypeForm = reactive({
+ // id: null,
+ // typeCode: "",
+ // typeName: "",
+ // });
+ // const productTypeRules = reactive({
+ // typeCode: [{ required: true, message: "璇疯緭鍏ョ被鍨嬬紪鐮�", trigger: "blur" }],
+ // typeName: [{ required: true, message: "璇疯緭鍏ョ被鍨嬪悕绉�", trigger: "blur" }],
+ // });
+ // const isProductTypeEdit = ref(false);
+ const handleParamTypeChange = () => {
+ if (formData.paramType === 1) {
+ formData.paramFormat = "#.00000";
+ } else if (formData.paramType === 4) {
+ formData.paramFormat = "YYYY-MM-DD HH:mm:ss";
+ } else {
+ formData.paramFormat = "";
+ }
+ // 瑙﹀彂鍗曚綅瀛楁楠岃瘉
+ if (formRef.value) {
+ formRef.value.validateField("unit");
+ }
+ };
+ // 浜у搧绫诲瀷缁存姢鎸夐挳鐐瑰嚮浜嬩欢 - 宸叉敞閲�
+ // const handleProductTypeMaintenance = () => {
+ // productTypeDialogVisible.value = true;
+ // getProductTypeList();
+ // };
+
+ // 鑾峰彇浜у搧绫诲瀷鍒楄〃 - 宸叉敞閲�
+ // const getProductTypeList = () => {
+ // productTypeList.value = [
+ // { id: 1, typeCode: "TYPE001", typeName: "3.5鐮屽潡" },
+ // { id: 2, typeCode: "TYPE002", typeName: "5.0鐮屽潡" },
+ // { id: 3, typeCode: "TYPE003", typeName: "鏉挎潗" },
+ // ];
+ // };
+
+ // 鏂板浜у搧绫诲瀷 - 宸叉敞閲�
+ // const handleAddProductType = () => {
+ // isProductTypeEdit.value = false;
+ // productTypeDialogTitle.value = "鏂板浜у搧绫诲瀷";
+ // productTypeForm.id = null;
+ // productTypeForm.typeCode = "";
+ // productTypeForm.typeName = "";
+ // productTypeFormVisible.value = true;
+ // };
+
+ // 缂栬緫浜у搧绫诲瀷 - 宸叉敞閲�
+ // const handleEditProductType = row => {
+ // isProductTypeEdit.value = true;
+ // productTypeDialogTitle.value = "缂栬緫浜у搧绫诲瀷";
+ // productTypeForm.id = row.id;
+ // productTypeForm.typeCode = row.typeCode;
+ // productTypeForm.typeName = row.typeName;
+ // productTypeFormVisible.value = true;
+ // };
+
+ // 鍒犻櫎浜у搧绫诲瀷 - 宸叉敞閲�
+ // const handleDeleteProductType = row => {
+ // ElMessageBox.confirm("纭畾瑕佸垹闄よ浜у搧绫诲瀷鍚楋紵", "鎻愮ず", {
+ // confirmButtonText: "纭畾",
+ // cancelButtonText: "鍙栨秷",
+ // type: "warning",
+ // })
+ // .then(() => {
+ // ElMessage.success("鍒犻櫎鎴愬姛");
+ // getProductTypeList();
+ // })
+ // .catch(() => {});
+ // };
+
+ // 鎻愪氦浜у搧绫诲瀷琛ㄥ崟 - 宸叉敞閲�
+ // const handleProductTypeSubmit = () => {
+ // productTypeFormRef.value.validate(valid => {
+ // if (valid) {
+ // ElMessage.success(isProductTypeEdit.value ? "缂栬緫鎴愬姛" : "鏂板鎴愬姛");
+ // productTypeFormVisible.value = false;
+ // getProductTypeList();
+ // }
+ // });
+ // };
+
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+
+ /** 閲嶇疆鎸夐挳鎿嶄綔 */
+ const handleReset = () => {
+ searchForm.paramName = "";
+ searchForm.productName = "";
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+
+ const getList = () => {
+ tableLoading.value = true;
+ // 璋冪敤鏂版帴鍙� /baseParam/list
+ getBaseParamList({
+ paramName: searchForm.paramName,
+ current: page.current,
+ size: page.size,
+ })
+ .then(res => {
+ tableLoading.value = false;
+ if (res.code === 200) {
+ tableData.value = res.data.records || [];
+ page.total = res.data.total || 0;
+ console.log(tableData.value, "tableData.value");
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ ElMessage.error("鏌ヨ澶辫触");
+ });
+ };
+
+ // 鑾峰彇浜у搧绫诲瀷鍒楄〃 - 宸叉敞閲�
+ // const getProductTypes = () => {
+ // productTypes.value = [
+ // { label: "3.5鐮屽潡", value: "type1" },
+ // { label: "5.0鐮屽潡", value: "type2" },
+ // { label: "鏉挎潗", value: "type3" },
+ // ];
+ // };
+
+ // 鏂板鎸夐挳鐐瑰嚮浜嬩欢
+ const handleAdd = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍙傛暟";
+ // 閲嶇疆琛ㄥ崟
+ formData.id = null;
+ formData.paramCode = "";
+ formData.paramName = "";
+ formData.paramType = "";
+ // formData.valueMode = "1";
+ formData.unit = "";
+ formData.remark = "";
+ formData.isRequired = 0;
+ dialogVisible.value = true;
+ };
+
+ // 缂栬緫鎸夐挳鐐瑰嚮浜嬩欢
+ const handleEdit = row => {
+ isEdit.value = true;
+ dialogTitle.value = "缂栬緫鍙傛暟";
+ // 濉厖琛ㄥ崟鏁版嵁
+ formData.id = row.id;
+ formData.paramCode = row.paramCode || "";
+ formData.paramName = row.paramName || "";
+ formData.paramType = row.paramType !== undefined ? row.paramType : null;
+ // formData.valueMode =
+ // row.valueMode !== undefined ? String(row.valueMode) : "1";
+ formData.unit = row.unit || "";
+ formData.remark = row.remark || "";
+ formData.paramFormat = row.paramFormat || "";
+ formData.isRequired = row.isRequired || 0;
+ dialogVisible.value = true;
+ };
+
+ // 鍒犻櫎鎸夐挳鐐瑰嚮浜嬩欢
+ const handleDelete = row => {
+ ElMessageBox.confirm("纭畾瑕佸垹闄よ繖鏉℃暟鎹悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ // 璋冪敤鏂版帴鍙� /baseParam/remove/{id}
+ removeBaseParam([row.id])
+ .then(res => {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ })
+ .catch(() => {
+ // 鍙栨秷鍒犻櫎
+ });
+ };
+
+ // 鎻愪氦琛ㄥ崟
+ const handleSubmit = () => {
+ if (formData.paramType == 3 && !formData.paramFormat) {
+ ElMessage.warning("涓嬫媺瀛楀吀涓嶈兘涓虹┖锛�");
+ return;
+ }
+ formRef.value.validate(valid => {
+ if (valid) {
+ if (formData.id) {
+ // 缂栬緫浣跨敤鏂版帴鍙� /technologyParam/edit
+ editBaseParam(formData)
+ .then(res => {
+ ElMessage.success("缂栬緫鎴愬姛");
+ dialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ // ElMessage.error("缂栬緫澶辫触");
+ });
+ } else {
+ // 鏂板浣跨敤鏂版帴鍙� /technologyParam/add
+ addBaseParam(formData)
+ .then(res => {
+ ElMessage.success("鏂板鎴愬姛");
+ dialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ ElMessage.error("鏂板澶辫触");
+ });
+ }
+ } else {
+ return false;
+ }
+ });
+ };
+ const dictTypes = ref([]);
+ const getDictTypes = () => {
+ listType({ pageNum: 1, pageSize: 1000 }).then(res => {
+ dictTypes.value = res.rows || [];
+ });
+ };
+
+ onMounted(() => {
+ getDictTypes();
+ getList();
+ // getProductTypes();
+ });
+</script>
+
+<style scoped lang="scss">
+ .app-container {
+ padding: 24px;
+ background-color: #f0f2f5;
+ min-height: calc(100vh - 48px);
+ }
+
+ .search_form {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+ padding: 20px;
+ background-color: #ffffff;
+ border-radius: 6px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
+ }
+
+ .search_title {
+ color: #606266;
+ font-size: 14px;
+ font-weight: 500;
+ }
+
+ .ml10 {
+ margin-left: 10px;
+ }
+ }
+
+ .table_list {
+ background-color: #ffffff;
+ border-radius: 6px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+ overflow: hidden;
+ height: calc(100vh - 230px);
+ }
+
+ :deep(.el-table) {
+ border: none;
+ border-radius: 6px;
+ overflow: hidden;
+ box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1);
+
+ .el-table__header-wrapper {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
+ th {
+ background: transparent;
+ font-weight: 600;
+ // color: #ffffff;
+ border-bottom: none;
+ padding: 16px 0;
+ letter-spacing: 0.5px;
+ }
+ }
+
+ .el-table__body-wrapper {
+ tr {
+ transition: all 0.3s ease;
+
+ &:hover {
+ background: linear-gradient(
+ 90deg,
+ rgba(102, 126, 234, 0.05) 0%,
+ rgba(118, 75, 162, 0.05) 100%
+ );
+ transform: scale(1.002);
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
+ }
+
+ td {
+ border-bottom: 1px solid #f0f0f0;
+ padding: 14px 0;
+ color: #303133;
+ }
+ }
+
+ tr.current-row {
+ background: linear-gradient(
+ 90deg,
+ rgba(102, 126, 234, 0.08) 0%,
+ rgba(118, 75, 162, 0.08) 100%
+ );
+ }
+
+ // 鏁板�煎瓧娈垫牱寮�
+ .quantity-cell,
+ .volume-cell,
+ .dimension-cell {
+ font-weight: 600;
+ color: #409eff;
+ font-family: "Courier New", monospace;
+ text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
+ }
+
+ // 瑙勬牸瀛楁鏍峰紡
+ .spec-cell {
+ color: #67c23a;
+ font-weight: 500;
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+
+ // 缂栫爜瀛楁鏍峰紡
+ .code-cell {
+ color: #e6a23c;
+ font-family: "Courier New", monospace;
+ font-weight: 500;
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+
+ // 鏃ユ湡瀛楁鏍峰紡
+ .date-cell {
+ color: #909399;
+ font-style: italic;
+ }
+ }
+
+ .el-table__empty-block {
+ padding: 60px 0;
+ background-color: #fafafa;
+ }
+ }
+
+ .pagination-container {
+ display: flex;
+ justify-content: flex-end;
+ padding: 16px 20px;
+ background-color: #ffffff;
+ border-top: 1px solid #ebeef5;
+ border-radius: 0 0 12px 12px;
+ }
+
+ :deep(.el-button) {
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-1px);
+ }
+ }
+
+ @media (max-width: 768px) {
+ .app-container {
+ padding: 16px;
+ }
+
+ .search_form {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+
+ .el-form {
+ width: 100%;
+
+ .el-form-item {
+ width: 100%;
+ }
+ }
+
+ .el-button {
+ margin-right: 12px;
+ }
+ }
+
+ :deep(.el-table) {
+ th,
+ td {
+ padding: 10px 0;
+ font-size: 12px;
+ }
+ }
+ }
+</style>
diff --git a/src/views/basicData/product/ProductSelectDialog.vue b/src/views/basicData/product/ProductSelectDialog.vue
index ded23cc..ad27baa 100644
--- a/src/views/basicData/product/ProductSelectDialog.vue
+++ b/src/views/basicData/product/ProductSelectDialog.vue
@@ -1,12 +1,12 @@
<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 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 label="浜у搧鍨嬪彿">
+ <el-input v-model="query.model" placeholder="杈撳叆浜у搧鍨嬪彿" clearable @keyup.enter="onSearch" />
</el-form-item>
<el-form-item>
@@ -20,8 +20,8 @@
@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="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>
@@ -43,7 +43,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
-import { productModelList } from '@/api/basicData/productModel'
+import { productModelList, productModelListByUrl } from '@/api/basicData/productModel'
export type ProductRow = {
id: number;
@@ -55,6 +55,8 @@
const props = defineProps<{
modelValue: boolean;
single?: boolean; // 鏄惁鍙兘閫夋嫨涓�涓紝榛樿false锛堝彲閫夋嫨澶氫釜锛�
+ topProductParentId?: number; // 涓�绾т骇鍝乮d
+ requestUrl?: string; // 鑷畾涔夋煡璇㈡帴鍙�
}>();
const emit = defineEmits(['update:modelValue', 'confirm']);
@@ -154,14 +156,19 @@
loading.value = true;
try {
multipleSelection.value = []; // 缈婚〉/鎼滅储鍚庢竻绌洪�夋嫨鏇寸鍚堥鏈�
- const res: any = await productModelList({
+ const params = {
productName: query.productName.trim(),
model: query.model.trim(),
current: page.pageNum,
size: page.pageSize,
- });
- tableData.value = res.records;
- total.value = res.total;
+ topProductParentId: props.topProductParentId,
+ };
+ const res: any = props.requestUrl
+ ? await productModelListByUrl(props.requestUrl, params)
+ : await productModelList(params);
+ const records = res?.records || res?.data?.records || res?.data || [];
+ tableData.value = Array.isArray(records) ? records : [];
+ total.value = Number(res?.total ?? res?.data?.total ?? tableData.value.length);
} finally {
loading.value = false;
}
diff --git a/src/views/basicData/product/index.vue b/src/views/basicData/product/index.vue
index 7b2a819..99ab028 100644
--- a/src/views/basicData/product/index.vue
+++ b/src/views/basicData/product/index.vue
@@ -2,37 +2,34 @@
<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"
- />
- <el-button
- type="primary"
- @click="openProDia('addOne')"
- style="margin-left: 10px"
- >鏂板浜у搧澶х被</el-button
- >
+ <el-input v-model="search"
+ style="width: 210px"
+ placeholder="杈撳叆鍏抽敭瀛楄繘琛屾悳绱�"
+ @change="searchFilter"
+ @clear="searchFilter"
+ clearable
+ prefix-icon="Search" />
+ <el-button v-if="false"
+ type="primary"
+ @click="openProDia('addOne')"
+ style="margin-left: 10px">鏂板浜у搧澶х被</el-button>
</div>
<div ref="containerRef">
- <el-tree
- ref="tree"
- v-loading="treeLoad"
- :data="list"
- @node-click="handleNodeClick"
- :expand-on-click-node="false"
- :default-expanded-keys="expandedKeys"
- :filter-node-method="filterNode"
- :props="{ children: 'children', label: 'label' }"
- highlight-current
- node-key="id"
- class="product-tree-scroll"
- style="height: calc(100vh - 190px); overflow-y: auto"
- >
+ <el-tree ref="tree"
+ v-loading="treeLoad"
+ :data="list"
+ @node-click="handleNodeClick"
+ :expand-on-click-node="false"
+ @node-expand="handleNodeExpand"
+ @node-collapse="handleNodeCollapse"
+ :key="treeKey"
+ :default-expanded-keys="expandedKeys"
+ :filter-node-method="filterNode"
+ :props="{ children: 'children', label: 'label' }"
+ highlight-current
+ node-key="id"
+ class="product-tree-scroll"
+ style="height: calc(100vh - 190px); overflow-y: auto">
<template #default="{ node, data }">
<div class="custom-tree-node">
<span class="tree-node-content">
@@ -43,23 +40,23 @@
<span class="tree-node-label">{{ data.label }}</span>
</span>
<div>
- <el-button
- type="primary"
- link
- @click="openProDia('edit', data)"
- >
+ <el-button type="primary"
+ link
+ :disabled="isTopLevelNode(data, node)"
+ @click="openProDia('edit', data)">
缂栬緫
</el-button>
- <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3">
+ <el-button type="primary"
+ link
+ @click="openProDia('add', data)">
娣诲姞浜у搧
</el-button>
- <el-button
- v-if="!node.childNodes.length"
- style="margin-left: 4px"
- type="danger"
- link
- @click="remove(node, data)"
- >
+ <el-button v-if="!node.childNodes.length"
+ style="margin-left: 4px"
+ type="danger"
+ link
+ :disabled="isTopLevelNode(data, node)"
+ @click="remove(node, data)">
鍒犻櫎
</el-button>
</div>
@@ -69,103 +66,109 @@
</div>
</div>
<div class="right">
- <div style="margin-bottom: 10px" v-if="isShowButton">
- <el-button type="primary" @click="openModelDia('add')">
+ <div style="margin-bottom: 10px"
+ v-if="isShowButton">
+ <el-button type="primary"
+ @click="openModelDia('add')">
鏂板瑙勬牸鍨嬪彿
</el-button>
- <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" />
- <el-button
- type="danger"
- @click="handleDelete"
- style="margin-left: 10px"
- plain
- >
+ <ImportExcel :product-id="currentId"
+ @uploadSuccess="getModelList" />
+ <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"
- ></PIMTable>
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"></PIMTable>
</div>
- <el-dialog v-model="productDia" title="浜у搧" width="400px" @keydown.enter.prevent>
- <el-form
- :model="form"
- label-width="140px"
- label-position="top"
- :rules="rules"
- ref="formRef"
- >
+ <el-dialog v-model="productDia"
+ title="浜у搧"
+ width="400px"
+ @keydown.enter.prevent>
+ <el-form :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef">
<el-row :gutter="30">
<el-col :span="24">
- <el-form-item label="浜у搧鍚嶇О锛�" prop="productName">
- <el-input
- v-model="form.productName"
- placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
- maxlength="20"
- show-word-limit
- clearable
- @keydown.enter.prevent
- />
+ <el-form-item label="浜у搧鍚嶇О锛�"
+ prop="productName">
+ <el-input v-model="form.productName"
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+ maxlength="20"
+ show-word-limit
+ clearable
+ @keydown.enter.prevent />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button type="primary"
+ @click="submitForm">纭</el-button>
<el-button @click="closeProDia">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
- <el-dialog
- v-model="modelDia"
- title="瑙勬牸鍨嬪彿"
- width="400px"
- @close="closeModelDia"
- @keydown.enter.prevent
- >
- <el-form
- :model="modelForm"
- label-width="140px"
- label-position="top"
- :rules="modelRules"
- ref="modelFormRef"
- >
+ <el-dialog v-model="modelDia"
+ title="瑙勬牸鍨嬪彿"
+ width="400px"
+ @close="closeModelDia"
+ @keydown.enter.prevent>
+ <el-form :model="modelForm"
+ label-width="140px"
+ label-position="top"
+ :rules="modelRules"
+ ref="modelFormRef">
<el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="浜у搧缂栧彿锛�"
+ prop="productCode">
+ <el-input v-model="modelForm.productCode"
+ placeholder="璇疯緭鍏ヤ骇鍝佺紪鍙�"
+ clearable
+ @keydown.enter.prevent />
+ </el-form-item>
+ </el-col>
+ </el-row>
<el-col :span="24">
- <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="model">
- <el-input
- v-model="modelForm.model"
- placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
- clearable
- @keydown.enter.prevent
- />
+ <el-form-item label="瑙勬牸鍨嬪彿锛�"
+ prop="model">
+ <el-input v-model="modelForm.model"
+ placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+ clearable
+ @keydown.enter.prevent />
</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
- @keydown.enter.prevent
- />
+ <el-form-item label="鍗曚綅锛�"
+ prop="unit">
+ <el-input v-model="modelForm.unit"
+ 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="submitModelForm">纭</el-button>
+ <el-button type="primary"
+ @click="submitModelForm">纭</el-button>
<el-button @click="closeModelDia">鍙栨秷</el-button>
</div>
</template>
@@ -174,359 +177,480 @@
</template>
<script setup>
-import { ref } from "vue";
-import { ElMessageBox } from "element-plus";
-import {
- addOrEditProduct,
- addOrEditProductModel,
- delProduct,
- delProductModel,
- modelListPage,
- productTreeList,
-} from "@/api/basicData/product.js";
-import ImportExcel from "./ImportExcel/index.vue";
+ import { nextTick, ref } from "vue";
+ import { ElMessageBox } from "element-plus";
+ import {
+ addOrEditProduct,
+ addOrEditProductModel,
+ delProduct,
+ delProductModel,
+ modelListPage,
+ productTreeList,
+ } from "@/api/basicData/product.js";
+ import ImportExcel from "./ImportExcel/index.vue";
-const { proxy } = getCurrentInstance();
-const tree = ref(null);
-const containerRef = ref(null);
+ const { proxy } = getCurrentInstance();
+ const tree = ref(null);
+ const containerRef = ref(null);
+ const treeKey = ref(0);
+ const expandedKeySet = new Set();
+ const EXPANDED_STORAGE_KEY = "basicData_product_tree_expanded_keys_v2";
-const productDia = ref(false);
-const modelDia = ref(false);
-const modelOperationType = ref("");
-const search = ref("");
-const currentId = ref("");
-const currentParentId = ref("");
-const operationType = ref("");
-const treeLoad = ref(false);
-const list = ref([]);
-const expandedKeys = ref([]);
-const tableColumn = ref([
- {
- label: "瑙勬牸鍨嬪彿",
- prop: "model",
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- },
- {
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
- operation: [
- {
- name: "缂栬緫",
- type: "text",
- clickFun: (row) => {
- openModelDia("edit", row);
- },
- },
- ],
- },
-]);
-const tableData = ref([]);
-const tableLoading = ref(false);
-const isShowButton = ref(false);
-const selectedRows = ref([]);
-const page = reactive({
- current: 1,
- size: 10,
- total: 0,
-});
-const data = reactive({
- form: {
- productName: "",
- },
- rules: {
- productName: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- { max: 20, message: "浜у搧鍚嶇О涓嶈兘瓒呰繃20涓瓧绗�", trigger: "blur" },
- ],
- },
- modelForm: {
- model: "",
- unit: "",
- },
- modelRules: {
- model: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- },
-});
-const { form, rules, modelForm, modelRules } = toRefs(data);
-// 鏌ヨ浜у搧鏍�
-const getProductTreeList = () => {
- treeLoad.value = true;
- productTreeList()
- .then((res) => {
- list.value = res;
- list.value.forEach((a) => {
- expandedKeys.value.push(a.label);
+ const loadExpandedKeys = () => {
+ if (typeof window === "undefined") {
+ return [];
+ }
+ try {
+ const saved = localStorage.getItem(EXPANDED_STORAGE_KEY);
+ return saved ? JSON.parse(saved) : [];
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ };
+
+ const saveExpandedKeys = () => {
+ if (typeof window === "undefined") {
+ return;
+ }
+ localStorage.setItem(
+ EXPANDED_STORAGE_KEY,
+ JSON.stringify(Array.from(expandedKeySet))
+ );
+ };
+
+ loadExpandedKeys().forEach(key => expandedKeySet.add(key));
+
+ const syncExpandedKeysFromTree = () => {
+ const keys = [];
+ const walk = nodes => {
+ (nodes || []).forEach(item => {
+ if (item.expanded && item.data?.id !== undefined) {
+ keys.push(item.data.id);
+ }
+ if (item.childNodes && item.childNodes.length) {
+ walk(item.childNodes);
+ }
});
- treeLoad.value = false;
- })
- .catch((err) => {
- treeLoad.value = false;
- });
-};
-// 杩囨护浜у搧鏍�
-const searchFilter = () => {
- proxy.$refs.tree.filter(search.value);
-};
-// 鎵撳紑浜у搧寮规
-const openProDia = (type, data) => {
- operationType.value = type;
- productDia.value = true;
- form.value.productName = "";
- if (type === "edit") {
- form.value.productName = data.productName;
- }
-};
-// 鎵撳紑瑙勬牸鍨嬪彿寮规
-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 submitForm = () => {
- proxy.$refs.formRef.validate((valid) => {
- if (valid) {
- if (operationType.value === "add") {
- form.value.parentId = currentId.value;
- form.value.id = "";
- } else if (operationType.value === "addOne") {
- form.value.id = "";
- form.value.parentId = "";
- } else {
- form.value.id = currentId.value;
- form.value.parentId = "";
+ };
+
+ walk(tree.value?.root?.childNodes);
+ expandedKeySet.clear();
+ keys.forEach(key => expandedKeySet.add(key));
+ expandedKeys.value = keys;
+ saveExpandedKeys();
+ };
+
+ const normalizeExpandedKeys = treeData => {
+ const parentMap = new Map();
+ const walk = (nodes, parentId = null) => {
+ (nodes || []).forEach(item => {
+ parentMap.set(item.id, parentId);
+ if (item.children && item.children.length) {
+ walk(item.children, item.id);
+ }
+ });
+ };
+
+ walk(treeData);
+
+ const normalizedKeys = Array.from(expandedKeySet).filter(key => {
+ if (!parentMap.has(key)) {
+ return false;
}
- addOrEditProduct(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeProDia();
- getProductTreeList();
- });
- }
- });
-};
-// 鍏抽棴浜у搧寮规
-const closeProDia = () => {
- proxy.$refs.formRef.resetFields();
- productDia.value = false;
-};
+ let currentId = key;
+ while (parentMap.has(currentId)) {
+ const parentId = parentMap.get(currentId);
+ if (!parentId) {
+ return true;
+ }
+ if (!expandedKeySet.has(parentId)) {
+ return false;
+ }
+ currentId = parentId;
+ }
+ return true;
+ });
-// 鍒犻櫎浜у搧
-const remove = (node, data) => {
- let ids = [];
- ids.push(data.id);
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- tableLoading.value = true;
- delProduct(ids)
- .then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ if (normalizedKeys.length !== expandedKeySet.size) {
+ expandedKeySet.clear();
+ normalizedKeys.forEach(key => expandedKeySet.add(key));
+ saveExpandedKeys();
+ }
+ };
+
+ const productDia = ref(false);
+ const modelDia = ref(false);
+ const modelOperationType = ref("");
+ const search = ref("");
+ const currentId = ref("");
+ const currentParentId = ref("");
+ const operationType = ref("");
+ const treeLoad = ref(false);
+ const list = ref([]);
+ const expandedKeys = ref([]);
+ const tableColumn = ref([
+ {
+ label: "浜у搧缂栧彿",
+ prop: "productCode",
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "model",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: row => {
+ openModelDia("edit", row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const isShowButton = ref(false);
+ const selectedRows = ref([]);
+ const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+ });
+ const data = reactive({
+ form: {
+ productName: "",
+ },
+ rules: {
+ productName: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ { max: 20, message: "浜у搧鍚嶇О涓嶈兘瓒呰繃20涓瓧绗�", trigger: "blur" },
+ ],
+ },
+ modelForm: {
+ model: "",
+ unit: "",
+ productCode: "",
+ },
+ modelRules: {
+ model: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ productCode: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ },
+ });
+ const { form, rules, modelForm, modelRules } = toRefs(data);
+ // 鏌ヨ浜у搧鏍�
+ const getProductTreeList = () => {
+ treeLoad.value = true;
+ productTreeList()
+ .then(res => {
+ list.value = res || [];
+ normalizeExpandedKeys(list.value);
+ expandedKeys.value = Array.from(expandedKeySet);
+ treeKey.value += 1;
+ nextTick(() => {
+ tree.value?.setDefaultExpandedKeys?.(expandedKeys.value);
+ });
+ })
+ .catch(err => {
+ console.error(err);
+ })
+ .finally(() => {
+ treeLoad.value = false;
+ });
+ };
+ const handleNodeExpand = data => {
+ nextTick(syncExpandedKeysFromTree);
+ };
+ const handleNodeCollapse = (data, node) => {
+ node?.eachNode?.(item => {
+ item.collapse();
+ });
+ nextTick(syncExpandedKeysFromTree);
+ };
+ // 杩囨护浜у搧鏍�
+ const searchFilter = () => {
+ proxy.$refs.tree.filter(search.value);
+ };
+ const isTopLevelNode = (data, node) => {
+ if (node?.level !== undefined) {
+ return node.level === 1;
+ }
+ return [null, undefined, "", 0, "0"].includes(data?.parentId);
+ };
+ // 鎵撳紑浜у搧寮规
+ const openProDia = (type, data) => {
+ if (data && type === "edit" && isTopLevelNode(data)) {
+ proxy.$modal.msgWarning("涓�绾ц妭鐐逛笉鑳界紪杈戞垨鍒犻櫎");
+ return;
+ }
+ operationType.value = type;
+ productDia.value = true;
+ form.value.productName = "";
+ if (type === "edit") {
+ form.value.productName = data.productName;
+ }
+ };
+ // 鎵撳紑瑙勬牸鍨嬪彿寮规
+ const openModelDia = (type, data) => {
+ modelOperationType.value = type;
+ modelDia.value = true;
+ modelForm.value.model = "";
+ modelForm.value.unit = "";
+ modelForm.value.productCode = "";
+ modelForm.value.id = "";
+ if (type === "edit") {
+ modelForm.value = { ...data };
+ }
+ };
+ // 鎻愪氦浜у搧鍚嶇О淇敼
+ const submitForm = () => {
+ proxy.$refs.formRef.validate(valid => {
+ if (valid) {
+ if (operationType.value === "add") {
+ form.value.parentId = currentId.value;
+ form.value.id = "";
+ } else if (operationType.value === "addOne") {
+ form.value.id = "";
+ form.value.parentId = "";
+ } else {
+ form.value.id = currentId.value;
+ form.value.parentId = "";
+ }
+ addOrEditProduct(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeProDia();
getProductTreeList();
- })
- .finally(() => {
- tableLoading.value = false;
});
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
+ }
});
-};
-// 閫夋嫨浜у搧
-const handleNodeClick = (val, node, el) => {
- // 鍒ゆ柇鏄惁涓哄彾瀛愯妭鐐�
- isShowButton.value = !(val.children && val.children.length > 0);
- // 鍙湁鍙跺瓙鑺傜偣鎵嶆墽琛屼互涓嬮�昏緫
- currentId.value = val.id;
- currentParentId.value = val.parentId;
- getModelList();
-};
+ };
+ // 鍏抽棴浜у搧寮规
+ const closeProDia = () => {
+ proxy.$refs.formRef.resetFields();
+ productDia.value = false;
+ };
-// 鎻愪氦瑙勬牸鍨嬪彿淇敼
-const submitModelForm = () => {
- proxy.$refs.modelFormRef.validate((valid) => {
- if (valid) {
- modelForm.value.productId = currentId.value;
- addOrEditProductModel(modelForm.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeModelDia();
- getModelList();
- });
+ // 鍒犻櫎浜у搧
+ const remove = (node, data) => {
+ if (isTopLevelNode(data, node)) {
+ proxy.$modal.msgWarning("涓�绾ц妭鐐逛笉鑳界紪杈戞垨鍒犻櫎");
+ return;
}
- });
-};
-// 鍏抽棴鍨嬪彿寮规
-const closeModelDia = () => {
- proxy.$refs.modelFormRef.resetFields();
- modelDia.value = false;
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-
-// 鏌ヨ瑙勬牸鍨嬪彿
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getModelList();
-};
-const getModelList = () => {
- tableLoading.value = true;
- modelListPage({
- id: currentId.value,
- current: page.current,
- size: page.size,
- }).then((res) => {
- console.log("res", res);
- tableData.value = res.records;
- page.total = res.total;
- tableLoading.value = false;
- });
-};
-// 鍒犻櫎瑙勬牸鍨嬪彿
-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;
- delProductModel(ids)
- .then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getModelList();
- })
- .finally(() => {
- tableLoading.value = false;
- });
+ let ids = [];
+ ids.push(data.id);
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
})
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
+ .then(() => {
+ tableLoading.value = true;
+ delProduct(ids)
+ .then(res => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getProductTreeList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
+ // 閫夋嫨浜у搧
+ const handleNodeClick = (val, node, el) => {
+ // 鍒ゆ柇鏄惁涓哄彾瀛愯妭鐐�
+ isShowButton.value = !(val.children && val.children.length > 0);
+ // 鍙湁鍙跺瓙鑺傜偣鎵嶆墽琛屼互涓嬮�昏緫
+ currentId.value = val.id;
+ currentParentId.value = val.parentId;
+ getModelList();
+ };
+
+ // 鎻愪氦瑙勬牸鍨嬪彿淇敼
+ const submitModelForm = () => {
+ proxy.$refs.modelFormRef.validate(valid => {
+ if (valid) {
+ modelForm.value.productId = currentId.value;
+ addOrEditProductModel(modelForm.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeModelDia();
+ getModelList();
+ });
+ }
});
-};
-// 璋冪敤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) {
+ };
+ // 鍏抽棴鍨嬪彿寮规
+ const closeModelDia = () => {
+ proxy.$refs.modelFormRef.resetFields();
+ modelDia.value = false;
+ };
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ };
+
+ // 鏌ヨ瑙勬牸鍨嬪彿
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getModelList();
+ };
+ const getModelList = () => {
+ tableLoading.value = true;
+ modelListPage({
+ id: currentId.value,
+ current: page.current,
+ size: page.size,
+ }).then(res => {
+ console.log("res", res);
+ tableData.value = res.records;
+ page.total = res.total;
+ tableLoading.value = false;
+ });
+ };
+ // 鍒犻櫎瑙勬牸鍨嬪彿
+ 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;
+ delProductModel(ids)
+ .then(res => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getModelList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
+ // 璋冪敤tree杩囨护鏂规硶 涓枃鑻辫繃婊�
+ const filterNode = (value, data, node) => {
+ if (!value) {
+ //濡傛灉鏁版嵁涓虹┖锛屽垯杩斿洖true,鏄剧ず鎵�鏈夌殑鏁版嵁椤�
return true;
}
- // 鍚﹀垯鐨勮瘽鍐嶅線涓婁竴灞傚仛鍖归厤
- parentData = parentData.parent;
- index++;
- }
- // 娌″尮閰嶅埌杩斿洖false
- return false;
-};
-getProductTreeList();
+ // 鏌ヨ鍒楄〃鏄惁鏈夊尮閰嶆暟鎹紝灏嗗�煎皬鍐欙紝鍖归厤鑻辨枃鏁版嵁
+ 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;
+ };
+ getProductTreeList();
</script>
<style scoped>
-.product-view {
- display: flex;
-}
-.left {
- width: 450px;
- min-width: 450px;
- padding: 16px;
- background: #ffffff;
-}
-.right {
- flex: 1;
- min-width: 0;
- padding: 16px;
- margin-left: 20px;
- background: #ffffff;
-}
-.custom-tree-node {
- flex: 1;
- min-width: 0;
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 14px;
- padding-right: 8px;
-}
-.tree-node-content {
- flex: 1;
- min-width: 0;
- display: flex;
- align-items: center;
- height: 100%;
- overflow: hidden;
-}
-.tree-node-content .orange-icon {
- flex-shrink: 0;
-}
-.tree-node-label {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-.orange-icon {
- color: orange;
- font-size: 18px;
- margin-right: 8px; /* 鍥炬爣涓庢枃瀛椾箣闂村姞鐐归棿璺� */
-}
-.product-tree-scroll {
- scrollbar-width: thin;
- scrollbar-color: #c0c4cc #f5f7fa;
-}
-.product-tree-scroll::-webkit-scrollbar {
- width: 8px;
-}
-.product-tree-scroll::-webkit-scrollbar-track {
- background: #f5f7fa;
- border-radius: 4px;
-}
-.product-tree-scroll::-webkit-scrollbar-thumb {
- background: #c0c4cc;
- border-radius: 4px;
-}
-.product-tree-scroll::-webkit-scrollbar-thumb:hover {
- background: #909399;
-}
+ .product-view {
+ display: flex;
+ }
+ .left {
+ width: 450px;
+ min-width: 450px;
+ padding: 16px;
+ background: #ffffff;
+ }
+ .right {
+ flex: 1;
+ min-width: 0;
+ padding: 16px;
+ margin-left: 20px;
+ background: #ffffff;
+ }
+ .custom-tree-node {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ padding-right: 8px;
+ }
+ .tree-node-content {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ height: 100%;
+ overflow: hidden;
+ }
+ .tree-node-content .orange-icon {
+ flex-shrink: 0;
+ }
+ .tree-node-label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .orange-icon {
+ color: orange;
+ font-size: 18px;
+ margin-right: 8px; /* 鍥炬爣涓庢枃瀛椾箣闂村姞鐐归棿璺� */
+ }
+ .product-tree-scroll {
+ scrollbar-width: thin;
+ scrollbar-color: #c0c4cc #f5f7fa;
+ }
+ .product-tree-scroll::-webkit-scrollbar {
+ width: 8px;
+ }
+ .product-tree-scroll::-webkit-scrollbar-track {
+ background: #f5f7fa;
+ border-radius: 4px;
+ }
+ .product-tree-scroll::-webkit-scrollbar-thumb {
+ background: #c0c4cc;
+ border-radius: 4px;
+ }
+ .product-tree-scroll::-webkit-scrollbar-thumb:hover {
+ background: #909399;
+ }
</style>
diff --git a/src/views/basicData/supplierManage/components/HomeTab.vue b/src/views/basicData/supplierManage/components/HomeTab.vue
index 85c3265..47dce00 100644
--- a/src/views/basicData/supplierManage/components/HomeTab.vue
+++ b/src/views/basicData/supplierManage/components/HomeTab.vue
@@ -1,7 +1,7 @@
<template>
- <div class="app-container">
+ <div>
<div class="search_form">
- <div>
+ <div style="margin-bottom: 10px;">
<span class="search_title">渚涘簲鍟嗘。妗堬細</span>
<el-input
v-model="searchForm.supplierName"
@@ -15,7 +15,7 @@
>鎼滅储</el-button
>
</div>
- <div>
+ <div style="margin-bottom: 10px;">
<el-button type="primary" @click="openForm('add')"
>鏂板渚涘簲鍟�</el-button
>
diff --git a/src/views/collaborativeApproval/approvalManagement/index.vue b/src/views/collaborativeApproval/approvalManagement/index.vue
new file mode 100644
index 0000000..4638392
--- /dev/null
+++ b/src/views/collaborativeApproval/approvalManagement/index.vue
@@ -0,0 +1,881 @@
+<template>
+ <div class="app-container">
+ <!-- 椤甸潰鏍囬 -->
+ <div class="page-header">
+ <div class="header-title">
+ <el-icon class="title-icon"><Setting /></el-icon>
+ <span>瀹℃壒娴佺▼閰嶇疆</span>
+ </div>
+ <div class="header-desc">涓轰笉鍚屽鎵圭被鍨嬮厤缃鎵规祦绋嬪拰瀹℃壒浜�</div>
+ </div>
+
+ <!-- 瀹℃壒绫诲瀷鍒囨崲 - 绱у噾鏍囩寮� -->
+ <div class="type-tabs">
+ <div
+ v-for="type in approveTypes"
+ :key="type.value"
+ class="type-tab"
+ :class="{ active: activeTab === type.value }"
+ @click="activeTab = type.value; handleTabChange()"
+ >
+ <el-icon :size="14" :style="{ color: activeTab === type.value ? type.color : '#909399' }">
+ <component :is="type.icon" />
+ </el-icon>
+ <span class="tab-name">{{ type.label }}</span>
+ </div>
+ </div>
+
+ <!-- 涓昏鍐呭鍖哄煙 -->
+ <el-card class="config-card" shadow="hover" v-loading="loading">
+ <template #header>
+ <div class="card-header">
+ <div class="header-left">
+ <div class="type-icon" :style="{ backgroundColor: getTypeColor(currentApproveType) }">
+ <el-icon :size="20" color="#fff"><component :is="getTypeIcon(currentApproveType)" /></el-icon>
+ </div>
+ <div class="header-info">
+ <span class="type-name">{{ currentApproveTypeName }}</span>
+ <el-tag :type="approverList.length > 0 ? 'success' : 'warning'" size="small" effect="light">
+ {{ approverList.length > 0 ? `宸查厤缃� ${approverList.length} 涓鎵逛汉` : '鏈厤缃鎵逛汉' }}
+ </el-tag>
+ </div>
+ </div>
+ <div class="header-actions" v-if="approverList.length > 0">
+ <el-button @click="handleReset" size="default">
+ <el-icon><RefreshLeft /></el-icon>
+ 閲嶇疆
+ </el-button>
+ <el-button type="primary" @click="handleSave" :loading="saveLoading" size="default">
+ <el-icon><Check /></el-icon>
+ 淇濆瓨閰嶇疆
+ </el-button>
+ </div>
+ </div>
+ </template>
+
+ <!-- 瀹℃壒娴佺▼灞曠ず -->
+ <div class="flow-wrapper" v-if="approverList.length > 0">
+ <div class="flow-container">
+ <div
+ v-for="(item, index) in approverList"
+ :key="index"
+ class="flow-item"
+ >
+ <!-- 瀹℃壒鑺傜偣鍗$墖 -->
+ <div class="node-card" :class="{ 'empty': !item.approverId }">
+ <!-- 椤堕儴搴忓彿鍜岀骇鍒� -->
+ <div class="node-badge">{{ index + 1 }}</div>
+
+ <!-- 澶村儚鍖哄煙 -->
+ <div class="node-avatar-section">
+ <div
+ class="node-avatar"
+ :class="{ 'has-user': item.approverId }"
+ :style="item.approverId ? { backgroundColor: getAvatarColor(item.approverName) } : {}"
+ >
+ <span v-if="item.approverId">{{ item.approverName.charAt(0) }}</span>
+ <el-icon v-else :size="24"><User /></el-icon>
+ </div>
+ <div class="node-level">{{ getLevelText(index) }}</div>
+ </div>
+
+ <!-- 閫夋嫨鍖哄煙 -->
+ <div class="node-select-section">
+ <el-select
+ v-model="item.approverId"
+ placeholder="閫夋嫨瀹℃壒浜�"
+ filterable
+ size="default"
+ @change="(val) => handleApproverChange(val, item)"
+ >
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </div>
+
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div class="node-actions">
+ <el-button
+ type="primary"
+ circle
+ :disabled="index === 0"
+ @click="moveLeft(index)"
+ size="small"
+ class="action-btn"
+ title="鍓嶇Щ"
+ >
+ <el-icon><ArrowLeft /></el-icon>
+ </el-button>
+ <el-button
+ type="primary"
+ circle
+ :disabled="index === approverList.length - 1"
+ @click="moveRight(index)"
+ size="small"
+ class="action-btn"
+ title="鍚庣Щ"
+ >
+ <el-icon><ArrowRight /></el-icon>
+ </el-button>
+ <el-button
+ type="danger"
+ circle
+ @click="handleDelete(index)"
+ size="small"
+ class="action-btn"
+ >
+ <el-icon><Delete /></el-icon>
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 杩炴帴绠ご -->
+ <div class="arrow-connector" v-if="index < approverList.length - 1">
+ <div class="arrow-line"></div>
+ <el-icon class="arrow-icon"><ArrowRight /></el-icon>
+ </div>
+ </div>
+
+ <!-- 鏂板鑺傜偣鎸夐挳 - 鏀惧湪娴佺▼鏈�鍚� -->
+ <div class="add-node-item">
+ <div class="arrow-connector" v-if="approverList.length > 0">
+ <div class="arrow-line"></div>
+ <el-icon class="arrow-icon"><ArrowRight /></el-icon>
+ </div>
+ <div class="add-node-card" @click="handleAdd">
+ <div class="add-icon-wrapper">
+ <el-icon :size="28"><Plus /></el-icon>
+ </div>
+ <span class="add-text">鏂板瀹℃壒浜�</span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 绌虹姸鎬� -->
+ <div class="empty-state" v-else>
+ <div class="empty-content">
+ <div class="empty-icon-wrapper">
+ <el-icon :size="48" color="#c0c4cc"><User /></el-icon>
+ </div>
+ <div class="empty-text">鏆傛棤瀹℃壒浜洪厤缃�</div>
+ <div class="empty-subtext">鐐瑰嚮涓嬫柟鎸夐挳娣诲姞绗竴涓鎵逛汉</div>
+ <el-button type="primary" size="large" @click="handleAdd" class="empty-add-btn">
+ <el-icon><Plus /></el-icon>
+ 鏂板瀹℃壒浜�
+ </el-button>
+ </div>
+ </div>
+ </el-card>
+
+ <!-- 搴曢儴鎻愮ず -->
+ <div class="bottom-tips">
+ <el-icon><InfoFilled /></el-icon>
+ <span>鎻愮ず锛氭瘡涓祦绋嬭嚦灏戦厤缃竴涓鎵逛汉锛屽鎵规寜椤哄簭娴佽浆锛屽彲閫氳繃绠ご璋冩暣椤哄簭</span>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import {
+ Plus, ArrowLeft, Delete, Check, RefreshLeft, Setting,
+ Suitcase, Calendar, Location, Money, ShoppingCart, DocumentChecked,
+ Van, ArrowRight, User, InfoFilled
+} from '@element-plus/icons-vue';
+import { getApproveProcessConfigNodeList, addApproveProcessConfigNode } from '@/api/collaborativeApproval/approvalManagement';
+import { userListNoPage } from '@/api/system/user';
+
+// 褰撳墠閫変腑鐨勬爣绛鹃〉
+const activeTab = ref('1');
+
+// 瀹℃壒绫诲瀷閰嶇疆鏁扮粍
+const approveTypes = [
+ { value: '1', label: '鍏嚭绠$悊', icon: 'Suitcase', color: '#409EFF' },
+ { value: '2', label: '璇峰亣绠$悊', icon: 'Calendar', color: '#67C23A' },
+ { value: '3', label: '鍑哄樊绠$悊', icon: 'Location', color: '#E6A23C' },
+ { value: '4', label: '鎶ラ攢绠$悊', icon: 'Money', color: '#F56C6C' },
+ { value: '5', label: '閲囪喘瀹℃壒', icon: 'ShoppingCart', color: '#909399' },
+ { value: '6', label: '鎶ヤ环瀹℃壒', icon: 'DocumentChecked', color: '#9B59B6' },
+ { value: '7', label: '鍙戣揣瀹℃壒', icon: 'Van', color: '#1ABC9C' },
+];
+
+// 瀹℃壒绫诲瀷鍚嶇О鏄犲皠
+const approveTypeNameMap = {
+ 1: '鍏嚭绠$悊',
+ 2: '璇峰亣绠$悊',
+ 3: '鍑哄樊绠$悊',
+ 4: '鎶ラ攢绠$悊',
+ 5: '閲囪喘瀹℃壒',
+ 6: '鎶ヤ环瀹℃壒',
+ 7: '鍙戣揣瀹℃壒',
+};
+
+// 瀹℃壒绫诲瀷鍥炬爣鏄犲皠
+const typeIconMap = {
+ 1: 'Suitcase',
+ 2: 'Calendar',
+ 3: 'Location',
+ 4: 'Money',
+ 5: 'ShoppingCart',
+ 6: 'DocumentChecked',
+ 7: 'Van',
+};
+
+// 瀹℃壒绫诲瀷棰滆壊鏄犲皠
+const typeColorMap = {
+ 1: '#409EFF',
+ 2: '#67C23A',
+ 3: '#E6A23C',
+ 4: '#F56C6C',
+ 5: '#909399',
+ 6: '#9B59B6',
+ 7: '#1ABC9C',
+};
+
+// 澶村儚棰滆壊姹�
+const avatarColors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#9B59B6', '#1ABC9C', '#FF6B6B', '#4ECDC4'];
+
+// 褰撳墠瀹℃壒绫诲瀷鍚嶇О
+const currentApproveTypeName = computed(() => {
+ return approveTypeNameMap[activeTab.value] || '鏈煡绫诲瀷';
+});
+
+// 褰撳墠瀹℃壒绫诲瀷
+const currentApproveType = computed(() => {
+ return Number(activeTab.value);
+});
+
+// 鑾峰彇绫诲瀷鍥炬爣
+const getTypeIcon = (type) => typeIconMap[type] || 'Setting';
+
+// 鑾峰彇绫诲瀷棰滆壊
+const getTypeColor = (type) => typeColorMap[type] || '#409EFF';
+
+// 鑾峰彇澶村儚棰滆壊
+const getAvatarColor = (name) => {
+ if (!name) return '#C0C4CC';
+ let hash = 0;
+ for (let i = 0; i < name.length; i++) {
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ return avatarColors[Math.abs(hash) % avatarColors.length];
+};
+
+// 鑾峰彇绾у埆鏂囨湰
+const getLevelText = (index) => {
+ const texts = ['绗竴绾�', '绗簩绾�', '绗笁绾�', '绗洓绾�', '绗簲绾�', '绗叚绾�', '绗竷绾�', '绗叓绾�'];
+ return texts[index] || `绗�${index + 1}绾;
+};
+
+// 瀹℃壒浜哄垪琛紙鐪熷疄鎺ュ彛锛�
+const userList = ref([]);
+
+// 瀹℃壒浜哄垪琛�
+const approverList = ref([]);
+
+// 鍘熷鏁版嵁锛岀敤浜庨噸缃�
+const originalList = ref([]);
+
+// 鍔犺浇鐘舵��
+const loading = ref(false);
+const saveLoading = ref(false);
+
+// 鏍囩椤靛垏鎹㈠鐞�
+const handleTabChange = () => {
+ loadData();
+};
+
+// 鍔犺浇瀹℃壒閰嶇疆鏁版嵁锛堟ā鎷燂級
+const loadData = async () => {
+ loading.value = true;
+ try {
+ const res = await getApproveProcessConfigNodeList(currentApproveType.value);
+ const source = Array.isArray(res?.data)
+ ? res.data
+ : Array.isArray(res?.rows)
+ ? res.rows
+ : Array.isArray(res?.data?.records)
+ ? res.data.records
+ : [];
+ const data = source.map((item, index) => ({
+ ...item,
+ sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1,
+ }));
+ approverList.value = data.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
+ originalList.value = JSON.parse(JSON.stringify(approverList.value));
+ } catch (error) {
+ approverList.value = [];
+ originalList.value = [];
+ ElMessage.error('鍔犺浇瀹℃壒閰嶇疆澶辫触');
+ } finally {
+ loading.value = false;
+ }
+};
+
+const loadUserList = async () => {
+ try {
+ const res = await userListNoPage();
+ userList.value = Array.isArray(res?.data) ? res.data : [];
+ } catch (error) {
+ userList.value = [];
+ ElMessage.error('鍔犺浇浜哄憳鍒楄〃澶辫触');
+ }
+};
+
+// 瀹℃壒浜洪�夋嫨鍙樺寲
+const handleApproverChange = (userId, row) => {
+ const user = userList.value.find((u) => u.userId === userId);
+ if (user) {
+ row.approverName = user.nickName;
+ }
+};
+
+// 鏂板瀹℃壒浜�
+const handleAdd = () => {
+ const newOrder = approverList.value.length + 1;
+ approverList.value.push({
+ id: null,
+ approveType: currentApproveType.value,
+ approverId: null,
+ approverName: '',
+ sortOrder: newOrder,
+ });
+};
+
+// 鍒犻櫎瀹℃壒浜�
+const handleDelete = (index) => {
+ ElMessageBox.confirm('纭畾鍒犻櫎璇ュ鎵逛汉鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ })
+ .then(() => {
+ approverList.value.splice(index, 1);
+ approverList.value.forEach((item, idx) => {
+ item.sortOrder = idx + 1;
+ });
+ ElMessage.success('鍒犻櫎鎴愬姛');
+ })
+ .catch(() => {});
+};
+
+// 鍓嶇Щ
+const moveLeft = (index) => {
+ if (index === 0) return;
+ const temp = approverList.value[index];
+ approverList.value[index] = approverList.value[index - 1];
+ approverList.value[index - 1] = temp;
+ approverList.value[index].sortOrder = index + 1;
+ approverList.value[index - 1].sortOrder = index;
+};
+
+// 鍚庣Щ
+const moveRight = (index) => {
+ if (index === approverList.value.length - 1) return;
+ const temp = approverList.value[index];
+ approverList.value[index] = approverList.value[index + 1];
+ approverList.value[index + 1] = temp;
+ approverList.value[index].sortOrder = index + 1;
+ approverList.value[index + 1].sortOrder = index + 2;
+};
+
+// 淇濆瓨閰嶇疆
+const handleSave = async () => {
+ if (approverList.value.length === 0) {
+ ElMessage.warning('璇疯嚦灏戦厤缃竴涓鎵逛汉');
+ return;
+ }
+
+ const hasEmptyApprover = approverList.value.some((item) => !item.approverId);
+ if (hasEmptyApprover) {
+ ElMessage.warning('璇烽�夋嫨鎵�鏈夊鎵逛汉');
+ return;
+ }
+
+ const approverIds = approverList.value.map((item) => item.approverId);
+ const uniqueIds = [...new Set(approverIds)];
+ if (uniqueIds.length !== approverIds.length) {
+ ElMessage.warning('瀹℃壒浜轰笉鑳介噸澶�');
+ return;
+ }
+
+ saveLoading.value = true;
+ try {
+ const payload = approverList.value.map((item, index) => ({
+ approveType: currentApproveType.value,
+ nodeOrder: index + 1,
+ approverId: item.approverId,
+ approverName: item.approverName,
+ }));
+ await addApproveProcessConfigNode(payload);
+ ElMessage.success('淇濆瓨鎴愬姛');
+ await loadData();
+ } catch (error) {
+ ElMessage.error('淇濆瓨澶辫触');
+ } finally {
+ saveLoading.value = false;
+ }
+};
+
+// 閲嶇疆
+const handleReset = () => {
+ if (originalList.value.length === 0) {
+ approverList.value = [];
+ return;
+ }
+ ElMessageBox.confirm('纭畾瑕侀噸缃綋鍓嶉厤缃悧锛熸湭淇濆瓨鐨勬洿鏀瑰皢涓㈠け銆�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ })
+ .then(() => {
+ approverList.value = JSON.parse(JSON.stringify(originalList.value));
+ ElMessage.success('宸查噸缃�');
+ })
+ .catch(() => {});
+};
+
+onMounted(async () => {
+ await loadUserList();
+ await loadData();
+});
+</script>
+
+<style scoped>
+.page-header {
+ margin-bottom: 20px;
+}
+
+.header-title {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 20px;
+ font-weight: 600;
+ color: var(--el-text-color-primary, #303133);
+ margin-bottom: 6px;
+}
+
+.title-icon {
+ font-size: 24px;
+ color: var(--el-color-primary, #409EFF);
+}
+
+.header-desc {
+ font-size: 13px;
+ color: var(--el-text-color-secondary, #909399);
+ margin-left: 34px;
+}
+
+/* 瀹℃壒绫诲瀷鍒囨崲 - 绱у噾鏍囩寮� */
+.type-tabs {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 16px;
+ padding: 4px;
+ background: var(--el-fill-color-light, #f5f7fa);
+ border-radius: 8px;
+ overflow-x: auto;
+}
+
+.type-tab {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+ font-size: 13px;
+ color: var(--el-text-color-regular, #606266);
+}
+
+.type-tab:hover {
+ background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1));
+ color: var(--el-color-primary, #409EFF);
+}
+
+.type-tab.active {
+ background: var(--el-bg-color, #fff);
+ color: var(--el-color-primary, #409EFF);
+ font-weight: 600;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.tab-name {
+ font-size: 13px;
+}
+
+.tab-count {
+ min-width: 16px;
+ height: 16px;
+ padding: 0 5px;
+ background: var(--el-color-success, #67C23A);
+ color: #fff;
+ border-radius: 8px;
+ font-size: 11px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.config-card {
+ margin-bottom: 16px;
+ border-radius: 12px;
+}
+
+:deep(.el-card__header) {
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.header-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.type-icon {
+ width: 44px;
+ height: 44px;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.header-info {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.type-name {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--el-text-color-primary, #303133);
+}
+
+.header-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.flow-wrapper {
+ overflow-x: auto;
+ padding: 8px 4px;
+}
+
+.flow-container {
+ display: flex;
+ align-items: center;
+ gap: 0;
+ min-width: min-content;
+}
+
+.flow-item {
+ display: flex;
+ align-items: center;
+}
+
+.node-card {
+ width: 200px;
+ background: var(--el-bg-color, #fff);
+ border: 2px solid var(--el-border-color, #e4e7ed);
+ border-radius: 12px;
+ padding: 16px;
+ position: relative;
+ transition: all 0.3s ease;
+ flex-shrink: 0;
+}
+
+.node-card:hover {
+ border-color: var(--el-color-primary, #409EFF);
+ box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15);
+ transform: translateY(-2px);
+}
+
+.node-card.empty {
+ border-style: dashed;
+ border-color: var(--el-border-color, #c0c4cc);
+ background: var(--el-fill-color-light, #fafbfc);
+}
+
+.node-card.empty:hover {
+ border-color: var(--el-color-primary, #409EFF);
+ background: var(--el-fill-color-light, #f5f7fa);
+}
+
+.node-badge {
+ position: absolute;
+ top: -10px;
+ left: 16px;
+ width: 24px;
+ height: 24px;
+ background: var(--el-color-primary, #409EFF);
+ color: #fff;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 13px;
+ font-weight: 700;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4);
+}
+
+.node-avatar-section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-bottom: 12px;
+ margin-top: 4px;
+}
+
+.node-avatar {
+ width: 56px;
+ height: 56px;
+ border-radius: 50%;
+ background: var(--el-fill-color, #f0f2f5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 8px;
+ color: var(--el-text-color-placeholder, #c0c4cc);
+ transition: all 0.3s ease;
+}
+
+.node-avatar.has-user {
+ color: #fff;
+ font-size: 22px;
+ font-weight: 600;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.node-level {
+ font-size: 12px;
+ color: var(--el-text-color-secondary, #909399);
+ font-weight: 500;
+}
+
+.node-select-section {
+ margin-bottom: 12px;
+}
+
+.node-select-section :deep(.el-select) {
+ width: 100%;
+}
+
+.node-actions {
+ display: flex;
+ justify-content: center;
+ gap: 8px;
+ padding-top: 12px;
+ border-top: 1px solid var(--el-border-color-light, #ebeef5);
+}
+
+.action-btn {
+ transition: all 0.2s;
+}
+
+.action-btn:hover:not(:disabled) {
+ transform: scale(1.1);
+}
+
+.arrow-connector {
+ display: flex;
+ align-items: center;
+ width: 50px;
+ position: relative;
+}
+
+.arrow-line {
+ flex: 1;
+ height: 2px;
+ background: var(--el-border-color, #c0c4cc);
+}
+
+.arrow-icon {
+ color: var(--el-text-color-placeholder, #c0c4cc);
+ font-size: 14px;
+ margin-left: -2px;
+}
+
+/* 鏂板鑺傜偣鏍峰紡 */
+.add-node-item {
+ display: flex;
+ align-items: center;
+}
+
+.add-node-card {
+ width: 140px;
+ height: 200px;
+ border: 2px dashed var(--el-border-color, #c0c4cc);
+ border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ background: var(--el-fill-color-light, #fafbfc);
+ flex-shrink: 0;
+ margin-left: 0;
+}
+
+.add-node-card:hover {
+ border-color: var(--el-color-primary, #409EFF);
+ background: var(--el-color-primary-light-9, #f0f7ff);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15);
+}
+
+.add-icon-wrapper {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background: var(--el-color-primary, #409EFF);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
+ transition: all 0.3s ease;
+}
+
+.add-node-card:hover .add-icon-wrapper {
+ transform: scale(1.1);
+ box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4);
+}
+
+.add-text {
+ font-size: 14px;
+ color: var(--el-text-color-regular, #606266);
+ font-weight: 500;
+}
+
+.add-node-card:hover .add-text {
+ color: var(--el-color-primary, #409EFF);
+}
+
+/* 绌虹姸鎬� */
+.empty-state {
+ padding: 50px 20px;
+}
+
+.empty-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+}
+
+.empty-icon-wrapper {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ background: var(--el-fill-color-light, #f5f7fa);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 8px;
+}
+
+.empty-text {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--el-text-color-regular, #606266);
+}
+
+.empty-subtext {
+ font-size: 13px;
+ color: var(--el-text-color-secondary, #909399);
+}
+
+.empty-add-btn {
+ margin-top: 8px;
+ padding: 12px 28px;
+}
+
+/* 搴曢儴鎻愮ず */
+.bottom-tips {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 12px 20px;
+ background: var(--el-fill-color-light, #f5f7fa);
+ border-radius: 8px;
+ color: var(--el-text-color-regular, #606266);
+ font-size: 13px;
+}
+
+.bottom-tips .el-icon {
+ color: var(--el-color-primary, #409EFF);
+ font-size: 16px;
+}
+
+@media (max-width: 768px) {
+ .type-tabs {
+ padding: 3px;
+ }
+
+ .type-tab {
+ padding: 6px 10px;
+ font-size: 12px;
+ }
+
+ .tab-name {
+ font-size: 12px;
+ }
+
+ .flow-container {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .arrow-connector {
+ width: 100%;
+ height: 30px;
+ flex-direction: row;
+ justify-content: center;
+ }
+
+ .arrow-line {
+ width: 2px;
+ height: 30px;
+ }
+
+ .arrow-icon {
+ right: auto;
+ top: auto;
+ bottom: -5px;
+ transform: rotate(90deg);
+ }
+
+ .add-node-item {
+ width: 100%;
+ justify-content: center;
+ margin-top: 10px;
+ }
+
+ .add-node-item .arrow-connector {
+ display: none;
+ }
+}
+</style>
diff --git a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
index 20a4ee6..1269621 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -39,39 +39,6 @@
</el-form-item>
</el-col>
</el-row>
- <!-- 瀹℃壒浜洪�夋嫨锛堝姩鎬佽妭鐐癸級 -->
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="鐢宠浜猴細" prop="approveUser">
- <el-select
- v-model="form.approveUser"
- placeholder="閫夋嫨浜哄憳"
- disabled
- >
- <el-option
- v-for="user in userList"
- :key="user.userId"
- :label="user.nickName"
- :value="user.userId"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鐢宠鏃ユ湡锛�" prop="approveTime">
- <el-date-picker
- v-model="form.approveTime"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- style="width: 100%"
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
</el-form>
<!-- 鎶ヤ环瀹℃壒锛氬睍绀烘姤浠疯鎯咃紙澶嶇敤閿�鍞姤浠�"鏌ョ湅璇︽儏瀵硅瘽妗�"鍐呭缁撴瀯锛� -->
@@ -228,7 +195,6 @@
updateApproveNode
} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user.js";
-import {userListNoPageByTenantId} from "@/api/system/user.js";
import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue'
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js";
@@ -248,7 +214,6 @@
const formRef = ref(null);
const userStore = useUserStore()
const productOptions = ref([]);
-const userList = ref([])
const quotationLoading = ref(false)
const currentQuotation = ref({})
const purchaseLoading = ref(false)
@@ -258,9 +223,7 @@
const data = reactive({
form: {
- approveTime: "",
approveId: "",
- approveUser: "",
approveDeptId: "",
approveReason: "",
checkResult: "",
@@ -295,9 +258,6 @@
dialogFormVisible.value = true;
currentQuotation.value = {}
currentPurchase.value = {}
- userListNoPageByTenantId().then((res) => {
- userList.value = res.data;
- });
form.value = {...row}
// 绔嬪嵆娓呴櫎琛ㄥ崟楠岃瘉鐘舵�侊紙鍥犱负瀛楁鏄痙isabled鐨勶紝涓嶉渶瑕侀獙璇侊級
nextTick(() => {
diff --git a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
index 998fa33..6461b2d 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -97,96 +97,11 @@
</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: 30px; text-align: center; margin-bottom: 10px;"
- >
- <div>
- <span>瀹℃壒浜�</span>
- 鈫�
- </div>
- <el-select
- v-model="node.userId"
- placeholder="閫夋嫨浜哄憳"
- style="width: 120px; 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="approveUser">
- <el-select
- v-model="form.approveUser"
- placeholder="閫夋嫨浜哄憳"
- filterable
- default-first-option
- :reserve-keyword="false"
- >
- <el-option
- v-for="user in userList"
- :key="user.userId"
- :label="user.nickName"
- :value="user.userId"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鐢宠鏃ユ湡锛�" prop="approveTime">
- <el-date-picker
- v-model="form.approveTime"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- style="width: 100%"
- />
- </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>
+ <FileUpload v-model:file-list="fileList" />
</el-form-item>
</el-col>
</el-row>
@@ -211,13 +126,11 @@
import {
delLedgerFile,
} from "@/api/salesManagement/salesLedger.js";
-import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getToken } from "@/utils/auth";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
import useUserStore from "@/store/modules/user";
-import { getCurrentDate } from "@/utils/index.js";
-import log from "@/views/monitor/job/log.vue";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
const userStore = useUserStore();
const dialogFormVisible = ref(false);
@@ -231,24 +144,18 @@
});
const data = reactive({
form: {
- approveTime: "",
approveId: "",
- approveUser: "",
- approveDeptId: "",
+ approveDeptId: "",
approveDeptName: "",
approveReason: "",
checkResult: "",
- tempFileIds: [],
- approverList: [], // 鏂板瀛楁锛屽瓨鍌ㄦ墍鏈夎妭鐐圭殑瀹℃壒浜篿d
startDate: "", // 璇峰亣寮�濮嬫椂闂�
endDate: "", // 璇峰亣缁撴潫鏃堕棿
price: null, // 鎶ラ攢閲戦
location: "" // 鍑哄樊鍦扮偣
},
rules: {
- approveTime: [{ required: false, message: "璇疯緭鍏�", trigger: "change" },],
approveId: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
- approveUser: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
approveDeptName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
approveReason: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
checkResult: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
@@ -268,18 +175,7 @@
}
})
-// 瀹℃壒浜鸿妭鐐圭浉鍏�
-const approverNodes = ref([
- { id: 1, userId: null }
-])
-let nextApproverId = 2
-const userList = ref([])
-function addApproverNode() {
- approverNodes.value.push({ id: nextApproverId++, userId: null })
-}
-function removeApproverNode(index) {
- approverNodes.value.splice(index, 1)
-}
+
// 澶勭悊閮ㄩ棬閫夋嫨鍙樺寲
const handleDeptChange = (deptId) => {
if (deptId) {
@@ -295,39 +191,20 @@
const openDialog = (type, row) => {
operationType.value = type;
dialogFormVisible.value = true;
- userListNoPageByTenantId().then((res) => {
- userList.value = res.data;
- });
- form.value = {}
- approverNodes.value = [
- { id: 1, userId: null }
- ]
- form.value.approveUser = userStore.id;
- form.value.approveTime = getCurrentDate();
-
+ form.value = {}
+
// 鑾峰彇褰撳墠鐢ㄦ埛淇℃伅骞惰缃儴闂↖D
form.value.approveDeptId = userStore.currentDeptId
-
+
// 鍔犺浇閮ㄩ棬閫夐」锛屽苟鍦ㄥ姞杞藉畬鎴愬悗璁剧疆閮ㄩ棬鍚嶇О
getProductOptions();
if (operationType.value === 'edit') {
fileList.value = row.commonFileList
form.value.tempFileIds = fileList.value.map(file => file.id)
- currentApproveStatus.value = row.approveStatus
+ currentApproveStatus.value = row.approveStatus
approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => {
- form.value = {...res.data}
- // 鍙嶆樉瀹℃壒浜�
- if (res.data && res.data.approveUserIds) {
- const userIds = res.data.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
- }
+ form.value = {...res.data}
+ fileList.value = res.data.storageBlobVOS
})
}
}
@@ -336,8 +213,8 @@
productOptions.value = res.data;
// 濡傛灉宸叉湁閮ㄩ棬ID锛岃嚜鍔ㄨ缃儴闂ㄥ悕绉帮紙鐢ㄤ簬楠岃瘉锛�
if (form.value.approveDeptId && productOptions.value.length > 0) {
- const matchedDept = productOptions.value.find(dept =>
- dept.deptId == form.value.approveDeptId ||
+ const matchedDept = productOptions.value.find(dept =>
+ dept.deptId == form.value.approveDeptId ||
String(dept.deptId) === String(form.value.approveDeptId)
);
if (matchedDept) {
@@ -356,21 +233,13 @@
if (children && children.length > 0) {
newItem.children = convertIdToValue(children);
}
-
+
return newItem;
});
}
// 鎻愪氦浜у搧琛ㄥ崟
const submitForm = () => {
- // 鏀堕泦鎵�鏈夎妭鐐圭殑瀹℃壒浜篿d
- form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
form.value.approveType = props.approveType
- // 瀹℃壒浜哄繀濉牎楠�
- const hasEmptyApprover = approverNodes.value.some(node => !node.userId)
- if (hasEmptyApprover) {
- proxy.$modal.msgError("璇蜂负鎵�鏈夊鎵硅妭鐐归�夋嫨瀹℃壒浜猴紒")
- return
- }
// 褰� approveType 涓� 2 鏃讹紝鏍¢獙璇峰亣鏃堕棿
if (props.approveType == 2) {
if (!form.value.startDate) {
@@ -401,6 +270,8 @@
return
}
}
+ form.value.storageBlobDTOList = fileList.value
+
proxy.$refs.formRef.validate(valid => {
if (valid) {
if (operationType.value === "add" || currentApproveStatus.value == 3) {
@@ -424,47 +295,6 @@
dialogFormVisible.value = false;
emit('close')
};
-
-// 涓婁紶鍓嶆牎妫�
-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) {
- // 纭繚 tempFileIds 瀛樺湪涓斾负鏁扮粍
- if (!form.value.tempFileIds) {
- form.value.tempFileIds = [];
- }
- form.value.tempFileIds.push(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("鍒犻櫎鎴愬姛");
- });
- }
-}
defineExpose({
openDialog,
diff --git a/src/views/collaborativeApproval/approvalProcess/fileList.vue b/src/views/collaborativeApproval/approvalProcess/fileList.vue
index 5cc65f1..498f474 100644
--- a/src/views/collaborativeApproval/approvalProcess/fileList.vue
+++ b/src/views/collaborativeApproval/approvalProcess/fileList.vue
@@ -4,9 +4,9 @@
<el-table-column label="闄勪欢鍚嶇О" prop="name" min-width="400" show-overflow-tooltip />
<el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
<template #default="scope">
- <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">涓嬭浇</el-button>
- <el-button link type="primary" size="small" @click="lookFile(scope.row)">棰勮</el-button>
- <el-button link type="danger" size="small" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ <el-button link type="primary" @click="downLoadFile(scope.row)">涓嬭浇</el-button>
+ <el-button link type="primary" @click="lookFile(scope.row)">棰勮</el-button>
+ <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/collaborativeApproval/approvalProcess/index.vue b/src/views/collaborativeApproval/approvalProcess/index.vue
index 33bde47..dba6bc1 100644
--- a/src/views/collaborativeApproval/approvalProcess/index.vue
+++ b/src/views/collaborativeApproval/approvalProcess/index.vue
@@ -1,67 +1,129 @@
<template>
<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="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="5"></el-tab-pane>
- <el-tab-pane label="鎶ヤ环瀹℃壒" name="6"></el-tab-pane>
- <el-tab-pane label="鍙戣揣瀹℃壒" name="7"></el-tab-pane>
- </el-tabs>
-
- <div class="search_form">
- <div>
- <span class="search_title">娴佺▼缂栧彿锛�</span>
- <el-input
- v-model="searchForm.approveId"
- 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="0" />
- <el-option label="瀹℃牳涓�" :value="1" />
- <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')"
- v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7"
- >鏂板</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button
- type="danger"
- plain
- @click="handleDelete"
- v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7"
- >鍒犻櫎</el-button>
+ <!-- 瀹℃壒绫诲瀷鍒囨崲 - 绱у噾鏍囩寮� -->
+ <div class="type-tabs">
+ <div
+ v-for="type in approveTypes"
+ :key="type.value"
+ class="type-tab"
+ :class="{ active: activeTab === type.value }"
+ @click="activeTab = type.value; handleTabChange()"
+ >
+ <el-icon :size="14" :style="{ color: activeTab === type.value ? type.color : '#909399' }">
+ <component :is="type.icon" />
+ </el-icon>
+ <span class="tab-name">{{ type.label }}</span>
</div>
</div>
- <div class="table_list">
+
+ <!-- 鎼滅储鍜屾搷浣滃尯鍩� -->
+ <el-card class="search-card" shadow="never">
+ <div class="search-content">
+ <div class="search-filters">
+ <div class="filter-item">
+ <span class="filter-label">娴佺▼缂栧彿</span>
+ <el-input
+ v-model="searchForm.approveId"
+ placeholder="璇疯緭鍏ユ祦绋嬬紪鍙�"
+ clearable
+ :prefix-icon="Search"
+ @keyup.enter="handleQuery"
+ class="search-input"
+ />
+ </div>
+ <div class="filter-item">
+ <span class="filter-label">瀹℃壒鐘舵��</span>
+ <el-select
+ v-model="searchForm.approveStatus"
+ clearable
+ @change="handleQuery"
+ placeholder="璇烽�夋嫨鐘舵��"
+ class="search-select"
+ >
+ <el-option label="寰呭鏍�" :value="0">
+ <el-tag size="small" type="warning">寰呭鏍�</el-tag>
+ </el-option>
+ <el-option label="瀹℃牳涓�" :value="1">
+ <el-tag size="small" type="primary">瀹℃牳涓�</el-tag>
+ </el-option>
+ <el-option label="瀹℃牳瀹屾垚" :value="2">
+ <el-tag size="small" type="success">瀹℃牳瀹屾垚</el-tag>
+ </el-option>
+ <el-option label="瀹℃牳鏈�氳繃" :value="3">
+ <el-tag size="small" type="danger">瀹℃牳鏈�氳繃</el-tag>
+ </el-option>
+ <el-option label="宸查噸鏂版彁浜�" :value="4">
+ <el-tag size="small" type="info">宸查噸鏂版彁浜�</el-tag>
+ </el-option>
+ </el-select>
+ </div>
+ <el-button type="primary" @click="handleQuery" class="search-btn">
+ <el-icon><Search /></el-icon>
+ 鎼滅储
+ </el-button>
+ <el-button @click="resetQuery" class="reset-btn">
+ <el-icon><RefreshRight /></el-icon>
+ 閲嶇疆
+ </el-button>
+ </div>
+ <div class="search-actions">
+ <el-button
+ type="primary"
+ @click="openForm('add')"
+ v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7"
+ class="action-btn primary"
+ >
+ <el-icon><Plus /></el-icon>
+ 鏂板
+ </el-button>
+ <el-button @click="handleOut" class="action-btn">
+ <el-icon><Download /></el-icon>
+ 瀵煎嚭
+ </el-button>
+ <el-button
+ type="danger"
+ plain
+ @click="handleDelete"
+ v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7"
+ class="action-btn danger"
+ >
+ <el-icon><Delete /></el-icon>
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ </el-card>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-card class="table-card" shadow="never" v-loading="tableLoading">
+ <template #header>
+ <div class="table-header">
+ <div class="table-title">
+ <div class="type-tag" :style="{ backgroundColor: currentTypeInfo.color }">
+ <el-icon color="#fff" :size="16"><component :is="currentTypeInfo.icon" /></el-icon>
+ </div>
+ <span>{{ currentTypeInfo.label }}鍒楄〃</span>
+ <el-tag type="info" size="small" effect="plain" class="count-tag">
+ 鍏� {{ page.total }} 鏉�
+ </el-tag>
+ </div>
+ </div>
+ </template>
<PIMTable
- rowKey="id"
- :column="tableColumnCopy"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- :total="page.total"
+ rowKey="id"
+ :column="tableColumnCopy"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ class="custom-table"
></PIMTable>
- </div>
+ </el-card>
+
+ <!-- 寮圭獥缁勪欢 -->
<info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia>
<approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia>
<FileList ref="fileListRef" />
@@ -70,7 +132,7 @@
<script setup>
import FileList from "./fileList.vue";
-import { Search } from "@element-plus/icons-vue";
+import { Search, Plus, Delete, Download, RefreshRight, DocumentChecked } from "@element-plus/icons-vue";
import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import { useRoute } from 'vue-router';
@@ -85,13 +147,37 @@
// 褰撳墠閫変腑鐨勬爣绛鹃〉锛岄粯璁や负鍏嚭绠$悊
const activeTab = ref('1');
+// 鍚勭被鍨嬫暟閲忕粺璁�
+const typeCounts = ref({});
+
+// 瀹℃壒绫诲瀷閰嶇疆
+const approveTypes = [
+ { value: '1', label: '鍏嚭绠$悊', icon: 'Suitcase', color: '#409EFF' },
+ { value: '2', label: '璇峰亣绠$悊', icon: 'Calendar', color: '#67C23A' },
+ { value: '3', label: '鍑哄樊绠$悊', icon: 'Location', color: '#E6A23C' },
+ { value: '4', label: '鎶ラ攢绠$悊', icon: 'Money', color: '#F56C6C' },
+ { value: '5', label: '閲囪喘瀹℃壒', icon: 'ShoppingCart', color: '#909399' },
+ { value: '6', label: '鎶ヤ环瀹℃壒', icon: 'DocumentChecked', color: '#9B59B6' },
+ { value: '7', label: '鍙戣揣瀹℃壒', icon: 'Van', color: '#1ABC9C' },
+];
+
+// 褰撳墠瀹℃壒绫诲瀷淇℃伅
+const currentTypeInfo = computed(() => {
+ return approveTypes.find(t => t.value === activeTab.value) || approveTypes[0];
+});
+
+// 鑾峰彇绫诲瀷鏁伴噺
+const getTypeCount = (value) => {
+ return typeCounts.value[value] || 0;
+};
+
// 褰撳墠瀹℃壒绫诲瀷锛屾牴鎹�変腑鐨勬爣绛鹃〉璁$畻
const currentApproveType = computed(() => {
return Number(activeTab.value);
});
// 鏍囩椤靛垏鎹㈠鐞�
-const handleTabChange = (tabName) => {
+const handleTabChange = () => {
// 鍒囨崲鏍囩椤垫椂閲嶇疆鎼滅储鏉′欢鍜屽垎椤碉紝骞堕噸鏂板姞杞芥暟鎹�
searchForm.value.approveId = '';
searchForm.value.approveStatus = '';
@@ -102,11 +188,18 @@
const data = reactive({
searchForm: {
- approveId: "",
- approveStatus: "",
+ approveId: "",
+ approveStatus: "",
},
});
const { searchForm } = toRefs(data);
+
+// 閲嶇疆鎼滅储
+const resetQuery = () => {
+ searchForm.value.approveId = '';
+ searchForm.value.approveStatus = '';
+ handleQuery();
+};
// 鍔ㄦ�佽〃鏍煎垪閰嶇疆锛屾牴鎹鎵圭被鍨嬬敓鎴愬垪
const tableColumnCopy = computed(() => {
@@ -238,7 +331,7 @@
},
];
- // 鎶ヤ环瀹℃壒锛堢被鍨� 6锛変笉灞曠ず鈥滈檮浠垛�濇搷浣�
+ // 鎶ヤ环瀹℃壒锛堢被鍨� 6锛変笉灞曠ず"闄勪欢"鎿嶄綔
if (!isQuotationType) {
actionOperations.push({
name: "闄勪欢",
@@ -294,6 +387,8 @@
tableLoading.value = false;
tableData.value = res.data.records
page.total = res.data.total;
+ // 鏇存柊褰撳墠绫诲瀷鏁伴噺
+ typeCounts.value[activeTab.value] = res.data.total;
}).catch(err => {
tableLoading.value = false;
})
@@ -388,7 +483,256 @@
</script>
<style scoped>
-.approval-tabs {
- margin-bottom: 10px;
+.page-header {
+ margin-bottom: 20px;
+}
+
+.header-title {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.title-icon {
+ font-size: 28px;
+ color: var(--el-color-primary, #409EFF);
+}
+
+.header-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.main-title {
+ font-size: 20px;
+ font-weight: 600;
+ color: var(--el-text-color-primary, #303133);
+}
+
+.sub-title {
+ font-size: 13px;
+ color: var(--el-text-color-secondary, #909399);
+}
+
+/* 瀹℃壒绫诲瀷鍒囨崲 - 绱у噾鏍囩寮� */
+.type-tabs {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 16px;
+ padding: 4px;
+ background: var(--el-fill-color-light, #f5f7fa);
+ border-radius: 8px;
+ overflow-x: auto;
+}
+
+.type-tab {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+ font-size: 13px;
+ color: var(--el-text-color-regular, #606266);
+}
+
+.type-tab:hover {
+ background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1));
+ color: var(--el-color-primary, #409EFF);
+}
+
+.type-tab.active {
+ background: var(--el-bg-color, #fff);
+ color: var(--el-color-primary, #409EFF);
+ font-weight: 600;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.tab-name {
+ font-size: 13px;
+}
+
+.tab-count {
+ min-width: 16px;
+ height: 16px;
+ padding: 0 5px;
+ background: var(--el-color-success, #67C23A);
+ color: #fff;
+ border-radius: 8px;
+ font-size: 11px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* 鎼滅储鍗$墖 */
+.search-card {
+ margin-bottom: 16px;
+ border-radius: 12px;
+}
+
+:deep(.el-card__body) {
+ padding: 20px;
+}
+
+.search-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 16px;
+}
+
+.search-filters {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+
+.filter-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.filter-label {
+ font-size: 14px;
+ color: var(--el-text-color-regular, #606266);
+ font-weight: 500;
+ white-space: nowrap;
+}
+
+.search-input,
+.search-select {
+ width: 200px;
+}
+
+.search-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.reset-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.search-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.action-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.action-btn.primary {
+ background: var(--el-color-primary, #409EFF);
+ border: none;
+}
+
+.action-btn.danger {
+ transition: all 0.3s;
+}
+
+.action-btn.danger:hover {
+ background: #f56c6c;
+ color: #fff;
+}
+
+/* 琛ㄦ牸鍗$墖 */
+.table-card {
+ border-radius: 12px;
+}
+
+:deep(.el-card__header) {
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
+}
+
+.table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.table-title {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--el-text-color-primary, #303133);
+}
+
+.type-tag {
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.count-tag {
+ margin-left: 8px;
+}
+
+.custom-table {
+ margin-top: 8px;
+}
+
+/* 鍝嶅簲寮� */
+@media (max-width: 1200px) {
+ .search-content {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .search-filters {
+ justify-content: flex-start;
+ }
+
+ .search-actions {
+ justify-content: flex-end;
+ }
+}
+
+@media (max-width: 768px) {
+ .type-tabs {
+ padding: 3px;
+ }
+
+ .type-tab {
+ padding: 6px 10px;
+ font-size: 12px;
+ }
+
+ .tab-name {
+ font-size: 12px;
+ }
+
+ .search-filters {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .filter-item {
+ width: 100%;
+ }
+
+ .search-input,
+ .search-select {
+ width: 100%;
+ }
}
</style>
diff --git a/src/views/collaborativeApproval/attendanceManagement/index.vue b/src/views/collaborativeApproval/attendanceManagement/index.vue
index f6a3e3c..1f5f956 100644
--- a/src/views/collaborativeApproval/attendanceManagement/index.vue
+++ b/src/views/collaborativeApproval/attendanceManagement/index.vue
@@ -303,8 +303,8 @@
<template #footer>
<span class="dialog-footer">
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
<el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
</span>
</template>
</el-dialog>
diff --git a/src/views/collaborativeApproval/customerVisit/index.vue b/src/views/collaborativeApproval/customerVisit/index.vue
index 3122f3f..68396eb 100644
--- a/src/views/collaborativeApproval/customerVisit/index.vue
+++ b/src/views/collaborativeApproval/customerVisit/index.vue
@@ -45,7 +45,7 @@
<el-table-column label="鎷滆浜�" prop="visitingPeople" width="120" show-overflow-tooltip />
<el-table-column fixed="right" label="鎿嶄綔" width="100" align="center">
<template #default="scope">
- <el-button link type="primary" size="small" @click="viewDetail(scope.row)">鏌ョ湅</el-button>
+ <el-button link type="primary" size="small" @click="viewDetail(scope.row)" style="color: #67C23A">鏌ョ湅</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/collaborativeApproval/enterpriseBook/index.vue b/src/views/collaborativeApproval/enterpriseBook/index.vue
index fa9fa5c..8239811 100644
--- a/src/views/collaborativeApproval/enterpriseBook/index.vue
+++ b/src/views/collaborativeApproval/enterpriseBook/index.vue
@@ -275,8 +275,8 @@
</el-form-item>
</el-form>
<template #footer>
- <el-button @click="showAddContactDialog = false">鍙栨秷</el-button>
<el-button type="primary" @click="addContact">纭畾</el-button>
+ <el-button @click="showAddContactDialog = false">鍙栨秷</el-button>
</template>
</el-dialog>
</div>
diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue
index bdaf0b4..43fee33 100644
--- a/src/views/collaborativeApproval/knowledgeBase/index.vue
+++ b/src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form" style="margin-bottom: 20px;">
<div>
<span class="search_title">鐭ヨ瘑鏍囬锛�</span>
<el-input
@@ -382,7 +382,7 @@
}
},
{
- name: "鏌ョ湅",
+ name: "璇︽儏",
type: "text",
clickFun: (row) => {
viewKnowledge(row);
diff --git a/src/views/collaborativeApproval/meetingBoard/index.vue b/src/views/collaborativeApproval/meetingBoard/index.vue
index dfbb922..ebedd1f 100644
--- a/src/views/collaborativeApproval/meetingBoard/index.vue
+++ b/src/views/collaborativeApproval/meetingBoard/index.vue
@@ -83,9 +83,7 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
-import { ElMessage } from 'element-plus'
import { Clock, Location, User, UserFilled } from '@element-plus/icons-vue'
-import Editor from "@/components/Editor/index.vue";
import {getMeetSummaryItems,getMeetSummary} from '@/api/collaborativeApproval/meeting.js'
import dayjs from "dayjs";
diff --git a/src/views/collaborativeApproval/noticeManagement/index.vue b/src/views/collaborativeApproval/noticeManagement/index.vue
index 0957882..a36b950 100644
--- a/src/views/collaborativeApproval/noticeManagement/index.vue
+++ b/src/views/collaborativeApproval/noticeManagement/index.vue
@@ -210,7 +210,6 @@
v-if="scope.row.editing"
link
type="primary"
- size="small"
@click="handleSaveNoticeType(scope.row)"
>
淇濆瓨
@@ -219,7 +218,6 @@
v-if="scope.row.editing"
link
type="info"
- size="small"
@click="handleCancelEdit(scope.row)"
>
鍙栨秷
@@ -228,7 +226,6 @@
v-if="!scope.row.editing"
link
type="primary"
- size="small"
@click="handleEditNoticeType(scope.row)"
>
缂栬緫
@@ -237,7 +234,6 @@
v-if="!scope.row.editing"
link
type="danger"
- size="small"
@click="handleDeleteNoticeType(scope.row)"
>
鍒犻櫎
@@ -933,7 +929,7 @@
}
.dialog-footer {
- text-align: right;
+ text-align: center;
}
.notice-type-container {
diff --git a/src/views/collaborativeApproval/notificationManagement/index.vue b/src/views/collaborativeApproval/notificationManagement/index.vue
index fb3f7a8..fa02f47 100644
--- a/src/views/collaborativeApproval/notificationManagement/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/index.vue
@@ -131,8 +131,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
<el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
</span>
</template>
</el-dialog>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue b/src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue
index ad4931a..636fd79 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue
@@ -93,8 +93,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button @click="cancel">鍙� 娑�</el-button>
<el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+ <el-button @click="cancel">鍙� 娑�</el-button>
</div>
</template>
</el-dialog>
@@ -313,8 +313,6 @@
}
.dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
+ text-align: center;
}
</style>
diff --git a/src/views/collaborativeApproval/notificationManagement/summary/index.vue b/src/views/collaborativeApproval/notificationManagement/summary/index.vue
index 2b26723..bf6e230 100644
--- a/src/views/collaborativeApproval/notificationManagement/summary/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -146,8 +146,8 @@
<template #footer>
<div class="dialog-footer">
- <el-button @click="minutesDialogVisible = false">鍙� 娑�</el-button>
<el-button type="primary" @click="submitMinutes">淇� 瀛�</el-button>
+ <el-button @click="minutesDialogVisible = false">鍙� 娑�</el-button>
</div>
</template>
</el-dialog>
@@ -367,9 +367,7 @@
}
.dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
+ text-align: center;
}
.content-section h4 {
diff --git a/src/views/collaborativeApproval/planTemplate/index.vue b/src/views/collaborativeApproval/planTemplate/index.vue
index 0af6d8b..fa2bfd9 100644
--- a/src/views/collaborativeApproval/planTemplate/index.vue
+++ b/src/views/collaborativeApproval/planTemplate/index.vue
@@ -123,7 +123,7 @@
</div>
<div class="plan-actions">
<el-button size="small" @click="handleEditPlan(plan)">缂栬緫</el-button>
- <el-button size="small" @click="handleViewDetail(plan)">璇︽儏</el-button>
+ <el-button size="small" @click="handleViewDetail(plan)" style="color: #67C23A">璇︽儏</el-button>
<el-dropdown @command="(command) => handleMoreAction(plan, command)">
<el-button size="small">
鏇村<el-icon class="el-icon--right"><ArrowDown /></el-icon>
diff --git a/src/views/collaborativeApproval/processTracking/index.vue b/src/views/collaborativeApproval/processTracking/index.vue
index 67f5106..197a438 100644
--- a/src/views/collaborativeApproval/processTracking/index.vue
+++ b/src/views/collaborativeApproval/processTracking/index.vue
@@ -59,7 +59,7 @@
<el-table-column label="鎿嶄綔" width="150">
<template #default="{ row }">
<el-button type="text" @click="updateStatus(row)">鏇存柊鐘舵��</el-button>
- <el-button type="text" @click="viewDetails(row)">璇︽儏</el-button>
+ <el-button type="text" @click="viewDetails(row)" style="color: #67C23A">璇︽儏</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/collaborativeApproval/purchaseApproval/index.vue b/src/views/collaborativeApproval/purchaseApproval/index.vue
index af17bbe..fe90686 100644
--- a/src/views/collaborativeApproval/purchaseApproval/index.vue
+++ b/src/views/collaborativeApproval/purchaseApproval/index.vue
@@ -2,678 +2,771 @@
<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>
- <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 @click="handleOut">瀵煎嚭</el-button>
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ <el-button type="danger"
+ plain
+ @click="handleDelete">鍒犻櫎</el-button>
</div>
- <el-table
- :data="tableData"
- border
- v-loading="tableLoading"
- @selection-change="handleSelectionChange"
- :expand-row-keys="expandedRowKeys"
- :row-key="(row) => row.id"
- show-summary
- :summary-method="summarizeMainTable"
- @expand-change="expandChange"
- height="calc(100vh - 18.5em)"
- :row-class-name="tableRowClassName"
- >
- <el-table-column align="center" type="selection" width="55" />
+ <el-table :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.id"
+ show-summary
+ :summary-method="summarizeMainTable"
+ @expand-change="expandChange"
+ height="calc(100vh - 18.5em)"
+ :row-class-name="tableRowClassName">
+ <el-table-column align="center"
+ type="selection"
+ width="55" />
<el-table-column type="expand">
<template #default="props">
- <el-table
- :data="props.row.children"
- border
- show-summary
- :summary-method="summarizeChildrenTable"
- >
- <el-table-column
- align="center"
- label="搴忓彿"
- type="index"
- width="60"
- />
- <el-table-column label="浜у搧澶х被" prop="productCategory" />
- <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
- <el-table-column label="鍗曚綅" prop="unit" />
- <el-table-column label="鏁伴噺" prop="quantity" />
- <el-table-column label="绋庣巼(%)" prop="taxRate" />
- <el-table-column
- label="鍚◣鍗曚环(鍏�)"
- prop="taxInclusiveUnitPrice"
- :formatter="formattedNumber"
- />
- <el-table-column
- label="鍚◣鎬讳环(鍏�)"
- prop="taxInclusiveTotalPrice"
- :formatter="formattedNumber"
- />
- <el-table-column
- label="涓嶅惈绋庢�讳环(鍏�)"
- prop="taxExclusiveTotalPrice"
- :formatter="formattedNumber"
- />
+ <el-table :data="props.row.children"
+ border
+ show-summary
+ :summary-method="summarizeChildrenTable">
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="浜у搧澶х被"
+ prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿"
+ prop="specificationModel" />
+ <el-table-column label="鍗曚綅"
+ prop="unit" />
+ <el-table-column label="鏁伴噺"
+ prop="quantity" />
+ <el-table-column label="绋庣巼(%)"
+ prop="taxRate" />
+ <el-table-column label="鍚◣鍗曚环(鍏�)"
+ prop="taxInclusiveUnitPrice"
+ :formatter="formattedNumber" />
+ <el-table-column label="鍚◣鎬讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ :formatter="formattedNumber" />
+ <el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
+ prop="taxExclusiveTotalPrice"
+ :formatter="formattedNumber" />
</el-table>
</template>
</el-table-column>
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
- <el-table-column
- label="閲囪喘鍚堝悓鍙�"
- prop="purchaseContractNumber"
- width="200"
- show-overflow-tooltip
- />
- <el-table-column
- label="閿�鍞悎鍚屽彿"
- prop="salesContractNo"
- width="200"
- show-overflow-tooltip
- />
- <el-table-column
- label="渚涘簲鍟嗗悕绉�"
- width="240"
- prop="supplierName"
- show-overflow-tooltip
- />
- <el-table-column label="璁㈠崟鐘舵��" width="100" align="center">
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="閲囪喘鍚堝悓鍙�"
+ prop="purchaseContractNumber"
+ width="200"
+ show-overflow-tooltip />
+ <el-table-column label="閿�鍞悎鍚屽彿"
+ prop="salesContractNo"
+ width="200"
+ show-overflow-tooltip />
+ <el-table-column label="渚涘簲鍟嗗悕绉�"
+ width="240"
+ prop="supplierName"
+ show-overflow-tooltip />
+ <el-table-column label="璁㈠崟鐘舵��"
+ width="100"
+ align="center">
<template #default="scope">
- <el-tag v-if="scope.row.isInvalid" type="danger" size="small">澶辨晥</el-tag>
- <el-tag v-else type="success" size="small">姝e父</el-tag>
+ <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
- >
+ <el-table-column label="椤圭洰鍚嶇О"
+ prop="projectName"
+ width="420"
+ show-overflow-tooltip />
+ <el-table-column label="瀹℃壒鐘舵��"
+ prop="approvalStatus"
+ width="200"
+ show-overflow-tooltip>
<template #default="scope">
- <el-tag
- size="small"
- >
+ <el-tag size="small">
{{ approvalStatusText[scope.row.approvalStatus] || '鏈煡鐘舵��' }}
</el-tag>
</template>
</el-table-column>
- <el-table-column
- label="浠樻鏂瑰紡"
- width="100"
- prop="paymentMethod"
- show-overflow-tooltip
- />
- <el-table-column
- label="鍚堝悓閲戦(鍏�)"
- prop="contractAmount"
- width="200"
- show-overflow-tooltip
- :formatter="formattedNumber"
- />
- <el-table-column
- label="褰曞叆浜�"
- prop="recorderName"
- width="100"
- show-overflow-tooltip
- />
- <el-table-column
- label="褰曞叆鏃ユ湡"
- prop="entryDate"
- width="100"
- show-overflow-tooltip
- />
- <el-table-column
- fixed="right"
- label="鎿嶄綔"
- min-width="150"
- align="center"
- >
+ <el-table-column label="浠樻鏂瑰紡"
+ width="100"
+ prop="paymentMethod"
+ show-overflow-tooltip />
+ <el-table-column label="鍚堝悓閲戦(鍏�)"
+ prop="contractAmount"
+ width="200"
+ show-overflow-tooltip
+ :formatter="formattedNumber" />
+ <el-table-column label="褰曞叆浜�"
+ prop="recorderName"
+ width="100"
+ show-overflow-tooltip />
+ <el-table-column label="褰曞叆鏃ユ湡"
+ prop="entryDate"
+ width="100"
+ show-overflow-tooltip />
+ <el-table-column fixed="right"
+ label="鎿嶄綔"
+ min-width="150"
+ align="center">
<template #default="scope">
- <el-button
- link
- type="primary"
- size="small"
- @click="approvePurchase(scope.row)"
- :disabled="scope.row.approvalStatus !== 0"
- >瀹℃壒</el-button
- >
- <el-button
- link
- type="primary"
- size="small"
- @click="rejectPurchase(scope.row)"
- :disabled="scope.row.approvalStatus !== 0"
- >鎷掔粷瀹℃壒</el-button
- >
+ <el-button link
+ type="primary"
+ size="small"
+ @click="approvePurchase(scope.row)"
+ :disabled="scope.row.approvalStatus !== 0">瀹℃壒</el-button>
+ <el-button link
+ type="primary"
+ size="small"
+ @click="rejectPurchase(scope.row)"
+ :disabled="scope.row.approvalStatus !== 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"
- />
+ <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 { 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 {
- getSalesLedgerWithProducts,
- addOrUpdateSalesLedgerProduct,
- delProduct,
- delLedgerFile,
- getProductInfoByContractNo,
-} from "@/api/salesManagement/salesLedger.js";
-import {
- addOrEditPurchase,
- delPurchase,
- getSalesNo,
- purchaseListPage,
- productList,
- getPurchaseById,
- getOptions,
- createPurchaseNo, updateApprovalStatus,
-} from "@/api/procurementManagement/procurementLedger.js";
-import useFormData from "@/hooks/useFormData.js";
-import QRCode from "qrcode";
+ 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 {
+ getSalesLedgerWithProducts,
+ addOrUpdateSalesLedgerProduct,
+ delProduct,
+ delLedgerFile,
+ getProductInfoByContractNo,
+ } from "@/api/salesManagement/salesLedger.js";
+ import {
+ addOrEditPurchase,
+ delPurchase,
+ getSalesNo,
+ purchaseListPage,
+ productList,
+ getPurchaseById,
+ getOptions,
+ createPurchaseNo,
+ updateApprovalStatus,
+ } 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 { getCurrentDate } from "@/utils/index.js";
-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 { getCurrentDate } from "@/utils/index.js";
+ const userStore = useUserStore();
-const userStore = useUserStore();
+ // 浜岀淮鐮佺浉鍏冲彉閲�
+ const qrCodeDialogVisible = ref(false);
+ const qrCodeUrl = ref("");
-// 浜岀淮鐮佺浉鍏冲彉閲�
-const qrCodeDialogVisible = ref(false);
-const qrCodeUrl = ref("");
+ // 璁㈠崟瀹℃壒鐘舵�佹樉绀烘枃鏈�
+ const approvalStatusText = {
+ 0: "寰呭鎵�",
+ 1: "瀹℃壒閫氳繃",
+ 2: "瀹℃壒澶辫触",
+ };
-// 璁㈠崟瀹℃壒鐘舵�佹樉绀烘枃鏈�
-const approvalStatusText = {
- 0: '寰呭鎵�',
- 1: '瀹℃壒閫氳繃',
- 2: '瀹℃壒澶辫触'
-};
+ // 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+ const 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: "",
+ approvalStatus: "0",
+ },
+ rules: {
+ purchaseContractNumber: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ projectName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+ });
+ const { form, rules } = toRefs(data);
+ const { form: searchForm } = useFormData(data.searchForm);
-// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
-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: "",
- approvalStatus: "0",
- },
- rules: {
- purchaseContractNumber: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- projectName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- supplierId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- },
-});
-const { form, rules } = toRefs(data);
-const { form: searchForm } = useFormData(data.searchForm);
+ // 浜у搧琛ㄥ崟寮规鏁版嵁
+ const productFormVisible = ref(false);
+ const productOperationType = ref("");
+ const productOperationIndex = ref("");
+ const currentId = ref("");
+ const productFormData = reactive({
+ productForm: {
+ productId: "",
+ productCategory: "",
+ productModelId: "",
+ specificationModel: "",
+ unit: "",
+ quantity: "",
+ taxInclusiveUnitPrice: "",
+ taxRate: "",
+ taxInclusiveTotalPrice: "",
+ taxExclusiveTotalPrice: "",
+ invoiceType: "",
+ warnNum: "",
+ },
+ productRules: {
+ productId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxInclusiveUnitPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ warnNum: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
+ taxInclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ taxExclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+ });
+ const { productForm, productRules } = toRefs(productFormData);
+ // const upload = reactive({
+ // // 涓婁紶鐨勫湴鍧�
+ // url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
+ // // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ // headers: { Authorization: "Bearer " + getToken() },
+ // });
-// 浜у搧琛ㄥ崟寮规鏁版嵁
-const productFormVisible = ref(false);
-const productOperationType = ref("");
-const productOperationIndex = ref("");
-const currentId = ref("");
-const productFormData = reactive({
- productForm: {
- productId: "",
- productCategory: "",
- productModelId: "",
- specificationModel: "",
- unit: "",
- quantity: "",
- taxInclusiveUnitPrice: "",
- taxRate: "",
- taxInclusiveTotalPrice: "",
- taxExclusiveTotalPrice: "",
- invoiceType: "",
- warnNum: "",
- },
- productRules: {
- productId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- taxInclusiveUnitPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- warnNum: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
- taxInclusiveTotalPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- taxExclusiveTotalPrice: [
- { required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- },
-});
-const { productForm, productRules } = toRefs(productFormData);
-const upload = reactive({
- // 涓婁紶鐨勫湴鍧�
- url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
- // 璁剧疆涓婁紶鐨勮姹傚ご閮�
- headers: { Authorization: "Bearer " + getToken() },
-});
+ const changeDaterange = value => {
+ if (value) {
+ searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ } else {
+ searchForm.entryDateStart = undefined;
+ searchForm.entryDateEnd = undefined;
+ }
+ handleQuery();
+ };
-const 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 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.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 = [];
+ 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;
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ } 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 = res.data.records.map(record => ({
- ...record,
- isInvalid: record.isWhite === 1
- }));
- tableData.value.map((item) => {
- item.children = [];
+ ]);
+ };
+ // 鎵撳紑寮规
+ 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;
});
- total.value = res.data.total;
- expandedRowKeys.value = [];
- })
- .catch(() => {
- tableLoading.value = false;
+ }
+ userListNoPage().then(res => {
+ userList.value = res.data;
});
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-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 = [];
- 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;
+ getSalesNo().then(res => {
+ salesContractList.value = res;
+ });
+ getOptions().then(res => {
+ // 渚涘簲鍟嗚繃婊ゅ嚭isWhite=0 鐨勬暟鎹�
+ supplierList.value = res.data.filter(item => item.isWhite == 0);
+ });
+ 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 = [];
}
- expandedRowKeys.value.push(row.id);
});
- } catch (error) {
- console.log(error);
}
- } else {
- expandedRowKeys.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;
}
-};
-// 涓昏〃鍚堣鏂规硶
-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;
- });
+ // 涓婁紶澶辫触
+ function handleUploadError(err) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢澶辫触");
+ proxy.$modal.closeLoading();
}
- userListNoPage().then((res) => {
- userList.value = res.data;
- });
- getSalesNo().then((res) => {
- salesContractList.value = res;
- });
- getOptions().then((res) => {
- // 渚涘簲鍟嗚繃婊ゅ嚭isWhite=0 鐨勬暟鎹�
- supplierList.value = res.data.filter((item) => item.isWhite == 0);
- });
- 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 = [];
+ // 涓婁紶鎴愬姛鍥炶皟
+ 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 = n => {
+ 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;
+ form.value.approvalStatus = n;
+ addOrEditPurchase(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
}
});
- }
- 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 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 submitForm = (n) => {
- proxy.$refs["formRef"].validate((valid) => {
- if (valid) {
- if (productData.value.length > 0) {
- form.value.productData = proxy.HaveJson(productData.value);
- } else {
- proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
+ // 鎻愪氦浜у搧琛ㄥ崟
+ 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 approvePurchase = row => {
+ ElMessageBox.confirm(
+ `纭閫氳繃閲囪喘鍚堝悓鍙蜂负 ${row.purchaseContractNumber} 鐨勫鎵癸紵`,
+ "瀹℃壒纭",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ )
+ .then(() => {
+ updateApprovalStatus({ id: row.id, approvalStatus: 1 }).then(res => {
+ proxy.$modal.msgSuccess("瀹℃壒鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑堝鎵�");
+ });
+ };
+
+ // 瀹℃壒鎷掔粷鏂规硶
+ const rejectPurchase = row => {
+ ElMessageBox.confirm(
+ `纭鎷掔粷閲囪喘鍚堝悓鍙蜂负 ${row.purchaseContractNumber} 鐨勫鎵癸紵`,
+ "瀹℃壒纭",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ )
+ .then(() => {
+ updateApprovalStatus({ id: row.id, approvalStatus: 2 }).then(res => {
+ proxy.$modal.msgSuccess("瀹℃壒鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑堝鎵�");
+ });
+ };
+
+ // 瀵煎嚭
+ 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;
}
- let tempFileIds = [];
- if (fileList.value.length > 0) {
- tempFileIds = fileList.value.map((item) => item.tempId);
- }
- form.value.tempFileIds = tempFileIds;
- form.value.type = 2;
- form.value.approvalStatus = n;
- addOrEditPurchase(form.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- });
- }
- });
-};
-// 鍏抽棴寮规
-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);
+ ids = selectedRows.value.map(item => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
}
ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
confirmButtonText: "纭",
@@ -681,384 +774,313 @@
type: "warning",
})
.then(() => {
- delProduct(ids).then((res) => {
+ delPurchase(ids).then(res => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- closeProductDia();
- getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then(
- (res) => {
- productData.value = res.productData;
- }
- );
+ getList();
});
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
});
- }
-};
-// 鍏抽棴浜у搧寮规
-const closeProductDia = () => {
- proxy.resetForm("productFormRef");
- productFormVisible.value = false;
-};
-// 瀹℃壒閫氳繃鏂规硶
-const approvePurchase = (row) => {
- ElMessageBox.confirm(`纭閫氳繃閲囪喘鍚堝悓鍙蜂负 ${row.purchaseContractNumber} 鐨勫鎵癸紵`, '瀹℃壒纭', {
- confirmButtonText: '纭',
- cancelButtonText: '鍙栨秷',
- type: 'warning',
- }).then(() => {
- updateApprovalStatus({ id: row.id, approvalStatus: 1}).then((res)=>{
- proxy.$modal.msgSuccess('瀹℃壒鎴愬姛');
- getList();
- })
- }).catch(() => {
- proxy.$modal.msg('宸插彇娑堝鎵�');
- });
-};
-
-// 瀹℃壒鎷掔粷鏂规硶
-const rejectPurchase = (row) => {
- ElMessageBox.confirm(`纭鎷掔粷閲囪喘鍚堝悓鍙蜂负 ${row.purchaseContractNumber} 鐨勫鎵癸紵`, '瀹℃壒纭', {
- confirmButtonText: '纭',
- cancelButtonText: '鍙栨秷',
- type: 'warning',
- }).then(() => {
- updateApprovalStatus({ id: row.id, approvalStatus: 2}).then((res)=>{
- proxy.$modal.msgSuccess('瀹℃壒鎴愬姛');
- getList();
- })
- }).catch(() => {
- proxy.$modal.msg('宸插彇娑堝鎵�');
- });
-};
-
-// 瀵煎嚭
-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("宸插彇娑�");
- });
-};
-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 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") {
+ // 宸茬煡鍚◣鎬讳环鍜屾暟閲忥紝鍙嶇畻鍚◣鍗曚环
+ 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 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 >= 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();
}
});
- qrCodeDialogVisible.value = true;
- } catch (error) {
- console.error('鐢熸垚浜岀淮鐮佸け璐�:', error);
- proxy.$modal.msgError("鐢熸垚浜岀淮鐮佸け璐ワ細" + error.message);
+ };
+
+ // 鎵撳紑鎵爜鐧昏瀵硅瘽妗�
+ 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 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 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();
- }
+ onMounted(() => {
+ getList();
});
-};
-
-// 鎵撳紑鎵爜鐧昏瀵硅瘽妗�
-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 tableRowClassName = ({ row }) => {
- return row.isInvalid ? 'invalid-row' : '';
-};
-
-onMounted(() => {
- getList();
-});
</script>
<style scoped lang="scss">
-.invalid-row {
- opacity: 0.6;
- background-color: #f5f7fa;
-}
+ .invalid-row {
+ opacity: 0.6;
+ background-color: #f5f7fa;
+ }
</style>
diff --git a/src/views/collaborativeApproval/rpaManagement/index.vue b/src/views/collaborativeApproval/rpaManagement/index.vue
index 9e5b504..c734b28 100644
--- a/src/views/collaborativeApproval/rpaManagement/index.vue
+++ b/src/views/collaborativeApproval/rpaManagement/index.vue
@@ -80,8 +80,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
<el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
</span>
</template>
</el-dialog>
diff --git a/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue b/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
index ba8e46d..f7ba9d9 100644
--- a/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
+++ b/src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -126,9 +126,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
+ <el-button type="primary" @click="submitRegulation">鍙戝竷鍒跺害</el-button>
<el-button @click="showRegulationDialog = false">鍙栨秷</el-button>
- <el-button type="primary"
- @click="submitRegulation">鍙戝竷鍒跺害</el-button>
</span>
</template>
</el-dialog>
@@ -213,14 +212,7 @@
</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"/>
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="rules_regulations_management" :record-id="recordId" />
</div>
</template>
@@ -236,7 +228,7 @@
addReadingStatus,
updateReadingStatus,
} from "@/api/collaborativeApproval/sealManagement.js";
- import FileListDialog from "@/components/Dialog/FileListDialog.vue";
+ const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
import {
listRuleFiles,
delRuleFile,
@@ -255,14 +247,7 @@
total: 0,
});
// 闄勪欢寮圭獥
- const fileDialogVisible = ref(false);
- const fileListDialogRef = ref(null);
const currentFileRuleId = ref(null);
- const filePage = reactive({
- current: 1,
- size: 1000,
- total: 0,
- });
// 瑙勭珷鍒跺害鐩稿叧
const showRegulationDialog = ref(false);
const showRegulationDetailDialog = ref(false);
@@ -341,10 +326,10 @@
fixed: "right",
align: "center",
operation: [
- { name: "鏌ョ湅", clickFun: (row) => viewRegulation(row) },
{ name: "缂栬緫", clickFun: (row) => handleEdit(row) },
{ name: "搴熷純", clickFun: (row) => repealEdit(row) },
{ name: "鐗堟湰鍘嗗彶", clickFun: (row) => viewVersionHistory(row) },
+ { name: "璇︽儏", clickFun: (row) => viewRegulation(row) },
{ name: "闄勪欢", clickFun: (row) => openFileDialog(row) },
],
},
@@ -565,63 +550,15 @@
);
};
- // 闄勪欢锛氭煡璇�
- 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 recordId =ref(0)
+ const fileDialogVisible = ref(false)
- // 鍒锋柊闄勪欢鍒楄〃
- 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 openFileDialog = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
+ }
// 鑾峰彇瑙勭珷鍒跺害鍒楄〃鏁版嵁
const getRegulationList = async () => {
@@ -687,8 +624,6 @@
}
.dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
+ text-align: center;
}
</style>
diff --git a/src/views/collaborativeApproval/sealManagement/index.vue b/src/views/collaborativeApproval/sealManagement/index.vue
index c0d13f2..a6232c2 100644
--- a/src/views/collaborativeApproval/sealManagement/index.vue
+++ b/src/views/collaborativeApproval/sealManagement/index.vue
@@ -236,7 +236,6 @@
fixed: 'right',
align: 'center',
operation: [
- { name: '鏌ョ湅', clickFun: (row) => viewSealDetail(row) },
{
name: '瀹℃壒',
clickFun: (row) => approveSeal(row),
@@ -246,7 +245,8 @@
name: '鎷掔粷',
clickFun: (row) => rejectSeal(row),
showHide: (row) => row.status === 'pending'
- }
+ },
+ { name: '璇︽儏', clickFun: (row) => viewSealDetail(row) }
]
}
])
diff --git a/src/views/customerService/afterSalesHandling/index.vue b/src/views/customerService/afterSalesHandling/index.vue
index 57cc2eb..a42337d 100644
--- a/src/views/customerService/afterSalesHandling/index.vue
+++ b/src/views/customerService/afterSalesHandling/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search-wrapper">
+ <div class="search-wrapper mb20">
<el-form
:model="searchForm"
class="demo-form-inline"
@@ -102,33 +102,19 @@
></PIMTable>
</div>
<form-dia ref="formDia" @close="handleQuery"></form-dia>
- <FileListDialog
- ref="fileListRef"
- v-model="fileListDialogVisible"
- title="鍞悗闄勪欢"
- :show-upload-button="true"
- :show-delete-button="true"
- :upload-method="handleFileUpload"
- :delete-method="handleFileDelete"
- />
- </div>
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="after_sales_service" :record-id="recordId" />
+ </div>
</template>
<script setup>
-import { onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
+import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, defineAsyncComponent} from "vue";
import FormDia from "@/views/customerService/afterSalesHandling/components/formDia.vue";
-import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import { ElMessageBox } from "element-plus";
-import request from "@/utils/request";
-import { getToken } from "@/utils/auth";
import {
afterSalesServiceListPage,
- afterSalesServiceFileListPage,
- afterSalesServiceFileDel,
} from "@/api/customerService/index.js";
-import useUserStore from "@/store/modules/user.js";
const { proxy } = getCurrentInstance();
-const userStore = useUserStore()
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
const data = reactive({
searchForm: {
@@ -303,144 +289,15 @@
})
}
+
+// 鎵撳紑闄勪欢寮圭獥
+const recordId =ref(0)
+const fileDialogVisible = ref(false)
+
// 鎵撳紑闄勪欢寮规
const openFilesFormDia = async (row) => {
- currentFileRow.value = row
- try {
- const res = await afterSalesServiceFileListPage({
- afterSalesServiceId: row.id,
- current: 1,
- size: 100,
- })
- if (res.code === 200 && fileListRef.value) {
- const fileList = (res.data?.records || []).map((item) => ({
- name: item.name || item.fileName,
- url: item.url || item.fileUrl,
- id: item.id,
- ...item,
- }))
- fileListRef.value.open(fileList)
- fileListDialogVisible.value = true
- } else {
- fileListRef.value?.open([])
- fileListDialogVisible.value = true
- }
- } catch (error) {
- proxy.$modal.msgError("鑾峰彇闄勪欢鍒楄〃澶辫触")
- fileListRef.value?.open([])
- fileListDialogVisible.value = true
- }
-}
-
-// 涓婁紶闄勪欢
-const handleFileUpload = async () => {
- if (!currentFileRow.value) {
- proxy.$modal.msgWarning("璇峰厛閫夋嫨鏁版嵁")
- return
- }
- 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 {
- const formData = new FormData()
- formData.append("file", file)
- formData.append("id", currentFileRow.value.id)
- const uploadRes = await request({
- url: "/afterSalesService/file/upload",
- method: "post",
- data: formData,
- headers: {
- "Content-Type": "multipart/form-data",
- Authorization: `Bearer ${getToken()}`,
- },
- })
- if (uploadRes.code === 200) {
- proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛")
- // 閲嶆柊鑾峰彇鏂囦欢鍒楄〃
- const listRes = await afterSalesServiceFileListPage({
- afterSalesServiceId: currentFileRow.value.id,
- current: 1,
- size: 100,
- })
- if (listRes.code === 200 && fileListRef.value) {
- const fileList = (listRes.data?.records || []).map((item) => ({
- name: item.fileName,
- url: item.fileUrl,
- id: item.id,
- ...item,
- }))
- fileListRef.value.setList(fileList)
- }
- resolve({ name: file.name, url: "", id: null })
- } else {
- proxy.$modal.msgError(uploadRes.msg || "鏂囦欢涓婁紶澶辫触")
- resolve(null)
- }
- } catch (err) {
- proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触")
- resolve(null)
- } finally {
- document.body.removeChild(input)
- }
- }
- document.body.appendChild(input)
- input.click()
- })
-}
-
-// 鍒犻櫎闄勪欢
-const handleFileDelete = async (row) => {
- try {
- // 娣诲姞纭瀵硅瘽妗�
- const confirmResult = await ElMessageBox.confirm(
- '纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�',
- '鍒犻櫎纭',
- {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }
- )
-
- if (confirmResult === 'confirm') {
- const res = await afterSalesServiceFileDel(row.id)
- if (res.code === 200) {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
- if (currentFileRow.value && fileListRef.value) {
- const listRes = await afterSalesServiceFileListPage({
- afterSalesServiceId: currentFileRow.value.id,
- current: 1,
- size: 100,
- })
- if (listRes.code === 200) {
- const fileList = (listRes.data?.records || []).map((item) => ({
- name: item.fileName,
- url: item.fileUrl,
- id: item.id,
- ...item,
- }))
- fileListRef.value.setList(fileList)
- }
- }
- } else {
- proxy.$modal.msgError(res.msg || "鍒犻櫎澶辫触")
- return false
- }
- }
- } catch (error) {
- // 濡傛灉鐢ㄦ埛鍙栨秷鍒犻櫎锛屼笉鏄剧ず閿欒淇℃伅
- if (error !== 'cancel') {
- proxy.$modal.msgError("鍒犻櫎澶辫触")
- }
- return false
- }
+ recordId.value = row.id
+ fileDialogVisible.value = true
}
// 鏌ヨ鍒楄〃
diff --git a/src/views/customerService/expiryAfterSales/index.vue b/src/views/customerService/expiryAfterSales/index.vue
index ee5395d..c94e3dd 100644
--- a/src/views/customerService/expiryAfterSales/index.vue
+++ b/src/views/customerService/expiryAfterSales/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">涓存湡鏃ユ湡锛�</span>
<el-date-picker
@@ -34,11 +34,12 @@
>閲嶇疆</el-button
>
</div>
+ <div class="table_actions">
+ <el-button type="primary" @click="openForm('add')">鏂板</el-button>
+ <el-button type="danger" @click="handleDelete">鍒犻櫎</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"
diff --git a/src/views/customerService/feedbackRegistration/components/formDia.vue b/src/views/customerService/feedbackRegistration/components/formDia.vue
index 71cd167..5c9e565 100644
--- a/src/views/customerService/feedbackRegistration/components/formDia.vue
+++ b/src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -79,9 +79,9 @@
</el-form-item>
</el-col>
<el-col :span="4">
- <el-form-item label="闂鎻忚堪锛�" prop="disRes">
+ <el-form-item label="闂鎻忚堪锛�" prop="proDesc">
<el-input
- v-model="form.disRes"
+ v-model="form.proDesc"
placeholder="璇疯緭鍏ラ棶棰樻弿杩�"
/>
</el-form-item>
@@ -106,6 +106,11 @@
:column="tableColumn"
:tableData="tableData"
>
+ <template #approveStatus="{ row }">
+ <el-tag :type="getApproveStatusType(row)" size="small">
+ {{ getApproveStatusText(row) }}
+ </el-tag>
+ </template>
<template #shippingStatus="{ row }">
<el-tag :type="getShippingStatusType(row)" size="small">
{{ getShippingStatusText(row) }}
@@ -155,7 +160,7 @@
productModelIds: "",
customerId: null,
salesContractNo: "",
- disRes: "",
+ proDesc: "",
customerName: ""
},
rules: {
@@ -207,6 +212,7 @@
taxInclusiveUnitPrice: row?.taxInclusiveUnitPrice ?? 0,
taxInclusiveTotalPrice: row?.taxInclusiveTotalPrice ?? 0,
taxExclusiveTotalPrice: row?.taxExclusiveTotalPrice ?? 0,
+ noQuantity: row?.noQuantity ?? 0,
}
}
@@ -219,9 +225,8 @@
prop: "approveStatus",
width: 100,
align: "center",
- dataType: "tag",
- formatData: (v) => (v === 1 ? "鍏呰冻" : "涓嶈冻"),
- formatType: (v) => (v === 1 ? "success" : "danger"),
+ dataType: "slot",
+ slot: "approveStatus",
},
{
label: "鍙戣揣鐘舵��",
@@ -304,9 +309,15 @@
})
const customerNameChange = (val) => {
+ form.value.salesContractNo = "";
+ form.value.salesLedgerId = null;
+ tableData.value = [];
+ associatedSalesOrderNumberOptions.value = [];
const opt = customerNameOptions.value.find(item => item.value === val);
if (opt) {
form.value.customerId = opt.id;
+ } else {
+ form.value.customerId = null;
}
getSalesLedger({
customerName: form.value.customerName
@@ -320,6 +331,22 @@
}))
}
})
+}
+
+const getApproveStatusText = (row) => {
+ if (!row) return '涓嶈冻'
+ if (row.approveStatus === 1 && (!row.shippingDate || !row.shippingCarNumber)) {
+ return '鍏呰冻'
+ }
+ if (row.approveStatus === 0 && (row.shippingDate || row.shippingCarNumber)) {
+ return '宸插嚭搴�'
+ }
+ return '涓嶈冻'
+}
+
+const getApproveStatusType = (row) => {
+ const statusText = getApproveStatusText(row)
+ return statusText === '涓嶈冻' ? 'danger' : 'success'
}
const getShippingStatusText = (row) => {
@@ -365,7 +392,11 @@
// 鎵撳紑寮规
const openDialog =async (type, row) => {
// 璇锋眰澶氫釜鎺ュ彛锛岃幏鍙栨暟鎹�
- let res = await getAllCustomerList();
+ let res = await getAllCustomerList({
+ current: 1,
+ size: 1000,
+ total: 0,
+ });
if(res.records){
customerNameOptions.value = res.records.map(item => ({
label: item.customerName,
diff --git a/src/views/customerService/feedbackRegistration/index.vue b/src/views/customerService/feedbackRegistration/index.vue
index 3a2d362..40d99be 100644
--- a/src/views/customerService/feedbackRegistration/index.vue
+++ b/src/views/customerService/feedbackRegistration/index.vue
@@ -255,7 +255,7 @@
},
{
label: "闂鎻忚堪",
- prop: "disRes",
+ prop: "proDesc",
width:300,
},
{
@@ -267,7 +267,6 @@
{
dataType: "action",
label: "鎿嶄綔",
- align: "center",
fixed: 'right',
operation: [
{
@@ -404,15 +403,19 @@
});
};
+const getStatsCountByStatus = (list, status) => {
+ if (!Array.isArray(list)) return 0;
+ return list.find((item) => item?.status === status)?.count || 0;
+};
+
// 鑾峰彇缁熻鏁版嵁骞跺埛鏂伴《閮ㄥ崱鐗�
const getSalesLedgerDetails = () => {
getSalesLedgerDetail({}).then((res) => {
if (res.code === 200) {
- statsList.value[0].count = res.data.filter((item) => item.status === 3)[0].count;
- statsList.value[1].count = res.data.filter((item) => item.status === 2)[0].count;
- statsList.value[2].count = res.data.filter((item) => item.status === 1)[0].count;
-
- // });
+ const statsData = Array.isArray(res.data) ? res.data : [];
+ statsList.value[0].count = getStatsCountByStatus(statsData, 3);
+ statsList.value[1].count = getStatsCountByStatus(statsData, 2);
+ statsList.value[2].count = getStatsCountByStatus(statsData, 1);
}
});
}
@@ -491,7 +494,6 @@
.table_list {
height: calc(100vh - 380px);
- min-height: 360px;
background: #fff;
margin-top: 20px;
display: flex;
diff --git a/src/views/energyManagement/dynamicEnergySaving/index.vue b/src/views/energyManagement/dynamicEnergySaving/index.vue
index b641276..8976b22 100644
--- a/src/views/energyManagement/dynamicEnergySaving/index.vue
+++ b/src/views/energyManagement/dynamicEnergySaving/index.vue
@@ -157,14 +157,12 @@
<el-table-column prop="lastUpdate" label="鏈�鍚庢洿鏂�" />
<el-table-column label="鎿嶄綔">
<template #default="scope">
- <el-button
- size="small"
+ <el-button
@click="updateModel(scope.row)"
>
鏇存柊妯″瀷
</el-button>
- <el-button
- size="small"
+ <el-button
type="danger"
@click="deleteModel(scope.row)"
>
diff --git a/src/views/equipmentManagement/brand/index.vue b/src/views/equipmentManagement/brand/index.vue
index 6607cc8..f93518a 100644
--- a/src/views/equipmentManagement/brand/index.vue
+++ b/src/views/equipmentManagement/brand/index.vue
@@ -60,8 +60,8 @@
</el-form-item>
</el-form>
<template #footer>
- <el-button @click="visible = false">鍙栨秷</el-button>
<el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ <el-button @click="visible = false">鍙栨秷</el-button>
</template>
</el-dialog>
</div>
diff --git a/src/views/equipmentManagement/calibration/index.vue b/src/views/equipmentManagement/calibration/index.vue
index e3eaef6..b04da2e 100644
--- a/src/views/equipmentManagement/calibration/index.vue
+++ b/src/views/equipmentManagement/calibration/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">妫�瀹氭棩鏈燂細</span>
<el-date-picker
diff --git a/src/views/equipmentManagement/defectManagement/index.vue b/src/views/equipmentManagement/defectManagement/index.vue
index f35454f..8673000 100644
--- a/src/views/equipmentManagement/defectManagement/index.vue
+++ b/src/views/equipmentManagement/defectManagement/index.vue
@@ -65,8 +65,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
- <el-button @click="showRegisterDialog = false">鍙栨秷</el-button>
<el-button type="primary" @click="submitDefectForm">纭畾</el-button>
+ <el-button @click="showRegisterDialog = false">鍙栨秷</el-button>
</span>
</template>
</el-dialog>
diff --git a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
index b048a9c..9f509b1 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -91,8 +91,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button @click="cancel">鍙栨秷</el-button>
<el-button type="primary" @click="submitForm">淇濆瓨</el-button>
+ <el-button @click="cancel">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
diff --git a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
index 27b4a59..66867e3 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -134,40 +134,6 @@
const currentMediaIndex = ref(0);
const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
const mediaType = ref('image'); // image | video
-const javaApi = proxy.javaApi;
-
-// 澶勭悊 URL锛氬皢 Windows 璺緞杞崲涓哄彲璁块棶鐨� URL
-function processFileUrl(fileUrl) {
- if (!fileUrl) return '';
-
- // 濡傛灉 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;
- } else {
- // 濡傛灉娌℃湁鎵惧埌 uploads锛屾彁鍙栨渶鍚庝竴涓洰褰曞拰鏂囦欢鍚�
- const parts = fileUrl.split('\\');
- const fileName = parts[parts.length - 1];
- fileUrl = '/uploads/' + fileName;
- }
- }
-
- // 纭繚鎵�鏈夐潪 http 寮�澶寸殑 URL 閮芥嫾鎺� baseUrl
- if (fileUrl && !fileUrl.startsWith('http')) {
- // 纭繚璺緞浠� / 寮�澶�
- if (!fileUrl.startsWith('/')) {
- fileUrl = '/' + fileUrl;
- }
- // 鎷兼帴 baseUrl
- fileUrl = javaApi + fileUrl;
- }
-
- return fileUrl;
-}
// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
function processItems(items) {
@@ -180,24 +146,18 @@
}
items.forEach(item => {
- if (!item || !item.url) return;
+ if (!item || !item.previewURL || !item.contentType) return;
+
// 澶勭悊鏂囦欢 URL
- const fileUrl = processFileUrl(item.url);
-
- // 鏍规嵁鏂囦欢鎵╁睍鍚嶅垽鏂槸鍥剧墖杩樻槸瑙嗛
- const urlLower = fileUrl.toLowerCase();
- if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) {
+ const fileUrl = item.previewURL;
+ const contentType = String(item.contentType).toLowerCase();
+
+ // 鏍规嵁 contentType 鍒ゆ柇鏄浘鐗囪繕鏄棰�
+ if (contentType.startsWith('image/')) {
images.push(fileUrl);
- } else if (urlLower.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/)) {
+ } else if (contentType.startsWith('video/')) {
videos.push(fileUrl);
- } else if (item.contentType) {
- // 濡傛灉鏈� contentType锛屼娇鐢� contentType 鍒ゆ柇
- if (item.contentType.startsWith('image/')) {
- images.push(fileUrl);
- } else if (item.contentType.startsWith('video/')) {
- videos.push(fileUrl);
- }
}
});
@@ -207,10 +167,9 @@
// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
const openDialog = async (row) => {
// 浣跨敤姝g‘鐨勫瓧娈靛悕锛歝ommonFileListBefore, commonFileListAfter
- // productionIssues 鍙兘涓嶅瓨鍦紝浣跨敤绌烘暟缁�
- const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBefore || []);
- const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfter || []);
- const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues || []);
+ const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBeforeVO || []);
+ const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfterVO || []);
+ const { images: issueImgs, videos: issueVids } = processItems(row.commonFileListVO || []);
beforeProductionImgs.value = beforeImgs;
beforeProductionVideos.value = beforeVids;
diff --git a/src/views/equipmentManagement/ledger/Form.vue b/src/views/equipmentManagement/ledger/Form.vue
index 72d594e..45ca248 100644
--- a/src/views/equipmentManagement/ledger/Form.vue
+++ b/src/views/equipmentManagement/ledger/Form.vue
@@ -100,22 +100,18 @@
</el-col>
<el-col :span="12">
<el-form-item label="绋庣巼(%)" prop="taxRate">
- <!-- <el-input
- v-model="form.taxRate"
- placeholder="璇疯緭鍏ョ◣鐜�"
- type="number"
- >
- <template #append> % </template>
- </el-input> -->
<el-select
v-model="form.taxRate"
placeholder="璇烽�夋嫨"
clearable
@change="mathNum"
>
- <el-option label="1" :value="1" />
- <el-option label="6" :value="6" />
- <el-option label="13" :value="13" />
+ <el-option
+ v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="Number(dict.value)"
+ />
</el-select>
</el-form-item>
</el-col>
@@ -174,7 +170,10 @@
calculateTaxExclusiveTotalPrice,
} from "@/utils/summarizeTable";
import { ElMessage } from "element-plus";
-import {ref} from "vue";
+import {ref, getCurrentInstance} from "vue";
+
+const { proxy } = getCurrentInstance();
+const { tax_rate } = proxy.useDict("tax_rate");
defineOptions({
name: "璁惧鍙拌处琛ㄥ崟",
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index 62f0c6a..7ba9401 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -4,7 +4,7 @@
<el-form-item label="璁惧鍚嶇О">
<el-input
v-model="filters.deviceName"
- style="width: 240px"
+ style="width: 200px"
placeholder="璇疯緭鍏ヨ澶囧悕绉�"
clearable
@change="getTableData"
@@ -13,7 +13,7 @@
<el-form-item label="瑙勬牸鍨嬪彿">
<el-input
v-model="filters.deviceModel"
- style="width: 240px"
+ style="width: 200px"
placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
clearable
@change="getTableData"
@@ -22,7 +22,7 @@
<el-form-item label="渚涘簲鍟�">
<el-input
v-model="filters.supplierName"
- style="width: 240px"
+ style="width: 200px"
placeholder="璇疯緭鍏ヤ緵搴斿晢"
clearable
@change="getTableData"
@@ -42,6 +42,7 @@
<div></div>
<div>
<el-button type="primary" @click="add" icon="Plus"> 鏂板 </el-button>
+ <el-button type="info" @click="handleImport" icon="Upload">瀵煎叆</el-button>
<el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
<el-button
type="danger"
@@ -77,6 +78,37 @@
</div>
</div>
</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"
+ :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; margin-left: 5px;" @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>
@@ -84,12 +116,13 @@
import { usePaginationApi } from "@/hooks/usePaginationApi";
// import { Search } from "@element-plus/icons-vue";
import { getLedgerPage, delLedger } from "@/api/equipmentManagement/ledger";
-import { onMounted, getCurrentInstance } from "vue";
+import { onMounted, getCurrentInstance, ref, reactive } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
+import { UploadFilled } from "@element-plus/icons-vue";
+import { getToken } from "@/utils/auth";
import dayjs from "dayjs";
import QRCode from "qrcode";
-import { ref } from "vue";
defineOptions({
name: "璁惧鍙拌处",
@@ -102,6 +135,21 @@
const qrDialogVisible = ref(false);
const qrCodeUrl = ref("");
const qrRowData = ref(null);
+
+// 瀵煎叆鐩稿叧
+const uploadRef = ref(null)
+const upload = reactive({
+ // 鏄惁鏄剧ず寮瑰嚭灞�
+ open: false,
+ // 寮瑰嚭灞傛爣棰�
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import"
+})
const {
filters,
@@ -262,6 +310,36 @@
a.click();
};
+// 瀵煎叆鎸夐挳鎿嶄綔
+const handleImport = () => {
+ upload.title = "璁惧鍙拌处瀵煎叆"
+ upload.open = true
+}
+
+// 涓嬭浇妯℃澘鎿嶄綔
+const importTemplate = () => {
+ proxy.download("/device/ledger/downloadTemplate", {}, `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`)
+}
+
+// 鏂囦欢涓婁紶涓鐞�
+const handleFileUploadProgress = (event, file, fileList) => {
+ 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 })
+ getTableData()
+}
+
+// 鎻愪氦涓婁紶鏂囦欢
+const submitFileForm = () => {
+ proxy.$refs["uploadRef"].submit()
+}
+
onMounted(() => {
getTableData();
});
diff --git a/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue b/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
index b7fa07e..923dd2c 100644
--- a/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
+++ b/src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
@@ -228,39 +228,6 @@
form.value.entryDate = getCurrentDate();
}
-// 涓婁紶鍓嶆牎妫�
-function handleBeforeUpload(file) {
- 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;
- form.value.tempFileIds.push(file.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("鍒犻櫎鎴愬姛");
- });
- }
-}
-
// 澶勭悊鏈夋晥鏃ユ湡杈撳叆锛屽彧鍏佽姝f暣鏁�
const handleValidInput = (value) => {
if (value === '' || value === null || value === undefined) {
diff --git a/src/views/equipmentManagement/measurementEquipment/index.vue b/src/views/equipmentManagement/measurementEquipment/index.vue
index 46ca100..c1d5379 100644
--- a/src/views/equipmentManagement/measurementEquipment/index.vue
+++ b/src/views/equipmentManagement/measurementEquipment/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">褰曞叆鏃ユ湡锛�</span>
<el-date-picker
diff --git a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
index 496b072..b0b09f0 100644
--- a/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -32,23 +32,61 @@
style="width: 100%"
/>
</el-form-item>
+ <el-form-item label="璁惧澶囦欢">
+ <el-select v-model="form.sparePartsIds" :loading="loadingSparePartOptions" placeholder="璇烽�夋嫨璁惧澶囦欢" multiple filterable>
+ <el-option
+ v-for="item in sparePartOptions"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item v-if="selectedSpareParts.length" label="棰嗙敤鏁伴噺">
+ <div style="width: 100%">
+ <div
+ v-for="item in selectedSpareParts"
+ :key="item.id"
+ style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;"
+ >
+ <div style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
+ {{ item.name }}
+ <span v-if="item.quantity !== null && item.quantity !== undefined" style="color: #909399;">
+ 锛堝簱瀛橈細{{ item.quantity }}锛�
+ </span>
+ </div>
+ <el-input-number
+ v-model="sparePartQtyMap[item.id]"
+ :min="1"
+ :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined"
+ :step="1"
+ controls-position="right"
+ style="width: 180px"
+ />
+ </div>
+ </div>
+ </el-form-item>
</el-form>
</FormDialog>
</template>
<script setup>
+import { computed, getCurrentInstance, nextTick, ref } from "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";
+import { getSparePartsList } from "@/api/equipmentManagement/spareParts";
defineOptions({
name: "缁翠慨妯℃�佹",
});
const emits = defineEmits(["ok"]);
+const { proxy } = getCurrentInstance();
// 淇濆瓨鎶ヤ慨璁板綍鐨刬d
const repairId = ref();
@@ -61,6 +99,16 @@
maintenanceResult: undefined, // 缁翠慨缁撴灉
maintenanceTime: undefined, // 缁翠慨鏃ユ湡
status: 0,
+ sparePartsIds: [],
+});
+const sparePartOptions = ref([])
+const loadingSparePartOptions = ref(true)
+const sparePartQtyMap = ref({})
+
+const selectedSpareParts = computed(() => {
+ const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : [];
+ const set = new Set(ids.map((i) => String(i)));
+ return (sparePartOptions.value || []).filter((p) => set.has(String(p.id)));
});
const setForm = (data) => {
@@ -71,16 +119,59 @@
? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss")
: dayjs().format("YYYY-MM-DD HH:mm:ss");
form.status = 1; // 榛樿鐘舵�佷负瀹岀粨
+ // multiple 閫夋嫨鍣ㄨ姹傛暟缁勶紱鍚庣甯歌繑鍥� "1,2,3"
+ if (Array.isArray(data?.sparePartsIds)) {
+ form.sparePartsIds = data.sparePartsIds.map((v) => Number(v)).filter((v) => Number.isFinite(v));
+ } else if (typeof data?.sparePartsIds === "string") {
+ form.sparePartsIds = data.sparePartsIds
+ .split(",")
+ .map((s) => Number(String(s).trim()))
+ .filter((v) => Number.isFinite(v));
+ } else if (typeof data?.sparePartsIds === "number") {
+ form.sparePartsIds = [data.sparePartsIds];
+ } else {
+ form.sparePartsIds = [];
+ }
};
const sendForm = async () => {
loading.value = true;
try {
- const { code } = await addMaintain({ id: repairId.value, ...form });
+ // 棰嗙敤鏁伴噺鏍¢獙
+ if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) {
+ for (const partId of form.sparePartsIds) {
+ const qty = Number(sparePartQtyMap.value?.[partId]);
+ if (!Number.isFinite(qty) || qty <= 0) {
+ proxy?.$modal?.msgError?.("璇峰~鍐欏浠堕鐢ㄦ暟閲�");
+ return;
+ }
+ const part = sparePartOptions.value.find((p) => String(p.id) === String(partId));
+ const stock = part?.quantity;
+ if (stock !== null && stock !== undefined && Number.isFinite(Number(stock))) {
+ if (qty > Number(stock)) {
+ proxy?.$modal?.msgError?.(`澶囦欢銆�${part?.name || ""}銆嶉鐢ㄦ暟閲忎笉鑳借秴杩囧簱瀛橈紙${stock}锛塦);
+ return;
+ }
+ }
+ }
+ }
+ const data = {
+ id: repairId.value,
+ ...form,
+ sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "",
+ sparePartsQty: form.sparePartsIds
+ ? form.sparePartsIds.map((id) => sparePartQtyMap.value?.[id] ?? 1).join(",")
+ : "",
+ sparePartsUseList: form.sparePartsIds
+ ? form.sparePartsIds.map((id) => ({ id, quantity: sparePartQtyMap.value?.[id] ?? 1 }))
+ : [],
+ }
+ const { code } = await addMaintain(data);
if (code == 200) {
ElMessage.success("缁翠慨鎴愬姛");
emits("ok");
resetForm();
+ sparePartQtyMap.value = {};
visible.value = false;
}
} finally {
@@ -88,13 +179,34 @@
}
};
+const fetchSparePartOptions = () => {
+ loadingSparePartOptions.value = true;
+ // 鍜屽浠剁鐞嗛〉涓�鑷达細/spareParts/listPage 鈫� res.data.records
+ getSparePartsList({ current: 1, size: 1000 })
+ .then((res) => {
+ if (res.code === 200) {
+ sparePartOptions.value = res?.data?.records || [];
+ } else {
+ sparePartOptions.value = [];
+ }
+ })
+ .catch(() => {
+ sparePartOptions.value = [];
+ })
+ .finally(() => {
+ loadingSparePartOptions.value = false;
+ });
+}
+
const handleCancel = () => {
resetForm();
+ sparePartQtyMap.value = {};
visible.value = false;
};
const handleClose = () => {
resetForm();
+ sparePartQtyMap.value = {};
visible.value = false;
};
@@ -103,6 +215,7 @@
visible.value = true;
await nextTick();
setForm(row);
+ fetchSparePartOptions()
};
defineExpose({
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 1aa82ec..5e31943 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -48,6 +48,11 @@
<el-input v-model="form.repairName" placeholder="璇疯緭鍏ユ姤淇汉" />
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="椤圭洰">
+ <el-input v-model="form.machineryCategory" placeholder="璇疯緭鍏ラ」鐩�" />
+ </el-form-item>
+ </el-col>
</el-row>
<el-row v-if="id">
<el-col :span="12">
@@ -72,12 +77,20 @@
</el-form-item>
</el-col>
</el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="闄勪欢" prop="attachmentIds">
+ <FileUpload v-model:file-list="form.storageBlobDTOs" />
+ </el-form-item>
+ </el-col>
+ </el-row>
</el-form>
</FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
import {
addRepair,
editRepair,
@@ -101,6 +114,7 @@
const userStore = useUserStore();
const deviceOptions = ref([]);
+const fileList = ref([]);
const loadDeviceName = async () => {
const { data } = await getDeviceLedger();
@@ -115,6 +129,8 @@
repairName: userStore.nickName, // 鎶ヤ慨浜�
remark: undefined, // 鏁呴殰鐜拌薄
status: 0, // 鎶ヤ慨鐘舵��
+ machineryCategory: undefined,
+ storageBlobDTOs: [],
});
const setDeviceModel = (deviceId) => {
@@ -130,6 +146,8 @@
form.repairName = data.repairName;
form.remark = data.remark;
form.status = data.status;
+ form.machineryCategory = data.machineryCategory;
+ form.storageBlobDTOs = data.storageBlobVOs || [];
};
const sendForm = async () => {
@@ -161,6 +179,7 @@
const openAdd = async () => {
id.value = undefined;
visible.value = true;
+ fileList.value = [];
await nextTick();
await loadDeviceName();
};
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index 1e7af53..f3a4330 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -127,22 +127,31 @@
>
鍒犻櫎
</el-button>
+ <el-button
+ type="primary"
+ link
+ @click="openFileDialog(row)"
+ >
+ 闄勪欢
+ </el-button>
</template>
</PIMTable>
</div>
<RepairModal ref="repairModalRef" @ok="getTableData"/>
<MaintainModal ref="maintainModalRef" @ok="getTableData"/>
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" :record-type="'device_repair'" :record-id="recordId" />
</div>
</template>
<script setup>
-import { onMounted, getCurrentInstance, computed } from "vue";
+import {onMounted, getCurrentInstance, computed, ref, defineAsyncComponent} 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 dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
defineOptions({
name: "璁惧鎶ヤ慨",
@@ -186,6 +195,11 @@
label: "瑙勬牸鍨嬪彿",
align: "center",
prop: "deviceModel",
+ },
+ {
+ label: "椤圭洰",
+ align: "center",
+ prop: "machineryCategory",
},
{
label: "鎶ヤ慨鏃ユ湡",
@@ -253,6 +267,15 @@
getTableData();
};
+// 鎵撳紑闄勪欢寮圭獥
+const recordId =ref(0)
+const fileDialogVisible = ref(false)
+
+const openFileDialog = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
+}
+
// 澶氶�夊悗鍋氫粈涔�
const handleSelectionChange = (selectionList) => {
multipleList.value = selectionList;
diff --git a/src/views/equipmentManagement/spareParts/index.vue b/src/views/equipmentManagement/spareParts/index.vue
index 4a48d28..8abe35d 100644
--- a/src/views/equipmentManagement/spareParts/index.vue
+++ b/src/views/equipmentManagement/spareParts/index.vue
@@ -1,107 +1,146 @@
<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>
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane label="澶囦欢鍒楄〃" name="list">
+ <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">
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="renderTableData"
+ :tableLoading="loading"
+ :page="pagination"
+ :isShowPagination="true"
+ @pagination="handleSizeChange"
+ >
+ <template #status="{ row }">
+ <el-tag type="success" size="small">{{ row.status }}</el-tag>
+ </template>
+ </PIMTable>
+ </div>
- <PIMTable
- rowKey="id"
- :column="columns"
- :tableData="renderTableData"
- :tableLoading="loading"
- :page="pagination"
- :isShowPagination="true"
- @pagination="handleSizeChange"
- >
- <template #status="{ row }">
- <el-tag type="success" size="small">{{ row.status }}</el-tag>
- </template>
- </PIMTable>
-
- <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>
+ <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 type="primary" @click="submitForm" :loading="formLoading">纭畾</el-button>
+ <el-button @click="dialogVisible = false" :disabled="formLoading">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </el-tab-pane>
+
+ <el-tab-pane label="澶囦欢棰嗙敤璁板綍" name="usage">
+ <div class="search_form">
+ <el-form :inline="true" :model="usageQuery" class="search-form">
+ <el-form-item label="澶囦欢鍚嶇О">
+ <el-input v-model="usageQuery.sparePartsName" placeholder="璇疯緭鍏ュ浠跺悕绉�" clearable style="width: 240px" />
+ </el-form-item>
+ <el-form-item label="鏉ユ簮">
+ <el-select v-model="usageQuery.sourceType" placeholder="璇烽�夋嫨" clearable style="width: 200px">
+ <el-option label="缁翠慨" :value="0" />
+ <el-option label="淇濆吇" :value="1" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleUsageQuery">鏌ヨ</el-button>
+ <el-button @click="resetUsageQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="rowKey"
+ :column="usageColumns"
+ :tableData="usageTableData"
+ :tableLoading="usageLoading"
+ :page="usagePagination"
+ :isShowPagination="true"
+ @pagination="handleUsagePageChange"
+ />
+ </div>
+ </el-tab-pane>
+ </el-tabs>
</div>
</template>
<script setup>
-import { ref, computed, onMounted, reactive, watch } from 'vue';
+import { ref, computed, onMounted, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
+import { getSparePartsUsagePage } from "@/api/equipmentManagement/sparePartsUsage";
// 鍔犺浇鐘舵��
const loading = ref(false);
const formLoading = ref(false);
+const activeTab = ref("list");
// 瀵硅瘽妗嗘樉绀虹姸鎬�
const dialogVisible = ref(false);
// 缂栬緫 ID
@@ -126,6 +165,35 @@
size: 10,
total: 0
});
+
+// 澶囦欢棰嗙敤璁板綍
+const usageLoading = ref(false);
+const usageQuery = reactive({
+ sparePartsName: "",
+ sourceType: "",
+});
+const usagePagination = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+});
+const usageTableData = ref([]);
+const usageColumns = ref([
+ { label: "鏉ユ簮", prop: "sourceText" },
+ { label: "鍗曟嵁/璁板綍ID", prop: "sourceId" },
+ { label: "璁惧鍚嶇О", prop: "deviceName" },
+ { label: "澶囦欢鍚嶇О", prop: "sparePartsName" },
+ { label: "棰嗙敤鏁伴噺", prop: "quantity" },
+ { label: "鎿嶄綔浜�", prop: "operator" },
+ { label: "鏃堕棿", prop: "createTime" },
+]);
+
+const handleTabChange = async (name) => {
+ if (name === "usage") {
+ usagePagination.current = 1;
+ await fetchUsageData();
+ }
+};
const columns = ref([
{
label: "璁惧鍚嶇О",
@@ -267,6 +335,48 @@
loading.value = false;
}
}
+
+const fetchUsageData = async () => {
+ usageLoading.value = true;
+ try {
+ const res = await getSparePartsUsagePage({
+ current: usagePagination.current,
+ size: usagePagination.size,
+ sparePartsName: usageQuery.sparePartsName || undefined,
+ sourceType: usageQuery.sourceType || undefined,
+ });
+ if (res?.code === 200) {
+ const records = res?.data?.records || [];
+ usagePagination.total = res?.data?.total || 0;
+ usageTableData.value = records.map((r, idx) => ({
+ rowKey: r.id ?? `${usagePagination.current}-${idx}`,
+ ...r,
+ sourceText: r.sourceText === "" ? "-" : r.sourceText,
+ }));
+ } else {
+ usagePagination.total = 0;
+ usageTableData.value = [];
+ }
+ } finally {
+ usageLoading.value = false;
+ }
+};
+
+const handleUsageQuery = () => {
+ usagePagination.current = 1;
+ fetchUsageData();
+};
+const resetUsageQuery = () => {
+ usageQuery.sparePartsName = "";
+ usageQuery.sourceType = "";
+ usagePagination.current = 1;
+ fetchUsageData();
+};
+const handleUsagePageChange = (obj) => {
+ usagePagination.current = obj.page;
+ usagePagination.size = obj.limit;
+ fetchUsageData();
+};
// 鏌ヨ
const handleQuery = () => {
@@ -430,7 +540,6 @@
margin-top: 20px;
display: flex;
justify-content: flex-end;
- padding: 16px 0;
}
.el-table__header-wrapper th {
diff --git a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
index c660840..e86b64a 100644
--- a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
+++ b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
@@ -38,6 +38,41 @@
placeholder="璇疯緭鍏ヤ繚鍏荤粨鏋�"
type="text" />
</el-form-item>
+ <el-form-item label="璁惧澶囦欢">
+ <el-select v-model="form.sparePartsIds" :loading="loadingSparePartOptions" placeholder="璇烽�夋嫨璁惧澶囦欢" multiple filterable>
+ <el-option
+ v-for="item in sparePartOptions"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item v-if="selectedSpareParts.length" label="棰嗙敤鏁伴噺">
+ <div style="width: 100%">
+ <div
+ v-for="item in selectedSpareParts"
+ :key="item.id"
+ style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;"
+ >
+ <div style="flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
+ {{ item.name }}
+ <span v-if="item.quantity !== null && item.quantity !== undefined" style="color: #909399;">
+ 锛堝簱瀛橈細{{ item.quantity }}锛�
+ </span>
+ </div>
+ <el-input-number
+ v-model="sparePartQtyMap[item.id]"
+ :min="1"
+ :max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined"
+ :step="1"
+ controls-position="right"
+ style="width: 180px"
+ />
+ </div>
+ </div>
+ </el-form-item>
</el-form>
</FormDialog>
</template>
@@ -49,6 +84,8 @@
import dayjs from "dayjs";
import useUserStore from "@/store/modules/user";
import { ElMessage } from "element-plus";
+import {computed, ref} from "vue";
+import {getSparePartsList} from "@/api/equipmentManagement/spareParts.js";
defineOptions({
name: "淇濆吇妯℃�佹",
@@ -67,6 +104,17 @@
maintenanceActuallyTime: undefined, // 瀹為檯淇濆吇鏃ユ湡
maintenanceResult: undefined, // 淇濆吇缁撴灉
status: 0, // 淇濆吇鐘舵��
+ sparePartsIds: [],
+});
+
+const sparePartOptions = ref([])
+const loadingSparePartOptions = ref(true)
+const sparePartQtyMap = ref({})
+
+const selectedSpareParts = computed(() => {
+ const ids = Array.isArray(form.sparePartsIds) ? form.sparePartsIds : [];
+ const set = new Set(ids.map((i) => String(i)));
+ return (sparePartOptions.value || []).filter((p) => set.has(String(p.id)));
});
const setForm = (data) => {
@@ -78,6 +126,19 @@
: dayjs().format("YYYY-MM-DD HH:mm:ss");
form.maintenanceResult = data.maintenanceResult;
form.status = 1; // 榛樿鐘舵�佷负瀹岀粨
+ // multiple 閫夋嫨鍣ㄨ姹傛暟缁勶紱鍚庣甯歌繑鍥� "1,2,3"
+ if (Array.isArray(data?.sparePartsIds)) {
+ form.sparePartsIds = data.sparePartsIds.map((v) => Number(v)).filter((v) => Number.isFinite(v));
+ } else if (typeof data?.sparePartsIds === "string") {
+ form.sparePartsIds = data.sparePartsIds
+ .split(",")
+ .map((s) => Number(String(s).trim()))
+ .filter((v) => Number.isFinite(v));
+ } else if (typeof data?.sparePartsIds === "number") {
+ form.sparePartsIds = [data.sparePartsIds];
+ } else {
+ form.sparePartsIds = [];
+ }
};
/**
@@ -86,11 +147,41 @@
const sendForm = async () => {
loading.value = true;
try {
- const { code } = await addMaintenance({ id: planId.value, ...form });
+ // 棰嗙敤鏁伴噺鏍¢獙
+ if (Array.isArray(form.sparePartsIds) && form.sparePartsIds.length > 0) {
+ for (const partId of form.sparePartsIds) {
+ const qty = Number(sparePartQtyMap.value?.[partId]);
+ if (!Number.isFinite(qty) || qty <= 0) {
+ proxy?.$modal?.msgError?.("璇峰~鍐欏浠堕鐢ㄦ暟閲�");
+ return;
+ }
+ const part = sparePartOptions.value.find((p) => String(p.id) === String(partId));
+ const stock = part?.quantity;
+ if (stock !== null && stock !== undefined && Number.isFinite(Number(stock))) {
+ if (qty > Number(stock)) {
+ proxy?.$modal?.msgError?.(`澶囦欢銆�${part?.name || ""}銆嶉鐢ㄦ暟閲忎笉鑳借秴杩囧簱瀛橈紙${stock}锛塦);
+ return;
+ }
+ }
+ }
+ }
+ const data = {
+ id: planId.value,
+ ...form,
+ sparePartsIds: form.sparePartsIds ? form.sparePartsIds.join(",") : "",
+ sparePartsQty: form.sparePartsIds
+ ? form.sparePartsIds.map((id) => sparePartQtyMap.value?.[id] ?? 1).join(",")
+ : "",
+ sparePartsUseList: form.sparePartsIds
+ ? form.sparePartsIds.map((id) => ({ id, quantity: sparePartQtyMap.value?.[id] ?? 1 }))
+ : [],
+ }
+ const { code } = await addMaintenance(data);
if (code == 200) {
ElMessage.success("淇濆吇鎴愬姛");
emits("ok");
resetForm();
+ sparePartQtyMap.value = {};
visible.value = false;
}
} finally {
@@ -98,13 +189,34 @@
}
};
+const fetchSparePartOptions = () => {
+ loadingSparePartOptions.value = true;
+ // 鍜屽浠剁鐞嗛〉涓�鑷达細/spareParts/listPage 鈫� res.data.records
+ getSparePartsList({ current: 1, size: 1000 })
+ .then((res) => {
+ if (res.code === 200) {
+ sparePartOptions.value = res?.data?.records || [];
+ } else {
+ sparePartOptions.value = [];
+ }
+ })
+ .catch(() => {
+ sparePartOptions.value = [];
+ })
+ .finally(() => {
+ loadingSparePartOptions.value = false;
+ });
+}
+
const handleCancel = () => {
resetForm();
+ sparePartQtyMap.value = {};
visible.value = false;
};
const handleClose = () => {
resetForm();
+ sparePartQtyMap.value = {};
visible.value = false;
};
@@ -112,6 +224,7 @@
planId.value = id; // 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
visible.value = true;
await nextTick();
+ fetchSparePartOptions()
setForm(row);
};
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
index 19095b9..ee59ce2 100644
--- a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
+++ b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
@@ -32,6 +32,12 @@
disabled
/>
</el-form-item>
+ <el-form-item label="椤圭洰">
+ <el-input
+ v-model="form.machineryCategory"
+ placeholder="璇疯緭鍏ラ」鐩�"
+ />
+ </el-form-item>
<el-form-item label="褰曞叆浜�">
<el-select
v-model="form.createUser"
@@ -67,6 +73,13 @@
clearable
/>
</el-form-item>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="闄勪欢" prop="attachmentIds">
+ <FileUpload v-model:file-list="form.storageBlobDTOs" />
+ </el-form-item>
+ </el-col>
+ </el-row>
</el-form>
</FormDialog>
</template>
@@ -84,6 +97,7 @@
import { onMounted } from "vue";
import dayjs from "dayjs";
import { userListNoPage } from "@/api/system/user.js";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
defineOptions({
name: "璁惧淇濆吇鏂板璁″垝",
@@ -108,6 +122,8 @@
maintenancePlanTime: undefined, // 璁″垝淇濆吇鏃ユ湡
createUser: undefined, // 褰曞叆浜�
status: 0, //淇濅慨鐘舵��
+ machineryCategory: undefined,
+ storageBlobDTOs: [],
});
const setDeviceModel = (deviceId) => {
@@ -125,9 +141,13 @@
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"
- );
+ form.machineryCategory = data.machineryCategory;
+ if (data.maintenancePlanTime) {
+ form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
+ "YYYY-MM-DD HH:mm:ss"
+ );
+ }
+ form.storageBlobDTOs = data.storageBlobVOs || [];
};
// 鐢ㄦ埛鍒楄〃
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 543e37b..0ee99eb 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -1,694 +1,636 @@
<template>
<div class="app-container">
- <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tabs v-model="activeTab"
+ @tab-change="handleTabChange">
<!-- 瀹氭椂浠诲姟绠$悊tab -->
- <el-tab-pane label="瀹氭椂浠诲姟绠$悊" name="scheduled">
+ <el-tab-pane label="瀹氭椂浠诲姟绠$悊"
+ name="scheduled">
<div class="search_form">
- <el-form :model="scheduledFilters" :inline="true">
+ <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-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 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 type="primary"
+ @click="getScheduledTableData">鎼滅储</el-button>
<el-button @click="resetScheduledFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
</div>
<div class="table_list">
<div class="actions">
- <el-text class="mx-1" size="large">瀹氭椂浠诲姟绠$悊</el-text>
+ <el-text class="mx-1"
+ size="large">瀹氭椂浠诲姟绠$悊</el-text>
<div>
- <el-button type="primary" icon="Plus" @click="addScheduledTask">
+ <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 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="{
+ <PIMTable rowKey="id"
+ isSelection
+ :column="scheduledColumns"
+ :tableData="scheduledDataList"
+ :page="{
current: scheduledPagination.currentPage,
size: scheduledPagination.pageSize,
total: scheduledPagination.total,
}"
- @selection-change="handleScheduledSelectionChange"
- @pagination="changeScheduledPage"
- >
+ @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>
+ <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 type="primary"
+ link
+ @click="editScheduledTask(row)">
缂栬緫
</el-button>
- <el-button
- type="danger"
- link
- @click="delScheduledTaskByIds(row.id)"
- >
+ <el-button type="danger"
+ link
+ @click="delScheduledTaskByIds(row.id)">
鍒犻櫎
</el-button>
</template>
</PIMTable>
</div>
</el-tab-pane>
-
<!-- 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛� -->
- <el-tab-pane label="浠诲姟璁板綍" name="record">
+ <el-tab-pane label="浠诲姟璁板綍"
+ name="record">
<div class="search_form">
- <el-form :model="filters" :inline="true">
+ <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-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-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-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-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 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>
+ <el-text class="mx-1"
+ size="large">浠诲姟璁板綍</el-text>
<div>
- <el-button type="success" icon="Van" @click="addPlan">
+ <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 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"
- :tableData="dataList"
- :page="{
+ <PIMTable rowKey="id"
+ isSelection
+ :column="columns"
+ :tableData="dataList"
+ :page="{
current: pagination.currentPage,
size: pagination.pageSize,
total: pagination.total,
}"
- @selection-change="handleSelectionChange"
- @pagination="changePage"
- >
- <template #maintenanceResultRef="{ row }">
- <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="warning">寰呬繚鍏�</el-tag>
- </template>
- <template #operation="{ row }">
- <!-- 杩欎釜鍔熻兘璺熸柊澧炰繚鍏诲姛鑳戒竴妯′竴鏍凤紝鏈夊暐鎰忎箟锛� -->
- <!-- <el-button
+ @selection-change="handleSelectionChange"
+ @pagination="changePage">
+ <template #maintenanceResultRef="{ row }">
+ <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="warning">寰呬繚鍏�</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <!-- 杩欎釜鍔熻兘璺熸柊澧炰繚鍏诲姛鑳戒竴妯′竴鏍凤紝鏈夊暐鎰忎箟锛� -->
+ <!-- <el-button
type="primary"
text
@click="addMaintain(row)"
>
鏂板淇濆吇
</el-button> -->
- <el-button
- type="primary"
- 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"
- link
- :disabled="row.status === 1"
- @click="delRepairByIds(row.id)"
- >
- 鍒犻櫎
- </el-button>
- <el-button
- type="primary"
- link
- @click="openFileDialog(row)"
- >
- 闄勪欢
- </el-button>
- </template>
- </PIMTable>
+ <el-button type="primary"
+ 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"
+ link
+ :disabled="row.status === 1"
+ @click="delRepairByIds(row.id)">
+ 鍒犻櫎
+ </el-button>
+ <el-button type="primary"
+ link
+ @click="openFileDialog(row)">
+ 闄勪欢
+ </el-button>
+ </template>
+ </PIMTable>
</div>
</el-tab-pane>
</el-tabs>
- <PlanModal ref="planModalRef" @ok="getTableData" />
- <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
- <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" />
- <FileListDialog
- ref="fileListDialogRef"
- v-model="fileDialogVisible"
- :show-upload-button="true"
- :show-delete-button="true"
- :delete-method="handleAttachmentDelete"
- :name-column-label="'闄勪欢鍚嶇О'"
- :rulesRegulationsManagementId="currentMaintenanceTaskId"
- @upload="handleAttachmentUpload" />
+ <PlanModal ref="planModalRef"
+ @ok="getTableData" />
+ <MaintenanceModal ref="maintainModalRef"
+ @ok="getTableData" />
+ <FormDia ref="formDiaRef"
+ @closeDia="getScheduledTableData" />
+ <FileList v-if="fileDialogVisible"
+ v-model:visible="fileDialogVisible"
+ :record-type="'device_maintenance'"
+ :record-id="currentMaintenanceTaskId" />
</div>
</template>
<script setup>
-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 FileListDialog from '@/components/Dialog/FileListDialog.vue'
-import {
- getUpkeepPage,
- delUpkeep,
- deviceMaintenanceTaskList,
- deviceMaintenanceTaskDel,
-} from '@/api/equipmentManagement/upkeep'
-import {
- listMaintenanceTaskFiles,
- addMaintenanceTaskFile,
- delMaintenanceTaskFile,
-} from '@/api/equipmentManagement/maintenanceTaskFile'
-import dayjs from 'dayjs'
+ import {
+ ref,
+ onMounted,
+ reactive,
+ getCurrentInstance,
+ nextTick,
+ computed,
+ defineAsyncComponent,
+ } 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";
-const { proxy } = getCurrentInstance()
+ const { proxy } = getCurrentInstance();
+ const FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+ );
-// Tab鐩稿叧
-const activeTab = ref('scheduled')
+ // Tab鐩稿叧
+ const activeTab = ref("scheduled");
-// 璁″垝寮圭獥鎺у埗鍣�
-const planModalRef = ref()
-// 淇濆吇寮圭獥鎺у埗鍣�
-const maintainModalRef = ref()
-// 瀹氭椂浠诲姟寮圭獥鎺у埗鍣�
-const formDiaRef = ref()
-// 闄勪欢寮圭獥
-const fileListDialogRef = ref(null)
-const fileDialogVisible = ref(false)
-const currentMaintenanceTaskId = ref(null)
+ // 璁″垝寮圭獥鎺у埗鍣�
+ const planModalRef = ref();
+ // 淇濆吇寮圭獥鎺у埗鍣�
+ const maintainModalRef = ref();
+ // 瀹氭椂浠诲姟寮圭獥鎺у埗鍣�
+ const formDiaRef = ref();
+ // 闄勪欢寮圭獥
+ const fileListDialogRef = ref(null);
+ const fileDialogVisible = ref(false);
+ const currentMaintenanceTaskId = ref(null);
-// 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
-const filters = reactive({
- deviceName: '',
- maintenancePlanTime: '',
- maintenanceActuallyTime: '',
- maintenanceActuallyName: '',
-})
+ // 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
+ const filters = reactive({
+ deviceName: "",
+ maintenancePlanTime: "",
+ maintenanceActuallyTime: "",
+ maintenanceActuallyName: "",
+ });
-const dataList = ref([])
-const pagination = ref({
- currentPage: 1,
- pageSize: 10,
- total: 0,
-})
-const multipleList = ref([])
+ const dataList = ref([]);
+ const pagination = ref({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+ });
+ const multipleList = ref([]);
-// 瀹氭椂浠诲姟绠$悊tab鐩稿叧鍙橀噺
-const scheduledFilters = reactive({
- taskName: '',
- status: '',
-})
+ // 瀹氭椂浠诲姟绠$悊tab鐩稿叧鍙橀噺
+ const scheduledFilters = reactive({
+ taskName: "",
+ status: "",
+ });
-const scheduledDataList = ref([])
-const scheduledPagination = reactive({
- currentPage: 1,
- pageSize: 10,
- total: 0,
-})
-const scheduledMultipleList = ref([])
+ 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 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: "350px",
- },
-])
+ // 浠诲姟璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
+ 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: "machineryCategory",
+ },
+ // {
+ // 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: "350px",
+ },
+ ]);
-// Tab鍒囨崲澶勭悊
-const handleTabChange = (tabName) => {
- if (tabName === 'record') {
- getTableData()
- } else if (tabName === 'scheduled') {
- getScheduledTableData()
- }
-}
-
-// 瀹氭椂浠诲姟绠$悊鐩稿叧鏂规硶
-const getScheduledTableData = async () => {
- try {
- const params = {
- current: scheduledPagination.currentPage,
- size: scheduledPagination.pageSize,
- taskName: scheduledFilters.taskName || undefined,
- status: scheduledFilters.status || undefined,
+ // Tab鍒囨崲澶勭悊
+ const handleTabChange = tabName => {
+ if (tabName === "record") {
+ getTableData();
+ } else if (tabName === "scheduled") {
+ getScheduledTableData();
}
- const { code, data } = await deviceMaintenanceTaskList(params)
- if (code === 200) {
- scheduledDataList.value = data?.records || []
- scheduledPagination.total = data?.total || 0
+ };
+
+ // 瀹氭椂浠诲姟绠$悊鐩稿叧鏂规硶
+ 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("鑾峰彇瀹氭椂浠诲姟鍒楄〃澶辫触");
}
- } catch (error) {
- ElMessage.error('鑾峰彇瀹氭椂浠诲姟鍒楄〃澶辫触')
- }
-}
+ };
-const resetScheduledFilters = () => {
- scheduledFilters.taskName = ''
- scheduledFilters.status = ''
- getScheduledTableData()
-}
+ const resetScheduledFilters = () => {
+ scheduledFilters.taskName = "";
+ scheduledFilters.status = "";
+ getScheduledTableData();
+ };
-const handleScheduledSelectionChange = (selection) => {
- scheduledMultipleList.value = selection
-}
+ const handleScheduledSelectionChange = selection => {
+ scheduledMultipleList.value = selection;
+ };
-const changeScheduledPage = (page) => {
- scheduledPagination.currentPage = page.page
- scheduledPagination.pageSize = page.limit
- getScheduledTableData()
-}
+ const changeScheduledPage = page => {
+ scheduledPagination.currentPage = page.page;
+ scheduledPagination.pageSize = page.limit;
+ getScheduledTableData();
+ };
-const addScheduledTask = () => {
- nextTick(() => {
- formDiaRef.value?.openDialog('add');
- });
-}
+ const addScheduledTask = () => {
+ nextTick(() => {
+ formDiaRef.value?.openDialog("add");
+ });
+ };
-const editScheduledTask = (row) => {
- if (row) {
- nextTick(() => {
- formDiaRef.value?.openDialog('edit', row);
- });
- }
-}
+ const editScheduledTask = row => {
+ if (row) {
+ nextTick(() => {
+ formDiaRef.value?.openDialog("edit", row);
+ });
+ }
+ };
-const delScheduledTaskByIds = async (ids) => {
- try {
- await ElMessageBox.confirm('纭畾鍒犻櫎閫変腑鐨勫畾鏃朵换鍔″悧锛�', '鎻愮ず', {
- type: 'warning',
+ 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",
})
- const payload = Array.isArray(ids) ? ids : [ids]
- await deviceMaintenanceTaskDel(payload)
- ElMessage.success('鍒犻櫎瀹氭椂浠诲姟鎴愬姛')
- getScheduledTableData()
- } catch (error) {
- // 鐢ㄦ埛鍙栨秷鍒犻櫎
- }
-}
+ .then(() => {
+ proxy.download("/device/maintenance/export", {}, "璁惧淇濆吇.xlsx");
+ })
+ .catch(() => {
+ ElMessage.info("宸插彇娑�");
+ });
+ };
-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 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();
+ };
- const { code, data } = await getUpkeepPage(params)
- if (code === 200) {
- dataList.value = data.records
- pagination.value.total = data.total
+ // 鎵撳紑闄勪欢寮圭獥
+ const openFileDialog = async row => {
+ currentMaintenanceTaskId.value = row.id;
+ fileDialogVisible.value = true;
+ };
+
+ onMounted(() => {
+ // 鏍规嵁榛樿婵�娲荤殑 Tab 璋冪敤瀵瑰簲鐨勬煡璇㈡帴鍙�
+ if (activeTab.value === "scheduled") {
+ getScheduledTableData();
+ } else {
+ getTableData();
}
- } 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()
-}
-
-// 闄勪欢鐩稿叧鏂规硶
-// 鏌ヨ闄勪欢鍒楄〃
-const fetchMaintenanceTaskFiles = async (deviceMaintenanceId) => {
- try {
- const params = {
- current: 1,
- size: 100,
- deviceMaintenanceId,
- rulesRegulationsManagementId:deviceMaintenanceId
- }
- const res = await listMaintenanceTaskFiles(params)
- const records = res?.data?.records || []
- const mapped = records.map(item => ({
- id: item.id,
- name: item.fileName || item.name,
- url: item.fileUrl || item.url,
- raw: item,
- }))
- fileListDialogRef.value?.setList(mapped)
- } catch (error) {
- ElMessage.error('鑾峰彇闄勪欢鍒楄〃澶辫触')
- }
-}
-
-// 鎵撳紑闄勪欢寮圭獥
-const openFileDialog = async (row) => {
- currentMaintenanceTaskId.value = row.id
- fileDialogVisible.value = true
- await fetchMaintenanceTaskFiles(row.id)
-}
-
-// 鍒锋柊闄勪欢鍒楄〃
-const refreshFileList = async () => {
- if (!currentMaintenanceTaskId.value) return
- await fetchMaintenanceTaskFiles(currentMaintenanceTaskId.value)
-}
-
-// 涓婁紶闄勪欢
-const handleAttachmentUpload = async (filePayload) => {
- if (!currentMaintenanceTaskId.value) return
- try {
- const payload = {
- name: filePayload?.fileName || filePayload?.name,
- url: filePayload?.fileUrl || filePayload?.url,
- deviceMaintenanceId: currentMaintenanceTaskId.value,
- }
- await addMaintenanceTaskFile(payload)
- ElMessage.success('鏂囦欢涓婁紶鎴愬姛')
- await refreshFileList()
- } catch (error) {
- ElMessage.error('鏂囦欢涓婁紶澶辫触')
- }
-}
-
-// 鍒犻櫎闄勪欢
-const handleAttachmentDelete = async (row) => {
- if (!row?.id) return false
- try {
- await ElMessageBox.confirm('纭鍒犻櫎璇ラ檮浠讹紵', '鎻愮ず', { type: 'warning' })
- } catch {
- return false
- }
- try {
- await delMaintenanceTaskFile(row.id)
- ElMessage.success('鍒犻櫎鎴愬姛')
- await refreshFileList()
- return true
- } catch (error) {
- ElMessage.error('鍒犻櫎澶辫触')
- return false
- }
-}
-
-onMounted(() => {
- // 鏍规嵁榛樿婵�娲荤殑 Tab 璋冪敤瀵瑰簲鐨勬煡璇㈡帴鍙�
- if (activeTab.value === 'scheduled') {
- getScheduledTableData()
- } else {
- getTableData()
- }
-})
+ });
</script>
<style lang="scss" scoped>
-.table_list {
- margin-top: unset;
-}
-.actions {
- display: flex;
- justify-content: space-between;
- margin-bottom: 10px;
-}
+ .table_list {
+ margin-top: unset;
+ }
+ .actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ }
</style>
diff --git a/src/views/example/DynamicTableExample.vue b/src/views/example/DynamicTableExample.vue
index 038cd43..14dfc92 100644
--- a/src/views/example/DynamicTableExample.vue
+++ b/src/views/example/DynamicTableExample.vue
@@ -94,8 +94,8 @@
<template #footer>
<div class="dialog-footer">
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
<el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
diff --git a/src/views/fileManagement/bookshelf/index.vue b/src/views/fileManagement/bookshelf/index.vue
index a084900..c73ae1c 100644
--- a/src/views/fileManagement/bookshelf/index.vue
+++ b/src/views/fileManagement/bookshelf/index.vue
@@ -97,8 +97,8 @@
</el-tree>
<template #footer>
<span class="dialog-footer">
- <el-button @click="keepVisible = false">鍙� 娑�</el-button>
<el-button type="primary" @click="keepVisible = false" >纭� 瀹�</el-button>
+ <el-button @click="keepVisible = false">鍙� 娑�</el-button>
</span>
</template>
</el-dialog>
@@ -115,8 +115,8 @@
</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>
+ <el-button @click="warehouseVisible = false">鍙� 娑�</el-button>
</span>
</template>
</el-dialog>
@@ -149,8 +149,8 @@
</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>
+ <el-button @click="shelvesVisible = false">鍙� 娑�</el-button>
</span>
</template>
</el-dialog>
diff --git a/src/views/fileManagement/borrow/index.vue b/src/views/fileManagement/borrow/index.vue
index 705a0f8..c7d8e0d 100644
--- a/src/views/fileManagement/borrow/index.vue
+++ b/src/views/fileManagement/borrow/index.vue
@@ -100,16 +100,14 @@
</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-select
+ v-if="borrowOperationType !== 'edit'"
+ v-model="borrowForm.documentationId"
+ placeholder="璇烽�夋嫨鍊熼槄涔︾睄"
+ style="flex: 1;width: 100px;"
+ @change="handleSelectChange"
+ >
<el-option
v-for="item in documentList"
:key="item.id"
@@ -118,6 +116,13 @@
/>
</el-select>
<el-input
+ v-else
+ v-model="currentEditDocName"
+ style="flex: 1;width: 100px;"
+ disabled
+ />
+ <el-input
+ v-if="borrowOperationType !== 'edit'"
v-model="scanContent"
placeholder="鎵爜杈撳叆"
style="width: 100px;"
@@ -205,6 +210,7 @@
const selectedRows = ref([]);
const documentList = ref([]); // 鏂囨。鍒楄〃锛岀敤浜庡�熼槄涔︾睄閫夋嫨
const scanContent = ref() // 鎵爜鍐呭
+const currentEditDocName = ref(''); // 缂栬緫鏃跺瓨鍌ㄧ殑鏂囨。鍚嶇О
// 鍒嗛〉鐩稿叧
const pagination = reactive({
currentPage: 1,
@@ -282,6 +288,7 @@
{
name: "缂栬緫",
type: "text",
+ disabled: (row) => row.borrowStatus === '褰掕繕',
clickFun: (row) => {
openBorrowDia('edit', row)
},
@@ -428,13 +435,16 @@
if (type === "edit") {
// 缂栬緫妯″紡锛屽姞杞界幇鏈夋暟鎹�
Object.assign(borrowForm, data);
+ // 瀛樺偍鏂囨。鍚嶇О鐢ㄤ簬鏄剧ず
+ currentEditDocName.value = data.docName || '';
} else {
// 鏂板妯″紡锛屾竻绌鸿〃鍗�
Object.keys(borrowForm).forEach(key => {
borrowForm[key] = "";
});
- // 璁剧疆榛樿鐘舵��
- borrowForm.borrowStatus = "鍊熼槄";
+ currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
+ // 璁剧疆榛樿鐘舵��
+ borrowForm.borrowStatus = "鍊熼槄";
// 璁剧疆褰撳墠鏃ユ湡涓哄�熼槄鏃ユ湡
borrowForm.borrowDate = new Date().toISOString().split('T')[0];
}
@@ -445,6 +455,7 @@
proxy.$refs.borrowFormRef.resetFields();
borrowDia.value = false;
scanContent.value = ''; // 娓呯┖鎵爜鍐呭
+ currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
};
// 鎻愪氦鍊熼槄琛ㄥ崟
@@ -625,7 +636,7 @@
}
.dialog-footer {
- text-align: right;
+ text-align: center;
}
:deep(.el-form-item__label) {
diff --git a/src/views/fileManagement/document/index.vue b/src/views/fileManagement/document/index.vue
index aa182b0..f4eac2d 100644
--- a/src/views/fileManagement/document/index.vue
+++ b/src/views/fileManagement/document/index.vue
@@ -862,12 +862,14 @@
documentForm[key] = "";
});
documentForm.attachments = []; // 鏂板妯″紡涓嬩篃娓呯┖闄勪欢
- // 璁剧疆榛樿鍊� - 浣跨敤瀛楀吀鏁版嵁鐨勭涓�涓�夐」浣滀负榛樿鍊�
+ // 璁剧疆榛樿鍊� - 鏂囨。鐘舵�侀粯璁よ缃负"姝e父"
if (document_status.value && document_status.value.length > 0) {
- documentForm.docStatus = document_status.value[0].value;
+ const normalStatus = document_status.value.find(item => item.label === '姝e父');
+ documentForm.docStatus = normalStatus ? normalStatus.value : document_status.value[0].value;
}
if (document_urgency.value && document_urgency.value.length > 0) {
- documentForm.urgencyLevel = document_urgency.value[0].value;
+ const normalUrgency = document_urgency.value.find(item => item.label === '鏅��');
+ documentForm.urgencyLevel = normalUrgency ? normalUrgency.value : document_urgency.value[0].value;
}
}
};
@@ -1298,7 +1300,7 @@
}
.dialog-footer {
- text-align: right;
+ text-align: center;
}
.operation-column {
diff --git a/src/views/fileManagement/return/index.vue b/src/views/fileManagement/return/index.vue
index 34e733f..097ab29 100644
--- a/src/views/fileManagement/return/index.vue
+++ b/src/views/fileManagement/return/index.vue
@@ -103,16 +103,14 @@
<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-select
+ v-if="returnOperationType !== 'edit'"
+ v-model="returnForm.borrowId"
+ placeholder="璇烽�夋嫨鏂囨。"
+ style="width: 120px;"
+ @change="handleDocumentChange"
+ >
<el-option
v-for="item in documentList"
:key="item.id"
@@ -121,6 +119,13 @@
/>
</el-select>
<el-input
+ v-else
+ v-model="currentEditDocName"
+ style="width: 120px;"
+ disabled
+ />
+ <el-input
+ v-if="returnOperationType !== 'edit'"
v-model="scanContent"
placeholder="鎵爜杈撳叆"
style="flex: 1;"
@@ -215,6 +220,7 @@
const documentList = ref([]); // 鏂囨。鍒楄〃
const borrowInfoList = ref([]); // 鍊熼槄淇℃伅鍒楄〃
const scanContent = ref(); // 鎵爜鍐呭
+const currentEditDocName = ref(''); // 缂栬緫鏃跺瓨鍌ㄧ殑鏂囨。鍚嶇О
// 鍒嗛〉鐩稿叧
const pagination = reactive({
@@ -286,6 +292,7 @@
{
name: "缂栬緫",
type: "text",
+ disabled: (row) => row.borrowStatus === '褰掕繕',
clickFun: (row) => {
openReturnDia('edit', row)
},
@@ -396,15 +403,14 @@
if (type === "edit") {
// 缂栬緫妯″紡锛屽姞杞界幇鏈夋暟鎹�
Object.assign(returnForm, data);
- // 缂栬緫妯″紡涓嬶紝鏂囨。閫夋嫨鍚庤嚜鍔ㄥ~鍏呭�熼槄浜哄拰搴斿綊杩樻棩鏈�
- if (returnForm.borrowId) {
- handleDocumentChange(returnForm.borrowId);
- }
+ // 瀛樺偍鏂囨。鍚嶇О鐢ㄤ簬鏄剧ず
+ currentEditDocName.value = data.docName || '';
} else {
// 鏂板妯″紡锛屾竻绌鸿〃鍗�
Object.keys(returnForm).forEach(key => {
returnForm[key] = "";
});
+ currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
// 璁剧疆榛樿鐘舵��
returnForm.borrowStatus = "褰掕繕";
// 璁剧疆褰撳墠鏃ユ湡涓哄綊杩樻棩鏈�
@@ -418,6 +424,7 @@
returnDia.value = false;
scanContent.value = ''; // 娓呯┖鎵爜鍐呭
borrowInfoList.value = []; // 娓呯┖鍊熼槄淇℃伅鍒楄〃
+ currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
};
// 鎻愪氦褰掕繕琛ㄥ崟
@@ -677,7 +684,7 @@
}
.dialog-footer {
- text-align: right;
+ text-align: center;
}
:deep(.el-form-item__label) {
diff --git a/src/views/financialManagement/accounting/index.vue b/src/views/financialManagement/accounting/index.vue
index 830fe1a..ea858e1 100644
--- a/src/views/financialManagement/accounting/index.vue
+++ b/src/views/financialManagement/accounting/index.vue
@@ -526,7 +526,6 @@
/* 椤甸潰鑳屾櫙 */
main {
- background: #f5f5f5;
padding: 0;
margin: 0 -20px;
padding: 0 20px 20px;
diff --git a/src/views/financialManagement/assets/fixedAssets.vue b/src/views/financialManagement/assets/fixedAssets.vue
new file mode 100644
index 0000000..61eb245
--- /dev/null
+++ b/src/views/financialManagement/assets/fixedAssets.vue
@@ -0,0 +1,462 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="璧勪骇缂栧彿:">
+ <el-input v-model="filters.assetCode" placeholder="璇疯緭鍏ヨ祫浜х紪鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇鍚嶇О:">
+ <el-input v-model="filters.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇绫诲埆:">
+ <el-select v-model="filters.category" placeholder="璇烽�夋嫨绫诲埆" clearable style="width: 150px;">
+ <el-option label="鎴垮眿寤虹瓚" value="building" />
+ <el-option label="鏈哄櫒璁惧" value="machine" />
+ <el-option label="杩愯緭宸ュ叿" value="vehicle" />
+ <el-option label="鐢靛瓙璁惧" value="electronic" />
+ <el-option label="鍔炲叕瀹跺叿" value="furniture" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="鍦ㄧ敤" value="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="鎶ュ簾" value="scrapped" />
+ </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>
+ <el-statistic title="璧勪骇鍘熷�煎悎璁�" :value="totalOriginalValue" precision="2" prefix="楼" />
+ <el-statistic title="绱鎶樻棫鍚堣" :value="totalDepreciation" precision="2" prefix="楼" style="margin-left: 30px;" />
+ <el-statistic title="鍑�鍊煎悎璁�" :value="totalNetValue" precision="2" prefix="楼" style="margin-left: 30px;" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板璧勪骇</el-button>
+ <el-button type="warning" @click="handleDepreciation" icon="Money">鎶樻棫璁℃彁</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #originalValue="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.originalValue) }}</span>
+ </template>
+ <template #accumulatedDepreciation="{ row }">
+ <span class="text-warning">楼{{ formatMoney(row.accumulatedDepreciation) }}</span>
+ </template>
+ <template #netValue="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.netValue) }}</span>
+ </template>
+ <template #category="{ row }">
+ <el-tag>{{ getCategoryLabel(row.category) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇缂栧彿" prop="assetCode">
+ <el-input v-model="form.assetCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍚嶇О" prop="assetName">
+ <el-input v-model="form.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇绫诲埆" prop="category">
+ <el-select v-model="form.category" placeholder="璇烽�夋嫨璧勪骇绫诲埆" style="width: 100%;">
+ <el-option label="鎴垮眿寤虹瓚" value="building" />
+ <el-option label="鏈哄櫒璁惧" value="machine" />
+ <el-option label="杩愯緭宸ュ叿" value="vehicle" />
+ <el-option label="鐢靛瓙璁惧" value="electronic" />
+ <el-option label="鍔炲叕瀹跺叿" value="furniture" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿" prop="specification">
+ <el-input v-model="form.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璐疆鏃ユ湡" prop="purchaseDate">
+ <el-date-picker v-model="form.purchaseDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍘熷��" prop="originalValue">
+ <el-input-number v-model="form.originalValue" :min="0" :precision="2" style="width: 100%;" @change="calculateNetValue" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浣跨敤骞撮檺" prop="usefulLife">
+ <el-input-number v-model="form.usefulLife" :min="1" :max="50" style="width: 100%;" />
+ <span style="margin-left: 10px;">骞�</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="娈嬪�肩巼" prop="residualRate">
+ <el-input-number v-model="form.residualRate" :min="0" :max="10" :precision="2" style="width: 100%;" />
+ <span style="margin-left: 10px;">%</span>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绱鎶樻棫">
+ <el-input v-model="form.accumulatedDepreciation" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍑�鍊�">
+ <el-input v-model="form.netValue" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀛樻斁鍦扮偣" prop="location">
+ <el-input v-model="form.location" placeholder="璇疯緭鍏ュ瓨鏀惧湴鐐�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浣跨敤閮ㄩ棬" prop="department">
+ <el-input v-model="form.department" placeholder="璇疯緭鍏ヤ娇鐢ㄩ儴闂�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="淇濈浜�" prop="keeper">
+ <el-input v-model="form.keeper" placeholder="璇疯緭鍏ヤ繚绠′汉" />
+ </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="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="鎶ュ簾" value="scrapped" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "鍥哄畾璧勪骇",
+});
+
+const filters = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
+ { label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
+ { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+ { label: "瑙勬牸鍨嬪彿", prop: "specification", width: "120" },
+ { label: "璧勪骇鍘熷��", prop: "originalValue", slot: "originalValue" },
+ { label: "绱鎶樻棫", prop: "accumulatedDepreciation", slot: "accumulatedDepreciation" },
+ { label: "璧勪骇鍑�鍊�", prop: "netValue", slot: "netValue" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const form = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ specification: "",
+ purchaseDate: "",
+ originalValue: 0,
+ usefulLife: 5,
+ residualRate: 5,
+ accumulatedDepreciation: 0,
+ netValue: 0,
+ location: "",
+ department: "",
+ keeper: "",
+ status: "in_use",
+ remark: "",
+});
+
+const rules = {
+ assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
+ category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
+ purchaseDate: [{ required: true, message: "璇烽�夋嫨璐疆鏃ユ湡", trigger: "change" }],
+ originalValue: [{ required: true, message: "璇疯緭鍏ヨ祫浜у師鍊�", trigger: "blur" }],
+ usefulLife: [{ required: true, message: "璇疯緭鍏ヤ娇鐢ㄥ勾闄�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, assetCode: "GD2024001", assetName: "鍔炲叕鐢佃剳", category: "electronic", specification: "鑱旀兂ThinkPad X1", purchaseDate: "2023-01-15", originalValue: 8000, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 1520, netValue: 6480, location: "鍔炲叕瀹�", department: "璐㈠姟閮�", keeper: "寮犱笁", status: "in_use", remark: "" },
+ { id: 2, assetCode: "GD2024002", assetName: "鎵撳嵃鏈�", category: "electronic", specification: "鎯犳櫘M479fdw", purchaseDate: "2023-03-20", originalValue: 3500, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 532, netValue: 2968, location: "鏂囧嵃瀹�", department: "琛屾斂閮�", keeper: "鏉庡洓", status: "in_use", remark: "" },
+ { id: 3, assetCode: "GD2024003", assetName: "鍔炲叕妗屾", category: "furniture", specification: "瀹炴湪鍔炲叕妗�", purchaseDate: "2023-06-10", originalValue: 2500, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 118.75, netValue: 2381.25, location: "鍔炲叕瀹�", department: "閿�鍞儴", keeper: "鐜嬩簲", status: "in_use", remark: "" },
+ { id: 4, assetCode: "GD2024004", assetName: "鍟嗗姟杞�", category: "vehicle", specification: "鍒厠GL8", purchaseDate: "2022-08-01", originalValue: 280000, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 53200, netValue: 226800, location: "鍋滆溅鍦�", department: "琛屾斂閮�", keeper: "璧靛叚", status: "in_use", remark: "" },
+];
+
+const totalOriginalValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0);
+});
+
+const totalDepreciation = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.accumulatedDepreciation), 0);
+});
+
+const totalNetValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.netValue), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getCategoryLabel = (category) => {
+ const map = {
+ building: "鎴垮眿寤虹瓚",
+ machine: "鏈哄櫒璁惧",
+ vehicle: "杩愯緭宸ュ叿",
+ electronic: "鐢靛瓙璁惧",
+ furniture: "鍔炲叕瀹跺叿",
+ };
+ return map[category] || category;
+};
+
+const getStatusLabel = (status) => {
+ const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", scrapped: "鎶ュ簾" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { in_use: "success", idle: "warning", scrapped: "info" };
+ return map[status] || "";
+};
+
+const calculateNetValue = () => {
+ form.netValue = Number((form.originalValue - form.accumulatedDepreciation).toFixed(2));
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.assetCode) {
+ result = result.filter(item => item.assetCode.includes(filters.assetCode));
+ }
+ if (filters.assetName) {
+ result = result.filter(item => item.assetName.includes(filters.assetName));
+ }
+ if (filters.category) {
+ result = result.filter(item => item.category === filters.category);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.assetCode = "";
+ filters.assetName = "";
+ filters.category = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍥哄畾璧勪骇";
+ Object.assign(form, {
+ assetCode: "GD" + Date.now().toString().slice(-8),
+ assetName: "",
+ category: "",
+ specification: "",
+ purchaseDate: new Date().toISOString().split('T')[0],
+ originalValue: 0,
+ usefulLife: 5,
+ residualRate: 5,
+ accumulatedDepreciation: 0,
+ netValue: 0,
+ location: "",
+ department: "",
+ keeper: "",
+ status: "in_use",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍥哄畾璧勪骇";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅璧勪骇: ${row.assetName}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ュ浐瀹氳祫浜у悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleDepreciation = () => {
+ ElMessageBox.confirm("纭杩涜鏈湀鎶樻棫璁℃彁鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ mockData.forEach(item => {
+ if (item.status === "in_use") {
+ const monthlyDepreciation = (item.originalValue * (1 - item.residualRate / 100)) / (item.usefulLife * 12);
+ item.accumulatedDepreciation = Number((item.accumulatedDepreciation + monthlyDepreciation).toFixed(2));
+ item.netValue = Number((item.originalValue - item.accumulatedDepreciation).toFixed(2));
+ }
+ });
+ ElMessage.success("鎶樻棫璁℃彁瀹屾垚");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ calculateNetValue();
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+
+ > div:first-child {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/assets/intangibleAssets.vue b/src/views/financialManagement/assets/intangibleAssets.vue
new file mode 100644
index 0000000..14dae55
--- /dev/null
+++ b/src/views/financialManagement/assets/intangibleAssets.vue
@@ -0,0 +1,458 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="璧勪骇缂栧彿:">
+ <el-input v-model="filters.assetCode" placeholder="璇疯緭鍏ヨ祫浜х紪鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇鍚嶇О:">
+ <el-input v-model="filters.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇绫诲埆:">
+ <el-select v-model="filters.category" placeholder="璇烽�夋嫨绫诲埆" clearable style="width: 150px;">
+ <el-option label="涓撳埄鏉�" value="patent" />
+ <el-option label="鍟嗘爣鏉�" value="trademark" />
+ <el-option label="钁椾綔鏉�" value="copyright" />
+ <el-option label="杞欢" value="software" />
+ <el-option label="鍦熷湴浣跨敤鏉�" value="land" />
+ <el-option label="鍏朵粬" value="other" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="鍦ㄧ敤" value="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="宸叉憡閿�瀹屾瘯" value="amortized" />
+ </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>
+ <el-statistic title="璧勪骇鍘熷�煎悎璁�" :value="totalOriginalValue" precision="2" prefix="楼" />
+ <el-statistic title="绱鎽婇攢鍚堣" :value="totalAmortization" precision="2" prefix="楼" style="margin-left: 30px;" />
+ <el-statistic title="鍑�鍊煎悎璁�" :value="totalNetValue" precision="2" prefix="楼" style="margin-left: 30px;" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板璧勪骇</el-button>
+ <el-button type="warning" @click="handleAmortization" icon="Money">鎽婇攢璁℃彁</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #originalValue="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.originalValue) }}</span>
+ </template>
+ <template #accumulatedAmortization="{ row }">
+ <span class="text-warning">楼{{ formatMoney(row.accumulatedAmortization) }}</span>
+ </template>
+ <template #netValue="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.netValue) }}</span>
+ </template>
+ <template #category="{ row }">
+ <el-tag>{{ getCategoryLabel(row.category) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇缂栧彿" prop="assetCode">
+ <el-input v-model="form.assetCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍚嶇О" prop="assetName">
+ <el-input v-model="form.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇绫诲埆" prop="category">
+ <el-select v-model="form.category" placeholder="璇烽�夋嫨璧勪骇绫诲埆" style="width: 100%;">
+ <el-option label="涓撳埄鏉�" value="patent" />
+ <el-option label="鍟嗘爣鏉�" value="trademark" />
+ <el-option label="钁椾綔鏉�" value="copyright" />
+ <el-option label="杞欢" value="software" />
+ <el-option label="鍦熷湴浣跨敤鏉�" value="land" />
+ <el-option label="鍏朵粬" value="other" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璇佷功缂栧彿" prop="certificateNo">
+ <el-input v-model="form.certificateNo" placeholder="璇疯緭鍏ヨ瘉涔︾紪鍙�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙栧緱鏃ユ湡" prop="acquisitionDate">
+ <el-date-picker v-model="form.acquisitionDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍘熷��" prop="originalValue">
+ <el-input-number v-model="form.originalValue" :min="0" :precision="2" style="width: 100%;" @change="calculateNetValue" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鎽婇攢骞撮檺" prop="amortizationPeriod">
+ <el-input-number v-model="form.amortizationPeriod" :min="1" :max="50" style="width: 100%;" />
+ <span style="margin-left: 10px;">骞�</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="娈嬪�肩巼" prop="residualRate">
+ <el-input-number v-model="form.residualRate" :min="0" :max="10" :precision="2" style="width: 100%;" />
+ <span style="margin-left: 10px;">%</span>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绱鎽婇攢">
+ <el-input v-model="form.accumulatedAmortization" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍑�鍊�">
+ <el-input v-model="form.netValue" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏈夋晥鏈熻嚦" prop="validityDate">
+ <el-date-picker v-model="form.validityDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%;">
+ <el-option label="鍦ㄧ敤" value="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="宸叉憡閿�瀹屾瘯" value="amortized" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="璧勪骇鎻忚堪" prop="description">
+ <el-input v-model="form.description" type="textarea" :rows="3" placeholder="璇疯緭鍏ヨ祫浜ф弿杩�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "鏃犲舰璧勪骇",
+});
+
+const filters = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
+ { label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
+ { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+ { label: "璇佷功缂栧彿", prop: "certificateNo", width: "150" },
+ { label: "璧勪骇鍘熷��", prop: "originalValue", slot: "originalValue" },
+ { label: "绱鎽婇攢", prop: "accumulatedAmortization", slot: "accumulatedAmortization" },
+ { label: "璧勪骇鍑�鍊�", prop: "netValue", slot: "netValue" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const form = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ certificateNo: "",
+ acquisitionDate: "",
+ originalValue: 0,
+ amortizationPeriod: 10,
+ residualRate: 0,
+ accumulatedAmortization: 0,
+ netValue: 0,
+ validityDate: "",
+ status: "in_use",
+ description: "",
+ remark: "",
+});
+
+const rules = {
+ assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
+ category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
+ acquisitionDate: [{ required: true, message: "璇烽�夋嫨鍙栧緱鏃ユ湡", trigger: "change" }],
+ originalValue: [{ required: true, message: "璇疯緭鍏ヨ祫浜у師鍊�", trigger: "blur" }],
+ amortizationPeriod: [{ required: true, message: "璇疯緭鍏ユ憡閿�骞撮檺", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, assetCode: "WX2024001", assetName: "ERP杞欢璁稿彲", category: "software", certificateNo: "SW-2023-001", acquisitionDate: "2023-01-01", originalValue: 50000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 5000, netValue: 45000, validityDate: "2033-01-01", status: "in_use", description: "浼佷笟璧勬簮璁″垝绠$悊绯荤粺", remark: "" },
+ { id: 2, assetCode: "WX2024002", assetName: "鍙戞槑涓撳埄", category: "patent", certificateNo: "ZL202210123456.7", acquisitionDate: "2022-06-15", originalValue: 100000, amortizationPeriod: 20, residualRate: 0, accumulatedAmortization: 3750, netValue: 96250, validityDate: "2042-06-15", status: "in_use", description: "涓�绉嶆柊鍨嬬敓浜у伐鑹�", remark: "" },
+ { id: 3, assetCode: "WX2024003", assetName: "鍟嗘爣鏉�", category: "trademark", certificateNo: "TM-2023-008", acquisitionDate: "2023-03-10", originalValue: 20000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 1500, netValue: 18500, validityDate: "2033-03-10", status: "in_use", description: "鍏徃鍝佺墝鍟嗘爣", remark: "" },
+ { id: 4, assetCode: "WX2024004", assetName: "鍦熷湴浣跨敤鏉�", category: "land", certificateNo: "鍦熷浗鐢�(2023)绗�001鍙�", acquisitionDate: "2023-07-01", originalValue: 500000, amortizationPeriod: 50, residualRate: 0, accumulatedAmortization: 5000, netValue: 495000, validityDate: "2073-07-01", status: "in_use", description: "宸ヤ笟鐢ㄥ湴浣跨敤鏉�", remark: "" },
+];
+
+const totalOriginalValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0);
+});
+
+const totalAmortization = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.accumulatedAmortization), 0);
+});
+
+const totalNetValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.netValue), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getCategoryLabel = (category) => {
+ const map = {
+ patent: "涓撳埄鏉�",
+ trademark: "鍟嗘爣鏉�",
+ copyright: "钁椾綔鏉�",
+ software: "杞欢",
+ land: "鍦熷湴浣跨敤鏉�",
+ other: "鍏朵粬",
+ };
+ return map[category] || category;
+};
+
+const getStatusLabel = (status) => {
+ const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", amortized: "宸叉憡閿�瀹屾瘯" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { in_use: "success", idle: "warning", amortized: "info" };
+ return map[status] || "";
+};
+
+const calculateNetValue = () => {
+ form.netValue = Number((form.originalValue - form.accumulatedAmortization).toFixed(2));
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.assetCode) {
+ result = result.filter(item => item.assetCode.includes(filters.assetCode));
+ }
+ if (filters.assetName) {
+ result = result.filter(item => item.assetName.includes(filters.assetName));
+ }
+ if (filters.category) {
+ result = result.filter(item => item.category === filters.category);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.assetCode = "";
+ filters.assetName = "";
+ filters.category = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鏃犲舰璧勪骇";
+ Object.assign(form, {
+ assetCode: "WX" + Date.now().toString().slice(-8),
+ assetName: "",
+ category: "",
+ certificateNo: "",
+ acquisitionDate: new Date().toISOString().split('T')[0],
+ originalValue: 0,
+ amortizationPeriod: 10,
+ residualRate: 0,
+ accumulatedAmortization: 0,
+ netValue: 0,
+ validityDate: "",
+ status: "in_use",
+ description: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鏃犲舰璧勪骇";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅璧勪骇: ${row.assetName}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ユ棤褰㈣祫浜у悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleAmortization = () => {
+ ElMessageBox.confirm("纭杩涜鏈湀鎽婇攢璁℃彁鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ mockData.forEach(item => {
+ if (item.status === "in_use") {
+ const monthlyAmortization = (item.originalValue * (1 - item.residualRate / 100)) / (item.amortizationPeriod * 12);
+ item.accumulatedAmortization = Number((item.accumulatedAmortization + monthlyAmortization).toFixed(2));
+ item.netValue = Number((item.originalValue - item.accumulatedAmortization).toFixed(2));
+ if (item.netValue <= 0) {
+ item.status = "amortized";
+ item.netValue = 0;
+ }
+ }
+ });
+ ElMessage.success("鎽婇攢璁℃彁瀹屾垚");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ calculateNetValue();
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+
+ > div:first-child {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/expenseManagement/index.vue b/src/views/financialManagement/expenseManagement/index.vue
index ac55d01..173f8e1 100644
--- a/src/views/financialManagement/expenseManagement/index.vue
+++ b/src/views/financialManagement/expenseManagement/index.vue
@@ -76,27 +76,18 @@
</PIMTable>
</div>
<Modal ref="modalRef" @success="getTableData"></Modal>
- <FileListDialog
- ref="fileListRef"
- v-model="fileListDialogVisible"
- :show-upload-button="true"
- :show-delete-button="true"
- :upload-method="handleUpload"
- :delete-method="handleFileDelete"
- />
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="account_expense" :record-id="recordId" />
</div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { listPage, delAccountExpense, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/expenseManagement";
-import { onMounted, getCurrentInstance, ref, computed } from "vue";
+import { listPage, delAccountExpense } from "@/api/financialManagement/expenseManagement";
+import {onMounted, getCurrentInstance, ref, computed, defineAsyncComponent} from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
-import FileListDialog from "@/components/Dialog/FileListDialog.vue";
-import request from "@/utils/request";
-import { getToken } from "@/utils/auth";
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
defineOptions({
name: "鏀嚭绠$悊",
@@ -108,9 +99,6 @@
const modalRef = ref();
const { checkout_payment } = proxy.useDict("checkout_payment");
const { expense_types } = proxy.useDict("expense_types");
-const fileListRef = ref(null);
-const fileListDialogVisible = ref(false);
-const currentFileRow = ref(null);
const accountType = ref('鏀嚭');
const {
@@ -315,156 +303,16 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 鎵撳紑闄勪欢寮圭獥
+const recordId =ref(0)
+const fileDialogVisible = ref(false)
+
// 鎵撳紑闄勪欢寮规
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;
- }
-};
+ recordId.value = row.id
+ fileDialogVisible.value = true
+}
onMounted(() => {
getTableData();
diff --git a/src/views/financialManagement/generalLedger/index.vue b/src/views/financialManagement/generalLedger/index.vue
new file mode 100644
index 0000000..0570627
--- /dev/null
+++ b/src/views/financialManagement/generalLedger/index.vue
@@ -0,0 +1,291 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="绉戠洰缂栫爜:">
+ <el-input v-model="filters.subjectCode" placeholder="璇疯緭鍏ョ鐩紪鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="绉戠洰鍚嶇О:">
+ <el-input v-model="filters.subjectName" placeholder="璇疯緭鍏ョ鐩悕绉�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="绉戠洰绫诲瀷:">
+ <el-select v-model="filters.subjectType" placeholder="璇烽�夋嫨" clearable style="width: 200px;">
+ <el-option label="璧勪骇绫�" value="asset" />
+ <el-option label="璐熷�虹被" value="liability" />
+ <el-option label="鏉冪泭绫�" value="equity" />
+ <el-option label="鎴愭湰绫�" value="cost" />
+ <el-option label="鎹熺泭绫�" value="profit_loss" />
+ </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>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #subjectType="{ row }">
+ <el-tag :type="getSubjectTypeType(row.subjectType)">{{ getSubjectTypeLabel(row.subjectType) }}</el-tag>
+ </template>
+ <template #balanceDirection="{ row }">
+ <el-tag :type="row.balanceDirection === 'debit' ? 'success' : 'danger'">
+ {{ row.balanceDirection === 'debit' ? '鍊熸柟' : '璐锋柟' }}
+ </el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="row.status === 'active' ? 'success' : 'info'">
+ {{ row.status === 'active' ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="600px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-form-item label="绉戠洰缂栫爜" prop="subjectCode">
+ <el-input v-model="form.subjectCode" placeholder="璇疯緭鍏ョ鐩紪鐮�" />
+ </el-form-item>
+ <el-form-item label="绉戠洰鍚嶇О" prop="subjectName">
+ <el-input v-model="form.subjectName" placeholder="璇疯緭鍏ョ鐩悕绉�" />
+ </el-form-item>
+ <el-form-item label="绉戠洰绫诲瀷" prop="subjectType">
+ <el-select v-model="form.subjectType" placeholder="璇烽�夋嫨绉戠洰绫诲瀷" style="width: 100%;">
+ <el-option label="璧勪骇绫�" value="asset" />
+ <el-option label="璐熷�虹被" value="liability" />
+ <el-option label="鏉冪泭绫�" value="equity" />
+ <el-option label="鎴愭湰绫�" value="cost" />
+ <el-option label="鎹熺泭绫�" value="profit_loss" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浣欓鏂瑰悜" prop="balanceDirection">
+ <el-radio-group v-model="form.balanceDirection">
+ <el-radio label="debit">鍊熸柟</el-radio>
+ <el-radio label="credit">璐锋柟</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio label="active">鍚敤</el-radio>
+ <el-radio label="inactive">绂佺敤</el-radio>
+ </el-radio-group>
+ </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>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "鎬诲笎绉戠洰",
+});
+
+const filters = reactive({
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "绉戠洰缂栫爜", prop: "subjectCode", width: "120" },
+ { label: "绉戠洰鍚嶇О", prop: "subjectName", width: "150" },
+ { label: "绉戠洰绫诲瀷", prop: "subjectType", slot: "subjectType" },
+ { label: "浣欓鏂瑰悜", prop: "balanceDirection", slot: "balanceDirection" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const form = reactive({
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+ balanceDirection: "debit",
+ status: "active",
+ remark: "",
+});
+
+const rules = {
+ subjectCode: [{ required: true, message: "璇疯緭鍏ョ鐩紪鐮�", trigger: "blur" }],
+ subjectName: [{ required: true, message: "璇疯緭鍏ョ鐩悕绉�", trigger: "blur" }],
+ subjectType: [{ required: true, message: "璇烽�夋嫨绉戠洰绫诲瀷", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, subjectCode: "1001", subjectName: "搴撳瓨鐜伴噾", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 2, subjectCode: "1002", subjectName: "閾惰瀛樻", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 3, subjectCode: "1122", subjectName: "搴旀敹璐︽", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 4, subjectCode: "2202", subjectName: "搴斾粯璐︽", subjectType: "liability", balanceDirection: "credit", status: "active", remark: "" },
+ { id: 5, subjectCode: "4001", subjectName: "瀹炴敹璧勬湰", subjectType: "equity", balanceDirection: "credit", status: "active", remark: "" },
+ { id: 6, subjectCode: "5001", subjectName: "鐢熶骇鎴愭湰", subjectType: "cost", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 7, subjectCode: "6001", subjectName: "涓昏惀涓氬姟鏀跺叆", subjectType: "profit_loss", balanceDirection: "credit", status: "active", remark: "" },
+ { id: 8, subjectCode: "6401", subjectName: "涓昏惀涓氬姟鎴愭湰", subjectType: "profit_loss", balanceDirection: "debit", status: "active", remark: "" },
+];
+
+const getSubjectTypeLabel = (type) => {
+ const map = {
+ asset: "璧勪骇绫�",
+ liability: "璐熷�虹被",
+ equity: "鏉冪泭绫�",
+ cost: "鎴愭湰绫�",
+ profit_loss: "鎹熺泭绫�",
+ };
+ return map[type] || type;
+};
+
+const getSubjectTypeType = (type) => {
+ const map = {
+ asset: "success",
+ liability: "danger",
+ equity: "warning",
+ cost: "info",
+ profit_loss: "primary",
+ };
+ return map[type] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.subjectCode) {
+ result = result.filter(item => item.subjectCode.includes(filters.subjectCode));
+ }
+ if (filters.subjectName) {
+ result = result.filter(item => item.subjectName.includes(filters.subjectName));
+ }
+ if (filters.subjectType) {
+ result = result.filter(item => item.subjectType === filters.subjectType);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.subjectCode = "";
+ filters.subjectName = "";
+ filters.subjectType = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板绉戠洰";
+ Object.assign(form, {
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+ balanceDirection: "debit",
+ status: "active",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫绉戠洰";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ョ鐩悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+</style>
diff --git a/src/views/financialManagement/payable/input-invoice.vue b/src/views/financialManagement/payable/input-invoice.vue
new file mode 100644
index 0000000..660d0dd
--- /dev/null
+++ b/src/views/financialManagement/payable/input-invoice.vue
@@ -0,0 +1,409 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍙戠エ浠g爜:">
+ <el-input v-model="filters.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="鍙戠エ鍙风爜:">
+ <el-input v-model="filters.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁よ瘉鐘舵��:">
+ <el-select v-model="filters.certifyStatus" placeholder="璇烽�夋嫨璁よ瘉鐘舵��" clearable style="width: 150px;">
+ <el-option label="鏈璇�" value="uncertified" />
+ <el-option label="宸茶璇�" value="certified" />
+ <el-option label="璁よ瘉澶辫触" value="failed" />
+ </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>
+ <el-button type="success" @click="handleBatchCertify" icon="Check" :disabled="selectedRows.length === 0">鎵归噺璁よ瘉</el-button>
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">褰曞叆鍙戠エ</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</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 #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #taxAmount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.taxAmount) }}</span>
+ </template>
+ <template #totalAmount="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.totalAmount) }}</span>
+ </template>
+ <template #certifyStatus="{ row }">
+ <el-tag :type="getCertifyStatusType(row.certifyStatus)">{{ getCertifyStatusLabel(row.certifyStatus) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleCertify(row)" v-if="row.certifyStatus === 'uncertified'">璁よ瘉</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ浠g爜" prop="invoiceCode">
+ <el-input v-model="form.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
+ <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
+ <el-date-picker v-model="form.invoiceDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="绋庣巼" prop="taxRate">
+ <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;" @change="calculateTax">
+ <el-option
+ v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="Number(dict.value)"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="绋庨">
+ <el-input v-model="form.taxAmount" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璁よ瘉鐘舵��" prop="certifyStatus">
+ <el-select v-model="form.certifyStatus" placeholder="璇烽�夋嫨璁よ瘉鐘舵��" style="width: 100%;" disabled>
+ <el-option label="鏈璇�" value="uncertified" />
+ <el-option label="宸茶璇�" value="certified" />
+ <el-option label="璁よ瘉澶辫触" value="failed" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁よ瘉鏃ユ湡" prop="certifyDate">
+ <el-date-picker v-model="form.certifyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍙戠エ鍐呭" prop="content">
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "杩涢」鍙戠エ",
+});
+
+const { proxy } = getCurrentInstance();
+const { tax_rate } = proxy.useDict("tax_rate");
+
+const filters = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ supplierId: "",
+ certifyStatus: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍙戠エ浠g爜", prop: "invoiceCode", width: "130" },
+ { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "120" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "寮�绁ㄦ棩鏈�", prop: "invoiceDate", width: "120" },
+ { label: "閲戦", prop: "amount", slot: "amount" },
+ { label: "绋庨", prop: "taxAmount", slot: "taxAmount" },
+ { label: "浠风◣鍚堣", prop: "totalAmount", slot: "totalAmount" },
+ { label: "璁よ瘉鐘舵��", prop: "certifyStatus", slot: "certifyStatus" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const selectedRows = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const form = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ supplierId: "",
+ invoiceDate: "",
+ amount: 0,
+ taxRate: 13,
+ taxAmount: 0,
+ totalAmount: 0,
+ certifyStatus: "uncertified",
+ certifyDate: "",
+ content: "",
+ remark: "",
+});
+
+const rules = {
+ invoiceCode: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄤ唬鐮�", trigger: "blur" }],
+ invoiceNo: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿鐮�", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ invoiceDate: [{ required: true, message: "璇烽�夋嫨寮�绁ㄦ棩鏈�", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", invoiceDate: "2024-01-08", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, certifyStatus: "certified", certifyDate: "2024-01-15", content: "鍘熸潗鏂欓噰璐�", remark: "" },
+ { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", invoiceDate: "2024-01-10", amount: 12000, taxRate: 13, taxAmount: 1560, totalAmount: 13560, certifyStatus: "uncertified", certifyDate: "", content: "鐢靛瓙鍏冨櫒浠�", remark: "" },
+ { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", invoiceDate: "2024-01-12", amount: 3500, taxRate: 13, taxAmount: 455, totalAmount: 3955, certifyStatus: "certified", certifyDate: "2024-01-18", content: "鍖呰鏉愭枡", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const calculateTax = () => {
+ form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2));
+ form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2));
+};
+
+const getCertifyStatusLabel = (status) => {
+ const map = { uncertified: "鏈璇�", certified: "宸茶璇�", failed: "璁よ瘉澶辫触" };
+ return map[status] || status;
+};
+
+const getCertifyStatusType = (status) => {
+ const map = { uncertified: "info", certified: "success", failed: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.invoiceCode) {
+ result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode));
+ }
+ if (filters.invoiceNo) {
+ result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.certifyStatus) {
+ result = result.filter(item => item.certifyStatus === filters.certifyStatus);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.invoiceCode = "";
+ filters.invoiceNo = "";
+ filters.supplierId = "";
+ filters.certifyStatus = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "褰曞叆鍙戠エ";
+ Object.assign(form, {
+ invoiceCode: "",
+ invoiceNo: "",
+ supplierId: "",
+ invoiceDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ taxRate: 13,
+ taxAmount: 0,
+ totalAmount: 0,
+ certifyStatus: "uncertified",
+ certifyDate: "",
+ content: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍙戠エ";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍙戠エ: ${row.invoiceCode}-${row.invoiceNo}`);
+};
+
+const handleCertify = (row) => {
+ ElMessageBox.confirm("纭璁よ瘉璇ュ彂绁ㄥ悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].certifyStatus = "certified";
+ mockData[index].certifyDate = new Date().toISOString().split('T')[0];
+ }
+ ElMessage.success("璁よ瘉鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleBatchCertify = () => {
+ ElMessageBox.confirm(`纭鎵归噺璁よ瘉閫変腑鐨� ${selectedRows.value.length} 寮犲彂绁ㄥ悧锛焋, "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ selectedRows.value.forEach(row => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1 && mockData[index].certifyStatus === "uncertified") {
+ mockData[index].certifyStatus = "certified";
+ mockData[index].certifyDate = new Date().toISOString().split('T')[0];
+ }
+ });
+ ElMessage.success("鎵归噺璁よ瘉鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name });
+ ElMessage.success("褰曞叆鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/payment.vue b/src/views/financialManagement/payable/payment.vue
new file mode 100644
index 0000000..d4774fe
--- /dev/null
+++ b/src/views/financialManagement/payable/payment.vue
@@ -0,0 +1,377 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="浠樻鍗曞彿:">
+ <el-input v-model="filters.paymentCode" placeholder="璇疯緭鍏ヤ粯娆惧崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠樻鏂瑰紡:">
+ <el-select v-model="filters.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" clearable style="width: 150px;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="寰呬粯娆�" value="pending" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="宸插彇娑�" value="cancelled" />
+ </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>
+ <el-statistic title="鏈湡浠樻鍚堣" :value="totalPaymentAmount" precision="2" prefix="楼" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板浠樻</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #paymentMethod="{ row }">
+ <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="row.status === 'completed' ? 'success' : row.status === 'pending' ? 'warning' : 'info'">
+ {{ row.status === 'completed' ? '宸插畬鎴�' : row.status === 'pending' ? '寰呬粯娆�' : '宸插彇娑�' }}
+ </el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleComplete(row)" v-if="row.status === 'pending'">瀹屾垚</el-button>
+ <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'pending'">鍙栨秷</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻鍗曞彿" prop="paymentCode">
+ <el-input v-model="form.paymentCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏宠仈鐢宠鍗�" prop="applyCode">
+ <el-select v-model="form.applyCode" placeholder="璇烽�夋嫨鍏宠仈鐢宠鍗�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in applyList" :key="item.applyCode" :label="item.applyCode" :value="item.applyCode" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浠樻鏃ユ湡" prop="paymentDate">
+ <el-date-picker v-model="form.paymentDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </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="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閾惰璐﹀彿" prop="bankAccount" v-if="form.paymentMethod === 'bank_transfer'">
+ <el-input v-model="form.bankAccount" placeholder="璇疯緭鍏ラ摱琛岃处鍙�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯" prop="bankName" v-if="form.paymentMethod === 'bank_transfer'">
+ <el-input v-model="form.bankName" placeholder="璇疯緭鍏ュ紑鎴疯" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "浠樻鍗�",
+});
+
+const filters = reactive({
+ paymentCode: "",
+ supplierId: "",
+ paymentMethod: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "浠樻鍗曞彿", prop: "paymentCode", width: "150" },
+ { label: "鍏宠仈鐢宠鍗�", prop: "applyCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "浠樻鏃ユ湡", prop: "paymentDate", width: "120" },
+ { label: "浠樻閲戦", prop: "amount", slot: "amount" },
+ { label: "浠樻鏂瑰紡", prop: "paymentMethod", slot: "paymentMethod" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const applyList = [
+ { applyCode: "FK2024001", supplierId: 1, amount: 5000 },
+ { applyCode: "FK2024002", supplierId: 2, amount: 8000 },
+ { applyCode: "FK2024003", supplierId: 3, amount: 3000 },
+];
+
+const form = reactive({
+ paymentCode: "",
+ applyCode: "",
+ supplierId: "",
+ paymentDate: "",
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ bankAccount: "",
+ bankName: "",
+ remark: "",
+});
+
+const rules = {
+ applyCode: [{ required: true, message: "璇烽�夋嫨鍏宠仈鐢宠鍗�", trigger: "change" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ paymentDate: [{ required: true, message: "璇烽�夋嫨浠樻鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
+ paymentMethod: [{ required: true, message: "璇烽�夋嫨浠樻鏂瑰紡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, paymentCode: "FKD2024001", applyCode: "FK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", paymentDate: "2024-01-15", amount: 5000, paymentMethod: "bank_transfer", status: "completed", bankAccount: "6222021234567890123", bankName: "宸ュ晢閾惰", remark: "" },
+ { id: 2, paymentCode: "FKD2024002", applyCode: "FK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", paymentDate: "2024-01-18", amount: 8000, paymentMethod: "bank_transfer", status: "pending", bankAccount: "6222029876543210987", bankName: "寤鸿閾惰", remark: "" },
+ { id: 3, paymentCode: "FKD2024003", applyCode: "FK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", paymentDate: "2024-01-20", amount: 3000, paymentMethod: "cash", status: "completed", remark: "" },
+];
+
+const totalPaymentAmount = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getPaymentMethodLabel = (method) => {
+ const map = {
+ bank_transfer: "閾惰杞处",
+ cash: "鐜伴噾",
+ check: "鏀エ",
+ draft: "姹囩エ",
+ };
+ return map[method] || method;
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.paymentCode) {
+ result = result.filter(item => item.paymentCode.includes(filters.paymentCode));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.paymentMethod) {
+ result = result.filter(item => item.paymentMethod === filters.paymentMethod);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.paymentCode = "";
+ filters.supplierId = "";
+ filters.paymentMethod = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板浠樻";
+ Object.assign(form, {
+ paymentCode: "FKD" + Date.now().toString().slice(-8),
+ applyCode: "",
+ supplierId: "",
+ paymentDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ bankAccount: "",
+ bankName: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫浠樻";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅浠樻鍗�: ${row.paymentCode}`);
+};
+
+const handleComplete = (row) => {
+ ElMessageBox.confirm("纭璇ヤ粯娆惧崟宸插畬鎴愬悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "completed";
+ }
+ ElMessage.success("浠樻瀹屾垚");
+ getTableData();
+ });
+};
+
+const handleCancel = (row) => {
+ ElMessageBox.confirm("纭鍙栨秷璇ヤ粯娆惧崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "cancelled";
+ }
+ ElMessage.success("宸插彇娑�");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/paymentApply.vue b/src/views/financialManagement/payable/paymentApply.vue
new file mode 100644
index 0000000..fb23db3
--- /dev/null
+++ b/src/views/financialManagement/payable/paymentApply.vue
@@ -0,0 +1,360 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鐢宠鍗曞彿:">
+ <el-input v-model="filters.applyCode" placeholder="璇疯緭鍏ョ敵璇峰崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="寰呭鎵�" value="pending" />
+ <el-option label="宸插鎵�" value="approved" />
+ <el-option label="宸查┏鍥�" value="rejected" />
+ <el-option label="宸蹭粯娆�" value="paid" />
+ </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="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">鎵归噺鐢宠</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 #amount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #paymentMethod="{ row }">
+ <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">瀹℃壒</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢宠鍗曞彿" prop="applyCode">
+ <el-input v-model="form.applyCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </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="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢宠鏃ユ湡" prop="applyDate">
+ <el-date-picker v-model="form.applyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈熸湜浠樻鏃ユ湡" prop="expectedDate">
+ <el-date-picker v-model="form.expectedDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍏宠仈鍏ュ簱鍗�" prop="relatedDocs">
+ <el-select v-model="form.relatedDocs" multiple placeholder="璇烽�夋嫨鍏宠仈鍏ュ簱鍗�" style="width: 100%;">
+ <el-option v-for="item in inList" :key="item.inCode" :label="item.inCode" :value="item.inCode" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠樻浜嬬敱" prop="reason">
+ <el-input v-model="form.reason" type="textarea" :rows="3" placeholder="璇疯緭鍏ヤ粯娆句簨鐢�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "浠樻鐢宠",
+});
+
+const filters = reactive({
+ applyCode: "",
+ supplierId: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鐢宠鍗曞彿", prop: "applyCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "浠樻閲戦", prop: "amount", slot: "amount" },
+ { label: "浠樻鏂瑰紡", prop: "paymentMethod", slot: "paymentMethod" },
+ { label: "鐢宠鏃ユ湡", prop: "applyDate", width: "120" },
+ { label: "鏈熸湜浠樻鏃�", prop: "expectedDate", width: "120" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const selectedRows = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const inList = [
+ { inCode: "RK2024001", supplierId: 1 },
+ { inCode: "RK2024002", supplierId: 2 },
+ { inCode: "RK2024003", supplierId: 3 },
+];
+
+const form = reactive({
+ applyCode: "",
+ supplierId: "",
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ applyDate: "",
+ expectedDate: "",
+ relatedDocs: [],
+ reason: "",
+ remark: "",
+});
+
+const rules = {
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
+ paymentMethod: [{ required: true, message: "璇烽�夋嫨浠樻鏂瑰紡", trigger: "change" }],
+ applyDate: [{ required: true, message: "璇烽�夋嫨鐢宠鏃ユ湡", trigger: "change" }],
+ expectedDate: [{ required: true, message: "璇烽�夋嫨鏈熸湜浠樻鏃ユ湡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, applyCode: "FK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", amount: 5000, paymentMethod: "bank_transfer", applyDate: "2024-01-12", expectedDate: "2024-01-15", status: "pending", relatedDocs: ["RK2024001"], reason: "鏀粯鍘熸潗鏂欒揣娆�", remark: "" },
+ { id: 2, applyCode: "FK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", amount: 8000, paymentMethod: "bank_transfer", applyDate: "2024-01-14", expectedDate: "2024-01-18", status: "approved", relatedDocs: ["RK2024002"], reason: "鏀粯鐢靛瓙鍏冨櫒浠惰揣娆�", remark: "" },
+ { id: 3, applyCode: "FK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", amount: 3000, paymentMethod: "cash", applyDate: "2024-01-16", expectedDate: "2024-01-20", status: "paid", relatedDocs: ["RK2024003"], reason: "鏀粯鍖呰鏉愭枡璐ф", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getPaymentMethodLabel = (method) => {
+ const map = {
+ bank_transfer: "閾惰杞处",
+ cash: "鐜伴噾",
+ check: "鏀エ",
+ draft: "姹囩エ",
+ };
+ return map[method] || method;
+};
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鎵�", approved: "宸插鎵�", rejected: "宸查┏鍥�", paid: "宸蹭粯娆�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger", paid: "primary" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.applyCode) {
+ result = result.filter(item => item.applyCode.includes(filters.applyCode));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.applyCode = "";
+ filters.supplierId = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板浠樻鐢宠";
+ Object.assign(form, {
+ applyCode: "FK" + Date.now().toString().slice(-8),
+ supplierId: "",
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ applyDate: new Date().toISOString().split('T')[0],
+ expectedDate: "",
+ relatedDocs: [],
+ reason: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫浠樻鐢宠";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鐢宠鍗�: ${row.applyCode}`);
+};
+
+const handleAudit = (row) => {
+ ElMessageBox.confirm("纭瀹℃壒閫氳繃璇ヤ粯娆剧敵璇峰悧锛�", "鎻愮ず", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ distinguishCancelAndClose: true,
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "approved";
+ }
+ ElMessage.success("瀹℃壒閫氳繃");
+ getTableData();
+ }).catch((action) => {
+ if (action === "cancel") {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "rejected";
+ }
+ ElMessage.warning("宸查┏鍥�");
+ getTableData();
+ }
+ });
+};
+
+const handleBatchApply = () => {
+ ElMessage.success(`鎵归噺鐢宠 ${selectedRows.value.length} 鏉¤褰昤);
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/purchaseIn.vue b/src/views/financialManagement/payable/purchaseIn.vue
new file mode 100644
index 0000000..4813159
--- /dev/null
+++ b/src/views/financialManagement/payable/purchaseIn.vue
@@ -0,0 +1,331 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍏ュ簱鍗曞彿:">
+ <el-input v-model="filters.inCode" placeholder="璇疯緭鍏ュ叆搴撳崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍏ュ簱鏃ユ湡:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </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 @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @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="inCode">
+ <el-input v-model="form.inCode" placeholder="璇疯緭鍏ュ叆搴撳崟鍙�" :disabled="isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱鏃ユ湡" prop="inDate">
+ <el-date-picker v-model="form.inDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍏ュ簱鏄庣粏" prop="details">
+ <el-table :data="form.details" border style="width: 100%">
+ <el-table-column prop="materialName" label="鐗╂枡鍚嶇О" width="150">
+ <template #default="{ $index }">
+ <el-input v-model="form.details[$index].materialName" placeholder="鐗╂枡鍚嶇О" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="spec" label="瑙勬牸" width="120">
+ <template #default="{ $index }">
+ <el-input v-model="form.details[$index].spec" placeholder="瑙勬牸" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="quantity" label="鏁伴噺" width="100">
+ <template #default="{ $index }">
+ <el-input-number v-model="form.details[$index].quantity" :min="0" style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitPrice" label="鍗曚环" width="120">
+ <template #default="{ $index }">
+ <el-input-number v-model="form.details[$index].unitPrice" :min="0" :precision="2" style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="total" label="閲戦" width="120">
+ <template #default="{ row }">
+ <span>楼{{ formatMoney(row.quantity * row.unitPrice) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80">
+ <template #default="{ $index }">
+ <el-button type="danger" link @click="removeDetail($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-button type="primary" link @click="addDetail" style="margin-top: 10px;">+ 娣诲姞鏄庣粏</el-button>
+ </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>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "閲囪喘鍏ュ簱",
+});
+
+const filters = reactive({
+ inCode: "",
+ supplierId: "",
+ dateRange: [],
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍏ュ簱鍗曞彿", prop: "inCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "鍏ュ簱鏃ユ湡", prop: "inDate", width: "120" },
+ { label: "鍏ュ簱閲戦", prop: "amount", slot: "amount" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const form = reactive({
+ inCode: "",
+ supplierId: "",
+ inDate: "",
+ amount: 0,
+ details: [],
+ remark: "",
+});
+
+const rules = {
+ inCode: [{ required: true, message: "璇疯緭鍏ュ叆搴撳崟鍙�", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ inDate: [{ required: true, message: "璇烽�夋嫨鍏ュ簱鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ュ叆搴撻噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, inCode: "RK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", inDate: "2024-01-10", amount: 8000, status: "approved", details: [{ materialName: "閽㈡潗", spec: "Q235", quantity: 10, unitPrice: 500 }], remark: "" },
+ { id: 2, inCode: "RK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", inDate: "2024-01-12", amount: 12000, status: "pending", details: [{ materialName: "鑺墖", spec: "STM32", quantity: 100, unitPrice: 80 }], remark: "" },
+ { id: 3, inCode: "RK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", inDate: "2024-01-15", amount: 3500, status: "approved", details: [{ materialName: "绾哥", spec: "50*40*30", quantity: 500, unitPrice: 5 }], remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.inCode) {
+ result = result.filter(item => item.inCode.includes(filters.inCode));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.inDate >= filters.dateRange[0] && item.inDate <= filters.dateRange[1]);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.inCode = "";
+ filters.supplierId = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const addDetail = () => {
+ form.details.push({ materialName: "", spec: "", quantity: 0, unitPrice: 0 });
+};
+
+const removeDetail = (index) => {
+ form.details.splice(index, 1);
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍏ュ簱";
+ Object.assign(form, {
+ inCode: "RK" + Date.now().toString().slice(-8),
+ supplierId: "",
+ inDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ details: [{ materialName: "", spec: "", quantity: 0, unitPrice: 0 }],
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍏ュ簱";
+ Object.assign(form, row);
+ if (!form.details || form.details.length === 0) {
+ form.details = [{ materialName: "", spec: "", quantity: 0, unitPrice: 0 }];
+ }
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍏ュ簱鍗�: ${row.inCode}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ュ叆搴撳崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/reconciliation.vue b/src/views/financialManagement/payable/reconciliation.vue
new file mode 100644
index 0000000..3aa23cd
--- /dev/null
+++ b/src/views/financialManagement/payable/reconciliation.vue
@@ -0,0 +1,469 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="瀵硅处鏈熼棿:">
+ <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
+ <span style="margin: 0 10px;">鑷�</span>
+ <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
+ </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>
+ <el-button type="primary" @click="generateStatement" icon="Document">鐢熸垚瀵硅处鍗�</el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭瀵硅处鍗�</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #beginBalance="{ row }">
+ <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.beginBalance) }}</span>
+ </template>
+ <template #currentPayable="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.currentPayable) }}</span>
+ </template>
+ <template #currentPayment="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.currentPayment) }}</span>
+ </template>
+ <template #endBalance="{ row }">
+ <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.endBalance) }}</span>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="viewDetail(row)">鏌ョ湅鏄庣粏</el-button>
+ <el-button type="primary" link @click="printStatement(row)">鎵撳嵃</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog title="瀵硅处鏄庣粏" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail">
+ <div class="statement-header">
+ <h3>{{ currentSupplier }} 搴斾粯瀵硅处鍗�</h3>
+ <p>瀵硅处鏈熼棿: {{ currentPeriod }}</p>
+ </div>
+ <el-table :data="detailData" border style="width: 100%">
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="type" label="绫诲瀷" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.type === '鍏ュ簱' ? 'success' : row.type === '閫�璐�' ? 'danger' : 'primary'">{{ row.type }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="debit" label="鍊熸柟(浠樻)" width="120">
+ <template #default="{ row }">
+ <span v-if="row.debit > 0" class="text-success">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="credit" label="璐锋柟(搴斾粯)" width="120">
+ <template #default="{ row }">
+ <span v-if="row.credit > 0" class="text-danger">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="balance" label="浣欓" width="120">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.balance) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip />
+ </el-table>
+ <template #footer>
+ <el-button type="primary" @click="printDetail">鎵撳嵃</el-button>
+ <el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
+ </template>
+ </FormDialog>
+
+ <FormDialog title="鐢熸垚瀵硅处鍗�" v-model="generateDialogVisible" width="1000px" @confirm="confirmGenerate" @cancel="generateDialogVisible = false">
+ <el-form :model="generateForm" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閫夋嫨渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="generateForm.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" @change="onSupplierChange">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀵硅处鏈堜唤" prop="period">
+ <el-date-picker v-model="generateForm.period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+
+ <div v-if="purchaseData.length > 0" class="purchase-section">
+ <div class="section-title">鏈湀閲囪喘鏁版嵁</div>
+ <el-table :data="purchaseData" border style="width: 100%; margin-bottom: 15px;" v-loading="purchaseLoading" @selection-change="handlePurchaseSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="type" label="绫诲瀷" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.type === '鍏ュ簱' ? 'success' : 'danger'">{{ row.type }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="amount" label="閲戦" width="120">
+ <template #default="{ row }">
+ <span :class="row.type === '鍏ュ簱' ? 'text-danger' : 'text-success'">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" />
+ </el-table>
+
+ <div class="summary-row">
+ <span>鏈熷垵浣欓: <strong class="text-primary">楼{{ formatMoney(generateForm.beginBalance) }}</strong></span>
+ <span>鏈湡搴斾粯: <strong class="text-danger">楼{{ formatMoney(generateForm.currentPayable) }}</strong></span>
+ <span>鏈湡浠樻: <strong class="text-success">楼{{ formatMoney(generateForm.currentPayment) }}</strong></span>
+ <span>鏈熸湯浣欓: <strong class="text-primary">楼{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment)) }}</strong></span>
+ </div>
+ </div>
+
+ <div v-else-if="generateForm.supplierId && !purchaseLoading" class="empty-tip">
+ <el-empty description="璇ヤ緵搴斿晢鏈湀鏆傛棤閲囪喘鏁版嵁" />
+ </div>
+
+ <template #footer>
+ <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">纭鐢熸垚</el-button>
+ <el-button @click="generateDialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "搴斾粯瀵硅处",
+});
+
+const filters = reactive({
+ supplierId: "",
+ startMonth: "",
+ endMonth: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "瀵硅处鍗曞彿", prop: "statementCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "瀵硅处鏈熼棿", prop: "period", width: "150" },
+ { label: "鏈熷垵浣欓", prop: "beginBalance", slot: "beginBalance" },
+ { label: "鏈湡搴斾粯", prop: "currentPayable", slot: "currentPayable" },
+ { label: "鏈湡浠樻", prop: "currentPayment", slot: "currentPayment" },
+ { label: "鏈熸湯浣欓", prop: "endBalance", slot: "endBalance" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+];
+
+const dataList = ref([]);
+const detailDialogVisible = ref(false);
+const currentSupplier = ref("");
+const currentPeriod = ref("");
+const detailData = ref([]);
+
+const generateDialogVisible = ref(false);
+const purchaseLoading = ref(false);
+const purchaseData = ref([]);
+const selectedPurchases = ref([]);
+
+const generateForm = reactive({
+ supplierId: "",
+ supplierName: "",
+ period: "",
+ beginBalance: 0,
+ currentPayable: 0,
+ currentPayment: 0,
+});
+
+const canGenerate = computed(() => {
+ return generateForm.supplierId && generateForm.period && selectedPurchases.value.length > 0;
+});
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const mockData = [
+ { id: 1, statementCode: "DZ202401001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", period: "2024-01", beginBalance: 20000, currentPayable: 15000, currentPayment: 10000, endBalance: 25000 },
+ { id: 2, statementCode: "DZ202401002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", period: "2024-01", beginBalance: 10000, currentPayable: 20000, currentPayment: 15000, endBalance: 15000 },
+ { id: 3, statementCode: "DZ202402001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", period: "2024-02", beginBalance: 25000, currentPayable: 18000, currentPayment: 20000, endBalance: 23000 },
+];
+
+const calculateEndBalance = (beginBalance, currentPayable, currentPayment) => {
+ return beginBalance + currentPayable - currentPayment;
+};
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.startMonth && filters.endMonth) {
+ result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.supplierId = "";
+ filters.startMonth = "";
+ filters.endMonth = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const generateStatement = () => {
+ generateForm.supplierId = "";
+ generateForm.supplierName = "";
+ generateForm.period = "";
+ generateForm.beginBalance = 0;
+ generateForm.currentPayable = 0;
+ generateForm.currentPayment = 0;
+ purchaseData.value = [];
+ selectedPurchases.value = [];
+ generateDialogVisible.value = true;
+};
+
+const onSupplierChange = (supplierId) => {
+ const supplier = supplierList.find(item => item.id === supplierId);
+ if (supplier) {
+ generateForm.supplierName = supplier.name;
+ }
+ loadPurchaseData();
+};
+
+const onPeriodChange = () => {
+ loadPurchaseData();
+};
+
+const loadPurchaseData = () => {
+ if (!generateForm.supplierId || !generateForm.period) {
+ purchaseData.value = [];
+ return;
+ }
+
+ purchaseLoading.value = true;
+
+ setTimeout(() => {
+ const mockPurchaseData = [
+ { id: 1, date: generateForm.period + "-05", code: "RK2024001", type: "鍏ュ簱", amount: 8000, remark: "鍘熸潗鏂欓噰璐�" },
+ { id: 2, date: generateForm.period + "-10", code: "FK2024001", type: "浠樻", amount: 5000, remark: "鏀粯璐ф" },
+ { id: 3, date: generateForm.period + "-15", code: "RK2024002", type: "鍏ュ簱", amount: 12000, remark: "鐢靛瓙鍏冨櫒浠�" },
+ { id: 4, date: generateForm.period + "-18", code: "TH2024001", type: "閫�璐�", amount: 2000, remark: "璐ㄩ噺闂閫�璐�" },
+ { id: 5, date: generateForm.period + "-22", code: "RK2024003", type: "鍏ュ簱", amount: 6000, remark: "鍖呰鏉愭枡" },
+ { id: 6, date: generateForm.period + "-25", code: "FK2024002", type: "浠樻", amount: 8000, remark: "鏀粯璐ф" },
+ ];
+
+ purchaseData.value = mockPurchaseData;
+
+ const lastPeriod = getLastPeriod(generateForm.period);
+ const lastStatement = mockData.find(item =>
+ item.supplierId === generateForm.supplierId && item.period === lastPeriod
+ );
+ generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0;
+
+ calculateSummary();
+
+ purchaseLoading.value = false;
+ }, 500);
+};
+
+const getLastPeriod = (period) => {
+ const [year, month] = period.split("-").map(Number);
+ if (month === 1) {
+ return `${year - 1}-12`;
+ }
+ return `${year}-${String(month - 1).padStart(2, "0")}`;
+};
+
+const calculateSummary = () => {
+ let payable = 0;
+ let payment = 0;
+
+ selectedPurchases.value.forEach(item => {
+ if (item.type === "鍏ュ簱") {
+ payable += item.amount;
+ } else if (item.type === "閫�璐�") {
+ payable -= item.amount;
+ } else if (item.type === "浠樻") {
+ payment += item.amount;
+ }
+ });
+
+ generateForm.currentPayable = payable;
+ generateForm.currentPayment = payment;
+};
+
+const handlePurchaseSelectionChange = (selection) => {
+ selectedPurchases.value = selection;
+ calculateSummary();
+};
+
+const confirmGenerate = () => {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment);
+
+ mockData.unshift({
+ id: newId,
+ statementCode: "DZ" + Date.now(),
+ supplierId: generateForm.supplierId,
+ supplierName: generateForm.supplierName,
+ period: generateForm.period,
+ beginBalance: generateForm.beginBalance,
+ currentPayable: generateForm.currentPayable,
+ currentPayment: generateForm.currentPayment,
+ endBalance,
+ });
+
+ generateDialogVisible.value = false;
+ ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
+ getTableData();
+};
+
+const viewDetail = (row) => {
+ currentSupplier.value = row.supplierName;
+ currentPeriod.value = row.period;
+
+ const purchaseInAmount = Math.floor(row.currentPayable * 0.7);
+ const returnAmount = Math.floor(row.currentPayable * 0.1);
+ const firstPayment = Math.floor(row.currentPayment * 0.5);
+ const secondPayment = row.currentPayment - firstPayment;
+
+ let runningBalance = row.beginBalance;
+
+ detailData.value = [
+ { date: row.period + "-01", type: "鏈熷垵", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "鏈熷垵浣欓" },
+ { date: row.period + "-05", type: "鍏ュ簱", code: "RK2024001", debit: 0, credit: purchaseInAmount, balance: runningBalance += purchaseInAmount, remark: "閲囪喘鍏ュ簱" },
+ { date: row.period + "-10", type: "浠樻", code: "FK2024001", debit: firstPayment, credit: 0, balance: runningBalance -= firstPayment, remark: "鏀粯璐ф" },
+ { date: row.period + "-15", type: "鍏ュ簱", code: "RK2024002", debit: 0, credit: row.currentPayable - purchaseInAmount - returnAmount, balance: runningBalance += (row.currentPayable - purchaseInAmount - returnAmount), remark: "閲囪喘鍏ュ簱" },
+ { date: row.period + "-20", type: "閫�璐�", code: "TH2024001", debit: 0, credit: -returnAmount, balance: runningBalance -= returnAmount, remark: "閲囪喘閫�璐�" },
+ { date: row.period + "-25", type: "浠樻", code: "FK2024002", debit: secondPayment, credit: 0, balance: runningBalance -= secondPayment, remark: "鏀粯璐ф" },
+ ];
+
+ detailDialogVisible.value = true;
+};
+
+const printStatement = (row) => {
+ ElMessage.info(`鎵撳嵃瀵硅处鍗�: ${row.statementCode}`);
+};
+
+const printDetail = () => {
+ ElMessage.info("鎵撳嵃鏄庣粏");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-success {
+ color: #67c23a;
+}
+
+.text-danger {
+ color: #f56c6c;
+}
+
+.statement-header {
+ text-align: center;
+ margin-bottom: 20px;
+ h3 {
+ margin: 0 0 10px 0;
+ }
+ p {
+ color: #909399;
+ margin: 0;
+ }
+}
+
+.purchase-section {
+ margin-top: 20px;
+
+ .section-title {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 15px;
+ padding-left: 10px;
+ border-left: 4px solid #409eff;
+ }
+}
+
+.summary-row {
+ display: flex;
+ justify-content: space-around;
+ padding: 15px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ margin-top: 15px;
+
+ span {
+ font-size: 14px;
+
+ strong {
+ font-size: 16px;
+ margin-left: 5px;
+ }
+ }
+}
+
+.empty-tip {
+ margin-top: 30px;
+}
+
+.text-primary {
+ color: #409eff;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/invoiceApply.vue b/src/views/financialManagement/receivable/invoiceApply.vue
new file mode 100644
index 0000000..2a4bd81
--- /dev/null
+++ b/src/views/financialManagement/receivable/invoiceApply.vue
@@ -0,0 +1,363 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鐢宠鍗曞彿:">
+ <el-input v-model="filters.applyCode" placeholder="璇疯緭鍏ョ敵璇峰崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="寰呭鏍�" value="pending" />
+ <el-option label="宸插鏍�" value="approved" />
+ <el-option label="宸查┏鍥�" value="rejected" />
+ <el-option label="宸插紑绁�" value="invoiced" />
+ </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="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">鎵归噺鐢宠</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 #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #taxRate="{ row }">
+ <span>{{ row.taxRate }}%</span>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">瀹℃牳</el-button>
+ <el-button type="warning" link @click="handleInvoice(row)" v-if="row.status === 'approved'">寮�绁�</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢宠鍗曞彿" prop="applyCode">
+ <el-input v-model="form.applyCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="寮�绁ㄩ噾棰�" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绋庣巼" prop="taxRate">
+ <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;">
+ <el-option
+ v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="Number(dict.value)"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
+ <el-select v-model="form.invoiceType" placeholder="璇烽�夋嫨鍙戠エ绫诲瀷" style="width: 100%;">
+ <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="special" />
+ <el-option label="澧炲�肩◣鏅�氬彂绁�" value="normal" />
+ <el-option label="鐢靛瓙鍙戠エ" value="electronic" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐢宠鏃ユ湡" prop="applyDate">
+ <el-date-picker v-model="form.applyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍙戠エ鍐呭" prop="content">
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "寮�绁ㄧ敵璇�",
+});
+
+const { proxy } = getCurrentInstance();
+const { tax_rate } = proxy.useDict("tax_rate");
+
+const filters = reactive({
+ applyCode: "",
+ customerId: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鐢宠鍗曞彿", prop: "applyCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "寮�绁ㄩ噾棰�", prop: "amount", slot: "amount" },
+ { label: "绋庣巼", prop: "taxRate", slot: "taxRate" },
+ { label: "鍙戠エ绫诲瀷", prop: "invoiceTypeLabel", width: "130" },
+ { label: "鐢宠鏃ユ湡", prop: "applyDate", width: "120" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const selectedRows = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const form = reactive({
+ applyCode: "",
+ customerId: "",
+ amount: 0,
+ taxRate: 13,
+ invoiceType: "special",
+ applyDate: "",
+ content: "",
+ remark: "",
+});
+
+const rules = {
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ュ紑绁ㄩ噾棰�", trigger: "blur" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨鍙戠エ绫诲瀷", trigger: "change" }],
+ applyDate: [{ required: true, message: "璇烽�夋嫨鐢宠鏃ユ湡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, applyCode: "KP2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", amount: 5000, taxRate: 13, invoiceType: "special", invoiceTypeLabel: "澧炲�肩◣涓撶敤鍙戠エ", applyDate: "2024-01-15", status: "pending", content: "杞欢鏈嶅姟璐�", remark: "" },
+ { id: 2, applyCode: "KP2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", amount: 8000, taxRate: 13, invoiceType: "normal", invoiceTypeLabel: "澧炲�肩◣鏅�氬彂绁�", applyDate: "2024-01-16", status: "approved", content: "鍟嗗搧閿�鍞�", remark: "" },
+ { id: 3, applyCode: "KP2024003", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", amount: 12000, taxRate: 6, invoiceType: "electronic", invoiceTypeLabel: "鐢靛瓙鍙戠エ", applyDate: "2024-01-18", status: "invoiced", content: "鎶�鏈湇鍔¤垂", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�", invoiced: "宸插紑绁�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger", invoiced: "primary" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.applyCode) {
+ result = result.filter(item => item.applyCode.includes(filters.applyCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.applyCode = "";
+ filters.customerId = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板寮�绁ㄧ敵璇�";
+ Object.assign(form, {
+ applyCode: "KP" + Date.now().toString().slice(-8),
+ customerId: "",
+ amount: 0,
+ taxRate: 13,
+ invoiceType: "special",
+ applyDate: new Date().toISOString().split('T')[0],
+ content: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫寮�绁ㄧ敵璇�";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鐢宠鍗�: ${row.applyCode}`);
+};
+
+const handleAudit = (row) => {
+ ElMessageBox.confirm("纭瀹℃牳閫氳繃璇ュ紑绁ㄧ敵璇峰悧锛�", "鎻愮ず", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ distinguishCancelAndClose: true,
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "approved";
+ }
+ ElMessage.success("瀹℃牳閫氳繃");
+ getTableData();
+ }).catch((action) => {
+ if (action === "cancel") {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "rejected";
+ }
+ ElMessage.warning("宸查┏鍥�");
+ getTableData();
+ }
+ });
+};
+
+const handleInvoice = (row) => {
+ ElMessageBox.confirm("纭宸插紑鍏峰彂绁紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "invoiced";
+ }
+ ElMessage.success("寮�绁ㄥ畬鎴�");
+ getTableData();
+ });
+};
+
+const handleBatchApply = () => {
+ ElMessage.success(`鎵归噺鐢宠 ${selectedRows.value.length} 鏉¤褰昤);
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ const invoiceTypeMap = { special: "澧炲�肩◣涓撶敤鍙戠エ", normal: "澧炲�肩◣鏅�氬彂绁�", electronic: "鐢靛瓙鍙戠エ" };
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType], status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/outputInvoice.vue b/src/views/financialManagement/receivable/outputInvoice.vue
new file mode 100644
index 0000000..3e597db
--- /dev/null
+++ b/src/views/financialManagement/receivable/outputInvoice.vue
@@ -0,0 +1,373 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍙戠エ浠g爜:">
+ <el-input v-model="filters.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="鍙戠エ鍙风爜:">
+ <el-input v-model="filters.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </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="handleImport" icon="Upload">瀵煎叆</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #taxAmount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.taxAmount) }}</span>
+ </template>
+ <template #totalAmount="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.totalAmount) }}</span>
+ </template>
+ <template #invoiceType="{ row }">
+ <el-tag :type="row.invoiceType === 'special' ? 'danger' : 'primary'">{{ row.invoiceTypeLabel }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">浣滃簾</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ浠g爜" prop="invoiceCode">
+ <el-input v-model="form.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
+ <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
+ <el-date-picker v-model="form.invoiceDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
+ <el-select v-model="form.invoiceType" placeholder="璇烽�夋嫨鍙戠エ绫诲瀷" style="width: 100%;" @change="handleInvoiceTypeChange">
+ <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="special" />
+ <el-option label="澧炲�肩◣鏅�氬彂绁�" value="normal" />
+ <el-option label="鐢靛瓙鍙戠エ" value="electronic" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绋庣巼" prop="taxRate">
+ <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;" @change="calculateTax">
+ <el-option
+ v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="Number(dict.value)"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="绋庨">
+ <el-input v-model="form.taxAmount" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="浠风◣鍚堣">
+ <el-input v-model="form.totalAmount" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍙戠エ鍐呭" prop="content">
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed, getCurrentInstance } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "閿�椤瑰彂绁�",
+});
+
+const { proxy } = getCurrentInstance();
+const { tax_rate } = proxy.useDict("tax_rate");
+
+const filters = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ customerId: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍙戠エ浠g爜", prop: "invoiceCode", width: "130" },
+ { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "120" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "寮�绁ㄦ棩鏈�", prop: "invoiceDate", width: "120" },
+ { label: "閲戦", prop: "amount", slot: "amount" },
+ { label: "绋庨", prop: "taxAmount", slot: "taxAmount" },
+ { label: "浠风◣鍚堣", prop: "totalAmount", slot: "totalAmount" },
+ { label: "鍙戠エ绫诲瀷", prop: "invoiceType", slot: "invoiceType" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const form = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ customerId: "",
+ invoiceDate: "",
+ invoiceType: "special",
+ taxRate: 13,
+ amount: 0,
+ taxAmount: 0,
+ totalAmount: 0,
+ content: "",
+ remark: "",
+});
+
+const rules = {
+ invoiceCode: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄤ唬鐮�", trigger: "blur" }],
+ invoiceNo: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿鐮�", trigger: "blur" }],
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ invoiceDate: [{ required: true, message: "璇烽�夋嫨寮�绁ㄦ棩鏈�", trigger: "change" }],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨鍙戠エ绫诲瀷", trigger: "change" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", invoiceDate: "2024-01-15", amount: 5000, taxRate: 13, taxAmount: 650, totalAmount: 5650, invoiceType: "special", invoiceTypeLabel: "澧炲�肩◣涓撶敤鍙戠エ", content: "杞欢鏈嶅姟璐�", remark: "" },
+ { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", invoiceDate: "2024-01-16", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, invoiceType: "normal", invoiceTypeLabel: "澧炲�肩◣鏅�氬彂绁�", content: "鍟嗗搧閿�鍞�", remark: "" },
+ { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", invoiceDate: "2024-01-18", amount: 12000, taxRate: 6, taxAmount: 720, totalAmount: 12720, invoiceType: "electronic", invoiceTypeLabel: "鐢靛瓙鍙戠エ", content: "鎶�鏈湇鍔¤垂", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const calculateTax = () => {
+ form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2));
+ form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2));
+};
+
+const handleInvoiceTypeChange = () => {
+ if (form.invoiceType === "special") {
+ form.taxRate = 13;
+ } else {
+ form.taxRate = 13;
+ }
+ calculateTax();
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.invoiceCode) {
+ result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode));
+ }
+ if (filters.invoiceNo) {
+ result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.invoiceCode = "";
+ filters.invoiceNo = "";
+ filters.customerId = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "褰曞叆鍙戠エ";
+ Object.assign(form, {
+ invoiceCode: "",
+ invoiceNo: "",
+ customerId: "",
+ invoiceDate: new Date().toISOString().split('T')[0],
+ invoiceType: "special",
+ taxRate: 13,
+ amount: 0,
+ taxAmount: 0,
+ totalAmount: 0,
+ content: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍙戠エ";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍙戠エ: ${row.invoiceCode}-${row.invoiceNo}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭浣滃簾璇ュ彂绁ㄥ悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("浣滃簾鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleImport = () => {
+ ElMessage.info("瀵煎叆鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ const invoiceTypeMap = { special: "澧炲�肩◣涓撶敤鍙戠エ", normal: "澧炲�肩◣鏅�氬彂绁�", electronic: "鐢靛瓙鍙戠エ" };
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] });
+ ElMessage.success("褰曞叆鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/receipt.vue b/src/views/financialManagement/receivable/receipt.vue
new file mode 100644
index 0000000..2bbbb96
--- /dev/null
+++ b/src/views/financialManagement/receivable/receipt.vue
@@ -0,0 +1,356 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鏀舵鍗曞彿:">
+ <el-input v-model="filters.receiptCode" placeholder="璇疯緭鍏ユ敹娆惧崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏀舵鏂瑰紡:">
+ <el-select v-model="filters.receiptMethod" placeholder="璇烽�夋嫨鏀舵鏂瑰紡" clearable style="width: 150px;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ <el-option label="鏀粯瀹�" value="alipay" />
+ <el-option label="寰俊" value="wechat" />
+ </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>
+ <el-statistic title="鏈湡鏀舵鍚堣" :value="totalReceiptAmount" precision="2" prefix="楼" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鏀舵</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #receiptMethod="{ row }">
+ <el-tag>{{ getReceiptMethodLabel(row.receiptMethod) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'">{{ row.status === 'confirmed' ? '宸茬‘璁�' : '寰呯‘璁�' }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleConfirm(row)" v-if="row.status === 'pending'">纭</el-button>
+ <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏀舵鍗曞彿" prop="receiptCode">
+ <el-input v-model="form.receiptCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏀舵鏃ユ湡" prop="receiptDate">
+ <el-date-picker v-model="form.receiptDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏀舵閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏀舵鏂瑰紡" prop="receiptMethod">
+ <el-select v-model="form.receiptMethod" placeholder="璇烽�夋嫨鏀舵鏂瑰紡" style="width: 100%;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ <el-option label="鏀粯瀹�" value="alipay" />
+ <el-option label="寰俊" value="wechat" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閾惰璐﹀彿" prop="bankAccount" v-if="form.receiptMethod === 'bank_transfer'">
+ <el-input v-model="form.bankAccount" placeholder="璇疯緭鍏ラ摱琛岃处鍙�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍏宠仈鍗曟嵁" prop="relatedDocs">
+ <el-select v-model="form.relatedDocs" multiple placeholder="璇烽�夋嫨鍏宠仈鍗曟嵁" style="width: 100%;">
+ <el-option v-for="item in outList" :key="item.outCode" :label="item.outCode" :value="item.outCode" />
+ </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>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "鏀舵鍗�",
+});
+
+const filters = reactive({
+ receiptCode: "",
+ customerId: "",
+ receiptMethod: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鏀舵鍗曞彿", prop: "receiptCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "鏀舵鏃ユ湡", prop: "receiptDate", width: "120" },
+ { label: "鏀舵閲戦", prop: "amount", slot: "amount" },
+ { label: "鏀舵鏂瑰紡", prop: "receiptMethod", slot: "receiptMethod" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const outList = [
+ { outCode: "CK2024001", customerId: 1 },
+ { outCode: "CK2024002", customerId: 2 },
+ { outCode: "CK2024003", customerId: 3 },
+];
+
+const form = reactive({
+ receiptCode: "",
+ customerId: "",
+ receiptDate: "",
+ amount: 0,
+ receiptMethod: "bank_transfer",
+ bankAccount: "",
+ relatedDocs: [],
+ remark: "",
+});
+
+const rules = {
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ receiptDate: [{ required: true, message: "璇烽�夋嫨鏀舵鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ユ敹娆鹃噾棰�", trigger: "blur" }],
+ receiptMethod: [{ required: true, message: "璇烽�夋嫨鏀舵鏂瑰紡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, receiptCode: "SK2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", receiptDate: "2024-01-16", amount: 3000, receiptMethod: "bank_transfer", status: "confirmed", relatedDocs: ["CK2024001"], remark: "" },
+ { id: 2, receiptCode: "SK2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", receiptDate: "2024-01-18", amount: 5000, receiptMethod: "cash", status: "pending", relatedDocs: ["CK2024002"], remark: "" },
+ { id: 3, receiptCode: "SK2024003", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", receiptDate: "2024-01-20", amount: 8000, receiptMethod: "alipay", status: "confirmed", relatedDocs: ["CK2024003"], remark: "" },
+];
+
+const totalReceiptAmount = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getReceiptMethodLabel = (method) => {
+ const map = {
+ bank_transfer: "閾惰杞处",
+ cash: "鐜伴噾",
+ check: "鏀エ",
+ draft: "姹囩エ",
+ alipay: "鏀粯瀹�",
+ wechat: "寰俊",
+ };
+ return map[method] || method;
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.receiptCode) {
+ result = result.filter(item => item.receiptCode.includes(filters.receiptCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.receiptMethod) {
+ result = result.filter(item => item.receiptMethod === filters.receiptMethod);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.receiptCode = "";
+ filters.customerId = "";
+ filters.receiptMethod = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鏀舵";
+ Object.assign(form, {
+ receiptCode: "SK" + Date.now().toString().slice(-8),
+ customerId: "",
+ receiptDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ receiptMethod: "bank_transfer",
+ bankAccount: "",
+ relatedDocs: [],
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鏀舵";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鏀舵鍗�: ${row.receiptCode}`);
+};
+
+const handleConfirm = (row) => {
+ ElMessageBox.confirm("纭璇ユ敹娆惧崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "confirmed";
+ }
+ ElMessage.success("纭鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ユ敹娆惧崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/reconciliation.vue b/src/views/financialManagement/receivable/reconciliation.vue
new file mode 100644
index 0000000..883e12e
--- /dev/null
+++ b/src/views/financialManagement/receivable/reconciliation.vue
@@ -0,0 +1,469 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="瀵硅处鏈熼棿:">
+ <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
+ <span style="margin: 0 10px;">鑷�</span>
+ <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
+ </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>
+ <el-button type="primary" @click="generateStatement" icon="Document">鐢熸垚瀵硅处鍗�</el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭瀵硅处鍗�</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #beginBalance="{ row }">
+ <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.beginBalance) }}</span>
+ </template>
+ <template #currentReceivable="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.currentReceivable) }}</span>
+ </template>
+ <template #currentReceipt="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.currentReceipt) }}</span>
+ </template>
+ <template #endBalance="{ row }">
+ <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.endBalance) }}</span>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="viewDetail(row)">鏌ョ湅鏄庣粏</el-button>
+ <el-button type="primary" link @click="printStatement(row)">鎵撳嵃</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog title="瀵硅处鏄庣粏" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail">
+ <div class="statement-header">
+ <h3>{{ currentCustomer }} 搴旀敹瀵硅处鍗�</h3>
+ <p>瀵硅处鏈熼棿: {{ currentPeriod }}</p>
+ </div>
+ <el-table :data="detailData" border style="width: 100%">
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="type" label="绫诲瀷" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.type === '鍑哄簱' ? 'success' : row.type === '閫�璐�' ? 'danger' : 'primary'">{{ row.type }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="debit" label="鍊熸柟(搴旀敹)" width="120">
+ <template #default="{ row }">
+ <span v-if="row.debit > 0" class="text-danger">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="credit" label="璐锋柟(鏀舵)" width="120">
+ <template #default="{ row }">
+ <span v-if="row.credit > 0" class="text-success">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="balance" label="浣欓" width="120">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.balance) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip />
+ </el-table>
+ <template #footer>
+ <el-button type="primary" @click="printDetail">鎵撳嵃</el-button>
+ <el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
+ </template>
+ </FormDialog>
+
+ <FormDialog title="鐢熸垚瀵硅处鍗�" v-model="generateDialogVisible" width="1000px" @confirm="confirmGenerate" @cancel="generateDialogVisible = false">
+ <el-form :model="generateForm" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閫夋嫨瀹㈡埛" prop="customerId">
+ <el-select v-model="generateForm.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" @change="onCustomerChange">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀵硅处鏈堜唤" prop="period">
+ <el-date-picker v-model="generateForm.period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+
+ <div v-if="salesData.length > 0" class="sales-section">
+ <div class="section-title">鏈湀閿�鍞暟鎹�</div>
+ <el-table :data="salesData" border style="width: 100%; margin-bottom: 15px;" v-loading="salesLoading" @selection-change="handleSalesSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="type" label="绫诲瀷" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.type === '鍑哄簱' ? 'success' : row.type === '鏀舵' ? 'primary' : 'danger'">{{ row.type }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="amount" label="閲戦" width="120">
+ <template #default="{ row }">
+ <span :class="row.type === '鍑哄簱' ? 'text-primary' : row.type === '鏀舵' ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" />
+ </el-table>
+
+ <div class="summary-row">
+ <span>鏈熷垵浣欓: <strong class="text-primary">楼{{ formatMoney(generateForm.beginBalance) }}</strong></span>
+ <span>鏈湡搴旀敹: <strong class="text-primary">楼{{ formatMoney(generateForm.currentReceivable) }}</strong></span>
+ <span>鏈湡鏀舵: <strong class="text-success">楼{{ formatMoney(generateForm.currentReceipt) }}</strong></span>
+ <span>鏈熸湯浣欓: <strong :class="calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt) >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt)) }}</strong></span>
+ </div>
+ </div>
+
+ <div v-else-if="generateForm.customerId && !salesLoading" class="empty-tip">
+ <el-empty description="璇ュ鎴锋湰鏈堟殏鏃犻攢鍞暟鎹�" />
+ </div>
+
+ <template #footer>
+ <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">纭鐢熸垚</el-button>
+ <el-button @click="generateDialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "搴旀敹瀵硅处",
+});
+
+const filters = reactive({
+ customerId: "",
+ startMonth: "",
+ endMonth: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "瀵硅处鍗曞彿", prop: "statementCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "瀵硅处鏈熼棿", prop: "period", width: "150" },
+ { label: "鏈熷垵浣欓", prop: "beginBalance", slot: "beginBalance" },
+ { label: "鏈湡搴旀敹", prop: "currentReceivable", slot: "currentReceivable" },
+ { label: "鏈湡鏀舵", prop: "currentReceipt", slot: "currentReceipt" },
+ { label: "鏈熸湯浣欓", prop: "endBalance", slot: "endBalance" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+];
+
+const dataList = ref([]);
+const detailDialogVisible = ref(false);
+const currentCustomer = ref("");
+const currentPeriod = ref("");
+const detailData = ref([]);
+
+const generateDialogVisible = ref(false);
+const salesLoading = ref(false);
+const salesData = ref([]);
+const selectedSales = ref([]);
+
+const generateForm = reactive({
+ customerId: "",
+ customerName: "",
+ period: "",
+ beginBalance: 0,
+ currentReceivable: 0,
+ currentReceipt: 0,
+});
+
+const canGenerate = computed(() => {
+ return generateForm.customerId && generateForm.period && selectedSales.value.length > 0;
+});
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const mockData = [
+ { id: 1, statementCode: "DZ202401001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", period: "2024-01", beginBalance: 10000, currentReceivable: 15000, currentReceipt: 8000, endBalance: 17000 },
+ { id: 2, statementCode: "DZ202401002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", period: "2024-01", beginBalance: 5000, currentReceivable: 12000, currentReceipt: 10000, endBalance: 7000 },
+ { id: 3, statementCode: "DZ202402001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", period: "2024-02", beginBalance: 17000, currentReceivable: 20000, currentReceipt: 15000, endBalance: 22000 },
+];
+
+const calculateEndBalance = (beginBalance, currentReceivable, currentReceipt) => {
+ return beginBalance + currentReceivable - currentReceipt;
+};
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.startMonth && filters.endMonth) {
+ result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.customerId = "";
+ filters.startMonth = "";
+ filters.endMonth = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const generateStatement = () => {
+ generateForm.customerId = "";
+ generateForm.customerName = "";
+ generateForm.period = "";
+ generateForm.beginBalance = 0;
+ generateForm.currentReceivable = 0;
+ generateForm.currentReceipt = 0;
+ salesData.value = [];
+ selectedSales.value = [];
+ generateDialogVisible.value = true;
+};
+
+const onCustomerChange = (customerId) => {
+ const customer = customerList.find(item => item.id === customerId);
+ if (customer) {
+ generateForm.customerName = customer.name;
+ }
+ loadSalesData();
+};
+
+const onPeriodChange = () => {
+ loadSalesData();
+};
+
+const loadSalesData = () => {
+ if (!generateForm.customerId || !generateForm.period) {
+ salesData.value = [];
+ return;
+ }
+
+ salesLoading.value = true;
+
+ setTimeout(() => {
+ const mockSalesData = [
+ { id: 1, date: generateForm.period + "-03", code: "CK2024001", type: "鍑哄簱", amount: 8000, remark: "浜у搧A閿�鍞�" },
+ { id: 2, date: generateForm.period + "-08", code: "SK2024001", type: "鏀舵", amount: 5000, remark: "瀹㈡埛鍥炴" },
+ { id: 3, date: generateForm.period + "-12", code: "CK2024002", type: "鍑哄簱", amount: 12000, remark: "浜у搧B閿�鍞�" },
+ { id: 4, date: generateForm.period + "-15", code: "TH2024001", type: "閫�璐�", amount: 2000, remark: "璐ㄩ噺闂閫�璐�" },
+ { id: 5, date: generateForm.period + "-20", code: "CK2024003", type: "鍑哄簱", amount: 5000, remark: "浜у搧C閿�鍞�" },
+ { id: 6, date: generateForm.period + "-25", code: "SK2024002", type: "鏀舵", amount: 8000, remark: "瀹㈡埛鍥炴" },
+ ];
+
+ salesData.value = mockSalesData;
+
+ const lastPeriod = getLastPeriod(generateForm.period);
+ const lastStatement = mockData.find(item =>
+ item.customerId === generateForm.customerId && item.period === lastPeriod
+ );
+ generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0;
+
+ calculateSummary();
+
+ salesLoading.value = false;
+ }, 500);
+};
+
+const getLastPeriod = (period) => {
+ const [year, month] = period.split("-").map(Number);
+ if (month === 1) {
+ return `${year - 1}-12`;
+ }
+ return `${year}-${String(month - 1).padStart(2, "0")}`;
+};
+
+const calculateSummary = () => {
+ let receivable = 0;
+ let receipt = 0;
+
+ selectedSales.value.forEach(item => {
+ if (item.type === "鍑哄簱") {
+ receivable += item.amount;
+ } else if (item.type === "閫�璐�") {
+ receivable -= item.amount;
+ } else if (item.type === "鏀舵") {
+ receipt += item.amount;
+ }
+ });
+
+ generateForm.currentReceivable = receivable;
+ generateForm.currentReceipt = receipt;
+};
+
+const handleSalesSelectionChange = (selection) => {
+ selectedSales.value = selection;
+ calculateSummary();
+};
+
+const confirmGenerate = () => {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt);
+
+ mockData.unshift({
+ id: newId,
+ statementCode: "DZ" + Date.now(),
+ customerId: generateForm.customerId,
+ customerName: generateForm.customerName,
+ period: generateForm.period,
+ beginBalance: generateForm.beginBalance,
+ currentReceivable: generateForm.currentReceivable,
+ currentReceipt: generateForm.currentReceipt,
+ endBalance,
+ });
+
+ generateDialogVisible.value = false;
+ ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
+ getTableData();
+};
+
+const viewDetail = (row) => {
+ currentCustomer.value = row.customerName;
+ currentPeriod.value = row.period;
+
+ const saleOutAmount = Math.floor(row.currentReceivable * 0.6);
+ const returnAmount = Math.floor(row.currentReceivable * 0.1);
+ const firstReceipt = Math.floor(row.currentReceipt * 0.4);
+ const secondReceipt = row.currentReceipt - firstReceipt;
+
+ let runningBalance = row.beginBalance;
+
+ detailData.value = [
+ { date: row.period + "-01", type: "鏈熷垵", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "鏈熷垵浣欓" },
+ { date: row.period + "-05", type: "鍑哄簱", code: "CK2024001", debit: saleOutAmount, credit: 0, balance: runningBalance += saleOutAmount, remark: "閿�鍞嚭搴�" },
+ { date: row.period + "-10", type: "鏀舵", code: "SK2024001", debit: 0, credit: firstReceipt, balance: runningBalance -= firstReceipt, remark: "瀹㈡埛鍥炴" },
+ { date: row.period + "-15", type: "鍑哄簱", code: "CK2024002", debit: row.currentReceivable - saleOutAmount - returnAmount, credit: 0, balance: runningBalance += (row.currentReceivable - saleOutAmount - returnAmount), remark: "閿�鍞嚭搴�" },
+ { date: row.period + "-20", type: "閫�璐�", code: "TH2024001", debit: 0, credit: returnAmount, balance: runningBalance -= returnAmount, remark: "閿�鍞��璐�" },
+ { date: row.period + "-25", type: "鏀舵", code: "SK2024002", debit: 0, credit: secondReceipt, balance: runningBalance -= secondReceipt, remark: "瀹㈡埛鍥炴" },
+ ];
+
+ detailDialogVisible.value = true;
+};
+
+const printStatement = (row) => {
+ ElMessage.info(`鎵撳嵃瀵硅处鍗�: ${row.statementCode}`);
+};
+
+const printDetail = () => {
+ ElMessage.info("鎵撳嵃鏄庣粏");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-success {
+ color: #67c23a;
+}
+
+.text-danger {
+ color: #f56c6c;
+}
+
+.text-primary {
+ color: #409eff;
+}
+
+.statement-header {
+ text-align: center;
+ margin-bottom: 20px;
+ h3 {
+ margin: 0 0 10px 0;
+ }
+ p {
+ color: #909399;
+ margin: 0;
+ }
+}
+
+.sales-section {
+ margin-top: 20px;
+
+ .section-title {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 15px;
+ padding-left: 10px;
+ border-left: 4px solid #409eff;
+ }
+}
+
+.summary-row {
+ display: flex;
+ justify-content: space-around;
+ padding: 15px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ margin-top: 15px;
+
+ span {
+ font-size: 14px;
+
+ strong {
+ font-size: 16px;
+ margin-left: 5px;
+ }
+ }
+}
+
+.empty-tip {
+ margin-top: 30px;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/salesOut.vue b/src/views/financialManagement/receivable/salesOut.vue
new file mode 100644
index 0000000..fce0c20
--- /dev/null
+++ b/src/views/financialManagement/receivable/salesOut.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.outCode" placeholder="璇疯緭鍏ュ嚭搴撳崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍑哄簱鏃ユ湡:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </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 @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @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="outCode">
+ <el-input v-model="form.outCode" placeholder="璇疯緭鍏ュ嚭搴撳崟鍙�" :disabled="isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍑哄簱鏃ユ湡" prop="outDate">
+ <el-date-picker v-model="form.outDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "閿�鍞嚭搴�",
+});
+
+const filters = reactive({
+ outCode: "",
+ customerId: "",
+ dateRange: [],
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍑哄簱鍗曞彿", prop: "outCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "鍑哄簱鏃ユ湡", prop: "outDate", width: "120" },
+ { label: "閲戦", prop: "amount", width: "120" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const form = reactive({
+ outCode: "",
+ customerId: "",
+ outDate: "",
+ amount: 0,
+ remark: "",
+});
+
+const rules = {
+ outCode: [{ required: true, message: "璇疯緭鍏ュ嚭搴撳崟鍙�", trigger: "blur" }],
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ outDate: [{ required: true, message: "璇烽�夋嫨鍑哄簱鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, outCode: "CK2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", outDate: "2024-01-15", amount: 5000, status: "approved", remark: "" },
+ { id: 2, outCode: "CK2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", outDate: "2024-01-16", amount: 8000, status: "pending", remark: "" },
+ { id: 3, outCode: "CK2024003", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", outDate: "2024-01-18", amount: 12000, status: "approved", remark: "" },
+ { id: 4, outCode: "CK2024004", customerId: 4, customerName: "娣卞湷鐢靛瓙鍏徃", outDate: "2024-01-20", amount: 3500, status: "pending", remark: "" },
+];
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.outCode) {
+ result = result.filter(item => item.outCode.includes(filters.outCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.outDate >= filters.dateRange[0] && item.outDate <= filters.dateRange[1]);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.outCode = "";
+ filters.customerId = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍑哄簱";
+ Object.assign(form, {
+ outCode: "CK" + Date.now(),
+ customerId: "",
+ outDate: "",
+ amount: 0,
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍑哄簱";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍑哄簱鍗�: ${row.outCode}`);
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ュ嚭搴撳崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/salesReturn.vue b/src/views/financialManagement/receivable/salesReturn.vue
new file mode 100644
index 0000000..4cf54d6
--- /dev/null
+++ b/src/views/financialManagement/receivable/salesReturn.vue
@@ -0,0 +1,305 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="閫�璐у崟鍙�:">
+ <el-input v-model="filters.returnCode" placeholder="璇疯緭鍏ラ��璐у崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="閫�璐ф棩鏈�:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </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 @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">瀹℃牳</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閫�璐у崟鍙�" prop="returnCode">
+ <el-input v-model="form.returnCode" placeholder="璇疯緭鍏ラ��璐у崟鍙�" :disabled="isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏宠仈鍑哄簱鍗�" prop="outCode">
+ <el-select v-model="form.outCode" placeholder="璇烽�夋嫨鍑哄簱鍗�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in outList" :key="item.outCode" :label="item.outCode" :value="item.outCode" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閫�璐ф棩鏈�" prop="returnDate">
+ <el-date-picker v-model="form.returnDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閫�璐ч噾棰�" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閫�璐у師鍥�" prop="reason">
+ <el-input v-model="form.reason" placeholder="璇疯緭鍏ラ��璐у師鍥�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "閿�鍞��璐�",
+});
+
+const filters = reactive({
+ returnCode: "",
+ customerId: "",
+ dateRange: [],
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "閫�璐у崟鍙�", prop: "returnCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "鍏宠仈鍑哄簱鍗�", prop: "outCode", width: "150" },
+ { label: "閫�璐ф棩鏈�", prop: "returnDate", width: "120" },
+ { label: "閫�璐ч噾棰�", prop: "amount", width: "120" },
+ { label: "閫�璐у師鍥�", prop: "reason", width: "150", showOverflowTooltip: true },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const outList = [
+ { outCode: "CK2024001", customerId: 1 },
+ { outCode: "CK2024002", customerId: 2 },
+ { outCode: "CK2024003", customerId: 3 },
+];
+
+const form = reactive({
+ returnCode: "",
+ outCode: "",
+ customerId: "",
+ returnDate: "",
+ amount: 0,
+ reason: "",
+ remark: "",
+});
+
+const rules = {
+ returnCode: [{ required: true, message: "璇疯緭鍏ラ��璐у崟鍙�", trigger: "blur" }],
+ outCode: [{ required: true, message: "璇烽�夋嫨鍏宠仈鍑哄簱鍗�", trigger: "change" }],
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ returnDate: [{ required: true, message: "璇烽�夋嫨閫�璐ф棩鏈�", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ��璐ч噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, returnCode: "TH2024001", outCode: "CK2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", returnDate: "2024-01-20", amount: 1000, reason: "璐ㄩ噺闂", status: "approved", remark: "" },
+ { id: 2, returnCode: "TH2024002", outCode: "CK2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", returnDate: "2024-01-22", amount: 500, reason: "瑙勬牸涓嶇", status: "pending", remark: "" },
+];
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.returnCode) {
+ result = result.filter(item => item.returnCode.includes(filters.returnCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.returnDate >= filters.dateRange[0] && item.returnDate <= filters.dateRange[1]);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.returnCode = "";
+ filters.customerId = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板閫�璐�";
+ Object.assign(form, {
+ returnCode: "TH" + Date.now(),
+ outCode: "",
+ customerId: "",
+ returnDate: "",
+ amount: 0,
+ reason: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫閫�璐�";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅閫�璐у崟: ${row.returnCode}`);
+};
+
+const handleAudit = (row) => {
+ ElMessageBox.confirm("纭瀹℃牳閫氳繃璇ラ��璐у崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ distinguishCancelAndClose: true,
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "approved";
+ }
+ ElMessage.success("瀹℃牳閫氳繃");
+ getTableData();
+ }).catch((action) => {
+ if (action === "cancel") {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "rejected";
+ }
+ ElMessage.warning("宸查┏鍥�");
+ getTableData();
+ }
+ });
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+</style>
diff --git a/src/views/financialManagement/revenueManagement/index.vue b/src/views/financialManagement/revenueManagement/index.vue
index 12abfe3..bcef5b6 100644
--- a/src/views/financialManagement/revenueManagement/index.vue
+++ b/src/views/financialManagement/revenueManagement/index.vue
@@ -3,22 +3,22 @@
<el-form :model="filters" :inline="true">
<el-form-item label="鏀跺叆鏃ユ湡:">
<el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange"/>
</el-form-item>
<el-form-item label="鏀舵鏂瑰紡:">
<el-select
- v-model="filters.incomeMethod"
- placeholder="璇烽�夋嫨"
- clearable
- style="width: 200px;"
- >
- <el-option
- v-for="item in payment_methods"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
+ v-model="filters.incomeMethodLabel"
+ placeholder="璇烽�夋嫨"
+ clearable
+ style="width: 200px;"
+ >
+ <el-option
+ v-for="item in incomeMethodOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getTableData">鎼滅储</el-button>
@@ -29,46 +29,51 @@
<div class="actions">
<div></div>
<div>
- <el-button type="primary" @click="add" icon="Plus"> 鏂板 </el-button>
+ <el-button type="primary" @click="add" icon="Plus"> 鏂板</el-button>
<el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
<el-button
- type="danger"
- icon="Delete"
- :disabled="multipleList.length <= 0 || hasBusinessIdInSelection"
- @click="handleBatchDelete"
+ type="danger"
+ icon="Delete"
+ :disabled="multipleList.length <= 0 || hasBusinessIdInSelection"
+ @click="handleBatchDelete"
>
鎵归噺鍒犻櫎
</el-button>
</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,
}"
- :isShowSummary="true"
- :summaryMethod="summarizeMainTable"
- @selection-change="handleSelectionChange"
- @pagination="changePage"
+ :isShowSummary="true"
+ :summaryMethod="summarizeMainTable"
+ @selection-change="handleSelectionChange"
+ @pagination="changePage"
>
+ <template #incomeMethodSlot="{ row }">
+ <el-tag>
+ {{ getIncomeMethodLabel(row) }}
+ </el-tag>
+ </template>
<template #operation="{ row }">
- <el-button
- type="primary"
- link
- :disabled="!!row.businessId"
- @click="edit(row.id)"
+ <el-button
+ type="primary"
+ link
+ :disabled="!!row.businessId"
+ @click="edit(row.id)"
>
缂栬緫
</el-button>
<el-button
- type="primary"
- link
- @click="openFilesFormDia(row)"
+ type="primary"
+ link
+ @click="openFilesFormDia(row)"
>
闄勪欢
</el-button>
@@ -76,27 +81,16 @@
</PIMTable>
</div>
<Modal ref="modalRef" @success="getTableData"></Modal>
- <FileListDialog
- ref="fileListRef"
- v-model="fileListDialogVisible"
- :show-upload-button="true"
- :show-delete-button="true"
- :upload-method="handleUpload"
- :delete-method="handleFileDelete"
- />
+ <FileListDialog v-if="fileListDialogVisible" :record-id="currentRecordId" record-type="account_income" v-model:visible="fileListDialogVisible"/>
</div>
</template>
<script setup>
-import { usePaginationApi } from "@/hooks/usePaginationApi";
-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 {usePaginationApi} from "@/hooks/usePaginationApi";
+import {listPage, delAccountIncome} from "@/api/financialManagement/revenueManagement";
+import {onMounted, getCurrentInstance, ref, computed} from "vue";
+import {ElMessageBox, ElMessage} from "element-plus";
import dayjs from "dayjs";
-import FileListDialog from "@/components/Dialog/FileListDialog.vue";
-import request from "@/utils/request";
-import { getToken } from "@/utils/auth";
defineOptions({
name: "鏀跺叆绠$悊",
@@ -104,14 +98,27 @@
// 琛ㄦ牸澶氶�夋閫変腑椤�
const multipleList = ref([]);
-const { proxy } = getCurrentInstance();
+const {proxy} = getCurrentInstance();
const modalRef = ref();
-const { payment_methods } = proxy.useDict("payment_methods");
-const { income_types } = proxy.useDict("income_types");
+const {payment_methods} = proxy.useDict("payment_methods");
+const {receipt_payment_type} = proxy.useDict("receipt_payment_type");
+const {income_types} = proxy.useDict("income_types");
const fileListRef = ref(null);
const fileListDialogVisible = ref(false);
-const currentFileRow = ref(null);
-const accountType = ref('鏀跺叆');
+const currentRecordId = ref(0);
+
+const incomeMethodOptions = computed(() => {
+ const merged = [...(payment_methods.value || []), ...(receipt_payment_type.value || [])];
+ const uniqueMap = new Map();
+ merged.forEach((item) => {
+ const label = item?.label;
+ if (!label) return;
+ if (!uniqueMap.has(label)) {
+ uniqueMap.set(label, {label, value: label});
+ }
+ });
+ return Array.from(uniqueMap.values());
+});
const {
filters,
@@ -122,91 +129,99 @@
resetFilters,
onCurrentChange,
} = usePaginationApi(
- listPage,
- {
- incomeMethod: undefined,
- entryDate: undefined,
- },
- [
+ listPage,
{
- label: "鏀跺叆鏃ユ湡",
- prop: "incomeDate",
+ incomeMethodLabel: undefined,
+ entryDate: undefined,
},
- {
- label: "鏀跺叆绫诲瀷",
- prop: "incomeType",
- dataType: "tag",
- formatData: (params) => {
- if (income_types.value.find((m) => m.value == params)) {
- return income_types.value.find((m) => m.value == params).label;
- } else {
- return null
- }
+ [
+ {
+ label: "鏀跺叆鏃ユ湡",
+ prop: "incomeDate",
},
- },
- {
- label: "瀹㈡埛鍚嶇О",
- prop: "customerName",
- width: '200'
-
- },
- {
- label: "鏀跺叆閲戦",
- prop: "incomeMoney",
-
- },
- {
- label: "鏀跺叆鎻忚堪",
- prop: "incomeDescribed",
-
- },
- {
- label: "鏀舵鏂瑰紡",
- prop: "incomeMethod",
- align: 'center',
- width: '100',
- dataType: "tag",
- formatData: (params) => {
- if (payment_methods.value.find((m) => m.value == params)) {
- return payment_methods.value.find((m) => m.value == params).label;
- } else {
- return null
- }
+ {
+ label: "鏀跺叆绫诲瀷",
+ prop: "incomeType",
+ dataType: "tag",
+ formatData: (params) => {
+ if (income_types.value.find((m) => m.value == params)) {
+ return income_types.value.find((m) => m.value == params).label;
+ } else {
+ return null
+ }
+ },
},
- },
- {
- label: "鍙戠エ鍙风爜",
- prop: "invoiceNumber",
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: '200'
- },
- {
- label: "澶囨敞",
- prop: "note",
+ },
+ {
+ label: "鏀跺叆閲戦",
+ prop: "incomeMoney",
- },
- {
- label: "褰曞叆浜�",
- prop: "inputUser",
- },
- {
- label: "褰曞叆鏃ユ湡",
- prop: "inputTime",
+ },
+ {
+ label: "鏀跺叆鎻忚堪",
+ prop: "incomeDescribed",
- },
+ },
+ {
+ label: "鏀舵鏂瑰紡",
+ prop: "incomeMethodLabel",
+ align: 'center',
+ width: '100',
+ dataType: "slot",
+ slot: "incomeMethodSlot",
+ },
+ {
+ label: "鍙戠エ鍙风爜",
+ prop: "invoiceNumber",
+
+ },
+ {
+ label: "澶囨敞",
+ prop: "note",
+
+ },
+ {
+ label: "褰曞叆浜�",
+ prop: "inputUser",
+ },
+ {
+ label: "褰曞叆鏃ユ湡",
+ prop: "inputTime",
+
+ },
+ {
+ fixed: "right",
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ align: "center",
+ width: "160px",
+ },
+ ],
+ undefined,
{
- fixed: "right",
- label: "鎿嶄綔",
- dataType: "slot",
- slot: "operation",
- align: "center",
- width: "160px",
- },
- ]
+ incomeMethodLabel: (value) => ({
+ incomeMethodLabel: value || undefined,
+ }),
+ }
);
// 琛ㄦ牸鍚堣锛氭敹鍏ラ噾棰�
const summarizeMainTable = (param) => {
return proxy.summarizeTable(param, ["incomeMoney"]);
+};
+
+const getIncomeMethodLabel = (row) => {
+ const methodValue = row?.incomeMethod;
+ const dictList = String(row?.businessType) === "1"
+ ? receipt_payment_type.value
+ : payment_methods.value;
+ return dictList.find((item) => item.value == methodValue)?.label || "--";
};
// 澶氶�夊悗鍋氫粈涔�
@@ -231,9 +246,9 @@
}
modalRef.value.loadForm(id);
};
-const changePage = ({ page, limit }) => {
+const changePage = ({page, limit}) => {
pagination.currentPage = page;
- pagination.pageSize = limit;
+ pagination.pageSize = limit;
onCurrentChange(page);
};
const deleteRow = (id) => {
@@ -255,13 +270,13 @@
return;
}
}
-
+
ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ暟鎹�, 鏄惁缁х画?", "鎻愮ず", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
}).then(async () => {
- const { code } = await delAccountIncome(id);
+ const {code} = await delAccountIncome(id);
if (code == 200) {
ElMessage({
type: "success",
@@ -278,13 +293,13 @@
proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
return;
}
-
+
// 妫�鏌ユ槸鍚︽湁 businessId
if (hasBusinessIdInSelection.value) {
proxy.$modal.msgWarning("閫変腑鐨勮褰曚腑鍖呭惈宸插叧鑱斾笟鍔$殑璁板綍锛屼笉鑳藉垹闄�");
return;
}
-
+
const ids = multipleList.value.map((item) => item.id);
deleteRow(ids);
};
@@ -308,162 +323,17 @@
cancelButtonText: "鍙栨秷",
type: "warning",
})
- .then(() => {
- proxy.download(`/account/accountIncome/export`, {}, "鏀跺叆鍙拌处.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ .then(() => {
+ proxy.download(`/account/accountIncome/export`, {}, "鏀跺叆鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鎵撳紑闄勪欢寮规
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;
- }
+ currentRecordId.value = row.id;
+ fileListDialogVisible.value = true;
};
onMounted(() => {
@@ -475,6 +345,7 @@
.table_list {
margin-top: unset;
}
+
.actions {
display: flex;
justify-content: space-between;
diff --git a/src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue b/src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue
index d8218c1..35215a5 100644
--- a/src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue
+++ b/src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue
@@ -1,5 +1,5 @@
<template>
- <el-dialog v-model="visible" title="鏀舵/閫�娆�" width="90%" append-to-body>
+ <FormDialog v-model="visible" title="鏀舵/閫�娆�" width="90%" @confirm="submit" @cancel="visible=false">
<div class="section">
<div class="section-title descriptions">鍩虹璧勬枡</div>
<el-form :model="form" label-width="100px">
@@ -140,12 +140,13 @@
<el-button type="primary" @click="submit">纭</el-button>
<el-button @click="visible=false">鍙栨秷</el-button>
</template>
- </el-dialog>
+ </FormDialog>
</template>
<script setup>
import { ref } from 'vue';
import { getToken } from '@/utils/auth';
+import FormDialog from "@/components/Dialog/FormDialog.vue";
const visible = ref(false);
const form = ref({
diff --git a/src/views/financialManagement/salesRefund/index.vue b/src/views/financialManagement/salesRefund/index.vue
index b4a792f..ccb775f 100644
--- a/src/views/financialManagement/salesRefund/index.vue
+++ b/src/views/financialManagement/salesRefund/index.vue
@@ -26,7 +26,7 @@
<el-table-column label="鍒涘缓鏃堕棿" prop="createTime" align="center" />
<el-table-column label="鎿嶄綔" align="center" width="150">
<template #default="scope">
- <el-button link type="primary" @click="openDetail(scope.row)">璇︽儏</el-button>
+ <el-button link type="primary" @click="openDetail(scope.row)" style="color: #67C23A">璇︽儏</el-button>
<el-button link type="primary" @click="openConfirm(scope.row)">纭</el-button>
</template>
</el-table-column>
@@ -83,7 +83,7 @@
function getList() {
loading.value = true;
const { pageNum, pageSize, ...filters } = queryParams;
- listPlan({
+ listPage({
current: pageNum,
size: pageSize,
...filters
diff --git a/src/views/financialManagement/voucher/detailLedger.vue b/src/views/financialManagement/voucher/detailLedger.vue
new file mode 100644
index 0000000..7f85790
--- /dev/null
+++ b/src/views/financialManagement/voucher/detailLedger.vue
@@ -0,0 +1,289 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="浼氳绉戠洰:">
+ <el-cascader v-model="filters.subject" :options="subjectOptions" :props="{ label: 'name', value: 'code' }" placeholder="璇烽�夋嫨浼氳绉戠洰" clearable style="width: 250px;" filterable />
+ </el-form-item>
+ <el-form-item label="杈呭姪鏍哥畻:">
+ <el-select v-model="filters.auxiliary" placeholder="璇烽�夋嫨杈呭姪鏍哥畻" clearable style="width: 180px;">
+ <el-option label="瀹㈡埛" value="customer" />
+ <el-option label="渚涘簲鍟�" value="supplier" />
+ <el-option label="閮ㄩ棬" value="department" />
+ <el-option label="鍛樺伐" value="employee" />
+ <el-option label="椤圭洰" value="project" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏍哥畻瀵硅薄:">
+ <el-select v-model="filters.auxiliaryItem" placeholder="璇烽�夋嫨鏍哥畻瀵硅薄" clearable style="width: 200px;" :disabled="!filters.auxiliary">
+ <el-option v-for="item in auxiliaryItems" :key="item.value" :label="item.label" :value="item.value" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏈熼棿:">
+ <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
+ <span style="margin: 0 10px;">鑷�</span>
+ <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鏌ヨ</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ <el-button @click="handlePrint" icon="Printer">鎵撳嵃</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div class="ledger-header" v-if="currentSubject">
+ <h2>绉戠洰鏄庣粏璐�</h2>
+ <p>绉戠洰: {{ currentSubject.code }} {{ currentSubject.name }}</p>
+ <p v-if="filters.auxiliary && filters.auxiliaryItem">杈呭姪鏍哥畻: {{ getAuxiliaryLabel() }}</p>
+ <p>鏈熼棿: {{ filters.startMonth }} 鑷� {{ filters.endMonth }}</p>
+ </div>
+
+ <div class="table_list">
+ <el-table :data="dataList" border style="width: 100%" show-summary :summary-method="getSummaries">
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="voucherNo" label="鍑瘉瀛楀彿" width="120" />
+ <el-table-column prop="summary" label="鎽樿" min-width="200" show-overflow-tooltip />
+ <el-table-column label="鍊熸柟" width="150">
+ <template #default="{ row }">
+ <span v-if="row.debit > 0" class="text-danger">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璐锋柟" width="150">
+ <template #default="{ row }">
+ <span v-if="row.credit > 0" class="text-success">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂瑰悜" width="80">
+ <template #default="{ row }">
+ <el-tag :type="row.direction === '鍊�' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="浣欓" width="150">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">楼{{ formatMoney(Math.abs(row.balance)) }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <el-empty v-if="!currentSubject" description="璇烽�夋嫨浼氳绉戠洰鏌ヨ" style="margin-top: 50px;" />
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed, watch } from "vue";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+ name: "绉戠洰鏄庣粏璐�",
+});
+
+const filters = reactive({
+ subject: [],
+ auxiliary: "",
+ auxiliaryItem: "",
+ startMonth: "2024-01",
+ endMonth: "2024-03",
+});
+
+const dataList = ref([]);
+
+const subjectOptions = [
+ {
+ code: "1122",
+ name: "搴旀敹璐︽",
+ children: [
+ { code: "112201", name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { code: "112202", name: "涓婃捣璐告槗鍏徃" },
+ { code: "112203", name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ ],
+ },
+ {
+ code: "2202",
+ name: "搴斾粯璐︽",
+ children: [
+ { code: "220201", name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { code: "220202", name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { code: "220203", name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ ],
+ },
+ {
+ code: "6602",
+ name: "绠$悊璐圭敤",
+ children: [
+ { code: "660201", name: "鍔炲叕璐�" },
+ { code: "660202", name: "宸梾璐�" },
+ { code: "660203", name: "涓氬姟鎷涘緟璐�" },
+ ],
+ },
+];
+
+const auxiliaryItems = computed(() => {
+ const map = {
+ customer: [
+ { value: "1", label: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { value: "2", label: "涓婃捣璐告槗鍏徃" },
+ { value: "3", label: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ ],
+ supplier: [
+ { value: "1", label: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { value: "2", label: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { value: "3", label: "骞垮窞鍖呰鏉愭枡鍘�" },
+ ],
+ department: [
+ { value: "1", label: "璐㈠姟閮�" },
+ { value: "2", label: "閿�鍞儴" },
+ { value: "3", label: "閲囪喘閮�" },
+ ],
+ employee: [
+ { value: "1", label: "寮犱笁" },
+ { value: "2", label: "鏉庡洓" },
+ { value: "3", label: "鐜嬩簲" },
+ ],
+ project: [
+ { value: "1", label: "椤圭洰A" },
+ { value: "2", label: "椤圭洰B" },
+ { value: "3", label: "椤圭洰C" },
+ ],
+ };
+ return map[filters.auxiliary] || [];
+});
+
+watch(() => filters.auxiliary, () => {
+ filters.auxiliaryItem = "";
+});
+
+const currentSubject = computed(() => {
+ if (!filters.subject || filters.subject.length === 0) return null;
+ const code = filters.subject[filters.subject.length - 1];
+ return findSubject(subjectOptions, code);
+});
+
+const findSubject = (options, code) => {
+ for (const item of options) {
+ if (item.code === code) return item;
+ if (item.children && item.children.length > 0) {
+ const found = findSubject(item.children, code);
+ if (found) return found;
+ }
+ }
+ return null;
+};
+
+const getAuxiliaryLabel = () => {
+ const item = auxiliaryItems.value.find(i => i.value === filters.auxiliaryItem);
+ return item ? item.label : "";
+};
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const mockData = [
+ { date: "2024-01-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 10000 },
+ { date: "2024-01-05", voucherNo: "璁�-0001", summary: "閿�鍞嚭搴�", debit: 5000, credit: 0, direction: "鍊�", balance: 15000 },
+ { date: "2024-01-10", voucherNo: "璁�-0002", summary: "鏀跺埌璐ф", debit: 0, credit: 3000, direction: "鍊�", balance: 12000 },
+ { date: "2024-01-15", voucherNo: "璁�-0003", summary: "閿�鍞嚭搴�", debit: 8000, credit: 0, direction: "鍊�", balance: 20000 },
+ { date: "2024-01-20", voucherNo: "璁�-0004", summary: "閿�鍞��璐�", debit: 0, credit: 2000, direction: "鍊�", balance: 18000 },
+ { date: "2024-01-25", voucherNo: "璁�-0005", summary: "鏀跺埌璐ф", debit: 0, credit: 5000, direction: "鍊�", balance: 13000 },
+ { date: "2024-01-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 13000, credit: 10000, direction: "鍊�", balance: 13000 },
+ { date: "2024-02-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 13000 },
+ { date: "2024-02-10", voucherNo: "璁�-0006", summary: "閿�鍞嚭搴�", debit: 6000, credit: 0, direction: "鍊�", balance: 19000 },
+ { date: "2024-02-15", voucherNo: "璁�-0007", summary: "鏀跺埌璐ф", debit: 0, credit: 4000, direction: "鍊�", balance: 15000 },
+ { date: "2024-02-28", voucherNo: "-", summary: "鏈湀鍚堣", debit: 6000, credit: 4000, direction: "鍊�", balance: 15000 },
+ { date: "2024-03-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 15000 },
+ { date: "2024-03-05", voucherNo: "璁�-0008", summary: "閿�鍞嚭搴�", debit: 7000, credit: 0, direction: "鍊�", balance: 22000 },
+ { date: "2024-03-10", voucherNo: "璁�-0009", summary: "鏀跺埌璐ф", debit: 0, credit: 6000, direction: "鍊�", balance: 16000 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 7000, credit: 6000, direction: "鍊�", balance: 16000 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈勾绱", debit: 26000, credit: 20000, direction: "鍊�", balance: 16000 },
+];
+
+const getTableData = () => {
+ if (!currentSubject.value) {
+ dataList.value = [];
+ return;
+ }
+ dataList.value = [...mockData];
+};
+
+const resetFilters = () => {
+ filters.subject = [];
+ filters.auxiliary = "";
+ filters.auxiliaryItem = "";
+ filters.startMonth = "2024-01";
+ filters.endMonth = "2024-03";
+ dataList.value = [];
+};
+
+const getSummaries = (param) => {
+ const { columns, data } = param;
+ const sums = [];
+ columns.forEach((column, index) => {
+ if (index === 0) {
+ sums[index] = "鍚堣";
+ return;
+ }
+ if (column.property === "debit") {
+ const values = data.map(item => Number(item.debit));
+ const sum = values.reduce((prev, curr) => prev + curr, 0);
+ sums[index] = "楼" + formatMoney(sum);
+ } else if (column.property === "credit") {
+ const values = data.map(item => Number(item.credit));
+ const sum = values.reduce((prev, curr) => prev + curr, 0);
+ sums[index] = "楼" + formatMoney(sum);
+ } else {
+ sums[index] = "";
+ }
+ });
+ return sums;
+};
+
+const handlePrint = () => {
+ ElMessage.info("鎵撳嵃鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+});
+</script>
+
+<style lang="scss" scoped>
+.ledger-header {
+ text-align: center;
+ margin-bottom: 20px;
+ h2 {
+ margin: 0 0 10px 0;
+ }
+ p {
+ color: #606266;
+ margin: 5px 0;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/voucher/generalLedger.vue b/src/views/financialManagement/voucher/generalLedger.vue
new file mode 100644
index 0000000..5da2d70
--- /dev/null
+++ b/src/views/financialManagement/voucher/generalLedger.vue
@@ -0,0 +1,230 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="浼氳绉戠洰:">
+ <el-cascader v-model="filters.subject" :options="subjectOptions" :props="{ label: 'name', value: 'code' }" placeholder="璇烽�夋嫨浼氳绉戠洰" clearable style="width: 250px;" filterable />
+ </el-form-item>
+ <el-form-item label="鏈熼棿:">
+ <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
+ <span style="margin: 0 10px;">鑷�</span>
+ <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鏌ヨ</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ <el-button @click="handlePrint" icon="Printer">鎵撳嵃</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div class="ledger-header" v-if="currentSubject">
+ <h2>绉戠洰鎬昏处</h2>
+ <p>绉戠洰: {{ currentSubject.code }} {{ currentSubject.name }}</p>
+ <p>鏈熼棿: {{ filters.startMonth }} 鑷� {{ filters.endMonth }}</p>
+ </div>
+
+ <div class="table_list">
+ <el-table :data="dataList" border style="width: 100%" show-summary :summary-method="getSummaries">
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="voucherNo" label="鍑瘉瀛楀彿" width="120" />
+ <el-table-column prop="summary" label="鎽樿" min-width="200" show-overflow-tooltip />
+ <el-table-column label="鍊熸柟" width="150">
+ <template #default="{ row }">
+ <span v-if="row.debit > 0" class="text-danger">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璐锋柟" width="150">
+ <template #default="{ row }">
+ <span v-if="row.credit > 0" class="text-success">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂瑰悜" width="80">
+ <template #default="{ row }">
+ <el-tag :type="row.direction === '鍊�' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="浣欓" width="150">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">楼{{ formatMoney(Math.abs(row.balance)) }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <el-empty v-if="!currentSubject" description="璇烽�夋嫨浼氳绉戠洰鏌ヨ" style="margin-top: 50px;" />
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+ name: "绉戠洰鎬昏处",
+});
+
+const filters = reactive({
+ subject: [],
+ startMonth: "2024-01",
+ endMonth: "2024-03",
+});
+
+const dataList = ref([]);
+
+const subjectOptions = [
+ {
+ code: "1001",
+ name: "搴撳瓨鐜伴噾",
+ children: [],
+ },
+ {
+ code: "1002",
+ name: "閾惰瀛樻",
+ children: [
+ { code: "100201", name: "宸ュ晢閾惰" },
+ { code: "100202", name: "寤鸿閾惰" },
+ ],
+ },
+ {
+ code: "1122",
+ name: "搴旀敹璐︽",
+ children: [],
+ },
+ {
+ code: "2202",
+ name: "搴斾粯璐︽",
+ children: [],
+ },
+ {
+ code: "6001",
+ name: "涓昏惀涓氬姟鏀跺叆",
+ children: [],
+ },
+];
+
+const currentSubject = computed(() => {
+ if (!filters.subject || filters.subject.length === 0) return null;
+ const code = filters.subject[filters.subject.length - 1];
+ return findSubject(subjectOptions, code);
+});
+
+const findSubject = (options, code) => {
+ for (const item of options) {
+ if (item.code === code) return item;
+ if (item.children && item.children.length > 0) {
+ const found = findSubject(item.children, code);
+ if (found) return found;
+ }
+ }
+ return null;
+};
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const mockData = [
+ { date: "2024-01-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 100000 },
+ { date: "2024-01-05", voucherNo: "璁�-0001", summary: "閿�鍞敹鍏�", debit: 5650, credit: 0, direction: "鍊�", balance: 105650 },
+ { date: "2024-01-10", voucherNo: "璁�-0002", summary: "閲囪喘鏀嚭", debit: 0, credit: 8000, direction: "鍊�", balance: 97650 },
+ { date: "2024-01-15", voucherNo: "璁�-0003", summary: "鏀跺埌璐ф", debit: 10000, credit: 0, direction: "鍊�", balance: 107650 },
+ { date: "2024-01-20", voucherNo: "璁�-0004", summary: "鏀粯璐圭敤", debit: 0, credit: 5000, direction: "鍊�", balance: 102650 },
+ { date: "2024-01-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 15650, credit: 13000, direction: "鍊�", balance: 102650 },
+ { date: "2024-02-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 102650 },
+ { date: "2024-02-10", voucherNo: "璁�-0005", summary: "閿�鍞敹鍏�", debit: 8000, credit: 0, direction: "鍊�", balance: 110650 },
+ { date: "2024-02-15", voucherNo: "璁�-0006", summary: "閲囪喘鏀嚭", debit: 0, credit: 12000, direction: "鍊�", balance: 98650 },
+ { date: "2024-02-28", voucherNo: "-", summary: "鏈湀鍚堣", debit: 8000, credit: 12000, direction: "鍊�", balance: 98650 },
+ { date: "2024-03-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 98650 },
+ { date: "2024-03-05", voucherNo: "璁�-0007", summary: "閿�鍞敹鍏�", debit: 12000, credit: 0, direction: "鍊�", balance: 110650 },
+ { date: "2024-03-10", voucherNo: "璁�-0008", summary: "鏀粯宸ヨ祫", debit: 0, credit: 15000, direction: "鍊�", balance: 95650 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 12000, credit: 15000, direction: "鍊�", balance: 95650 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈勾绱", debit: 35650, credit: 40000, direction: "鍊�", balance: 95650 },
+];
+
+const getTableData = () => {
+ if (!currentSubject.value) {
+ dataList.value = [];
+ return;
+ }
+ dataList.value = [...mockData];
+};
+
+const resetFilters = () => {
+ filters.subject = [];
+ filters.startMonth = "2024-01";
+ filters.endMonth = "2024-03";
+ dataList.value = [];
+};
+
+const getSummaries = (param) => {
+ const { columns, data } = param;
+ const sums = [];
+ columns.forEach((column, index) => {
+ if (index === 0) {
+ sums[index] = "鍚堣";
+ return;
+ }
+ if (column.property === "debit") {
+ const values = data.map(item => Number(item.debit));
+ const sum = values.reduce((prev, curr) => prev + curr, 0);
+ sums[index] = "楼" + formatMoney(sum);
+ } else if (column.property === "credit") {
+ const values = data.map(item => Number(item.credit));
+ const sum = values.reduce((prev, curr) => prev + curr, 0);
+ sums[index] = "楼" + formatMoney(sum);
+ } else {
+ sums[index] = "";
+ }
+ });
+ return sums;
+};
+
+const handlePrint = () => {
+ ElMessage.info("鎵撳嵃鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+});
+</script>
+
+<style lang="scss" scoped>
+.ledger-header {
+ text-align: center;
+ margin-bottom: 20px;
+ h2 {
+ margin: 0 0 10px 0;
+ }
+ p {
+ color: #606266;
+ margin: 5px 0;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/voucher/index.vue b/src/views/financialManagement/voucher/index.vue
new file mode 100644
index 0000000..817185c
--- /dev/null
+++ b/src/views/financialManagement/voucher/index.vue
@@ -0,0 +1,836 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍑瘉瀛楀彿:">
+ <el-input v-model="filters.voucherNo" placeholder="璇疯緭鍏ュ嚟璇佸瓧鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="鍑瘉鏃ユ湡:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </el-form-item>
+ <el-form-item label="鍒跺崟浜�:">
+ <el-select v-model="filters.creator" placeholder="璇烽�夋嫨鍒跺崟浜�" clearable style="width: 150px;">
+ <el-option label="寮犱笁" value="寮犱笁" />
+ <el-option label="鏉庡洓" value="鏉庡洓" />
+ <el-option label="鐜嬩簲" value="鐜嬩簲" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="鏈繃璐�" value="unposted" />
+ <el-option label="宸茶繃璐�" value="posted" />
+ <el-option label="宸蹭綔搴�" value="cancelled" />
+ </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>
+ <el-statistic title="鍊熸柟鍚堣" :value="totalDebit" precision="2" prefix="楼" />
+ <el-statistic title="璐锋柟鍚堣" :value="totalCredit" precision="2" prefix="楼" style="margin-left: 30px;" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鍑瘉</el-button>
+ <el-button @click="handleImport" icon="Upload">瀵煎叆</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #debit="{ row }">
+ <span class="text-danger" v-if="row.debit > 0">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ <template #credit="{ row }">
+ <span class="text-success" v-if="row.credit > 0">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else>-</span>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'unposted'">缂栬緫</el-button>
+ <el-button type="success" link @click="handlePost(row)" v-if="row.status === 'unposted'">杩囪处</el-button>
+ <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'unposted'">浣滃簾</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <FormDialog :title="dialogTitle" v-model="dialogVisible" width="1200px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <div class="voucher-container">
+ <div class="voucher-header">
+ <h2 class="voucher-title">璁拌处鍑瘉</h2>
+ <div class="voucher-period">{{ form.voucherDate ? form.voucherDate.substring(0, 7) + '鏈�' : '' }}</div>
+ </div>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="0">
+ <div class="voucher-info">
+ <div class="voucher-no-section">
+ <span class="label">鍑瘉瀛楋細</span>
+ <el-select v-model="form.voucherPrefix" style="width: 70px;">
+ <el-option label="璁�" value="璁�" />
+ </el-select>
+ <el-input v-model="form.voucherNum" style="width: 60px;" />
+ <span class="label" style="margin-left: 5px;">鍙�</span>
+ </div>
+ <div class="voucher-date-section">
+ <span class="label">鏃ユ湡锛�</span>
+ <el-date-picker v-model="form.voucherDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 140px;" />
+ </div>
+ <div class="voucher-attachment-section">
+ <span class="label">闄勪欢锛�</span>
+ <el-input-number v-model="form.attachmentCount" :min="0" :controls="false" style="width: 60px;" />
+ <span class="label" style="margin-left: 5px;">寮�</span>
+ <el-button type="primary" link style="margin-left: 10px;">涓婁紶鏂囦欢</el-button>
+ </div>
+ </div>
+ <div class="voucher-table">
+ <table class="accounting-voucher">
+ <thead>
+ <tr>
+ <th class="col-summary" rowspan="2">鎽樿</th>
+ <th class="col-subject" rowspan="2">浼氳绉戠洰</th>
+ <th class="col-debit-header" colspan="11">鍊熸柟</th>
+ <th class="col-credit-header" colspan="11">璐锋柟</th>
+ <th class="col-action" rowspan="2">鎿嶄綔</th>
+ </tr>
+ <tr class="amount-header">
+ <th>浜�</th>
+ <th>鍗�</th>
+ <th>鐧�</th>
+ <th>鍗�</th>
+ <th>涓�</th>
+ <th>鍗�</th>
+ <th>鐧�</th>
+ <th>鍗�</th>
+ <th>鍏�</th>
+ <th>瑙�</th>
+ <th>鍒�</th>
+ <th>浜�</th>
+ <th>鍗�</th>
+ <th>鐧�</th>
+ <th>鍗�</th>
+ <th>涓�</th>
+ <th>鍗�</th>
+ <th>鐧�</th>
+ <th>鍗�</th>
+ <th>鍏�</th>
+ <th>瑙�</th>
+ <th>鍒�</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="(entry, rowIndex) in form.entries" :key="rowIndex" @click="selectRow(rowIndex)" :class="{ 'selected-row': selectedRowIndex === rowIndex }">
+ <td class="col-summary">
+ <el-input v-model="entry.summary" placeholder="璇疯緭鍏ユ憳瑕�" @focus="selectRow(rowIndex)" />
+ </td>
+ <td class="col-subject">
+ <el-select v-model="entry.subjectCode" placeholder="閫夋嫨绉戠洰" filterable @change="(val) => handleSubjectChange(val, rowIndex)" @focus="selectRow(rowIndex)">
+ <el-option v-for="item in subjectList" :key="item.code" :label="item.code + item.name" :value="item.code" />
+ </el-select>
+ <div class="subject-name">{{ entry.subjectName }}</div>
+ </td>
+ <!-- 鍊熸柟11鍒� -->
+ <template v-if="editingCell.row === rowIndex && editingCell.type === 'debit'">
+ <td colspan="11" class="debit-input-cell">
+ <el-input-number ref="amountInputRef" v-model="entry.debit" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" />
+ </td>
+ </template>
+ <template v-else>
+ <td v-for="(digit, dIndex) in getAmountDigits(entry.debit, 11)" :key="'debit-'+dIndex" class="amount-cell debit-cell" @click="openAmountInput(rowIndex, 'debit')">
+ <span :class="{ 'text-primary': digit !== '', 'zero': digit === '' }">{{ digit || '' }}</span>
+ </td>
+ </template>
+ <!-- 璐锋柟11鍒� -->
+ <template v-if="editingCell.row === rowIndex && editingCell.type === 'credit'">
+ <td colspan="11" class="credit-input-cell">
+ <el-input-number ref="amountInputRef" v-model="entry.credit" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" />
+ </td>
+ </template>
+ <template v-else>
+ <td v-for="(digit, dIndex) in getAmountDigits(entry.credit, 11)" :key="'credit-'+dIndex" class="amount-cell credit-cell" @click="openAmountInput(rowIndex, 'credit')">
+ <span :class="{ 'text-danger': digit !== '', 'zero': digit === '' }">{{ digit || '' }}</span>
+ </td>
+ </template>
+ <td class="col-action">
+ <el-button type="danger" link size="small" @click="removeEntry(rowIndex)" icon="Delete" :disabled="form.entries.length <= 2">鍒犻櫎</el-button>
+ </td>
+ </tr>
+ <tr class="total-row">
+ <td class="col-summary" colspan="2" style="text-align: center; font-weight: bold;">鍚堣锛�</td>
+ <td v-for="(digit, index) in getAmountDigits(totalDebitEntry, 11)" :key="'total-debit-'+index" class="amount-cell total-debit-cell">
+ <span :class="{ 'text-primary': digit !== '' }">{{ digit }}</span>
+ </td>
+ <td v-for="(digit, index) in getAmountDigits(totalCreditEntry, 11)" :key="'total-credit-'+index" class="amount-cell total-credit-cell">
+ <span :class="{ 'text-danger': digit !== '' }">{{ digit }}</span>
+ </td>
+ <td class="col-action"></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div class="voucher-toolbar">
+ <el-button type="primary" link @click="addEntry" icon="Plus">鏂板琛�</el-button>
+ </div>
+ <div class="voucher-footer">
+ <div class="creator-section">
+ <span class="label">鍒跺崟浜猴細{{ form.creator }}</span>
+ </div>
+ </div>
+ </el-form>
+ </div>
+ <template #footer>
+ <div>
+ <el-button type="primary" @click="submitForm" :disabled="!isBalanced">淇濆瓨</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </div>
+ </template>
+ </FormDialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed, nextTick } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+defineOptions({
+ name: "鍑瘉绠$悊",
+});
+
+const filters = reactive({
+ voucherNo: "",
+ dateRange: [],
+ creator: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍑瘉瀛楀彿", prop: "voucherNo", width: "120" },
+ { label: "鍑瘉鏃ユ湡", prop: "voucherDate", width: "120" },
+ { label: "鎽樿", prop: "summary", showOverflowTooltip: true },
+ { label: "鍊熸柟閲戦", prop: "debit", slot: "debit" },
+ { label: "璐锋柟閲戦", prop: "credit", slot: "credit" },
+ { label: "鍒跺崟浜�", prop: "creator", width: "100" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const subjectList = [
+ { code: "1001", name: "搴撳瓨鐜伴噾" },
+ { code: "1002", name: "閾惰瀛樻" },
+ { code: "1122", name: "搴旀敹璐︽" },
+ { code: "2202", name: "搴斾粯璐︽" },
+ { code: "5001", name: "鐢熶骇鎴愭湰" },
+ { code: "6001", name: "涓昏惀涓氬姟鏀跺叆" },
+ { code: "6401", name: "涓昏惀涓氬姟鎴愭湰" },
+];
+
+const form = reactive({
+ voucherNo: "",
+ voucherPrefix: "璁�",
+ voucherNum: "",
+ voucherDate: "",
+ attachmentCount: 0,
+ entries: [],
+ creator: "寮犱笁",
+ remark: "",
+});
+
+const selectedRowIndex = ref(-1);
+const editingCell = reactive({
+ row: -1,
+ type: "",
+});
+const amountInputRef = ref(null);
+
+const isBalanced = computed(() => {
+ return totalDebitEntry.value === totalCreditEntry.value && totalDebitEntry.value > 0;
+});
+
+const rules = {
+ voucherDate: [{ required: true, message: "璇烽�夋嫨鍑瘉鏃ユ湡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, voucherNo: "璁�-0001", voucherDate: "2024-01-15", summary: "閿�鍞敹鍏�", debit: 5650, credit: 5650, creator: "寮犱笁", status: "posted", entries: [{ subjectCode: "1002", subjectName: "閾惰瀛樻", summary: "閿�鍞敹鍏�", debit: 5650, credit: 0 }, { subjectCode: "6001", subjectName: "涓昏惀涓氬姟鏀跺叆", summary: "閿�鍞敹鍏�", debit: 0, credit: 5000 }, { subjectCode: "2221", subjectName: "搴斾氦绋庤垂", summary: "閿�椤圭◣棰�", debit: 0, credit: 650 }] },
+ { id: 2, voucherNo: "璁�-0002", voucherDate: "2024-01-16", summary: "閲囪喘鍘熸潗鏂�", debit: 9040, credit: 9040, creator: "鏉庡洓", status: "unposted", entries: [{ subjectCode: "5001", subjectName: "鐢熶骇鎴愭湰", summary: "閲囪喘鍘熸潗鏂�", debit: 8000, credit: 0 }, { subjectCode: "2221", subjectName: "搴斾氦绋庤垂", summary: "杩涢」绋庨", debit: 1040, credit: 0 }, { subjectCode: "2202", subjectName: "搴斾粯璐︽", summary: "閲囪喘鍘熸潗鏂�", debit: 0, credit: 9040 }] },
+ { id: 3, voucherNo: "璁�-0003", voucherDate: "2024-01-18", summary: "鏀粯璐ф", debit: 5000, credit: 5000, creator: "寮犱笁", status: "posted", entries: [{ subjectCode: "2202", subjectName: "搴斾粯璐︽", summary: "鏀粯璐ф", debit: 5000, credit: 0 }, { subjectCode: "1002", subjectName: "閾惰瀛樻", summary: "鏀粯璐ф", debit: 0, credit: 5000 }] },
+];
+
+const totalDebit = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.debit), 0);
+});
+
+const totalCredit = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.credit), 0);
+});
+
+const totalDebitEntry = computed(() => {
+ return form.entries.reduce((sum, item) => sum + Number(item.debit || 0), 0);
+});
+
+const totalCreditEntry = computed(() => {
+ return form.entries.reduce((sum, item) => sum + Number(item.credit || 0), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getStatusLabel = (status) => {
+ const map = { unposted: "鏈繃璐�", posted: "宸茶繃璐�", cancelled: "宸蹭綔搴�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { unposted: "warning", posted: "success", cancelled: "info" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.voucherNo) {
+ result = result.filter(item => item.voucherNo.includes(filters.voucherNo));
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.voucherDate >= filters.dateRange[0] && item.voucherDate <= filters.dateRange[1]);
+ }
+ if (filters.creator) {
+ result = result.filter(item => item.creator === filters.creator);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.voucherNo = "";
+ filters.dateRange = [];
+ filters.creator = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const addEntry = () => {
+ form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
+};
+
+const selectRow = (index) => {
+ selectedRowIndex.value = index;
+};
+
+const openAmountInput = (index, type) => {
+ editingCell.row = index;
+ editingCell.type = type;
+ nextTick(() => {
+ if (amountInputRef.value) {
+ amountInputRef.value.focus();
+ }
+ });
+};
+
+const finishEdit = () => {
+ editingCell.row = -1;
+ editingCell.type = "";
+};
+
+const getAmountDigits = (amount, length) => {
+ if (!amount || amount === 0) {
+ return new Array(length).fill('');
+ }
+
+ const amountStr = Number(amount).toFixed(2);
+ const [intPart, decPart] = amountStr.split('.');
+ const fullAmount = intPart + decPart;
+
+ // 宸﹀~鍏�0鍒版寚瀹氶暱搴�
+ const paddedAmount = fullAmount.padStart(length, '0');
+ const digits = paddedAmount.split('');
+
+ // 鎵惧埌绗竴涓潪闆舵暟瀛楃殑浣嶇疆
+ let firstNonZeroIndex = 0;
+ for (let i = 0; i < digits.length; i++) {
+ if (digits[i] !== '0') {
+ firstNonZeroIndex = i;
+ break;
+ }
+ }
+
+ // 鍙殣钘忓墠瀵奸浂锛堢涓�涓潪闆舵暟瀛椾箣鍓嶇殑闆讹級
+ return digits.map((d, index) => {
+ if (index < firstNonZeroIndex) {
+ return ''; // 鍓嶅闆舵樉绀轰负绌�
+ }
+ return d; // 淇濈暀閲戦涓殑闆�
+ });
+};
+
+const removeEntry = (index) => {
+ form.entries.splice(index, 1);
+ calculateTotal();
+};
+
+const handleSubjectChange = (val, index) => {
+ const subject = subjectList.find(item => item.code === val);
+ if (subject) {
+ form.entries[index].subjectName = subject.name;
+ }
+};
+
+const calculateTotal = () => {
+ // 鑷姩璁$畻锛岀敱computed灞炴�у鐞�
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍑瘉";
+ const nextNum = String(mockData.length + 1).padStart(2, "0");
+ Object.assign(form, {
+ voucherNo: "璁�-" + nextNum,
+ voucherPrefix: "璁�",
+ voucherNum: nextNum,
+ voucherDate: new Date().toISOString().split('T')[0],
+ attachmentCount: 0,
+ entries: [
+ { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+ { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+ { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+ { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
+ ],
+ creator: "寮犱笁",
+ remark: "",
+ });
+ selectedRowIndex.value = 0;
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍑瘉";
+ const parts = row.voucherNo.split('-');
+ Object.assign(form, {
+ ...row,
+ voucherPrefix: parts[0] || '璁�',
+ voucherNum: parts[1] || '',
+ });
+ if (form.entries.length < 4) {
+ while (form.entries.length < 4) {
+ form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
+ }
+ }
+ selectedRowIndex.value = 0;
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍑瘉: ${row.voucherNo}`);
+};
+
+const handlePost = (row) => {
+ ElMessageBox.confirm("纭杩囪处璇ュ嚟璇佸悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "posted";
+ }
+ ElMessage.success("杩囪处鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleCancel = (row) => {
+ ElMessageBox.confirm("纭浣滃簾璇ュ嚟璇佸悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "cancelled";
+ }
+ ElMessage.success("浣滃簾鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleImport = () => {
+ ElMessage.info("瀵煎叆鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (!isBalanced.value) {
+ ElMessage.error("鍊熻捶涓嶅钩琛★紝璇锋鏌ュ垎褰�");
+ return;
+ }
+
+ const validEntries = form.entries.filter(e => e.subjectCode && (e.debit > 0 || e.credit > 0));
+ const summary = validEntries.find(e => e.debit > 0)?.summary || "";
+
+ const voucherNo = `${form.voucherPrefix}-${form.voucherNum}`;
+ const dataToSave = {
+ ...form,
+ voucherNo,
+ summary,
+ debit: totalDebitEntry.value,
+ credit: totalCreditEntry.value,
+ entries: validEntries,
+ };
+
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...dataToSave };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...dataToSave, status: "unposted" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+
+ > div:first-child {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-primary {
+ color: #409eff;
+}
+
+.voucher-container {
+ background: #fff;
+ padding: 20px;
+}
+
+.voucher-header {
+ text-align: center;
+ margin-bottom: 15px;
+
+ .voucher-title {
+ font-size: 22px;
+ font-weight: bold;
+ margin: 0 0 5px 0;
+ color: #303133;
+ }
+
+ .voucher-period {
+ font-size: 14px;
+ color: #909399;
+ }
+}
+
+.voucher-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 0 10px;
+
+ .label {
+ font-size: 14px;
+ color: #606266;
+ }
+
+ .voucher-no-section,
+ .voucher-date-section,
+ .voucher-attachment-section {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.voucher-table {
+ border: 1px solid #dcdfe6;
+ border-right: none;
+ margin-bottom: 15px;
+}
+
+.accounting-voucher {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 13px;
+
+ th,
+ td {
+ border: 1px solid #dcdfe6;
+ text-align: center;
+ padding: 0;
+ height: 36px;
+ }
+
+ & th:last-child,
+ & td:last-child {
+ border-right: none !important;
+ }
+
+ thead {
+ background-color: #f5f7fa;
+
+ th {
+ font-weight: normal;
+ color: #606266;
+ font-size: 12px;
+ }
+
+ .col-summary,
+ .col-subject {
+ font-weight: bold;
+ font-size: 13px;
+ }
+
+ .col-debit-header,
+ .col-credit-header {
+ background-color: #ecf5ff;
+ color: #409eff;
+ font-weight: bold;
+ }
+ }
+
+ .amount-header {
+ th {
+ font-size: 11px;
+ padding: 2px 0;
+ background-color: #f5f7fa;
+ }
+ }
+
+ .col-summary {
+ width: 160px;
+ min-width: 160px;
+ }
+
+ .col-subject {
+ width: 180px;
+ min-width: 180px;
+ }
+
+ .col-action {
+ width: 60px;
+ min-width: 60px;
+ text-align: center;
+ }
+
+ .amount-cell {
+ width: 24px;
+ min-width: 24px;
+ max-width: 24px;
+ padding: 0;
+ font-size: 13px;
+ font-family: 'Courier New', monospace;
+ cursor: pointer;
+ text-align: center;
+
+ &:hover {
+ background-color: #f5f7fa;
+ }
+
+ span {
+ display: block;
+ width: 100%;
+ height: 100%;
+ line-height: 36px;
+
+ &.zero {
+ color: #c0c4cc;
+ }
+ }
+ }
+
+ .debit-input-cell,
+ .credit-input-cell {
+ padding: 0;
+ background-color: #ecf5ff;
+
+ .full-width-input {
+ width: 100%;
+
+ :deep(.el-input__wrapper) {
+ padding: 0 10px;
+ box-shadow: none;
+ background-color: transparent;
+ }
+
+ input {
+ text-align: right;
+ font-size: 14px;
+ height: 34px;
+ }
+ }
+ }
+
+ tbody {
+ tr {
+ &:hover {
+ background-color: #f5f7fa;
+ }
+
+ &.selected-row {
+ background-color: #ecf5ff;
+ }
+ }
+
+ td {
+ .el-input {
+ .el-input__wrapper {
+ box-shadow: none;
+ padding: 0 5px;
+ }
+
+ input {
+ text-align: center;
+ height: 34px;
+ }
+ }
+
+ .el-select {
+ width: 100%;
+
+ .el-input__wrapper {
+ box-shadow: none;
+ }
+
+ input {
+ text-align: center;
+ height: 34px;
+ }
+ }
+ }
+
+ .col-summary {
+ .el-input input {
+ text-align: left;
+ padding-left: 10px;
+ }
+ }
+
+ .col-subject {
+ position: relative;
+
+ .el-select {
+ .el-input input {
+ font-size: 12px;
+ }
+ }
+
+ .subject-name {
+ font-size: 11px;
+ color: #909399;
+ margin-top: 2px;
+ line-height: 1.2;
+ }
+ }
+ }
+
+ .total-row {
+ background-color: #fdf6ec;
+
+ td {
+ font-weight: bold;
+ }
+
+ .total-cell {
+ background-color: #fdf6ec;
+ font-weight: bold;
+ }
+ }
+}
+
+.voucher-toolbar {
+ display: flex;
+ justify-content: flex-start;
+ padding: 10px 0;
+ margin-top: 5px;
+}
+
+.voucher-footer {
+ display: flex;
+ justify-content: flex-end;
+ padding: 0 10px;
+ margin-top: 10px;
+
+ .creator-section {
+ .label {
+ font-size: 14px;
+ color: #606266;
+ }
+ }
+}
+
+:deep(.el-dialog__body) {
+ padding: 10px 20px;
+}
+</style>
diff --git a/src/views/index.vue b/src/views/index.vue
index 4ae5fe4..00d4312 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -8,7 +8,7 @@
<!-- 椤堕儴闂�欐潯 -->
<div class="welcome-banner">
<div class="welcome-title">
- <span class="welcome-user">{{ userStore.roleName || '绯荤粺绠$悊鍛�' }}</span>
+ <span class="welcome-user">{{ userStore.nickName || '绯荤粺绠$悊鍛�' }}</span>
<span> 鎮ㄥソ锛佺鎮ㄥ紑蹇冩瘡涓�澶�</span>
</div>
<div class="welcome-time">鐧诲綍浜�: {{ userStore.currentLoginTime }}</div>
@@ -728,7 +728,6 @@
<style scoped>
.dashboard {
- background: #f5f7fa;
min-height: 100vh;
padding: 20px;
box-sizing: border-box;
diff --git a/src/views/inventoryManagement/dispatchLog/Record.vue b/src/views/inventoryManagement/dispatchLog/Record.vue
index 53bb3c3..6060cf5 100644
--- a/src/views/inventoryManagement/dispatchLog/Record.vue
+++ b/src/views/inventoryManagement/dispatchLog/Record.vue
@@ -1,6 +1,6 @@
<template>
- <div class="app-container">
- <div class="search_form">
+ <div>
+ <div class="search_form" style="margin-bottom: 10px;">
<div>
<span class="search_title ml10">鍑哄簱鏃ユ湡锛�</span>
<el-date-picker
@@ -27,6 +27,7 @@
>
</div>
<div>
+ <el-button type="primary" @click="handleBatchApprove">瀹℃壒</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
<el-button type="primary" plain @click="handlePrint">鎵撳嵃</el-button>
@@ -67,6 +68,11 @@
show-overflow-tooltip
/>
<el-table-column
+ label="鎵瑰彿"
+ prop="batchNo"
+ show-overflow-tooltip
+ />
+ <el-table-column
label="鍗曚綅"
prop="unit"
show-overflow-tooltip
@@ -88,6 +94,11 @@
{{ getRecordType(scope.row.recordType) }}
</template>
</el-table-column>
+ <el-table-column label="瀹℃壒鐘舵��" prop="approvalStatus" show-overflow-tooltip>
+ <template #default="scope">
+ {{ getApprovalStatusLabel(scope.row.approvalStatus) }}
+ </template>
+ </el-table-column>
</el-table>
<pagination
v-show="total > 0"
@@ -109,7 +120,8 @@
import { getCurrentDate } from "@/utils/index.js";
import {
getStockOutPage,
- delStockOut,
+ delPendingStockOut,
+ batchApproveStockOutRecords,
} from "@/api/inventoryManagement/stockOut.js";
import {
findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions,
@@ -133,6 +145,10 @@
type: String,
required: true,
default: '0'
+ },
+ topParentProductId: {
+ type: [String, Number],
+ default: undefined
}
})
@@ -163,7 +179,7 @@
};
const getList = () => {
tableLoading.value = true;
- getStockOutPage({ ...searchForm.value, ...page, type: props.type })
+ getStockOutPage({ ...searchForm.value, ...page, type: props.type, topParentProductId: props.topParentProductId })
.then((res) => {
tableLoading.value = false;
tableData.value = res.data.records;
@@ -180,6 +196,25 @@
const getRecordType = (recordType) => {
return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
}
+
+const approvalStatusLabelMap = {
+ 0: "寰呭鎵�",
+ 1: "閫氳繃",
+ 2: "椹冲洖",
+ pending: "寰呭鎵�",
+ approved: "閫氳繃",
+ rejected: "椹冲洖",
+ PENDING: "寰呭鎵�",
+ APPROVED: "閫氳繃",
+ REJECTED: "椹冲洖",
+};
+
+const getApprovalStatusLabel = (status) => {
+ if (status === null || status === undefined || status === "") {
+ return "寰呭鎵�";
+ }
+ return approvalStatusLabelMap[status] || "寰呭鎵�";
+};
// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
const fetchStockRecordTypeOptions = () => {
@@ -203,6 +238,44 @@
console.log("selection", selectedRows.value);
};
const expandedRowKeys = ref([]);
+
+const handleBatchApprove = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ const ids = selectedRows.value.map((item) => item.id);
+ ElMessageBox.confirm("璇烽�夋嫨瀹℃壒缁撴灉", "瀹℃壒", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ type: "warning",
+ distinguishCancelAndClose: true,
+ })
+ .then(() => {
+ batchApproveStockOutRecords({ ids, approvalStatus: 1 })
+ .then(() => {
+ proxy.$modal.msgSuccess("瀹℃壒閫氳繃鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("瀹℃壒閫氳繃澶辫触");
+ });
+ })
+ .catch((action) => {
+ if (action === "cancel") {
+ batchApproveStockOutRecords({ ids, approvalStatus: 2 })
+ .then(() => {
+ proxy.$modal.msgSuccess("瀹℃壒椹冲洖鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("瀹℃壒椹冲洖澶辫触");
+ });
+ return;
+ }
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
// 瀵煎嚭
const handleOut = () => {
@@ -234,7 +307,7 @@
type: "warning",
})
.then(() => {
- delStockOut(ids).then((res) => {
+ delPendingStockOut(ids).then((res) => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
getList();
});
@@ -405,7 +478,6 @@
<div class="print-page">
<div class="delivery-note">
<div class="header">
- <div class="company-name">鑻辨辰闃查攬鏂版潗鏂欐湁闄愬叕鍙�</div>
<div class="document-title">闆跺敭鍙戣揣鍗�</div>
</div>
@@ -417,7 +489,7 @@
</div>
<div>
<span class="label">瀹㈡埛鍚嶇О锛�</span>
- <span class="value">${item.supplierName || '寮犵埍鏈�'}</span>
+ <span class="value">${item.supplierName}</span>
</div>
</div>
<div class="info-row">
@@ -537,6 +609,14 @@
getList();
fetchStockRecordTypeOptions();
});
+
+watch(
+ () => props.topParentProductId,
+ () => {
+ page.current = 1;
+ getList();
+ }
+);
</script>
<style scoped lang="scss">
diff --git a/src/views/inventoryManagement/dispatchLog/index.vue b/src/views/inventoryManagement/dispatchLog/index.vue
index 88d9984..458e9ec 100644
--- a/src/views/inventoryManagement/dispatchLog/index.vue
+++ b/src/views/inventoryManagement/dispatchLog/index.vue
@@ -1,38 +1,55 @@
<!-- 鍦ㄤ綘鐨勪富椤甸潰涓� -->
<template>
<div class="app-container">
- <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <div v-loading="loading" element-loading-text="鍔犺浇涓�..." style="min-height: 80vh;">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange" v-if="!loading">
<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" />
+ :label="tab.productName"
+ :name="tab.id"
+ :key="tab.id">
+ <Record v-bind="{ type: tab.type, topParentProductId: activeTab }" v-if="tab.id === activeTab" />
</el-tab-pane>
</el-tabs>
+ </div>
</div>
</template>
<script setup>
+import { ref, onMounted } from 'vue';
+import { productTreeList } from "@/api/basicData/product.js";
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 activeTab = ref(null)
+const tabs = ref([])
+const loading = ref(false)
+
+const resolveTypeByName = (name) => {
+ return String(name || "").includes("涓嶅悎鏍�") ? "1" : "0";
+};
const handleTabChange = (tabName) => {
activeTab.value = tabName;
- type.value = tabName === 'qualified' ? 0 : 1
}
+
+const fetchProducts = async () => {
+ loading.value = true;
+ try {
+ const res = await productTreeList();
+ tabs.value = res
+ .filter((item) => item.parentId === null)
+ .map(({ id, productName }) => ({
+ id,
+ productName,
+ type: resolveTypeByName(productName),
+ }));
+ if (tabs.value.length > 0) {
+ activeTab.value = tabs.value[0].id;
+ }
+ } finally {
+ loading.value = false;
+ }
+}
+
+onMounted(() => {
+ fetchProducts();
+})
</script>
diff --git a/src/views/inventoryManagement/issueManagement/index.vue b/src/views/inventoryManagement/issueManagement/index.vue
index f5d2ea9..2bba7d0 100644
--- a/src/views/inventoryManagement/issueManagement/index.vue
+++ b/src/views/inventoryManagement/issueManagement/index.vue
@@ -44,7 +44,7 @@
<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" @click="openForm(scope.row);">棰嗙敤</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/inventoryManagement/receiptManagement/Record.vue b/src/views/inventoryManagement/receiptManagement/Record.vue
index 3f90edf..7ca8fea 100644
--- a/src/views/inventoryManagement/receiptManagement/Record.vue
+++ b/src/views/inventoryManagement/receiptManagement/Record.vue
@@ -1,6 +1,6 @@
<template>
- <div class="app-container">
- <div class="search_form">
+ <div>
+ <div class="search_form" style="margin-bottom: 10px;">
<div>
<span class="search_title ml10">鍏ュ簱鏃ユ湡锛�</span>
<el-date-picker v-model="searchForm.timeStr"
@@ -31,6 +31,7 @@
</el-button>
</div>
<div>
+ <el-button type="primary" @click="handleBatchApprove">瀹℃壒</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="danger"
plain
@@ -67,6 +68,9 @@
<el-table-column label="瑙勬牸鍨嬪彿"
prop="model"
show-overflow-tooltip/>
+ <el-table-column label="鎵瑰彿"
+ prop="batchNo"
+ show-overflow-tooltip/>
<el-table-column label="鍗曚綅"
prop="unit"
show-overflow-tooltip/>
@@ -81,6 +85,13 @@
show-overflow-tooltip>
<template #default="scope">
{{ getRecordType(scope.row.recordType) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹℃壒鐘舵��"
+ prop="approvalStatus"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ getApprovalStatusLabel(scope.row.approvalStatus) }}
</template>
</el-table-column>
</el-table>
@@ -106,7 +117,8 @@
import {ElMessageBox} from "element-plus";
import {
getStockInRecordListPage,
- batchDeleteStockInRecords,
+ batchDeletePendingStockInRecords,
+ batchApproveStockInRecords,
} from "@/api/inventoryManagement/stockInRecord.js";
import {
findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions,
@@ -119,6 +131,10 @@
type: String,
required: true,
default: '0'
+ },
+ topParentProductId: {
+ type: [String, Number],
+ default: undefined
}
})
@@ -152,6 +168,25 @@
return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
}
+const approvalStatusLabelMap = {
+ 0: "寰呭鎵�",
+ 1: "閫氳繃",
+ 2: "椹冲洖",
+ pending: "寰呭鎵�",
+ approved: "閫氳繃",
+ rejected: "椹冲洖",
+ PENDING: "寰呭鎵�",
+ APPROVED: "閫氳繃",
+ REJECTED: "椹冲洖",
+};
+
+const getApprovalStatusLabel = (status) => {
+ if (status === null || status === undefined || status === "") {
+ return "寰呭鎵�";
+ }
+ return approvalStatusLabelMap[status] || "寰呭鎵�";
+};
+
const pageProductChange = obj => {
page.current = obj.page;
page.size = obj.limit;
@@ -160,7 +195,7 @@
const getList = () => {
tableLoading.value = true;
- const params = {...page, type: props.type};
+ const params = {...page, type: props.type, topParentProductId: props.topParentProductId};
params.timeStr = searchForm.value.timeStr;
params.productName = searchForm.value.productName;
params.recordType = searchForm.value.recordType;
@@ -195,6 +230,44 @@
const expandedRowKeys = ref([]);
+const handleBatchApprove = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ const ids = selectedRows.value.map(item => item.id);
+ ElMessageBox.confirm("璇烽�夋嫨瀹℃壒缁撴灉", "瀹℃壒", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ type: "warning",
+ distinguishCancelAndClose: true,
+ })
+ .then(() => {
+ batchApproveStockInRecords({ids, approvalStatus: 1})
+ .then(() => {
+ proxy.$modal.msgSuccess("瀹℃壒閫氳繃鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("瀹℃壒閫氳繃澶辫触");
+ });
+ })
+ .catch((action) => {
+ if (action === "cancel") {
+ batchApproveStockInRecords({ids, approvalStatus: 2})
+ .then(() => {
+ proxy.$modal.msgSuccess("瀹℃壒椹冲洖鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("瀹℃壒椹冲洖澶辫触");
+ });
+ return;
+ }
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
// 瀵煎嚭
const handleOut = () => {
ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
@@ -225,7 +298,7 @@
type: "warning",
})
.then(() => {
- batchDeleteStockInRecords(ids)
+ batchDeletePendingStockInRecords(ids)
.then(() => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
getList();
@@ -243,6 +316,14 @@
getList();
fetchStockRecordTypeOptions();
});
+
+watch(
+ () => props.topParentProductId,
+ () => {
+ page.current = 1;
+ getList();
+ }
+);
</script>
<style scoped lang="scss"></style>
diff --git a/src/views/inventoryManagement/receiptManagement/index.vue b/src/views/inventoryManagement/receiptManagement/index.vue
index 8ca110f..17a0823 100644
--- a/src/views/inventoryManagement/receiptManagement/index.vue
+++ b/src/views/inventoryManagement/receiptManagement/index.vue
@@ -1,36 +1,55 @@
<template>
<div class="app-container">
- <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <div v-loading="loading" element-loading-text="鍔犺浇涓�..." style="min-height: 80vh;">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange" v-if="!loading">
<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" />
+ :label="tab.productName"
+ :name="tab.id"
+ :key="tab.id">
+ <Record v-bind="{ type: tab.type, topParentProductId: activeTab }" v-if="tab.id === activeTab" />
</el-tab-pane>
</el-tabs>
+ </div>
</div>
</template>
<script setup>
+import { ref, onMounted } from 'vue';
+import { productTreeList } from "@/api/basicData/product.js";
import Record from "@/views/inventoryManagement/receiptManagement/Record.vue";
-const activeTab = ref('qualified')
-const type = ref(0)
-const tabs = ref([
- {
- label: '鍚堟牸鍏ュ簱',
- name: 'qualified',
- type: '0'
- },
- {
- label: '涓嶅悎鏍煎叆搴�',
- name: 'unqualified',
- type: '1'
- }
-])
+const activeTab = ref(null)
+const tabs = ref([])
+const loading = ref(false)
+
+const resolveTypeByName = (name) => {
+ return String(name || "").includes("涓嶅悎鏍�") ? "1" : "0";
+};
const handleTabChange = (tabName) => {
activeTab.value = tabName;
- type.value = tabName === 'qualified' ? 0 : 1
}
+
+const fetchProducts = async () => {
+ loading.value = true;
+ try {
+ const res = await productTreeList();
+ tabs.value = res
+ .filter((item) => item.parentId === null)
+ .map(({ id, productName }) => ({
+ id,
+ productName,
+ type: resolveTypeByName(productName),
+ }));
+ if (tabs.value.length > 0) {
+ activeTab.value = tabs.value[0].id;
+ }
+ } finally {
+ loading.value = false;
+ }
+}
+
+onMounted(() => {
+ fetchProducts();
+})
</script>
diff --git a/src/views/inventoryManagement/stockManagement/FrozenAndThaw.vue b/src/views/inventoryManagement/stockManagement/FrozenAndThaw.vue
index 463cb83..a7a9400 100644
--- a/src/views/inventoryManagement/stockManagement/FrozenAndThaw.vue
+++ b/src/views/inventoryManagement/stockManagement/FrozenAndThaw.vue
@@ -8,10 +8,27 @@
>
<el-form label-width="140px" :model="formState" ref="formRef">
<el-form-item
+ label="搴撳瓨绫诲瀷"
+ prop="type"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨搴撳瓨绫诲瀷',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-select v-model="formState.type" placeholder="璇烽�夋嫨搴撳瓨绫诲瀷" @change="handleChangeType">
+ <el-option label="鍚堟牸搴撳瓨" value="qualified" :disabled="(operationType === 'frozen' && props.record.qualifiedUnLockedQuantity <= 0) || (operationType === 'thaw' && props.record.qualifiedLockedQuantity <= 0)" />
+ <el-option label="涓嶅悎鏍煎簱瀛�" value="unqualified" :disabled="(operationType === 'frozen' && props.record.unQualifiedUnLockedQuantity <= 0) || (operationType === 'thaw' && props.record.unQualifiedLockedQuantity <= 0)" />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item
:label="operationType === 'frozen' ? '鍐荤粨鏁伴噺锛�' : '瑙e喕鏁伴噺锛�'"
prop="lockedQuantity"
>
- <el-input-number v-model="formState.lockedQuantity" :step="1" :min="1" precision="0" style="width: 100%" :max="maxCount" />
+ <el-input-number v-model="formState.lockedQuantity" :step="1" :min="maxCount > 0 ? 1 : 0" precision="0" style="width: 100%" :max="maxCount" :disabled="maxCount < 1" />
</el-form-item>
</el-form>
@@ -26,7 +43,7 @@
</template>
<script setup>
-import {ref, computed, getCurrentInstance} from "vue";
+import {ref, computed, getCurrentInstance, onMounted} from "vue";
import {frozenStockInventory, thawStockInventory} from "@/api/inventoryManagement/stockInventory.js";
import {frozenStockUninventory, thawStockUninventory} from "@/api/inventoryManagement/stockUninventory.js";
@@ -42,12 +59,6 @@
default: 'frozen',
},
- type: {
- type: String,
- required: true,
- default: 'qualified',
- },
-
record: {
type: Object,
default: () => {},
@@ -58,7 +69,8 @@
// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
const formState = ref({
- lockedQuantity: 0,
+ type: undefined,
+ lockedQuantity: undefined,
});
const isShow = computed({
@@ -76,7 +88,8 @@
const closeModal = () => {
// 閲嶇疆琛ㄥ崟鏁版嵁
formState.value = {
- lockedQuantity: undefined
+ lockedQuantity: undefined,
+ type: undefined,
};
isShow.value = false;
};
@@ -84,17 +97,32 @@
const maxCount = computed(() => {
// 鍐荤粨搴撳瓨鏈�澶ф暟閲忎负鏈В鍐绘暟閲�
if (props.operationType === 'frozen') {
- return props.record.unLockedQuantity
+ // 鍐荤粨鍚堟牸搴撳瓨鏈�澶ф暟閲忎负鏈В鍐诲悎鏍兼暟閲�
+ if (formState.value.type === 'qualified') {
+ return Math.max(0, props.record.qualifiedUnLockedQuantity || 0)
+ }
+ // 鍐荤粨涓嶅悎鏍煎簱瀛樻渶澶ф暟閲忎负鏈В鍐讳笉鍚堟牸鏁伴噺
+ return Math.max(0, props.record.unQualifiedUnLockedQuantity || 0)
}
// 瑙e喕搴撳瓨鏈�澶ф暟閲忎负宸插喕缁撴暟閲�
- return props.record.lockedQuantity
+ if (formState.value.type === 'qualified') {
+ // 瑙e喕鍚堟牸搴撳瓨鏈�澶ф暟閲忎负宸插喕缁撳悎鏍兼暟閲�
+ return Math.max(0, props.record.qualifiedLockedQuantity || 0)
+ }
+ // 瑙e喕涓嶅悎鏍煎簱瀛樻渶澶ф暟閲忎负宸插喕缁撲笉鍚堟牸鏁伴噺
+ return Math.max(0, props.record.unQualifiedLockedQuantity || 0)
})
+
+const handleChangeType = (type) => {
+ formState.value.lockedQuantity = maxCount.value;
+}
const handleSubmit = () => {
proxy.$refs["formRef"].validate(valid => {
if (valid) {
- const data = Object.assign({id: props.record.id}, formState.value);
- if (props.type === 'qualified') {
+ const data = Object.assign({}, formState.value);
+ if (formState.value.type === 'qualified') {
+ data.id = props.record.qualifiedId;
// 鍐荤粨
if (props.operationType === 'frozen') {
frozenStockInventory(data).then(res => {
@@ -122,6 +150,7 @@
})
}
} else {
+ data.id = props.record.unQualifiedId;
if (props.operationType === 'frozen') {
frozenStockUninventory(data).then(res => {
if (res.code === 200) {
@@ -153,7 +182,6 @@
};
onMounted(() => {
- formState.value.lockedQuantity = maxCount.value;
})
defineExpose({
@@ -161,4 +189,4 @@
handleSubmit,
isShow,
});
-</script>
+</script>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockManagement/New.vue b/src/views/inventoryManagement/stockManagement/New.vue
index 1f86fd6..2addb95 100644
--- a/src/views/inventoryManagement/stockManagement/New.vue
+++ b/src/views/inventoryManagement/stockManagement/New.vue
@@ -1,71 +1,90 @@
<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="[
+ <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">
+ ]">
+ <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 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 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" style="width: 100%" />
+ <el-form-item label="搴撳瓨绫诲瀷"
+ prop="type"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨搴撳瓨绫诲瀷',
+ trigger: 'change',
+ }
+ ]">
+ <el-select v-model="formState.type"
+ placeholder="璇烽�夋嫨搴撳瓨绫诲瀷">
+ <el-option label="鍚堟牸搴撳瓨"
+ value="qualified" />
+ <el-option label="涓嶅悎鏍煎簱瀛�"
+ value="unqualified" />
+ </el-select>
</el-form-item>
-
- <el-form-item
- v-if="type === 'qualified'"
- label="搴撳瓨棰勮鏁伴噺"
- prop="warnNum"
- >
- <el-input-number v-model="formState.warnNum" :step="1" :min="0" :max="formState.qualitity" style="width: 100%" />
+ <el-form-item label="搴撳瓨鏁伴噺"
+ prop="qualitity">
+ <el-input-number v-model="formState.qualitity"
+ :step="1"
+ :min="1"
+ style="width: 100%" />
</el-form-item>
-
- <el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="formState.remark" type="textarea" />
+ <el-form-item label="鎵瑰彿"
+ prop="batchNo">
+ <el-input v-model="formState.batchNo"
+ placeholder="璇疯緭鍏ユ壒鍙�" />
+ </el-form-item>
+ <el-form-item v-if="formState.type === 'qualified'"
+ label="搴撳瓨棰勮鏁伴噺"
+ prop="warnNum">
+ <el-input-number v-model="formState.warnNum"
+ :step="1"
+ :min="0"
+ :max="formState.qualitity"
+ 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
- />
+ <ProductSelectDialog v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ :top-product-parent-id="props.topProductParentId"
+ single />
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button type="primary"
+ @click="handleSubmit">纭</el-button>
<el-button @click="closeModal">鍙栨秷</el-button>
</div>
</template>
@@ -74,116 +93,131 @@
</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";
+ import { ref, computed, watch, getCurrentInstance } from "vue";
+ import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import { addStockInRecordOnly } from "@/api/inventoryManagement/stockInventory.js";
+ import { createStockUnInventory } from "@/api/inventoryManagement/stockUninventory.js";
-const props = defineProps({
- visible: {
- type: Boolean,
- required: true,
- },
+ const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ topProductParentId: {
+ type: Number,
+ default: undefined,
+ required: false,
+ },
+ });
- type: {
- type: String,
- required: true,
- default: 'qualified',
- },
-});
+ const emit = defineEmits(["update:visible", "completed"]);
-const emit = defineEmits(['update:visible', 'completed']);
-
-// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
-const formState = ref({
- productId: undefined,
- productModelId: undefined,
- productName: "",
- productModelName: "",
- unit: "",
- qualitity: 0,
- warnNum: 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 = {
+ // 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+ const formState = ref({
productId: undefined,
productModelId: undefined,
productName: "",
productModelName: "",
- description: '',
- };
- isShow.value = false;
-};
+ unit: "",
+ type: undefined,
+ qualitity: 0,
+ batchNo: null,
+ warnNum: 0,
+ remark: "",
+ });
-// 浜у搧閫夋嫨澶勭悊
-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 isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit("update:visible", val);
+ },
+ });
-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("鎻愪氦鎴愬姛");
- })
- }
+ const showProductSelectDialog = ref(false);
+ // 鎵瑰彿涓虹┖鏃惰浆涓� null
+ watch(
+ () => formState.value.batchNo,
+ val => {
+ if (val === "") {
+ formState.value.batchNo = null;
+ }
}
- })
-};
+ );
+ let { proxy } = getCurrentInstance();
-defineExpose({
- closeModal,
- handleSubmit,
- isShow,
-});
+ const closeModal = () => {
+ // 閲嶇疆琛ㄥ崟鏁版嵁
+ formState.value = {
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ unit: "",
+ type: undefined,
+ qualitity: 0,
+ batchNo: null,
+ warnNum: 0,
+ remark: "",
+ };
+ 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 (formState.value.type === "qualified") {
+ addStockInRecordOnly(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit("completed");
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
+ } else {
+ formState.value.warnNum = 0;
+ 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
index 8b15db1..4286772 100644
--- a/src/views/inventoryManagement/stockManagement/Qualified.vue
+++ b/src/views/inventoryManagement/stockManagement/Qualified.vue
@@ -25,17 +25,18 @@
<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="batchNo" 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="lockedQuantity" 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">
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="90" align="center">
<template #default="scope">
- <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">棰嗙敤</el-button>
- <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
- <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</el-button>
+ <el-button link type="primary" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">棰嗙敤</el-button>
+ <el-button link type="primary" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
+ <el-button link type="primary" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/inventoryManagement/stockManagement/Record.vue b/src/views/inventoryManagement/stockManagement/Record.vue
new file mode 100644
index 0000000..7c0a461
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/Record.vue
@@ -0,0 +1,223 @@
+<template>
+ <div>
+ <div class="search_form mb10">
+ <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 type="info" plain icon="Upload" @click="isShowImportModal = 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, index) => index" style="width: 100%"
+ :row-class-name="tableRowClassName" height="calc(100vh - 18.5em)">
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="浜у搧澶х被" prop="productName" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" show-overflow-tooltip />
+ <el-table-column label="鎵瑰彿" prop="batchNo" show-overflow-tooltip />
+ <el-table-column label="鍚堟牸搴撳瓨鏁伴噺" prop="qualifiedQuantity" show-overflow-tooltip />
+ <el-table-column label="涓嶅悎鏍煎簱瀛樻暟閲�" prop="unQualifiedQuantity" show-overflow-tooltip />
+ <el-table-column label="鍚堟牸鍐荤粨鏁伴噺" prop="qualifiedLockedQuantity" show-overflow-tooltip />
+ <el-table-column label="涓嶅悎鏍煎喕缁撴暟閲�" prop="unQualifiedLockedQuantity" 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="90" align="center">
+ <template #default="scope">
+ <el-button link type="primary" @click="showSubtractModal(scope.row)" :disabled="((scope.row.qualifiedUnLockedQuantity || 0) + (scope.row.qualifiedPendingOutQuantity || 0) <= 0) && ((scope.row.unQualifiedUnLockedQuantity || 0) + (scope.row.unQualifiedPendingOutQuantity || 0) <= 0)">棰嗙敤</el-button>
+ <el-button link type="primary" v-if="scope.row.unQualifiedUnLockedQuantity > 0 || scope.row.qualifiedUnLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
+ <el-button link type="primary" v-if="scope.row.qualifiedLockedQuantity > 0 || scope.row.unQualifiedLockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</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"
+ :top-product-parent-id="props.productId"
+ @completed="handleQuery" />
+
+ <subtract-stock-inventory v-if="isShowSubtractModal"
+ v-model:visible="isShowSubtractModal"
+ :record="record"
+ :type="record.stockType"
+ @completed="handleQuery" />
+ <!-- 瀵煎叆搴撳瓨-->
+ <import-stock-inventory v-if="isShowImportModal"
+ v-model:visible="isShowImportModal"
+ type="qualified"
+ @uploadSuccess="handleQuery" />
+ <!-- 鍐荤粨/瑙e喕搴撳瓨-->
+ <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
+ v-model:visible="isShowFrozenAndThawModal"
+ :record="record"
+ :operation-type="operationType"
+ :type="record.stockType"
+ @completed="handleQuery" />
+ </div>
+</template>
+
+<script setup>
+import pagination from '@/components/PIMTable/Pagination.vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import {ElMessage, ElMessageBox} from "element-plus";
+import {
+ getStockInventoryListPageCombined
+} from "@/api/inventoryManagement/stockInventory.js";
+const props = defineProps({
+ productId: {
+ type: Number,
+ required: true,
+ default: 0
+ }
+});
+
+const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue"));
+const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue"));
+const ImportStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Import.vue"));
+const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.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)
+// 鏄惁鏄剧ず鍐荤粨/瑙e喕寮规
+const isShowFrozenAndThawModal = ref(false)
+// 鎿嶄綔绫诲瀷
+const operationType = ref('frozen')
+// 鏄惁鏄剧ず瀵煎叆寮规
+const isShowImportModal = ref(false)
+const data = reactive({
+ searchForm: {
+ productName: '',
+ topParentProductId: props.productId,
+ }
+})
+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
+ getStockInventoryListPageCombined({ ...searchForm.value, ...page }).then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+ total.value = res.data.total
+ // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+ // checkStockAndCreatePurchase();
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+const handleFileSuccess = (response) => {
+ const { code, msg } = response;
+ if (code == 200) {
+ ElMessage({ message: "瀵煎叆鎴愬姛", type: "success" });
+ upload.open = false;
+ emits("uploadSuccess");
+ } else {
+ ElMessage({ message: msg, type: "error" });
+ }
+};
+
+// 鐐瑰嚮棰嗙敤
+const showSubtractModal = (row) => {
+ record.value = row
+ isShowSubtractModal.value = true
+}
+
+// 鐐瑰嚮鍐荤粨
+const showFrozenModal = (row) => {
+ record.value = row
+ isShowFrozenAndThawModal.value = true
+ operationType.value = 'frozen'
+}
+
+// 鐐瑰嚮瑙e喕
+const showThawModal = (row) => {
+ record.value = row
+ isShowFrozenAndThawModal.value = true
+ operationType.value = 'thaw'
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+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?.qualifiedUnLockedQuantity ?? 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", {topParentProductId: props.productId}, '搴撳瓨淇℃伅.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
index a277a00..2cdfcb6 100644
--- a/src/views/inventoryManagement/stockManagement/Subtract.vue
+++ b/src/views/inventoryManagement/stockManagement/Subtract.vue
@@ -38,6 +38,23 @@
</el-form-item>
<el-form-item
+ label="搴撳瓨绫诲瀷"
+ prop="type"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨搴撳瓨绫诲瀷',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-select v-model="formState.type" placeholder="璇烽�夋嫨搴撳瓨绫诲瀷" @change="handleTypeChange">
+ <el-option label="鍚堟牸搴撳瓨" value="qualified" :disabled="(props.record.qualifiedUnLockedQuantity || 0) + (props.record.qualifiedPendingOutQuantity || 0) <= 0" />
+ <el-option label="涓嶅悎鏍煎簱瀛�" value="unqualified" :disabled="(props.record.unQualifiedUnLockedQuantity || 0) + (props.record.unQualifiedPendingOutQuantity || 0) <= 0" />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item
label="鏁伴噺"
prop="qualitity"
>
@@ -68,8 +85,8 @@
<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";
+import {addStockOutRecordOnly} from "@/api/inventoryManagement/stockInventory.js";
+import {addUnqualifiedStockOutRecordOnly} from "@/api/inventoryManagement/stockUninventory.js";
const props = defineProps({
visible: {
@@ -79,12 +96,7 @@
record: {
type: Object,
default: () => {},
- },
- type: {
- type: String,
- required: true,
- default: 'qualified',
- },
+ }
});
const emit = defineEmits(['update:visible', 'completed']);
@@ -94,8 +106,17 @@
})
const maxQuality = computed(() => {
- return props.record.unLockedQuantity ? props.record.unLockedQuantity : 0;
+ if (formState.value.type === 'qualified') {
+ // 鍚堟牸鍙嚭 = 鏈喕缁撻噺 + 寰呭鏍稿嚭搴撻噺锛堝嵆宸茬敵璇蜂絾灏氭湭瀹℃壒鐨勬暟閲忥級
+ return Math.max(1, Number(props.record.qualifiedUnLockedQuantity || 0) + Number(props.record.qualifiedPendingOutQuantity || 0));
+ } else {
+ return Math.max(1, Number(props.record.unQualifiedUnLockedQuantity || 0) + Number(props.record.unQualifiedPendingOutQuantity || 0));
+ }
})
+
+const handleTypeChange = () => {
+ formState.value.qualitity = undefined;
+}
const initFormData = () => {
if (props.record) {
@@ -145,7 +166,6 @@
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;
@@ -169,8 +189,8 @@
proxy.$modal.msgError("璇烽�夋嫨瑙勬牸");
return;
}
- if (props.type === 'qualified') {
- subtractStockInventory(formState.value).then(res => {
+ if (formState.value.type === 'qualified') {
+ addStockOutRecordOnly(formState.value).then(res => {
// 鍏抽棴妯℃�佹
isShow.value = false;
// 鍛婄煡鐖剁粍浠跺凡瀹屾垚
@@ -178,7 +198,7 @@
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
})
} else {
- subtractStockUnInventory(formState.value).then(res => {
+ addUnqualifiedStockOutRecordOnly(formState.value).then(res => {
// 鍏抽棴妯℃�佹
isShow.value = false;
// 鍛婄煡鐖剁粍浠跺凡瀹屾垚
@@ -196,4 +216,4 @@
handleSubmit,
isShow,
});
-</script>
+</script>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockManagement/Unqualified.vue b/src/views/inventoryManagement/stockManagement/Unqualified.vue
index 9b5652d..55662be 100644
--- a/src/views/inventoryManagement/stockManagement/Unqualified.vue
+++ b/src/views/inventoryManagement/stockManagement/Unqualified.vue
@@ -27,11 +27,11 @@
<el-table-column label="鍐荤粨鏁伴噺" prop="lockedQuantity" 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">
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="90" align="center">
<template #default="scope">
- <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">棰嗙敤</el-button>
- <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
- <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</el-button>
+ <el-button link type="primary" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">棰嗙敤</el-button>
+ <el-button link type="primary" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
+ <el-button link type="primary" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/inventoryManagement/stockManagement/index.vue b/src/views/inventoryManagement/stockManagement/index.vue
index 347de38..b3aa7ee 100644
--- a/src/views/inventoryManagement/stockManagement/index.vue
+++ b/src/views/inventoryManagement/stockManagement/index.vue
@@ -1,33 +1,44 @@
<template>
<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">
- <component :is="tab.name === 'qualified' ? QualifiedRecord : UnqualifiedRecord" />
- </el-tab-pane>
- </el-tabs>
+ <div v-loading="loading" element-loading-text="鍔犺浇涓�..." style="min-height: 80vh;">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange" v-if="!loading">
+ <el-tab-pane v-for="tab in products"
+ :label="tab.productName"
+ :name="tab.id"
+ :key="tab.id">
+ <Record :product-id="tab.id" v-if="tab.id === activeTab" />
+ </el-tab-pane>
+ </el-tabs>
+ </div>
</div>
</template>
<script setup>
-import QualifiedRecord from "@/views/inventoryManagement/stockManagement/Qualified.vue";
-import UnqualifiedRecord from "@/views/inventoryManagement/stockManagement/Unqualified.vue";
-
-const activeTab = ref('qualified')
-const tabs = ref([
- {
- label: '鍚堟牸搴撳瓨',
- name: 'qualified'
- },
- {
- label: '涓嶅悎鏍煎簱瀛�',
- name: 'unqualified'
- }
-])
+import { ref, onMounted } from 'vue';
+import { productTreeList } from "@/api/basicData/product.js";
+import Record from "@/views/inventoryManagement/stockManagement/Record.vue";
+const products = ref([])
+const activeTab = ref(null)
+const loading = ref(false)
const handleTabChange = (tabName) => {
activeTab.value = tabName;
}
-</script>
+
+const fetchProducts = async () => {
+ loading.value = true;
+ try {
+ const res = await productTreeList();
+ products.value = res.filter((item) => item.parentId === null).map(({ id, productName }) => ({ id, productName }));
+ if (products.value.length > 0) {
+ activeTab.value = products.value[0].id;
+ }
+ } finally {
+ loading.value = false;
+ }
+}
+
+onMounted(() => {
+ fetchProducts();
+})
+</script>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockWarning/index.vue b/src/views/inventoryManagement/stockWarning/index.vue
index d0d5834..b5216cb 100644
--- a/src/views/inventoryManagement/stockWarning/index.vue
+++ b/src/views/inventoryManagement/stockWarning/index.vue
@@ -119,9 +119,9 @@
<!-- 鎿嶄綔鍒� -->
<el-table-column fixed="right" label="鎿嶄綔" width="200" align="center">
<template #default="scope">
- <el-button link type="primary" size="small" @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button link type="primary" @click="handleEdit(scope.row)">缂栬緫</el-button>
<!-- <el-button link type="success" size="small" @click="handleProcess(scope.row)">澶勭悊@</el-button>-->
- <el-button link type="danger" size="small" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/inventoryManagement/transportTaskManagement/index.vue b/src/views/inventoryManagement/transportTaskManagement/index.vue
index 1feb54b..8e73004 100644
--- a/src/views/inventoryManagement/transportTaskManagement/index.vue
+++ b/src/views/inventoryManagement/transportTaskManagement/index.vue
@@ -681,11 +681,11 @@
text-align: right;
}
-::v-deep(.row-finished) {
+:deep(.row-finished) {
background-color: #f6ffed;
}
-::v-deep(.row-running) {
+:deep(.row-running) {
background-color: #fffbe6;
}
</style>
diff --git a/src/views/inventoryManagement/vehicleFuelManagement/index.vue b/src/views/inventoryManagement/vehicleFuelManagement/index.vue
index 8579cba..eaf543c 100644
--- a/src/views/inventoryManagement/vehicleFuelManagement/index.vue
+++ b/src/views/inventoryManagement/vehicleFuelManagement/index.vue
@@ -549,7 +549,7 @@
text-align: right;
}
-::v-deep(.row-abnormal) {
+:deep(.row-abnormal) {
background-color: #fff5f5;
}
</style>
diff --git a/src/views/lavorissue/statistics/index.vue b/src/views/lavorissue/statistics/index.vue
index 2c34f67..54e1f38 100644
--- a/src/views/lavorissue/statistics/index.vue
+++ b/src/views/lavorissue/statistics/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">鍙戞斁瀛e害锛�</span>
<el-select
diff --git a/src/views/login.vue b/src/views/login.vue
index 6217877..872537b 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -1,232 +1,401 @@
-<template>
- <div class="login">
- <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
- <h3 class="title">{{ title }}</h3>
- <el-divider />
- <el-form-item prop="username">
- <el-input
- v-model="loginForm.username"
- type="text"
- size="large"
- auto-complete="off"
- placeholder="璐﹀彿"
- >
- <template #prefix><el-icon><User /></el-icon></template>
- </el-input>
- </el-form-item>
- <el-form-item prop="password">
- <el-input
- v-model="loginForm.password"
- type="password"
- size="large"
- auto-complete="off"
- placeholder="瀵嗙爜"
- show-password
- @keyup.enter="handleLogin"
- >
- <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
- </el-input>
- </el-form-item>
-<!-- <el-form-item prop="code" v-if="captchaEnabled">-->
-<!-- <el-input-->
-<!-- v-model="loginForm.code"-->
-<!-- size="large"-->
-<!-- auto-complete="off"-->
-<!-- placeholder="楠岃瘉鐮�"-->
-<!-- style="width: 63%"-->
-<!-- @keyup.enter="handleLogin"-->
-<!-- >-->
-<!-- <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>-->
-<!-- </el-input>-->
-<!-- <div class="login-code">-->
-<!-- <img :src="codeUrl" @click="getCode" class="login-code-img"/>-->
-<!-- </div>-->
-<!-- </el-form-item>-->
- <el-form-item style="width:100%;">
- <el-button
- :loading="loading"
- size="large"
- type="primary"
- style="width:100%;"
- @click.prevent="handleLogin"
- >
- <span v-if="!loading">鐧� 褰�</span>
- <span v-else>鐧� 褰� 涓�...</span>
- </el-button>
- <div style="float: right;" v-if="register">
- <router-link class="link-type" :to="'/register'">绔嬪嵆娉ㄥ唽</router-link>
- </div>
- </el-form-item>
- <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">璁颁綇瀵嗙爜</el-checkbox>
- </el-form>
- <!-- 搴曢儴 -->
-<!-- <div class="el-login-footer">-->
-<!-- <span>Copyright 漏 2018-2025 ruoyi.vip All Rights Reserved.</span>-->
-<!-- </div>-->
- </div>
-</template>
-
-<script setup>
-import {getCodeImg} from "@/api/login"
-import Cookies from "js-cookie"
-import { encrypt, decrypt } from "@/utils/jsencrypt"
-import useUserStore from '@/store/modules/user'
-
-const title = import.meta.env.VITE_APP_TITLE
-const userStore = useUserStore()
-const route = useRoute()
-const router = useRouter()
-const { proxy } = getCurrentInstance()
-
-const loginForm = ref({
- username: "",
- password: "",
- rememberMe: false,
-})
-
-const loginRules = {
- username: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勮处鍙�" }],
- password: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勫瘑鐮�" }],
- // code: [{ required: true, trigger: "change", message: "璇疯緭鍏ラ獙璇佺爜" }]
-}
-
-const codeUrl = ref("")
-const loading = ref(false)
-// 楠岃瘉鐮佸紑鍏�
-const captchaEnabled = ref(true)
-// 娉ㄥ唽寮�鍏�
-const register = ref(false)
-const redirect = ref(undefined)
-
-watch(route, (newRoute) => {
- redirect.value = newRoute.query && newRoute.query.redirect
-}, { immediate: true })
-
-function handleLogin() {
- proxy.$refs.loginRef.validate(valid => {
- if (valid) {
- loading.value = true
- // 鍕鹃�変簡闇�瑕佽浣忓瘑鐮佽缃湪 cookie 涓缃浣忕敤鎴峰悕鍜屽瘑鐮�
- Cookies.set("username", loginForm.value.username, { expires: 30 })
- Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
- Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
- userStore.loginCheckFactory(loginForm.value).then(res => {
- const query = route.query
- const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
- if (cur !== "redirect") {
- acc[cur] = query[cur]
- }
- return acc
- }, {})
- router.push({ path: redirect.value || "/", query: otherQueryParams })
- }).catch(() => {
- loading.value = false
- // 閲嶆柊鑾峰彇楠岃瘉鐮�
- if (captchaEnabled.value) {
- getCode()
- }
- })
- }
- })
-}
-
-function getCode() {
- getCodeImg().then(res => {
- captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
- if (captchaEnabled.value) {
- codeUrl.value = "data:image/gif;base64," + res.img
- loginForm.value.uuid = res.uuid
- }
- })
-}
-
-function getCookie() {
- const username = Cookies.get("username")
- const password = Cookies.get("password")
- const rememberMe = Cookies.get("rememberMe")
- loginForm.value = {
- username: username === undefined ? loginForm.value.username : username,
- password: password === undefined ? loginForm.value.password : decrypt(password),
- rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
- }
-}
-
-getCode()
-getCookie()
-</script>
-
-<style lang='scss' scoped>
-.login {
- height: 100%;
- background-image: url("../assets/images/login-background.png");
- background-size: cover;
- position: relative;
-}
-.title {
- margin: 20px auto 14px auto;
- text-align: center;
- color: #2C51D9;
- font-size: 28px;
- font-weight: 700;
-}
-
-.login-form {
- position: absolute;
- top: 50%;
- right: 15%;
- transform: translate(0, -50%);
- border-radius: 6px;
- background: #ffffff;
- width: 420px;
- height: 500px;
- padding: 40px;
- z-index: 1;
- box-shadow: 0 0 5px 1px #ccc;
- .el-input {
- height: 40px;
- input {
- height: 40px;
- }
- }
- .input-icon {
- height: 39px;
- width: 14px;
- margin-left: 0px;
- }
-}
-.login-tip {
- font-size: 13px;
- text-align: center;
- color: #bfbfbf;
-}
-.login-code {
- width: 33%;
- height: 40px;
- float: right;
- img {
- cursor: pointer;
- vertical-align: middle;
- }
-}
-.el-login-footer {
- height: 40px;
- line-height: 40px;
- position: fixed;
- bottom: 0;
- width: 100%;
- text-align: center;
- color: #fff;
- font-family: Arial;
- font-size: 12px;
- letter-spacing: 1px;
-}
-.login-code-img {
- height: 40px;
- padding-left: 12px;
-}
-:deep() {
- .el-form-item--default {
- margin-bottom: 36px;
- }
-}
-</style>
+<template>
+ <div class="login-page">
+ <div class="login-shell">
+ <section class="login-brand">
+ <div class="brand-badge">PRODUCT INVENTORY</div>
+ <img :src="brandLogo" alt="brand logo" class="brand-logo" />
+ <h1 class="brand-title">{{ title }}</h1>
+ <p class="brand-copy">
+ 缁熶竴绠$悊搴撳瓨銆佹祦绋嬩笌涓氬姟鏁版嵁锛岃绯荤粺鍏ュ彛鍜屽悗鍙颁富鐣岄潰淇濇寔鍚屼竴濂楃畝绾﹁瑙夎瑷�銆�
+ </p>
+ <div class="brand-points">
+ <div class="brand-point">
+ <span class="point-dot"></span>
+ <span>娓呮櫚鐨勬暟鎹叆鍙�</span>
+ </div>
+ <div class="brand-point">
+ <span class="point-dot"></span>
+ <span>鏇磋交鐨勭晫闈㈠眰娆�</span>
+ </div>
+ <div class="brand-point">
+ <span class="point-dot"></span>
+ <span>绋冲畾鐨勪笟鍔″崗鍚屼綋楠�</span>
+ </div>
+ </div>
+ </section>
+
+ <section class="login-panel">
+ <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
+ <div class="panel-head">
+ <p class="panel-kicker">WELCOME BACK</p>
+ <h2 class="panel-title">鐧诲綍绯荤粺</h2>
+ <p class="panel-subtitle">杈撳叆璐﹀彿鍜屽瘑鐮佽繘鍏ュ伐浣滃彴銆�</p>
+ </div>
+
+ <el-form-item prop="username">
+ <el-input
+ v-model="loginForm.username"
+ type="text"
+ size="large"
+ auto-complete="off"
+ placeholder="璐﹀彿"
+ >
+ <template #prefix><el-icon><User /></el-icon></template>
+ </el-input>
+ </el-form-item>
+
+ <el-form-item prop="password">
+ <el-input
+ v-model="loginForm.password"
+ type="password"
+ size="large"
+ auto-complete="off"
+ placeholder="瀵嗙爜"
+ show-password
+ @keyup.enter="handleLogin"
+ >
+ <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
+ </el-input>
+ </el-form-item>
+
+ <div class="login-options">
+ <el-checkbox v-model="loginForm.rememberMe">璁颁綇瀵嗙爜</el-checkbox>
+ <router-link v-if="register" class="register-link" :to="'/register'">绔嬪嵆娉ㄥ唽</router-link>
+ </div>
+
+ <el-button
+ :loading="loading"
+ size="large"
+ type="primary"
+ class="login-submit"
+ @click.prevent="handleLogin"
+ >
+ <span v-if="!loading">鐧诲綍</span>
+ <span v-else>鐧诲綍涓�...</span>
+ </el-button>
+ </el-form>
+ </section>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { getCodeImg } from "@/api/login"
+import Cookies from "js-cookie"
+import { encrypt, decrypt } from "@/utils/jsencrypt"
+import useUserStore from "@/store/modules/user"
+import brandLogo from "@/assets/logo/logo.png"
+
+const title = import.meta.env.VITE_APP_TITLE
+const userStore = useUserStore()
+const route = useRoute()
+const router = useRouter()
+const { proxy } = getCurrentInstance()
+
+const loginForm = ref({
+ username: "",
+ password: "",
+ rememberMe: false,
+})
+
+const loginRules = {
+ username: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勮处鍙�" }],
+ password: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勫瘑鐮�" }],
+}
+
+const codeUrl = ref("")
+const loading = ref(false)
+const captchaEnabled = ref(true)
+const register = ref(false)
+const redirect = ref(undefined)
+
+watch(
+ route,
+ (newRoute) => {
+ redirect.value = newRoute.query && newRoute.query.redirect
+ },
+ { immediate: true }
+)
+
+function handleLogin() {
+ proxy.$refs.loginRef.validate((valid) => {
+ if (valid) {
+ loading.value = true
+ Cookies.set("username", loginForm.value.username, { expires: 30 })
+ Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
+ Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
+ userStore
+ .loginCheckFactory(loginForm.value)
+ .then(() => {
+ const query = route.query
+ const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
+ if (cur !== "redirect") {
+ acc[cur] = query[cur]
+ }
+ return acc
+ }, {})
+ router.push({ path: redirect.value || "/", query: otherQueryParams })
+ })
+ .catch(() => {
+ loading.value = false
+ if (captchaEnabled.value) {
+ getCode()
+ }
+ })
+ }
+ })
+}
+
+function getCode() {
+ getCodeImg().then((res) => {
+ captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
+ if (captchaEnabled.value) {
+ codeUrl.value = "data:image/gif;base64," + res.img
+ loginForm.value.uuid = res.uuid
+ }
+ })
+}
+
+function getCookie() {
+ const username = Cookies.get("username")
+ const password = Cookies.get("password")
+ const rememberMe = Cookies.get("rememberMe")
+ loginForm.value = {
+ username: username === undefined ? loginForm.value.username : username,
+ password: password === undefined ? loginForm.value.password : decrypt(password),
+ rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
+ }
+}
+
+getCode()
+getCookie()
+</script>
+
+<style lang="scss" scoped>
+.login-page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 32px;
+ background:
+ radial-gradient(circle at top left, rgba(207, 223, 214, 0.95), transparent 30%),
+ radial-gradient(circle at bottom right, rgba(222, 232, 227, 0.9), transparent 28%),
+ linear-gradient(180deg, #f7faf8 0%, #eef2ee 100%);
+}
+
+.login-shell {
+ width: min(1120px, 100%);
+ min-height: 680px;
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr;
+ border: 1px solid rgba(216, 225, 219, 0.9);
+ border-radius: 32px;
+ overflow: hidden;
+ background: rgba(255, 255, 255, 0.76);
+ box-shadow: 0 26px 80px rgba(31, 49, 38, 0.12);
+ backdrop-filter: blur(24px);
+}
+
+.login-brand {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 56px 64px;
+ background:
+ linear-gradient(180deg, rgba(244, 248, 245, 0.9), rgba(233, 240, 236, 0.9)),
+ linear-gradient(135deg, rgba(31, 122, 114, 0.05), rgba(255, 255, 255, 0));
+
+ &::after {
+ content: "";
+ position: absolute;
+ inset: 28px;
+ border: 1px solid rgba(31, 122, 114, 0.08);
+ border-radius: 28px;
+ pointer-events: none;
+ }
+}
+
+.brand-badge {
+ width: fit-content;
+ padding: 8px 14px;
+ border-radius: 999px;
+ background: rgba(31, 122, 114, 0.1);
+ color: #1f7a72;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.14em;
+}
+
+.brand-logo {
+ width: 160px;
+ height: auto;
+ margin: 30px 0 24px;
+ object-fit: contain;
+}
+
+.brand-title {
+ margin: 0;
+ font-size: 42px;
+ line-height: 1.12;
+ color: #21313f;
+ letter-spacing: -0.03em;
+}
+
+.brand-copy {
+ max-width: 460px;
+ margin: 18px 0 0;
+ font-size: 16px;
+ line-height: 1.75;
+ color: #5f6d7e;
+}
+
+.brand-points {
+ margin-top: 34px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.brand-point {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ color: #3d4b59;
+ font-size: 15px;
+ font-weight: 500;
+}
+
+.point-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #1f7a72, #5ca39c);
+ box-shadow: 0 0 0 6px rgba(31, 122, 114, 0.08);
+}
+
+.login-panel {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40px;
+ background: rgba(255, 255, 255, 0.7);
+}
+
+.login-form {
+ width: min(420px, 100%);
+ padding: 38px 34px 34px;
+ border: 1px solid rgba(216, 225, 219, 0.92);
+ border-radius: 28px;
+ background: rgba(255, 255, 255, 0.88);
+ box-shadow: 0 18px 52px rgba(31, 49, 38, 0.1);
+}
+
+.panel-head {
+ margin-bottom: 28px;
+}
+
+.panel-kicker {
+ margin: 0 0 10px;
+ color: #8a98a8;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.16em;
+}
+
+.panel-title {
+ margin: 0;
+ color: #21313f;
+ font-size: 30px;
+ font-weight: 700;
+}
+
+.panel-subtitle {
+ margin: 10px 0 0;
+ color: #6b7888;
+ font-size: 14px;
+}
+
+.login-options {
+ margin: -4px 0 22px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ color: #5f6d7e;
+}
+
+.register-link {
+ color: var(--el-color-primary);
+ font-weight: 600;
+}
+
+.login-submit {
+ width: 100%;
+ height: 48px;
+}
+
+.input-icon {
+ width: 14px;
+}
+
+:deep(.el-form-item) {
+ margin-bottom: 22px;
+}
+
+:deep(.el-input__wrapper) {
+ min-height: 42px;
+ height: 42px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+:deep(.el-input__inner) {
+ height: 42px;
+ line-height: 42px;
+}
+
+:deep(.el-checkbox) {
+ color: #5f6d7e;
+}
+
+@media (max-width: 960px) {
+ .login-page {
+ padding: 18px;
+ }
+
+ .login-shell {
+ min-height: auto;
+ grid-template-columns: 1fr;
+ }
+
+ .login-brand {
+ padding: 40px 28px 22px;
+ }
+
+ .login-brand::after {
+ inset: 16px;
+ }
+
+ .brand-title {
+ font-size: 32px;
+ }
+
+ .brand-copy {
+ font-size: 14px;
+ }
+
+ .brand-points {
+ margin-top: 24px;
+ }
+
+ .login-panel {
+ padding: 12px 18px 24px;
+ }
+
+ .login-form {
+ width: 100%;
+ padding: 28px 22px 24px;
+ }
+}
+</style>
diff --git a/src/views/oaSystem/projectManagement/components/milestoneList.vue b/src/views/oaSystem/projectManagement/components/milestoneList.vue
index 47b0027..ed31772 100644
--- a/src/views/oaSystem/projectManagement/components/milestoneList.vue
+++ b/src/views/oaSystem/projectManagement/components/milestoneList.vue
@@ -84,8 +84,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
<el-button type="primary" @click="submitEditForm">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
diff --git a/src/views/oaSystem/projectManagement/components/taskTree.vue b/src/views/oaSystem/projectManagement/components/taskTree.vue
index 11e3ae8..03a1a15 100644
--- a/src/views/oaSystem/projectManagement/components/taskTree.vue
+++ b/src/views/oaSystem/projectManagement/components/taskTree.vue
@@ -164,8 +164,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button @click="dialogOpen = false">鍙栨秷</el-button>
<el-button type="primary" @click="submitTaskForm">纭畾</el-button>
+ <el-button @click="dialogOpen = false">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
diff --git a/src/views/oaSystem/projectManagement/index.vue b/src/views/oaSystem/projectManagement/index.vue
index 2a0ec3a..79fbe7f 100644
--- a/src/views/oaSystem/projectManagement/index.vue
+++ b/src/views/oaSystem/projectManagement/index.vue
@@ -188,8 +188,8 @@
/>
<template #footer>
<div class="dialog-footer">
- <el-button @click="cancel">鍙栨秷</el-button>
<el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="cancel">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
diff --git a/src/views/oaSystem/projectManagement/projectDetail.vue b/src/views/oaSystem/projectManagement/projectDetail.vue
index c3b0779..578f76e 100644
--- a/src/views/oaSystem/projectManagement/projectDetail.vue
+++ b/src/views/oaSystem/projectManagement/projectDetail.vue
@@ -137,8 +137,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button @click="cancel">鍙栨秷</el-button>
<el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button @click="cancel">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
@@ -239,8 +239,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
- <el-button @click="cancelGoal">鍙栨秷</el-button>
<el-button type="primary" @click="submitGoalForm">纭畾</el-button>
+ <el-button @click="cancelGoal">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
diff --git a/src/views/personnelManagement/analytics/index.vue b/src/views/personnelManagement/analytics/index.vue
index 9e6e449..6c5a1d6 100644
--- a/src/views/personnelManagement/analytics/index.vue
+++ b/src/views/personnelManagement/analytics/index.vue
@@ -503,7 +503,6 @@
<style scoped>
.analytics-container {
padding: 20px;
- background-color: #f5f7fa;
min-height: 100vh;
}
diff --git a/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue b/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
index a410be0..b17b234 100644
--- a/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
+++ b/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
@@ -110,12 +110,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
+ <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">纭畾</el-button>
<el-button @click="dialogVisible = false">鍙栨秷</el-button>
- <el-button type="primary"
- @click="submitForm"
- v-if="operationType !== 'view'">
- 纭畾
- </el-button>
</span>
</template>
</el-dialog>
diff --git a/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue b/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
index 58d4b4f..c47dd2e 100644
--- a/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
+++ b/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
@@ -99,11 +99,9 @@
align="center">
<template #default="scope">
<el-button type="primary"
- size="small"
link
@click="openForm('edit', scope.row)">缂栬緫</el-button>
<el-button type="danger"
- size="small"
link
@click="handleDelete(scope.row.id)">鍒犻櫎</el-button>
</template>
diff --git a/src/views/personnelManagement/attendanceCheckin/index.vue b/src/views/personnelManagement/attendanceCheckin/index.vue
index b7b0f92..168a71d 100644
--- a/src/views/personnelManagement/attendanceCheckin/index.vue
+++ b/src/views/personnelManagement/attendanceCheckin/index.vue
@@ -455,6 +455,9 @@
</script>
<style scoped lang="scss">
+.table_list {
+ margin-top: unset;
+}
.mb16 {
margin-bottom: 16px;
}
@@ -497,7 +500,7 @@
color: #333;
}
- ::v-deep(.row-abnormal) {
+ :deep(.row-abnormal) {
background-color: #fff5f5;
}
diff --git a/src/views/personnelManagement/classsSheduling/index.vue b/src/views/personnelManagement/classsSheduling/index.vue
index 6891a57..140ffba 100644
--- a/src/views/personnelManagement/classsSheduling/index.vue
+++ b/src/views/personnelManagement/classsSheduling/index.vue
@@ -49,13 +49,11 @@
<div class="search-actions">
<el-button size="small"
type="primary"
- @click="refreshTable()"
- :icon="Search">
+ @click="refreshTable()">
鏌ヨ
</el-button>
<el-button size="small"
@click="refresh()"
- :icon="Refresh"
style="margin-left: 8px">
閲嶇疆
</el-button>
@@ -63,22 +61,19 @@
<div class="search-buttons">
<el-button size="small"
type="primary"
- @click="configTime"
- :icon="Setting">
+ @click="configTime">
鐝閰嶇疆
</el-button>
<el-button size="small"
type="success"
@click="handleDown"
:loading="downLoading"
- :icon="Download"
style="margin-left: 8px">
瀵煎嚭
</el-button>
<el-button size="small"
type="warning"
@click="schedulingVisible = true"
- :icon="Calendar"
style="margin-left: 8px">
鎺掔彮
</el-button>
@@ -674,10 +669,12 @@
})
.then(res => {
proxy.$modal.msgSuccess("涓嬭浇鎴愬姛");
- downLoading.value = false;
- const blob = new Blob([res], {
- type: "application/force-download",
- });
+ const blob =
+ res instanceof Blob
+ ? res
+ : new Blob([res], {
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ });
let fileName = "";
if (query.month) {
fileName = year + "-" + query.month + " 鐝淇℃伅";
@@ -687,6 +684,8 @@
proxy.$download.saveAs(blob, fileName + ".xlsx");
})
.catch(err => {
+ })
+ .finally(() => {
downLoading.value = false;
});
};
diff --git a/src/views/personnelManagement/contractManagement/index.vue b/src/views/personnelManagement/contractManagement/index.vue
index 1d2aab7..a55a502 100644
--- a/src/views/personnelManagement/contractManagement/index.vue
+++ b/src/views/personnelManagement/contractManagement/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">濮撳悕锛�</span>
<el-input v-model="searchForm.staffName" style="width: 240px" placeholder="璇疯緭鍏ュ鍚嶆悳绱�" @change="handleQuery"
diff --git a/src/views/personnelManagement/dimission/components/formDia.vue b/src/views/personnelManagement/dimission/components/formDia.vue
index 6d3cb46..84922ec 100644
--- a/src/views/personnelManagement/dimission/components/formDia.vue
+++ b/src/views/personnelManagement/dimission/components/formDia.vue
@@ -101,6 +101,7 @@
<el-date-picker
v-model="form.leaveDate"
type="date"
+ :disabled="operationType === 'edit'"
placeholder="璇烽�夋嫨绂昏亴鏃ユ湡"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
diff --git a/src/views/personnelManagement/dimission/index.vue b/src/views/personnelManagement/dimission/index.vue
index 5993b3f..c2b8c3e 100644
--- a/src/views/personnelManagement/dimission/index.vue
+++ b/src/views/personnelManagement/dimission/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">濮撳悕锛�</span>
<el-input
diff --git a/src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue b/src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue
index d3b8c4c..be33436 100644
--- a/src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue
+++ b/src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue
@@ -19,6 +19,7 @@
placeholder="璇烽�夋嫨"
style="width: 100%"
clearable
+ @change="calculateContractTerm"
/>
</el-form-item>
</el-col>
@@ -43,6 +44,7 @@
placeholder="璇烽�夋嫨"
style="width: 100%"
clearable
+ @change="calculateContractTerm"
/>
</el-form-item>
</el-col>
@@ -131,6 +133,34 @@
});
const { form, postOptions, deptOptions } = toRefs(props);
+
+// 璁$畻鍚堝悓骞撮檺
+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;
+ }
+};
</script>
<style scoped>
diff --git a/src/views/personnelManagement/employeeRecord/index.vue b/src/views/personnelManagement/employeeRecord/index.vue
index d0c97b4..cd4ecf5 100644
--- a/src/views/personnelManagement/employeeRecord/index.vue
+++ b/src/views/personnelManagement/employeeRecord/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">濮撳悕锛�</span>
<el-input
@@ -11,15 +11,32 @@
clearable
:prefix-icon="Search"
/>
- <span style="margin-left: 10px" class="search_title">鍚堝悓缁撴潫鏃ユ湡锛�</span>
- <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ <span class="search_title search_title2">閮ㄩ棬锛�</span>
+ <el-tree-select
+ v-model="searchForm.sysDeptId"
+ :data="deptOptions"
+ check-strictly
+ :render-after-expand="false"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨"
+ />
+ <span class="search_title search_title2">鍏ヨ亴鏃ユ湡锛�</span>
+ <el-date-picker
+ v-model="searchForm.contractStartTime"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ placeholder="璇烽�夋嫨"
+ />
+ <!-- <span style="margin-left: 10px" class="search_title">鍚堝悓缁撴潫鏃ユ湡锛�</span> -->
+ <!-- <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" /> -->
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
</div>
<div>
<el-button type="primary" @click="openFormNewOrEditFormDia('add')">鏂板鍏ヨ亴</el-button>
+ <el-button type="info" @click="handleImport">瀵煎叆</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
<!-- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button> -->
</div>
@@ -45,15 +62,49 @@
:id="id"
@completed="handleQuery"
/>
+
+ <!-- 瀵煎叆瀵硅瘽妗� -->
+ <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>
+ <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @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>
-import { Search } from "@element-plus/icons-vue";
+import { Search, UploadFilled } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {ElMessageBox} from "element-plus";
+import { deptTreeSelect } from "@/api/system/user.js";
import {batchDeleteStaffOnJobs, staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
+import { getToken } from "@/utils/auth";
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"));
@@ -65,8 +116,9 @@
entryDateStart: undefined,
entryDateEnd: undefined,
},
+ deptOptions: [],
});
-const { searchForm } = toRefs(data);
+const { searchForm, deptOptions } = toRefs(data);
const isShowRenewContractModal = ref(false);
const id = ref(0);
const tableColumn = ref([
@@ -117,6 +169,11 @@
{
label: "鍑虹敓鏃ユ湡",
prop: "birthDate",
+ width: 120,
+ },
+ {
+ label: "鍏ヨ亴鏃ユ湡",
+ prop: "contractStartTime",
width: 120,
},
{
@@ -182,6 +239,40 @@
const formDiaNewOrEditFormDia = ref()
const { proxy } = getCurrentInstance()
+// 瀵煎叆鐩稿叧
+const uploadRef = ref(null)
+const upload = reactive({
+ // 鏄惁鏄剧ず寮瑰嚭灞�
+ open: false,
+ // 寮瑰嚭灞傛爣棰�
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import"
+})
+
+const fetchDeptOptions = () => {
+ deptTreeSelect().then(response => {
+ console.log(response.data)
+ deptOptions.value = filterDisabledDept(
+ JSON.parse(JSON.stringify(response.data))
+ );
+ });
+ };
+const 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 changeDaterange = (value) => {
searchForm.value.entryDateStart = undefined;
searchForm.value.entryDateEnd = undefined;
@@ -203,6 +294,7 @@
getList();
};
const getList = () => {
+ fetchDeptOptions();
tableLoading.value = true;
const params = { ...searchForm.value, ...page };
params.entryDate = undefined
@@ -270,9 +362,44 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 瀵煎叆鎸夐挳鎿嶄綔
+const handleImport = () => {
+ upload.title = "鍛樺伐瀵煎叆"
+ upload.open = true
+}
+
+// 涓嬭浇妯℃澘鎿嶄綔
+const importTemplate = () => {
+ proxy.download("/staff/staffOnJob/downloadTemplate", {}, `鍛樺伐瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`)
+}
+
+// 鏂囦欢涓婁紶涓鐞�
+const handleFileUploadProgress = (event, file, fileList) => {
+ 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()
+}
+
+// 鎻愪氦涓婁紶鏂囦欢
+const submitFileForm = () => {
+ proxy.$refs["uploadRef"].submit()
+}
+
onMounted(() => {
getList();
});
</script>
-<style scoped></style>
+<style scoped>
+.search_title2 {
+ margin-left: 10px;
+}
+</style>
diff --git a/src/views/personnelManagement/monthlyStatistics/index.vue b/src/views/personnelManagement/monthlyStatistics/index.vue
index f389f57..4ac9e97 100644
--- a/src/views/personnelManagement/monthlyStatistics/index.vue
+++ b/src/views/personnelManagement/monthlyStatistics/index.vue
@@ -36,7 +36,7 @@
</div>
</div>
<div class="table_list">
- <div style="margin-bottom: 10px">
+ <div style="margin-bottom: 10px;text-align: right">
<el-button type="primary" @click="openForm('add')">鏂板缓宸ヨ祫琛�</el-button>
<el-button @click="handleDelete">鍒犻櫎</el-button>
<el-button @click="openBankSetting">璁剧疆閾惰</el-button>
diff --git a/src/views/personnelManagement/socialSecuritySet/index.vue b/src/views/personnelManagement/socialSecuritySet/index.vue
index 2a1ff65..1f3f104 100644
--- a/src/views/personnelManagement/socialSecuritySet/index.vue
+++ b/src/views/personnelManagement/socialSecuritySet/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">涓婚锛�</span>
<el-input
diff --git a/src/views/procurementManagement/paymentEntry/index.vue b/src/views/procurementManagement/paymentEntry/index.vue
index 3f4512b..9ce59ad 100644
--- a/src/views/procurementManagement/paymentEntry/index.vue
+++ b/src/views/procurementManagement/paymentEntry/index.vue
@@ -89,8 +89,12 @@
placeholder="璇烽�夋嫨"
clearable
>
- <el-option label="鐢垫眹" value="鐢垫眹" />
- <el-option label="鎵垮厬" value="鎵垮厬" />
+ <el-option
+ v-for="item in checkout_payment"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
</el-select>
</template>
</el-table-column>
@@ -101,7 +105,6 @@
<el-button
link
type="primary"
- size="small"
@click="changeEditType(scope.row)"
v-if="!scope.row.editType"
>缂栬緫</el-button
@@ -109,7 +112,6 @@
<el-button
link
type="primary"
- size="small"
@click="saveReceiptPayment(scope.row)"
v-if="scope.row.editType"
>淇濆瓨</el-button
@@ -117,7 +119,6 @@
<el-button
link
type="primary"
- size="small"
@click="handleDelete(scope.row)"
>鍒犻櫎</el-button
>
@@ -186,8 +187,12 @@
<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-option
+ v-for="item in checkout_payment"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
</el-select>
</template>
</el-table-column>
@@ -242,6 +247,7 @@
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance();
+const { checkout_payment } = proxy.useDict("checkout_payment");
const tableColumn = ref([
{
type: "expand",
@@ -515,7 +521,15 @@
return;
}
}
- paymentRegistrationAdd(forms.value).then(() => {
+ const normalizePaymentMethodValue = (method) => {
+ const hit = checkout_payment.value.find((item) => item.value == method || item.label == method);
+ return hit ? hit.value : method;
+ };
+ const submitRows = forms.value.map((item) => ({
+ ...item,
+ paymentMethod: normalizePaymentMethodValue(item.paymentMethod),
+ }));
+ paymentRegistrationAdd(submitRows).then(() => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
getList();
@@ -572,7 +586,7 @@
.table_list {
margin-top: unset;
}
-::v-deep(.el-checkbox__label) {
+:deep(.el-checkbox__label) {
font-weight: bold;
}
.empty-tip {
diff --git a/src/views/procurementManagement/paymentHistory/index.vue b/src/views/procurementManagement/paymentHistory/index.vue
index 179373b..c9e9836 100644
--- a/src/views/procurementManagement/paymentHistory/index.vue
+++ b/src/views/procurementManagement/paymentHistory/index.vue
@@ -70,7 +70,6 @@
<el-button
type="primary"
link
- size="small"
@click="handleDelete(row)"
>
鍒犻櫎
diff --git a/src/views/procurementManagement/paymentLedger/index.vue b/src/views/procurementManagement/paymentLedger/index.vue
index 294561d..23a62aa 100644
--- a/src/views/procurementManagement/paymentLedger/index.vue
+++ b/src/views/procurementManagement/paymentLedger/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form" style="margin-bottom: 20px;">
<div>
<span class="search_title">渚涘簲鍟嗗悕绉�:</span>
<el-input
diff --git a/src/views/procurementManagement/procurementInvoiceLedger/index.vue b/src/views/procurementManagement/procurementInvoiceLedger/index.vue
index d82e3e7..c94d8c2 100644
--- a/src/views/procurementManagement/procurementInvoiceLedger/index.vue
+++ b/src/views/procurementManagement/procurementInvoiceLedger/index.vue
@@ -69,7 +69,7 @@
<el-button
type="primary"
link
- @click="downLoadFile(row)"
+ @click="openFileDialog(row)"
>
闄勪欢
</el-button>
@@ -83,16 +83,7 @@
</template>
</PIMTable>
</div>
- <FileListDialog
- ref="fileListRef"
- v-model="fileListDialogVisible"
- title="闄勪欢鍒楄〃"
- :showUploadButton="true"
- :showDeleteButton="true"
- :deleteMethod="handleDeleteFile"
- :uploadMethod="handleFileUpload"
- :rulesRegulationsManagementId="currentRowId"
- />
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="ticket_registration" :record-id="recordId" />
<EditModal ref="editmodalRef" @success="getTableData"></EditModal>
</div>
</template>
@@ -113,9 +104,9 @@
import { onMounted } from "vue";
import { ElMessageBox } from "element-plus";
import EditModal from "./Modal/EditModal.vue";
-import FileListDialog from '@/components/Dialog/FileListDialog.vue';
import useUserStore from "@/store/modules/user.js";
const userStore = useUserStore();
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
defineOptions({
name: "鏉ョエ鍙拌处",
@@ -290,143 +281,15 @@
onCurrentChange(page);
};
-const downLoadFile = row => {
- currentRowId.value = row.id;
- if (fileListRef.value) {
- fileListRef.value.open(row.commonFiles || []);
- }
-};
+// 鎵撳紑闄勪欢寮圭獥
+const recordId =ref(0)
+const fileDialogVisible = ref(false)
-// 涓婁紶闄勪欢锛堣嚜瀹氫箟涓婁紶鏂规硶锛�
-const handleFileUpload = async () => {
- if (!currentRowId.value) {
- proxy.$modal.msgWarning("缂哄皯鐧昏ID锛屾棤娉曚繚瀛橀檮浠�");
- return;
- }
-
- 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);
- formData.append('type', '4'); // type 鍙傛暟锛岀敤鎴锋湭鎸囧畾鍏蜂綋鍊硷紝鍏堜紶绌哄瓧绗︿覆
- formData.append('id', currentRowId.value); // 褰撳墠琛岀殑 id
-
- const uploadRes = await request({
- url: '/file/uploadByCommon',
- method: 'post',
- data: formData,
- headers: {
- 'Content-Type': 'multipart/form-data',
- Authorization: `Bearer ${getToken()}`
- }
- });
-
- if (uploadRes.code === 200) {
- proxy.$modal.msgSuccess("闄勪欢涓婁紶鎴愬姛");
-
- // 鍒锋柊鍒楄〃鑾峰彇鏈�鏂版暟鎹�
- await new Promise((resolveRefresh) => {
- // 璋冪敤 API 鑾峰彇鏈�鏂板垪琛ㄦ暟鎹�
- productRecordPage({
- ...filters,
- current: pagination.currentPage,
- size: pagination.pageSize
- }).then(({ code, data }) => {
- if (code === 200) {
- // 鏇存柊鏁版嵁鍒楄〃
- dataList.value = data.records;
- pagination.total = data.total;
-
- // 浠庡閮ㄦ暟鎹幏鍙� commonFiles
- const currentRow = dataList.value.find(row => row.id === currentRowId.value);
- if (currentRow && fileListRef.value) {
- // 鍒锋柊闄勪欢鍒楄〃锛屼娇鐢ㄤ粠澶栭儴鑾峰彇鐨勬渶鏂� commonFiles
- fileListRef.value.open(currentRow.commonFiles || []);
- }
- resolveRefresh();
- } else {
- resolveRefresh();
- }
- }).catch(() => {
- resolveRefresh();
- });
- });
-
- resolve({
- name: uploadRes.data?.originalName || file.name,
- url: uploadRes.data?.tempPath || uploadRes.data?.url,
- id: uploadRes.data?.id
- });
- } else {
- proxy.$modal.msgError(uploadRes.msg || "鏂囦欢涓婁紶澶辫触");
- resolve(null);
- }
- } catch (error) {
- console.error("闄勪欢涓婁紶澶辫触:", error);
- proxy.$modal.msgError("闄勪欢涓婁紶澶辫触");
- resolve(null);
- } finally {
- document.body.removeChild(input);
- }
- };
-
- document.body.appendChild(input);
- input.click();
- });
-};
-
-// 鍒犻櫎闄勪欢
-const handleDeleteFile = async (file) => {
- try {
- await delCommonFile([file.id]);
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
-
- // 鍒锋柊鍒楄〃鑾峰彇鏈�鏂版暟鎹�
- await new Promise((resolveRefresh) => {
- // 璋冪敤 API 鑾峰彇鏈�鏂板垪琛ㄦ暟鎹�
- productRecordPage({
- ...filters,
- current: pagination.currentPage,
- size: pagination.pageSize
- }).then(({ code, data }) => {
- if (code === 200) {
- // 鏇存柊鏁版嵁鍒楄〃
- dataList.value = data.records;
- pagination.total = data.total;
-
- // 浠庡閮ㄦ暟鎹幏鍙� commonFiles
- const currentRow = dataList.value.find(row => row.id === currentRowId.value);
- if (currentRow && fileListRef.value) {
- // 鍒锋柊闄勪欢鍒楄〃锛屼娇鐢ㄤ粠澶栭儴鑾峰彇鐨勬渶鏂� commonFiles
- fileListRef.value.open(currentRow.commonFiles || []);
- }
- resolveRefresh();
- } else {
- resolveRefresh();
- }
- }).catch(() => {
- resolveRefresh();
- });
- });
-
- return true;
- } catch (error) {
- proxy.$modal.msgError("鍒犻櫎澶辫触");
- return false;
- }
-};
+// 鎵撳紑闄勪欢寮规
+const openFileDialog = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
+}
const openEdit = (row) => {
editmodalRef.value.open(row);
diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
index 20c6b1c..cffdcc6 100644
--- a/src/views/procurementManagement/procurementLedger/index.vue
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -44,7 +44,8 @@
</el-form-item>
<el-form-item>
<el-button type="primary"
- @click="handleQuery"> 鎼滅储 </el-button>
+ @click="handleQuery"> 鎼滅储
+ </el-button>
</el-form-item>
</el-form>
</div>
@@ -53,11 +54,14 @@
<div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
<el-button type="primary"
@click="openForm('add')">鏂板鍙拌处</el-button>
- <el-button type="primary" plain @click="handleImport">瀵煎叆</el-button>
+ <el-button type="primary"
+ plain
+ @click="handleImport">瀵煎叆</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="danger"
plain
- @click="handleDelete">鍒犻櫎</el-button>
+ @click="handleDelete">鍒犻櫎
+ </el-button>
</div>
<el-table :data="tableData"
border
@@ -90,6 +94,10 @@
prop="unit" />
<el-table-column label="鏁伴噺"
prop="quantity" />
+ <el-table-column label="鍙敤鏁伴噺"
+ prop="availableQuality" />
+ <el-table-column label="閫�璐ф暟閲�"
+ prop="returnQuality" />
<el-table-column label="绋庣巼(%)"
prop="taxRate" />
<el-table-column label="鍚◣鍗曚环(鍏�)"
@@ -114,11 +122,11 @@
show-overflow-tooltip />
<el-table-column label="閿�鍞悎鍚屽彿"
prop="salesContractNo"
- width="160"
+ width="160"
show-overflow-tooltip />
<el-table-column label="渚涘簲鍟嗗悕绉�"
prop="supplierName"
- width="160"
+ width="160"
show-overflow-tooltip />
<el-table-column label="椤圭洰鍚嶇О"
prop="projectName"
@@ -129,9 +137,8 @@
width="100"
show-overflow-tooltip>
<template #default="scope">
- <el-tag
- :type="getApprovalStatusType(scope.row.approvalStatus)"
- size="small">
+ <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)"
+ size="small">
{{ approvalStatusText[scope.row.approvalStatus] || '鏈煡鐘舵��' }}
</el-tag>
</template>
@@ -168,13 +175,12 @@
<template #default="scope">
<el-button link
type="primary"
- size="small"
@click="openForm('edit', scope.row)"
- :disabled="scope.row.approvalStatus !== 1 && scope.row.approvalStatus !== 4">缂栬緫</el-button>
+ :disabled="scope.row.approvalStatus !== 1 && scope.row.approvalStatus !== 4">缂栬緫
+ </el-button>
<el-button link
type="primary"
- size="small"
- @click="downLoadFile(scope.row)">闄勪欢</el-button>
+ @click="openFileDialog(scope.row)">闄勪欢</el-button>
</template>
</el-table-column>
</el-table>
@@ -186,12 +192,12 @@
@pagination="paginationChange" />
</div>
<FormDialog v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板閲囪喘鍙拌处椤甸潰' : '缂栬緫閲囪喘鍙拌处椤甸潰'"
- :width="'70%'"
- :operation-type="operationType"
- @close="closeDia"
- @confirm="submitForm"
- @cancel="closeDia">
+ :title="operationType === 'add' ? '鏂板閲囪喘鍙拌处椤甸潰' : '缂栬緫閲囪喘鍙拌处椤甸潰'"
+ :width="'70%'"
+ :operation-type="operationType"
+ @close="closeDia"
+ @confirm="submitForm"
+ @cancel="closeDia">
<el-form :model="form"
label-width="140px"
label-position="top"
@@ -233,7 +239,8 @@
<el-option v-for="item in supplierList"
:key="item.id"
:label="item.supplierName"
- :value="item.id" >{{item.supplierName + '---' + item.supplierType}}</el-option>
+ :value="item.id">{{ item.supplierName + '---' + item.supplierType }}
+ </el-option>
</el-select>
</el-form-item>
</el-col>
@@ -295,58 +302,16 @@
</el-form-item>
</el-col>
</el-row>
- <el-row :gutter="30">
- <el-col :span="24">
- <el-form-item>
- <template #label>
- <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
- <span>瀹℃壒浜洪�夋嫨锛�</span>
- <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">鏂板鑺傜偣</el-button>
- </div>
- </template>
- <div class="approver-nodes-container">
- <div
- v-for="(node, index) in approverNodes"
- :key="node.id"
- class="approver-node-item"
- >
- <div class="approver-node-header">
- <span class="approver-node-label">瀹℃壒鑺傜偣 {{ index + 1 }}</span>
- <el-button
- v-if="approverNodes.length > 1"
- type="danger"
- size="small"
- text
- @click="removeApproverNode(index)"
- icon="Delete"
- >鍒犻櫎</el-button>
- </div>
- <el-select
- v-model="node.userId"
- placeholder="璇烽�夋嫨瀹℃壒浜�"
- filterable
- style="width: 100%;"
- >
- <el-option
- v-for="user in userList"
- :key="user.userId"
- :label="user.nickName"
- :value="user.userId"
- />
- </el-select>
- </div>
- </div>
- </el-form-item>
- </el-col>
- </el-row>
<el-row>
<el-form-item label="浜у搧淇℃伅锛�"
prop="entryDate">
<el-button type="primary"
- @click="openProductForm('add')">娣诲姞</el-button>
+ @click="openProductForm('add')">娣诲姞
+ </el-button>
<el-button plain
type="danger"
- @click="deleteProduct">鍒犻櫎</el-button>
+ @click="deleteProduct">鍒犻櫎
+ </el-button>
</el-form-item>
<div class="select-button-group"
style="width: 500px; margin: 20px 0;"
@@ -370,11 +335,10 @@
:value="item.templateName">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>{{ item.templateName }}</span>
- <el-icon
- v-if="item.id"
- class="delete-icon"
- @click.stop="handleDeleteTemplate(item)"
- style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;">
+ <el-icon v-if="item.id"
+ class="delete-icon"
+ @click.stop="handleDeleteTemplate(item)"
+ style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;">
<Delete />
</el-icon>
</div>
@@ -446,8 +410,8 @@
<template #default="scope">
<el-button link
type="primary"
- size="small"
- @click="openProductForm('edit', scope.row, scope.$index)">缂栬緫</el-button>
+ @click="openProductForm('edit', scope.row, scope.$index)">缂栬緫
+ </el-button>
</template>
</el-table-column>
</el-table>
@@ -467,52 +431,31 @@
<el-col :span="24">
<el-form-item label="闄勪欢鏉愭枡锛�"
prop="purchaseLedgerFiles">
- <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">
- 鏂囦欢鏍煎紡鏀寔
- doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
- </div>
- </template>
- </el-upload>
+ <FileUpload v-model:file-list="fileList" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</FormDialog>
<!-- 瀵煎叆寮圭獥 -->
- <FormDialog
- v-model="importUpload.open"
- :title="importUpload.title"
- :width="'600px'"
- @close="importUpload.open = false"
- @confirm="submitImportFile"
- @cancel="importUpload.open = false"
- >
- <el-upload
- ref="importUploadRef"
- :limit="1"
- accept=".xlsx,.xls"
- :action="importUpload.url"
- :headers="importUpload.headers"
- :before-upload="importUpload.beforeUpload"
- :on-success="importUpload.onSuccess"
- :on-error="importUpload.onError"
- :on-progress="importUpload.onProgress"
- :on-change="importUpload.onChange"
- :auto-upload="false"
- drag
- >
+ <FormDialog v-model="importUpload.open"
+ :title="importUpload.title"
+ :width="'600px'"
+ @close="importUpload.open = false"
+ @confirm="submitImportFile"
+ @cancel="importUpload.open = false">
+ <el-upload ref="importUploadRef"
+ :limit="1"
+ accept=".xlsx,.xls"
+ :action="importUpload.url"
+ :headers="importUpload.headers"
+ :before-upload="importUpload.beforeUpload"
+ :on-success="importUpload.onSuccess"
+ :on-error="importUpload.onError"
+ :on-progress="importUpload.onProgress"
+ :on-change="importUpload.onChange"
+ :auto-upload="false"
+ drag>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em>
@@ -520,18 +463,20 @@
<template #tip>
<div class="el-upload__tip">
浠呮敮鎸� xls/xlsx锛屽ぇ灏忎笉瓒呰繃 10MB銆�
- <el-button link type="primary" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
+ <el-button link
+ type="primary"
+ @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
</div>
</template>
</el-upload>
</FormDialog>
<FormDialog v-model="productFormVisible"
- :title="productOperationType === 'add' ? '鏂板浜у搧' : '缂栬緫浜у搧'"
- :width="'40%'"
- :operation-type="productOperationType"
- @close="closeProductDia"
- @confirm="submitProduct"
- @cancel="closeProductDia">
+ :title="productOperationType === 'add' ? '鏂板浜у搧' : '缂栬緫浜у搧'"
+ :width="'40%'"
+ :operation-type="productOperationType"
+ @close="closeProductDia"
+ @confirm="submitProduct"
+ @cancel="closeProductDia">
<el-form :model="productForm"
label-width="140px"
label-position="top"
@@ -544,6 +489,7 @@
<el-tree-select v-model="productForm.productId"
placeholder="璇烽�夋嫨"
clearable
+ filterable
check-strictly
@change="getModels"
:data="productOptions"
@@ -558,6 +504,7 @@
prop="productModelId">
<el-select v-model="productForm.productModelId"
placeholder="璇烽�夋嫨"
+ filterable
clearable
@change="getProductModel">
<el-option v-for="item in modelOptions"
@@ -584,12 +531,10 @@
placeholder="璇烽�夋嫨"
clearable
@change="mathNum">
- <el-option label="1"
- value="1" />
- <el-option label="6"
- value="6" />
- <el-option label="13"
- value="13" />
+ <el-option v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
@@ -688,15 +633,16 @@
</el-row>
</el-form>
</FormDialog>
- <FileListDialog
- ref="fileListRef"
- v-model="fileListDialogVisible"
- title="闄勪欢鍒楄〃"
- />
+ <FileList v-if="fileListDialogVisible"
+ v-model:visible="fileListDialogVisible"
+ record-type="purchase_ledger"
+ :record-id="recordId" />
</div>
</template>
<script setup>
+ import FormDialog from "@/components/Dialog/FormDialog.vue";
+ import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import {
@@ -710,10 +656,7 @@
import { Search, Delete } from "@element-plus/icons-vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
- import FormDialog from '@/components/Dialog/FormDialog.vue';
- import FileListDialog from '@/components/Dialog/FileListDialog.vue';
import {
- getSalesLedgerWithProducts,
addOrUpdateSalesLedgerProduct,
delProduct,
delLedgerFile,
@@ -734,8 +677,12 @@
delPurchaseTemplate,
} from "@/api/procurementManagement/procurementLedger.js";
import useFormData from "@/hooks/useFormData.js";
+ const FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+ );
const { proxy } = getCurrentInstance();
+ const { tax_rate } = proxy.useDict("tax_rate");
const tableData = ref([]);
const productData = ref([]);
const selectedRows = ref([]);
@@ -746,6 +693,7 @@
const salesContractList = ref([]);
const supplierList = ref([]);
const tableLoading = ref(false);
+ const fileListDialogVisible = ref(false);
const page = reactive({
current: 1,
size: 100,
@@ -755,18 +703,9 @@
import useUserStore from "@/store/modules/user";
import { modelList, productTreeList } from "@/api/basicData/product.js";
import dayjs from "dayjs";
+ import FileUpload from "@/components/AttachmentUpload/file/index.vue";
const userStore = useUserStore();
-
- // 瀹℃壒浜鸿妭鐐癸紙浠块攢鍞彴璐﹀彂璐у鎵逛汉锛�
- 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 approvalStatusText = {
@@ -777,12 +716,12 @@
};
// 鑾峰彇瀹℃壒鐘舵�佹爣绛剧被鍨�
- const getApprovalStatusType = (status) => {
+ const getApprovalStatusType = status => {
const typeMap = {
- 1: "info", // 寰呭鏍� - 鐏拌壊
- 2: "warning", // 瀹℃壒涓� - 姗欒壊
- 3: "success", // 瀹℃壒閫氳繃 - 缁胯壊
- 4: "danger", // 瀹℃壒澶辫触 - 绾㈣壊
+ 1: "info", // 寰呭鏍� - 鐏拌壊
+ 2: "warning", // 瀹℃壒涓� - 姗欒壊
+ 3: "success", // 瀹℃壒閫氳繃 - 缁胯壊
+ 4: "danger", // 瀹℃壒澶辫触 - 绾㈣壊
};
return typeMap[status] || "";
};
@@ -864,7 +803,8 @@
form.value.paymentMethod = matchedTemplate.paymentMethod;
}
// 妯℃澘鏁版嵁涓殑浜у搧瀛楁鏄� productList锛岄渶瑕佽浆鎹负 productData
- productData.value = matchedTemplate.productList || matchedTemplate.productData || [];
+ productData.value =
+ matchedTemplate.productList || matchedTemplate.productData || [];
} else {
// 鏈尮閰嶅埌宸叉湁妯℃澘锛岃涓烘柊妯℃澘
currentTemplateId.value = null;
@@ -912,14 +852,11 @@
supplierId: "",
paymentMethod: "",
executionDate: "",
- isChecked: true,
+ isChecked: false,
},
rules: {
purchaseContractNumber: [
{ required: true, message: "璇疯緭鍏�", trigger: "blur" },
- ],
- approverId: [
- { required: true, message: "璇烽�夋嫨瀹℃壒浜�", trigger: "change" },
],
projectName: [
{ required: true, message: "璇疯緭鍏ラ」鐩悕绉�", trigger: "blur" },
@@ -960,7 +897,7 @@
taxExclusiveTotalPrice: "",
invoiceType: "",
warnNum: "",
- isChecked: true,
+ isChecked: false,
},
productRules: {
productId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
@@ -998,7 +935,7 @@
url: import.meta.env.VITE_APP_BASE_API + "/purchase/ledger/import",
headers: { Authorization: "Bearer " + getToken() },
isUploading: false,
- beforeUpload: (file) => {
+ beforeUpload: file => {
const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isExcel) {
@@ -1047,7 +984,11 @@
// 涓嬭浇瀵煎叆妯℃澘锛堝鍚庣璺緞涓嶅悓锛屽彲鍦ㄦ澶勮皟鏁达級
const downloadTemplate = () => {
- proxy.download("/purchase/ledger/exportTemplate", {}, "閲囪喘鍙拌处瀵煎叆妯℃澘.xlsx");
+ proxy.download(
+ "/purchase/ledger/exportTemplate",
+ {},
+ "閲囪喘鍙拌处瀵煎叆妯℃澘.xlsx"
+ );
};
const submitImportFile = () => {
@@ -1112,29 +1053,27 @@
// 妫�鏌ユ槸鍚︽湁浜у搧鏁版嵁
if (!productData.value || productData.value.length === 0) {
ElMessage({
- message: '璇峰厛娣诲姞浜у搧淇℃伅',
- type: 'warning',
+ message: "璇峰厛娣诲姞浜у搧淇℃伅",
+ type: "warning",
});
return;
}
try {
- // 鑾峰彇瀹℃壒浜篒D瀛楃涓�
- const approveUserIds = approverNodes.value
- .filter(node => node.userId)
- .map(node => node.userId)
- .join(",");
-
let params = {
productData: proxy.HaveJson(productData.value),
supplierId: form.value.supplierId,
paymentMethod: form.value.paymentMethod,
recorderId: form.value.recorderId,
projectName: form.value.projectName,
- approveUserIds: approveUserIds,
templateName: templateName.value.trim(),
};
- console.log("template params ===>", params, "currentTemplateId:", currentTemplateId.value);
+ console.log(
+ "template params ===>",
+ params,
+ "currentTemplateId:",
+ currentTemplateId.value
+ );
// 濡傛灉 currentTemplateId 鏈夊�硷紝璇存槑褰撳墠鏄�滅紪杈戝凡鏈夋ā鏉库�� 鈫� 璋冪敤鏇存柊鎺ュ彛
// 鍚﹀垯涓衡�滄柊寤烘ā鏉库�� 鈫� 璋冪敤鏂板鎺ュ彛
@@ -1273,8 +1212,11 @@
return;
}
}
-
+
await getTemplateList();
+ await userListNoPage().then(res => {
+ userList.value = res.data;
+ });
operationType.value = type;
form.value = {};
productData.value = [];
@@ -1282,26 +1224,19 @@
templateName.value = "";
filterInputValue.value = "";
isTemplateNameDuplicate.value = false;
- // 閲嶇疆瀹℃壒浜鸿妭鐐癸紙榛樿涓�涓┖鑺傜偣锛�
- approverNodes.value = [{ id: 1, userId: null }];
- nextApproverId = 2;
try {
// 骞惰鍔犺浇鍩虹鏁版嵁
- const [userRes, salesRes, supplierRes] = await Promise.all([
- userListNoPage(),
+ const [salesRes, supplierRes] = await Promise.all([
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") {
@@ -1322,16 +1257,7 @@
const purchaseRes = await getPurchaseById({ id: row.id, type: 2 });
form.value = { ...purchaseRes };
productData.value = purchaseRes.productData || [];
- fileList.value = purchaseRes.salesLedgerFiles || [];
- // 濡傛灉缂栬緫鏃舵湁瀹℃壒浜猴紝瑙f瀽瀹℃壒浜篒D瀛楃涓插苟璁剧疆鍒拌妭鐐逛腑
- if (purchaseRes.approveUserIds) {
- const approverIds = purchaseRes.approveUserIds.split(",");
- approverNodes.value = approverIds.map((id, index) => ({
- id: index + 1,
- userId: Number(id)
- }));
- nextApproverId = approverIds.length + 1;
- }
+ fileList.value = purchaseRes.storageBlobVOS || [];
} catch (error) {
console.error("鍔犺浇閲囪喘鍙拌处鏁版嵁澶辫触:", error);
proxy.$modal.msgError("鍔犺浇鏁版嵁澶辫触");
@@ -1349,6 +1275,7 @@
proxy.$modal.msgError("鍔犺浇鍩虹鏁版嵁澶辫触");
}
};
+
// 涓婁紶鍓嶆牎妫�
function handleBeforeUpload(file) {
// 鏍℃鏂囦欢澶у皬
@@ -1359,11 +1286,13 @@
proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
return true;
}
+
// 涓婁紶澶辫触
function handleUploadError(err) {
proxy.$modal.msgError("涓婁紶鏂囦欢澶辫触");
proxy.$modal.closeLoading();
}
+
// 涓婁紶鎴愬姛鍥炶皟
function handleUploadSuccess(res, file, uploadFiles) {
proxy.$modal.closeLoading();
@@ -1375,6 +1304,7 @@
proxy.$refs.fileUpload.handleRemove(file);
}
}
+
// 绉婚櫎鏂囦欢
async function handleRemove(file) {
if (!file?.id) {
@@ -1396,18 +1326,11 @@
}
}
}
+
// 鎻愪氦琛ㄥ崟
const submitForm = () => {
proxy.$refs["formRef"].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(",");
-
if (productData.value.length > 0) {
// 鏂板鏃讹紝闇�瑕佷粠姣忎釜浜у搧瀵硅薄涓垹闄� id 瀛楁
let processedProductData = productData.value;
@@ -1422,13 +1345,8 @@
proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
return;
}
- let tempFileIds = [];
- if (fileList.value.length > 0) {
- tempFileIds = fileList.value.map(item => item.tempId);
- }
- form.value.tempFileIds = tempFileIds;
+ form.value.storageBlobDTOS = fileList.value;
form.value.type = 2;
- form.value.approveUserIds = approveUserIds;
// 濡傛灉salesLedgerId涓虹┖锛屽垯涓嶄紶閫抯alesContractNo
if (!form.value.salesLedgerId) {
@@ -1452,9 +1370,6 @@
// 鍏抽棴寮规
const closeDia = () => {
proxy.resetForm("formRef");
- // 閲嶇疆瀹℃壒浜鸿妭鐐癸紙榛樿涓�涓┖鑺傜偣锛�
- approverNodes.value = [{ id: 1, userId: null }];
- nextApproverId = 2;
dialogFormVisible.value = false;
};
// 鎵撳紑浜у搧寮规
@@ -1464,17 +1379,21 @@
productForm.value = {};
proxy.resetForm("productFormRef");
productFormVisible.value = true;
-
+
// 鍏堣幏鍙栦骇鍝侀�夐」锛岀‘淇濇暟鎹姞杞藉畬鎴�
await getProductOptions();
-
+
// 绛夊緟 DOM 鏇存柊
await nextTick();
-
+
+ if (type === "add") {
+ productForm.value.isChecked = false;
+ }
+
if (type === "edit") {
// 澶嶅埗琛屾暟鎹�
productForm.value = { ...row };
-
+
// 濡傛灉鏄粠妯℃澘鍔犺浇鐨勬暟鎹紝鍙兘娌℃湁 productId 鍜� productModelId
// 闇�瑕佹牴鎹� productCategory 鍜� specificationModel 鏉ユ煡鎵惧搴旂殑 ID
if (!productForm.value.productId && productForm.value.productCategory) {
@@ -1485,25 +1404,34 @@
return nodes[i].value;
}
if (nodes[i].children && nodes[i].children.length > 0) {
- const found = findProductIdByCategory(nodes[i].children, categoryName);
+ const found = findProductIdByCategory(
+ nodes[i].children,
+ categoryName
+ );
if (found) return found;
}
}
return null;
};
-
- const productId = findProductIdByCategory(productOptions.value, productForm.value.productCategory);
+
+ const productId = findProductIdByCategory(
+ productOptions.value,
+ productForm.value.productCategory
+ );
if (productId) {
productForm.value.productId = productId;
// 鑾峰彇鍨嬪彿鍒楄〃骞剁瓑寰呭畬鎴�
const modelRes = await modelList({ id: productId });
modelOptions.value = modelRes;
-
+
// 绛夊緟 DOM 鏇存柊
await nextTick();
-
+
// 鏍规嵁 specificationModel 鏌ユ壘 productModelId
- if (productForm.value.specificationModel && modelOptions.value.length > 0) {
+ if (
+ productForm.value.specificationModel &&
+ modelOptions.value.length > 0
+ ) {
const modelItem = modelOptions.value.find(
item => item.model === productForm.value.specificationModel
);
@@ -1517,15 +1445,15 @@
} else if (productForm.value.productId) {
// 濡傛灉鏈� productId锛屾甯稿姞杞藉瀷鍙峰垪琛�
await getModels(productForm.value.productId);
-
+
// 绛夊緟 DOM 鏇存柊
await nextTick();
-
+
if (productForm.value.productModelId) {
getProductModel(productForm.value.productModelId);
}
}
-
+
// 鏈�鍚庡啀绛夊緟涓�娆� DOM 鏇存柊锛岀‘淇濇墍鏈夋暟鎹兘宸茶缃�
await nextTick();
}
@@ -1574,6 +1502,7 @@
}
return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
};
+
function convertIdToValue(data) {
return data.map(item => {
const { id, children, ...rest } = item;
@@ -1588,6 +1517,7 @@
return newItem;
});
}
+
// 鎻愪氦浜у搧琛ㄥ崟
const submitProduct = () => {
proxy.$refs["productFormRef"].validate(valid => {
@@ -1648,11 +1578,9 @@
delProduct(ids).then(res => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
closeProductDia();
- getPurchaseById({ id: currentId.value, type: 2 }).then(
- res => {
- productData.value = res.productData;
- }
- );
+ getPurchaseById({ id: currentId.value, type: 2 }).then(res => {
+ productData.value = res.productData;
+ });
});
})
.catch(() => {
@@ -1683,12 +1611,14 @@
const handleDelete = () => {
let ids = [];
if (selectedRows.value.length > 0) {
- ids = selectedRows.value.map(item => item.id);
+ ids = selectedRows.value
+ .filter(item => item.salesLedgerId === null)
+ .map(item => item.id);
} else {
proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
return;
}
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "warning",
@@ -1703,6 +1633,7 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
function getCurrentDate() {
const today = new Date();
@@ -1711,6 +1642,7 @@
const day = String(today.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
+
const mathNum = () => {
if (!productForm.value.taxRate) {
proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
@@ -1843,14 +1775,6 @@
}
};
- const fileListRef = ref(null);
- const fileListDialogVisible = ref(false);
- const downLoadFile = row => {
- if (fileListRef.value) {
- fileListRef.value.open(row.salesLedgerFiles);
- }
- };
-
// 鑾峰彇妯℃澘淇℃伅
const getTemplateList = async () => {
let res = await getPurchaseTemplateList();
@@ -1859,13 +1783,19 @@
}
};
+ // 鎵撳紑闄勪欢寮规
+ const openFileDialog = async row => {
+ recordId.value = row.id;
+ fileListDialogVisible.value = true;
+ };
+
// 鍒犻櫎妯℃澘
- const handleDeleteTemplate = async (item) => {
+ const handleDeleteTemplate = async item => {
if (!item.id) {
proxy.$modal.msgWarning("鏃犳硶鍒犻櫎璇ユā鏉�");
return;
}
-
+
try {
await ElMessageBox.confirm(
`纭畾瑕佸垹闄ゆā鏉�"${item.templateName}"鍚楋紵`,
@@ -1876,7 +1806,7 @@
type: "warning",
}
);
-
+
const res = await delPurchaseTemplate([item.id]);
if (res && res.code === 200) {
ElMessage({
@@ -1918,18 +1848,21 @@
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;
}
-
+
// 瀹℃壒浜鸿妭鐐瑰鍣ㄦ牱寮�
.approver-nodes-container {
display: flex;
@@ -1940,7 +1873,7 @@
border-radius: 4px;
border: 1px solid #e4e7ed;
}
-
+
.approver-node-item {
flex: 0 0 calc(33.333% - 12px);
min-width: 200px;
@@ -1949,41 +1882,42 @@
border-radius: 4px;
border: 1px solid #dcdfe6;
transition: all 0.3s;
-
+
&:hover {
border-color: #409eff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
}
}
-
+
.approver-node-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
-
+
.approver-node-label {
font-size: 13px;
font-weight: 500;
color: #606266;
}
-
+
@media (max-width: 1200px) {
.approver-node-item {
flex: 0 0 calc(50% - 8px);
}
}
-
+
@media (max-width: 768px) {
.approver-node-item {
flex: 0 0 100%;
}
}
-
+
// 鍒犻櫎鍥炬爣鏍峰紡
.delete-icon {
transition: all 0.3s;
+
&:hover {
color: #f56c6c !important;
transform: scale(1.2);
diff --git a/src/views/procurementManagement/procurementReport/index.vue b/src/views/procurementManagement/procurementReport/index.vue
index adf554d..26e682d 100644
--- a/src/views/procurementManagement/procurementReport/index.vue
+++ b/src/views/procurementManagement/procurementReport/index.vue
@@ -54,6 +54,10 @@
<span class="stat-label">鍟嗗搧绉嶇被锛�</span>
<span class="stat-value">{{ businessSummaryStats.productTypes }}</span>
</div>
+ <div class="stat-item">
+ <span class="stat-label">閫�娆炬�婚锛�</span>
+ <span class="stat-value">{{ businessSummaryStats.returnAmount }}</span>
+ </div>
</div>
</div>
@@ -119,7 +123,23 @@
}
},
{
- label: '閲囪喘閲戦',
+ label: '閫�璐ф暟閲�',
+ prop: 'returnQuantity',
+ width: 120,
+ formatData: (val) => {
+ return val ? parseFloat(val).toLocaleString() : '0'
+ }
+ },
+ {
+ label: '閫�璐ч噾棰�',
+ prop: 'returnAmount',
+ width: 120,
+ formatData: (val) => {
+ return val ? `楼${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '楼0.00'
+ }
+ },
+ {
+ label: '閫�娆惧偍閲�',
prop: 'purchaseAmount',
formatData: (val) => {
return val ? `楼${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '楼0.00'
@@ -239,7 +259,10 @@
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
+ businessSummaryStats.value.returnAmount = businessSummaryData.value.reduce((sum, item) => {
+ return sum + (parseFloat(item.returnAmount) || 0)
+ }, 0)
+ businessSummaryStats.value.productTypes = businessSummaryData.value.length
} else {
businessSummaryStats.value = {
totalAmount: 0,
@@ -311,12 +334,6 @@
</script>
<style scoped>
-.app-container {
- padding: 20px;
- background-color: #f5f7fa;
- min-height: 100vh;
-}
-
.page-header {
text-align: center;
margin-bottom: 20px;
diff --git a/src/views/procurementManagement/purchaseReturnOrder/New.vue b/src/views/procurementManagement/purchaseReturnOrder/New.vue
index d2fe2a9..dffdeea 100644
--- a/src/views/procurementManagement/purchaseReturnOrder/New.vue
+++ b/src/views/procurementManagement/purchaseReturnOrder/New.vue
@@ -26,6 +26,7 @@
v-model="formState.no"
:placeholder="formState.isDefaultNo ? '浣跨敤绯荤粺缂栧彿' : '璇疯緭鍏ラ��鏂欏崟鍙�'"
:disabled="formState.isDefaultNo"
+ style="width: 240px"
>
<template #append>
<el-checkbox v-model="formState.isDefaultNo" size="large" @change="handleChangeIsDefaultNo" />
@@ -69,6 +70,7 @@
v-model="formState.supplierId"
placeholder="璇烽�夋嫨渚涘簲鍟�"
style="width: 240px"
+ filterable
@focus="fetchSupplierOptions"
@change="handleChangeSupplierId"
>
@@ -77,25 +79,6 @@
:key="item.id"
:label="item.supplierName"
:value="item.id"
- />
- </el-select>
- </el-form-item>
-
- <el-form-item
- label="椤圭洰"
- prop="projectId"
- >
- <el-select
- v-model="formState.projectId"
- placeholder="璇烽�夋嫨椤圭洰"
- style="width: 240px"
- @focus="fetchProjectOptions"
- >
- <el-option
- v-for="item in projectOptions"
- :key="item.id"
- :label="item.name"
- :value="item.id"
/>
</el-select>
</el-form-item>
@@ -159,6 +142,7 @@
:reserve-keyword="false"
style="width: 240px"
@focus="fetchUserOptions"
+ @change="formState.preparedUserName = userOptions.find(item => item.userId === formState.preparedUserId)?.nickName || ''"
>
<el-option
v-for="item in userOptions"
@@ -189,6 +173,7 @@
style="width: 240px"
:reserve-keyword="false"
@focus="fetchUserOptions"
+ @change="formState.returnUserName = userOptions.find(item => item.userId === formState.returnUserId)?.nickName || ''"
>
<el-option
v-for="item in userOptions"
@@ -233,7 +218,7 @@
label="澶囨敞锛�"
prop="remark"
>
- <el-input v-model="formState.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�"/>
+ <el-input style="width: 240px" v-model="formState.remark" :rows="1" type="textarea" placeholder="璇疯緭鍏ュ娉�"/>
</el-form-item>
<div style="margin: 20px 0;">
@@ -264,7 +249,10 @@
width="70" />
<el-table-column label="鏁伴噺"
prop="quantity"
- width="70" />
+ width="100" />
+ <el-table-column label="鍙��璐ф暟閲�"
+ prop="availableQuality"
+ width="130" />
<el-table-column label="閫�璐ф暟閲�"
prop="returnQuantity"
width="180">
@@ -272,8 +260,10 @@
<el-input-number v-model="scope.row.returnQuantity"
controls-position="right"
:step="1"
- :min="1"
- :max="scope.row.quantity"
+ :min="0"
+ :max="getReturnQtyMax(scope.row)"
+ :disabled="getReturnQtyMax(scope.row) <= 0"
+ @change="syncReturnTotal(scope.row)"
required
placeholder="璇疯緭鍏ラ��璐ф暟閲�" />
</template>
@@ -289,14 +279,13 @@
prop="taxInclusiveUnitPrice"
:formatter="formattedNumber"
width="150" />
- <el-table-column label="鍚◣鎬讳环(鍏�)"
+ <el-table-column label="閫�璐ф�讳环(鍏�)"
prop="taxInclusiveTotalPrice"
- :formatter="formattedNumber"
- width="150" />
- <el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
- prop="taxExclusiveTotalPrice"
- :formatter="formattedNumber"
- width="150" />
+ width="180">
+ <template #default="scope">
+ {{ formatAmount(getReturnTotal(scope.row)) || '--' }}
+ </template>
+ </el-table-column>
<el-table-column label="鏄惁璐ㄦ"
prop="isChecked"
width="150">
@@ -346,26 +335,54 @@
label="鏁村崟鎶樻墸鐜囷細"
prop="totalDiscountAmount"
>
- <el-input-number v-model="formState.totalDiscountRate"
+ <el-input v-model="formState.totalDiscountRate"
controls-position="right"
:step="0.01"
:precision="2"
style="width: 100%;"
- placeholder="璇疯緭鍏ユ暣鍗曟姌鎵g巼"/>
+ @change="totalDiscount"
+ placeholder="璇疯緭鍏ユ暣鍗曟姌鎵g巼">
+ <template #append>
+ %
+ </template>
+ </el-input>
</el-form-item>
<el-form-item
label="鎴愪氦閲戦锛�"
prop="totalAmount"
+ :rules="[
+ {
+ required: true,
+ message: '璇疯緭鍏ユ垚浜ら噾棰�',
+ trigger: 'change',
+ }
+ ]"
>
<el-input-number v-model="formState.totalAmount"
controls-position="right"
:step="0.01"
:precision="2"
style="width: 100%;"
- @change="handleChangeTotalAmount"
placeholder="璇疯緭鍏ユ垚浜ら噾棰�"/>
</el-form-item>
+ <el-form-item label="鏀舵鏂瑰紡" prop="incomeType" :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨鏀舵鏂瑰紡',
+ trigger: 'change',
+ }
+ ]">
+ <el-select
+ style="width: 240px;"
+ v-model="formState.incomeType"
+ 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>
<template #footer>
<div class="dialog-footer">
@@ -386,11 +403,14 @@
</template>
<script setup>
-import {ref, computed, getCurrentInstance} from "vue";
+import {ref, computed, getCurrentInstance, watch, defineAsyncComponent} from "vue";
import {createPurchaseReturnOrder} from "@/api/procurementManagement/purchase_return_order.js";
import {getOptions, purchaseList} from "@/api/procurementManagement/procurementLedger.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
const ProductList = defineAsyncComponent(() => import("@/views/procurementManagement/purchaseReturnOrder/ProductList.vue"));
+ import {
+ productList,
+ } from "@/api/procurementManagement/procurementLedger.js";
const props = defineProps({
visible: {
type: Boolean,
@@ -398,6 +418,24 @@
}
});
let { proxy } = getCurrentInstance()
+const payment_methods = [
+ {
+ "label": "鐜伴噾",
+ "value": "0",
+ },
+ {
+ "label": "鏀エ",
+ "value": "1",
+ },
+ {
+ "label": "閾惰杞处",
+ "value": "2",
+ },
+ {
+ "label": "鍏朵粬",
+ "value": "3",
+ },
+]
const emit = defineEmits(['update:visible', 'completed']);
// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
@@ -405,6 +443,7 @@
no: '',
isDefaultNo: true,
returnType: 0,
+ incomeType: undefined,
remark: '',
supplierId: undefined,
projectId: undefined,
@@ -420,8 +459,6 @@
});
// 渚涘簲鍟嗛�夐」
const supplierOptions = ref([])
-// 椤圭洰閫夐」
-const projectOptions = ref([])
// 椤圭洰闃舵閫夐」
const projectStageOptions = ref([
{
@@ -465,6 +502,64 @@
return parseFloat(cellValue).toFixed(2);
};
+const formatAmount = (value) => {
+ if (value === null || value === undefined || value === '') {
+ return '--'
+ }
+ const num = Number(value)
+ if (Number.isNaN(num)) {
+ return '--'
+ }
+ return num.toFixed(2)
+}
+
+const toNumber = (val) => {
+ const num = Number(val)
+ return Number.isNaN(num) ? 0 : num
+}
+
+const getReturnTotal = (row) => {
+ const qty = toNumber(row?.returnQuantity)
+ const unitPrice = toNumber(row?.taxInclusiveUnitPrice)
+ const total = qty * unitPrice
+ return Number(total.toFixed(2))
+}
+
+const syncReturnTotal = (row) => {
+ if (!row) {
+ return
+ }
+ row.taxInclusiveTotalPrice = getReturnTotal(row)
+}
+
+const getBaseAmount = () => {
+ const rows = formState.value.purchaseReturnOrderProductsDtos || []
+ return rows.reduce((sum, item) => {
+ return sum + toNumber(item.taxInclusiveTotalPrice)
+ }, 0)
+}
+
+// 鍚屾鎶樻墸棰�
+const totalDiscount = () => {
+ const discountRate = toNumber(formState.value.totalDiscountRate)
+ if (discountRate < 0 || discountRate > 100) {
+ proxy.$modal.msgError("璇疯緭鍏�0-100涔嬮棿鐨勬姌鎵g巼")
+ return
+ }
+ const baseAmount = getBaseAmount()
+ // 鎶樻墸棰� = 浜у搧閫�璐ф�讳环鍚堣 * 鎶樻墸鐜�
+ formState.value.totalDiscountAmount = Number((baseAmount * (discountRate / 100)).toFixed(2))
+ syncTotalAmount()
+}
+
+const getReturnQtyMax = (row) => {
+ const max = Number(row?.availableQuality)
+ if (Number.isNaN(max) || max < 0) {
+ return 0
+ }
+ return max
+}
+
const closeModal = () => {
isShow.value = false;
};
@@ -474,6 +569,7 @@
param,
[
"quantity",
+ "availableQuality",
"returnQuantity",
"taxInclusiveUnitPrice",
"taxInclusiveTotalPrice",
@@ -482,16 +578,48 @@
{
quantity: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
returnQuantity: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ availableQuality: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
}
);
};
const handleChangeTotalDiscountAmount= () => {
- formState.value.totalAmount = formState.value.totalDiscountAmount * -1
+ const discountAmount = toNumber(formState.value.totalDiscountAmount)
+ if (discountAmount < 0) {
+ proxy.$modal.msgError("鏁村崟鎶樻墸棰濅笉鑳藉皬浜�0")
+ formState.value.totalDiscountAmount = 0
+ }
+
+ const baseAmount = getBaseAmount()
+ const normalizedAmount = toNumber(formState.value.totalDiscountAmount)
+ if (baseAmount <= 0) {
+ formState.value.totalDiscountRate = 0
+ syncTotalAmount()
+ return
+ }
+
+ if (normalizedAmount > baseAmount) {
+ proxy.$modal.msgError("鏁村崟鎶樻墸棰濅笉鑳藉ぇ浜庝骇鍝侀��璐ф�讳环鍚堣")
+ formState.value.totalDiscountAmount = Number(baseAmount.toFixed(2))
+ }
+
+ const discountRate = (toNumber(formState.value.totalDiscountAmount) / baseAmount) * 100
+ formState.value.totalDiscountRate = Number(discountRate.toFixed(2))
+ syncTotalAmount()
}
-const handleChangeTotalAmount= () => {
- formState.value.totalDiscountAmount = formState.value.totalAmount * -1
+const resetFeeInfo = () => {
+ formState.value.totalDiscountAmount = 0
+ formState.value.totalDiscountRate = undefined
+ formState.value.totalAmount = 0
+ formState.value.incomeType = undefined
+}
+
+const syncTotalAmount = () => {
+ const baseAmount = getBaseAmount()
+ const discount = toNumber(formState.value.totalDiscountAmount)
+ // 鎴愪氦閲戦 = 浜у搧閫�璐ф�讳环鍚堣 - 鎶樻墸棰�
+ formState.value.totalAmount = Number((baseAmount - discount).toFixed(2))
}
// 鑾峰彇渚涘簲鍟嗛�夐」
@@ -504,13 +632,6 @@
});
}
-// 鑾峰彇椤圭洰閫夐」
-const fetchProjectOptions = () => {
- if (projectOptions.value.length > 0) {
- return
- }
- // todo 椤圭洰閫夐」
-}
// 鑾峰彇鐢ㄦ埛閫夐」
const fetchUserOptions = () => {
@@ -525,6 +646,7 @@
// 澶勭悊鏀瑰彉渚涘簲鍟嗘暟鎹�
const handleChangeSupplierId = () => {
formState.value.purchaseLedgerId = undefined
+ formState.value.supplierName = supplierOptions.value.find(item => item.id === formState.value.supplierId)?.supplierName || ''
fetchPurchaseLedgerOptions()
}
@@ -532,15 +654,27 @@
const fetchPurchaseLedgerOptions = () => {
purchaseLedgerOptions.value = []
if (formState.value.supplierId) {
- purchaseList({supplierId: formState.value.supplierId}).then((res) => {
+ purchaseList({supplierId: formState.value.supplierId,approvalStatus:3}).then((res) => {
purchaseLedgerOptions.value = res.rows;
});
}
}
// 澶勭悊鏀瑰彉閲囪喘鍙拌处鏁版嵁
-const handleChangePurchaseLedgerId = () => {
- formState.value.purchaseReturnOrderProductsDtos = []
+const handleChangePurchaseLedgerId = async () => {
+ resetFeeInfo()
+ if (!formState.value.purchaseLedgerId) {
+ formState.value.purchaseReturnOrderProductsDtos = []
+ return
+ }
+ const res = await productList({ salesLedgerId: formState.value.purchaseLedgerId, type: 2 });
+ formState.value.purchaseReturnOrderProductsDtos = res.data.map(item => ({
+ ...item,
+ returnQuantity: undefined,
+ taxInclusiveTotalPrice: 0,
+ salesLedgerProductId: item.id,
+ }))
+ syncTotalAmount()
}
// 澶勭悊鏀瑰彉鏄惁榛樿缂栧彿
@@ -556,23 +690,50 @@
const newProducts = selectedRows.filter(item => !existingIds.has(item.id)).map(item => ({
...item,
returnQuantity: undefined,
+ taxInclusiveTotalPrice: 0,
salesLedgerProductId: item.id,
}));
formState.value.purchaseReturnOrderProductsDtos.push(...newProducts);
+ syncTotalAmount()
}
// 鍒犻櫎鍗曢」浜у搧
const delProduct = (index) => {
formState.value.purchaseReturnOrderProductsDtos.splice(index, 1)
+ syncTotalAmount()
}
// 鎻愪氦琛ㄥ崟
const handleSubmit = () => {
- // 楠岃瘉閫�璐ф暟閲�
- const hasEmptyReturnQuantity = formState.value.purchaseReturnOrderProductsDtos.some(item => !item.returnQuantity || item.returnQuantity <= 0);
- if (hasEmptyReturnQuantity) {
- proxy.$modal.msgError("璇蜂负鎵�鏈変骇鍝佸~鍐欓��璐ф暟閲�");
- return;
+ const productList = formState.value.purchaseReturnOrderProductsDtos || []
+
+ productList.forEach(syncReturnTotal)
+
+ if (productList.length === 0) {
+ proxy.$modal.msgError("璇峰厛閫夋嫨浜у搧")
+ return
+ }
+
+ // 閫愯鏍¢獙閫�璐ф暟閲忥細浠绘剰涓�琛屾湭濉�/闈炴硶/瓒呴檺閮戒笉鍏佽鎻愪氦
+ const invalidRowIndex = productList.findIndex((item) => {
+ const qty = Number(item.returnQuantity)
+ const maxQty = Number(item.availableQuality)
+
+ if (item.returnQuantity === null || item.returnQuantity === undefined || item.returnQuantity === "") {
+ return true
+ }
+ if (Number.isNaN(qty) || qty <= 0) {
+ return true
+ }
+ if (!Number.isNaN(maxQty) && maxQty > 0 && qty > maxQty) {
+ return true
+ }
+ return false
+ })
+
+ if (invalidRowIndex !== -1) {
+ proxy.$modal.msgError(`绗�${invalidRowIndex + 1}琛岄��璐ф暟閲忔湭濉啓鎴栦笉鍚堟硶`)
+ return
}
proxy.$refs["formRef"].validate(valid => {
@@ -588,6 +749,15 @@
})
};
+watch(
+ () => formState.value.purchaseReturnOrderProductsDtos,
+ (rows) => {
+ (rows || []).forEach(syncReturnTotal)
+ syncTotalAmount()
+ },
+ { deep: true }
+)
+
defineExpose({
closeModal,
handleSubmit,
diff --git a/src/views/procurementManagement/purchaseReturnOrder/index.vue b/src/views/procurementManagement/purchaseReturnOrder/index.vue
index 4e91e59..cd2c7f4 100644
--- a/src/views/procurementManagement/purchaseReturnOrder/index.vue
+++ b/src/views/procurementManagement/purchaseReturnOrder/index.vue
@@ -23,37 +23,98 @@
</div>
<div class="table_list">
- <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" :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="no" show-overflow-tooltip />
- <el-table-column label="閫�璐ф柟寮�" prop="returnType" show-overflow-tooltip />
- <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" show-overflow-tooltip />
- <el-table-column label="鍏宠仈鍗曞彿" prop="purchaseContractNumber" show-overflow-tooltip />
- <el-table-column label="閫�鏂欎汉" prop="returnUserName" show-overflow-tooltip />
- <el-table-column label="澶囨敞" prop="remark" show-overflow-tooltip />
- <el-table-column label="鍒涘缓浜�" prop="createUserName" show-overflow-tooltip />
- <el-table-column label="鍒涘缓鏃堕棿" prop="createTime" 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">璇︽儏</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" />
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :tableLoading="tableLoading"
+ :isSelection="true"
+ :page="page"
+ :height="'calc(100vh - 18.5em)'"
+ @selection-change="handleSelectionChange"
+ @pagination="paginationChange"
+ >
+ <template #operation="{ row }">
+ <el-button link type="primary" size="small" style="color: #67C23A" @click="handleDetail(row)">璇︽儏</el-button>
+ <el-button link size="small" @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
</div>
<new v-if="isShowNewModal"
v-model:visible="isShowNewModal"
@completed="handleQuery" />
+
+ <el-dialog
+ v-model="detailVisible"
+ title="閲囪喘閫�璐ц鎯�"
+ width="1200"
+ destroy-on-close
+ >
+ <div v-loading="detailLoading">
+ <el-descriptions :column="3" border>
+ <el-descriptions-item label="閫�鏂欏崟鍙�">{{ detailData.no || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="閫�璐ф柟寮�">{{ getReturnTypeLabel(detailData.returnType) }}</el-descriptions-item>
+ <el-descriptions-item label="渚涘簲鍟嗗悕绉�">{{ detailData.supplierName || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="椤圭洰闃舵">{{ getProjectPhaseLabel(detailData.projectPhase) }}</el-descriptions-item>
+ <el-descriptions-item label="鍏宠仈鍗曞彿">{{ detailData.purchaseContractNumber || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍒朵綔鏃ユ湡">{{ detailData.preparedAt || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍒跺崟浜�">{{ detailData.preparedUserName || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="閫�鏂欎汉">{{ detailData.returnUserName || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鏁村崟鎶樻墸棰�">{{ formatAmount(detailData.totalDiscountAmount) }}</el-descriptions-item>
+ <el-descriptions-item label="鏁村崟鎶樻墸鐜�">{{ detailData.totalDiscountRate ?? '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鎴愪氦閲戦">{{ formatAmount(detailData.totalAmount) }}</el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓浜�">{{ detailData.createUserName || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓鏃堕棿">{{ detailData.createTime || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鏈�杩戞洿鏂版椂闂�">{{ detailData.updateTime || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="澶囨敞" :span="3">{{ detailData.remark || '--' }}</el-descriptions-item>
+ </el-descriptions>
+
+ <el-divider content-position="left">浜у搧鍒楄〃</el-divider>
+
+ <el-table
+ :data="detailProducts"
+ border
+ max-height="420"
+ style="width: 100%"
+ >
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" min-width="120" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" min-width="140" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" width="80" />
+ <el-table-column label="鏁伴噺" prop="quantity" width="80" />
+ <el-table-column label="閫�璐ф暟閲�" prop="returnQuantity" width="100" />
+ <el-table-column label="搴撳瓨棰勮鏁伴噺" prop="warnNum" width="120" />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" width="90" />
+ <el-table-column label="鍚◣鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="130">
+ <template #default="scope">{{ formatAmount(scope.row.taxInclusiveUnitPrice) }}</template>
+ </el-table-column>
+ <el-table-column label="鍚◣鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="130">
+ <template #default="scope">{{ formatAmount(scope.row.taxInclusiveTotalPrice) }}</template>
+ </el-table-column>
+ <el-table-column label="涓嶅惈绋庢�讳环(鍏�)" prop="taxExclusiveTotalPrice" width="140">
+ <template #default="scope">{{ formatAmount(scope.row.taxExclusiveTotalPrice) }}</template>
+ </el-table-column>
+ <el-table-column label="鏄惁璐ㄦ" prop="isChecked" width="100" align="center">
+ <template #default="scope">
+ <el-tag :type="scope.row.isChecked ? 'success' : 'info'">
+ {{ scope.row.isChecked ? '鏄�' : '鍚�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <template #footer>
+ <el-button @click="detailVisible = false">鍏抽棴</el-button>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
-import pagination from '@/components/PIMTable/Pagination.vue'
-import { ref, reactive, toRefs, onMounted } from 'vue'
-import {findPurchaseReturnOrderListPage} from "@/api/procurementManagement/purchase_return_order.js";
+import PIMTable from '@/components/PIMTable/PIMTable.vue'
+import { ref, reactive, toRefs, onMounted, defineAsyncComponent, getCurrentInstance } from 'vue'
+const { proxy } = getCurrentInstance()
+import {findPurchaseReturnOrderListPage, getPurchaseReturnOrderDetail, deletePurchaseReturnOrder} from "@/api/procurementManagement/purchase_return_order.js";
const New = defineAsyncComponent(() => import("@/views/procurementManagement/purchaseReturnOrder/New.vue"));
const tableData = ref([])
const selectedRows = ref([])
@@ -61,10 +122,122 @@
const page = reactive({
current: 1,
size: 100,
+ total: 0,
})
-const total = ref(0)
+const detailVisible = ref(false)
+const detailLoading = ref(false)
+const detailData = ref({})
+const detailProducts = ref([])
// 鏄惁鏄剧ず鏂板寮规
const isShowNewModal = ref(false)
+const returnTypeOptions = [
+ { label: '閫�璐ч��娆�', value: 0 },
+ { label: '鎷掓敹', value: 1 },
+]
+const projectPhaseOptions = [
+ { label: '绔嬮」', value: 0 },
+ { label: '璁捐', value: 1 },
+ { label: '閲囪喘', value: 2 },
+ { label: '鐢熶骇', value: 3 },
+ { label: '鍑鸿揣', value: 4 },
+]
+const tableColumn = ref([
+ {
+ label: '閫�鏂欏崟鍙�',
+ prop: 'no',
+ },
+ {
+ label: '閫�璐ф柟寮�',
+ prop: 'returnType',
+ formatData: (val) => returnTypeOptions.find(item => item.value === val)?.label || '--',
+ },
+ {
+ label: '渚涘簲鍟嗗悕绉�',
+ prop: 'supplierName',
+ width: 180,
+ },
+ {
+ label: '椤圭洰闃舵',
+ prop: 'projectPhase',
+ width: 100,
+ formatData: (val) => projectPhaseOptions.find(item => String(item.value) === String(val))?.label || '--',
+ },
+ {
+ label: '鍏宠仈鍗曞彿',
+ prop: 'purchaseContractNumber',
+ width: 160,
+ },
+ {
+ label: '鍒朵綔鏃ユ湡',
+ prop: 'preparedAt',
+ width: 130,
+ },
+ {
+ label: '鍒跺崟浜�',
+ prop: 'preparedUserName',
+ width: 110,
+ },
+ {
+ label: '閫�鏂欎汉',
+ prop: 'returnUserName',
+ width: 110,
+ },
+
+ {
+ label: '鏁村崟鎶樻墸棰�',
+ prop: 'totalDiscountAmount',
+ width: 120,
+ },
+ {
+ label: '鏁村崟鎶樻墸鐜�',
+ prop: 'totalDiscountRate',
+ width: 120,
+ },
+ {
+ label: '鎴愪氦閲戦',
+ prop: 'totalAmount',
+ width: 120,
+ },
+ {
+ label: '鍒涘缓浜�',
+ prop: 'createUserName',
+ width: 110,
+ },
+ {
+ label: '鍒涘缓鏃堕棿',
+ prop: 'createTime',
+ width: 170,
+ },
+ {
+ label: '鏈�杩戞洿鏂版椂闂�',
+ prop: 'updateTime',
+ width: 170,
+ },
+ {
+ label: '澶囨敞',
+ prop: 'remark',
+ width: 180,
+ },
+ {
+ dataType: "action",
+ width: 120,
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ operation: [
+ {
+ name: "璇︽儏",
+ type: "text",
+ clickFun: row => {handleDetail(row);},
+ },
+ {
+ name: "鍒犻櫎",
+ clickFun: row => {handleDelete(row)},
+ },
+ ],
+ },
+
+])
const data = reactive({
searchForm: {
no: '',
@@ -79,6 +252,48 @@
getList()
}
+// 鍒犻櫎鎿嶄綔
+const handleDelete = (row) => {
+ console.log('鍒犻櫎琛屾暟鎹細', row)
+ proxy?.$modal?.confirm('纭畾瑕佸垹闄ゅ悧锛熷垹闄ゅ皢鏃犳硶鎭㈠').then(() => {
+ // 杩欓噷璋冪敤鍒犻櫎鎺ュ彛锛屼紶鍏� row.id
+ deletePurchaseReturnOrder(row.id).then(() => {
+ proxy?.$modal?.msgSuccess?.("鍒犻櫎鎴愬姛");
+ getList()
+ }).catch(() => {
+ proxy?.$modal?.msgError?.('鍒犻櫎澶辫触')
+ })
+ }).catch(() => {
+ // 鍙栨秷鍒犻櫎
+ proxy?.$modal?.msgInfo?.('宸插彇娑堝垹闄�')
+
+ })
+}
+// 鏌ョ湅璇︽儏
+const handleDetail = (row) => {
+ if (!row?.id) {
+ proxy?.$modal?.msgWarning?.('鏈幏鍙栧埌鍗曟嵁ID')
+ return
+ }
+ detailVisible.value = true
+ detailLoading.value = true
+ getPurchaseReturnOrderDetail(row.id).then(res => {
+ const payload = res?.data || {}
+ detailData.value = payload
+ // 鎷兼帴杩炰釜瀵硅薄鎴愪竴涓璞★紝鏂逛究灞曠ず item 鍜� item.salesLedgerProduct 閲岀殑瀛楁
+
+
+ detailProducts.value =
+ payload.purchaseReturnOrderProductsDetailVoList.map(item => ({ ...item, ...item.salesLedgerProduct })) ||
+ []
+ }).catch(() => {
+ proxy?.$modal?.msgError?.('鑾峰彇璇︽儏澶辫触')
+ }).finally(() => {
+ detailLoading.value = false
+ })
+}
+
+
const paginationChange = (obj) => {
page.current = obj.page;
page.size = obj.limit;
@@ -90,7 +305,7 @@
findPurchaseReturnOrderListPage({ ...searchForm.value, ...page }).then(res => {
tableLoading.value = false
tableData.value = res.data.records
- total.value = res.data.total
+ page.total = res.data.total
}).catch(() => {
tableLoading.value = false
})
@@ -102,8 +317,32 @@
selectedRows.value = selection.filter(item => item.id);
}
+const getReturnTypeLabel = (value) => {
+ return returnTypeOptions.find(item => String(item.value) === String(value))?.label || '--'
+}
+
+const getProjectPhaseLabel = (value) => {
+ return projectPhaseOptions.find(item => String(item.value) === String(value))?.label || '--'
+}
+
+const formatAmount = (value) => {
+ if (value === null || value === undefined || value === '') {
+ return '--'
+ }
+ const num = Number(value)
+ if (Number.isNaN(num)) {
+ return value
+ }
+ return num.toFixed(2)
+}
+
onMounted(() => {
getList()
})
</script>
+<style scoped>
+.table_list {
+ margin-top: unset;
+}
+</style>
diff --git a/src/views/productManagement/productIdentifier/index.vue b/src/views/productManagement/productIdentifier/index.vue
index 519f745..94359e4 100644
--- a/src/views/productManagement/productIdentifier/index.vue
+++ b/src/views/productManagement/productIdentifier/index.vue
@@ -101,7 +101,7 @@
<template #default="scope">
<el-button link
type="primary"
- @click="handleView(scope.row)">鏌ョ湅</el-button>
+ @click="handleView(scope.row)" style="color: #67C23A">鏌ョ湅</el-button>
<el-button link
type="primary"
@click="handleEdit(scope.row)">缂栬緫</el-button>
diff --git a/src/views/productionManagement/processRoute/New.vue b/src/views/productionManagement/processRoute/New.vue
index 62c6873..b7f4f26 100644
--- a/src/views/productionManagement/processRoute/New.vue
+++ b/src/views/productionManagement/processRoute/New.vue
@@ -1,71 +1,63 @@
<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="[
+ <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">
+ ]">
+ <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="[
+ <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 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 label="澶囨敞"
+ prop="description">
+ <el-input v-model="formState.description"
+ type="textarea" />
</el-form-item>
</el-form>
-
<!-- 浜у搧閫夋嫨寮圭獥 -->
- <ProductSelectDialog
- v-model="showProductSelectDialog"
- @confirm="handleProductSelect"
- single
- />
+ <ProductSelectDialog v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single />
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button type="primary"
+ @click="handleSubmit">纭</el-button>
<el-button @click="closeModal">鍙栨秷</el-button>
</div>
</template>
@@ -74,121 +66,122 @@
</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";
+ 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 props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ });
-const emit = defineEmits(['update:visible', 'completed']);
+ 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 = {
+ // 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+ const formState = ref({
productId: undefined,
productModelId: undefined,
productName: "",
productModelName: "",
bomId: undefined,
- description: '',
- };
- bomOptions.value = [];
- isShow.value = false;
-};
+ description: "",
+ });
-// 浜у搧閫夋嫨澶勭悊
-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 {
+ 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");
}
- } 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;
+ 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;
+ }
+ console.log(formState.value, "formState.value====");
+
+ add(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit("completed");
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
}
- 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,
-});
+ defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+ });
</script>
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
index 41103f9..43425c6 100644
--- a/src/views/productionManagement/processRoute/index.vue
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -1,191 +1,279 @@
<template>
<div class="app-container">
<div class="search_form">
- <el-form :model="searchForm" :inline="true">
+ <el-form :model="searchForm"
+ :inline="true">
<el-form-item label="瑙勬牸鍚嶇О:">
- <el-input v-model="searchForm.model" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ <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-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 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"
- />
+ <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"
- />
+ <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" />
+ <FileList v-if="fileDialogVisible"
+ v-model:visible="fileDialogVisible"
+ :record-type="'technology_routing'"
+ :record-id="currentProcessRouteId" />
</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'
+ 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";
+ const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
-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()
+ import { useRouter } from "vue-router";
+ import { ElMessage, ElMessageBox } from "element-plus";
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
+ 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);
+ },
+ },
+ {
+ name: "闄勪欢",
+ type: "text",
+ clickFun: row => {
+ openFileDialog(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 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,
+ // 闄勪欢鐩稿叧
+ const fileDialogVisible = ref(false);
+ const fileListDialogRef = ref(null);
+ const currentProcessRouteId = ref(null);
+ const filePage = reactive({
+ current: 1,
+ size: 1000,
+ total: 0,
+ });
+
+ const { proxy } = getCurrentInstance();
+
+ // 闄勪欢锛氭煡璇�
+ const fetchProcessRouteFiles = async processRouteId => {
+ const params = {
+ current: filePage.current,
+ size: filePage.size,
+ processRouteId,
+ };
+ const res = await listProcessRouteFiles(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,
}));
- page.total = res.data.total;
- }).catch(err => {
- tableLoading.value = false;
- })
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
+ fileListDialogRef.value?.setList(mapped);
+ };
-// 鎵撳紑鏂板寮规
-const showNewModal = () => {
- isShowNewModal.value = true
-};
+ // 鎵撳紑闄勪欢寮圭獥
+ const openFileDialog = async row => {
+ currentProcessRouteId.value = row.id;
+ fileDialogVisible.value = true;
+ await fetchProcessRouteFiles(row.id);
+ };
-const showEditModal = (row) => {
- isShowEditModal.value = true
- record.value = row
-};
+ // 鍒锋柊闄勪欢鍒楄〃
+ const refreshFileList = async () => {
+ if (!currentProcessRouteId.value) return;
+ await fetchProcessRouteFiles(currentProcessRouteId.value);
+ };
-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',
+ // 涓婁紶闄勪欢
+ const handleAttachmentUpload = async filePayload => {
+ if (!currentProcessRouteId.value) return;
+ const payload = {
+ fileName: filePayload?.fileName || filePayload?.name,
+ fileUrl: filePayload?.fileUrl || filePayload?.url,
+ processRouteId: currentProcessRouteId.value,
+ };
+ await addProcessRouteFile(payload);
+ ElMessage.success("鏂囦欢涓婁紶鎴愬姛");
+ await refreshFileList();
+ };
+
+ // 鍒犻櫎闄勪欢
+ const handleAttachmentDelete = async row => {
+ if (!row?.id) return false;
+ try {
+ await ElMessageBox.confirm("纭鍒犻櫎璇ラ檮浠讹紵", "鎻愮ず", {
+ type: "warning",
+ });
+ } catch {
+ return false;
}
- })
-};
+ await delProcessRouteFile([row.id]);
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ await refreshFileList();
+ };
-// 鍒犻櫎
-function handleDelete() {
- const ids = selectedRows.value.map((item) => item.id);
- proxy.$modal
- .confirm('鏄惁纭鍒犻櫎宸插嬀閫夌殑鏁版嵁椤癸紵')
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ 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 || "",
+ bomId: row.bomId || "",
+ description: row.description || "",
+ type: "route",
+ },
+ });
+ };
+
+ // 鍒犻櫎
+ function handleDelete() {
+ const ids = selectedRows.value.map(item => item.id);
+ proxy.$modal
+ .confirm("鏄惁纭鍒犻櫎宸插嬀閫夌殑鏁版嵁椤癸紵")
.then(function () {
return del(ids);
})
@@ -194,11 +282,15 @@
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
})
.catch(() => {});
-}
+ }
-onMounted(() => {
- getList();
-});
+ onMounted(() => {
+ getList();
+ });
</script>
-<style scoped></style>
+<style scoped>
+ .table_list {
+ margin-top: unset;
+ }
+</style>
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index c1c490c..6d64c30 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -1,9 +1,10 @@
<template>
<div class="app-container">
<PageHeader content="宸ヨ壓璺嚎椤圭洰" />
-
<!-- 宸ヨ壓璺嚎淇℃伅灞曠ず -->
- <el-card v-if="routeInfo.processRouteCode" class="route-info-card" shadow="hover">
+ <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">
@@ -37,7 +38,17 @@
<span class="info-value">{{ routeInfo.bomNo || '-' }}</span>
</div>
</div>
- <div class="info-item full-width" v-if="routeInfo.description">
+ <div class="info-item"
+ v-if="routeInfo.quantity && routeInfo.quantity !== 0">
+ <div class="info-label-wrapper">
+ <span class="info-label">闇�姹傛暟閲�</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.quantity || '-' }}</span>
+ </div>
+ </div>
+ <div class="info-item full-width"
+ v-if="routeInfo.description">
<div class="info-label-wrapper">
<span class="info-label">鎻忚堪</span>
</div>
@@ -47,850 +58,1565 @@
</div>
</div>
</el-card>
-
<!-- 琛ㄦ牸瑙嗗浘 -->
- <div v-if="viewMode === 'table'" class="section-header">
+ <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 icon="Grid"
+ @click="toggleView"
+ style="margin-right: 10px;">
鍗$墖瑙嗗浘
</el-button>
- <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ <el-button v-if="editable"
+ 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">
+ <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="technologyOperationId"
+ width="200">
<template #default="scope">
- {{ getProcessName(scope.row.processId) || '-' }}
+ {{ scope.row.technologyOperationName || scope.row.operationName || '-' }}
</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="鏄惁璐ㄦ" prop="isQuality" width="100">
+ <el-table-column label="鍙傛暟鍒楄〃"
+ min-width="160">
+ <template #default="scope">
+ <el-button type="primary"
+ link
+ size="small"
+ @click="handleViewParams(scope.row)">鍙傛暟鍒楄〃</el-button>
+ </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="鏄惁璐ㄦ"
+ prop="isQuality"
+ width="100">
<template #default="scope">
{{scope.row.isQuality ? "鏄�" : "鍚�"}}
</template>
</el-table-column>
- <el-table-column label="鎿嶄綔" align="center" fixed="right" width="150">
+ <el-table-column label="鏄惁鐢熶骇"
+ prop="isProduction"
+ width="100">
<template #default="scope">
- <el-button type="primary" link size="small" @click="handleEdit(scope.row)" :disabled="scope.row.isComplete">缂栬緫</el-button>
- <el-button type="danger" link size="small" @click="handleDelete(scope.row)" :disabled="scope.row.isComplete">鍒犻櫎</el-button>
+ {{scope.row.isProduction ? "鏄�" : "鍚�"}}
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ align="center"
+ fixed="right"
+ width="150">
+ <template #default="scope">
+ <el-button type="primary"
+ link
+ size="small"
+ @click="handleEdit(scope.row)"
+ :disabled="scope.row.isComplete || !editable">缂栬緫</el-button>
+ <el-button type="danger"
+ link
+ size="small"
+ @click="handleDelete(scope.row)"
+ :disabled="scope.row.isComplete || !editable">鍒犻櫎</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 icon="Menu"
+ @click="toggleView"
+ style="margin-right: 10px;">
琛ㄦ牸瑙嗗浘
</el-button>
- <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ <el-button v-if="editable"
+ 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>
- <el-tag type="primary" class="product-tag" v-if="item.isQuality">璐ㄦ</el-tag>
+ <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">{{ item.technologyOperationName || item.operationName || '-' }}</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)" :disabled="item.isComplete">缂栬緫</el-button>
- <el-button type="danger" link size="small" @click="handleDelete(item)" :disabled="item.isComplete">鍒犻櫎</el-button>
+ <!-- 浜у搧淇℃伅 -->
+ <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>
+ <el-tag type="primary"
+ class="product-tag"
+ v-if="item.isQuality">璐ㄦ</el-tag>
+ <el-tag type="primary"
+ class="product-tag"
+ :style="item.isQuality?'margin-left:8px':''"
+ v-if="item.isProduction">鐢熶骇</el-tag>
+ </div>
+ <div v-else
+ class="product-info empty">鏆傛棤浜у搧淇℃伅</div>
+ </div>
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div class="card-footer">
+ <el-button type="primary"
+ link
+ size="small"
+ @click="handleEdit(item)"
+ :disabled="item.isComplete || !editable">缂栬緫</el-button>
+ <el-button type="info"
+ link
+ size="small"
+ @click="handleViewParams(item)">鍙傛暟鍒楄〃</el-button>
+ <el-button type="danger"
+ link
+ size="small"
+ @click="handleDelete(item)"
+ :disabled="item.isComplete || !editable">鍒犻櫎</el-button>
+ </div>
</div>
</div>
- </div>
</div>
</template>
-
+ <!-- bom妯″潡 -->
+ <div class="section-header"
+ style="margin-top: 20px;">
+ <div class="section-title">BOM 缁撴瀯</div>
+ <div class="section-actions"
+ v-if="pageType === 'order' && editable">
+ <el-button v-if="!bomDataValue.isEdit"
+ type="primary"
+ @click="bomDataValue.isEdit = true">
+ 缂栬緫
+ </el-button>
+ <el-button v-if="bomDataValue.isEdit"
+ @click="cancelEditBom">
+ 鍙栨秷
+ </el-button>
+ <el-button v-if="bomDataValue.isEdit"
+ type="primary"
+ @click="handleSaveBom"
+ :loading="bomDataValue.loading">
+ 淇濆瓨BOM
+ </el-button>
+ </div>
+ </div>
+ <el-table :data="bomTableData"
+ border
+ :preserve-expanded-content="false"
+ :default-expand-all="true"
+ style="width: 100%">
+ <el-table-column type="expand">
+ <template #default>
+ <el-form ref="bomFormRef"
+ :model="bomDataValue">
+ <el-table :data="bomDataValue.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 }">
+ <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+ :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-select v-model="row.model"
+ placeholder="璇烽�夋嫨瑙勬牸"
+ clearable
+ :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)"
+ style="width: 100%"
+ @visible-change="(v) => { if (v) openBomDialog(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 }">
+ <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+ :rules="bomDataValue.dataList.some(item => (item).tempId === row.tempId) ? [] : [{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+ style="margin: 0">
+ <el-select v-model="row.processId"
+ placeholder="璇烽�夋嫨"
+ filterable
+ clearable
+ style="width: 100%"
+ @change="value => handleBomProcessChange(row, value)"
+ :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)">
+ <el-option v-for="item in bomDataValue.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 }">
+ <el-form-item v-if="pageType === 'order' && bomDataValue.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%"
+ @change="handleUnitQuantityChange(row)"
+ :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column v-if="pageType === 'order'"
+ prop="demandedQuantity"
+ label="闇�姹傛�婚噺">
+ <template #default="{ row }">
+ <el-form-item v-if="pageType === 'order' && bomDataValue.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="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unit"
+ label="鍗曚綅">
+ <template #default="{ row }">
+ <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input v-model="row.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable
+ :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ fixed="right"
+ width="200"
+ v-if="pageType === 'order' && bomDataValue.isEdit">
+ <template #default="{ row }">
+ <el-button v-if="bomDataValue.isEdit && !bomDataValue.dataList.some(item => (item).tempId === row.tempId)"
+ type="danger"
+ text
+ @click="removeBomItem(row.tempId)">鍒犻櫎
+ </el-button>
+ <el-button v-if="bomDataValue.isEdit"
+ type="primary"
+ text
+ @click="addBomItem(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>
+ <ProductSelectDialog v-if="bomDataValue.showProductDialog"
+ v-model="bomDataValue.showProductDialog"
+ :single="true"
+ @confirm="handleBomProduct" />
<!-- 鏂板/缂栬緫寮圭獥 -->
- <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-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="宸ュ簭"
+ v-if="operationType === 'add' || pageType === 'route'"
+ prop="technologyOperationId">
+ <el-select v-model="form.technologyOperationId"
+ placeholder="璇烽�夋嫨宸ュ簭"
+ clearable
+ @change="processChange"
+ 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-form-item label="宸ュ簭"
+ v-else>
+ <span>{{ getProcessName(form.technologyOperationId) }}</span>
+ </el-form-item>
+ <el-form-item label="浜у搧鍚嶇О"
+ v-if="operationType === 'add' || pageType === 'route'"
+ prop="productModelId">
+ <el-button type="primary"
+ @click="showProductSelectDialog = true">
+ {{ form.productName
+ ? (form.model ? `${form.productName} - ${form.model}` : form.productName)
: '閫夋嫨浜у搧' }}
</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 label="浜у搧鍚嶇О"
+ v-else>
+ <span>{{ form.productName }}{{ form.model ? ' - ' + form.model : '' }}</span>
</el-form-item>
-
- <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
- <el-switch v-model="form.isQuality" :active-value="true" inactive-value="false"/>
+ <el-form-item label="鍗曚綅"
+ v-if="operationType === 'add' || pageType === 'route'"
+ prop="unit">
+ <el-input v-model="form.unit"
+ :placeholder="form.productModelId ? '鏍规嵁閫夋嫨鐨勪骇鍝佽嚜鍔ㄥ甫鍑�' : '璇峰厛閫夋嫨浜у搧'"
+ clearable
+ :disabled="true" />
+ </el-form-item>
+ <el-form-item label="鍗曚綅"
+ v-else>
+ <span>{{ form.unit }}</span>
+ </el-form-item>
+ <el-form-item label="鏄惁璐ㄦ"
+ prop="isQuality">
+ <el-switch v-model="form.isQuality"
+ :active-value="true"
+ :inactive-value="false" />
+ </el-form-item>
+ <el-form-item label="鏄惁鐢熶骇"
+ prop="isProduction">
+ <el-switch v-model="form.isProduction"
+ :active-value="true"
+ :inactive-value="false" />
</el-form-item>
</el-form>
-
<template #footer>
- <el-button type="primary" @click="handleSubmit" :loading="submitLoading">纭畾</el-button>
+ <el-button type="primary"
+ @click="handleSubmit"
+ :loading="submitLoading">纭畾</el-button>
<el-button @click="closeDialog">鍙栨秷</el-button>
</template>
</el-dialog>
-
<!-- 浜у搧閫夋嫨瀵硅瘽妗� -->
- <ProductSelectDialog
- v-model="showProductSelectDialog"
- @confirm="handleProductSelect"
- single
- />
+ <ProductSelectDialog v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single />
+ <!-- 鍙傛暟鍒楄〃瀵硅瘽妗� -->
+ <ProcessParamListDialog v-model="showParamListDialog"
+ :title="`${currentProcess ? (currentProcess.processName || currentProcess.technologyOperationName || currentProcess.operationName) : ''} - 鍙傛暟鍒楄〃`"
+ :route-id="routeId"
+ :order-id="orderId"
+ :process="currentProcess"
+ :page-type="pageType"
+ :param-list="paramList"
+ :editable="editable"
+ @getsyncProcessParamItem="getsyncProcessParamItem"
+ @refresh="refreshParamList" />
</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'
+ import {
+ ref,
+ computed,
+ getCurrentInstance,
+ onMounted,
+ onUnmounted,
+ nextTick,
+ } from "vue";
+ import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import ProcessParamListDialog from "@/components/ProcessParamListDialog.vue";
+ import {
+ findProcessRouteItemList,
+ addOrUpdateProcessRouteItem,
+ addOrUpdateProcessRouteItem1,
+ sortProcessRouteItem,
+ batchDeleteProcessRouteItem,
+ getProcessParamList,
+ } from "@/api/productionManagement/processRouteItem.js";
+ import {
+ syncProcessParamItem,
+ syncProcessParamItemOrder,
+ } from "@/api/productionManagement/processRouteItem.js";
+ import {
+ findProductProcessRouteItemList,
+ deleteRouteItem,
+ addRouteItem,
+ findProcessParamListOrder,
+ addOrUpdateProductProcessRouteItem,
+ sortRouteItem,
+ } from "@/api/productionManagement/productProcessRoute.js";
+ import { processList } from "@/api/productionManagement/productionProcess.js";
+ import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
+ import {
+ queryList,
+ queryList2,
+ add2,
+ } from "@/api/productionManagement/productStructure.js";
-const route = useRoute()
-const { proxy } = getCurrentInstance() || {};
+ import { useRoute } from "vue-router";
+ import { ElMessageBox, ElMessage } from "element-plus";
+ import Sortable from "sortablejs";
-const routeId = computed(() => route.query.id);
-const orderId = computed(() => route.query.orderId);
-const pageType = computed(() => route.query.type);
+ const route = useRoute();
+ const { proxy } = getCurrentInstance() || {};
-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 routeId = computed(() => route.query.id);
+ const orderId = computed(() => route.query.orderId);
+ const pageType = computed(() => route.query.type);
+ const editable = computed(() => route.query.editable !== "false");
-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 tableLoading = ref(false);
+ const tableData = ref([]);
+ const dialogVisible = ref(false);
+ const operationType = ref("add"); // add | edit
+ const formRef = ref(null);
+ const bomFormRef = ref(null);
+ const submitLoading = ref(false);
+ const cardsContainer = ref(null);
+ const tableRef = ref(null);
+ const viewMode = ref("card"); // table | card
+ const routeInfo = ref({
+ processRouteCode: "",
+ productName: "",
+ model: "",
+ bomNo: "",
+ description: "",
+ quantity: 0,
});
-};
-const form = ref({
- id: undefined,
- routeId: routeId.value,
- processId: undefined,
- productModelId: undefined,
- productName: "",
- model: "",
- unit: "",
- isQuality: false,
-});
+ const processOptions = ref([]);
+ const showProductSelectDialog = ref(false);
+ const showParamListDialog = ref(false);
+ const currentProcess = ref(null);
+ const paramList = ref([]);
+ let tableSortable = null;
+ let cardSortable = null;
-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 toggleView = () => {
+ viewMode.value = viewMode.value === "table" ? "card" : "table";
+ // 鍒囨崲瑙嗗浘鍚庨噸鏂板垵濮嬪寲鎷栨嫿鎺掑簭
+ nextTick(() => {
+ initSortable();
});
-};
-
-// 鑾峰彇宸ュ簭鍒楄〃
-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 || "",
- isQuality: row.isQuality,
- };
- 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,
- isQuality: form.value.isQuality,
- dragSort,
- })
- : addOrUpdateProcessRouteItem({
- routeId: routeId.value,
- processId: form.value.processId,
- productModelId: form.value.productModelId,
- isQuality: form.value.isQuality,
- 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,
- isQuality: form.value.isQuality,
- })
- : addOrUpdateProcessRouteItem({
- routeId: routeId.value,
- processId: form.value.processId,
- productModelId: form.value.productModelId,
- id: form.value.id,
- isQuality: form.value.isQuality,
- });
-
- updatePromise
- .then(() => {
- proxy?.$modal?.msgSuccess('淇敼鎴愬姛');
- closeDialog();
- getList();
- })
- .catch(() => {
- proxy?.$modal?.msgError('淇敼澶辫触');
- })
- .finally(() => {
- submitLoading.value = false;
- });
- }
- }
- });
-};
-
-// 閲嶇疆琛ㄥ崟
-const resetForm = () => {
- form.value = {
+ const form = ref({
id: undefined,
routeId: routeId.value,
- processId: undefined,
+ technologyOperationId: undefined,
productModelId: undefined,
productName: "",
model: "",
unit: "",
+ isQuality: false,
+ isProduction: false,
+ });
+
+ const rules = {
+ technologyOperationId: [
+ { required: true, message: "璇烽�夋嫨宸ュ簭", trigger: "change" },
+ ],
+ productModelId: [
+ { required: true, message: "璇烽�夋嫨浜у搧", trigger: "change" },
+ ],
};
- formRef.value?.resetFields();
-};
-// 鍏抽棴寮圭獥
-const closeDialog = () => {
- dialogVisible.value = false;
- resetForm();
-};
+ const getsyncProcessParamItem = () => {
+ ElMessageBox.confirm("鏄惁瑕嗙洊褰撳墠宸ュ簭宸插瓨鍦ㄥ弬鏁帮紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ if (pageType.value === "order") {
+ syncProcessParamItemOrder({
+ replaceExisting: true,
+ technologyRoutingOperationId: currentProcess.value.id,
+ }).then(res => {
+ if (res.code === 200) {
+ ElMessage.success("鍚屾鎴愬姛");
+ refreshParamList();
+ } else {
+ ElMessage.error(res.msg || "鍚屾澶辫触");
+ }
+ });
+ } else {
+ syncProcessParamItem({
+ replaceExisting: true,
+ technologyRoutingOperationId: currentProcess.value.id,
+ }).then(res => {
+ if (res.code === 200) {
+ ElMessage.success("鍚屾鎴愬姛");
+ refreshParamList();
+ } else {
+ ElMessage.error(res.msg || "鍚屾澶辫触");
+ }
+ });
+ }
+ })
+ .catch(() => {});
+ };
-// 鍒濆鍖栨嫋鎷芥帓搴�
-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;
+ // 鏍规嵁宸ュ簭ID鑾峰彇宸ュ簭鍚嶇О
+ const getProcessName = technologyOperationId => {
+ if (!technologyOperationId) return "";
+ const process = processOptions.value.find(
+ p => p.id === technologyOperationId
+ );
+ return process ? process.name : "";
+ };
- 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 getList = () => {
+ tableLoading.value = true;
+ const listPromise =
+ pageType.value === "order"
+ ? findProductProcessRouteItemList({ orderId: orderId.value })
+ : findProcessRouteItemList({ routeId: routeId.value });
- // 閲嶆柊鎺掑簭鏁扮粍
- 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
+ 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({ size: -1, current: -1 })
+ .then(res => {
+ processOptions.value = res.data.records || [];
+ bomDataValue.value.processOptions = processOptions.value;
+ })
+ .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 || "",
+ bomId: route.query.bomId || "",
+ description: route.query.description || "",
+ quantity: route.query.quantity || 0,
+ status: !(route.query.status == 1 || route.query.status === "false"),
+ };
+ bomTableData.value[0].productName = routeInfo.value.productName;
+ bomTableData.value[0].model = routeInfo.value.model;
+ bomTableData.value[0].bomNo = routeInfo.value.bomNo;
+ };
+
+ // 鏂板
+ const handleAdd = () => {
+ operationType.value = "add";
+ resetForm();
+ dialogVisible.value = true;
+ };
+
+ // 缂栬緫
+ const handleEdit = row => {
+ operationType.value = "edit";
+ form.value = {
+ id: row.id,
+ routeId: routeId.value,
+ technologyOperationId: row.technologyOperationId,
+ productModelId: row.productModelId,
+ productName: row.productName || "",
+ model: row.model || "",
+ unit: row.unit || "",
+ isQuality: row.isQuality,
+ isProduction: row.isProduction,
+ };
+ 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 => {
+ console.log(products, "===products===");
+ if (products && products.length > 0) {
+ const product = products[0];
+ console.log(product, "product");
+ form.value = {
+ ...form.value,
+ productModelId: product.id,
+ productName: product.productName,
+ model: product.model,
+ 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({
+ productionOrderId: Number(orderId.value),
+ orderRoutingId: Number(routeId.value),
+ technologyOperationId: form.value.technologyOperationId,
+ technologyRoutingId: Number(routeId.value),
+ operationName: getProcessName(form.value.technologyOperationId),
+ productModelId: form.value.productModelId,
+ isQuality: form.value.isQuality,
+ isProduction: form.value.isProduction,
+ dragSort,
})
- : sortProcessRouteItem({
- id: moveItem.id,
- dragSort: dragSort
+ : addOrUpdateProcessRouteItem({
+ technologyRoutingId: Number(routeId.value),
+ technologyOperationId: form.value.technologyOperationId,
+ productModelId: form.value.productModelId,
+ isQuality: form.value.isQuality,
+ isProduction: form.value.isProduction,
+ dragSort,
});
- sortPromise
+ addPromise
.then(() => {
- // 鏇存柊鎵�鏈夎鐨刣ragSort
- tableData.value.forEach((item, index) => {
- if (item.id) {
- item.dragSort = index + 1;
- }
- });
- proxy?.$modal?.msgSuccess('鎺掑簭鎴愬姛');
+ proxy?.$modal?.msgSuccess("鏂板鎴愬姛");
+ closeDialog();
+ getList();
})
- .catch((err) => {
- // 鎺掑簭澶辫触锛屾仮澶嶅師鏁扮粍
- tableData.value.splice(newIndex, 1);
- tableData.value.splice(evt.oldIndex, 0, moveItem);
- proxy?.$modal?.msgError('鎺掑簭澶辫触');
- console.error("鎺掑簭澶辫触锛�", err);
+ .catch(() => {
+ proxy?.$modal?.msgError("鏂板澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+ } else {
+ // 缂栬緫锛氱敓浜ц鍗曚笅浣跨敤 productProcessRoute/updateRouteItem锛屽叾瀹冩儏鍐典娇鐢ㄥ伐鑹鸿矾绾块」鐩洿鏂版帴鍙�
+ const isOrderPage = pageType.value === "order";
+
+ const updatePromise = isOrderPage
+ ? addOrUpdateProductProcessRouteItem({
+ id: form.value.id,
+ technologyOperationId: form.value.technologyOperationId,
+ operationName: getProcessName(form.value.technologyOperationId),
+ productModelId: form.value.productModelId,
+ isQuality: form.value.isQuality,
+ isProduction: form.value.isProduction,
+ })
+ : addOrUpdateProcessRouteItem1({
+ technologyRoutingId: Number(routeId.value),
+ technologyOperationId: form.value.technologyOperationId,
+ productModelId: form.value.productModelId,
+ id: form.value.id,
+ isQuality: form.value.isQuality,
+ isProduction: form.value.isProduction,
+ });
+
+ updatePromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess("淇敼鎴愬姛");
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy?.$modal?.msgError("淇敼澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
});
}
}
});
- } 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 resetForm = () => {
+ form.value = {
+ id: undefined,
+ routeId: routeId.value,
+ technologyOperationId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+ isQuality: false,
+ isProduction: false,
+ };
+ formRef.value?.resetFields();
+ };
- // 閲嶆柊鎺掑簭鏁扮粍
- 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
+ // 鍏抽棴寮圭獥
+ const closeDialog = () => {
+ dialogVisible.value = false;
+ resetForm();
+ };
+
+ // 鏌ョ湅鍙傛暟鍒楄〃
+ const handleViewParams = row => {
+ currentProcess.value = row;
+ const param = {
+ productionOrderRoutingOperationId: row.id,
+ productionOrderId: orderId.value,
+ };
+ const param1 = {
+ technologyRoutingOperationId: row.id,
+ productionOrderId: orderId.value,
+ };
+
+ const apiPromise =
+ pageType.value === "order"
+ ? findProcessParamListOrder(param)
+ : getProcessParamList(param1);
+
+ apiPromise
+ .then(res => {
+ paramList.value = res.data || [];
+ showParamListDialog.value = true;
+ })
+ .catch(err => {
+ console.error("鑾峰彇鍙傛暟鍒楄〃澶辫触锛�", err);
+ proxy?.$modal?.msgError("鑾峰彇鍙傛暟鍒楄〃澶辫触");
+ });
+ };
+
+ // 鍒锋柊鍙傛暟鍒楄〃
+ const refreshParamList = () => {
+ if (currentProcess.value) {
+ handleViewParams(currentProcess.value);
+ }
+ };
+
+ // 鍒濆鍖栨嫋鎷芥帓搴�
+ const initSortable = () => {
+ destroySortable();
+ if (!editable.value) return;
+
+ 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("鎺掑簭鎴愬姛");
})
- : sortProcessRouteItem({
- id: moveItem.id,
- dragSort: dragSort
+ .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;
- sortPromise
- .then(() => {
- // 鏇存柊鎵�鏈夎鐨刣ragSort
- tableData.value.forEach((item, index) => {
- if (item.id) {
- item.dragSort = index + 1;
- }
+ 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);
});
- 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;
+ }
+ };
+
+ // BOM鐩稿叧鐘舵�佸拰鏂规硶
+ const bomTableData = ref([
+ {
+ productName: "",
+ model: "",
+ bomNo: "",
+ },
+ ]);
+
+ const bomDataValue = ref({
+ dataList: [],
+ processOptions: [],
+ showProductDialog: false,
+ currentRowName: null,
+ loading: false,
+ isEdit: false,
+ });
+
+ const syncProcessOperationFields = item => {
+ const processId =
+ item.processId ?? item.operationId ?? item.technologyOperationId ?? "";
+ if (!processId) {
+ item.processId = "";
+ return;
+ }
+ const option = bomDataValue.value.processOptions.find(
+ p => p.id === processId
+ );
+ const processName =
+ option?.name || item.processName || item.operationName || "";
+
+ item.processId = processId;
+ if (pageType.value === "order") {
+ item.technologyOperationId = processId;
+ } else {
+ item.operationId = processId;
+ }
+ item.processName = processName;
+ item.operationName = processName;
+ };
+
+ const normalizeTreeData = items => {
+ items.forEach(item => {
+ item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`;
+ syncProcessOperationFields(item);
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ normalizeTreeData(item.children);
}
});
- }
-};
+ };
+ const processChange = value => {
+ processOptions.value.forEach(item => {
+ if (item.id == value) {
+ form.value.isQuality = item.isQuality;
+ form.value.isProduction = item.isProduction;
+ }
+ });
+ };
-// 閿�姣佹嫋鎷芥帓搴�
-const destroySortable = () => {
- if (tableSortable) {
- tableSortable.destroy();
- tableSortable = null;
- }
- if (cardSortable) {
- cardSortable.destroy();
- cardSortable = null;
- }
-};
+ const handleBomProcessChange = (row, value) => {
+ row.processId = value || "";
+ syncProcessOperationFields(row);
+ };
-onMounted(() => {
- getRouteInfo();
- getList();
- getProcessList();
-});
+ const openBomDialog = tempId => {
+ bomDataValue.value.currentRowName = tempId;
+ bomDataValue.value.showProductDialog = true;
+ };
-onUnmounted(() => {
- destroySortable();
-});
+ const fetchBomData = async () => {
+ try {
+ const isOrderPage = pageType.value === "order";
+ const { data } = await (isOrderPage ? queryList2 : queryList)(
+ routeInfo.value.bomId
+ );
+ bomDataValue.value.dataList = data || [];
+ normalizeTreeData(bomDataValue.value.dataList);
+ } catch (err) {
+ console.error("鑾峰彇BOM鏁版嵁澶辫触锛�", err);
+ }
+ };
+
+ const childItem = (item, tempId, productData) => {
+ 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 handleBomProduct = row => {
+ if (!Array.isArray(row) || row.length === 0) {
+ ElMessage.warning("璇烽�夋嫨涓�涓骇鍝�");
+ return;
+ }
+ const productData = row[row.length - 1];
+
+ const isTopLevel = bomDataValue.value.dataList.some(
+ item => item.tempId === bomDataValue.value.currentRowName
+ );
+ if (isTopLevel) {
+ if (
+ productData.productName === bomTableData.value[0].productName &&
+ productData.model === bomTableData.value[0].model
+ ) {
+ const hasOther = bomDataValue.value.dataList.some(
+ item =>
+ item.tempId !== bomDataValue.value.currentRowName &&
+ item.productName === bomTableData.value[0].productName &&
+ item.model === bomTableData.value[0].model
+ );
+ if (hasOther) {
+ ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
+ return;
+ }
+ }
+ }
+ bomDataValue.value.dataList.forEach(item => {
+ if (item.tempId === bomDataValue.value.currentRowName) {
+ item.productName = productData.productName;
+ item.model = productData.model;
+ item.productModelId = productData.id;
+ item.unit = productData.unit || "";
+ return;
+ }
+ childItem(item, bomDataValue.value.currentRowName, productData);
+ });
+ bomDataValue.value.showProductDialog = false;
+ };
+
+ const removeBomItem = tempId => {
+ const topIndex = bomDataValue.value.dataList.findIndex(
+ item => item.tempId === tempId
+ );
+ if (topIndex !== -1) {
+ bomDataValue.value.dataList.splice(topIndex, 1);
+ return;
+ }
+
+ const delchildItem = (items, tempId) => {
+ 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;
+ };
+
+ bomDataValue.value.dataList.forEach(item => {
+ if (item.children && item.children.length > 0) {
+ delchildItem(item.children, tempId);
+ }
+ });
+ };
+
+ const handleUnitQuantityChange = row => {
+ if (routeInfo.value.quantity && routeInfo.value.quantity !== 0) {
+ row.demandedQuantity = (row.unitQuantity || 0) * routeInfo.value.quantity;
+ }
+ };
+
+ const addchildItem = (item, tempId) => {
+ 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: "",
+ [pageType.value === "order" ? "technologyOperationId" : "operationId"]:
+ "",
+ operationName: "",
+ unitQuantity: 1,
+ demandedQuantity:
+ routeInfo.value.quantity && routeInfo.value.quantity !== 0
+ ? 1 * routeInfo.value.quantity
+ : 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 addBomItem = tempId => {
+ bomDataValue.value.dataList.forEach(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: "",
+ [pageType.value === "order" ? "technologyOperationId" : "operationId"]:
+ "",
+ operationName: "",
+ unitQuantity: 1,
+ demandedQuantity:
+ routeInfo.value.quantity && routeInfo.value.quantity !== 0
+ ? 1 * routeInfo.value.quantity
+ : 0,
+ unit: "",
+ children: [],
+ tempId: new Date().getTime(),
+ });
+ return;
+ }
+ addchildItem(item, tempId);
+ });
+ };
+
+ const validateAllBom = () => {
+ let isValid = true;
+ const isOrderPage = pageType.value === "order";
+
+ const validateItem = (item, 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 && !item.demandedQuantity) {
+ ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
+ isValid = false;
+ return;
+ }
+
+ if (item.children && item.children.length > 0) {
+ item.children.forEach(child => {
+ validateItem(child, false);
+ });
+ }
+ };
+
+ bomDataValue.value.dataList.forEach(item => {
+ validateItem(item, true);
+ });
+
+ return isValid;
+ };
+
+ const buildSubmitTree = items => {
+ return items.map(item => {
+ const current = { ...item };
+ syncProcessOperationFields(current);
+ current.children = Array.isArray(current.children)
+ ? buildSubmitTree(current.children)
+ : [];
+ return current;
+ });
+ };
+
+ const cancelEditBom = () => {
+ bomDataValue.value.isEdit = false;
+ fetchBomData();
+ };
+
+ const handleSaveBom = () => {
+ bomDataValue.value.loading = true;
+ console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList");
+
+ normalizeTreeData(bomDataValue.value.dataList);
+
+ const valid = validateAllBom();
+ if (valid) {
+ add2({
+ // bomId: Number(routeInfo.value.bomId),
+ productionOrderBomId: Number(routeInfo.value.bomId) || null,
+ children: buildSubmitTree(bomDataValue.value.dataList || []),
+ })
+ .then(() => {
+ ElMessage.success("BOM淇濆瓨鎴愬姛");
+ bomDataValue.value.isEdit = false;
+ fetchBomData();
+ })
+ .catch(() => {
+ ElMessage.error("BOM淇濆瓨澶辫触");
+ })
+ .finally(() => {
+ bomDataValue.value.loading = false;
+ });
+ } else {
+ bomDataValue.value.loading = false;
+ }
+ };
+
+ onMounted(() => {
+ getRouteInfo();
+ getList();
+ getProcessList();
+ fetchBomData();
+ });
+
+ onUnmounted(() => {
+ destroySortable();
+ });
</script>
<style scoped>
-.card-container {
- padding: 20px 0;
-}
+ .card-container {
+ padding: 20px 0;
+ }
-.cards-wrapper {
- display: flex;
- gap: 16px;
- overflow-x: auto;
- padding: 10px 0;
- min-height: 200px;
-}
+ .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 {
+ height: 8px;
+ }
-.cards-wrapper::-webkit-scrollbar-track {
- background: #f1f1f1;
- border-radius: 4px;
-}
+ .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 {
+ background: #c1c1c1;
+ border-radius: 4px;
+ }
-.cards-wrapper::-webkit-scrollbar-thumb:hover {
- background: #a8a8a8;
-}
+ .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 {
+ 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);
-}
+ .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-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-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-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;
-}
+ .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 {
+ font-size: 13px;
+ color: #666;
+ text-align: center;
+ width: 100%;
+ }
-.product-info.empty {
- color: #999;
- text-align: center;
- padding: 20px 0;
-}
+ .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-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-model {
+ color: #909399;
+ font-size: 12px;
+ word-break: break-all;
+ line-height: 1.5;
+ text-align: center;
+ }
-.product-unit {
- margin-left: 4px;
- color: #409eff;
-}
+ .product-unit {
+ margin-left: 4px;
+ color: #409eff;
+ }
-.product-tag {
- margin: 10px 0;
-}
+ .product-tag {
+ margin: 10px 0;
+ }
-.card-footer {
- display: flex;
- justify-content: space-around;
- padding-top: 12px;
- border-top: 1px solid #f0f0f0;
-}
+ .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;
-}
+ .card-footer .el-button {
+ padding: 0;
+ font-size: 12px;
+ }
-:deep(.sortable-ghost) {
- opacity: 0.5;
- background-color: #f5f7fa !important;
-}
+ :deep(.sortable-ghost) {
+ opacity: 0.5;
+ background-color: #f5f7fa !important;
+ }
-:deep(.sortable-drag) {
- opacity: 0.8;
-}
+ :deep(.sortable-drag) {
+ opacity: 0.8;
+ }
-/* 琛ㄦ牸瑙嗗浘鏍峰紡 */
-:deep(.el-table__row) {
- transition: background-color 0.2s;
- cursor: move;
-}
+ /* 琛ㄦ牸瑙嗗浘鏍峰紡 */
+ :deep(.el-table__row) {
+ transition: background-color 0.2s;
+ cursor: move;
+ }
-:deep(.el-table__row:hover) {
- background-color: #f9fafc !important;
-}
+ :deep(.el-table__row:hover) {
+ background-color: #f9fafc !important;
+ }
-/* 鍖哄煙鏍囬鏍峰紡 */
-.section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
-}
+ /* 鍖哄煙鏍囬鏍峰紡 */
+ .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 {
+ 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-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;
-}
+ .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-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;
-}
+ .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 {
+ 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: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-item.full-width {
+ grid-column: 1 / -1;
+ }
-.info-label-wrapper {
- margin-bottom: 8px;
-}
+ .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 {
+ 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-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-wrapper {
+ flex: 1;
+ }
-.info-value {
- display: block;
- color: #303133;
- font-size: 15px;
- font-weight: 500;
- line-height: 1.5;
- word-break: break-all;
-}
+ .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/processStatistics/index.vue b/src/views/productionManagement/processStatistics/index.vue
new file mode 100644
index 0000000..25fa531
--- /dev/null
+++ b/src/views/productionManagement/processStatistics/index.vue
@@ -0,0 +1,268 @@
+<template>
+ <div class="app-container">
+ <div class="search-bar">
+ <el-form :model="searchForm"
+ inline>
+ <el-form-item label="鏃ユ湡鍖洪棿:">
+ <el-date-picker v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 240px" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ icon="Search"
+ @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="stats-grid"
+ v-loading="loading">
+ <el-row :gutter="16"
+ v-if="statsData.length > 0">
+ <el-col v-for="(item, index) in statsData"
+ :key="index"
+ :xs="24"
+ :sm="12"
+ :md="8"
+ :lg="4.8"
+ :xl="4.8"
+ class="mb-16">
+ <div class="stats-card">
+ <div class="card-header">
+ <span class="process-name">{{ item.name }}</span>
+ <div class="header-stats">
+ <div class="stat-row">
+ <span class="label">璁″垝鏁�</span>
+ <span class="value">{{ item.planned }}</span>
+ </div>
+ <div class="stat-row">
+ <span class="label">鑹搧鏁�</span>
+ <span class="value">{{ item.good }}</span>
+ </div>
+ <div class="stat-row">
+ <span class="label">涓嶈壇鍝佹暟</span>
+ <span class="value">{{ item.bad }}</span>
+ </div>
+ </div>
+ </div>
+ <div class="card-body">
+ <div class="main-stat">
+ <div class="big-number">{{ item.total }}</div>
+ <div class="sub-label">鐢熶骇浠诲姟鏁�</div>
+ </div>
+ </div>
+ <div class="card-footer">
+ <div class="progress-info">
+ <span class="progress-label">杩涘害:</span>
+ <el-progress :percentage="Math.min(item.percentage, 100)"
+ :color="getProgressColor(item.percentage)"
+ :stroke-width="10"
+ :show-text="false"
+ class="flex-1" />
+ <span class="percentage-text">{{ item.percentage }}%</span>
+ </div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+ <el-empty v-else
+ description="鏆傛棤鏁版嵁" />
+ </div>
+ </div>
+</template>
+
+<script setup>
+ import { reactive, ref, onMounted } from "vue";
+ import dayjs from "dayjs";
+ import { getOperationStatistics } from "@/api/productionManagement/workOrder.js";
+
+ const loading = ref(false);
+ const searchForm = reactive({
+ dateRange: [],
+ });
+
+ const statsData = ref([]);
+
+ const getProgressColor = percentage => {
+ if (percentage >= 100) return "#67c23a";
+ if (percentage >= 50) return "#409eff";
+ if (percentage >= 25) return "#e6a23c";
+ return "red";
+ };
+
+ const getList = () => {
+ loading.value = true;
+ const params = {
+ startDate: searchForm.dateRange?.[0] || "",
+ endDate: searchForm.dateRange?.[1] || "",
+ };
+ getOperationStatistics(params)
+ .then(res => {
+ // 鏍规嵁瀹為檯鎺ュ彛杩斿洖鐨勫瓧娈佃繘琛屾槧灏�
+ statsData.value = (res.data || []).map(item => ({
+ name: item.operationName || "-",
+ total: item.productionTaskCount || 0,
+ planned: item.planQuantity || 0,
+ good: item.goodQuantity || 0,
+ bad: item.scrapQty || 0,
+ percentage: Number(item.completionStatus || 0),
+ }));
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+ };
+
+ const handleQuery = () => {
+ getList();
+ };
+
+ onMounted(() => {
+ getList();
+ });
+</script>
+
+<style scoped lang="scss">
+ .app-container {
+ padding: 20px;
+ background-color: #f0f2f5;
+ min-height: calc(100vh - 84px);
+ }
+
+ .search-bar {
+ background: #fff;
+ padding: 15px 20px 0;
+ border-radius: 4px;
+ margin-bottom: 20px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+ }
+
+ .mb-16 {
+ margin-bottom: 16px;
+ }
+
+ // 妯℃嫙 lg="4.8" 鍥犱负 element 涓嶆敮鎸� 24/5
+ @media only screen and (min-width: 1200px) {
+ .el-col-lg-4-8 {
+ width: 20%;
+ max-width: 20%;
+ flex: 0 0 20%;
+ }
+ }
+
+ .stats-card {
+ background: #fff;
+ border-radius: 8px;
+ padding: 16px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+ transition: transform 0.3s;
+
+ &:hover {
+ transform: translateY(-2px);
+ }
+
+ .card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 12px;
+
+ .process-name {
+ background-color: #e6f7ff;
+ color: #1890ff;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 14px;
+ font-weight: 500;
+ }
+
+ .header-stats {
+ text-align: right;
+
+ .stat-row {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 2px;
+
+ .label {
+ font-size: 12px;
+ color: #909399;
+ }
+
+ .value {
+ font-size: 13px;
+ color: #303133;
+ font-weight: bold;
+ min-width: 24px;
+ }
+ }
+ }
+ }
+
+ .card-body {
+ padding: 10px 0;
+
+ .main-stat {
+ .big-number {
+ font-size: 28px;
+ font-weight: bold;
+ color: #303133;
+ line-height: 1;
+ }
+
+ .sub-label {
+ font-size: 14px;
+ color: #606266;
+ margin-top: 8px;
+ font-weight: 500;
+ }
+ }
+ }
+
+ .card-footer {
+ margin-top: 16px;
+
+ .progress-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .progress-label {
+ font-size: 12px;
+ color: #909399;
+ white-space: nowrap;
+ }
+
+ .flex-1 {
+ flex: 1;
+ }
+
+ .percentage-text {
+ font-size: 12px;
+ color: #606266;
+ min-width: 45px;
+ text-align: right;
+ }
+ }
+ }
+ }
+
+ // 淇 el-col 甯冨眬閫傞厤 5 鍒�
+ :deep(.el-row) {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ @media only screen and (min-width: 1200px) {
+ .el-col-lg-4\.8 {
+ flex: 0 0 20%;
+ max-width: 20%;
+ }
+ }
+</style>
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
index 6734830..750d584 100644
--- a/src/views/productionManagement/productStructure/Detail/index.vue
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -64,6 +64,7 @@
filterable
clearable
style="width: 100%"
+ @change="value => handleProcessChange(row, value)"
:disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
<el-option v-for="item in dataValue.processOptions"
:key="item.id"
@@ -148,6 +149,7 @@
</el-table>
<product-select-dialog v-if="dataValue.showProductDialog"
v-model:model-value="dataValue.showProductDialog"
+ :single="true"
@confirm="handleProduct" />
</div>
</template>
@@ -161,7 +163,10 @@
reactive,
ref,
} from "vue";
- import { queryList, add } from "@/api/productionManagement/productStructure.js";
+ import {
+ queryList,
+ addBomDetail,
+ } from "@/api/productionManagement/productStructure.js";
import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
import { list } from "@/api/productionManagement/productionProcess";
import { ElMessage } from "element-plus";
@@ -212,6 +217,73 @@
isEdit: false,
});
+ const normalizeListData = (source: any) => {
+ if (Array.isArray(source)) {
+ return source;
+ }
+ if (Array.isArray(source?.records)) {
+ return source.records;
+ }
+ return [];
+ };
+
+ const getProcessOptionById = (id: any) => {
+ if (id === undefined || id === null || id === "") {
+ return null;
+ }
+ return (
+ normalizeListData(dataValue.processOptions).find(
+ option => String(option.id) === String(id)
+ ) || null
+ );
+ };
+
+ const syncProcessOperationFields = (item: any) => {
+ const processId = item.processId ?? item.operationId ?? "";
+ if (!processId) {
+ item.processId = "";
+ item.operationId = "";
+ item.processName = "";
+ item.operationName = "";
+ return;
+ }
+
+ const option = getProcessOptionById(processId);
+ const processName =
+ option?.name || item.processName || item.operationName || "";
+
+ item.processId = processId;
+ item.operationId = processId;
+ item.processName = processName;
+ item.operationName = processName;
+ };
+
+ const normalizeTreeData = (items: any[]) => {
+ items.forEach((item: any) => {
+ item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`;
+ syncProcessOperationFields(item);
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ normalizeTreeData(item.children);
+ }
+ });
+ };
+
+ const buildSubmitTree = (items: any[]) => {
+ return items.map((item: any) => {
+ const current = { ...item };
+ syncProcessOperationFields(current);
+ current.children = Array.isArray(current.children)
+ ? buildSubmitTree(current.children)
+ : [];
+ return current;
+ });
+ };
+
+ const handleProcessChange = (row: any, value: any) => {
+ row.processId = value || "";
+ syncProcessOperationFields(row);
+ };
+
const tableData = reactive([
{
productName: "",
@@ -231,37 +303,30 @@
// 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
const { data } = await listProcessBom({ orderId: routeOrderId.value });
dataValue.dataList = (data as any) || [];
+ normalizeTreeData(dataValue.dataList);
} 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);
+ normalizeTreeData(dataValue.dataList);
console.log(dataValue.dataList, "dataValue.dataList");
}
};
const fetchProcessOptions = async () => {
- const { data } = await list();
- dataValue.processOptions = data as any;
+ const { data } = await list({});
+ console.log(data, "dataValue.dataList");
+ dataValue.processOptions = normalizeListData(data);
};
const handleProduct = (row: any) => {
- if (row?.length > 1) {
- ElMessage.error("鍙兘閫夋嫨涓�涓骇鍝�");
+ if (!Array.isArray(row) || row.length === 0) {
+ ElMessage.warning("璇烽�夋嫨涓�涓骇鍝�");
+ return;
}
- const productData = row[0];
+ // 鍙厑璁镐竴涓細濡傛灉涓婃父杩斿洖浜嗗涓紝榛樿浣跨敤鏈�鍚庝竴娆¢�夋嫨骞惰鐩栧綋鍓嶅��
+ const productData = row[row.length - 1];
// 鏈�澶栧眰缁勪欢涓紝涓庡綋鍓嶄骇鍝佺浉鍚岀殑浜у搧鍙兘鏈変竴涓�
const isTopLevel = dataValue.dataList.some(
@@ -371,19 +436,18 @@
const submit = () => {
dataValue.loading = true;
+ normalizeTreeData(dataValue.dataList);
// 鍏堣繘琛岃〃鍗曟牎楠�
const valid = validateAll();
console.log(dataValue.dataList, "dataValue.dataList");
if (valid) {
- add({
+ addBomDetail({
bomId: routeId.value,
- children: dataValue.dataList || [],
+ children: buildSubmitTree(dataValue.dataList || []),
})
.then(res => {
- router.push({
- path: "/productionManagement/productionManagement/productStructure/index",
- });
+ router.go(-1);
ElMessage.success("淇濆瓨鎴愬姛");
dataValue.loading = false;
})
@@ -441,7 +505,9 @@
productModelId: undefined,
processId: "",
processName: "",
- unitQuantity: 0,
+ operationId: "",
+ operationName: "",
+ unitQuantity: 1,
demandedQuantity: 0,
unit: "",
children: [],
@@ -467,7 +533,10 @@
model: undefined,
productModelId: undefined,
processId: "",
- unitQuantity: 0,
+ processName: "",
+ operationId: "",
+ operationName: "",
+ unitQuantity: 1,
demandedQuantity: 0,
children: [],
unit: "",
diff --git a/src/views/productionManagement/productStructure/index.vue b/src/views/productionManagement/productStructure/index.vue
index 2c109cd..e05ed3c 100644
--- a/src/views/productionManagement/productStructure/index.vue
+++ b/src/views/productionManagement/productStructure/index.vue
@@ -1,380 +1,514 @@
<template>
<div class="app-container">
- <div style="text-align: right; margin-bottom: 10px;">
- <el-button type="info" plain icon="Upload" @click="handleImport"
- v-hasPermi="['product:bom:import']">瀵煎叆</el-button>
- <el-button type="warning" plain icon="Download" @click="handleExport" :disabled="selectedRows.length !== 1"
- v-hasPermi="['product:bom:export']">瀵煎嚭</el-button>
- <el-button type="primary" @click="handleAdd">鏂板</el-button>
- <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">鍒犻櫎</el-button>
+ <div class="table_list">
+ <div style="text-align: right; margin-bottom: 10px;">
+ <el-button type="primary"
+ @click="handleAdd">鏂板</el-button>
+ <el-button type="info"
+ plain
+ icon="Upload"
+ @click="handleImport"
+ v-hasPermi="['product:bom:import']">瀵煎叆</el-button>
+ <el-button type="warning"
+ plain
+ icon="Download"
+ @click="handleExport"
+ :disabled="selectedRows.length !== 1"
+ v-hasPermi="['product:bom:export']">瀵煎嚭</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>
</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" />
-
+ <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">
+ <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 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 label="澶囨敞"
+ prop="remark">
+ <el-input v-model="form.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉�"
+ clearable />
</el-form-item>
</el-form>
<template #footer>
- <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ <el-button type="primary"
+ @click="handleSubmit">纭畾</el-button>
<el-button @click="closeDialog">鍙栨秷</el-button>
</template>
</el-dialog>
-
<!-- 浜у搧閫夋嫨寮圭獥 -->
- <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single />
-
+ <ProductSelectDialog v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single />
<!-- BOM瀵煎叆瀵硅瘽妗� -->
- <ImportDialog ref="uploadRef" v-model="upload.open" :title="upload.title" :action="upload.url"
- :headers="upload.headers" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress"
- :on-success="handleFileSuccess" :show-download-template="true" @confirm="submitFileForm"
- @download-template="handleDownloadTemplate" @close="handleImportClose" />
+ <ImportDialog ref="uploadRef"
+ v-model="upload.open"
+ :title="upload.title"
+ :action="upload.url"
+ :headers="upload.headers"
+ :disabled="upload.isUploading"
+ :on-progress="handleFileUploadProgress"
+ :on-success="handleFileSuccess"
+ :show-download-template="true"
+ @confirm="submitFileForm"
+ @download-template="handleDownloadTemplate"
+ @close="handleImportClose" />
</div>
</template>
<script setup>
-import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
-import { getToken } from "@/utils/auth";
-import { listPage, add, update, batchDelete, exportBom, downloadTemplate } from "@/api/productionManagement/productBom.js";
-import { useRouter } from 'vue-router'
-import { ElMessageBox } from 'element-plus'
-import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
-import ImportDialog from "@/components/Dialog/ImportDialog.vue";
+ import {
+ ref,
+ reactive,
+ toRefs,
+ onMounted,
+ getCurrentInstance,
+ defineAsyncComponent,
+ } from "vue";
+ import { getToken } from "@/utils/auth";
+ import {
+ listPage,
+ add,
+ copy,
+ update,
+ batchDelete,
+ exportBom,
+ downloadTemplate,
+ } from "@/api/productionManagement/productBom.js";
+ import { useRouter } from "vue-router";
+ import { ElMessageBox } from "element-plus";
+ import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import ImportDialog from "@/components/Dialog/ImportDialog.vue";
-const router = useRouter()
-const { proxy } = getCurrentInstance()
-const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.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",
+ 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)
- }
- }
- ]
- }
-]);
+ 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: 250,
+ operation: [
+ {
+ name: "澶嶅埗",
+ type: "text",
+ clickFun: row => {
+ handleCopy(row);
+ },
+ },
+ {
+ 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);
+ 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: ""
+ // BOM瀵煎叆鍙傛暟
+ const upload = reactive({
+ // 鏄惁鏄剧ず寮瑰嚭灞傦紙BOM瀵煎叆锛�
+ open: false,
+ // 寮瑰嚭灞傛爣棰橈紙BOM瀵煎叆锛�
+ title: "",
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/technologyBom/uploadBom",
});
- 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 || ""
+ const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
});
- 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 data = reactive({
+ form: {
+ id: undefined,
+ productName: "",
+ productModelName: "",
+ productModelId: "",
+ remark: "",
+ version: "",
+ },
+ rules: {
+ productModelId: [
+ { required: true, message: "璇烽�夋嫨浜у搧", trigger: "change" },
+ ],
+ version: [{ required: true, message: "璇疯緭鍏ョ増鏈彿", trigger: "blur" }],
+ },
});
-};
-// 鍏抽棴寮圭獥
-const closeDialog = () => {
- dialogVisible.value = false;
- formRef.value?.resetFields();
-};
+ const { form, rules } = toRefs(data);
-// 瀵煎叆鎸夐挳鎿嶄綔
-const handleImport = () => {
- upload.title = "BOM瀵煎叆";
- upload.open = true;
-};
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ };
-// 鍏抽棴瀵煎叆瀵硅瘽妗嗘椂娓呴櫎鏂囦欢
-const handleImportClose = () => {
- proxy.$refs["uploadRef"].clearFiles();
-};
-
-// 鏂囦欢涓婁紶涓鐞�
-const handleFileUploadProgress = (event, file, fileList) => {
- upload.isUploading = true;
-};
-
-// 鏂囦欢涓婁紶鎴愬姛澶勭悊
-const handleFileSuccess = (response, file, fileList) => {
- upload.open = false;
- upload.isUploading = false;
- proxy.$refs["uploadRef"].clearFiles();
- if (response.code === 200) {
- proxy.$modal.msgSuccess(response.msg || "瀵煎叆鎴愬姛");
+ // 鍒嗛〉
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
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 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 handleExport = () => {
- if (selectedRows.value.length !== 1) {
- proxy.$modal.msgWarning("璇烽�夋嫨涓�鏉℃暟鎹繘琛屽鍑�");
- return;
- }
+ // 鏂板
+ const handleAdd = () => {
+ operationType.value = "add";
+ Object.assign(form.value, {
+ id: undefined,
+ productName: "",
+ productModelName: "",
+ productModelId: "",
+ remark: "",
+ version: "",
+ });
+ dialogVisible.value = true;
+ };
+ const handleCopy = row => {
+ // handleAdd(row);
+ ElMessageBox.confirm("纭澶嶅埗璇OM锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ copy({
+ id: row.id,
+ })
+ .then(() => {
+ proxy.$modal.msgSuccess("澶嶅埗鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("澶嶅埗澶辫触");
+ });
+ })
+ .catch(() => {});
+ };
- const bomId = selectedRows.value[0].id;
- const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`;
+ // 缂栬緫
+ 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;
+ };
- exportBom(bomId).then(res => {
- // 杩斿洖鐨勬暟鎹槸鍚︿负绌�
- if (!res) {
- proxy.$modal.msgError("瀵煎嚭澶辫触锛岃繑鍥炴暟鎹负绌�");
+ // 鍒犻櫎锛堝崟鏉★級
+ 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 handleImportClose = () => {
+ proxy.$refs["uploadRef"].clearFiles();
+ };
+
+ // 鏂囦欢涓婁紶涓鐞�
+ const handleFileUploadProgress = (event, file, fileList) => {
+ upload.isUploading = true;
+ };
+
+ // 鏂囦欢涓婁紶鎴愬姛澶勭悊
+ const handleFileSuccess = (response, file, fileList) => {
+ upload.open = false;
+ upload.isUploading = false;
+ proxy.$refs["uploadRef"].clearFiles();
+ 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 blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
- const downloadElement = document.createElement('a');
+ 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 handleDownloadTemplate = async () => {
+ const res = await downloadTemplate();
+ // 杩斿洖鐨勬暟鎹槸鍚︿负绌�
+ 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;
+ downloadElement.download = "BOM妯℃澘.xlsx";
document.body.appendChild(downloadElement);
downloadElement.click();
@@ -382,52 +516,23 @@
document.body.removeChild(downloadElement);
window.URL.revokeObjectURL(href);
- proxy.$modal.msgSuccess("瀵煎嚭鎴愬姛");
- }).catch(err => {
- console.error("瀵煎嚭寮傚父锛�", err);
- proxy.$modal.msgError("绯荤粺寮傚父锛屽鍑哄け璐�");
+ proxy.$modal.msgSuccess("涓嬭浇鎴愬姛");
+ };
+
+ // 鏌ョ湅璇︽儏
+ const showDetail = row => {
+ router.push({
+ path: "/productionManagement/productStructureDetail",
+ query: {
+ id: row.id,
+ bomNo: row.bomNo || "",
+ productName: row.productName || "",
+ productModelName: row.productModelName || "",
+ },
+ });
+ };
+
+ onMounted(() => {
+ getList();
});
-};
-
-// 涓嬭浇妯℃澘
-const handleDownloadTemplate = async () => {
- const res = await downloadTemplate();
- // 杩斿洖鐨勬暟鎹槸鍚︿负绌�
- 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.href = href;
- downloadElement.download = "BOM妯℃澘.xlsx";
-
- document.body.appendChild(downloadElement);
- downloadElement.click();
-
- document.body.removeChild(downloadElement);
- window.URL.revokeObjectURL(href);
-
- proxy.$modal.msgSuccess("涓嬭浇鎴愬姛");
-};
-
-// 鏌ョ湅璇︽儏
-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 8e1d40b..f438518 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -1,371 +1,389 @@
<template>
- <div class="app-container">
- <el-row :gutter="16" class="content-row">
- <!-- 宸︿晶鍙拌处 + 椤堕儴绛涢�� -->
- <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8" class="left-col">
- <div class="left-panel">
- <div class="left-header">
- <el-form :model="searchForm" inline>
- <el-form-item prop="dateType">
- <el-radio-group v-model="searchForm.dateType" size="small" @change="handleDateTypeChange">
- <el-radio-button label="day">鏃�</el-radio-button>
- <el-radio-button label="month">鏈�</el-radio-button>
- </el-radio-group>
- </el-form-item>
-
- <el-form-item label="鏃ユ湡锛�" prop="dateRange">
- <el-date-picker
- v-model="searchForm.dateRange"
- :type="searchForm.dateType === 'day' ? 'date' : 'daterange'"
- range-separator="鑷�"
- start-placeholder="寮�濮嬫棩鏈�"
- end-placeholder="缁撴潫鏃ユ湡"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 200px"
- @change="handleDateRangeChange"
- />
- </el-form-item>
- </el-form>
- </div>
- <PIMTable
- rowKey="id"
- :column="leftTableColumn"
- :tableData="leftTableData"
- :tableLoading="tableLoading"
- :page="page"
- @row-click="handleLeftRowClick"
- @pagination="pagination"
- ></PIMTable>
- </div>
- </el-col>
-
- <!-- 鍙充晶鏄庣粏 -->
- <el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16" class="right-col">
- <div class="right-panel">
-
- <el-form inline>
- <el-form-item>
- <el-button type="primary" @click="handleOut">瀵煎嚭</el-button>
- </el-form-item>
- </el-form>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page1"
- :tableLoading="tableLoading1"
- style="margin-right: 20px;"
- @pagination="pagination1"
- ></PIMTable>
- </div>
- </el-col>
- </el-row>
- </div>
+ <div class="app-container">
+ <div class="table_list">
+ <el-row :gutter="16"
+ class="content-row">
+ <!-- 宸︿晶鍙拌处 + 椤堕儴绛涢�� -->
+ <el-col :xs="24"
+ :sm="24"
+ :md="24"
+ :lg="8"
+ :xl="8"
+ class="left-col">
+ <div class="left-panel">
+ <div class="left-header">
+ <el-form :model="searchForm"
+ inline>
+ <el-form-item prop="dateType">
+ <el-radio-group v-model="searchForm.dateType"
+ size="small"
+ @change="handleDateTypeChange">
+ <el-radio-button label="day">鏃�</el-radio-button>
+ <el-radio-button label="month">鏈�</el-radio-button>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="鏃ユ湡锛�"
+ prop="dateRange">
+ <el-date-picker v-model="searchForm.dateRange"
+ :type="searchForm.dateType === 'day' ? 'date' : 'daterange'"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 200px"
+ @change="handleDateRangeChange" />
+ </el-form-item>
+ </el-form>
+ </div>
+ <PIMTable rowKey="id"
+ :column="leftTableColumn"
+ :tableData="leftTableData"
+ :tableLoading="tableLoading"
+ :page="page"
+ @row-click="handleLeftRowClick"
+ @pagination="pagination"></PIMTable>
+ </div>
+ </el-col>
+ <!-- 鍙充晶鏄庣粏 -->
+ <el-col :xs="24"
+ :sm="24"
+ :md="24"
+ :lg="16"
+ :xl="16"
+ class="right-col">
+ <div class="right-panel">
+ <el-form inline>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleOut">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page1"
+ :tableLoading="tableLoading1"
+ style="margin-right: 20px;"
+ @pagination="pagination1"></PIMTable>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ </div>
</template>
<script setup>
-import {onMounted, ref} from "vue";
-import { ElMessageBox } from "element-plus";
-import dayjs from "dayjs";
-import {salesLedgerProductionAccountingListProductionDetails, salesLedgerProductionAccountingList} from "@/api/productionManagement/productionCosting.js";
-const { proxy } = getCurrentInstance();
+ import { onMounted, ref } from "vue";
+ import { ElMessageBox } from "element-plus";
+ import dayjs from "dayjs";
+ import {
+ salesLedgerProductionAccountingListProductionDetails,
+ salesLedgerProductionAccountingList,
+ } from "@/api/productionManagement/productionCosting.js";
+ const { proxy } = getCurrentInstance();
-const tableColumn = ref([
- {
- label: "鐢熶骇鏃ユ湡",
- prop: "schedulingDate",
- minWidth: 100,
- },
- {
- label: "鐢熶骇浜�",
- prop: "schedulingUserName",
- minWidth: 100,
- },
- {
- label: "鍚堝悓鍙�",
- prop: "salesContractNo",
- minWidth: 100,
- },
- {
- label: "瀹㈡埛鍚嶇О",
- prop: "customerName",
- minWidth: 100,
- },
- {
- label: "浜у搧澶х被",
- prop: "productName",
- minWidth: 100,
- },
- {
- label: "瑙勬牸鍨嬪彿",
- prop: "productModelName",
- minWidth: 100,
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- minWidth: 100,
- },
- {
- label: "宸ュ簭",
- prop: "process",
- minWidth: 100,
- },
- {
- label: "鐢熶骇鏁伴噺",
- prop: "quantity",
- minWidth: 100,
- },
- {
- label: "宸ユ椂瀹氶",
- prop: "workHours",
- minWidth: 100,
- },
- {
- label: "宸ヨ祫",
- prop: "wages",
- minWidth: 100,
- },
-]);
-
-// 宸︿晶姹囨�诲彴璐﹀垪锛堢敓浜т汉銆佷骇閲忋�佸伐璧勩�佸悎鏍肩巼锛�
-const leftTableColumn = ref([
- {
- label: "鐢熶骇浜�",
- prop: "schedulingUserName",
- minWidth: 100,
- },
- {
- label: "浜ч噺",
- prop: "outputNum",
- minWidth: 100,
-
- },
- {
- label: "宸ヨ祫",
- prop: "wages",
- minWidth: 100,
-
- },
- {
- label: "鍚堟牸鐜�",
- prop: "outputRate",
- minWidth: 100,
- formatData: (val) => {
- if (val == null || val === '') return '-'
- return parseFloat(val).toFixed(2)
+ const tableColumn = ref([
+ {
+ label: "鐢熶骇鏃ユ湡",
+ prop: "schedulingDate",
+ minWidth: 100,
},
- },
-]);
+ {
+ label: "鐢熶骇浜�",
+ prop: "schedulingUserName",
+ minWidth: 100,
+ },
+ // {
+ // label: "鍚堝悓鍙�",
+ // prop: "salesContractNo",
+ // minWidth: 100,
+ // },
+ // {
+ // label: "瀹㈡埛鍚嶇О",
+ // prop: "customerName",
+ // minWidth: 100,
+ // },
+ {
+ label: "浜у搧澶х被",
+ prop: "productName",
+ minWidth: 100,
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "productModelName",
+ minWidth: 100,
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ minWidth: 100,
+ },
+ {
+ label: "宸ュ簭",
+ prop: "process",
+ minWidth: 100,
+ },
+ {
+ label: "鐢熶骇鏁伴噺",
+ prop: "quantity",
+ minWidth: 100,
+ },
+ {
+ label: "宸ユ椂瀹氶",
+ prop: "workHours",
+ minWidth: 100,
+ },
+ {
+ label: "宸ヨ祫",
+ prop: "wages",
+ minWidth: 100,
+ },
+ ]);
-const tableData = ref([]);
-const tableLoading = ref(false);
-const tableLoading1 = ref(false);
-const leftTableData = ref([]);
-// 鏃� / 鏈� 鍒囨崲锛堥粯璁ゆ寜鏃ワ級
-const page = reactive({
- current: 1,
- size: 100,
- total: 0,
-});
+ // 宸︿晶姹囨�诲彴璐﹀垪锛堢敓浜т汉銆佷骇閲忋�佸伐璧勩�佸悎鏍肩巼锛�
+ const leftTableColumn = ref([
+ {
+ label: "鐢熶骇浜�",
+ prop: "schedulingUserName",
+ minWidth: 100,
+ },
+ {
+ label: "浜ч噺",
+ prop: "finishedNum",
+ minWidth: 100,
+ },
+ {
+ label: "宸ヨ祫",
+ prop: "wages",
+ minWidth: 100,
+ },
+ {
+ label: "鍚堟牸鐜�",
+ prop: "outputRate",
+ minWidth: 100,
+ formatData: val => {
+ if (val == null || val === "") return "-";
+ return parseFloat(val).toFixed(2) + "%";
+ },
+ },
+ ]);
-const page1 = reactive({
- current: 1,
- size: 100,
- total: 0,
-});
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const tableLoading1 = ref(false);
+ const leftTableData = ref([]);
+ // 鏃� / 鏈� 鍒囨崲锛堥粯璁ゆ寜鏃ワ級
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
-const data = reactive({
- searchForm: {
- schedulingUserName: "",
- salesContractNo: "",
- dateType: "day",
- dateRange: dayjs().format("YYYY-MM-DD"),
- entryDate: dayjs().format("YYYY-MM-DD"),
- entryDateStart: undefined,
- entryDateEnd: undefined,
- },
-});
-const { searchForm } = toRefs(data);
+ const page1 = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
+ const data = reactive({
+ searchForm: {
+ schedulingUserName: "",
+ salesContractNo: "",
+ dateType: "day",
+ dateRange: dayjs().format("YYYY-MM-DD"),
+ entryDate: dayjs().format("YYYY-MM-DD"),
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
+ },
+ });
+ const { searchForm } = toRefs(data);
-const pagination1 = (obj) => {
- page1.current = obj.page;
- page1.size = obj.limit;
- getList1();
-};
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
-const handleDateRangeChange = (value) => {
- if (value) {
- if (searchForm.value.dateType === "day") {
- searchForm.value.entryDate = value;
+ const pagination1 = obj => {
+ page1.current = obj.page;
+ page1.size = obj.limit;
+ getList1();
+ };
+
+ const handleDateRangeChange = value => {
+ if (value) {
+ if (searchForm.value.dateType === "day") {
+ searchForm.value.entryDate = value;
+ } else {
+ searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ }
} else {
- searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
- searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ searchForm.value.entryDate = undefined;
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ reloadData();
+ };
+
+ const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+
+ salesLedgerProductionAccountingList(params)
+ .then(res => {
+ const records = res.data.records || [];
+ leftTableData.value = records;
+ page.total = res.data.total || 0;
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ };
+
+ const getList1 = () => {
+ tableLoading1.value = true;
+ const params = { ...page1, ...searchForm.value };
+ salesLedgerProductionAccountingListProductionDetails(params)
+ .then(res => {
+ tableData.value = res.data.records || [];
+ page1.total = res.data.total || 0;
+ })
+ .finally(() => {
+ tableLoading1.value = false;
+ });
+ };
+
+ // 鏋勫缓宸︿晶姹囨�诲彴璐︼紙鎸夌敓浜т汉姹囨�讳骇閲忋�佸伐璧勭瓑锛�
+ const buildLeftTableData = records => {
+ const map = {};
+ records.forEach(item => {
+ const key = item.schedulingUserName || "鏈煡";
+ if (!map[key]) {
+ map[key] = {
+ id: key,
+ schedulingUserName: key,
+ finishedNum: 0,
+ wages: 0,
+ qualifiedRate: item.qualifiedRate ?? null,
+ };
+ }
+ map[key].finishedNum += Number(item.finishedNum || 0);
+ map[key].wages += Number(item.wages || 0);
+ if (item.qualifiedRate != null) {
+ map[key].qualifiedRate = item.qualifiedRate;
+ }
+ });
+ leftTableData.value = Object.values(map);
+ };
+
+ // 宸︿晶鏃�/鏈堝垏鎹�
+ const handleDateTypeChange = value => {
+ // 杩欓噷鍙綔涓虹瓫閫夋潯浠剁殑涓�閮ㄥ垎锛岀洿鎺ラ噸鏂版煡璇㈠垪琛�
+ if (value === "day") {
+ searchForm.value.entryDate = dayjs().format("YYYY-MM-DD");
+ searchForm.value.dateRange = searchForm.value.entryDate;
+ } else {
+ searchForm.value.entryDateStart = dayjs()
+ .startOf("month")
+ .format("YYYY-MM-DD");
+ searchForm.value.entryDateEnd = dayjs().endOf("month").format("YYYY-MM-DD");
+ searchForm.value.dateRange = [
+ searchForm.value.entryDateStart,
+ searchForm.value.entryDateEnd,
+ ];
}
- } else {
- searchForm.value.entryDate = undefined;
- searchForm.value.entryDateStart = undefined;
- searchForm.value.entryDateEnd = undefined;
- }
- reloadData()
-};
+ reloadData();
+ };
-const getList = () => {
- tableLoading.value = true;
- const params = { ...searchForm.value, ...page };
+ const reloadData = () => {
+ page.current = 1;
+ page1.current = 1;
+ getList();
+ tableData.value = [];
+ };
- salesLedgerProductionAccountingList(params).then((res) => {
- const records = res.data.records || [];
- leftTableData.value = records;
- page.total = res.data.total || 0;
- }).finally(() => {
- tableLoading.value = false;
- })
+ // 鐐瑰嚮宸︿晶琛岋紝鍒峰彸渚ф槑缁嗭紙鎸夌敓浜т汉杩囨护锛�
+ const handleLeftRowClick = row => {
+ searchForm.value.schedulingUserName = row.schedulingUserName || "";
+ handleQuery();
+ };
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page1.current = 1;
+ getList1();
+ };
+ // 瀵煎嚭
+ const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download(
+ "/salesLedger/productionAccounting/export",
+ {},
+ "鐢熶骇鏍哥畻.xlsx"
+ );
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
-};
-
-const getList1 = () => {
- tableLoading1.value = true;
- const params = { ...page1, ...searchForm.value };
- salesLedgerProductionAccountingListProductionDetails(params).then((res) => {
- tableData.value = res.data.records || [];;
- page1.total = res.data.total || 0;
- }).finally(() => {
- tableLoading1.value = false;
- })
-};
-
-// 鏋勫缓宸︿晶姹囨�诲彴璐︼紙鎸夌敓浜т汉姹囨�讳骇閲忋�佸伐璧勭瓑锛�
-const buildLeftTableData = (records) => {
- const map = {};
- records.forEach((item) => {
- const key = item.schedulingUserName || "鏈煡";
- if (!map[key]) {
- map[key] = {
- id: key,
- schedulingUserName: key,
- finishedNum: 0,
- wages: 0,
- qualifiedRate: item.qualifiedRate ?? null,
- };
- }
- map[key].finishedNum += Number(item.finishedNum || 0);
- map[key].wages += Number(item.wages || 0);
- if (item.qualifiedRate != null) {
- map[key].qualifiedRate = item.qualifiedRate;
- }
- });
- leftTableData.value = Object.values(map);
-};
-
-// 宸︿晶鏃�/鏈堝垏鎹�
-const handleDateTypeChange = (value) => {
- // 杩欓噷鍙綔涓虹瓫閫夋潯浠剁殑涓�閮ㄥ垎锛岀洿鎺ラ噸鏂版煡璇㈠垪琛�
- if (value === "day") {
- searchForm.value.entryDate = dayjs().format("YYYY-MM-DD");
- searchForm.value.dateRange = searchForm.value.entryDate
- } else {
- searchForm.value.entryDateStart = dayjs().startOf("month").format("YYYY-MM-DD");
- searchForm.value.entryDateEnd = dayjs().endOf("month").format("YYYY-MM-DD");
- searchForm.value.dateRange = [searchForm.value.entryDateStart, searchForm.value.entryDateEnd]
- }
-
- reloadData()
-};
-
-const reloadData = () => {
- page.current = 1;
- page1.current = 1;
- getList();
- tableData.value = []
-}
-
-// 鐐瑰嚮宸︿晶琛岋紝鍒峰彸渚ф槑缁嗭紙鎸夌敓浜т汉杩囨护锛�
-const handleLeftRowClick = (row) => {
- searchForm.value.schedulingUserName = row.schedulingUserName || "";
- handleQuery();
-};
-
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page1.current = 1;
- getList1();
-};
-
-
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/salesLedger/productionAccounting/export", {}, "鐢熶骇鏍哥畻.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-onMounted(() => {
- getList();
-});
+ onMounted(() => {
+ getList();
+ });
</script>
<style scoped lang="scss">
-.content-row {
- width: 100%;
-}
+ .content-row {
+ width: 100%;
+ }
-.content-row .left-col,
-.content-row .right-col {
- margin-bottom: 16px;
-}
+ .content-row .left-col,
+ .content-row .right-col {
+ margin-bottom: 16px;
+ }
-.left-panel,
-.right-panel {
- display: flex;
- flex-direction: column;
- gap: 10px;
- min-width: 0;
-}
+ .left-panel,
+ .right-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ min-width: 0;
+ }
-.left-header {
- display: flex;
- align-items: center;
- gap: 12px;
-}
+ .left-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ }
-.left-title {
- font-size: 16px;
- color: #ffffff;
-}
+ .left-title {
+ font-size: 16px;
+ color: #ffffff;
+ }
-.header-filters {
- display: flex;
- align-items: center;
- flex: 1;
- justify-content: flex-end;
- gap: 8px;
-}
+ .header-filters {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ justify-content: flex-end;
+ gap: 8px;
+ }
-.search_title {
- color: #ffffff;
-}
+ .search_title {
+ color: #ffffff;
+ }
-.ml10 {
- margin-left: 10px;
-}
+ .ml10 {
+ margin-left: 10px;
+ }
</style>
diff --git a/src/views/productionManagement/productionOrder/New.vue b/src/views/productionManagement/productionOrder/New.vue
index c9c478b..ea74186 100644
--- a/src/views/productionManagement/productionOrder/New.vue
+++ b/src/views/productionManagement/productionOrder/New.vue
@@ -1,44 +1,39 @@
<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="[
+ <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">
+ ]">
+ <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 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 label="鍗曚綅"
+ prop="unit">
+ <el-input v-model="formState.unit"
+ disabled />
</el-form-item>
-
<el-form-item label="宸ヨ壓璺嚎">
- <el-select v-model="formState.routeId"
+ <el-select v-model="formState.technologyRoutingId"
placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
style="width: 100%;"
:loading="bindRouteLoading">
@@ -48,24 +43,30 @@
:value="item.id" />
</el-select>
</el-form-item>
-
- <el-form-item
- label="闇�姹傛暟閲�"
- prop="quantity"
- >
- <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" />
+ <el-form-item label="闇�姹傛暟閲�"
+ prop="quantity">
+ <el-input-number v-model="formState.quantity"
+ :step="1"
+ :min="0"
+ style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="璁″垝瀹屾垚鏃堕棿"
+ prop="planCompleteTime">
+ <el-date-picker v-model="formState.planCompleteTime"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 100%" />
</el-form-item>
</el-form>
-
<!-- 浜у搧閫夋嫨寮圭獥 -->
- <ProductSelectDialog
- v-model="showProductSelectDialog"
- @confirm="handleProductSelect"
- single
- />
+ <ProductSelectDialog v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single />
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button type="primary"
+ @click="handleSubmit">纭</el-button>
<el-button @click="closeModal">鍙栨秷</el-button>
</div>
</template>
@@ -74,119 +75,130 @@
</template>
<script setup>
-import {ref, computed, getCurrentInstance} from "vue";
-import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
-import {addProductOrder, listProcessRoute} from "@/api/productionManagement/productionOrder.js";
+ import { ref, computed, getCurrentInstance } from "vue";
+ import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import {
+ addProductOrder,
+ listProcessRoute,
+ } from "@/api/productionManagement/productionOrder.js";
+ import { listPage } from "@/api/productionManagement/processRoute.js";
+ const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
-const props = defineProps({
- visible: {
- type: Boolean,
- required: true,
- },
+ type: {
+ type: String,
+ required: true,
+ default: "qualified",
+ },
+ });
- type: {
- type: String,
- required: true,
- default: 'qualified',
- },
-});
+ const emit = defineEmits(["update:visible", "completed"]);
-const emit = defineEmits(['update:visible', 'completed']);
-
-// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
-const formState = ref({
- productId: undefined,
- productModelId: undefined,
- routeId: undefined,
- productName: "",
- productModelName: "",
- unit: "",
- quantity: 0,
-});
-
-const isShow = computed({
- get() {
- return props.visible;
- },
- set(val) {
- emit('update:visible', val);
- },
-});
-
-const showProductSelectDialog = ref(false);
-
-let { proxy } = getCurrentInstance()
-
-const closeModal = () => {
- // 閲嶇疆琛ㄥ崟鏁版嵁
- formState.value = {
+ // 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+ const formState = ref({
productId: undefined,
productModelId: undefined,
- routeId: undefined,
+ technologyRoutingId: undefined,
productName: "",
productModelName: "",
- quantity: '',
+ unit: "",
+ quantity: 0,
+ planCompleteTime: "",
+ });
+
+ 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,
+ technologyRoutingId: undefined,
+ productName: "",
+ productModelName: "",
+ unit: "",
+ quantity: "",
+ planCompleteTime: "",
+ };
+ isShow.value = false;
};
- 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;
- fetchRouteOptions( product.id);
- // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
- proxy.$refs["formRef"]?.validateField('productModelId');
- }
-};
-
-const routeOptions = ref([]);
-const bindRouteLoading = ref(false);
-const fetchRouteOptions = (productModelId) => {
- formState.value.routeId = undefined;
- routeOptions.value = []
- bindRouteLoading.value = true;
- listProcessRoute({ productModelId: productModelId }).then(res => {
- routeOptions.value = res.data || [];
- }).finally(() => {
- bindRouteLoading.value = false;
- })
-}
-
-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;
- }
-
- addProductOrder(formState.value).then(res => {
- // 鍏抽棴妯℃�佹
- isShow.value = false;
- // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
- emit('completed');
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- })
+ // 浜у搧閫夋嫨澶勭悊
+ 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;
+ fetchRouteOptions(product.id);
+ // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+ proxy.$refs["formRef"]?.validateField("productModelId");
}
- })
-};
+ };
+ const routeOptions = ref([]);
+ const bindRouteLoading = ref(false);
+ const fetchRouteOptions = productModelId => {
+ formState.value.technologyRoutingId = undefined;
+ routeOptions.value = [];
+ bindRouteLoading.value = true;
+ listPage({ productModelId: productModelId })
+ .then(res => {
+ routeOptions.value = res.data.records || [];
+ })
+ .finally(() => {
+ bindRouteLoading.value = false;
+ });
+ };
-defineExpose({
- closeModal,
- handleSubmit,
- isShow,
-});
+ 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 (formState.value.quantity <= 0) {
+ proxy.$modal.msgError("闇�姹傛暟閲忓繀椤诲ぇ浜�0");
+ return;
+ }
+
+ addProductOrder(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit("completed");
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
+ }
+ });
+ };
+
+ defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+ });
</script>
diff --git a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
new file mode 100644
index 0000000..370815e
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -0,0 +1,284 @@
+<template>
+ <div>
+ <el-dialog v-model="dialogVisible"
+ title="棰嗘枡璇︽儏"
+ width="1400px"
+ @close="handleClose">
+ <el-table v-loading="materialDetailLoading"
+ :data="materialDetailTableData"
+ border
+ row-key="id">
+ <el-table-column label="宸ュ簭鍚嶇О"
+ prop="operationName"
+ min-width="180" />
+ <el-table-column label="鍘熸枡鍚嶇О"
+ prop="productName"
+ min-width="160" />
+ <el-table-column label="鍘熸枡鍨嬪彿"
+ prop="model"
+ min-width="180" />
+ <el-table-column label="鎵瑰彿"
+ prop="batchNo"
+ min-width="150" />
+ <el-table-column label="闇�姹傛暟閲�"
+ prop="demandedQuantity"
+ min-width="110" />
+ <el-table-column label="璁¢噺鍗曚綅"
+ prop="unit"
+ width="100" />
+ <el-table-column label="棰嗙敤鏁伴噺"
+ prop="pickQuantity"
+ min-width="110" />
+ <el-table-column label="琛ユ枡鏁伴噺"
+ min-width="120">
+ <template #default="{ row }">
+ <el-button type="primary"
+ link
+ @click="handleViewSupplementRecord(row)">
+ {{ row.feedingQty ?? 0 }}
+ </el-button>
+ </template>
+ </el-table-column>
+ <el-table-column label="閫�鏂欐暟閲�"
+ min-width="110">
+ <template #default="{ row }">
+ {{ row.returnQty ?? 0 }}
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹為檯鏁伴噺"
+ min-width="140">
+ <template #default="{ row }">
+ <el-input-number v-model="row.actualQty"
+ :min="0"
+ :precision="3"
+ :step="1"
+ controls-position="right"
+ placeholder="杈撳叆瀹為檯鏁伴噺"
+ style="width: 100%;"
+ :disabled="row.returned || orderRow?.end"
+ @change="val => handleActualQtyChange(row, val)" />
+ </template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button v-if="!orderRow?.end"
+ type="warning"
+ :loading="materialReturnConfirming"
+ :disabled="!canOpenReturnSummary"
+ @click="openReturnSummaryDialog">
+ 閫�鏂欑‘璁�
+ </el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="supplementRecordDialogVisible"
+ title="琛ユ枡璁板綍"
+ width="800px">
+ <el-table v-loading="supplementRecordLoading"
+ :data="supplementRecordTableData"
+ border
+ row-key="id">
+ <el-table-column label="琛ユ枡鏁伴噺"
+ prop="pickQuantity"
+ min-width="120" />
+ <el-table-column label="琛ユ枡浜�"
+ prop="supplementUserName"
+ min-width="120" />
+ <el-table-column label="琛ユ枡鏃ユ湡"
+ prop="supplementTime"
+ min-width="160" />
+ <el-table-column label="琛ユ枡鍘熷洜"
+ prop="feedingReason"
+ min-width="200" />
+ </el-table>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="returnSummaryDialogVisible"
+ title="閫�鏂欐眹鎬荤‘璁�"
+ width="900px">
+ <el-table :data="returnSummaryList"
+ border
+ row-key="summaryKey">
+ <el-table-column label="鍘熸枡鍚嶇О"
+ prop="materialName"
+ min-width="180" />
+ <el-table-column label="鍘熸枡鍨嬪彿"
+ prop="materialModel"
+ min-width="180" />
+ <el-table-column label="璁¢噺鍗曚綅"
+ prop="unit"
+ min-width="100" />
+ <el-table-column label="閫�鏂欐眹鎬绘暟閲�"
+ prop="returnQtyTotal"
+ min-width="140" />
+ </el-table>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ :loading="materialReturnConfirming"
+ @click="handleReturnConfirm">纭鎻愪氦</el-button>
+ <el-button @click="returnSummaryDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { computed, ref, watch } from "vue";
+ import { ElMessage } from "element-plus";
+ import {
+ listMaterialPickingDetail,
+ listMaterialSupplementRecord,
+ updateMaterialPickingLedger,
+ } from "@/api/productionManagement/productionOrder.js";
+
+ const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ orderRow: { type: Object, default: null },
+ });
+ const emit = defineEmits(["update:modelValue", "confirmed"]);
+
+ const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: val => emit("update:modelValue", val),
+ });
+
+ const materialDetailLoading = ref(false);
+ const materialDetailTableData = ref([]);
+ const materialReturnConfirming = ref(false);
+ const supplementRecordDialogVisible = ref(false);
+ const supplementRecordLoading = ref(false);
+ const supplementRecordTableData = ref([]);
+ const returnSummaryDialogVisible = ref(false);
+ const returnSummaryList = ref([]);
+ const calcReturnQty = item =>
+ Number(item.pickQuantity || 0) +
+ Number(item.feedingQty || 0) -
+ Number(item.actualQty || 0);
+ const canOpenReturnSummary = computed(() =>
+ materialDetailTableData.value.some(
+ item => item.returned !== true && calcReturnQty(item) > 0
+ )
+ );
+
+ const loadDetailList = async () => {
+ if (!props.orderRow?.id) return;
+ materialDetailLoading.value = true;
+ materialDetailTableData.value = [];
+ try {
+ const res = await listMaterialPickingDetail(props.orderRow.id);
+ materialDetailTableData.value = (res.data || []).map(item => ({
+ ...item,
+ actualQty:
+ item.actualQty ??
+ Number(item.pickQuantity || 0) + Number(item.feedingQty || 0),
+ returnQty: item.returnQty ?? 0,
+ }));
+ } finally {
+ materialDetailLoading.value = false;
+ }
+ };
+
+ watch(
+ () => dialogVisible.value,
+ visible => {
+ if (visible) {
+ loadDetailList();
+ }
+ }
+ );
+
+ const handleClose = () => {
+ materialDetailTableData.value = [];
+ };
+
+ const handleActualQtyChange = (row, val) => {
+ row.returnQty = calcReturnQty(row);
+ };
+
+ const handleViewSupplementRecord = async row => {
+ if (!row?.id) return;
+ supplementRecordDialogVisible.value = true;
+ supplementRecordLoading.value = true;
+ supplementRecordTableData.value = [];
+ try {
+ const res = await listMaterialSupplementRecord({
+ pickId: row.id,
+ productionOrderId: props.orderRow.id,
+ });
+ supplementRecordTableData.value = res.data || [];
+ } finally {
+ supplementRecordLoading.value = false;
+ }
+ };
+
+ const buildReturnSummary = () => {
+ const map = new Map();
+ materialDetailTableData.value.forEach(item => {
+ const returnQty = calcReturnQty(item);
+ if (returnQty <= 0) return;
+ const key = `${item.productModelId || ""}_${item.productName || ""}_${
+ item.model || ""
+ }_${item.unit || ""}`;
+ const old = map.get(key) || {
+ summaryKey: key,
+ materialName: item.productName || "",
+ materialModel: item.model || "",
+ unit: item.unit || "",
+ returnQtyTotal: 0,
+ };
+ old.returnQtyTotal += returnQty;
+ map.set(key, old);
+ });
+ return Array.from(map.values());
+ };
+
+ const openReturnSummaryDialog = async () => {
+ if (!canOpenReturnSummary.value) {
+ ElMessage.warning("閫�鏂欐暟閲�=棰嗙敤鏁伴噺+琛ユ枡鏁伴噺-瀹為檯鏁伴噺锛屼笖闇�澶т簬0");
+ return;
+ }
+ returnSummaryList.value = buildReturnSummary();
+ returnSummaryDialogVisible.value = true;
+ };
+
+ const handleReturnConfirm = async () => {
+ if (!props.orderRow?.id) return;
+ materialReturnConfirming.value = true;
+ try {
+ await updateMaterialPickingLedger({
+ productionOrderId: props.orderRow.id,
+ productionOrderPickDto: materialDetailTableData.value.map(item => ({
+ id: item.id,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName,
+ bom: item.bom === true,
+ productModelId: item.productModelId,
+ demandedQuantity: item.demandedQuantity,
+ unit: item.unit,
+ pickQuantity: item.pickQuantity,
+ batchNo: item.batchNo,
+ feedingQty: item.feedingQty,
+ returnQty: item.returnQty,
+ actualQty: item.actualQty,
+ feedingReason: item.feedingReason,
+ returned: true,
+ })),
+ });
+ returnSummaryDialogVisible.value = false;
+ dialogVisible.value = false;
+ emit("confirmed");
+ } finally {
+ materialReturnConfirming.value = false;
+ }
+ };
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
new file mode 100644
index 0000000..210dbba
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -0,0 +1,391 @@
+<template>
+ <div>
+ <el-dialog v-model="dialogVisible"
+ title="棰嗘枡鍙拌处"
+ width="1200px"
+ @close="handleClose">
+ <div class="material-toolbar">
+ <el-button type="primary"
+ @click="handleAddMaterialRow">鏂板</el-button>
+ </div>
+ <el-table v-loading="materialTableLoading"
+ :data="materialTableData"
+ border
+ row-key="tempId">
+ <el-table-column label="宸ュ簭鍚嶇О"
+ min-width="140">
+ <template #default="{ row }">
+ <span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
+ <el-select v-else
+ v-model="row.operationName"
+ placeholder="璇烽�夋嫨宸ュ簭"
+ clearable
+ filterable
+ style="width: 100%;"
+ @change="val => handleProcessNameChange(row, val)">
+ <el-option v-for="item in processOptions"
+ :key="item.technologyOperationId"
+ :label="item.name"
+ :value="item.name" />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍘熸枡鍚嶇О"
+ min-width="140">
+ <template #default="{ row }">
+ <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
+ <el-button v-else
+ type="primary"
+ link
+ @click="openMaterialProductSelect(row)">
+ {{ row.materialName || "閫夋嫨鍘熸枡" }}
+ </el-button>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍘熸枡鍨嬪彿"
+ min-width="140">
+ <template #default="{ row }">
+ {{ row.materialModel || "-" }}
+ </template>
+ </el-table-column>
+ <!-- 鎵瑰彿澶氶�� -->
+ <el-table-column min-width="200"
+ label="鎵瑰彿">
+ <template #default="{ row }">
+ <el-select v-model="row.batchNo"
+ multiple
+ collapse-tags
+ collapse-tags-indicator
+ placeholder="璇烽�夋嫨鎵瑰彿"
+ style="width: 100%;">
+ <el-option v-for="item in row.batchNoList"
+ :key="item"
+ :label="item"
+ :value="item" />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="闇�姹傛暟閲�"
+ min-width="120">
+ <template #default="{ row }">
+ <span v-if="row.bom === true">{{ row.demandedQuantity ?? "-" }}</span>
+ <el-input-number v-else
+ v-model="row.demandedQuantity"
+ :min="0"
+ :precision="3"
+ :step="1"
+ controls-position="right"
+ style="width: 100%;"
+ @change="val => handleRequiredQtyChange(row, val)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="璁¢噺鍗曚綅"
+ width="100">
+ <template #default="{ row }">
+ {{ row.unit || "-" }}
+ </template>
+ </el-table-column>
+ <el-table-column label="棰嗙敤鏁伴噺"
+ min-width="120">
+ <template #default="{ row }">
+ <el-input-number v-model="row.pickQty"
+ :min="0"
+ :precision="3"
+ :step="1"
+ controls-position="right"
+ style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ width="90"
+ fixed="right">
+ <template #default="{ $index, row }">
+ <el-button v-if="row.bom !== true"
+ type="danger"
+ link
+ @click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ :loading="materialSaving"
+ @click="handleMaterialSave">淇濆瓨</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <ProductSelectDialog v-model="materialProductDialogVisible"
+ @confirm="handleMaterialProductConfirm"
+ single />
+ <!-- request-url="/stockInventory/rawMaterials" -->
+ </div>
+</template>
+
+<script setup>
+ import { computed, ref, watch } from "vue";
+ import { ElMessage } from "element-plus";
+ import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import {
+ findProductProcessRouteItemList,
+ listMain,
+ } from "@/api/productionManagement/productProcessRoute.js";
+ import {
+ listMaterialPickingDetail,
+ listMaterialPickingBom,
+ listMaterialPickingLedger,
+ saveMaterialPickingLedger,
+ updateMaterialPickingLedger,
+ } from "@/api/productionManagement/productionOrder.js";
+ import { queryList2 } from "@/api/productionManagement/productStructure.js";
+
+ const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ orderRow: { type: Object, default: null },
+ });
+ const emit = defineEmits(["update:modelValue", "saved"]);
+
+ const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: val => emit("update:modelValue", val),
+ });
+
+ const materialProductDialogVisible = ref(false);
+ const materialTableLoading = ref(false);
+ const materialSaving = ref(false);
+ const materialTableData = ref([]);
+ const processOptions = ref([]);
+ const currentMaterialSelectRowIndex = ref(-1);
+ let materialTempId = 0;
+
+ const createMaterialRow = (row = {}) => ({
+ tempId: row.id || `temp_${++materialTempId}`,
+ id: row.id,
+ processId: row.processId || row.technologyOperationId,
+ technologyOperationId: row.technologyOperationId || row.processId,
+ operationName: row.operationName || "",
+ bom: row.bom === true,
+ materialModelId: row.materialModelId || row.productModelId,
+ materialName: row.materialName || row.productName || "",
+ materialModel: row.materialModel || row.model || "",
+ demandedQuantity: Number(row.requiredQty ?? row.demandedQuantity ?? 0),
+ unit: row.unit || "",
+ pickQty: Number(row.pickQty ?? row.pickQuantity ?? 0),
+ batchNo: row.batchNo
+ ? typeof row.batchNo === "string"
+ ? row.batchNo.split(",")
+ : row.batchNo
+ : [],
+ batchNoList: row.batchNoList || [],
+ });
+
+ const getProcessOptions = async () => {
+ if (!props.orderRow?.id) return;
+ const res = await findProductProcessRouteItemList({
+ orderId: props.orderRow.id,
+ });
+ const routeList = Array.isArray(res?.data)
+ ? res.data
+ : res?.data?.records || [];
+ const processMap = new Map();
+ routeList.forEach(item => {
+ const processId = item.technologyOperationId;
+ const operationName = item.operationName;
+ if (!processId || !operationName) return;
+ const key = `${processId}_${operationName}`;
+ if (!processMap.has(key)) {
+ processMap.set(key, {
+ id: processId,
+ name: operationName,
+ });
+ }
+ });
+ processOptions.value = Array.from(processMap.values());
+ };
+ const isDetail = ref(true);
+
+ const loadMaterialData = async () => {
+ if (!props.orderRow?.id) return;
+ materialTableLoading.value = true;
+ materialTableData.value = [];
+ await getProcessOptions();
+ try {
+ const detailRes = await listMaterialPickingDetail(props.orderRow.id);
+ const detailList = Array.isArray(detailRes?.data)
+ ? detailRes.data
+ : detailRes?.data?.records || [];
+ if (detailList.length > 0) {
+ isDetail.value = true;
+ materialTableData.value = detailList.map(item => createMaterialRow(item));
+ return;
+ } else {
+ isDetail.value = false;
+ const bomRes = await listMaterialPickingBom(props.orderRow.id);
+ const bomList = Array.isArray(bomRes?.data)
+ ? bomRes.data
+ : bomRes?.data?.records || [];
+ materialTableData.value = bomList.map(item => createMaterialRow(item));
+ return;
+ }
+ } finally {
+ materialTableLoading.value = false;
+ }
+ };
+
+ watch(
+ () => dialogVisible.value,
+ visible => {
+ if (visible) {
+ loadMaterialData();
+ }
+ }
+ );
+
+ const handleClose = () => {
+ materialTableData.value = [];
+ currentMaterialSelectRowIndex.value = -1;
+ };
+
+ const handleAddMaterialRow = () => {
+ materialTableData.value.push(createMaterialRow());
+ };
+
+ const handleDeleteMaterialRow = index => {
+ materialTableData.value.splice(index, 1);
+ };
+
+ const handleProcessNameChange = (row, operationName) => {
+ const process = processOptions.value.find(
+ item => item.name === operationName
+ );
+ row.technologyOperationId = process?.technologyOperationId;
+ };
+
+ const handleRequiredQtyChange = (row, val) => {
+ const required = Number(val ?? 0);
+ row.demandedQuantity = required;
+ if (!row.pickQty || Number(row.pickQty) === 0) {
+ row.pickQty = required;
+ }
+ };
+
+ const openMaterialProductSelect = row => {
+ currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(
+ item => item.tempId === row.tempId
+ );
+ materialProductDialogVisible.value = true;
+ };
+
+ const handleMaterialProductConfirm = products => {
+ console.log(products, "products");
+
+ if (!products || products.length === 0) return;
+ const index = currentMaterialSelectRowIndex.value;
+ if (index < 0 || !materialTableData.value[index]) return;
+ const product = products[0];
+ const row = materialTableData.value[index];
+ row.materialModelId =
+ product.materialModelId || product.modelId || product.id;
+ row.materialName =
+ product.materialName || product.productName || product.name || "";
+ row.materialModel = product.materialModel || product.model || "";
+ row.unit = product.unit || product.measureUnit || "";
+ row.batchNoList = product.batchNoList;
+ currentMaterialSelectRowIndex.value = -1;
+ materialProductDialogVisible.value = false;
+ };
+
+ const validateMaterialRows = () => {
+ if (materialTableData.value.length === 0) {
+ return { valid: false, message: "璇峰厛鏂板棰嗘枡鏁版嵁" };
+ }
+ const invalidNewRow = materialTableData.value.find(
+ item => item.bom !== true && (!item.operationName || !item.materialName)
+ );
+ if (invalidNewRow) {
+ return { valid: false, message: "鏂板琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤�" };
+ }
+ const invalidRow = materialTableData.value.find(
+ item =>
+ !item.operationName ||
+ !item.materialName ||
+ (Number(item.pickQty) > 0 &&
+ (!item.batchNo || item.batchNo.length === 0)) ||
+ item.demandedQuantity === null ||
+ item.demandedQuantity === undefined ||
+ item.pickQty === null ||
+ item.pickQty === undefined
+ );
+ if (invalidRow) {
+ return { valid: false, message: "璇峰畬鍠勫伐搴忋�佸師鏂欍�佹壒鍙峰拰鏁伴噺鍚庡啀淇濆瓨" };
+ }
+ return { valid: true, message: "" };
+ };
+
+ const handleMaterialSave = async () => {
+ if (!props.orderRow?.id) return;
+ const validateResult = validateMaterialRows();
+ if (!validateResult.valid) {
+ ElMessage.warning(validateResult.message);
+ return;
+ }
+ materialSaving.value = true;
+ try {
+ if (isDetail.value) {
+ await updateMaterialPickingLedger({
+ productionOrderId: props.orderRow.id,
+ productionOrderPickDto: materialTableData.value.map(item => ({
+ id: item.id,
+ // processId: item.operationName,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName,
+ bom: item.bom === true,
+ productModelId: item.materialModelId,
+ // materialName: item.materialName,
+ // materialModel: item.materialModel,
+ demandedQuantity: item.demandedQuantity,
+ unit: item.unit,
+ pickQuantity: item.pickQty,
+ batchNo: Array.isArray(item.batchNo)
+ ? item.batchNo.join(",")
+ : item.batchNo,
+ })),
+ });
+ } else {
+ await saveMaterialPickingLedger({
+ productionOrderId: props.orderRow.id,
+ productionOrderPickDto: materialTableData.value.map(item => ({
+ id: item.id,
+ // processId: item.operationName,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName,
+ bom: item.bom === true,
+ productModelId: item.materialModelId,
+ // materialName: item.materialName,
+ // materialModel: item.materialModel,
+ demandedQuantity: item.demandedQuantity,
+ unit: item.unit,
+ pickQuantity: item.pickQty,
+ batchNo: Array.isArray(item.batchNo)
+ ? item.batchNo.join(",")
+ : item.batchNo,
+ })),
+ });
+ }
+
+ ElMessage({ message: "棰嗘枡鎴愬姛", type: "success" });
+ emit("saved");
+ dialogVisible.value = false;
+ } finally {
+ materialSaving.value = false;
+ }
+ };
+</script>
+
+<style scoped lang="scss">
+ .material-toolbar {
+ margin-bottom: 12px;
+ text-align: right;
+ }
+</style>
diff --git a/src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
new file mode 100644
index 0000000..4f052ed
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
@@ -0,0 +1,165 @@
+<template>
+ <el-dialog v-model="dialogVisible"
+ title="琛ユ枡"
+ width="1200px"
+ @close="handleClose">
+ <el-table v-loading="loading"
+ :data="tableData"
+ border
+ row-key="id">
+ <el-table-column label="宸ュ簭鍚嶇О"
+ prop="operationName"
+ min-width="140" />
+ <el-table-column label="鍘熸枡鍚嶇О"
+ prop="productName"
+ min-width="140" />
+ <el-table-column label="鍘熸枡鍨嬪彿"
+ prop="model"
+ min-width="140" />
+ <el-table-column label="璁¢噺鍗曚綅"
+ prop="unit"
+ width="100" />
+ <el-table-column label="闇�姹傛暟閲�"
+ prop="demandedQuantity"
+ width="100" />
+ <el-table-column label="棰嗙敤鏁伴噺"
+ prop="pickQuantity"
+ width="100" />
+ <el-table-column label="宸茶ˉ鏁伴噺"
+ prop="feedingQty"
+ width="100" />
+ <el-table-column label="琛ユ枡鏁伴噺"
+ min-width="150">
+ <template #default="{ row }">
+ <el-input-number v-model="row.newSupplementQty"
+ :min="0"
+ :precision="3"
+ :step="1"
+ controls-position="right"
+ placeholder="杈撳叆琛ユ枡鏁伴噺"
+ style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column label="琛ユ枡鍘熷洜"
+ min-width="200">
+ <template #default="{ row }">
+ <el-input v-model="row.newSupplementReason"
+ placeholder="杈撳叆琛ユ枡鍘熷洜"
+ maxlength="200"
+ show-word-limit />
+ </template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ :loading="submitting"
+ @click="handleSubmit">纭� 瀹�</el-button>
+ <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+ </span>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+ import { computed, ref, watch } from "vue";
+ import { ElMessage } from "element-plus";
+ import {
+ listMaterialPickingDetail,
+ updateMaterialPickingLedger,
+ } from "@/api/productionManagement/productionOrder.js";
+
+ const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ orderRow: { type: Object, default: null },
+ });
+ const emit = defineEmits(["update:modelValue", "saved"]);
+
+ const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: val => emit("update:modelValue", val),
+ });
+
+ const loading = ref(false);
+ const submitting = ref(false);
+ const tableData = ref([]);
+
+ const loadData = async () => {
+ if (!props.orderRow?.id) return;
+ loading.value = true;
+ try {
+ const res = await listMaterialPickingDetail(props.orderRow.id);
+ tableData.value = (res.data || []).map(item => ({
+ ...item,
+ newSupplementQty: 0,
+ newSupplementReason: "",
+ }));
+ } catch (e) {
+ console.error("鑾峰彇鐗╂枡鏄庣粏澶辫触锛�", e);
+ ElMessage.error("鑾峰彇鐗╂枡鏄庣粏澶辫触");
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ watch(
+ () => dialogVisible.value,
+ visible => {
+ if (visible) {
+ loadData();
+ }
+ }
+ );
+
+ const handleClose = () => {
+ tableData.value = [];
+ };
+
+ const handleSubmit = async () => {
+ const supplementList = tableData.value.filter(
+ item => item.newSupplementQty > 0
+ );
+ if (supplementList.length === 0) {
+ ElMessage.warning("璇疯嚦灏戣緭鍏ヤ竴鏉¤ˉ鏂欐暟閲�");
+ return;
+ }
+
+ const invalidRow = supplementList.find(item => !item.newSupplementReason);
+ if (invalidRow) {
+ ElMessage.warning("璇疯緭鍏ヨˉ鏂欏師鍥�");
+ return;
+ }
+
+ submitting.value = true;
+ try {
+ await updateMaterialPickingLedger({
+ productionOrderId: props.orderRow.id,
+ productionOrderPickDto: tableData.value.map(item => ({
+ id: item.id,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName,
+ bom: item.bom === true,
+ productModelId: item.productModelId,
+ demandedQuantity: item.demandedQuantity,
+ unit: item.unit,
+ pickQuantity: item.pickQuantity,
+ batchNo: item.batchNo,
+ feedingQuantity: item.newSupplementQty || 0,
+ feedingReason: item.newSupplementReason || "",
+ pickType: 2,
+ })),
+ });
+ ElMessage.success("琛ユ枡鎴愬姛");
+ dialogVisible.value = false;
+ emit("saved");
+ } catch (e) {
+ console.error("琛ユ枡澶辫触锛�", e);
+ ElMessage.error("琛ユ枡澶辫触");
+ } finally {
+ submitting.value = false;
+ }
+ };
+</script>
+
+<style scoped lang="scss">
+</style>
diff --git a/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue b/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
new file mode 100644
index 0000000..d1aa267
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
@@ -0,0 +1,225 @@
+<template>
+ <div class="print-container"
+ id="print-requisition">
+ <div class="print-content">
+ <div class="bill-title">鐢熶骇棰嗘枡鍗�</div>
+ <div class="info-grid">
+ <div class="info-row">
+ <div class="info-item">
+ <span class="label">鍒涘缓鏃ユ湡锛�</span>
+ <span class="value">{{ formatDate(orderRow?.createTime) }}</span>
+ </div>
+ <div class="info-item">
+ <span class="label">棰嗘枡鍗曞彿锛�</span>
+ <span class="value">{{ orderRow?.npsNo }}</span>
+ </div>
+ <div class="info-item">
+ <span class="label">鐢宠浜猴細</span>
+ <span class="value">{{ userName }}</span>
+ </div>
+ </div>
+ <div class="info-row">
+ <div class="info-item"
+ style="width: 50%;">
+ <span class="label">浜у搧鍚嶇О/鍨嬪彿锛�</span>
+ <span class="value">{{ orderRow?.productName }} / {{ orderRow?.model }}</span>
+ </div>
+ <div class="info-item"
+ style="width: 25%;">
+ <span class="label">鐢熶骇鏁伴噺锛�</span>
+ <span class="value">{{ orderRow?.quantity }}</span>
+ </div>
+ <div class="info-item"
+ style="width: 25%;">
+ <span class="label">闇�姹傛棩鏈燂細</span>
+ <span class="value">{{ formatDate(orderRow?.planCompleteTime) }}</span>
+ </div>
+ </div>
+ </div>
+ <table class="material-table">
+ <thead>
+ <tr>
+ <th width="50">搴忓彿</th>
+ <th>宸ュ簭鍚嶇О</th>
+ <th>瑙勬牸/鍚嶇О</th>
+ <th>鎵瑰彿</th>
+ <th width="80">闇�姹傛暟閲�</th>
+ <th width="80">棰嗘枡鏁伴噺</th>
+ <th width="60">鍗曚綅</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="(item, index) in materialList"
+ :key="index">
+ <td align="center">{{ index + 1 }}</td>
+ <td>{{ item.operationName || '-' }}</td>
+ <td>{{ item.materialName || item.productName }} {{ item.materialModel || item.model }}</td>
+ <td>{{ item.batchNo || '-' }}</td>
+ <td align="right">{{ item.demandedQuantity }}</td>
+ <td align="right">{{ item.pickQuantity || item.pickQty || 0 }}</td>
+ <td align="center">{{ item.unit }}</td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="print-footer">
+ <div class="footer-item">棰嗘枡锛歘_______________</div>
+ <div class="footer-item">鍙戞枡锛歘_______________</div>
+ <div class="footer-item">瀹℃牳锛歘_______________</div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+ import dayjs from "dayjs";
+ import useUserStore from "@/store/modules/user";
+ import { computed } from "vue";
+
+ const props = defineProps({
+ orderRow: {
+ type: Object,
+ default: () => ({}),
+ },
+ materialList: {
+ type: Array,
+ default: () => [],
+ },
+ });
+
+ const userStore = useUserStore();
+ const userName = computed(() => userStore.nickName || userStore.name || "-");
+
+ const formatDate = date => {
+ return date ? dayjs(date).format("YYYY骞碝M鏈圖D鏃�") : "-";
+ };
+</script>
+
+<style lang="scss">
+ /* 灞忓箷鏄剧ず鏍峰紡 */
+ .print-requisition-wrapper {
+ display: none;
+ }
+
+ /* 鎵撳嵃涓撶敤鏍峰紡 */
+ @media print {
+ @page {
+ size: landscape;
+ margin: 10mm;
+ }
+
+ /* 鍩虹鎵撳嵃璁剧疆 */
+ html,
+ body {
+ visibility: hidden;
+ height: auto !important;
+ overflow: visible !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ width: 100%;
+ }
+
+ /* 鏄惧紡鏄剧ず鎵撳嵃瀹瑰櫒鍙婂叾鎵�鏈夊瓙鍏冪礌 */
+ .print-requisition-wrapper,
+ .print-requisition-wrapper * {
+ visibility: visible !important;
+ }
+
+ /* 纭繚鎵撳嵃瀹瑰櫒鍗犳嵁鏁翠釜椤甸潰骞剁Щ闄ょ粷瀵瑰畾浣嶅共鎵� */
+ .print-requisition-wrapper {
+ display: block !important;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: auto;
+ background: white;
+ margin: 0 !important;
+ padding: 0 !important;
+ z-index: 99999;
+ }
+
+ .print-container {
+ width: 100% !important;
+ padding: 0 10mm; /* 浣跨敤瀵圭О鐨勫乏鍙冲唴杈硅窛纭繚灞呬腑 */
+ box-sizing: border-box;
+ height: auto;
+ overflow: visible;
+ color: #000;
+ font-family: "SimSun", "STSong", serif;
+ page-break-inside: avoid;
+ display: block;
+ .print-content {
+ width: 100%;
+ text-align: center;
+ }
+ .bill-title {
+ font-size: 20px;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 20px;
+ letter-spacing: 5px;
+ text-decoration: underline;
+ }
+
+ .info-grid {
+ margin-bottom: 10px;
+ font-size: 14px;
+
+ .info-row {
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: 8px;
+
+ .info-item {
+ width: 33.33%;
+ display: flex;
+ align-items: flex-end;
+
+ .label {
+ font-weight: bold;
+ white-space: nowrap;
+ }
+ .value {
+ border-bottom: 1px solid #000;
+ padding: 0 5px;
+ flex: 1;
+ min-height: 20px;
+ }
+ }
+ }
+ }
+
+ .material-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 2px solid #000;
+ font-size: 13px;
+
+ th,
+ td {
+ border: 1px solid #000 !important;
+ padding: 6px 4px;
+ height: 25px;
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ }
+
+ th {
+ background-color: #f2f2f2 !important;
+ font-weight: bold;
+ }
+ }
+
+ .print-footer {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 30px;
+ padding: 0 10px;
+
+ .footer-item {
+ font-size: 14px;
+ }
+ }
+ }
+ }
+</style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index fc64063..8d44b85 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -3,16 +3,8 @@
<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"
+ <el-form-item label="鐢熶骇璁㈠崟鍙�:">
+ <el-input v-model="searchForm.npsNo"
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
@@ -20,7 +12,7 @@
@change="handleQuery" />
</el-form-item>
<el-form-item label="浜у搧鍚嶇О:">
- <el-input v-model="searchForm.productCategory"
+ <el-input v-model="searchForm.productName"
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
@@ -28,21 +20,42 @@
@change="handleQuery" />
</el-form-item>
<el-form-item label="瑙勬牸:">
- <el-input v-model="searchForm.specificationModel"
+ <el-input v-model="searchForm.model"
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
style="width: 160px;"
@change="handleQuery" />
</el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="searchForm.status"
+ placeholder="璇烽�夋嫨"
+ style="width: 160px;"
+ @change="handleQuery">
+ <el-option label="寰呭紑濮�"
+ value="1" />
+ <el-option label="杩涜涓�"
+ value="2" />
+ <el-option label="宸插畬鎴�"
+ value="3" />
+ <el-option label="宸插彇娑�"
+ value="4" />
+ <el-option label="宸茬粨鏉�"
+ value="5" />
+ </el-select>
+ </el-form-item>
<el-form-item>
<el-button type="primary"
@click="handleQuery">鎼滅储</el-button>
+ <el-button type="info"
+ @click="handleReset">閲嶇疆</el-button>
</el-form-item>
</el-form>
- <div>
- <el-button type="primary" @click="isShowNewModal = true">鏂板</el-button>
- <el-button type="danger" @click="handleDelete">鍒犻櫎</el-button>
+ <div class="action-buttons">
+ <!-- <el-button type="primary"
+ @click="isShowNewModal = true">鏂板</el-button> -->
+ <el-button type="danger"
+ @click="handleDelete">閫�鍥�</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
</div>
</div>
@@ -54,14 +67,33 @@
:tableLoading="tableLoading"
:row-class-name="tableRowClassName"
:isSelection="true"
+ :selectable="row => !row.endOrder"
@selection-change="handleSelectionChange"
@pagination="pagination">
<template #completionStatus="{ row }">
- <el-progress
- :percentage="toProgressPercentage(row?.completionStatus)"
- :color="progressColor(toProgressPercentage(row?.completionStatus))"
- :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
- />
+ <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
+ :color="progressColor(toProgressPercentage(row?.completionStatus))"
+ :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
+ </template>
+ <template #processRouteStatus="{ row }">
+ <div v-if="row.processRouteStatus && row.processRouteStatus.length"
+ class="process-progress-container">
+ <div v-for="(item, index) in row.processRouteStatus"
+ :key="index"
+ class="process-step">
+ <div class="step-content">
+ <div class="step-circle"
+ :class="{ 'is-completed': item.percentage >= 100 }">
+ <span class="step-percentage"
+ :style="{ color: item.percentage >= 70 ? item.percentage >= 100 ? '#67c23a' : '#f56c6c' : '#000' }">{{ item.percentage }}%</span>
+ </div>
+ <div class="step-name">{{ item.name }}</div>
+ </div>
+ <div v-if="index < row.processRouteStatus.length - 1"
+ class="step-line"></div>
+ </div>
+ </div>
+ <span v-else>-</span>
</template>
</PIMTable>
</div>
@@ -90,15 +122,107 @@
</span>
</template>
</el-dialog>
-
+ <!-- 鏉ユ簮鏁版嵁寮圭獥 -->
+ <el-dialog v-model="sourceDataDialogVisible"
+ title="鏉ユ簮鏁版嵁"
+ width="1200px">
+ <div v-if="sourceRowData"
+ class="applyno-summary1">
+ <div class="summary-item">
+ <span class="summary-label">浜у搧鍚嶇О锛�</span>
+ <span class="summary-value">
+ <el-tag type="primary">{{ sourceRowData.productName || '-' }}</el-tag>
+ </span>
+ </div>
+ <div class="summary-item">
+ <span class="summary-label">瑙勬牸锛�</span>
+ <span class="summary-value">{{ sourceRowData.model || '-' }}</span>
+ </div>
+ <div class="summary-item">
+ <span class="summary-label">璁㈠崟闇�姹傛暟閲忥細</span>
+ <span class="summary-value">{{ sourceRowData.quantity || 0 }}</span>
+ </div>
+ </div>
+ <div class="source-table-container">
+ <div class="source-data-cards-container">
+ <div v-for="(item, index) in sourceTableData"
+ :key="index"
+ class="source-data-card">
+ <div class="card-body">
+ <div class="info-grid">
+ <div class="info-item">
+ <div class="info-label">璁″垝鍙�</div>
+ <div class="info-value">{{ item.mpsNo || '-' }}</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">鏁版嵁鏉ユ簮</div>
+ <div class="info-value">
+ <el-tag :type="item.source === '閿�鍞�' ? 'primary' : 'warning'">
+ {{ item.source || '鏈煡' }}
+ </el-tag>
+ </div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">鍚堝悓鍙�</div>
+ <div class="info-value">{{ item.salesContractNo || '-' }}</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">瀹㈡埛鍚嶇О</div>
+ <div class="info-value">{{ item.customerName || '-' }}</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">椤圭洰鍚嶇О</div>
+ <div class="info-value">{{ item.projectName || '-' }}</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">璁″垝闇�姹傛暟閲�</div>
+ <div class="info-value">{{ item.qtyRequired || 0 }} {{ item.unit || '' }}</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">鍗曚綅</div>
+ <div class="info-value">{{ item.unit || '-' }}</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">闇�姹傛棩鏈�</div>
+ <div class="info-value">{{ item.requiredDate ? dayjs(item.requiredDate).format('YYYY-MM-DD') : '-' }}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+ <MaterialLedgerDialog v-model="materialDialogVisible"
+ :order-row="currentMaterialOrder"
+ @saved="getList" />
+ <MaterialDetailDialog v-model="materialDetailDialogVisible"
+ :order-row="currentMaterialDetailOrder"
+ @confirmed="getList" />
+ <MaterialSupplementDialog v-model="materialSupplementDialogVisible"
+ :order-row="currentMaterialSupplementOrder"
+ @saved="getList" />
<new-product-order v-if="isShowNewModal"
- v-model:visible="isShowNewModal"
- @completed="handleQuery" />
+ v-model:visible="isShowNewModal"
+ @completed="handleQuery" />
+ <!-- 鎵撳嵃棰嗘枡鍗曠粍浠� -->
+ <div class="print-requisition-wrapper">
+ <PrintMaterialRequisition ref="printRef"
+ :order-row="printOrderRow"
+ :material-list="printMaterialList" />
+ </div>
</div>
</template>
<script setup>
- import { onMounted, ref } from "vue";
+ import {
+ computed,
+ defineAsyncComponent,
+ getCurrentInstance,
+ onMounted,
+ reactive,
+ ref,
+ toRefs,
+ } from "vue";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import { useRouter } from "vue-router";
@@ -106,48 +230,95 @@
productOrderListPage,
listProcessRoute,
bindingRoute,
- listProcessBom, delProductOrder,
+ listProcessBom,
+ delProductOrder,
+ getProductOrderSource,
+ updateProductOrder,
} from "@/api/productionManagement/productionOrder.js";
+ import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js";
import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
- import {fileDel} from "@/api/financialManagement/revenueManagement.js";
+ import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue";
+ import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue";
+ import MaterialSupplementDialog from "@/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue";
+ import PrintMaterialRequisition from "@/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
- const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue"));
+ import { listPage } from "@/api/productionManagement/processRoute.js";
+ import {
+ listMaterialPickingDetail,
+ listMaterialPickingBom,
+ } from "@/api/productionManagement/productionOrder.js";
+ const NewProductOrder = defineAsyncComponent(() =>
+ import("@/views/productionManagement/productionOrder/New.vue")
+ );
const { proxy } = getCurrentInstance();
const router = useRouter();
const isShowNewModal = ref(false);
+ const sourceDataDialogVisible = ref(false);
+ const sourceTableData = ref([]);
+ const sourceRowData = ref(null);
+ const sourcePage = reactive({
+ total: 0,
+ });
- const tableColumn = ref([
+ const processColumnWidth = computed(() => {
+ if (!tableData.value || tableData.value.length === 0) return "200px";
+ const maxProcesses = Math.max(
+ ...tableData.value.map(row => row.processRouteStatus?.length || 0)
+ );
+ if (maxProcesses === 0) return "100px";
+ // 姣忎釜宸ュ簭鍦嗗湀 36px + 绾挎潯 30px = 66px锛岄澶栧姞 60px 杈硅窛鍜屾枃瀛楃┖闂�
+ return `${maxProcesses * 66 + 60}px`;
+ });
+
+ const tableColumn = computed(() => [
{
label: "鐢熶骇璁㈠崟鍙�",
prop: "npsNo",
- width: '120px',
+ width: "150px",
},
+ // 1.寰呭紑濮嬨��2.杩涜涓��3.宸插畬鎴愩��4.宸插彇娑堛��5.宸茬粨鏉�
{
- label: "閿�鍞悎鍚屽彿",
- prop: "salesContractNo",
- width: '150px',
- },
- {
- label: "瀹㈡埛鍚嶇О",
- prop: "customerName",
- width: '200px',
+ label: "鐘舵��",
+ prop: "status",
+ width: "150px",
+ dataType: "tag",
+ formatData: val =>
+ val === 1
+ ? "寰呭紑濮�"
+ : val === 2
+ ? "杩涜涓�"
+ : val === 3
+ ? "宸插畬鎴�"
+ : val === 5
+ ? "宸茬粨鏉�"
+ : "宸插彇娑�",
+ formatType: val =>
+ val === 1
+ ? "primary"
+ : val === 2
+ ? "warning"
+ : val === 3
+ ? "success"
+ : val === 5
+ ? "danger"
+ : "info",
},
{
label: "浜у搧鍚嶇О",
- prop: "productCategory",
- width: '120px',
+ prop: "productName",
+ width: "120px",
},
{
label: "瑙勬牸",
- prop: "specificationModel",
- width: '120px',
+ prop: "model",
+ width: "120px",
},
{
label: "宸ヨ壓璺嚎缂栧彿",
prop: "processRouteCode",
- width: '200px',
+ width: "200px",
},
{
label: "闇�姹傛暟閲�",
@@ -156,6 +327,13 @@
{
label: "瀹屾垚鏁伴噺",
prop: "completeQuantity",
+ },
+ {
+ label: "宸ュ簭鐢熶骇杩涘害",
+ prop: "processRouteStatus",
+ dataType: "slot",
+ slot: "processRouteStatus",
+ width: processColumnWidth.value,
},
{
dataType: "slot",
@@ -177,8 +355,8 @@
width: 120,
},
{
- label: "浜や粯鏃ユ湡",
- prop: "deliveryDate",
+ label: "璁″垝瀹屾垚鏃堕棿",
+ prop: "planCompleteTime",
formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
width: 120,
},
@@ -187,11 +365,12 @@
label: "鎿嶄綔",
align: "center",
fixed: "right",
- width: 200,
+ width: 280,
operation: [
{
name: "宸ヨ壓璺嚎",
type: "text",
+ showHide: row => row.processRouteCode,
clickFun: row => {
showRouteItemModal(row);
},
@@ -199,16 +378,83 @@
{
name: "缁戝畾宸ヨ壓璺嚎",
type: "text",
- showHide: row => !row.processRouteCode,
+ showHide: row => !row.processRouteCode && !row.endOrder,
clickFun: row => {
- openBindRouteDialog(row);
+ openBindRouteDialog(row, "add");
},
},
{
- name: "浜у搧缁撴瀯",
+ name: "鏇存崲宸ヨ壓璺嚎",
+ type: "text",
+ showHide: row => row.processRouteCode && !row.endOrder,
+ clickFun: row => {
+ openBindRouteDialog(row, "change");
+ },
+ },
+ {
+ name: "鏉ユ簮",
type: "text",
clickFun: row => {
- showProductStructure(row);
+ showSourceData(row);
+ },
+ },
+ {
+ name: "棰嗘枡",
+ type: "text",
+ color: "#5EC7AB",
+ showHide: row => !row.endOrder && !row.returned,
+ clickFun: row => {
+ openMaterialDialog(row);
+ },
+ },
+ {
+ name: "琛ユ枡",
+ type: "text",
+ color: "#5EC7AB",
+ showHide: row => !row.endOrder && !row.returned,
+ clickFun: row => {
+ openMaterialSupplementDialog(row);
+ },
+ },
+ {
+ name: "棰嗘枡璇︽儏",
+ type: "text",
+ color: "#5EC7AB",
+ clickFun: row => {
+ openMaterialDetailDialog(row);
+ },
+ },
+ {
+ name: "鎵撳嵃棰嗘枡鍗�",
+ type: "text",
+ color: "#5EC7AB",
+ showHide: row => !row.endOrder,
+ clickFun: row => {
+ handlePrint(row);
+ },
+ },
+ {
+ name: "鐢熶骇杩芥函",
+ type: "text",
+ color: "#409eff",
+ clickFun: row => {
+ router.push({
+ path: "/productionManagement/productionTraceability",
+ query: {
+ npsNo: row.npsNo,
+ productName: row.productName,
+ model: row.model,
+ },
+ });
+ },
+ },
+ {
+ name: "缁撴潫璁㈠崟",
+ type: "text",
+ color: "red",
+ showHide: row => !row.endOrder,
+ clickFun: row => {
+ handleEndOrder(row);
},
},
],
@@ -225,11 +471,13 @@
const data = reactive({
searchForm: {
+ npsNo: "",
customerName: "",
salesContractNo: "",
projectName: "",
- productCategory: "",
- specificationModel: "",
+ productName: "",
+ model: "",
+ status: "",
},
});
const { searchForm } = toRefs(data);
@@ -253,18 +501,18 @@
// 娣诲姞琛ㄨ绫诲悕鏂规硶
const tableRowClassName = ({ row }) => {
- if (!row.deliveryDate) return '';
- if (row.isFh) return '';
+ if (!row.planCompleteTime) return "";
+ if (row.isFh) return "";
const diff = row.deliveryDaysDiff;
if (diff === 15) {
- return 'yellow';
+ return "yellow";
} else if (diff === 10) {
- return 'pink';
+ return "pink";
} else if (diff === 2) {
- return 'purple';
+ return "purple";
} else if (diff < 2) {
- return 'red';
+ return "red";
}
};
@@ -277,10 +525,52 @@
orderId: null,
routeId: null,
});
+ const materialDialogVisible = ref(false);
+ const currentMaterialOrder = ref(null);
+ const materialDetailDialogVisible = ref(false);
+ const currentMaterialDetailOrder = ref(null);
+ const materialSupplementDialogVisible = ref(false);
+ const currentMaterialSupplementOrder = ref(null);
- const openBindRouteDialog = async row => {
+ // 鎵撳嵃鐩稿叧
+ const printOrderRow = ref(null);
+ const printMaterialList = ref([]);
+ const handlePrint = async row => {
+ printOrderRow.value = row;
+ proxy.$modal.loading("姝e湪鑾峰彇棰嗘枡鏁版嵁...");
+ try {
+ printMaterialList.value = [];
+ const detailRes = await listMaterialPickingDetail(row.id);
+ const detailList = Array.isArray(detailRes?.data)
+ ? detailRes.data
+ : detailRes?.data?.records || [];
+
+ if (detailList.length > 0) {
+ printMaterialList.value = detailList;
+ }
+
+ if (printMaterialList.value.length === 0) {
+ proxy.$modal.msgWarning("鏆傛棤棰嗘枡鏁版嵁");
+ return;
+ }
+
+ // 绛夊緟 DOM 鏇存柊鍚庢墽琛屾墦鍗�
+ proxy.$nextTick(() => {
+ setTimeout(() => {
+ window.print();
+ }, 800);
+ });
+ } catch (e) {
+ console.error("鑾峰彇棰嗘枡鏁版嵁澶辫触锛�", e);
+ proxy.$modal.msgError("鑾峰彇棰嗘枡鏁版嵁澶辫触");
+ } finally {
+ proxy.$modal.closeLoading();
+ }
+ };
+
+ const openBindRouteDialog = async (row, type) => {
bindForm.orderId = row.id;
- bindForm.routeId = null;
+ bindForm.routeId = type === "add" ? null : row.processRouteCode;
bindRouteDialogVisible.value = true;
routeOptions.value = [];
if (!row.productModelId) {
@@ -290,8 +580,8 @@
}
bindRouteLoading.value = true;
try {
- const res = await listProcessRoute({ productModelId: row.productModelId });
- routeOptions.value = res.data || [];
+ const res = await listPage({ productModelId: row.productModelId });
+ routeOptions.value = res.data.records || [];
} catch (e) {
console.error("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触锛�", e);
proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触");
@@ -309,7 +599,7 @@
try {
await bindingRoute({
id: bindForm.orderId,
- routeId: bindForm.routeId,
+ technologyRoutingId: bindForm.routeId,
});
proxy.$modal.msgSuccess("缁戝畾鎴愬姛");
bindRouteDialogVisible.value = false;
@@ -320,6 +610,35 @@
} finally {
bindRouteSaving.value = false;
}
+ };
+
+ const openMaterialDialog = row => {
+ currentMaterialOrder.value = row;
+ materialDialogVisible.value = true;
+ };
+
+ const openMaterialDetailDialog = async row => {
+ currentMaterialDetailOrder.value = row;
+ materialDetailDialogVisible.value = true;
+ };
+
+ const openMaterialSupplementDialog = row => {
+ currentMaterialSupplementOrder.value = row;
+ materialSupplementDialogVisible.value = true;
+ };
+
+ const handleReset = () => {
+ searchForm.value = {
+ ...searchForm.value,
+ npsNo: "",
+ customerName: "",
+ salesContractNo: "",
+ projectName: "",
+ productName: "",
+ model: "",
+ status: "",
+ };
+ handleQuery();
};
// 鏌ヨ鍒楄〃
@@ -349,10 +668,35 @@
const params = { ...searchForm.value, ...page };
params.entryDate = undefined;
productOrderListPage(params)
- .then(res => {
- tableLoading.value = false;
- tableData.value = res.data.records;
+ .then(async res => {
+ const records = res.data.records || [];
+ // 涓烘瘡涓鍗曟煡璇㈠搴旂殑宸ュ簭杩涘害鏁版嵁
+ const processPromises = records.map(async item => {
+ if (item.npsNo) {
+ try {
+ const workOrderRes = await productWorkOrderPage({
+ npsNo: item.npsNo,
+ size: 100,
+ });
+ const workOrders = workOrderRes.data.records || [];
+ // 鎸夌収宸ュ簭椤哄簭鎺掑簭锛堝鏋滄湁椤哄簭瀛楁锛屽亣璁句负 orderNum 鎴栨寜杩斿洖椤哄簭锛�
+ // 杞崲涓� processRouteStatus 鏍煎紡
+ const processRouteStatus = workOrders.map(wo => ({
+ name: wo.operationName || "鏈煡宸ュ簭",
+ percentage: wo.completionStatus > 100 ? 100 : wo.completionStatus,
+ }));
+ return { ...item, processRouteStatus };
+ } catch (error) {
+ console.error(`鑾峰彇宸ュ崟 ${item.npsNo} 杩涘害澶辫触:`, error);
+ return { ...item, processRouteStatus: [] };
+ }
+ }
+ return { ...item, processRouteStatus: [] };
+ });
+
+ tableData.value = await Promise.all(processPromises);
page.total = res.data.total;
+ tableLoading.value = false;
})
.catch(() => {
tableLoading.value = false;
@@ -372,13 +716,16 @@
path: "/productionManagement/processRouteItem",
query: {
id: data.id,
+ bomId: data.orderBomId,
processRouteCode: data.processRouteCode || "",
- productName: data.productName || "",
- model: data.model || "",
- bomNo: data.bomNo || "",
+ productName: row.productName || "",
+ model: row.model || "",
+ bomNo: row.bomNo || "",
description: data.description || "",
+ quantity: row.quantity || 0,
orderId,
type: "order",
+ editable: !row.endOrder,
},
});
} catch (e) {
@@ -393,39 +740,65 @@
query: {
id: row.id,
bomNo: row.bomNo || "",
- productName: row.productCategory || "",
- productModelName: row.specificationModel || "",
+ productName: row.productName || "",
+ productModelName: row.model || "",
orderId: row.id,
type: "order",
},
});
};
+ // 鏌ョ湅鏉ユ簮鐢熶骇璁″垝鏁版嵁
+ const showSourceData = row => {
+ // 瀛樺偍鐐瑰嚮鏉ユ簮鎸夐挳鏃朵紶閫掔殑row鍙傛暟
+ sourceRowData.value = row;
+ // 璋冪敤API鑾峰彇鏉ユ簮鏁版嵁
+ getProductOrderSource(row.id)
+ .then(res => {
+ if (res.code === 200) {
+ // 鐩存帴瀛樺偍杩斿洖鐨勬墎骞冲寲鏁版嵁
+ sourceTableData.value = res.data || [];
+ sourcePage.total = sourceTableData.value.length;
+ // 鎵撳紑寮圭獥
+ sourceDataDialogVisible.value = true;
+ } else {
+ proxy.$modal.msgError(res.msg || "鑾峰彇鏉ユ簮鏁版嵁澶辫触");
+ }
+ })
+ .catch(err => {
+ proxy.$modal.msgError("鑾峰彇鏉ユ簮鏁版嵁澶辫触");
+ console.error(err);
+ });
+ };
+
// 琛ㄦ牸閫夋嫨鏁版嵁
- const handleSelectionChange = (selection) => {
+ const handleSelectionChange = selection => {
selectedRows.value = selection;
};
const handleDelete = () => {
let ids = [];
if (selectedRows.value.length > 0) {
- ids = selectedRows.value.map((item) => item.id);
+ ids = selectedRows.value.map(item => item.id);
} else {
proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
return;
}
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ ElMessageBox.confirm("鏄惁閫�鍥炶鐢熶骇璁㈠崟锛�", "閫�鍥�", {
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "warning",
- }).then(() => {
- delProductOrder(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
+ })
+ .then(() => {
+ console.log(ids, "ids");
+ delProductOrder(ids).then(res => {
+ proxy.$modal.msgSuccess("閫�鍥炴垚鍔�");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
});
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
};
// 瀵煎嚭
@@ -436,11 +809,35 @@
type: "warning",
})
.then(() => {
- proxy.download("/productOrder/export", {...searchForm.value}, "鐢熶骇璁㈠崟.xlsx");
+ proxy.download(
+ "/productOrder/export",
+ { ...searchForm.value },
+ "鐢熶骇璁㈠崟.xlsx"
+ );
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
});
+ };
+
+ // 缁撴潫璁㈠崟
+ const handleEndOrder = row => {
+ ElMessageBox.confirm(`鏄惁纭缁撴潫璁㈠崟锛�${row.npsNo}锛焋, "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ const params = {
+ id: row.id,
+ endOrder: true,
+ };
+ updateProductOrder(params).then(() => {
+ proxy.$modal.msgSuccess("缁撴潫璁㈠崟鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {});
};
const handleConfirmRoute = () => {};
@@ -451,23 +848,177 @@
</script>
<style scoped lang="scss">
-.search_form{
- align-items: start;
-}
+ .search_form {
+ align-items: start;
+ }
-::v-deep .yellow {
- background-color: #FAF0DE;
-}
+ .action-buttons {
+ display: flex;
+ flex-wrap: nowrap;
+ gap: 8px;
+ }
-::v-deep .pink {
- background-color: #FAE1DE;
-}
+ :deep(.yellow) {
+ background-color: #faf0de;
+ }
-::v-deep .red {
- background-color: #f80202;
-}
+ :deep(.pink) {
+ background-color: #fae1de;
+ }
-::v-deep .purple{
- background-color: #F4DEFA;
-}
+ :deep(.red) {
+ background-color: #f80202;
+ }
+
+ :deep(.purple) {
+ background-color: #f4defa;
+ }
+ .table_list {
+ margin-top: unset;
+ }
+
+ .process-progress-container {
+ display: inline-flex;
+ align-items: center;
+ padding: 10px 0;
+ white-space: nowrap;
+
+ .process-step {
+ display: flex;
+ align-items: center;
+ position: relative;
+
+ .step-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ z-index: 1;
+
+ .step-circle {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: 2px solid #409eff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #fff;
+ margin-bottom: 4px;
+
+ .step-percentage {
+ font-size: 11px;
+ font-weight: bold;
+ }
+
+ &.is-completed {
+ border-color: #67c23a;
+ .step-percentage {
+ color: #67c23a;
+ }
+ }
+ }
+
+ .step-name {
+ font-size: 12px;
+ color: #606266;
+ white-space: nowrap;
+ }
+ }
+
+ .step-line {
+ width: 30px;
+ height: 1px;
+ background-color: #dcdfe6;
+ margin: 0 -2px;
+ margin-top: -20px; // 鍚戜笂鍋忕Щ浠ュ榻愬渾蹇�
+ }
+ }
+ }
+</style>
+<style lang="scss">
+ .status-cell {
+ font-weight: 600;
+ color: #409eff;
+ font-family: "Courier New", monospace;
+ text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
+ }
+
+ .source-table-container {
+ margin-top: 20px;
+ }
+
+ .source-data-cards-container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ max-height: 500px;
+ overflow-y: auto;
+ padding: 10px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ padding-bottom: 20px;
+
+ .source-data-card {
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+
+ .card-body {
+ padding: 20px;
+
+ .info-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 16px;
+
+ .info-item {
+ display: flex;
+ flex-direction: column;
+
+ .info-label {
+ font-size: 12px;
+ color: #909399;
+ margin-bottom: 4px;
+ font-weight: 500;
+ }
+
+ .info-value {
+ font-size: 14px;
+ color: #303133;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .applyno-summary1 {
+ padding: 16px 20px;
+ background: #f5f7fa;
+ border-bottom: 1px solid #e4e7ed;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+
+ .summary-item {
+ display: flex;
+ align-items: center;
+ margin-right: 20px;
+
+ .summary-label {
+ font-size: 13px;
+ color: #909399;
+ margin-right: 8px;
+ font-weight: 500;
+ }
+
+ .summary-value {
+ font-size: 14px;
+ color: #303133;
+ font-weight: 500;
+ }
+ }
+ }
</style>
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
index fb0ee74..28077b6 100644
--- a/src/views/productionManagement/productionProcess/Edit.vue
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -46,6 +46,12 @@
<el-form-item label="鏄惁璐ㄦ" prop="isQuality">
<el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
</el-form-item>
+ <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
+ <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
+ <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
+ </el-form-item>
<el-form-item label="澶囨敞" prop="remark">
<el-input v-model="formState.remark" type="textarea" />
</el-form-item>
@@ -87,6 +93,8 @@
remark: props.record.remark,
salaryQuota: props.record.salaryQuota,
isQuality: props.record.isQuality,
+ inbound: props.record.inbound,
+ reportWork: props.record.reportWork,
});
const isShow = computed({
@@ -109,6 +117,8 @@
remark: newRecord.remark || '',
salaryQuota: newRecord.salaryQuota || '',
isQuality: props.record.isQuality,
+ inbound: newRecord.inbound,
+ reportWork: newRecord.reportWork,
};
}
}, { immediate: true, deep: true });
@@ -124,6 +134,8 @@
remark: props.record.remark || '',
salaryQuota: props.record.salaryQuota || '',
isQuality: props.record.isQuality,
+ inbound: props.record.inbound,
+ reportWork: props.record.reportWork,
};
}
});
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
index a5f00aa..0b3fd47 100644
--- a/src/views/productionManagement/productionProcess/New.vue
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -48,6 +48,12 @@
<el-form-item label="鏄惁璐ㄦ" prop="isQuality">
<el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
</el-form-item>
+ <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
+ <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
+ <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
+ </el-form-item>
<el-form-item label="澶囨敞" prop="remark">
<el-input v-model="formState.remark" type="textarea" />
</el-form-item>
@@ -82,6 +88,8 @@
remark: '',
salaryQuota: '',
isQuality: false,
+ inbound: false,
+ reportWork: false,
});
const isShow = computed({
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
index ffe13fc..216e73b 100644
--- a/src/views/productionManagement/productionProcess/index.vue
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -1,319 +1,1085 @@
<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>
+ <div class="process-config-container">
+ <!-- 宸︿晶宸ュ簭鍒楄〃 -->
+ <div class="process-list-section">
+ <div class="section-header">
+ <h3 class="section-title">宸ュ簭鍒楄〃</h3>
<el-button type="primary"
- @click="handleQuery">鎼滅储</el-button>
+ size="small"
+ @click="handleAddProcess">
+ <el-icon>
+ <Plus />
+ </el-icon>鏂板宸ュ簭
+ </el-button>
+ </div>
+ <div class="process-card-list"
+ v-loading="processLoading">
+ <div v-for="process in processValueList"
+ :key="process.id"
+ class="process-card"
+ :class="{ active: selectedProcess?.id === process.id }"
+ @click="selectProcess(process)">
+ <div class="card-header">
+ <div class="process-name">{{ process.name }} <span class="process-code">{{ process.no }}</span></div>
+ <div class="card-actions">
+ <el-button link
+ type="primary"
+ @click.stop="handleEditProcess(process)">
+ <el-icon>
+ <Edit />
+ </el-icon>
+ 缂栬緫
+ </el-button>
+ <el-button link
+ type="danger"
+ @click.stop="handleDeleteProcess(process)">
+ <el-icon>
+ <Delete />
+ </el-icon>
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <div class="card-body">
+ <!-- <div class="process-name">{{ process.name }}</div> -->
+ <div class="process-desc">{{ process.remark || '鏆傛棤鎻忚堪' }}</div>
+ <div class="process-device">鍏宠仈璁惧: {{ deviceOptions.find(item => item.id === Number(process.deviceLedgerId))?.deviceName|| '鏈叧鑱�' }}</div>
+ </div>
+ <div class="card-footer">
+ <div class="status-tag">
+ <el-tag size="small"
+ :type="process.isQuality ? 'warning' : 'info'">
+ {{ process.isQuality ? '璐ㄦ' : '闈炶川妫�' }}
+ </el-tag>
+ <el-tag size="small"
+ style="margin-left: 8px"
+ :type="process.isProduction ? 'warning' : 'info'">
+ {{ process.isProduction ? '鐢熶骇' : '涓嶇敓浜�' }}
+ </el-tag>
+ <el-tag v-if="process.type !== null && process.type !== undefined"
+ size="small"
+ :type="process.type == 1 ? 'primary' : 'success'"
+ style="margin-left: 8px">
+ {{ process.type == 0 ? '璁℃椂' : '璁′欢' }}
+ </el-tag>
+ </div>
+ <span class="param-count">宸ヨ祫瀹氶: 楼{{ process.salaryQuota || 0 }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- 鍙充晶鍙傛暟鍒楄〃 -->
+ <div class="param-list-section">
+ <div class="section-header">
+ <h3 class="section-title">
+ {{ selectedProcess ? selectedProcess.name + ' - 鍙傛暟閰嶇疆' : '璇烽�夋嫨宸ュ簭' }}
+ </h3>
+ <el-button type="primary"
+ size="small"
+ :disabled="!selectedProcess"
+ @click="openParamDialog">
+ <el-icon>
+ <Plus />
+ </el-icon>閫夋嫨鍙傛暟
+ </el-button>
+ </div>
+ <div class="param-table-wrapper">
+ <PIMTable v-if="selectedProcess"
+ rowKey="id"
+ :column="paramColumn"
+ :tableData="paramList"
+ :page="paramPage2"
+ height="calc(100vh - 280px)"
+ :isSelection="false"
+ @pagination="handleParamPagination" />
+ <div v-else
+ class="empty-tip">
+ <el-empty description="璇蜂粠宸︿晶閫夋嫨涓�涓伐搴�" />
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- 宸ュ簭鏂板/缂栬緫瀵硅瘽妗� -->
+ <el-dialog v-model="processDialogVisible"
+ :title="isProcessEdit ? '缂栬緫宸ュ簭' : '鏂板宸ュ簭'"
+ width="500px">
+ <el-form :model="processForm"
+ :rules="processRules"
+ ref="processFormRef"
+ label-width="100px">
+ <el-form-item label="宸ュ簭缂栫爜"
+ prop="no">
+ <el-input v-model="processForm.no"
+ placeholder="璇疯緭鍏ュ伐搴忕紪鐮�" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭鍚嶇О"
+ prop="name">
+ <el-input v-model="processForm.name"
+ placeholder="璇疯緭鍏ュ伐搴忓悕绉�" />
+ </el-form-item>
+ <el-form-item label="宸ヨ祫瀹氶"
+ prop="salaryQuota">
+ <el-input v-model="processForm.salaryQuota"
+ type="number"
+ :step="0.001" />
+ </el-form-item>
+ <el-form-item label="鏄惁璐ㄦ"
+ prop="isQuality">
+ <el-switch v-model="processForm.isQuality" />
+ </el-form-item>
+ <el-form-item label="鏄惁鐢熶骇"
+ prop="isProduction">
+ <el-switch v-model="processForm.isProduction" />
+ </el-form-item>
+ <el-form-item label="璁¤垂绫诲瀷"
+ prop="type">
+ <el-radio-group v-model="processForm.type">
+ <el-radio :label="0">璁℃椂</el-radio>
+ <el-radio :label="1">璁′欢</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="鍏宠仈璁惧"
+ prop="deviceLedgerId">
+ <el-select v-model="processForm.deviceLedgerId"
+ placeholder="璇烽�夋嫨璁惧"
+ clearable
+ filterable
+ style="width: 100%">
+ <el-option v-for="item in deviceOptions"
+ :key="item.id"
+ :label="item.deviceName"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="宸ュ簭鎻忚堪"
+ prop="remark">
+ <el-input v-model="processForm.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ伐搴忔弿杩�" />
</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>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" @click="handleProcessSubmit">纭畾</el-button>
+ <el-button @click="processDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <!-- 閫夋嫨鍙傛暟瀵硅瘽妗� -->
+ <el-dialog v-model="paramDialogVisible"
+ title="閫夋嫨鍙傛暟"
+ width="1000px">
+ <div class="param-select-container">
+ <!-- 宸︿晶鍙傛暟鍒楄〃 -->
+ <div class="param-list-area">
+ <div class="area-title">鍙�夊弬鏁�</div>
+ <div class="search-box">
+ <el-input v-model="paramSearchKeyword"
+ placeholder="璇疯緭鍏ュ弬鏁板悕绉版悳绱�"
+ clearable
+ size="small"
+ @input="handleSelectParam">
+ <template #prefix>
+ <el-icon>
+ <Search />
+ </el-icon>
+ </template>
+ </el-input>
+ </div>
+ <el-table :data="filteredParamList"
+ height="300"
+ border
+ highlight-current-row
+ @current-change="handleParamSelect">
+ <el-table-column prop="paramName"
+ label="鍙傛暟鍚嶇О" />
+ <el-table-column prop="paramType"
+ label="鍙傛暟绫诲瀷">
+ <template #default="scope">
+ <el-tag size="small"
+ :type="getParamTypeTag(scope.row.paramType)">
+ {{ getParamTypeText(scope.row.paramType) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- 鍒嗛〉鎺т欢 -->
+ <div class="pagination-container"
+ style="margin-top: 10px;">
+ <el-pagination v-model:current-page="paramPage.current"
+ v-model:page-size="paramPage.size"
+ :page-sizes="[10, 20, 50, 100]"
+ layout="total, sizes, prev, pager, next, jumper"
+ :total="paramPage.total"
+ @size-change="handleParamSizeChange"
+ @current-change="handleParamCurrentChange"
+ size="small" />
+ </div>
+ </div>
+ <!-- 鍙充晶鍙傛暟璇︽儏 -->
+ <div class="param-detail-area">
+ <div class="area-title">鍙傛暟璇︽儏</div>
+ <el-form v-if="selectedParam"
+ :model="selectedParam"
+ label-width="100px"
+ class="param-detail-form">
+ <el-form-item label="鍙傛暟鍚嶇О">
+ <span class="detail-text">{{ selectedParam.paramName }}</span>
+ </el-form-item>
+ <el-form-item label="鍙傛暟绫诲瀷">
+ <el-tag size="small"
+ :type="getParamTypeTag(selectedParam.paramType)">
+ {{ getParamTypeText(selectedParam.paramType) }}
+ </el-tag>
+ </el-form-item>
+ <el-form-item label="鍙傛暟鏍煎紡">
+ <span class="detail-text">{{ selectedParam.paramFormat || '-' }}</span>
+ </el-form-item>
+ <el-form-item label="鍗曚綅">
+ <span class="detail-text">{{ selectedParam.unit || '-' }}</span>
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍊�">
+ <el-input v-model="selectedParam.standardValue"
+ placeholder="璇疯緭鍏ラ粯璁ゅ��" />
+ </el-form-item>
+ </el-form>
+ <el-empty v-else
+ description="璇蜂粠宸︿晶閫夋嫨鍙傛暟" />
+ </div>
</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" />
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" :disabled="!selectedParam" @click="handleParamSubmit">纭畾</el-button>
+ <el-button @click="paramDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <!-- 缂栬緫鍙傛暟瀵硅瘽妗� -->
+ <el-dialog v-model="editParamDialogVisible"
+ title="缂栬緫鍙傛暟"
+ width="600px">
+ <el-form :model="editParamForm"
+ :rules="editParamRules"
+ ref="editParamFormRef"
+ label-width="120px">
+ <el-form-item label="鍙傛暟鍚嶇О">
+ <span class="detail-text">{{ editParamForm.paramName }}</span>
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍊�"
+ prop="standardValue">
+ <el-input v-model="editParamForm.standardValue"
+ placeholder="璇疯緭鍏ユ爣鍑嗗��" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="editParamDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary"
+ @click="handleEditParamSubmit">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
</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 { ref, reactive, computed, onMounted } from "vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import { Plus, Edit, Delete, Search } from "@element-plus/icons-vue";
+ import PIMTable from "@/components/PIMTable/PIMTable.vue";
+ import { listType } from "@/api/system/dict/type";
import {
- listPage,
+ add,
+ update,
del,
- importData,
- downloadTemplate,
+ list as getProcessListApi,
+ processList,
+ getProcessParamList,
+ addProcessParam,
+ editProcessParam,
+ deleteProcessParam,
} from "@/api/productionManagement/productionProcess.js";
- import { getToken } from "@/utils/auth";
+ import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+ import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js";
- const data = reactive({
- searchForm: {
- name: "",
- no: "",
- },
+ // 宸ュ簭鍒楄〃鏁版嵁
+ const processValueList = ref([]);
+ const selectedProcess = ref(null);
+ const processLoading = ref(false);
+ const deviceOptions = ref([]);
+
+ // 宸ュ簭宸查�夊弬鏁拌〃鏍煎垎椤碉紙鎺ュ彛涓�娆¤繑鍥炲叏閲忥級
+ const paramPage2 = ref({
+ current: 1,
+ size: 10,
+ total: 0,
});
- const { searchForm } = toRefs(data);
- const tableColumn = ref([
+ const paramListRaw = ref([]);
+ const paramList = computed(() => {
+ const all = paramListRaw.value;
+ const { current, size } = paramPage2.value;
+ const start = (current - 1) * size;
+ return all.slice(start, start + size);
+ });
+ const paramLoading = ref(false);
+
+ // 鏁版嵁瀛楀吀
+ const dictTypes = ref([]);
+
+ // 宸ュ簭瀵硅瘽妗�
+ const processDialogVisible = ref(false);
+ const isProcessEdit = ref(false);
+ const processFormRef = ref(null);
+ const processForm = reactive({
+ id: null,
+ no: "",
+ name: "",
+ salaryQuota: null,
+ isQuality: false,
+ isProduction: false,
+ remark: "",
+ deviceLedgerId: null,
+ type: 0,
+ });
+ const processRules = {
+ no: [{ required: true, message: "璇疯緭鍏ュ伐搴忕紪鐮�", trigger: "blur" }],
+ name: [{ required: true, message: "璇疯緭鍏ュ伐搴忓悕绉�", trigger: "blur" }],
+ salaryQuota: [
+ {
+ required: false,
+ message: "璇疯緭鍏ュ伐璧勫畾棰�",
+ trigger: "blur",
+ validator: (rule, value, callback) => {
+ if (isNaN(value) || value < 0) {
+ callback(new Error("宸ヨ祫瀹氶蹇呴』鏄潪璐熸暟瀛�"));
+ } else {
+ callback();
+ }
+ },
+ },
+ ],
+ deviceLedgerId: [
+ { required: false, message: "璇烽�夋嫨璁惧", trigger: "change" },
+ ],
+ type: [{ required: false, message: "璇烽�夋嫨璁¤垂绫诲瀷", trigger: "change" }],
+ };
+
+ // 鍙傛暟瀵硅瘽妗�
+ const paramDialogVisible = ref(false);
+ const availableParamList = ref([]);
+ const filteredParamList = ref([]);
+ const selectedParam = ref(null);
+ const paramSearchKeyword = ref("");
+
+ // 鍙�夊弬鏁板垎椤�
+ const paramPage = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+ });
+
+ // 缂栬緫鍙傛暟瀵硅瘽妗�
+ const editParamDialogVisible = ref(false);
+ const editParamFormRef = ref(null);
+ const editParamForm = reactive({
+ id: null,
+ technologyOperationId: null,
+ technologyParamId: null,
+ paramName: "",
+ standardValue: null,
+ });
+ const editParamRules = {
+ standardValue: [
+ {
+ required: true,
+ message: "璇疯緭鍏ユ爣鍑嗗��",
+ trigger: "blur",
+ validator: (rule, value, callback) => {
+ if (value === null || value === undefined || value === "") {
+ callback(new Error("璇疯緭鍏ユ爣鍑嗗��"));
+ } else {
+ callback();
+ }
+ },
+ },
+ ],
+ };
+
+ // 鍙傛暟琛ㄦ牸鍒楅厤缃�
+ const paramColumn = ref([
{
- label: "宸ュ簭缂栧彿",
- prop: "no",
+ label: "鍙傛暟鍚嶇О",
+ prop: "paramName",
},
{
- label: "宸ュ簭鍚嶇О",
- prop: "name",
- },
- {
- label: "宸ュ簭绫诲瀷",
- prop: "typeText",
- },
- {
- label: "宸ヨ祫瀹氶",
- prop: "salaryQuota",
- },
- {
- label: "鏄惁璐ㄦ",
- prop: "isQuality",
- formatData: (params) => {
- return params ? "鏄�" : "鍚�";
+ label: "鍙傛暟绫诲瀷",
+ prop: "paramType",
+ dataType: "tag",
+ formatType: params => {
+ const typeMap = {
+ 1: "primary",
+ 2: "info",
+ 3: "warning",
+ 4: "success",
+ };
+ return typeMap[params] || "default";
+ },
+ formatData: val => {
+ const labelMap = {
+ 1: "鏁板�兼牸寮�",
+ 2: "鏂囨湰鏍煎紡",
+ 3: "涓嬫媺閫夐」",
+ 4: "鏃堕棿鏍煎紡",
+ };
+ return labelMap[val] || val;
},
},
{
- label: "澶囨敞",
- prop: "remark",
+ label: "鍙栧�兼牸寮�",
+ prop: "paramFormat",
},
{
- label: "鏇存柊鏃堕棿",
- prop: "updateTime",
+ label: "鏍囧噯鍊�",
+ prop: "standardValue",
},
{
- dataType: "action",
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
label: "鎿嶄綔",
- align: "center",
- fixed: "right",
- width: 280,
+ dataType: "action",
+ width: "150",
operation: [
{
name: "缂栬緫",
- type: "text",
- clickFun: row => {
- showEditModal(row);
- },
+ clickFun: row => handleEditParam(row),
+ },
+ {
+ name: "鍒犻櫎",
+ clickFun: row => handleDeleteParam(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)
+ // 鑾峰彇宸ュ簭鍒楄〃
+ const getProcessList = () => {
+ processLoading.value = true;
+ getProcessListApi({ size: -1, current: -1 })
.then(res => {
- tableLoading.value = false;
- tableData.value = res.data.records.map(item => ({
- ...item,
- typeText: item.type !== undefined && item.type !== null ? (item.type === 0 ? "璁℃椂" : "璁′欢") : "",
- }));
- page.total = res.data.total;
+ processValueList.value = res.data.records || [];
+ console.log(
+ processValueList.value,
+ "reprocessValueList.value==========s"
+ );
})
- .catch(err => {
- tableLoading.value = false;
+ .catch(() => {
+ ElMessage.error("鑾峰彇宸ュ簭鍒楄〃澶辫触");
+ })
+ .finally(() => {
+ processLoading.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 () => {
+ const loadDeviceName = 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("妯℃澘涓嬭浇鎴愬姛");
+ const { data } = await getDeviceLedger();
+ deviceOptions.value = data || [];
} catch (error) {
- proxy.$modal.msgError("妯℃澘涓嬭浇澶辫触");
+ console.error("鍔犺浇璁惧鍒楄〃澶辫触", error);
}
};
- // 瀵煎嚭
- // const handleOut = () => {
- // ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- // confirmButtonText: "纭",
- // cancelButtonText: "鍙栨秷",
- // type: "warning",
- // })
- // .then(() => {
- // proxy.download("/salesLedger/scheduling/exportTwo", {}, "宸ュ簭鎺掍骇.xlsx");
- // })
- // .catch(() => {
- // proxy.$modal.msg("宸插彇娑�");
- // });
- // };
+ // 鑾峰彇鍙傛暟鍒楄〃
+ const getParamList = processId => {
+ paramLoading.value = true;
+ getProcessParamList({ technologyOperationId: processId })
+ .then(res => {
+ const list = res.data || [];
+ paramListRaw.value = Array.isArray(list) ? list : [];
+ paramPage2.value.total = paramListRaw.value.length;
+ const maxPage = Math.max(
+ 1,
+ Math.ceil(paramPage2.value.total / paramPage2.value.size) || 1
+ );
+ if (paramPage2.value.current > maxPage) {
+ paramPage2.value.current = maxPage;
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鑾峰彇鍙傛暟鍒楄〃澶辫触");
+ })
+ .finally(() => {
+ paramLoading.value = false;
+ });
+ };
+
+ // 閫夋嫨宸ュ簭
+ const selectProcess = process => {
+ selectedProcess.value = process;
+ paramPage2.value.current = 1;
+ getParamList(process.id);
+ };
+
+ // 宸ュ簭鎿嶄綔
+ const handleAddProcess = () => {
+ isProcessEdit.value = false;
+ processForm.id = null;
+ processForm.no = "";
+ processForm.name = "";
+ processForm.salaryQuota = null;
+ processForm.isQuality = false;
+ processForm.isProduction = false;
+ processForm.remark = "";
+ processForm.deviceLedgerId = null;
+ processForm.type = 0;
+ processDialogVisible.value = true;
+ };
+
+ const handleEditProcess = async process => {
+ isProcessEdit.value = true;
+ processForm.id = process.id;
+ processForm.no = process.no;
+ processForm.name = process.name;
+ processForm.salaryQuota = process.salaryQuota;
+ processForm.isQuality = !!process.isQuality;
+ processForm.isProduction = !!process.isProduction;
+ processForm.remark = process.remark || "";
+ processForm.deviceLedgerId = Number(process.deviceLedgerId);
+ processForm.type = process.type;
+ processDialogVisible.value = true;
+ };
+
+ const handleDeleteProcess = process => {
+ ElMessageBox.confirm("纭畾瑕佸垹闄よ宸ュ簭鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ del([process.id])
+ .then(() => {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getProcessList();
+ if (selectedProcess.value?.id === process.id) {
+ selectedProcess.value = null;
+ paramListRaw.value = [];
+ paramPage2.value.total = 0;
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ });
+ };
+
+ const handleProcessSubmit = () => {
+ processFormRef.value.validate(valid => {
+ if (valid) {
+ if (processForm.id) {
+ update(processForm)
+ .then(() => {
+ ElMessage.success("缂栬緫鎴愬姛");
+ processDialogVisible.value = false;
+ getProcessList();
+ })
+ .catch(() => {
+ ElMessage.error("缂栬緫澶辫触");
+ });
+ } else {
+ add(processForm)
+ .then(() => {
+ ElMessage.success("鏂板鎴愬姛");
+ processDialogVisible.value = false;
+ getProcessList();
+ })
+ .catch(() => {
+ ElMessage.error("鏂板澶辫触");
+ });
+ }
+ }
+ });
+ };
+ const openParamDialog = () => {
+ paramSearchKeyword.value = "";
+ if (!selectedProcess.value) {
+ ElMessage.warning("璇峰厛閫夋嫨涓�涓伐搴�");
+ return;
+ }
+ // 鑾峰彇鍙�夊弬鏁板垪琛�
+ getBaseParamList({
+ paramName: paramSearchKeyword.value,
+ current: paramPage.current,
+ size: paramPage.size,
+ }).then(res => {
+ if (res.code === 200) {
+ filteredParamList.value = res.data?.records || [];
+ paramPage.total = res.data?.total || 0;
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ });
+ console.log(filteredParamList.value, "鍙�夊弬鏁板垪琛�");
+ selectedParam.value = null;
+ paramDialogVisible.value = true;
+ };
+
+ // 鍙傛暟鎿嶄綔
+ const handleSelectParam = () => {
+ if (!selectedProcess.value) {
+ ElMessage.warning("璇峰厛閫夋嫨涓�涓伐搴�");
+ return;
+ }
+ // 鑾峰彇鍙�夊弬鏁板垪琛�
+ getBaseParamList({
+ paramName: paramSearchKeyword.value,
+ current: paramPage.current,
+ size: paramPage.size,
+ }).then(res => {
+ if (res.code === 200) {
+ filteredParamList.value = res.data?.records || [];
+ paramPage.total = res.data?.total || 0;
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ });
+ console.log(filteredParamList.value, "鍙�夊弬鏁板垪琛�");
+ selectedParam.value = null;
+ paramDialogVisible.value = true;
+ };
+
+ const handleParamSelect = row => {
+ selectedParam.value = row;
+ };
+
+ const handleParamSearch = () => {
+ // 閲嶇疆鍒嗛〉
+ paramPage.current = 1;
+ // 閲嶆柊鍔犺浇鏁版嵁
+ handleSelectParam();
+ };
+
+ // 澶勭悊鍒嗛〉澶у皬鍙樺寲
+ const handleParamSizeChange = size => {
+ paramPage.size = size;
+ handleSelectParam();
+ };
+
+ // 澶勭悊褰撳墠椤电爜鍙樺寲
+ const handleParamCurrentChange = current => {
+ paramPage.current = current;
+ handleSelectParam();
+ };
+ const getParamTypeText = type => {
+ const typeMap = {
+ 1: "鏁板�兼牸寮�",
+ 2: "鏂囨湰鏍煎紡",
+ 3: "涓嬫媺閫夐」",
+ 4: "鏃堕棿鏍煎紡",
+ };
+ return typeMap[type] || "鏈煡鍙傛暟绫诲瀷";
+ };
+ const getParamTypeTag = type => {
+ const typeMap = {
+ 1: "primary",
+ 2: "info",
+ 3: "warning",
+ 4: "success",
+ };
+ return typeMap[type] || "default";
+ };
+
+ const handleDeleteParam = row => {
+ ElMessageBox.confirm("纭畾瑕佸垹闄よ鍙傛暟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ deleteProcessParam(row.id)
+ .then(() => {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getParamList(selectedProcess.value.id);
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ });
+ };
+
+ const handleEditParam = row => {
+ editParamForm.id = row.id;
+ editParamForm.technologyOperationId = row.technologyOperationId;
+ editParamForm.technologyParamId = row.technologyParamId;
+ editParamForm.paramName = row.paramName;
+ editParamForm.standardValue = row.standardValue;
+ editParamDialogVisible.value = true;
+ };
+
+ const handleEditParamSubmit = () => {
+ editParamFormRef.value.validate(valid => {
+ if (valid) {
+ editProcessParam(editParamForm)
+ .then(() => {
+ ElMessage.success("缂栬緫鎴愬姛");
+ editParamDialogVisible.value = false;
+ getParamList(selectedProcess.value.id);
+ })
+ .catch(() => {
+ ElMessage.error("缂栬緫澶辫触");
+ });
+ }
+ });
+ };
+
+ const handleParamSubmit = () => {
+ if (!selectedParam.value) {
+ ElMessage.warning("璇峰厛閫夋嫨涓�涓弬鏁�");
+ return;
+ }
+ addProcessParam({
+ technologyOperationId: selectedProcess.value.id,
+ technologyParamId: selectedParam.value.id,
+ standardValue: selectedParam.value.standardValue,
+ })
+ .then(() => {
+ ElMessage.success("娣诲姞鎴愬姛");
+ paramDialogVisible.value = false;
+ getParamList(selectedProcess.value.id);
+ })
+ .catch(() => {
+ ElMessage.error("娣诲姞澶辫触");
+ });
+ };
+
+ const handleParamPagination = obj => {
+ paramPage2.value.current = obj.page;
+ paramPage2.value.size = obj.limit;
+ };
+
+ // 鑾峰彇鏁版嵁瀛楀吀
+ const getDictTypes = () => {
+ listType({ pageNum: 1, pageSize: 1000 }).then(res => {
+ dictTypes.value = res.rows || [];
+ });
+ };
onMounted(() => {
- getList();
+ loadDeviceName();
+ getProcessList();
+ getDictTypes();
});
</script>
-<style scoped></style>
+<style scoped lang="scss">
+ .app-container {
+ padding: 20px;
+ background-color: #f0f2f5;
+ min-height: calc(100vh - 84px);
+ }
+
+ .process-config-container {
+ display: flex;
+ gap: 20px;
+ height: calc(100vh - 124px);
+ }
+
+ // 宸︿晶宸ュ簭鍒楄〃
+ .process-list-section {
+ width: 370px;
+ min-width: 370px;
+ flex-shrink: 0;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ display: flex;
+ flex-direction: column;
+ }
+
+ .section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #ebeef5;
+
+ .section-title {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+ }
+
+ .process-card-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+ }
+
+ .process-card {
+ background: #fff;
+ border: 1px solid #ebeef5;
+ border-radius: 8px;
+ padding: 16px;
+ margin-bottom: 12px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ transform: translateY(-2px);
+ }
+
+ &.active {
+ border-color: #409eff;
+ background: #f5f7fa;
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
+ }
+
+ .card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+
+ .process-code {
+ font-size: 12px;
+ // color: #909399;
+ color: #cb9b18;
+ font-family: "Courier New", monospace;
+ }
+
+ .card-actions {
+ display: flex;
+ gap: 4px;
+
+ .el-button {
+ padding: 4px;
+ }
+ }
+ }
+
+ .card-body {
+ margin-bottom: 12px;
+
+ .process-name {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 4px;
+ }
+
+ .process-desc {
+ font-size: 12px;
+ color: #909399;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ margin-bottom: 4px;
+ }
+
+ .process-device {
+ font-size: 12px;
+ color: #606266;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+
+ .card-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .param-count {
+ font-size: 12px;
+ color: #606266;
+ }
+ }
+ }
+
+ // 鍙充晶鍙傛暟鍒楄〃
+ .param-list-section {
+ flex: 1;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ }
+
+ .param-table-wrapper {
+ flex: 1;
+ padding: 0 20px 20px;
+ overflow: auto;
+ min-width: 100%;
+ }
+
+ /* 琛ㄦ牸妯悜婊氬姩 */
+ .param-table-wrapper :deep(.el-table) {
+ min-width: 100%;
+ }
+
+ .param-table-wrapper :deep(.el-table__body-wrapper) {
+ overflow-x: auto;
+ }
+
+ .pagination-container {
+ margin-top: 10px;
+ overflow-x: auto;
+ padding-bottom: 8px;
+ }
+
+ .pagination-container .el-pagination {
+ white-space: nowrap;
+ }
+
+ /* 鍝嶅簲寮忚皟鏁� */
+ @media screen and (max-width: 768px) {
+ .pagination-container {
+ font-size: 12px;
+ }
+
+ .pagination-container .el-pagination__sizes {
+ margin-right: 8px;
+ }
+
+ .pagination-container .el-pagination__jump {
+ margin-left: 8px;
+ }
+
+ .pagination-container .el-pagination__page-size {
+ font-size: 12px;
+ }
+ }
+
+ .empty-tip {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ // 琛ㄦ牸鏍峰紡
+ :deep(.el-table) {
+ border: none;
+ border-radius: 6px;
+ overflow: hidden;
+
+ .el-table__header-wrapper {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
+ th {
+ background: transparent;
+ font-weight: 600;
+ // color: #ffffff;
+ border-bottom: none;
+ padding: 16px 0;
+ }
+ }
+
+ .el-table__body-wrapper {
+ tr {
+ transition: all 0.3s ease;
+
+ &:hover {
+ background: linear-gradient(
+ 90deg,
+ rgba(102, 126, 234, 0.05) 0%,
+ rgba(118, 75, 162, 0.05) 100%
+ );
+ }
+
+ td {
+ border-bottom: 1px solid #f0f0f0;
+ padding: 14px 0;
+ color: #303133;
+ }
+ }
+ }
+ }
+
+ // 缂栫爜鍗曞厓鏍兼牱寮�
+ :deep(.code-cell) {
+ color: #e6a23c;
+ font-family: "Courier New", monospace;
+ font-weight: 500;
+ }
+
+ // 鏁板�煎崟鍏冩牸鏍峰紡
+ :deep(.quantity-cell) {
+ font-weight: 600;
+ color: #409eff;
+ font-family: "Courier New", monospace;
+ }
+
+ // 閫夋嫨鍙傛暟瀵硅瘽妗嗘牱寮�
+ .param-select-container {
+ display: flex;
+ gap: 20px;
+ height: 450px;
+
+ .param-list-area {
+ // flex: 1;
+ width: 380px;
+ display: flex;
+ flex-direction: column;
+
+ .area-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 12px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #ebeef5;
+ }
+
+ .search-box {
+ margin-bottom: 12px;
+
+ .el-input {
+ width: 100%;
+ }
+ }
+ }
+
+ .param-detail-area {
+ // width: 380px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: #f5f7fa;
+ border-radius: 8px;
+ padding: 16px;
+
+ .area-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 16px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #ebeef5;
+ }
+
+ .param-detail-form {
+ .el-form-item {
+ margin-bottom: 12px;
+
+ .el-form-item__label {
+ color: #606266;
+ font-weight: 500;
+ }
+ }
+
+ .detail-text {
+ color: #303133;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+</style>
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index 0b42dae..aff050f 100644
--- a/src/views/productionManagement/productionReporting/index.vue
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -99,8 +99,7 @@
style="width: 100%" />
</template>
</el-table-column>
- <el-table-column label="鎿嶄綔"
- >
+ <el-table-column label="鎿嶄綔">
<template #default="scope">
<el-button link
type="primary"
@@ -124,11 +123,36 @@
<input-modal v-if="isShowInput"
v-model:visible="isShowInput"
:production-product-main-id="isShowingId" />
+ <!-- 鍙傛暟璇︽儏寮圭獥 -->
+ <el-dialog v-model="paramDetailVisible"
+ title="鍙傛暟璇︽儏"
+ width="600px">
+ <div v-if="currentParams && currentParams.length > 0"
+ class="param-detail-list">
+ <el-descriptions :column="1"
+ border>
+ <el-descriptions-item v-for="param in currentParams"
+ :key="param.id"
+ :label="param.paramName">
+ {{ param.inputValue }}
+ <span v-if="param.unit && param.unit !== '/'"
+ class="unit-text">({{ param.unit }})</span>
+ </el-descriptions-item>
+ </el-descriptions>
+ </div>
+ <el-empty v-else
+ description="鏆傛棤鍙傛暟鏁版嵁" />
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="paramDetailVisible = false">鍏抽棴</el-button>
+ </span>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
- import { onMounted, ref } from "vue";
+ import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
import { ElMessageBox } from "element-plus";
import {
@@ -202,7 +226,7 @@
prop: "unit",
width: 120,
},
-
+
{
label: "鍒涘缓鏃堕棿",
prop: "createTime",
@@ -213,12 +237,20 @@
label: "鎿嶄綔",
align: "center",
fixed: "right",
+ width: 250,
operation: [
{
name: "鏌ョ湅鎶曞叆",
type: "text",
clickFun: row => {
showInput(row);
+ },
+ },
+ {
+ name: "鍙傛暟璇︽儏",
+ type: "text",
+ clickFun: row => {
+ showParamDetail(row);
},
},
{
@@ -232,6 +264,13 @@
},
]);
const tableData = ref([]);
+ const paramDetailVisible = ref(false);
+ const currentParams = ref([]);
+
+ const showParamDetail = row => {
+ currentParams.value = row.productionOperationParamList || [];
+ paramDetailVisible.value = true;
+ };
const selectedRows = ref([]);
const tableLoading = ref(false);
const childrenLoading = ref(false);
@@ -417,4 +456,16 @@
});
</script>
-<style scoped></style>
+<style scoped>
+ .unit-text {
+ margin-left: 5px;
+ color: #909399;
+ font-size: 12px;
+ }
+ .param-detail-list {
+ padding: 10px;
+ }
+ .table_list {
+ margin-top: unset;
+ }
+</style>
diff --git a/src/views/productionManagement/productionTraceability/index.vue b/src/views/productionManagement/productionTraceability/index.vue
new file mode 100644
index 0000000..a850788
--- /dev/null
+++ b/src/views/productionManagement/productionTraceability/index.vue
@@ -0,0 +1,706 @@
+<template>
+ <div class="app-container">
+ <el-card style="height:82vh;overflow:auto;">
+ <template #header>
+ <div class="card-header">
+ <el-form :inline="true"
+ :model="searchForm"
+ class="search-form">
+ <el-form-item label="鐢熶骇璁㈠崟鍙�">
+ <el-select v-model="selectedNpsNo"
+ filterable
+ remote
+ reserve-keyword
+ placeholder="璇疯緭鍏ョ敓浜ц鍗曞彿"
+ :loading="npsNoLoading"
+ :remote-method="handleNpsNoSearch"
+ @change="handleSearch"
+ style="width: 400px;">
+ <el-option v-for="option in npsNoOptions"
+ :key="option.id"
+ :label="option.npsNo + '-' + option.productName + '-' + option.model"
+ :value="option.id" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ </div>
+ </template>
+ <!-- 鍩虹淇℃伅 -->
+ <div v-if="rowData.productionOrderDto"
+ class="detail-section">
+ <h3 class="section-title">鍩虹淇℃伅</h3>
+ <el-descriptions :column="3"
+ border>
+ <el-descriptions-item label="鐢熶骇璁㈠崟鍙�">{{ rowData.productionOrderDto?.npsNo || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="浜у搧鍚嶇О">{{ rowData.productionOrderDto?.productName || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="浜у搧瑙勬牸">{{ rowData.productionOrderDto?.model || '-' }}</el-descriptions-item>
+ <!-- <el-descriptions-item label="鐗╂枡缂栫爜">{{ rowData.productionOrderDto?.materialCode || '-' }}</el-descriptions-item> -->
+ <el-descriptions-item label="璁″垝鏁伴噺">{{ rowData.productionOrderDto?.quantity || 0 }} <span class="unit">{{ rowData.productionOrderDto?.unit || '-' }}</span></el-descriptions-item>
+ <el-descriptions-item label="褰撳墠鐘舵��">
+ <el-tag :type="getStatusType(rowData.productionOrderDto?.status)">
+ {{ getStatusText(rowData.productionOrderDto?.status) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="瀹㈡埛鍚嶇О">{{ rowData.productionOrderDto?.customerName || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="寮�濮嬫棩鏈�">{{ parseTime(rowData.productionOrderDto?.startTime) || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="瀹屾垚杩涘害">
+ <el-progress :percentage="rowData.productionOrderDto?.completionStatus>=100?100:rowData.productionOrderDto?.completionStatus"
+ :color="customColors(rowData.productionOrderDto?.completionStatus)"
+ :status="rowData.productionOrderDto?.completionStatus >= 100 ? 'success' : ''"
+ style="width: 80%;" />
+ </el-descriptions-item>
+ </el-descriptions>
+ </div>
+ <el-empty v-else
+ description="璇锋悳绱㈢敓浜ц鍗曞彿" />
+ <!-- 鐢熶骇鎶ュ伐璁板綍 -->
+ <div v-if="rowData.productionRecords && rowData.productionRecords.length > 0"
+ class="progress-container">
+ <div class="progress-section">
+ <h3 class="section-title">宸ュ崟淇℃伅</h3>
+ <div class="order-item">
+ <el-table :data="rowData.productionRecords"
+ border
+ style="width: 100%">
+ <el-table-column prop="workOrder.workOrderNo"
+ label="宸ュ崟缂栧彿"
+ align="center">
+ </el-table-column>
+ <el-table-column label="浜у搧鍚嶇О"
+ align="center">
+ <template #default="{ row }">
+ {{ row.workOrder.productName || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="瑙勬牸"
+ align="center">
+ <template #default="{ row }">
+ {{ row.workOrder.model || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="workOrder.planQuantity"
+ label="闇�姹傛暟閲�"
+ align="center" />
+ <el-table-column prop="workOrder.completeQuantity"
+ label="瀹屾垚鏁伴噺"
+ align="center" />
+ <el-table-column prop="workOrder.completionStatus"
+ label="瀹屾垚杩涘害"
+ align="center">
+ <template #default="{ row }">
+ <span :style="{ color: customColors(row.workOrder.completionStatus) }">{{ row.workOrder.completionStatus || 0 }}%</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璇︽儏"
+ align="center"
+ width="200">
+ <template #default="{ row }">
+ <el-link @click="handleClickStep(row)"
+ type="primary">鎶ュ伐璁板綍</el-link>
+ <el-link @click="handleClickQuality(row)"
+ style="margin-left:20px"
+ type="primary">璐ㄦ淇℃伅</el-link>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+ </div>
+ <el-empty v-else-if="rowData.productionOrderDto"
+ description="鏆傛棤鎶ュ伐璁板綍" />
+ </el-card>
+ <!-- 鐢熶骇鎶ュ伐璇︽儏寮圭獥 -->
+ <el-dialog v-model="detailDialogVisible"
+ title="鐢熶骇鎶ュ伐璇︽儏"
+ width="1000px"
+ :close-on-click-modal="false"
+ custom-class="custom-dialog">
+ <div class="detail-container">
+ <!-- 鍩虹淇℃伅 -->
+ <div class="detail-section">
+ <h3 class="section-title">宸ュ崟鍩虹淇℃伅</h3>
+ <el-descriptions :column="3"
+ border>
+ <el-descriptions-item label="鐢熶骇宸ュ崟鍙�">{{ detailData.workOrder.workOrderNo || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="璁″垝鏁伴噺">{{ detailData.workOrder.planQuantity || 0 }}</el-descriptions-item>
+ <el-descriptions-item label="瀹屾垚鏁伴噺">{{ detailData.workOrder.completeQuantity || 0 }}</el-descriptions-item>
+ <el-descriptions-item label="瀹為檯寮�濮嬫椂闂�">{{ parseTime(detailData.workOrder.actualStartTime) || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="瀹為檯缁撴潫鏃堕棿">{{ parseTime(detailData.workOrder.actualEndTime) || '-' }}</el-descriptions-item>
+ <!-- <el-descriptions-item label="瀹屾垚杩涘害">
+ <el-progress :percentage="detailData.workOrder.completionStatus >= 100 ? 100 : (detailData.workOrder.completionStatus || 0)"
+ :color="customColors(detailData.workOrder.completionStatus)"
+ :status="detailData.workOrder.completionStatus >= 100 ? 'success' : ''"
+ style="width:500px;" />
+ </el-descriptions-item> -->
+ <el-descriptions-item label="瀹屾垚杩涘害"><span :style="{ color: customColors(detailData.workOrder.completionStatus) }">{{ detailData.workOrder.completionStatus || 0 }}%</span></el-descriptions-item>
+ </el-descriptions>
+ </div>
+ <div class="detail-section">
+ <h3 class="section-title">鎶ュ伐鏄庣粏</h3>
+ <el-table :data="detailData.reports"
+ border
+ style="width: 100%">
+ <el-table-column label="鎶ュ伐鍗曞彿"
+ prop="productNo"
+ align="center" />
+ <el-table-column label="鍒涘缓浜�"
+ prop="userName"
+ align="center" />
+ <el-table-column label="鍒涘缓鏃堕棿"
+ align="center">
+ <template #default="{ row }">
+ {{ parseTime(row.createTime) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ align="center"
+ width="200">
+ <template #default="{ row }">
+ <el-button type="primary"
+ link
+ @click="showInput(row.id)">鏌ョ湅鎶曞叆</el-button>
+ <el-button type="primary"
+ link
+ @click="showParamDetail(row.productionOperationParamList)">鍙傛暟璇︽儏</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <!-- 鎶曞叆妯℃�佹 -->
+ <input-modal v-if="isShowInput"
+ v-model:visible="isShowInput"
+ :production-product-main-id="isShowingId" />
+ <!-- 鍙傛暟璇︽儏寮圭獥 -->
+ <el-dialog v-model="paramDetailVisible"
+ title="鍙傛暟璇︽儏"
+ width="600px">
+ <div v-if="currentParams && currentParams.length > 0"
+ class="param-detail-list">
+ <el-descriptions :column="1"
+ border>
+ <el-descriptions-item v-for="param in currentParams"
+ :key="param.id"
+ :label="param.paramName">
+ {{ param.inputValue }}
+ <span v-if="param.unit && param.unit !== '/'"
+ class="unit-text">({{ param.unit }})</span>
+ </el-descriptions-item>
+ </el-descriptions>
+ </div>
+ <el-empty v-else
+ description="鏆傛棤鍙傛暟鏁版嵁" />
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="paramDetailVisible = false">鍏抽棴</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <!-- 璐ㄦ淇℃伅寮圭獥 -->
+ <el-dialog v-model="qualityDialogVisible"
+ title="璐ㄦ璇︽儏"
+ width="1000px"
+ :close-on-click-modal="false"
+ custom-class="custom-dialog">
+ <div class="detail-container">
+ <div v-for="(record, index) in qualityRecords"
+ :key="record.id"
+ class="quality-record-block">
+ <div class="detail-section">
+ <h3 class="section-title">妫�娴嬭褰� {{ index + 1 }} - {{ parseTime(record.createTime) }}</h3>
+ <el-descriptions :column="3"
+ border>
+ <el-descriptions-item label="妫�娴嬫棩鏈�">{{ parseTime(record.createTime) || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="鎶ュ伐鍗曞彿">{{ record.reportNo || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="妫�楠屽憳">{{ record.userName || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="浜у搧鍚嶇О">{{ record.productName || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="瑙勬牸鍨嬪彿">{{ record.model || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="鏁伴噺">{{ record.quantity || 0 }} {{ record.unit || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="妫�娴嬪崟浣�">{{ record.checkCompany || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="妫�娴嬬粨鏋�">
+ <el-tag :type="record.checkResult === '鍚堟牸' ? 'success' : 'danger'">
+ {{ record.checkResult || '寰呮娴�' }}
+ </el-tag>
+ </el-descriptions-item>
+ </el-descriptions>
+ <h4 class="sub-section-title">妫�楠屾寚鏍囧垪琛�</h4>
+ <el-table :data="record.inspectParamList"
+ border
+ style="width: 100%">
+ <el-table-column label="搴忓彿"
+ type="index"
+ width="60"
+ align="center" />
+ <el-table-column label="鎸囨爣"
+ prop="parameterItem"
+ align="center" />
+ <el-table-column label="鍗曚綅"
+ prop="unit"
+ align="center" />
+ <el-table-column label="鏍囧噯鍊�"
+ prop="standardValue"
+ align="center" />
+ <el-table-column label="鍐呮帶鍊�"
+ prop="controlValue"
+ align="center" />
+ <el-table-column label="瀹為檯鍊�"
+ prop="testValue"
+ align="center" />
+ </el-table>
+ </div>
+ <el-divider v-if="index < qualityRecords.length - 1" />
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="qualityDialogVisible = false">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { ref, reactive, onMounted } from "vue";
+ import { useRoute, useRouter } from "vue-router";
+ import { ElMessage } from "element-plus";
+ import { parseTime } from "@/utils/ruoyi";
+ import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
+ import {
+ getOrderDetail,
+ productOrderListPage,
+ } from "@/api/productionManagement/productionOrder";
+
+ const route = useRoute();
+ const router = useRouter();
+
+ // 鎼滅储鐩稿叧
+ const searchForm = reactive({
+ npsNo: "",
+ });
+ const selectedNpsNo = ref(null);
+ const npsNoLoading = ref(false);
+ const npsNoOptions = ref([]);
+
+ // 璇︽儏鏁版嵁
+ const rowData = reactive({
+ productionOrderDto: null,
+ productionRecords: [],
+ });
+
+ // 鎶ュ伐璇︽儏寮圭獥
+ const detailDialogVisible = ref(false);
+ const detailData = ref({
+ workOrder: {},
+ reports: [],
+ });
+
+ // 鎶曞叆妯℃�佹
+ const isShowInput = ref(false);
+ const isShowingId = ref(0);
+ const showInput = id => {
+ isShowInput.value = true;
+ isShowingId.value = id;
+ };
+
+ // 鍙傛暟璇︽儏寮圭獥
+ const paramDetailVisible = ref(false);
+ const currentParams = ref([]);
+ const showParamDetail = params => {
+ currentParams.value = params || [];
+ paramDetailVisible.value = true;
+ };
+
+ // 璐ㄦ淇℃伅寮圭獥
+ const qualityDialogVisible = ref(false);
+ const qualityRecords = ref([]);
+
+ // 鐘舵�佸鐞�
+ const getStatusType = status => {
+ const typeMap = { 1: "primary", 2: "warning", 3: "success", 5: "danger" };
+ return typeMap[status] || "info";
+ };
+ const getStatusText = status => {
+ const statusMap = { 1: "寰呭紑濮�", 2: "杩涜涓�", 3: "宸插畬鎴�", 5: "宸茬粨鏉�" };
+ return statusMap[status] || "宸插彇娑�";
+ };
+ const customColors = percentage => {
+ if (percentage < 30) return "#f56c6c";
+ if (percentage < 70) return "#e6a23c";
+ return "#67c23a";
+ };
+
+ // 妯℃嫙鎼滅储鏂规硶
+ const handleNpsNoSearch = async query => {
+ npsNoLoading.value = true;
+ try {
+ const res = await productOrderListPage({
+ npsNo: query || "",
+ pageNum: 1,
+ pageSize: 50,
+ });
+ // 鍙傜収 productionOrder/index.vue 鐨勬暟鎹粨鏋� res.data.records
+ npsNoOptions.value = res.data?.records || res.rows || [];
+ } catch (error) {
+ console.error(error);
+ } finally {
+ npsNoLoading.value = false;
+ }
+ };
+
+ const handleSearch = async id => {
+ const selected = npsNoOptions.value.find(item => item.id === id);
+ if (selected) {
+ try {
+ const res = await getOrderDetail(selected.npsNo);
+ if (res.code === 200) {
+ const { productionOrder, workOrderList } = res.data;
+ rowData.productionOrderDto = productionOrder || selected;
+ rowData.productionRecords = workOrderList || [];
+ } else {
+ rowData.productionOrderDto = selected;
+ rowData.productionRecords = [];
+ }
+ } catch (error) {
+ console.error(error);
+ ElMessage.error("鑾峰彇璁㈠崟璇︽儏澶辫触");
+ rowData.productionOrderDto = selected;
+ }
+ }
+ };
+
+ const handleBack = () => {
+ router.back();
+ };
+
+ const handleClickStep = row => {
+ // row 鏄� workOrderList 涓殑涓�椤癸紝鍖呭惈 workOrder, reportList, inspectList
+ detailData.value = {
+ workOrder: row.workOrder || {},
+ reports: (row.reportList || []).map(r => ({
+ ...r.reportMain,
+ productionOperationParamList: r.reportParamList || [],
+ })),
+ };
+ detailDialogVisible.value = true;
+ };
+
+ const handleClickQuality = row => {
+ // row 鏄� workOrderList 涓殑涓�椤�
+ const inspects = row.inspectList || [];
+ qualityRecords.value = inspects.map(i => ({
+ ...i.inspect,
+ reportNo: i.reportNo,
+ userName: i.reportMain?.userName || "-",
+ inspectParamList: i.inspectParamList || [],
+ inspectFileList: i.inspectFileList || [],
+ }));
+ qualityDialogVisible.value = true;
+ };
+
+ onMounted(async () => {
+ // 鍒濆鍔犺浇鍒楄〃
+ await handleNpsNoSearch();
+
+ if (route.query.npsNo) {
+ const npsNo = route.query.npsNo;
+ const found = npsNoOptions.value.find(item => item.npsNo === npsNo);
+ if (found) {
+ selectedNpsNo.value = found.id;
+ handleSearch(found.id);
+ } else {
+ // 濡傛灉鍒楄〃涓病鏈夛紙鍙兘鏄垎椤靛師鍥狅級锛屽垯鏍规嵁 npsNo 鍐嶆绮惧噯鎼滅储
+ try {
+ const res = await productOrderListPage({
+ npsNo,
+ pageNum: -1,
+ pageSize: -1,
+ });
+ const records = res.data?.records || res.rows || [];
+ if (records.length > 0) {
+ const item = records[0];
+ npsNoOptions.value.unshift(item);
+ selectedNpsNo.value = item.id;
+ handleSearch(item.id);
+ }
+ } catch (error) {
+ console.error("鑾峰彇璺敱鍙傛暟瀵瑰簲鐨勮鍗曞け璐�", error);
+ }
+ }
+ }
+ });
+</script>
+
+<style scoped>
+ .app-container {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+ }
+
+ .card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 10px;
+ }
+
+ .search-form {
+ width: 100%;
+ }
+
+ .search-form .el-form-item {
+ margin-right: 10px;
+ }
+
+ .detail-section {
+ margin-bottom: 24px;
+ background-color: #ffffff;
+ border-radius: 10px;
+ padding: 24px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+ }
+
+ .detail-section:hover {
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12);
+ }
+
+ .section-title {
+ font-size: 16px;
+ font-weight: 600;
+ margin-bottom: 20px;
+ color: #1a1a1a;
+ border-bottom: 2px solid #409eff;
+ padding-bottom: 10px;
+ display: flex;
+ align-items: center;
+ }
+
+ .section-title::before {
+ content: "";
+ display: inline-block;
+ width: 4px;
+ height: 16px;
+ background-color: #409eff;
+ margin-right: 8px;
+ border-radius: 2px;
+ }
+
+ .sub-section-title {
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 16px;
+ color: #303133;
+ display: flex;
+ align-items: center;
+ }
+
+ .sub-section-title::before {
+ content: "";
+ display: inline-block;
+ width: 3px;
+ height: 12px;
+ background-color: #67c23a;
+ margin-right: 8px;
+ border-radius: 2px;
+ }
+
+ .unit {
+ font-size: 12px;
+ color: #909399;
+ margin-left: 4px;
+ }
+
+ :deep(.el-descriptions) {
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+ }
+
+ :deep(.el-descriptions__row:nth-child(odd)) {
+ background-color: #f9f9f9;
+ }
+
+ :deep(.el-descriptions__label) {
+ font-weight: 500;
+ color: #606266;
+ background-color: #f5f7fa;
+ }
+
+ :deep(.el-descriptions__content) {
+ color: #303133;
+ font-weight: 500;
+ }
+
+ .progress-container {
+ display: flex;
+ gap: 24px;
+ }
+
+ .progress-section {
+ margin-bottom: 24px;
+ background-color: #ffffff;
+ border-radius: 10px;
+ padding: 24px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
+ flex: 1;
+ transition: all 0.3s ease;
+ width: 100%;
+ }
+
+ .progress-section:hover {
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12);
+ }
+
+ .order-item {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+ }
+
+ :deep(.el-table) {
+ border-radius: 8px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+ }
+
+ :deep(.el-table th) {
+ background-color: #f5f7fa !important;
+ font-weight: 600;
+ color: #606266;
+ }
+
+ :deep(.el-progress-bar__inner) {
+ border-radius: 10px;
+ }
+
+ :deep(.el-tag) {
+ border-radius: 12px;
+ padding: 2px 10px;
+ }
+
+ /* 寮圭獥鏍峰紡 */
+ .detail-container {
+ max-height: 600px;
+ overflow-y: auto;
+ padding: 0 16px;
+ }
+
+ .process-item {
+ margin-bottom: 24px;
+ padding: 20px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ border: 1px solid #ebeef5;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ }
+
+ .process-header {
+ margin-bottom: 20px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f2f5;
+ }
+
+ .process-title {
+ font-size: 15px;
+ font-weight: 600;
+ margin-bottom: 12px;
+ color: #1a1a1a;
+ display: flex;
+ align-items: center;
+ }
+
+ .process-title::before {
+ content: "";
+ display: inline-block;
+ width: 4px;
+ height: 16px;
+ background-color: #409eff;
+ margin-right: 8px;
+ border-radius: 2px;
+ }
+
+ .process-info {
+ display: flex;
+ gap: 20px;
+ font-size: 13px;
+ color: #606266;
+ }
+
+ .process-label {
+ padding: 4px 12px;
+ background-color: #ecf5ff;
+ border-radius: 4px;
+ color: #409eff;
+ font-weight: 500;
+ }
+
+ .process-details {
+ margin-bottom: 20px;
+ }
+
+ .num1 {
+ color: #1107cc;
+ font-weight: 600;
+ }
+
+ .num2 {
+ color: #0fcf25;
+ font-weight: 600;
+ }
+
+ .num3 {
+ color: #d31818;
+ font-weight: 600;
+ }
+
+ .dialog-footer {
+ text-align: center;
+ padding: 20px;
+ border-top: 1px solid #ebeef5;
+ }
+
+ .dialog-footer .el-button {
+ min-width: 100px;
+ padding: 8px 20px;
+ }
+
+ /* 鑷畾涔夊璇濇鏍峰紡 */
+ :deep(.custom-dialog) {
+ border-radius: 12px;
+ overflow: hidden;
+ }
+
+ :deep(.custom-dialog .el-dialog__header) {
+ background-color: #f5f7fa;
+ padding: 20px;
+ border-bottom: 1px solid #ebeef5;
+ }
+
+ :deep(.custom-dialog .el-dialog__title) {
+ font-size: 18px;
+ font-weight: 600;
+ color: #1a1a1a;
+ }
+
+ :deep(.custom-dialog .el-dialog__body) {
+ padding: 20px;
+ }
+
+ .unit-text {
+ margin-left: 5px;
+ color: #909399;
+ font-size: 12px;
+ }
+
+ .param-detail-list {
+ padding: 10px;
+ }
+</style>
diff --git a/src/views/productionManagement/workOrder/index.vue b/src/views/productionManagement/workOrder/index.vue
index 162dd91..1e4810a 100644
--- a/src/views/productionManagement/workOrder/index.vue
+++ b/src/views/productionManagement/workOrder/index.vue
@@ -596,7 +596,7 @@
const showReportDialog = row => {
currentReportRowData.value = row;
- reportForm.planQuantity = row.planQuantity;
+ reportForm.planQuantity = row.planQuantity - row.completeQuantity;
reportForm.quantity =
row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
diff --git a/src/views/productionManagement/workOrderEdit/index.vue b/src/views/productionManagement/workOrderEdit/index.vue
index 50f9fce..1cde5ea 100644
--- a/src/views/productionManagement/workOrderEdit/index.vue
+++ b/src/views/productionManagement/workOrderEdit/index.vue
@@ -1,10 +1,19 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<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">
+ <span class="search_title">鐢熶骇璁㈠崟鍙凤細</span>
+ <el-input v-model="searchForm.npsNo"
style="width: 240px"
placeholder="璇疯緭鍏�"
@change="handleQuery"
@@ -31,8 +40,6 @@
</template>
</PIMTable>
</div>
-
- <!-- 缂栬緫鏃堕棿寮圭獥 -->
<el-dialog v-model="editDialogVisible"
title="缂栬緫璁″垝鏃堕棿"
width="500px">
@@ -61,18 +68,68 @@
</span>
</template>
</el-dialog>
+ <!-- 鎸囧畾鎶ュ伐浜哄脊绐� -->
+ <el-dialog v-model="assignReporterDialogVisible"
+ title="鎸囧畾鎶ュ伐浜�"
+ width="800px">
+ <div class="assign-reporter-content">
+ <div class="selected-tags-box"
+ v-if="selectedEmployeeIds.length > 0">
+ <div class="tags-label">宸查�夋嫨锛�</div>
+ <div class="tags-list">
+ <el-tag v-for="id in selectedEmployeeIds"
+ :key="id"
+ closable
+ @close="removeEmployeeTag(id)"
+ class="employee-tag">
+ {{ getEmployeeNameById(id) }}
+ </el-tag>
+ </div>
+ </div>
+ <div class="employee-list-container"
+ v-loading="employeeTableLoading">
+ <el-checkbox-group v-model="selectedEmployeeIds">
+ <div class="employee-grid">
+ <div v-for="item in employeeTableData"
+ :key="item.userId"
+ class="employee-item">
+ <el-checkbox :label="item.userId"
+ border>
+ <div class="employee-info">
+ <span class="name">{{ item.nickName }}</span>
+ <span class="dept">{{ item.dept?.deptName }}</span>
+ </div>
+ </el-checkbox>
+ </div>
+ </div>
+ </el-checkbox-group>
+ <div v-if="employeeTableData.length === 0"
+ class="empty-text">
+ 鏆傛棤鍖归厤浜哄憳
+ </div>
+ </div>
+ </div>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="assignReporterDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary"
+ @click="handleSaveReporters">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
- import { onMounted, ref, nextTick } from "vue";
+ import { getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
import { ElMessageBox } from "element-plus";
- import dayjs from "dayjs";
import {
productWorkOrderPage,
updateProductWorkOrder,
+ assignProductWorkOrder,
} from "@/api/productionManagement/workOrder.js";
- import { getCurrentInstance, reactive, toRefs } from "vue";
+ import { listUser } from "@/api/system/user.js";
+
const { proxy } = getCurrentInstance();
const tableColumn = ref([
@@ -88,7 +145,7 @@
},
{
label: "鐢熶骇璁㈠崟鍙�",
- prop: "productOrderNpsNo",
+ prop: "npsNo",
width: "140",
},
{
@@ -106,7 +163,8 @@
},
{
label: "宸ュ簭鍚嶇О",
- prop: "processName",
+ prop: "operationName",
+ width: "100",
},
{
label: "闇�姹傛暟閲�",
@@ -146,8 +204,13 @@
width: "140",
},
{
+ label: "鎸囧畾鎶ュ伐浜�",
+ prop: "userNames",
+ width: "180",
+ },
+ {
label: "鎿嶄綔",
- width: "100",
+ width: "200",
align: "center",
dataType: "action",
fixed: "right",
@@ -158,26 +221,49 @@
handleEdit(row);
},
},
+ {
+ name: "鎸囧畾鎶ュ伐浜�",
+ clickFun: row => {
+ handleAssignReporter(row);
+ },
+ },
],
},
]);
-
+
const tableData = ref([]);
const tableLoading = ref(false);
const editDialogVisible = ref(false);
- let editrow = ref(null);
+ const editrow = ref(null);
const page = reactive({
current: 1,
size: 100,
total: 0,
});
+ // 鎸囧畾鎶ュ伐浜虹浉鍏�
+ const assignReporterDialogVisible = ref(false);
+ const employeeTableLoading = ref(false);
+ const employeeTableData = ref([]);
+ const employeePage = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const employeeSearchForm = reactive({
+ staffName: "",
+ });
+ const selectedEmployeeIds = ref([]);
+ const currentWorkOrder = ref(null);
+
const data = reactive({
searchForm: {
workOrderNo: "",
+ npsNo: "",
},
});
const { searchForm } = toRefs(data);
+
const toProgressPercentage = val => {
const n = Number(val);
if (!Number.isFinite(n)) return 0;
@@ -185,6 +271,7 @@
if (n >= 100) return 100;
return Math.round(n);
};
+
const progressColor = percentage => {
const p = toProgressPercentage(percentage);
if (p < 30) return "#f56c6c";
@@ -193,17 +280,17 @@
return "#67c23a";
};
- // 鏌ヨ鍒楄〃
- /** 鎼滅储鎸夐挳鎿嶄綔 */
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 };
@@ -225,7 +312,7 @@
const handleUpdate = () => {
updateProductWorkOrder(editrow.value)
- .then(res => {
+ .then(() => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
editDialogVisible.value = false;
getList();
@@ -234,6 +321,74 @@
ElMessageBox.alert("淇敼澶辫触", "鎻愮ず", {
confirmButtonText: "纭畾",
});
+ });
+ };
+
+ const handleAssignReporter = row => {
+ currentWorkOrder.value = row;
+ assignReporterDialogVisible.value = true;
+ // 鍥炴樉宸插嬀閫夌殑浜哄憳
+ if (row.userIds) {
+ try {
+ selectedEmployeeIds.value = JSON.parse(row.userIds);
+ } catch (e) {
+ selectedEmployeeIds.value = [];
+ }
+ } else {
+ selectedEmployeeIds.value = [];
+ }
+ employeeSearchForm.staffName = "";
+ getEmployeeList();
+ };
+
+ const getEmployeeList = () => {
+ employeeTableLoading.value = true;
+ const params = {
+ pageNum: 1,
+ pageSize: 100,
+ };
+ listUser(params)
+ .then(res => {
+ employeeTableLoading.value = false;
+ employeeTableData.value = res.rows;
+ employeePage.total = res.total;
+ })
+ .catch(() => {
+ employeeTableLoading.value = false;
+ });
+ };
+
+ const getEmployeeNameById = id => {
+ const employee = employeeTableData.value.find(item => item.userId === id);
+ return employee ? employee.nickName : id;
+ };
+
+ const removeEmployeeTag = id => {
+ selectedEmployeeIds.value = selectedEmployeeIds.value.filter(
+ item => item !== id
+ );
+ };
+
+ const handleSaveReporters = () => {
+ if (selectedEmployeeIds.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鎶ュ伐浜�");
+ return;
+ }
+
+ const updateData = {
+ id: currentWorkOrder.value.id,
+ userIds: JSON.stringify(selectedEmployeeIds.value),
+ };
+ console.log(updateData, "updateData");
+
+ assignProductWorkOrder(updateData)
+ .then(() => {
+ proxy.$modal.msgSuccess("鎸囧畾鎴愬姛");
+ assignReporterDialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("鎸囧畾澶辫触");
});
};
@@ -248,13 +403,94 @@
align-items: center;
gap: 12px;
}
+
.search-item {
display: flex;
align-items: center;
}
+
.search_title {
margin-right: 8px;
font-size: 14px;
color: #606266;
}
-</style>
\ No newline at end of file
+
+ .assign-reporter-content {
+ .selected-tags-box {
+ margin-bottom: 16px;
+ padding: 12px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ display: flex;
+ align-items: flex-start;
+
+ .tags-label {
+ font-size: 14px;
+ color: #606266;
+ margin-right: 8px;
+ white-space: nowrap;
+ margin-top: 4px;
+ }
+
+ .tags-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+
+ .employee-tag {
+ margin-bottom: 4px;
+ }
+ }
+ }
+
+ .employee-list-container {
+ max-height: 400px;
+ overflow-y: auto;
+ padding: 10px;
+ border: 1px solid #f0f0f0;
+ border-radius: 4px;
+
+ .employee-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ gap: 12px;
+ }
+
+ .employee-item {
+ :deep(.el-checkbox) {
+ width: 100%;
+ margin-right: 0;
+ height: auto;
+ padding: 8px;
+
+ .el-checkbox__label {
+ width: 100%;
+ }
+ }
+
+ .employee-info {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ .name {
+ font-weight: bold;
+ font-size: 14px;
+ color: #303133;
+ }
+
+ .dept {
+ font-size: 12px;
+ color: #909399;
+ }
+ }
+ }
+
+ .empty-text {
+ text-align: center;
+ color: #909399;
+ padding: 20px;
+ }
+ }
+ }
+</style>
diff --git a/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
new file mode 100644
index 0000000..45944b5
--- /dev/null
+++ b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
@@ -0,0 +1,320 @@
+<template>
+ <div>
+ <el-dialog v-model="dialogVisible"
+ title="鐗╂枡"
+ width="1200px"
+ @close="handleCloseMaterialDialog">
+ <el-table v-loading="materialTableLoading"
+ :data="materialTableData"
+ border
+ row-key="id">
+ <el-table-column label="宸ュ簭鍚嶇О"
+ prop="processName"
+ min-width="140" />
+ <el-table-column label="鍘熸枡鍚嶇О"
+ prop="materialName"
+ min-width="140" />
+ <el-table-column label="鍘熸枡鍨嬪彿"
+ prop="materialModel"
+ min-width="140" />
+ <el-table-column label="璁¢噺鍗曚綅"
+ prop="unit"
+ min-width="100" />
+ <el-table-column label="绾胯竟浠撴暟閲�"
+ prop="pickQty"
+ min-width="100" />
+ <el-table-column label="琛ユ枡鏁伴噺"
+ prop="supplementQty"
+ min-width="100" />
+ <el-table-column label="瀹為檯鏁伴噺"
+ min-width="140">
+ <template #default="{ row }">
+ <el-input-number v-model="row.actualQty"
+ :min="0"
+ :precision="3"
+ :step="1"
+ controls-position="right"
+ style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ align="center"
+ fixed="right"
+ width="180">
+ <template #default="{ row }">
+ <el-button type="primary"
+ link
+ @click="openSupplementDialog(row)">琛ユ枡</el-button>
+ <el-button type="info"
+ link
+ @click="openSupplementRecordDialog(row)">琛ユ枡璁板綍</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ :loading="pickSubmitting"
+ @click="handleSubmitPick">棰嗙敤</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <FormDialog v-model="supplementDialogVisible"
+ title="琛ユ枡"
+ width="500px"
+ @confirm="handleSubmitSupplement">
+ <el-form ref="supplementFormRef"
+ :model="supplementForm"
+ :rules="supplementRules"
+ label-width="100px">
+ <el-form-item label="琛ユ枡鏁伴噺"
+ prop="supplementQty">
+ <el-input-number v-model="supplementForm.supplementQty"
+ :min="0.001"
+ :precision="3"
+ :step="1"
+ style="width: 100%;" />
+ </el-form-item>
+ <el-form-item label="琛ユ枡鍘熷洜"
+ prop="supplementReason">
+ <el-input v-model="supplementForm.supplementReason"
+ type="textarea"
+ :rows="3"
+ maxlength="200"
+ show-word-limit
+ placeholder="璇疯緭鍏ヨˉ鏂欏師鍥�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ :loading="supplementSubmitting"
+ @click="handleSubmitSupplement">纭畾</el-button>
+ <el-button @click="supplementDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </FormDialog>
+ <el-dialog v-model="supplementRecordDialogVisible"
+ title="琛ユ枡璁板綍"
+ width="900px">
+ <el-table v-loading="supplementRecordLoading"
+ :data="supplementRecordTableData"
+ border
+ row-key="id">
+ <el-table-column label="琛ユ枡鏁伴噺"
+ prop="supplementQty"
+ min-width="100" />
+ <el-table-column label="琛ユ枡鍘熷洜"
+ prop="supplementReason"
+ min-width="200" />
+ <el-table-column label="琛ユ枡浜�"
+ prop="supplementUserName"
+ min-width="120" />
+ <el-table-column label="琛ユ枡鏃ユ湡"
+ prop="supplementTime"
+ min-width="160" />
+ </el-table>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { computed, nextTick, reactive, ref, watch } from "vue";
+ import { ElMessage } from "element-plus";
+ import FormDialog from "@/components/Dialog/FormDialog.vue";
+ import {
+ listWorkOrderMaterialLedger,
+ addWorkOrderMaterialSupplement,
+ listWorkOrderMaterialSupplementRecord,
+ pickWorkOrderMaterial,
+ } from "@/api/productionManagement/workOrder.js";
+
+ const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
+ rowData: {
+ type: Object,
+ default: () => null,
+ },
+ });
+
+ const emit = defineEmits(["update:modelValue", "refresh"]);
+
+ const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: val => emit("update:modelValue", val),
+ });
+
+ const materialTableLoading = ref(false);
+ const materialTableData = ref([]);
+ const currentMaterialRow = ref(null);
+ const currentMaterialOrderRow = ref(null);
+ const pickSubmitting = ref(false);
+
+ const supplementDialogVisible = ref(false);
+ const supplementSubmitting = ref(false);
+ const supplementFormRef = ref(null);
+ const supplementForm = reactive({
+ supplementQty: null,
+ supplementReason: "",
+ });
+
+ const supplementRecordDialogVisible = ref(false);
+ const supplementRecordLoading = ref(false);
+ const supplementRecordTableData = ref([]);
+
+ const supplementRules = {
+ supplementQty: [
+ { required: true, message: "璇疯緭鍏ヨˉ鏂欐暟閲�", trigger: "blur" },
+ ],
+ supplementReason: [
+ { required: true, message: "璇疯緭鍏ヨˉ鏂欏師鍥�", trigger: "blur" },
+ ],
+ };
+ const loadMaterialTable = async row => {
+ if (!row?.id) return;
+ currentMaterialOrderRow.value = row;
+ materialTableLoading.value = true;
+ materialTableData.value = [];
+ try {
+ const res = await listWorkOrderMaterialLedger({
+ workOrderId: row.id,
+ processId: row.processId,
+ productProcessRouteItemId: row.productProcessRouteItemId,
+ });
+ materialTableData.value = res.data || [];
+ } catch (e) {
+ console.error("鑾峰彇鐗╂枡鍙拌处澶辫触", e);
+ ElMessage.error("鑾峰彇鐗╂枡鍙拌处澶辫触");
+ } finally {
+ materialTableLoading.value = false;
+ }
+ };
+
+ watch(
+ () => props.modelValue,
+ visible => {
+ if (visible && props.rowData) {
+ loadMaterialTable(props.rowData);
+ }
+ }
+ );
+
+ const handleCloseMaterialDialog = () => {
+ materialTableData.value = [];
+ currentMaterialRow.value = null;
+ currentMaterialOrderRow.value = null;
+ };
+
+ const openSupplementDialog = row => {
+ currentMaterialRow.value = row;
+ supplementForm.supplementQty = null;
+ supplementForm.supplementReason = "";
+ supplementDialogVisible.value = true;
+ nextTick(() => {
+ supplementFormRef.value?.clearValidate();
+ });
+ };
+
+ const handleSubmitSupplement = () => {
+ supplementFormRef.value?.validate(async valid => {
+ if (!valid || !currentMaterialRow.value?.id) {
+ ElMessage.warning("缂哄皯鐗╂枡鏄庣粏ID");
+ return;
+ }
+ supplementSubmitting.value = true;
+ try {
+ await addWorkOrderMaterialSupplement({
+ materialLedgerId: currentMaterialRow.value.id,
+ supplementQty: Number(supplementForm.supplementQty),
+ supplementReason: supplementForm.supplementReason,
+ workOrderId: currentMaterialOrderRow.value?.id,
+ });
+ supplementDialogVisible.value = false;
+ await loadMaterialTable(currentMaterialOrderRow.value);
+ ElMessage.success("琛ユ枡鎴愬姛");
+ emit("refresh");
+ } catch (e) {
+ console.error("琛ユ枡澶辫触", e);
+ ElMessage.error("琛ユ枡澶辫触");
+ } finally {
+ supplementSubmitting.value = false;
+ }
+ });
+ };
+
+ const openSupplementRecordDialog = async row => {
+ supplementRecordDialogVisible.value = true;
+ supplementRecordLoading.value = true;
+ supplementRecordTableData.value = [];
+ try {
+ const res = await listWorkOrderMaterialSupplementRecord({
+ materialLedgerId: row.id,
+ });
+ supplementRecordTableData.value = res.data || [];
+ } catch (e) {
+ console.error("鑾峰彇琛ユ枡璁板綍澶辫触", e);
+ ElMessage.error("鑾峰彇琛ユ枡璁板綍澶辫触");
+ } finally {
+ supplementRecordLoading.value = false;
+ }
+ };
+
+ const validatePickRows = () => {
+ if (materialTableData.value.length === 0) {
+ return { valid: false, message: "鏆傛棤鍙鐢ㄧ墿鏂�" };
+ }
+ const invalidRow = materialTableData.value.find(
+ item =>
+ item.actualQty === null ||
+ item.actualQty === undefined ||
+ item.actualQty === ""
+ );
+ if (invalidRow) {
+ return { valid: false, message: "璇峰~鍐欏疄闄呮暟閲忓悗鍐嶉鐢�" };
+ }
+ const exceedRow = materialTableData.value.find(item => {
+ const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0);
+ return Number(item.actualQty || 0) > maxQty;
+ });
+ if (exceedRow) {
+ return { valid: false, message: "瀹為檯鏁伴噺涓嶈兘澶т簬棰嗙敤鏁伴噺+琛ユ枡鏁伴噺" };
+ }
+ return { valid: true, message: "" };
+ };
+
+ const handleSubmitPick = async () => {
+ if (!currentMaterialOrderRow.value?.id) return;
+ const validateResult = validatePickRows();
+ if (!validateResult.valid) {
+ ElMessage.warning(validateResult.message);
+ return;
+ }
+ pickSubmitting.value = true;
+ try {
+ await pickWorkOrderMaterial({
+ workOrderId: currentMaterialOrderRow.value.id,
+ items: materialTableData.value.map(item => ({
+ materialLedgerId: item.id,
+ actualQty: Number(item.actualQty || 0),
+ })),
+ });
+ ElMessage.success("棰嗙敤鎴愬姛");
+ await loadMaterialTable(currentMaterialOrderRow.value);
+ emit("refresh");
+ } catch (e) {
+ console.error("棰嗙敤澶辫触", e);
+ ElMessage.error("棰嗙敤澶辫触");
+ } finally {
+ pickSubmitting.value = false;
+ }
+ };
+</script>
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 6def04c..600b274 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -1,10 +1,19 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<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">
+ <span class="search_title">鐢熶骇璁㈠崟鍙凤細</span>
+ <el-input v-model="searchForm.npsNo"
style="width: 240px"
placeholder="璇疯緭鍏�"
@change="handleQuery"
@@ -31,7 +40,6 @@
</template>
</PIMTable>
</div>
-
<!-- 娴佽浆鍗″脊绐� -->
<el-dialog v-model="transferCardVisible"
title="娴佽浆鍗�"
@@ -107,7 +115,6 @@
@click="printTransferCard">鎵撳嵃娴佽浆鍗�</el-button>
</div>
</el-dialog>
-
<!-- 鎶ュ伐寮圭獥 -->
<el-dialog v-model="reportDialogVisible"
title="鎶ュ伐"
@@ -121,14 +128,14 @@
readonly
style="width: 300px" />
</el-form-item>
- <el-form-item label="鏈鐢熶骇鏁伴噺"
+ <el-form-item label="鐢熶骇鍚堟牸鏁伴噺"
prop="quantity">
<el-input v-model.number="reportForm.quantity"
type="number"
- min="1"
+ min="0"
step="1"
style="width: 300px"
- placeholder="璇疯緭鍏ユ湰娆$敓浜ф暟閲�"
+ placeholder="璇疯緭鍏ョ敓浜у悎鏍兼暟閲�"
@input="handleQuantityInput" />
</el-form-item>
<el-form-item label="鎶ュ簾鏁伴噺"
@@ -154,6 +161,75 @@
:value="user.userId" />
</el-select>
</el-form-item>
+ <div v-if="params.length > 0"
+ class="param-grid"
+ v-loading="paramLoading">
+ <el-form-item v-for="param in params"
+ :key="param.id"
+ :label="param.paramName"
+ :label-width="120"
+ class="param-item">
+ <template v-if="param.paramType == '1'">
+ <div class="param-input-group">
+ <el-input-number v-model="reportForm.paramGroups[param.id]"
+ controls-position="right"
+ :key="param.id"
+ style="width: 250px"
+ class="param-input" />
+ <span v-if="param.unit && param.unit != '/'"
+ class="param-unit">{{ param.unit }}</span>
+ </div>
+ </template>
+ <template v-else-if="param.paramType == '2'">
+ <div class="param-input-group">
+ <el-input v-model="reportForm.paramGroups[param.id]"
+ :key="param.id"
+ style="width: 250px"
+ class="param-input" />
+ <span v-if="param.unit && param.unit != '/'"
+ class="param-unit">{{ param.unit }}</span>
+ </div>
+ </template>
+ <template v-else-if="param.paramType == '3'">
+ <div class="param-input-group">
+ <el-select v-model="reportForm.paramGroups[param.id]"
+ placeholder="璇烽�夋嫨"
+ :key="param.id"
+ class="param-select"
+ style="width: 250px">
+ <el-option v-for="option in dictOptions[param.paramFormat] || []"
+ :key="option.dictLabel"
+ :label="option.dictLabel"
+ :value="option.dictLabel" />
+ </el-select>
+ <span v-if="param.unit && param.unit != '/'"
+ class="param-unit">{{ param.unit }}</span>
+ </div>
+ </template>
+ <template v-else-if="param.paramType == '4'">
+ <div class="param-input-group">
+ <el-date-picker :value-format="param.paramFormat.replace('yyyy', 'YYYY').replace('dd', 'DD')"
+ :format="param.paramFormat.replace('yyyy', 'YYYY').replace('dd', 'DD')"
+ :key="param.id"
+ :type="param.paramFormat=='yyyy-MM-dd'?'date':'datetime'"
+ v-model="reportForm.paramGroups[param.id]"
+ class="param-input"
+ style="width: 250px" />
+ <span v-if="param.unit && param.unit != '/'"
+ class="param-unit">{{ param.unit }}</span>
+ </div>
+ </template>
+ <template v-else>
+ <div class="param-input-group">
+ <el-input v-model="reportForm.paramGroups[param.id]"
+ :key="param.id"
+ class="param-input" />
+ <span v-if="param.unit && param.unit != '/'"
+ class="param-unit">{{ param.unit }}</span>
+ </div>
+ </template>
+ </el-form-item>
+ </div>
</el-form>
<template #footer>
<span class="dialog-footer">
@@ -163,8 +239,14 @@
</span>
</template>
</el-dialog>
-
- <FilesDia ref="workOrderFilesRef" />
+ <MaterialDialog v-model="materialDialogVisible"
+ :row-data="currentMaterialOrderRow"
+ @refresh="getList" />
+ <FileList v-if="fileDialogVisible"
+ v-model:visible="fileDialogVisible"
+ :editable="!currentWorkOrderRow?.endOrder"
+ :record-type="'production_operation_task'"
+ :record-id="currentWorkOrderId" />
</div>
</template>
@@ -177,11 +259,19 @@
addProductMain,
downProductWorkOrder,
} from "@/api/productionManagement/workOrder.js";
+ import { findProcessParamListOrder } from "@/api/productionManagement/productProcessRoute.js";
import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
+ import { getDicts } from "@/api/system/dict/data";
import QRCode from "qrcode";
import { getCurrentInstance, reactive, toRefs } from "vue";
- import FilesDia from "./components/filesDia.vue";
+ import MaterialDialog from "./components/MaterialDialog.vue";
+ const FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+ );
+
+ import useUserStore from "@/store/modules/user";
const { proxy } = getCurrentInstance();
+ const userStore = useUserStore();
const tableColumn = ref([
{
@@ -196,7 +286,7 @@
},
{
label: "鐢熶骇璁㈠崟鍙�",
- prop: "productOrderNpsNo",
+ prop: "npsNo",
width: "140",
},
{
@@ -214,7 +304,8 @@
},
{
label: "宸ュ簭鍚嶇О",
- prop: "processName",
+ prop: "operationName",
+ width: "100",
},
{
label: "闇�姹傛暟閲�",
@@ -255,7 +346,7 @@
},
{
label: "鎿嶄綔",
- width: "200",
+ width: "260",
align: "center",
dataType: "action",
fixed: "right",
@@ -272,17 +363,38 @@
openWorkOrderFiles(row);
},
},
+ // {
+ // name: "鐗╂枡",
+ // clickFun: row => {
+ // openMaterialDialog(row);
+ // },
+ // },
{
name: "鎶ュ伐",
clickFun: row => {
showReportDialog(row);
},
- disabled: row => row.planQuantity <= 0,
+ showHide: row => !row.endOrder,
+ disabled: row => {
+ if (row.planQuantity <= 0) return true;
+ if (!row.userIds) return false;
+ try {
+ const userIds =
+ typeof row.userIds === "string"
+ ? JSON.parse(row.userIds)
+ : row.userIds;
+ if (Array.isArray(userIds)) {
+ return !userIds.some(id => String(id) === String(userStore.id));
+ }
+ return true;
+ } catch (e) {
+ return true;
+ }
+ },
},
],
},
]);
-
const tableData = ref([]);
const tableLoading = ref(false);
const transferCardVisible = ref(false);
@@ -290,7 +402,8 @@
const transferCardQrUrl = ref("");
const transferCardRowData = ref(null);
const reportDialogVisible = ref(false);
- const workOrderFilesRef = ref(null);
+ const fileDialogVisible = ref(false);
+ const currentWorkOrderId = ref(null);
const reportFormRef = ref(null);
const userOptions = ref([]);
const reportForm = reactive({
@@ -303,18 +416,25 @@
productProcessRouteItemId: "",
userId: "",
productMainId: null,
+ productionOrderRoutingOperationId: "",
+ productionOrderId: "",
+ paramGroups: {},
});
- // 鏈鐢熶骇鏁伴噺楠岃瘉瑙勫垯
+ const params = ref({});
+ const dictOptions = ref({});
+ const paramLoading = ref(false);
+
+ // 鐢熶骇鍚堟牸鏁伴噺楠岃瘉瑙勫垯
const validateQuantity = (rule, value, callback) => {
if (value === null || value === undefined || value === "") {
- callback(new Error("璇疯緭鍏ユ湰娆$敓浜ф暟閲�"));
+ callback(new Error("璇疯緭鍏ョ敓浜у悎鏍兼暟閲�"));
return;
}
const num = Number(value);
// 鏁存暟涓斿ぇ浜庣瓑浜�1
- if (isNaN(num) || !Number.isInteger(num) || num < 1) {
- callback(new Error("鏈鐢熶骇鏁伴噺蹇呴』澶т簬绛変簬1"));
+ if (isNaN(num) || !Number.isInteger(num) || num < 0) {
+ callback(new Error("鐢熶骇鍚堟牸鏁伴噺蹇呴』澶т簬绛変簬0"));
return;
}
callback();
@@ -341,7 +461,7 @@
scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
};
- // 澶勭悊鏈鐢熶骇鏁伴噺杈撳叆锛岄檺鍒跺繀椤诲ぇ浜庣瓑浜�1
+ // 澶勭悊鐢熶骇鍚堟牸鏁伴噺杈撳叆锛岄檺鍒跺繀椤诲ぇ浜庣瓑浜�0
const handleQuantityInput = value => {
if (value === "" || value === null || value === undefined) {
reportForm.quantity = null;
@@ -352,7 +472,7 @@
return;
}
// 濡傛灉灏忎簬1锛屾竻闄�
- if (num < 1) {
+ if (num < 0) {
reportForm.quantity = null;
return;
}
@@ -360,7 +480,7 @@
if (!Number.isInteger(num)) {
const intValue = Math.floor(num);
// 濡傛灉鍙栨暣鍚庡皬浜�1锛屾竻闄�
- if (intValue < 1) {
+ if (intValue < 0) {
reportForm.quantity = null;
return;
}
@@ -394,8 +514,10 @@
// 鏈夋晥鐨勯潪璐熸暣鏁帮紙鍖呮嫭0锛�
reportForm.scrapQty = num;
};
-
+
const currentReportRowData = ref(null);
+ const materialDialogVisible = ref(false);
+ const currentMaterialOrderRow = ref(null);
const page = reactive({
current: 1,
size: 100,
@@ -405,6 +527,7 @@
const data = reactive({
searchForm: {
workOrderNo: "",
+ npsNo: "",
},
});
const { searchForm } = toRefs(data);
@@ -429,13 +552,13 @@
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 };
@@ -513,9 +636,12 @@
const printTransferCard = () => {
window.print();
};
+ const currentWorkOrderRow = ref(null);
const openWorkOrderFiles = row => {
- workOrderFilesRef.value?.openDialog(row);
+ currentWorkOrderId.value = row.id;
+ currentWorkOrderRow.value = row;
+ fileDialogVisible.value = true;
};
const showReportDialog = row => {
@@ -529,10 +655,15 @@
reportForm.productMainId = row.productMainId;
reportForm.scrapQty =
row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
+ reportForm.productionOrderRoutingOperationId =
+ row.productionOrderRoutingOperationId;
+ reportForm.productionOrderId = row.productionOrderId;
nextTick(() => {
reportFormRef.value?.clearValidate();
+ if (row.productionOrderRoutingOperationId && row.productionOrderId) {
+ loadParams(row.productionOrderRoutingOperationId, row.productionOrderId);
+ }
});
- // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅锛岃缃负榛樿閫変腑
getUserProfile()
.then(res => {
if (res.code === 200) {
@@ -545,6 +676,11 @@
});
reportDialogVisible.value = true;
+ };
+
+ const openMaterialDialog = row => {
+ currentMaterialOrderRow.value = row;
+ materialDialogVisible.value = true;
};
const handleReport = () => {
@@ -560,13 +696,13 @@
return;
}
- // 楠岃瘉鏈鐢熶骇鏁伴噺
+ // 楠岃瘉鐢熶骇鍚堟牸鏁伴噺
if (
reportForm.quantity === null ||
reportForm.quantity === undefined ||
reportForm.quantity === ""
) {
- ElMessageBox.alert("璇疯緭鍏ユ湰娆$敓浜ф暟閲�", "鎻愮ず", {
+ ElMessageBox.alert("璇疯緭鍏ョ敓浜у悎鏍兼暟閲�", "鎻愮ず", {
confirmButtonText: "纭畾",
});
return;
@@ -574,19 +710,19 @@
const quantity = Number(reportForm.quantity);
- if (isNaN(quantity) || quantity <= 0) {
- ElMessageBox.alert("鏈鐢熶骇鏁伴噺蹇呴』澶т簬0", "鎻愮ず", {
+ if (isNaN(quantity) || quantity < 0) {
+ ElMessageBox.alert("鐢熶骇鍚堟牸鏁伴噺蹇呴』澶т簬绛変簬0", "鎻愮ず", {
confirmButtonText: "纭畾",
});
return;
}
- if (quantity > reportForm.planQuantity) {
- ElMessageBox.alert("鏈鐢熶骇鏁伴噺涓嶈兘瓒呰繃寰呯敓浜ф暟閲�", "鎻愮ず", {
- confirmButtonText: "纭畾",
- });
- return;
- }
+ // if (quantity > reportForm.planQuantity) {
+ // ElMessageBox.alert("鏈鐢熶骇鏁伴噺涓嶈兘瓒呰繃寰呯敓浜ф暟閲�", "鎻愮ず", {
+ // confirmButtonText: "纭畾",
+ // });
+ // return;
+ // }
// 楠岃瘉鎶ュ簾鏁伴噺
const scrapQty = Number(reportForm.scrapQty);
@@ -597,25 +733,34 @@
return;
}
- if (!isNaN(scrapQty) && scrapQty > quantity) {
- ElMessageBox.alert("鎶ュ簾鏁伴噺涓嶈兘澶т簬鏈鐢熶骇鏁伴噺", "鎻愮ず", {
- confirmButtonText: "纭畾",
- });
- return;
- }
+ // if (!isNaN(scrapQty) && scrapQty > quantity) {
+ // ElMessageBox.alert("鎶ュ簾鏁伴噺涓嶈兘澶т簬鏈鐢熶骇鏁伴噺", "鎻愮ず", {
+ // confirmButtonText: "纭畾",
+ // });
+ // return;
+ // }
- const params = {
+ const productionOperationParamList = params.value.map(param => ({
+ ...param,
+ inputValue: reportForm.paramGroups[param.id] ?? "",
+ }));
+
+ const submitParams = {
quantity: quantity,
scrapQty: isNaN(scrapQty) ? 0 : scrapQty,
userId: reportForm.userId,
userName: reportForm.userName,
- workOrderId: reportForm.workOrderId,
+ productionOperationTaskId: reportForm.workOrderId,
productProcessRouteItemId: reportForm.productProcessRouteItemId,
reportWork: reportForm.reportWork,
productMainId: reportForm.productMainId,
+ productionOrderRoutingOperationId:
+ reportForm.productionOrderRoutingOperationId,
+ productionOrderId: reportForm.productionOrderId,
+ productionOperationParamList: productionOperationParamList,
};
- addProductMain(params)
+ addProductMain(submitParams)
.then(res => {
proxy.$modal.msgSuccess("鎶ュ伐鎴愬姛");
reportDialogVisible.value = false;
@@ -634,7 +779,53 @@
reportForm.userName = user ? user.nickName : "";
};
+ const getDictOptions = async dictType => {
+ if (!dictType) return [];
+ if (dictOptions.value[dictType]) return dictOptions.value[dictType];
+ try {
+ const res = await getDicts(dictType);
+ if (res.code === 200) {
+ dictOptions.value[dictType] = res.data;
+ return res.data;
+ }
+ return [];
+ } catch (error) {
+ console.error("鑾峰彇瀛楀吀鏁版嵁澶辫触:", error);
+ return [];
+ }
+ };
+
+ const loadParams = (productionOrderRoutingOperationId, productionOrderId) => {
+ paramLoading.value = true;
+ findProcessParamListOrder({
+ productionOrderRoutingOperationId,
+ productionOrderId,
+ })
+ .then(res => {
+ if (res.code === 200) {
+ const paramList = res.data || [];
+ params.value = paramList;
+ reportForm.paramGroups = {};
+ paramList.forEach(param => {
+ if (!reportForm.paramGroups[param.id]) {
+ reportForm.paramGroups[param.id] = "";
+ }
+ if (param.paramType == "3" && param.paramFormat) {
+ getDictOptions(param.paramFormat);
+ }
+ });
+ }
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭鍙傛暟澶辫触:", err);
+ })
+ .finally(() => {
+ paramLoading.value = false;
+ });
+ };
+
onMounted(() => {
+ userStore.getInfo();
getList();
// 鑾峰彇鐢ㄦ埛鍒楄〃
userListNoPageByTenantId().then(res => {
@@ -703,6 +894,30 @@
.print-button-container {
text-align: center;
margin-top: 20px;
+ }
+ .param-grid {
+ margin-top: 10px;
+ border-top: 1px solid #ebe9f3;
+ padding-top: 10px;
+ }
+ .param-item {
+ margin-bottom: 12px;
+ }
+ .param-input-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ .param-input {
+ flex: 1;
+ }
+ .param-select {
+ flex: 1;
+ }
+ .param-unit {
+ color: #909399;
+ font-size: 12px;
+ min-width: 30px;
}
</style>
@@ -786,4 +1001,4 @@
height: 140px !important;
}
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/productionPlan/productionPlan/components/PIMTable.vue b/src/views/productionPlan/productionPlan/components/PIMTable.vue
new file mode 100644
index 0000000..9431002
--- /dev/null
+++ b/src/views/productionPlan/productionPlan/components/PIMTable.vue
@@ -0,0 +1,470 @@
+<template>
+ <el-table ref="multipleTable"
+ v-loading="tableLoading"
+ :border="border"
+ :data="tableData"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ :height="height"
+ :highlight-current-row="highlightCurrentRow"
+ :row-class-name="rowClassName"
+ :row-style="rowStyle"
+ :row-key="rowKey"
+ :style="tableStyle"
+ tooltip-effect="dark"
+ :expand-row-keys="expandRowKeys"
+ :show-summary="isShowSummary"
+ :summary-method="summaryMethod"
+ @row-click="rowClick"
+ @current-change="currentChange"
+ @selection-change="handleSelectionChange"
+ @expand-change="expandChange"
+ @select-all="handleSelectAll"
+ class="lims-table">
+ <el-table-column align="center"
+ type="selection"
+ width="55"
+ v-if="isSelection"
+ :selectable="selectable" />
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column v-for="(item, index) in column"
+ :key="index"
+ :column-key="item.columnKey"
+ :filter-method="item.filterHandler"
+ :filter-multiple="item.filterMultiple"
+ :filtered-value="item.filteredValue"
+ :filters="item.filters"
+ :fixed="item.fixed"
+ :label="item.label"
+ :prop="item.prop"
+ :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
+ :align="item.align"
+ :sortable="!!item.sortable"
+ :type="item.type"
+ :width="item.width"
+ :class-name="item.className || ''">
+ <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">
+ <slot v-if="item.theadSlot"
+ :name="item.theadSlot"
+ :index="scope.$index"
+ :row="scope.row" />
+ </template>
+ <template #default="scope">
+ <!-- 鎻掓Ы -->
+ <div v-if="item.dataType == 'slot'"
+ :class="item.className || ''">
+ <slot v-if="item.slot"
+ :index="scope.$index"
+ :name="item.slot"
+ :row="scope.row" />
+ </div>
+ <!-- 杩涘害鏉� -->
+ <div v-else-if="item.dataType == 'progress'"
+ :class="item.className || ''">
+ <el-progress :percentage="Number(scope.row[item.prop])" />
+ </div>
+ <!-- 鍥剧墖 -->
+ <div v-else-if="item.dataType == 'image'"
+ :class="item.className || ''">
+ <img :src="javaApi + '/img/' + scope.row[item.prop]"
+ alt=""
+ style="width: 40px; height: 40px; margin-top: 10px" />
+ </div>
+ <!-- tag -->
+ <div v-else-if="item.dataType == 'tag'"
+ :class="item.className || ''">
+ <el-tag v-if="
+ typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
+ 'string'
+ "
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(scope.row[item.prop], item.formatType)">
+ {{ formatters(scope.row[item.prop], item.formatData) }}
+ </el-tag>
+ <el-tag v-for="(tag, index) in dataTypeFn(
+ scope.row[item.prop],
+ item.formatData
+ )"
+ v-else-if="
+ typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
+ 'object'
+ "
+ :key="index"
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(tag, item.formatType)">
+ {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }}
+ </el-tag>
+ <el-tag v-else
+ :title="formatters(scope.row[item.prop], item.formatData)"
+ :type="formatType(scope.row[item.prop], item.formatType)">
+ {{ formatters(scope.row[item.prop], item.formatData) }}
+ </el-tag>
+ </div>
+ <!-- 鎸夐挳 -->
+ <div v-else-if="item.dataType == 'action'"
+ :class="item.className || ''"
+ @click.stop>
+ <template v-for="(o, key) in item.operation"
+ :key="key">
+ <el-button v-show="o.type != 'upload'"
+ v-if="o.showHide ? o.showHide(scope.row) : true"
+ :disabled="o.disabled ? o.disabled(scope.row) : false"
+ :plain="o.plain"
+ type="primary"
+ :style="{
+ color:
+ o.name === '鍒犻櫎' || o.name === 'delete'
+ ? '#f56c6c'
+ : o.color,
+ }"
+ link
+ @click.stop="o.clickFun(scope.row)"
+ :key="key">
+ {{ o.name }}
+ </el-button>
+ <el-upload :action="
+ javaApi +
+ o.url +
+ '?id=' +
+ (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
+ "
+ ref="uploadRef"
+ :multiple="o.multiple ? o.multiple : false"
+ :limit="1"
+ :disabled="o.disabled ? o.disabled(scope.row) : false"
+ :accept="
+ o.accept
+ ? o.accept
+ : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
+ "
+ v-if="o.type == 'upload'"
+ style="display: inline-block; width: 50px"
+ v-show="o.showHide ? o.showHide(scope.row) : true"
+ :headers="uploadHeader"
+ :before-upload="(file) => beforeUpload(file, scope.$index)"
+ :on-change="
+ (file, fileList) => handleChange(file, fileList, scope.$index)
+ "
+ :on-error="
+ (error, file, fileList) =>
+ onError(error, file, fileList, scope.$index)
+ "
+ :on-success="
+ (response, file, fileList) =>
+ handleSuccessUp(response, file, fileList, scope.$index)
+ "
+ :on-exceed="onExceed"
+ :show-file-list="false">
+ <el-button link
+ type="primary"
+ :disabled="o.disabled ? o.disabled(scope.row) : false">{{ o.name }}</el-button>
+ </el-upload>
+ </template>
+ </div>
+ <!-- 鍙偣鍑荤殑鏂囧瓧 -->
+ <div v-else-if="item.dataType == 'link'"
+ :class="item.className || ''"
+ class="cell link"
+ style="width: 100%"
+ @click="goLink(scope.row, item.linkMethod)">
+ <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
+ </div>
+ <!-- 榛樿绾睍绀烘暟鎹� -->
+ <div v-else
+ class="cell"
+ :class="item.className || ''"
+ style="width: 100%">
+ <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
+ <span v-else>{{
+ formatters(scope.row[item.prop], item.formatData)
+ }}</span>
+ </div>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination v-if="isShowPagination"
+ :total="page.total"
+ :layout="page.layout"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationSearch" />
+</template>
+
+<script setup>
+ import pagination from "../../../../components/PIMTable/Pagination.vue";
+ import { ref, inject, getCurrentInstance } from "vue";
+ import { ElMessage } from "element-plus";
+
+ // 鑾峰彇鍏ㄥ眬鐨� uploadHeader
+ const { proxy } = getCurrentInstance();
+ const uploadHeader = proxy.uploadHeader;
+ const javaApi = proxy.javaApi;
+
+ const emit = defineEmits([
+ "pagination",
+ "expand-change",
+ "selection-change",
+ "row-click",
+ ]);
+
+ // Filters
+ const typeFn = (val, row) => {
+ return typeof val === "function" ? val(row) : val;
+ };
+
+ const formatters = (val, format) => {
+ return typeof format === "function" ? format(val) : val;
+ };
+
+ // Props锛堜娇鐢� defineProps 鐨勯潪 TS 褰㈠紡锛�
+ const props = defineProps({
+ tableLoading: {
+ type: Boolean,
+ default: false,
+ },
+ height: {
+ type: [Number, String],
+ default: "calc(100vh - 22em)",
+ },
+ expandRowKeys: {
+ type: Array,
+ default: () => [],
+ },
+ summaryMethod: {
+ type: Function,
+ default: () => {},
+ },
+ rowClick: {
+ type: Function,
+ default: () => {},
+ },
+ currentChange: {
+ type: Function,
+ default: () => {},
+ },
+ border: {
+ type: Boolean,
+ default: true,
+ },
+ isSelection: {
+ type: Boolean,
+ default: false,
+ },
+ selectable: {
+ type: Function,
+ default: () => true,
+ },
+ isShowPagination: {
+ type: Boolean,
+ default: true,
+ },
+ isShowSummary: {
+ type: Boolean,
+ default: false,
+ },
+ highlightCurrentRow: {
+ type: Boolean,
+ default: false,
+ },
+ headerCellStyle: {
+ type: Object,
+ default: () => ({}),
+ },
+ column: {
+ type: Array,
+ default: () => [],
+ },
+ rowClassName: {
+ type: Function,
+ default: () => "",
+ },
+ rowStyle: {
+ type: [Object, Function],
+ default: () => ({}),
+ },
+ tableData: {
+ type: Array,
+ default: () => [],
+ },
+ rowKey: {
+ type: String,
+ default: "id",
+ },
+ page: {
+ type: Object,
+ default: () => ({
+ total: 0,
+ current: 0,
+ size: 10,
+ layout: "total, sizes, prev, pager, next, jumper",
+ }),
+ },
+ total: {
+ type: Number,
+ default: 0,
+ },
+ tableStyle: {
+ type: [String, Object],
+ default: () => ({ width: "100%" }),
+ },
+ });
+
+ // Data
+ const multipleTable = ref(null);
+ const uploadRefs = ref([]);
+ const currentFiles = ref({});
+ const uploadKeys = ref({});
+
+ const indexMethod = index => {
+ return (props.page.current - 1) * props.page.size + index + 1;
+ };
+
+ // 鐐瑰嚮 link 浜嬩欢
+ const goLink = (row, linkMethod) => {
+ if (!linkMethod) {
+ return ElMessage.warning("璇烽厤缃� link 浜嬩欢");
+ }
+ const parentMethod = getParentMethod(linkMethod);
+ if (typeof parentMethod === "function") {
+ parentMethod(row);
+ } else {
+ console.warn(`鐖剁粍浠朵腑鏈壘鍒版柟娉�: ${linkMethod}`);
+ }
+ };
+
+ // 鑾峰彇鐖剁粍浠舵柟娉曪紙绀轰緥瀹炵幇锛�
+ const getParentMethod = methodName => {
+ const parentMethods = inject("parentMethods", {});
+ return parentMethods[methodName];
+ };
+
+ const dataTypeFn = (val, format) => {
+ if (typeof format === "function") {
+ return format(val);
+ } else return val;
+ };
+
+ const formatType = (val, format) => {
+ if (typeof format === "function") {
+ return format(val);
+ } else return "";
+ };
+
+ // 鏂囦欢鍙樺寲澶勭悊
+ const handleChange = (file, fileList, index) => {
+ if (fileList.length > 1) {
+ const earliestFile = fileList[0];
+ uploadRefs.value[index]?.handleRemove(earliestFile);
+ }
+ currentFiles.value[index] = file;
+ };
+
+ // 鏂囦欢涓婁紶鍓嶆牎楠�
+ const beforeUpload = (rawFile, index) => {
+ currentFiles.value[index] = {};
+ if (rawfile.size > 1024 * 1024 * 10 * 10) {
+ ElMessage.error("涓婁紶鏂囦欢涓嶈秴杩�10M");
+ return false;
+ }
+ return true;
+ };
+
+ // 涓婁紶鎴愬姛
+ const handleSuccessUp = (response, file, fileList, index) => {
+ if (response.code == 200) {
+ if (uploadRefs[index]) {
+ uploadRefs[index].clearFiles();
+ }
+ currentFiles[index] = file;
+ ElMessage.success("涓婁紶鎴愬姛");
+ resetUploadComponent(index);
+ } else {
+ ElMessage.error(response.message);
+ }
+ };
+
+ const resetUploadComponent = index => {
+ uploadKeys[index] = Date.now();
+ };
+
+ // 涓婁紶澶辫触
+ const onError = (error, file, fileList, index) => {
+ ElMessage.error("鏂囦欢涓婁紶澶辫触锛岃閲嶈瘯");
+ if (uploadRefs.value[index]) {
+ uploadRefs.value[index].clearFiles();
+ }
+ };
+
+ // 鏂囦欢鏁伴噺瓒呴檺鎻愮ず
+ const onExceed = () => {
+ ElMessage.warning("瓒呭嚭鏂囦欢涓暟");
+ };
+
+ const paginationSearch = ({ page, limit }) => {
+ emit("pagination", { page: page, limit: limit });
+ };
+
+ const rowClick = row => {
+ emit("row-click", row);
+ };
+
+ const expandChange = (row, expandedRows) => {
+ emit("expand-change", row, expandedRows);
+ };
+
+ const handleSelectionChange = newSelection => {
+ emit("selection-change", newSelection);
+ };
+
+ // 澶勭悊鍏ㄩ�夋搷浣�
+ const handleSelectAll = selection => {
+ if (selection.length) {
+ console.log(selection, "selection");
+ // 鍏ㄩ�夋椂锛屽彧閫夋嫨鍙�夋嫨鐨勮
+ const selectableRows = props.tableData.filter(row => props.selectable(row));
+ // 娓呯┖褰撳墠閫夋嫨
+ multipleTable.value.clearSelection();
+ // 鍙�夋嫨鍙�夋嫨鐨勮
+ selectableRows.forEach(row => {
+ multipleTable.value.toggleRowSelection(row, true);
+ });
+ } else {
+ // 鍙栨秷鍏ㄩ�夋椂锛屽彧鍙栨秷鍙�夋嫨鐨勮鐨勯�夋嫨
+ props.tableData.forEach(row => {
+ if (props.selectable(row)) {
+ multipleTable.value.toggleRowSelection(row, false);
+ }
+ });
+ }
+ };
+</script>
+
+<style scoped lang="scss">
+ .cell {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ 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/views/productionPlan/productionPlan/index.vue b/src/views/productionPlan/productionPlan/index.vue
new file mode 100644
index 0000000..46d0548
--- /dev/null
+++ b/src/views/productionPlan/productionPlan/index.vue
@@ -0,0 +1,1332 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ ref="queryRef"
+ :inline="true">
+ <!-- 绠�鍖栫増鎼滅储鏉′欢 -->
+ <el-form-item label="涓荤敓浜ц鍒掑彿:"
+ prop="mpsNo">
+ <el-input v-model="searchForm.mpsNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="闇�姹傛棩鏈熻寖鍥�:"
+ prop="dateRange">
+ <el-date-picker v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 240px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="涓嬪彂鐘舵��:"
+ prop="status">
+ <el-select v-model="searchForm.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ clearable
+ filterable
+ style="width: 100px">
+ <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="productName">
+ <el-input v-model="searchForm.productName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item label="浜у搧瑙勬牸:"
+ prop="model">
+ <el-input v-model="searchForm.model"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ <el-button type="info"
+ @click="handleReset">閲嶇疆</el-button>
+ <el-button type="primary"
+ @click="handleAdd">鏂板</el-button>
+ <el-button type="warning"
+ @click="handleMerge">鍚堝苟涓嬪彂</el-button>
+ <el-button type="warning"
+ @click="handleImport">瀵煎叆</el-button>
+ <el-button type="warning"
+ @click="handleExport">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+ <div>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ height="calc(100vh - 350px)"
+ :tableLoading="tableLoading"
+ :isSelection="true"
+ :selectable="isSelectable"
+ @selection-change="handleSelectionChange"
+ @pagination="pagination">
+ <template #qtyRequired="{ row }">
+ {{ row.qtyRequired || '-' }}<span style="color:rgba(12, 46, 40, 0.76)"> {{ row.unit || '鏂�' }}</span>
+ </template>
+ <template #salesContractNo="{ row }">
+ <el-button type="primary"
+ text
+ link
+ @click="showDetail(row)">{{ row.salesContractNo }}
+ </el-button>
+ </template>
+ </PIMTable>
+ </div>
+ <!-- 鍚堝苟涓嬪彂寮圭獥 -->
+ <el-dialog v-model="isShowNewModal"
+ destroy-on-close
+ title="鍚堝苟涓嬪彂"
+ width="600px">
+ <el-form :model="mergeForm"
+ label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="10">
+ <el-form-item label="浜у搧鍚嶇О">
+ <el-tag class="info-display">{{ mergeForm.productName || '-' }}</el-tag>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col>
+ <el-form-item label="浜у搧瑙勬牸">
+ <div class="info-display">{{ mergeForm.model || '-' }}</div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="璁″垝瀹屾垚鏃堕棿">
+ <el-date-picker v-model="mergeForm.planCompleteTime"
+ type="date"
+ value-format="YYYY-MM-DD"
+ style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="鐢熶骇鏁伴噺">
+ <el-input-number v-model="mergeForm.totalAssignedQuantity"
+ :min="0"
+ :max="sumAssignedQuantity"
+ @change="onBlur"
+ style="width: 100%" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" @click="handleMergeSubmit">纭畾涓嬪彂</el-button>
+ <el-button @click="isShowNewModal = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <!-- 瀵煎叆寮圭獥 -->
+ <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" />
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog v-model="dialogVisible"
+ destroy-on-close
+ :title="operationType === 'add' ? '鏂板鐢熶骇璁″垝' : '缂栬緫鐢熶骇璁″垝'"
+ width="600px">
+ <el-form ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="120px">
+ <el-form-item label="涓荤敓浜ц鍒掑彿"
+ prop="mpsNo">
+ <el-input v-model="form.mpsNo"
+ disabled
+ placeholder="鏂板鍚庤嚜鍔ㄧ敓鎴�" />
+ </el-form-item>
+ <el-form-item label="浜у搧鍚嶇О"
+ prop="productId">
+ <el-tree-select v-model="form.productId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :data="productOptions"
+ :render-after-expand="false"
+ filterable
+ @change="handleProductChange"
+ style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="浜у搧瑙勬牸"
+ prop="productModelId">
+ <el-select v-model="form.productModelId"
+ @change="handleChangeSpecification"
+ filterable
+ style="width: 100%"
+ placeholder="璇烽�夋嫨">
+ <el-option v-for="item in specificationOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎵�闇�鏁伴噺"
+ prop="qtyRequired">
+ <el-input-number v-model="form.qtyRequired"
+ :min="0"
+ style="width: 100%"
+ 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="requiredDate">
+ <el-date-picker v-model="form.requiredDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ style="width: 100%"
+ placeholder="璇烽�夋嫨闇�姹傛棩鏈�" />
+ </el-form-item>
+ <el-form-item label="鎵胯鏃ユ湡"
+ prop="promisedDeliveryDate">
+ <el-date-picker v-model="form.promisedDeliveryDate"
+ type="date"
+ value-format="YYYY-MM-DD"
+ style="width: 100%"
+ placeholder="璇烽�夋嫨鎵胯鏃ユ湡" />
+ </el-form-item>
+ <el-form-item label="澶囨敞"
+ prop="remark">
+ <el-input v-model="form.remark"
+ type="textarea"
+ placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import {
+ reactive,
+ ref,
+ onMounted,
+ toRefs,
+ getCurrentInstance,
+ computed,
+ } from "vue";
+ import { useRouter } from "vue-router";
+ import dayjs from "dayjs";
+ import { ElMessage } from "element-plus";
+ import { ArrowUp, ArrowDown } from "@element-plus/icons-vue";
+ import { getToken } from "@/utils/auth";
+ import { useDict } from "@/utils/dict";
+ import {
+ productionPlanListPage,
+ productionPlanAdd,
+ productionPlanUpdate,
+ productionPlanDelete,
+ productionPlanCombine,
+ } from "@/api/productionPlan/productionPlan.js";
+ import { productTreeList, modelListPage } from "@/api/basicData/product.js";
+ import PIMTable from "./components/PIMTable.vue";
+ import ImportDialog from "@/components/Dialog/ImportDialog.vue";
+
+ const { proxy } = getCurrentInstance();
+ const router = useRouter();
+
+ const loadProdData = () => {
+ console.log("Mock loadProdData called");
+ return Promise.resolve({ code: 200, msg: "鍚屾鎴愬姛" });
+ };
+
+ const exportProductionPlan = () => {
+ console.log("Mock exportProductionPlan called");
+ return Promise.resolve();
+ };
+
+ // const productionPlanCombine = payload => {
+ // console.log("Mock productionPlanCombine called with:", payload);
+ // return Promise.resolve({ code: 200, msg: "鍚堝苟涓嬪彂鎴愬姛" });
+ // };
+
+ const tableColumn = ref([
+ {
+ label: "涓荤敓浜ц鍒掑彿",
+ prop: "mpsNo",
+ width: "150px",
+ },
+ {
+ label: "鏉ユ簮",
+ prop: "source",
+ width: "150px",
+ dataType: "tag",
+ formatType: params => {
+ return params == "閿�鍞�" ? "primary" : "info";
+ },
+ formatData: params => {
+ return params == "閿�鍞�" ? "閿�鍞�" : "鍐呴儴";
+ },
+ },
+
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ width: "200px",
+ dataType: "tag",
+ formatType: params => {
+ return "primary";
+ },
+ },
+ {
+ label: "浜у搧瑙勬牸",
+ prop: "model",
+ width: "150px",
+ className: "spec-cell",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ width: "100px",
+ },
+ {
+ label: "鎵�闇�鏁伴噺",
+ prop: "qtyRequired",
+ width: "150px",
+ align: "right",
+ dataType: "slot",
+ slot: "qtyRequired",
+ className: "volume-cell",
+ },
+ {
+ label: "涓嬪彂鐘舵��",
+ prop: "status",
+ width: "120px",
+ className: "status-cell",
+ dataType: "tag",
+ formatType: params => {
+ const typeMap = {
+ 0: "warning",
+ 1: "primary",
+ 2: "info",
+ };
+ return typeMap[params] || "info";
+ },
+ formatData: cell => {
+ const statusMap = {
+ 0: "寰呬笅鍙�",
+ 1: "閮ㄥ垎涓嬪彂",
+ 2: "宸蹭笅鍙�",
+ };
+ return statusMap[cell] || "";
+ },
+ },
+ {
+ label: "宸蹭笅鍙戞暟閲�",
+ prop: "quantityIssued",
+ width: "120px",
+ className: "spec-cell",
+ // formatData: (cell, row) => (cell ? `${cell}${row.unit || "鏂�"}` : 0),
+ },
+ {
+ label: "闇�姹傛棩鏈�",
+ prop: "requiredDate",
+ width: "160px",
+ className: "date-cell",
+ formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
+ },
+ {
+ label: "鎵胯鏃ユ湡",
+ prop: "promisedDeliveryDate",
+ width: "160px",
+ className: "date-cell",
+ formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
+ },
+ {
+ label: "閿�鍞悎鍚屽彿",
+ prop: "salesContractNo",
+ width: "200px",
+ dataType: "slot",
+ slot: "salesContractNo",
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width: "150px",
+ },
+ {
+ label: "椤圭洰鍚嶇О",
+ prop: "projectName",
+ width: "150px",
+ },
+ {
+ label: "澶囨敞",
+ width: "150px",
+ prop: "remark",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 250,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "primary",
+ link: true,
+ showHide: row => {
+ return row.status == 0 && row.source != "閿�鍞�";
+ },
+ clickFun: row => {
+ handleEdit(row);
+ },
+ },
+ {
+ name: "涓嬪彂",
+ type: "text",
+ showHide: row => {
+ return row.status != 2;
+ },
+ clickFun: row => {
+ mergeForm.productName = row.productName || "";
+ mergeForm.model = row.model || "";
+ mergeForm.totalAssignedQuantity =
+ Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
+ mergeForm.planCompleteTime = row.requiredDate || "";
+ mergeForm.productId = row.productId || "";
+ mergeForm.ids = [row.id];
+ sumAssignedQuantity.value =
+ Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
+ isShowNewModal.value = true;
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ showHide: row => {
+ return row.status == 0;
+ },
+ clickFun: row => {
+ handleDelete(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const selectedRows = ref([]);
+
+ // 浜у搧绫诲埆姹囨�荤粺璁℃暟鎹�
+ const categorySummary = ref([]);
+ // 浜у搧绫诲埆姹囨�诲脊绐楁帶鍒�
+ const showCategorySummaryDialog = ref(false);
+
+ // 鍚堝苟涓嬪彂寮圭獥鎺у埗
+ const isShowNewModal = ref(false);
+ // 鍚堝苟涓嬪彂琛ㄥ崟鏁版嵁
+ const mergeForm = reactive({
+ productName: "",
+ model: "",
+ totalAssignedQuantity: 0,
+ planCompleteTime: "",
+ productId: "",
+ });
+
+ // 瀵煎叆鐩稿叧
+ const importDialogRef = ref(null);
+ const importDialogVisible = ref(false);
+ const importAction =
+ import.meta.env.VITE_APP_BASE_API + "/productionPlan/import";
+ const importHeaders = ref({
+ Authorization: `Bearer ${getToken()}`,
+ });
+
+ // 鏂板/缂栬緫鐩稿叧
+ const dialogVisible = ref(false);
+ const operationType = ref("add"); // add | edit
+ const productOptions = ref([]);
+ const specificationOptions = ref([]);
+ const queryRef = ref(null);
+ const formRef = ref(null);
+ const form = reactive({
+ id: undefined,
+ mpsNo: "",
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ qtyRequired: 0,
+ unit: "鏂�",
+ requiredDate: "",
+ promisedDeliveryDate: "",
+ remark: "",
+ });
+ const rules = reactive({
+ productId: [{ required: true, message: "璇烽�夋嫨浜у搧", trigger: "change" }],
+ productModelId: [
+ { required: true, message: "璇烽�夋嫨浜у搧瑙勬牸", trigger: "change" },
+ ],
+ qtyRequired: [{ required: true, message: "璇疯緭鍏ユ暟閲�", trigger: "blur" }],
+ requiredDate: [
+ { required: true, message: "璇烽�夋嫨闇�姹傛棩鏈�", trigger: "change" },
+ ],
+ });
+
+ // 澶勭悊杩借釜杩涘害鎸夐挳鐐瑰嚮
+ const handleTrackProgress = row => {
+ // 璺宠浆鍒拌拷韪繘搴﹂〉闈�
+ router.push({
+ path: "/productionPlan/trackProgress",
+ query: {
+ id: row.id,
+ productName: row.productName,
+ model: row.model,
+ },
+ });
+ };
+ const onBlur = value => {
+ // 闄愬埗鍥涗綅灏忔暟
+ mergeForm.totalAssignedQuantity = Number(value.toFixed(4));
+ };
+
+ const fetchProductOptions = () => {
+ return productTreeList().then(res => {
+ productOptions.value = convertIdToValue(res || []);
+ });
+ };
+
+ const convertIdToValue = data => {
+ return data.map(item => {
+ const newItem = {
+ value: item.id,
+ label: item.label,
+ };
+ if (item.children && item.children.length > 0) {
+ newItem.children = convertIdToValue(item.children);
+ }
+ return newItem;
+ });
+ };
+
+ const handleProductChange = value => {
+ form.productModelId = undefined;
+ form.model = undefined;
+ // 鏌ユ壘閫変腑鐨勪骇鍝佸悕绉�
+ const findProductName = (options, val) => {
+ for (const option of options) {
+ if (option.value === val) {
+ return option.label;
+ }
+ if (option.children) {
+ const found = findProductName(option.children, val);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return "";
+ };
+ form.productName = findProductName(productOptions.value, value);
+ fetchSpecificationOptions(value);
+ };
+
+ const fetchSpecificationOptions = productId => {
+ specificationOptions.value = [];
+ if (productId) {
+ modelListPage({ id: productId, size: 1000, current: 1 }).then(res => {
+ specificationOptions.value = res.records || [];
+ });
+ }
+ };
+
+ const handleChangeSpecification = value => {
+ form.model = undefined;
+ form.unit = "";
+ const selectedModel = specificationOptions.value.find(
+ item => item.id === value
+ );
+ if (selectedModel) {
+ form.model = selectedModel.model;
+ form.unit = selectedModel.unit || "鏂�";
+ }
+ };
+
+ const data = reactive({
+ searchForm: {
+ mpsNo: "",
+ productName: "",
+ model: "",
+ status: "",
+ dateRange: [],
+ },
+ searchFormExpanded: false,
+ });
+ const { searchForm, searchFormExpanded } = toRefs(data);
+
+ // 鍒囨崲鎼滅储琛ㄥ崟灞曞紑/鏀惰捣鐘舵��
+ const toggleSearchForm = () => {
+ data.searchFormExpanded = !data.searchFormExpanded;
+ };
+
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+
+ /** 閲嶇疆鎸夐挳鎿嶄綔 */
+ const handleReset = () => {
+ if (proxy.resetForm) {
+ proxy.resetForm("queryRef");
+ }
+ Object.assign(searchForm.value, {
+ mpsNo: "",
+ productName: "",
+ model: "",
+ status: "",
+ dateRange: [],
+ });
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ // 璁$畻浜у搧绫诲埆姹囨�荤粺璁�
+ const calculateCategorySummary = () => {
+ const summary = {};
+
+ // 閬嶅巻琛ㄦ牸鏁版嵁锛屾寜浜у搧绫诲埆姹囨��
+ tableData.value.forEach(row => {
+ const category = row.productName || "鏈煡浜у搧";
+ if (!summary[category]) {
+ summary[category] = {
+ materialCode: category,
+ totalAssignedQuantity: 0,
+ };
+ }
+ summary[category].totalAssignedQuantity += Number(
+ (Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0)).toFixed(
+ 4
+ )
+ );
+ });
+
+ // 杞崲涓烘暟缁勬牸寮�
+ categorySummary.value = Object.values(summary);
+ };
+
+ const getList = () => {
+ tableLoading.value = true;
+ // 鏋勯�犳悳绱㈠弬鏁�
+ const params = { ...searchForm.value, ...page };
+ params.requiredDateStart =
+ params.dateRange && params.dateRange.length > 0 ? params.dateRange[0] : "";
+ params.requiredDateEnd =
+ params.dateRange && params.dateRange.length > 1 ? params.dateRange[1] : "";
+ delete params.dateRange;
+ productionPlanListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ // 璁$畻浜у搧绫诲埆姹囨�荤粺璁�
+ calculateCategorySummary();
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+ };
+
+ // 閫変腑鐨勪骇鍝佽鏍糏D
+ const selectedProductModelId = ref("");
+
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ // 濡傛灉鏈夐�変腑鐨勮锛岃褰曠涓�涓�変腑琛岀殑浜у搧瑙勬牸ID
+ if (selection.length > 0) {
+ selectedProductModelId.value = selection[0].productModelId;
+ } else {
+ // 濡傛灉娌℃湁閫変腑鐨勮锛屾竻绌轰骇鍝佽鏍糏D
+ selectedProductModelId.value = "";
+ }
+ };
+
+ // 鍒ゆ柇琛屾槸鍚﹀彲閫夋嫨
+ const isSelectable = row => {
+ // 濡傛灉鏄凡涓嬪彂鐘舵�侊紝绂佹鍕鹃��
+ if (row.status == 2) {
+ return false;
+ }
+ // 璁$畻鍓╀綑鏁伴噺
+ const remainingQty = (row.qtyRequired || 0) - (row.quantityIssued || 0);
+ // 濡傛灉鍓╀綑鏁伴噺灏忎簬绛変簬0锛岀姝㈤�夋嫨
+ if (remainingQty <= 0) {
+ return false;
+ }
+ // 濡傛灉娌℃湁閫変腑鐨勮锛屾墍鏈夎閮藉彲閫夋嫨
+ if (!selectedProductModelId.value) {
+ return true;
+ }
+ // 濡傛灉鏈夐�変腑鐨勮锛屽彧鏈変骇鍝佽鏍糏D鐩稿悓鐨勮鎵嶅彲閫夋嫨
+ return row.productModelId === selectedProductModelId.value;
+ };
+ // 鎷夊彇鏁版嵁鎸夐挳鎿嶄綔
+ const loadProdDataLoading = ref(false);
+ const sumAssignedQuantity = ref(0);
+
+ // 澶勭悊鍚堝苟涓嬪彂鎸夐挳鐐瑰嚮
+ const handleMerge = () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨瑕佸悎骞朵笅鍙戠殑鐢熶骇璁″垝");
+ return;
+ }
+ console.log(selectedRows.value);
+ const firstRow = selectedRows.value[0];
+ const productName = firstRow.productName || "";
+
+ // 璁$畻鎬诲埗閫犳暟閲� (榛樿qtyRequired鐨勫拰)
+ const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
+ return sum + Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
+ }, 0);
+ sumAssignedQuantity.value = totalAssignedQuantity;
+ console.log(totalAssignedQuantity);
+ // 璁剧疆琛ㄥ崟鏁版嵁
+ mergeForm.productName = productName;
+ mergeForm.model = firstRow.model || "";
+ mergeForm.totalAssignedQuantity = totalAssignedQuantity;
+ mergeForm.planCompleteTime = firstRow.requiredDate || "";
+ mergeForm.productId = firstRow.productId || "";
+ mergeForm.ids = selectedRows.value.map(row => row.id);
+
+ // 鎵撳紑寮圭獥
+ isShowNewModal.value = true;
+ };
+ const showDetail = row => {
+ router.push({
+ path: "/salesManagement/salesLedger",
+ query: {
+ salesContractNo: row.salesContractNo,
+ },
+ });
+ };
+
+ // 澶勭悊鍚堝苟涓嬪彂鎻愪氦
+ const handleMergeSubmit = () => {
+ if (mergeForm.totalAssignedQuantity === 0) {
+ ElMessage.warning("璇疯緭鍏ョ敓浜ф暟閲�");
+ return;
+ }
+ console.log(sumAssignedQuantity.value, "sumAssignedQuantity");
+
+ // 楠岃瘉totalAssignedQuantity涓嶈兘澶т簬鎬绘柟鏁�
+ if (mergeForm.totalAssignedQuantity > sumAssignedQuantity.value) {
+ ElMessage.error("鐢熶骇鏁伴噺涓嶈兘澶т簬褰撳墠璁$畻鐨勬�诲��");
+ return;
+ }
+
+ console.log(mergeForm, "mergeForm");
+ const payload = {
+ ...mergeForm,
+ };
+ productionPlanCombine(payload)
+ .then(res => {
+ if (res.code === 200) {
+ ElMessage.success("涓嬪彂鎴愬姛");
+ getList();
+ isShowNewModal.value = false;
+ // 鍙互閫夋嫨鍒锋柊鍒楄〃鎴栧叾浠栨搷浣�
+ getList();
+ } else {
+ ElMessage.error(res.message || "涓嬪彂澶辫触");
+ }
+ })
+ .catch(err => {
+ console.error("鍚堝苟涓嬪彂寮傚父锛�", err);
+ ElMessage.error("绯荤粺寮傚父锛屽悎骞朵笅鍙戝け璐�");
+ });
+ // 鍙互閫夋嫨鍒锋柊鍒楄〃鎴栧叾浠栨搷浣�
+ };
+
+ // 瀵煎叆
+ const handleImport = () => {
+ importDialogVisible.value = true;
+ };
+
+ // 瀵煎嚭
+ const handleExport = () => {
+ const fileName = `鐢熶骇璁″垝.xlsx`;
+ exportProductionPlan()
+ .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 handleImportSuccess = response => {
+ if (response.code === 200) {
+ ElMessage.success("瀵煎叆鎴愬姛");
+ importDialogVisible.value = false;
+ getList();
+ } else {
+ ElMessage.error(response.msg || "瀵煎叆澶辫触");
+ }
+ };
+
+ // 瀵煎叆澶辫触
+ const handleImportError = error => {
+ ElMessage.error("瀵煎叆澶辫触锛岃妫�鏌ユ枃浠舵牸寮忔槸鍚︽纭�");
+ };
+
+ // 纭瀵煎叆
+ const handleImportConfirm = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.submit();
+ }
+ };
+
+ // 涓嬭浇妯℃澘
+ const handleDownloadTemplate = () => {
+ proxy.download(
+ "/productionPlan/downloadTemplate",
+ {},
+ "鐢熶骇璁″垝瀵煎叆妯℃澘.xlsx"
+ );
+ };
+
+ // 鍏抽棴瀵煎叆寮圭獥
+ const handleImportClose = () => {
+ importDialogVisible.value = false;
+ };
+
+ // 鏂板
+ const handleAdd = () => {
+ operationType.value = "add";
+ Object.assign(form, {
+ id: undefined,
+ mpsNo: "",
+ productName: "",
+ productId: undefined,
+ productModelId: undefined,
+ model: "",
+ qtyRequired: 0,
+ unit: "鏂�",
+ requiredDate: "",
+ promisedDeliveryDate: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+ fetchProductOptions();
+ };
+
+ // 缂栬緫
+ const handleEdit = row => {
+ operationType.value = "edit";
+ Object.assign(form, {
+ id: row.id,
+ mpsNo: row.mpsNo || "",
+ productName: row.productName || "",
+ productId: row.productId || undefined,
+ productModelId: row.productModelId || undefined,
+ model: row.model || "",
+ qtyRequired: row.qtyRequired || 0,
+ unit: row.unit || "鏂�",
+ requiredDate: row.requiredDate || "",
+ promisedDeliveryDate: row.promisedDeliveryDate || "",
+ remark: row.remark || "",
+ });
+ dialogVisible.value = true;
+ fetchProductOptions();
+ fetchSpecificationOptions(row.productId);
+ };
+
+ // 鍒犻櫎
+ const handleDelete = row => {
+ proxy.$modal
+ .confirm("纭鍒犻櫎璇ョ敓浜ц鍒掞紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ productionPlanDelete([row.id])
+ .then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ });
+ })
+ .catch(() => {});
+ };
+
+ // 鎻愪氦琛ㄥ崟
+ const handleSubmit = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ if (form.qtyRequired === 0) {
+ proxy.$modal.msgError("鏁伴噺涓嶈兘涓�0");
+ return;
+ }
+ const payload = { ...form };
+ if (operationType.value === "add") {
+ payload.id = null;
+ productionPlanAdd(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess("鏂板鎴愬姛");
+ dialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("鏂板澶辫触");
+ });
+ }
+ if (operationType.value === "edit") {
+ productionPlanUpdate(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess("淇敼鎴愬姛");
+ dialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("淇敼澶辫触");
+ });
+ }
+ }
+ });
+ };
+
+ onMounted(() => {
+ getList();
+ });
+</script>
+
+<style scoped lang="scss">
+ .app-container {
+ padding: 24px;
+ background-color: #f0f2f5;
+ min-height: calc(100vh - 48px);
+ }
+
+ .search_form {
+ // margin-bottom: 24px;
+ padding: 20px;
+ background-color: #ffffff;
+ border-radius: 6px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
+ }
+ }
+
+ .search-header {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ // margin-bottom: 5px;
+ // padding-bottom: 5px;
+ position: relative;
+ bottom: 35px;
+ // border-bottom: 1px solid #ebeef5;
+ }
+
+ .search-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+
+ .search-header .el-button {
+ color: #606266;
+ transition: all 0.3s ease;
+ }
+
+ .search-header .el-button:hover {
+ color: #409eff;
+ }
+
+ .search-header .el-icon {
+ margin-right: 4px;
+ }
+
+ .table_list {
+ // margin-bottom: 24px;
+ background-color: #ffffff;
+ border-radius: 6px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+ overflow: hidden;
+ height: calc(100vh - 250px);
+ margin-top: 0px !important;
+ }
+
+ :deep(.el-table) {
+ border: none;
+ border-radius: 6px;
+ overflow: hidden;
+ box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1);
+
+ .el-table__header-wrapper {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
+ th {
+ background: transparent;
+ font-weight: 600;
+ color: #ffffff;
+ border-bottom: none;
+ padding: 16px 0;
+ letter-spacing: 0.5px;
+ }
+ }
+
+ .el-table__body-wrapper {
+ tr {
+ transition: all 0.3s ease;
+
+ &:hover {
+ background: linear-gradient(
+ 90deg,
+ rgba(102, 126, 234, 0.05) 0%,
+ rgba(118, 75, 162, 0.05) 100%
+ );
+ transform: scale(1.002);
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
+ }
+
+ td {
+ border-bottom: 1px solid #f0f0f0;
+ padding: 14px 0;
+ color: #303133;
+ }
+ }
+
+ tr.current-row {
+ background: linear-gradient(
+ 90deg,
+ rgba(102, 126, 234, 0.08) 0%,
+ rgba(118, 75, 162, 0.08) 100%
+ );
+ }
+
+ // 鏁板�煎瓧娈垫牱寮�
+ .volume-cell,
+ .dimension-cell {
+ font-weight: 600;
+ color: #409eff;
+ font-family: "Courier New", monospace;
+ text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
+ }
+
+ // 瑙勬牸瀛楁鏍峰紡
+ .spec-cell {
+ color: #67c23a;
+ font-weight: 500;
+
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+
+ // 缂栫爜瀛楁鏍峰紡
+ .code-cell {
+ color: #e6a23c;
+ font-family: "Courier New", monospace;
+ font-weight: 500;
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+
+ // 鏃ユ湡瀛楁鏍峰紡
+ .date-cell {
+ color: #909399;
+ font-style: italic;
+ }
+
+ // 鐘舵�佹爣绛炬牱寮�
+ .status-tag {
+ &.pending {
+ background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
+ color: #d63031;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(253, 203, 110, 0.3);
+ }
+
+ &.processing {
+ background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
+ color: #ffffff;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(9, 132, 227, 0.3);
+ }
+
+ &.completed {
+ background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
+ color: #ffffff;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(0, 184, 148, 0.3);
+ }
+ }
+ }
+
+ .el-table__empty-block {
+ padding: 60px 0;
+ background-color: #fafafa;
+ }
+ }
+
+ // 鎿嶄綔鎸夐挳鏍峰紡
+ :deep(.el-table .cell .el-button--text) {
+ padding: 6px 10px;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+ font-weight: 500;
+
+ &:hover {
+ background-color: rgba(64, 158, 255, 0.1);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
+ }
+
+ &:nth-of-type(1) {
+ color: #409eff;
+ background: linear-gradient(
+ 135deg,
+ rgba(64, 158, 255, 0.1) 0%,
+ rgba(64, 158, 255, 0.05) 100%
+ );
+ }
+
+ &:nth-of-type(2) {
+ color: #67c23a;
+ background: linear-gradient(
+ 135deg,
+ rgba(103, 194, 58, 0.1) 0%,
+ rgba(103, 194, 58, 0.05) 100%
+ );
+ }
+ }
+
+ // 淇℃伅灞曠ず鏍峰紡
+ .info-display {
+ border-radius: 6px;
+ color: #303133;
+ font-size: 14px;
+ min-height: 32px;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ }
+
+ .pagination-container {
+ display: flex;
+ justify-content: flex-end;
+ padding: 16px 20px;
+ background-color: #ffffff;
+ border-top: 1px solid #ebeef5;
+ border-radius: 0 0 12px 12px;
+ }
+
+ :deep(.el-button) {
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-1px);
+ }
+ }
+
+ :deep(.el-dialog) {
+ border-radius: 6px;
+ overflow: hidden;
+
+ .el-dialog__header {
+ background-color: #fafafa;
+ border-bottom: 1px solid #ebeef5;
+ padding: 20px 24px;
+
+ .el-dialog__title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+ }
+
+ .el-dialog__body {
+ padding: 24px;
+ }
+
+ .el-dialog__footer {
+ padding: 16px 24px;
+ border-top: 1px solid #ebeef5;
+ background-color: #fafafa;
+ }
+ }
+
+ :deep(.el-form) {
+ .el-form-item {
+ margin-bottom: 20px;
+
+ .el-form-item__label {
+ font-weight: 500;
+ color: #303133;
+ }
+
+ .el-input,
+ .el-select,
+ .el-date-picker,
+ .el-input-number {
+ width: 100%;
+
+ // .el-input__inner {
+ // border-radius: 6px;
+ // border: 1px solid #dcdfe6;
+ // transition: all 0.3s ease;
+
+ // &:focus {
+ // border-color: #409eff;
+ // box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+ // }
+ // }
+ }
+ }
+ }
+
+ :deep(.el-tag) {
+ border-radius: 4px;
+ padding: 2px 8px;
+ font-size: 12px;
+ }
+
+ @media (max-width: 768px) {
+ .app-container {
+ padding: 16px;
+ }
+
+ .search_form {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+
+ .el-form {
+ width: 100%;
+
+ .el-form-item {
+ width: 100%;
+ }
+ }
+
+ > div {
+ width: 100%;
+ display: flex;
+ gap: 12px;
+
+ .el-button {
+ flex: 1;
+ }
+ }
+ }
+
+ :deep(.el-table) {
+ th,
+ td {
+ padding: 10px 0;
+ font-size: 12px;
+ }
+ }
+
+ :deep(.el-dialog) {
+ width: 90% !important;
+ margin: 20px auto !important;
+ }
+ }
+ .consumption-value {
+ font-weight: bold;
+ color: #409eff;
+ }
+
+ .consumption-unit {
+ font-size: 12px;
+ color: #909399;
+ margin-left: 4px;
+ }
+ // .search_form {
+ // :deep(.el-form-item) {
+ // margin-bottom: 0px !important;
+ // }
+ // }
+ :deep(.el-table .el-table__body-wrapper tr td) {
+ background-color: #fff;
+ }
+</style>
diff --git a/src/views/projectManagement/Management/components/formDia.vue b/src/views/projectManagement/Management/components/formDia.vue
index f29512b..c2ee9c2 100644
--- a/src/views/projectManagement/Management/components/formDia.vue
+++ b/src/views/projectManagement/Management/components/formDia.vue
@@ -571,9 +571,12 @@
<el-col :span="12">
<el-form-item label="绋庣巼(%)锛�" prop="taxRate">
<el-select v-model="productForm.taxRate" placeholder="璇烽�夋嫨" clearable @change="calculateFromTaxRate">
- <el-option label="1" value="1" />
- <el-option label="6" value="6" />
- <el-option label="13" value="13" />
+ <el-option
+ v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
</el-select>
</el-form-item>
</el-col>
@@ -650,7 +653,7 @@
const emit = defineEmits(['completed'])
const { proxy } = getCurrentInstance()
-const { bill_status, project_management, plan_status } = proxy.useDict('bill_status', 'project_management', 'plan_status')
+const { bill_status, project_management, plan_status, tax_rate } = proxy.useDict('bill_status', 'project_management', 'plan_status', 'tax_rate')
const dialogVisible = ref(false)
const operationType = ref('add')
diff --git a/src/views/projectManagement/Management/index.vue b/src/views/projectManagement/Management/index.vue
index 7547209..0438aef 100644
--- a/src/views/projectManagement/Management/index.vue
+++ b/src/views/projectManagement/Management/index.vue
@@ -37,7 +37,7 @@
</el-dropdown> -->
<el-button :loading="submitLoading" @click="handleSubmit">鎻愪氦</el-button>
<el-button :loading="auditLoading" @click="handleAudit">瀹℃牳</el-button>
- <el-button :loading="reverseAuditLoading" @click="handleReverseAudit">鍙嶅鏍�</el-button>
+ <!-- <el-button :loading="reverseAuditLoading" @click="handleReverseAudit">鍙嶅鏍�</el-button> -->
<el-button :loading="deleteLoading" @click="handleDelete">鍒犻櫎</el-button>
</div>
@@ -384,7 +384,7 @@
actualStartTime: payload.actualStartTime || null,
actualEndTime: payload.actualEndTime || null,
progress: Number(payload.totalProgress ?? payload.completionProgress ?? 0) || 0,
- attachmentIds: Array.isArray(payload.attachmentIds) ? payload.attachmentIds : []
+ storageBlobDTOs: Array.isArray(payload.storageBlobDTOs) ? payload.storageBlobDTOs : []
}
const res = await saveStage(req)
if (res?.code === 200) {
@@ -422,9 +422,7 @@
border-radius: 4px;
}
.table-actions {
- margin-bottom: 15px;
- display: flex;
- align-items: center;
- gap: 10px;
+ text-align: right;
+ margin-bottom: 10px;
}
</style>
diff --git a/src/views/projectManagement/Management/projectDetail.vue b/src/views/projectManagement/Management/projectDetail.vue
index c54a389..b526fc6 100644
--- a/src/views/projectManagement/Management/projectDetail.vue
+++ b/src/views/projectManagement/Management/projectDetail.vue
@@ -319,7 +319,7 @@
actualStartTime: payload.actualStartTime || null,
actualEndTime: payload.actualEndTime || null,
progress: Number(payload.totalProgress ?? payload.completionProgress ?? 0) || 0,
- attachmentIds: Array.isArray(payload.attachmentIds) ? payload.attachmentIds : []
+ storageBlobDTOs: Array.isArray(payload.storageBlobDTOs) ? payload.storageBlobDTOs : []
}
const res = await saveStage(req)
if (res?.code === 200) {
diff --git a/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue b/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
index 2888516..bc1e196 100644
--- a/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
+++ b/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
@@ -29,24 +29,15 @@
style="width: 220px"
/>
</div>
+ </div>
+ <div class="base-info-row">
<div class="info-item">
<span class="item-label">闄勪欢</span>
- <el-upload
- v-if="isEdit"
- :action="uploadUrl"
- :headers="uploadHeaders"
- :on-success="handleUploadSuccess"
- :on-remove="handleRemove"
- v-model:file-list="uploadFileList"
- :limit="3"
- name="files"
- multiple
- >
- <el-button type="primary">涓婁紶闄勪欢</el-button>
- </el-upload>
+ <FileUpload v-if="isEdit" v-model:file-list="uploadFileList" />
<span v-else class="text-gray-400 text-sm">璇峰厛淇濆瓨鍚庡啀涓婁紶闄勪欢</span>
</div>
</div>
+
<!-- 姝ラ閰嶇疆琛ㄦ牸 -->
<p class="top-tip">璇锋寜鐓ч『搴忛厤缃」鐩樁娈碉紝鎷栨嫿<b>姝ラ</b>鎺掑簭鍗冲彲</p>
@@ -148,8 +139,8 @@
<template #footer>
<div class="dialog-footer">
- <el-button @click="visible = false">鍙栨秷</el-button>
<el-button type="primary" @click="submitForm">鎻愪氦</el-button>
+ <el-button @click="visible = false">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
@@ -162,6 +153,7 @@
import { ElMessage, ElMessageBox } from 'element-plus';
import { getToken } from '@/utils/auth';
import Sortable from 'sortablejs';
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
const props = defineProps({
modelValue: Boolean,
@@ -175,16 +167,12 @@
const formRef = ref(null);
const userOptions = ref([]);
const isEdit = ref(false);
-const uploadHeaders = { Authorization: "Bearer " + getToken() };
-// 涓婁紶鍦板潃
-const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/upload";
let sortable = null;
const form = ref({
id: undefined,
name: '',
description: '',
- attachmentIds: [],
savePlanNodeList: []
});
const uploadFileList = ref([]);
@@ -204,11 +192,9 @@
id: props.data.id,
name: props.data.name,
description: props.data.description,
- attachmentIds: Array.isArray(props.data.attachmentIds)
- ? props.data.attachmentIds
- : (props.data.attachmentList || []).map(f => f.id).filter(Boolean),
savePlanNodeList: []
};
+ uploadFileList.value = props.data.storageBlobDTOs || [];
// 鍥炴樉姝ラ鑺傜偣
if (props.data.planNodeList && props.data.planNodeList.length > 0) {
@@ -279,7 +265,6 @@
id: undefined,
name: '',
description: '',
- attachmentIds: [],
savePlanNodeList: [createDefaultNode()]
};
uploadFileList.value = [];
@@ -306,29 +291,6 @@
if (user) {
row.leaderName = user.nickName;
}
-}
-
-/** 澶勭悊鏂囦欢涓婁紶鎴愬姛 */
-function handleUploadSuccess(response, file, fileList) {
- if (response.code === 200) {
- const newFile = response.data;
- const list = Array.isArray(newFile) ? newFile : [newFile];
- list.forEach(element => {
- const id = element?.id;
- if (id && !form.value.attachmentIds.includes(id)) {
- form.value.attachmentIds.push(id);
- }
- });
- } else {
- ElMessage.error(response.msg || '涓婁紶澶辫触');
- }
-}
-
-/** 澶勭悊鏂囦欢绉婚櫎 */
-function handleRemove(file) {
- const removedId = file?.id || file?.response?.data?.id;
- if (!removedId) return;
- form.value.attachmentIds = form.value.attachmentIds.filter(id => id !== removedId);
}
/** 娣诲姞姝ラ */
@@ -374,6 +336,7 @@
form.value.savePlanNodeList.forEach((node, index) => {
node.sort = index;
});
+ form.value.storageBlobDTOs = uploadFileList.value;
emit('submit', form.value);
}
} catch (error) {
@@ -457,10 +420,7 @@
}
.dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 15px;
- padding-top: 10px;
+ text-align: center;
}
.top-tip {
diff --git a/src/views/projectManagement/projectType/index.vue b/src/views/projectManagement/projectType/index.vue
index c2ea441..16a17e0 100644
--- a/src/views/projectManagement/projectType/index.vue
+++ b/src/views/projectManagement/projectType/index.vue
@@ -266,8 +266,6 @@
<style scoped lang="scss">
.app-container {
- background-color: #f5f7fa;
- height: calc(100vh - 84px);
padding: 20px;
display: flex;
flex-direction: column;
diff --git a/src/views/projectManagement/roles/index.vue b/src/views/projectManagement/roles/index.vue
index b3abe10..1e161f8 100644
--- a/src/views/projectManagement/roles/index.vue
+++ b/src/views/projectManagement/roles/index.vue
@@ -60,45 +60,46 @@
</el-row>
<!-- 琛ㄦ牸鏁版嵁 -->
- <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55" align="center" />
- <el-table-column label="瑙掕壊缂栧彿" prop="no" />
- <el-table-column label="瑙掕壊鍚嶇О" prop="name" :show-overflow-tooltip="true" />
- <el-table-column label="鐘舵��" align="center" width="100">
- <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">
- <template #default="scope">
- <span>{{ parseTime(scope.row.createTime) }}</span>
- </template>
- </el-table-column>
- <el-table-column fixed="right" label="鎿嶄綔" align="center" width="120">
- <template #default="scope">
- <el-tooltip content="淇敼" placement="top" v-if="scope.row.roleId !== 1">
- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
- </el-tooltip>
- <el-tooltip content="鍒犻櫎" placement="top" v-if="scope.row.roleId !== 1">
- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
- </el-tooltip>
- </template>
- </el-table-column>
- </el-table>
-
- <pagination
- v-show="total > 0"
- :total="total"
- v-model:page="queryParams.current"
- v-model:limit="queryParams.size"
- @pagination="getList"
- />
-
+ <div class="table_list">
+ <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="瑙掕壊缂栧彿" prop="no" />
+ <el-table-column label="瑙掕壊鍚嶇О" prop="name" :show-overflow-tooltip="true" />
+ <el-table-column label="鐘舵��" align="center" width="100">
+ <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">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.createTime) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔" align="center" width="120">
+ <template #default="scope">
+ <el-tooltip content="淇敼" placement="top" v-if="scope.row.roleId !== 1">
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
+ </el-tooltip>
+ <el-tooltip content="鍒犻櫎" placement="top" v-if="scope.row.roleId !== 1">
+ <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
+ </el-tooltip>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ v-model:page="queryParams.current"
+ v-model:limit="queryParams.size"
+ @pagination="getList"
+ />
+ </div>
<!-- 娣诲姞鎴栦慨鏀硅鑹查厤缃璇濇 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="roleRef" :model="form" :rules="rules" label-width="100px">
diff --git a/src/views/qualityManagement/finalInspection/index.vue b/src/views/qualityManagement/finalInspection/index.vue
index db44222..a2d1acc 100644
--- a/src/views/qualityManagement/finalInspection/index.vue
+++ b/src/views/qualityManagement/finalInspection/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">浜у搧鍚嶇О锛�</span>
<el-input
@@ -11,11 +11,12 @@
clearable
:prefix-icon="Search"
/>
- <span style="margin-left: 10px" class="search_title">妫�娴嬫棩鏈燂細</span>
- <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ <span style="margin-left: 10px" class="search_title">妫�娴嬫棩鏈燂細</span>
+ <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange"/>
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
- >鎼滅储</el-button
+ >鎼滅储
+ </el-button
>
</div>
<div>
@@ -40,37 +41,37 @@
<InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia>
<FormDia ref="formDia" @close="handleQuery"></FormDia>
<files-dia ref="filesDia" @close="handleQuery"></files-dia>
- <el-dialog v-model="dialogFormVisible" title="缂栬緫妫�楠屽憳" width="30%"
- @close="closeDia">
- <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
- <el-form-item label="妫�楠屽憳锛�" prop="checkName">
- <el-select v-model="form.checkName" placeholder="璇烽�夋嫨" clearable>
- <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
- :value="item.nickName"/>
- </el-select>
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
+ <el-dialog v-model="dialogFormVisible" title="缂栬緫妫�楠屽憳" width="30%"
+ @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-form-item label="妫�楠屽憳锛�" prop="checkName">
+ <el-select v-model="form.checkName" placeholder="璇烽�夋嫨" clearable>
+ <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
+ :value="item.nickName"/>
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
-import { Search } from "@element-plus/icons-vue";
+import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import InspectionFormDia from "@/views/qualityManagement/finalInspection/components/inspectionFormDia.vue";
import FormDia from "@/views/qualityManagement/finalInspection/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {
- downloadQualityInspect,
- qualityInspectDel,
- qualityInspectListPage, qualityInspectUpdate,
- submitQualityInspect
+ downloadQualityInspect,
+ qualityInspectDel,
+ qualityInspectListPage, qualityInspectUpdate,
+ submitQualityInspect
} from "@/api/qualityManagement/rawMaterialInspection.js";
import FilesDia from "@/views/qualityManagement/finalInspection/components/filesDia.vue";
import dayjs from "dayjs";
@@ -84,15 +85,20 @@
entryDateStart: undefined,
entryDateEnd: undefined,
},
- rules: {
- checkName: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
- },
+ rules: {
+ checkName: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
+ },
});
-const { searchForm } = toRefs(data);
+const {searchForm} = toRefs(data);
const tableColumn = ref([
{
label: "妫�娴嬫棩鏈�",
prop: "checkTime",
+ width: 120
+ },
+ {
+ label: "閿�鍞崟鍙�",
+ prop: "salesContractNo",
width: 120
},
{
@@ -140,17 +146,17 @@
}
},
},
- {
- label: "鎻愪氦鐘舵��",
- prop: "inspectState",
- formatData: (params) => {
- if (params) {
- return "宸叉彁浜�";
- } else {
- return "鏈彁浜�";
- }
- },
- },
+ {
+ label: "鎻愪氦鐘舵��",
+ prop: "inspectState",
+ formatData: (params) => {
+ if (params) {
+ return "宸叉彁浜�";
+ } else {
+ return "鏈彁浜�";
+ }
+ },
+ },
{
dataType: "action",
label: "鎿嶄綔",
@@ -164,15 +170,15 @@
clickFun: (row) => {
openForm("edit", row);
},
- disabled: (row) => {
- // 宸叉彁浜ゅ垯绂佺敤
- if (row.inspectState == 1) return true;
- // 濡傛灉妫�楠屽憳鏈夊�硷紝鍙湁褰撳墠鐧诲綍鐢ㄦ埛鑳界紪杈�
- if (row.checkName) {
- return row.checkName !== userStore.nickName;
- }
- return false;
- }
+ disabled: (row) => {
+ // 宸叉彁浜ゅ垯绂佺敤
+ if (row.inspectState == 1) return true;
+ // 濡傛灉妫�楠屽憳鏈夊�硷紝鍙湁褰撳墠鐧诲綍鐢ㄦ埛鑳界紪杈�
+ if (row.checkName) {
+ return row.checkName !== userStore.nickName;
+ }
+ return false;
+ }
},
{
name: "闄勪欢",
@@ -181,43 +187,43 @@
openFilesFormDia(row);
},
},
- {
- name: "鎻愪氦",
- type: "text",
- clickFun: (row) => {
- submit(row.id);
- },
- disabled: (row) => {
- // 宸叉彁浜ゅ垯绂佺敤
- if (row.inspectState == 1) return true;
- // 濡傛灉妫�楠屽憳鏈夊�硷紝鍙湁褰撳墠鐧诲綍鐢ㄦ埛鑳芥彁浜�
- if (row.checkName) {
- return row.checkName !== userStore.nickName;
- }
- return false;
- }
- },
- {
- name: "鍒嗛厤妫�楠屽憳",
- type: "text",
- clickFun: (row) => {
- if (!row.checkName) {
- open(row)
- } else {
- proxy.$modal.msgError("妫�楠屽憳宸插瓨鍦�");
- }
- },
- disabled: (row) => {
- return row.inspectState == 1 || row.checkName;
- }
- },
- {
- name: "涓嬭浇",
- type: "text",
- clickFun: (row) => {
- downLoadFile(row);
- },
- },
+ {
+ name: "鎻愪氦",
+ type: "text",
+ clickFun: (row) => {
+ submit(row.id);
+ },
+ disabled: (row) => {
+ // 宸叉彁浜ゅ垯绂佺敤
+ if (row.inspectState == 1) return true;
+ // 濡傛灉妫�楠屽憳鏈夊�硷紝鍙湁褰撳墠鐧诲綍鐢ㄦ埛鑳芥彁浜�
+ if (row.checkName) {
+ return row.checkName !== userStore.nickName;
+ }
+ return false;
+ }
+ },
+ {
+ name: "鍒嗛厤妫�楠屽憳",
+ type: "text",
+ clickFun: (row) => {
+ if (!row.checkName) {
+ open(row)
+ } else {
+ proxy.$modal.msgError("妫�楠屽憳宸插瓨鍦�");
+ }
+ },
+ disabled: (row) => {
+ return row.inspectState == 1 || row.checkName;
+ }
+ },
+ {
+ name: "涓嬭浇",
+ type: "text",
+ clickFun: (row) => {
+ downLoadFile(row);
+ },
+ },
],
},
]);
@@ -233,11 +239,11 @@
const formDia = ref()
const filesDia = ref()
const inspectionFormDia = ref()
-const { proxy } = getCurrentInstance()
+const {proxy} = getCurrentInstance()
const userStore = useUserStore()
const userList = ref([]);
const form = ref({
- checkName: ""
+ checkName: ""
});
const dialogFormVisible = ref(false);
@@ -263,7 +269,7 @@
};
const getList = () => {
tableLoading.value = true;
- const params = { ...searchForm.value, ...page };
+ const params = {...searchForm.value, ...page};
params.entryDate = undefined
qualityInspectListPage({...params, inspectType: 2}).then(res => {
tableLoading.value = false;
@@ -338,56 +344,56 @@
// 鎻愪环
const submit = async (id) => {
- const res = await submitQualityInspect({id: id})
- if (res.code === 200) {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- getList();
- }
+ const res = await submitQualityInspect({id: id})
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ getList();
+ }
}
// 鍏抽棴寮规
const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
};
const submitForm = () => {
- if (currentRow.value) {
- const data = {
- ...form.value,
- id: currentRow.value.id
- }
- qualityInspectUpdate(data).then(res => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- getList();
- })
- }
+ if (currentRow.value) {
+ const data = {
+ ...form.value,
+ id: currentRow.value.id
+ }
+ qualityInspectUpdate(data).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ })
+ }
};
const open = async (row) => {
- let userLists = await userListNoPage();
- userList.value = userLists.data;
- currentRow.value = row
- dialogFormVisible.value = true
+ let userLists = await userListNoPage();
+ userList.value = userLists.data;
+ currentRow.value = row
+ dialogFormVisible.value = true
}
const downLoadFile = (row) => {
- downloadQualityInspect({ id: row.id }).then((blobData) => {
- const blob = new Blob([blobData], {
- type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- })
- const downloadUrl = window.URL.createObjectURL(blob)
-
- const link = document.createElement('a')
- link.href = downloadUrl
- link.download = '鍘熸潗鏂欐楠屾姤鍛�.docx'
- document.body.appendChild(link)
- link.click()
-
- document.body.removeChild(link)
- window.URL.revokeObjectURL(downloadUrl)
- })
+ downloadQualityInspect({id: row.id}).then((blobData) => {
+ const blob = new Blob([blobData], {
+ type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ })
+ const downloadUrl = window.URL.createObjectURL(blob)
+
+ const link = document.createElement('a')
+ link.href = downloadUrl
+ link.download = '鍘熸潗鏂欐楠屾姤鍛�.docx'
+ document.body.appendChild(link)
+ link.click()
+
+ document.body.removeChild(link)
+ window.URL.revokeObjectURL(downloadUrl)
+ })
};
onMounted(() => {
getList();
diff --git a/src/views/qualityManagement/metricBinding/index.vue b/src/views/qualityManagement/metricBinding/index.vue
index 2a78e1d..1ac268a 100644
--- a/src/views/qualityManagement/metricBinding/index.vue
+++ b/src/views/qualityManagement/metricBinding/index.vue
@@ -432,10 +432,6 @@
</script>
<style scoped>
-.metric-binding {
- padding: 0;
-}
-
.metric-binding-row {
width: 100%;
}
diff --git a/src/views/qualityManagement/metricMaintenance/index.vue b/src/views/qualityManagement/metricMaintenance/index.vue
index 5b2c6bd..deb3e5f 100644
--- a/src/views/qualityManagement/metricMaintenance/index.vue
+++ b/src/views/qualityManagement/metricMaintenance/index.vue
@@ -127,10 +127,10 @@
<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 link type="primary" :disabled="isStandardReadonly" @click="openParamDialog('edit', row)">
缂栬緫
</el-button>
- <el-button link type="danger" size="small" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
+ <el-button link type="danger" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
鍒犻櫎
</el-button>
</template>
@@ -432,6 +432,18 @@
const handleSelectionChange = (selection) => {
selectedRows.value = selection
+
+ if (!selection.length) {
+ currentStandard.value = null
+ detailTableData.value = []
+ return
+ }
+
+ const nextStandard = selection[selection.length - 1]
+ if (currentStandard.value?.id === nextStandard.id) return
+
+ currentStandard.value = nextStandard
+ loadDetail(nextStandard.id)
}
// 鎵归噺瀹℃牳锛氱姸鎬� 1=鎵瑰噯锛�2=鎾ら攢
@@ -698,11 +710,6 @@
</script>
<style scoped>
-.metric-maintenance {
- padding: 0;
- min-width: 0;
-}
-
.metric-maintenance-row {
width: 100%;
}
@@ -829,4 +836,4 @@
width: 100%;
margin-top: 4px;
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/qualityManagement/nonconformingManagement/index.vue b/src/views/qualityManagement/nonconformingManagement/index.vue
index 55d2472..6306397 100644
--- a/src/views/qualityManagement/nonconformingManagement/index.vue
+++ b/src/views/qualityManagement/nonconformingManagement/index.vue
@@ -1,24 +1,21 @@
<template>
<div class="app-container">
<div class="search_form">
- <div style="display: flex;flex-direction: row;align-items: center;">
- <div>
- <span class="search_title">绫诲瀷锛�</span>
+ <el-form :model="searchForm" inline style="margin-bottom: 0;">
+ <el-form-item label="绫诲瀷锛�">
<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" />
</el-select>
- </div>
- <div style="margin-left: 10px">
- <span class="search_title">鐘舵�侊細</span>
+ </el-form-item>
+ <el-form-item label="鐘舵�侊細">
<el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery">
<el-option label="寰呭鐞�" :value="0" />
<el-option label="宸插鐞�" :value="1" />
</el-select>
- </div>
- <div style="margin-left: 10px">
- <span class="search_title">浜у搧鍚嶇О锛�</span>
+ </el-form-item>
+ <el-form-item label="浜у搧鍚嶇О锛�">
<el-input
v-model="searchForm.productName"
style="width: 200px"
@@ -27,19 +24,22 @@
clearable
:prefix-icon="Search"
/>
- </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>
- <div>
+ </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"
+ style="width: 300px"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="mb20" style="text-align: right;">
<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"
diff --git a/src/views/qualityManagement/processInspection/index.vue b/src/views/qualityManagement/processInspection/index.vue
index cbeab71..e5504b6 100644
--- a/src/views/qualityManagement/processInspection/index.vue
+++ b/src/views/qualityManagement/processInspection/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">宸ュ簭锛�</span>
<el-input
diff --git a/src/views/qualityManagement/rawMaterialInspection/index.vue b/src/views/qualityManagement/rawMaterialInspection/index.vue
index 26504b0..cc2c151 100644
--- a/src/views/qualityManagement/rawMaterialInspection/index.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">渚涘簲鍟嗭細</span>
<el-input
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue b/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
index da736e1..58c83d8 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
@@ -45,7 +45,7 @@
import PanelHeader from '../PanelHeader.vue'
import DateTypeSwitch from '../DateTypeSwitch.vue'
import { customerRevenueAnalysis } from '@/api/viewIndex.js'
-import { listCustomer } from '@/api/basicData/customerFile.js'
+import { listCustomer } from '@/api/basicData/customer.js'
const dateType = ref(1) // 1=鍛� 2=鏈� 3=瀛e害
const customerValue = ref(null)
diff --git a/src/views/reportAnalysis/dataDashboard/index0.vue b/src/views/reportAnalysis/dataDashboard/index0.vue
index 5c318c8..a89a174 100644
--- a/src/views/reportAnalysis/dataDashboard/index0.vue
+++ b/src/views/reportAnalysis/dataDashboard/index0.vue
@@ -316,7 +316,7 @@
getWorkInProcessTurnover
} from "@/api/viewIndex.js";
import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js";
-import {listCustomer} from "@/api/basicData/customerFile.js";
+import { listCustomer } from '@/api/basicData/customer.js'
import {listSupplier} from "@/api/basicData/supplierManageFile.js";
import {getLedgerPage} from "@/api/equipmentManagement/ledger.js";
import {getRepairPage} from "@/api/equipmentManagement/repair.js";
diff --git a/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue b/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
index 33f431d..9f6a8c1 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
@@ -157,9 +157,11 @@
.panel-item-customers {
border: 1px solid #1a58b0;
+ border-radius: 16px;
padding: 18px;
width: 100%;
height: 449px;
+ overflow: hidden;
}
.chart-wrapper {
diff --git a/src/views/reportAnalysis/productionAnalysis/components/left-top.vue b/src/views/reportAnalysis/productionAnalysis/components/left-top.vue
index 932def8..fd52b1b 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/left-top.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/left-top.vue
@@ -189,9 +189,11 @@
.panel-item-customers {
border: 1px solid #1a58b0;
+ border-radius: 16px;
padding: 18px;
width: 100%;
height: 449px;
+ overflow: hidden;
}
.filters-row {
diff --git a/src/views/reportAnalysis/reportManagement/index.vue b/src/views/reportAnalysis/reportManagement/index.vue
index dc9d486..cd8ddd5 100644
--- a/src/views/reportAnalysis/reportManagement/index.vue
+++ b/src/views/reportAnalysis/reportManagement/index.vue
@@ -912,7 +912,6 @@
<style scoped>
.report-management {
padding: 20px;
- background-color: #f5f5f5;
min-height: 100vh;
/* height: 87vh;
overflow: hidden; */
diff --git a/src/views/safeProduction/accidentReportingRecord/index.vue b/src/views/safeProduction/accidentReportingRecord/index.vue
index ff5b301..5a6c345 100644
--- a/src/views/safeProduction/accidentReportingRecord/index.vue
+++ b/src/views/safeProduction/accidentReportingRecord/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">浜嬫晠鍚嶇О锛�</span>
<el-input v-model="searchForm.accidentName"
diff --git a/src/views/safeProduction/dangerInvestigation/index.vue b/src/views/safeProduction/dangerInvestigation/index.vue
index 49965e9..cb09e52 100644
--- a/src/views/safeProduction/dangerInvestigation/index.vue
+++ b/src/views/safeProduction/dangerInvestigation/index.vue
@@ -415,17 +415,12 @@
</el-row>
</el-form>
</FormDialog>
- <!-- 闄勪欢鍒楄〃寮圭獥 -->
- <FileListDialog ref="fileListRef"
- v-model="fileListDialogVisible"
- :show-upload-button="true"
- :show-delete-button="true"
- :is-show-pagination="true"
- :page="filePagination"
- :upload-method="handleUpload"
- :delete-method="handleFileDelete"
- @pagination="paginationSearch"
- title="闄勪欢鍒楄〃" />
+ <FileListDialog
+ v-if="fileListDialogVisible"
+ :record-id="currentRecordId"
+ record-type="safe_hidden"
+ v-model:visible="fileListDialogVisible"/>
+
</div>
</template>
@@ -436,7 +431,6 @@
import { ElMessageBox, ElMessage } from "element-plus";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
- import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import {
@@ -445,13 +439,9 @@
safeHiddenUpdate,
safeHiddenDel,
fileListPage,
- safeHiddenFileAdd,
- safeHiddenFileDel,
} from "@/api/safeProduction/dangerInvestigation.js";
import useFormData from "@/hooks/useFormData.js";
- import request from "@/utils/request";
import dayjs from "dayjs";
- import { get } from "@vueuse/core";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
@@ -459,6 +449,7 @@
const selectedRows = ref([]);
const userList = ref([]);
const tableLoading = ref(false);
+ const currentRecordId = ref(0);
const page = reactive({
current: 1,
size: 100,
@@ -943,18 +934,8 @@
const fileListDialogVisible = ref(false);
const currentFileRow = ref(null);
const downLoadFile = row => {
- currentFileRow.value = row;
- fileListPage({
- safeHiddenId: row.id,
- current: filePagination.value.current,
- size: filePagination.value.size,
- }).then(res => {
- if (fileListRef.value) {
- fileListRef.value.open(res.data.records || []);
- console.log("res.data", res.data);
- filePagination.value.total = res.data.total || 0;
- }
- });
+ currentRecordId.value = row.id;
+ fileListDialogVisible.value = true;
};
const currentUserId = ref("");
const currentUserName = ref("");
@@ -990,147 +971,6 @@
userList.value = res.data;
});
});
- // 涓婁紶闄勪欢
- 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 = {
- safeHiddenId: currentFileRow.value.id,
- name: uploadRes.data.originalName || file.name,
- url: uploadRes.data.tempPath || uploadRes.data.url,
- };
-
- const saveRes = await safeHiddenFileAdd(fileData);
- if (saveRes.code === 200) {
- proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
- // 閲嶆柊鍔犺浇鏂囦欢鍒楄〃
- const listRes = await fileListPage({
- safeHiddenId: currentFileRow.value.id,
- current: filePagination.value.current,
- size: filePagination.value.size,
- });
- 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);
- filePagination.value.total = listRes.data?.total || 0;
- }
- // 杩斿洖鏂版枃浠朵俊鎭�
- 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 paginationSearch = async (page, size) => {
- filePagination.value.current = page;
- filePagination.value.size = size;
- const listRes = await fileListPage({
- safeHiddenId: currentFileRow.value.id,
- current: filePagination.value.current,
- size: filePagination.value.size,
- });
- 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);
- filePagination.value.total = listRes.data?.total || 0;
- }
- };
- // 鍒犻櫎闄勪欢
- const handleFileDelete = async row => {
- try {
- const res = await safeHiddenFileDel([row.id]);
- if (res.code === 200) {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- // 閲嶆柊鍔犺浇鏂囦欢鍒楄〃
- if (currentFileRow.value && fileListRef.value) {
- const listRes = await fileListPage({
- safeHiddenId: currentFileRow.value.id,
- current: filePagination.value.current,
- size: filePagination.value.size,
- });
- 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);
- filePagination.value.total = listRes.data?.total || 0;
- }
- }
- return true; // 杩斿洖 true 琛ㄧず鍒犻櫎鎴愬姛锛岀粍浠朵細鏇存柊鍒楄〃
- } else {
- proxy.$modal.msgError(res.msg || "鍒犻櫎澶辫触");
- return false;
- }
- } catch (error) {
- proxy.$modal.msgError("鍒犻櫎澶辫触");
- return false;
- }
- };
</script>
<style scoped lang="scss">
diff --git a/src/views/safeProduction/emergencyPlanReview/index.vue b/src/views/safeProduction/emergencyPlanReview/index.vue
index eb68508..1850c18 100644
--- a/src/views/safeProduction/emergencyPlanReview/index.vue
+++ b/src/views/safeProduction/emergencyPlanReview/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">搴旀�ラ妗堝悕绉帮細</span>
<el-input v-model="searchForm.planName"
diff --git a/src/views/safeProduction/hazardSourceLedger/index.vue b/src/views/safeProduction/hazardSourceLedger/index.vue
index 9aa131a..416202e 100644
--- a/src/views/safeProduction/hazardSourceLedger/index.vue
+++ b/src/views/safeProduction/hazardSourceLedger/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">鍗遍櫓婧愬悕绉帮細</span>
<el-input v-model="searchForm.name"
diff --git a/src/views/safeProduction/hazardousMaterialsControl/index.vue b/src/views/safeProduction/hazardousMaterialsControl/index.vue
index a53490c..e43b4f6 100644
--- a/src/views/safeProduction/hazardousMaterialsControl/index.vue
+++ b/src/views/safeProduction/hazardousMaterialsControl/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">鍗遍櫓婧愬悕绉帮細</span>
<el-input v-model="searchForm.name"
diff --git a/src/views/safeProduction/safeQualifications/index.vue b/src/views/safeProduction/safeQualifications/index.vue
index e61a39c..39111be 100644
--- a/src/views/safeProduction/safeQualifications/index.vue
+++ b/src/views/safeProduction/safeQualifications/index.vue
@@ -110,7 +110,7 @@
<el-button link
type="primary"
size="small"
- @click="downLoadFile(scope.row)">闄勪欢</el-button>
+ @click="openFileDialog(scope.row)">闄勪欢</el-button>
</template>
</el-table-column>
</el-table>
@@ -202,17 +202,8 @@
</el-row>
</el-form>
</FormDialog>
- <!-- 闄勪欢鍒楄〃寮圭獥 -->
- <FileListDialog ref="fileListRef"
- v-model="fileListDialogVisible"
- :show-upload-button="true"
- :show-delete-button="true"
- :is-show-pagination="true"
- :page="filePagination"
- :upload-method="handleUpload"
- :delete-method="handleFileDelete"
- @pagination="paginationSearch"
- title="闄勪欢鍒楄〃" />
+<!-- todo 闄勪欢棰勮鐩稿叧 -->
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="safe_certification" :record-id="recordId" />
</div>
</template>
@@ -223,7 +214,6 @@
import { ElMessageBox, ElMessage } from "element-plus";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
- import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import {
@@ -238,7 +228,7 @@
import useFormData from "@/hooks/useFormData.js";
import request from "@/utils/request";
import dayjs from "dayjs";
-
+ const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
const tableData = ref([]);
@@ -408,7 +398,9 @@
executionDate: "",
};
} else {
- form.value = row;
+ // 鍏抽敭锛氱紪杈戞椂涓嶈鐩存帴寮曠敤琛ㄦ牸琛屽璞★紝閬垮厤鍙栨秷/閲嶇疆鏃舵妸鍒楄〃鏁版嵁涓�璧锋竻绌�
+ // 浣跨敤娣辨嫹璐濇柇寮�寮曠敤鍏崇郴
+ form.value = JSON.parse(JSON.stringify(row || {}));
}
dialogFormVisible.value = true;
};
@@ -522,19 +514,17 @@
size: 10,
total: 0,
});
- const downLoadFile = row => {
- currentFileRow.value = row;
- fileListPage({
- safeCertificationId: row.id,
- current: filePagination.value.current,
- size: filePagination.value.size,
- }).then(res => {
- if (fileListRef.value) {
- fileListRef.value.open(res.data.records);
- }
- filePagination.value.total = res.data.total || 0;
- });
- };
+
+ // 鎵撳紑闄勪欢寮圭獥
+ const recordId =ref(0)
+ const fileDialogVisible = ref(false)
+
+ // 鎵撳紑闄勪欢寮规
+ const openFileDialog = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
+ }
+
const currentFactoryName = ref("");
const getCurrentFactoryName = async () => {
let res = await userStore.getInfo();
diff --git a/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue b/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue
index 02b728e..a0146ba 100644
--- a/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue
+++ b/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue
@@ -171,26 +171,7 @@
<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>
+ <FileUpload v-model:file-list="fileList" />
</el-form-item>
</el-col>
</el-row>
@@ -222,17 +203,12 @@
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import log from "@/views/monitor/job/log.vue";
+ import FileUpload from "@/components/AttachmentUpload/file/index.vue";
const userStore = useUserStore();
const dialogFormVisible = ref(false);
const operationType = ref("");
const fileList = ref([]);
- const upload = reactive({
- // 涓婁紶鐨勫湴鍧�
- url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
- // 璁剧疆涓婁紶鐨勮姹傚ご閮�
- headers: { Authorization: "Bearer " + getToken() },
- });
const data = reactive({
form: {
approveTime: "",
@@ -242,7 +218,7 @@
approveDeptName: "",
approveReason: "",
checkResult: "",
- tempFileIds: [],
+ storageBlobDTOs: [],
approverList: [], // 鏂板瀛楁锛屽瓨鍌ㄦ墍鏈夎妭鐐圭殑瀹℃壒浜篿d
startDate: "", // 璇峰亣寮�濮嬫椂闂�
endDate: "", // 璇峰亣缁撴潫鏃堕棿
@@ -317,12 +293,12 @@
// 鍔犺浇閮ㄩ棬閫夐」锛屽苟鍦ㄥ姞杞藉畬鎴愬悗璁剧疆閮ㄩ棬鍚嶇О
getProductOptions();
if (operationType.value === "edit") {
- fileList.value = row.commonFileList;
- form.value.tempFileIds = fileList.value.map(file => file.id);
+ fileList.value = row.storageBlobVOs;
currentApproveStatus.value = row.approveStatus;
approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then(
res => {
form.value = { ...res.data };
+ form.value.storageBlobDTOs = res.data.storageBlobVOS;
// 鍙嶆樉瀹℃壒浜�
if (res.data && res.data.approveUserIds) {
const userIds = res.data.approveUserIds.split(",");
@@ -412,6 +388,7 @@
return;
}
}
+ form.value.storageBlobDTOs = fileList.value;
proxy.$refs.formRef.validate(valid => {
if (valid) {
if (operationType.value === "add" || currentApproveStatus.value == 3) {
@@ -435,47 +412,6 @@
dialogFormVisible.value = false;
emit("close");
};
-
- // 涓婁紶鍓嶆牎妫�
- 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) {
- // 纭繚 tempFileIds 瀛樺湪涓斾负鏁扮粍
- if (!form.value.tempFileIds) {
- form.value.tempFileIds = [];
- }
- form.value.tempFileIds.push(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("鍒犻櫎鎴愬姛");
- });
- }
- }
defineExpose({
openDialog,
diff --git a/src/views/safeProduction/safeWorkApproval/index.vue b/src/views/safeProduction/safeWorkApproval/index.vue
index 2d8362e..bfd1d90 100644
--- a/src/views/safeProduction/safeWorkApproval/index.vue
+++ b/src/views/safeProduction/safeWorkApproval/index.vue
@@ -1,7 +1,7 @@
<template>
<div class="app-container">
<!-- 鏍囩椤靛垏鎹笉鍚岀殑瀹℃壒绫诲瀷 -->
- <div class="search_form">
+ <div class="search_form mb20">
<div>
<span class="search_title">娴佺▼缂栧彿锛�</span>
<el-input v-model="searchForm.approveId"
diff --git a/src/views/safeProduction/safetyTrainingAssessment/index.vue b/src/views/safeProduction/safetyTrainingAssessment/index.vue
index cbc35b8..2c98308 100644
--- a/src/views/safeProduction/safetyTrainingAssessment/index.vue
+++ b/src/views/safeProduction/safetyTrainingAssessment/index.vue
@@ -239,7 +239,7 @@
<el-descriptions-item label="闄勪欢鍒楄〃:">
<el-button type="primary"
size="small"
- @click="downLoadFile(endform)">闄勪欢鍒楄〃</el-button>
+ @click="openFileDialog(endform)">闄勪欢鍒楄〃</el-button>
</el-descriptions-item>
</el-descriptions>
<!-- <el-divider style="margin: 20px 0;" /> -->
@@ -358,23 +358,13 @@
</span>
</template>
</el-dialog>
- <!-- 闄勪欢鍒楄〃寮圭獥 -->
- <FileListDialog ref="fileListRef"
- v-model="fileListDialogVisible"
- :show-upload-button="true"
- :show-delete-button="true"
- :is-show-pagination="true"
- :page="filePagination"
- :upload-method="handleUpload"
- :delete-method="handleFileDelete"
- @pagination="paginationSearch"
- title="闄勪欢鍒楄〃" />
+ <!-- todo 闄勪欢棰勮鐩稿叧 -->
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="safe_training" :record-id="recordId" />
</div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
- import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import {
onMounted,
ref,
@@ -403,6 +393,7 @@
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
const userStore = useUserStore();
+ const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
// 琛ㄥ崟楠岃瘉瑙勫垯
const rules = {
@@ -621,7 +612,7 @@
name: "闄勪欢",
type: "text",
clickFun: row => {
- downLoadFile(row);
+ openFileDialog(row);
},
color: "#007AFF",
},
@@ -783,27 +774,17 @@
form.value.principalMobile = selectedUser.phonenumber;
}
};
- /**
- * 涓嬭浇鏂囦欢
- *
- * @param row 涓嬭浇鏂囦欢鐨勭浉鍏充俊鎭璞�
- */
- const fileListRef = ref(null);
- const fileListDialogVisible = ref(false);
- const currentFileRow = ref(null);
- const downLoadFile = row => {
- currentFileRow.value = row;
- safeTrainingFileListPage({
- safeTrainingId: row.id,
- current: filePagination.value.current,
- size: filePagination.value.size,
- }).then(res => {
- if (fileListRef.value) {
- fileListRef.value.open(res.data.records);
- filePagination.value.total = res.data?.total || 0;
- }
- });
- };
+
+ // 鎵撳紑闄勪欢寮圭獥
+ const recordId =ref(0)
+ const fileDialogVisible = ref(false)
+
+ // 鎵撳紑闄勪欢寮规
+ const openFileDialog = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
+ }
+
// 涓婁紶闄勪欢
const handleUpload = async () => {
if (!currentFileRow.value) {
diff --git a/src/views/salesManagement/deliveryLedger/index.vue b/src/views/salesManagement/deliveryLedger/index.vue
index 2b1b0b0..d7f47dd 100644
--- a/src/views/salesManagement/deliveryLedger/index.vue
+++ b/src/views/salesManagement/deliveryLedger/index.vue
@@ -3,19 +3,22 @@
<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-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-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-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-button type="primary" @click="handleQuery"> 鎼滅储</el-button>
</el-form-item>
</el-form>
</div>
@@ -28,18 +31,18 @@
</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="productName" show-overflow-tooltip />
- <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" 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 />
+ :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="productName" show-overflow-tooltip/>
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" 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)">
@@ -47,46 +50,48 @@
</el-tag>
</template>
</el-table-column>
- <el-table-column fixed="right" label="鎿嶄綔" width="200" align="center">
+ <el-table-column fixed="right" label="鎿嶄綔" width="220" 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="primary"
- size="small"
- @click="openDetail(scope.row)"
- >璇︽儏</el-button>
- <el-button
- link
- type="danger"
- size="small"
- :disabled="isApproving(scope.row.status)"
- @click="handleDeleteSingle(scope.row)">鍒犻櫎</el-button>
+ link
+ type="primary"
+ :disabled="!isApproved(scope.row.status)"
+ @click="openForm('edit', scope.row)">琛ュ厖鍙戣揣淇℃伅
+ </el-button>
+ <el-button
+ link
+ type="primary"
+ style="color: #67C23A"
+ @click="openDetail(scope.row)"
+ >璇︽儏
+ </el-button>
+ <el-button
+ link
+ type="danger"
+ :disabled="isApproving(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" />
+ :page="page.current" :limit="page.size" @pagination="paginationChange"/>
</div>
- <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板鍙戣揣鍙拌处' : '缂栬緫鍙戣揣鍙拌处'" width="40%"
- @close="closeDia">
+ <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"
+ v-model="form.type"
+ placeholder="璇烽�夋嫨鍙戣揣绫诲瀷"
+ style="width: 100%"
+ @change="handleShippingTypeChange"
>
- <el-option label="璐ц溅" value="璐ц溅" />
- <el-option label="蹇��" value="蹇��" />
+ <el-option label="璐ц溅" value="璐ц溅"/>
+ <el-option label="蹇��" value="蹇��"/>
</el-select>
</el-form-item>
</el-col>
@@ -95,13 +100,13 @@
<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
+ 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>
@@ -110,18 +115,18 @@
<el-col :span="24" v-if="form.type === '璐ц溅'">
<el-form-item label="鍙戣揣杞︾墝鍙凤細" prop="shippingCarNumber">
<el-input
- v-model="form.shippingCarNumber"
- placeholder="璇疯緭鍏ュ彂璐ц溅鐗屽彿"
- clearable
+ 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
+ v-model="form.expressCompany"
+ placeholder="璇疯緭鍏ュ揩閫掑叕鍙�"
+ clearable
/>
</el-form-item>
</el-col>
@@ -130,9 +135,9 @@
<el-col :span="24">
<el-form-item label="蹇�掑崟鍙凤細" prop="expressNumber">
<el-input
- v-model="form.expressNumber"
- placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
- clearable
+ v-model="form.expressNumber"
+ placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
+ clearable
/>
</el-form-item>
</el-col>
@@ -140,29 +145,7 @@
<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>
+ <ImageUpload v-model:file-list="deliveryFileList" :limit="9"/>
</el-form-item>
</el-col>
</el-row>
@@ -191,19 +174,29 @@
<el-descriptions-item label="蹇�掑叕鍙�">{{ detailRow.expressCompany || '--' }}</el-descriptions-item>
<el-descriptions-item label="蹇�掑崟鍙�" :span="2">{{ detailRow.expressNumber || '--' }}</el-descriptions-item>
</el-descriptions>
-
- <div class="detail-images" v-if="detailImages.length">
- <div class="detail-images-title">鍙戣揣鍥剧墖</div>
- <el-image
- v-for="img in detailImages"
- :key="img.url"
- :src="img.url"
- :preview-src-list="detailImages.map(i => i.url)"
- fit="cover"
- class="detail-image"
- />
- </div>
- <div v-else class="detail-images-empty">鏆傛棤鍙戣揣鍥剧墖</div>
+ <el-table :data="getDeliveryProductInfoList()"
+ border
+ size="small"
+ class="delivery-product-table"
+ style="width: 100%; margin-top: 16px;">
+ <el-table-column label="鎵瑰彿"
+ prop="batchNo"
+ min-width="160"
+ show-overflow-tooltip/>
+ <el-table-column label="浜у搧鍚嶇О"
+ prop="productName"
+ min-width="160"
+ show-overflow-tooltip/>
+ <el-table-column label="瑙勬牸鍨嬪彿"
+ prop="specificationModel"
+ min-width="160"
+ show-overflow-tooltip/>
+ <el-table-column label="鍙戣揣鏁伴噺"
+ prop="deliveryQuantity"
+ min-width="120"
+ align="center"/>
+ </el-table>
+ <ImagePreview :file-list="detailRow.storageBlobVOs || []" />
</div>
<template #footer>
<div class="dialog-footer">
@@ -216,20 +209,21 @@
<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 {onMounted, ref, reactive, toRefs, getCurrentInstance} from "vue";
+import {ElMessageBox} from "element-plus";
+import {getCurrentDate} from "@/utils/index.js";
import {
- deliveryLedgerListPage,
- addOrUpdateDeliveryLedger,
- delDeliveryLedger, deductStock,
+ deliveryLedgerListPage,
+ delDeliveryLedger,
+ deductStock,
+ getDeliveryDetail,
} from "@/api/salesManagement/deliveryLedger.js";
-import { delLedgerFile } from "@/api/salesManagement/salesLedger.js";
-
+import {delLedgerFile} from "@/api/salesManagement/salesLedger.js";
+import ImageUpload from "@/components/AttachmentUpload/image/index.vue";
+import ImagePreview from "@/components/AttachmentPreview/image/index.vue";
-const { proxy } = getCurrentInstance();
+
+const {proxy} = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
@@ -240,40 +234,10 @@
});
const total = ref(0);
const deliveryFileList = ref([]);
-const javaApi = proxy.javaApi;
// 璇︽儏寮规
const detailDialogVisible = ref(false);
const detailRow = ref(null);
-const detailImages = ref([]);
-
-const normalizeFileUrl = (rawUrl = '') => {
- let fileUrl = rawUrl || '';
- // Windows 璺緞杞� URL
- if (fileUrl && fileUrl.indexOf('\\') > -1) {
- const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
- if (uploadsIndex > -1) {
- const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
- fileUrl = '/' + relativePath;
- } else {
- const parts = fileUrl.split('\\');
- const fileName = parts[parts.length - 1];
- fileUrl = '/uploads/' + fileName;
- }
- }
- if (fileUrl && !fileUrl.startsWith('http')) {
- if (!fileUrl.startsWith('/')) fileUrl = '/' + fileUrl;
- fileUrl = javaApi + fileUrl;
- }
- return fileUrl;
-};
-
-// 涓婁紶閰嶇疆
-const upload = reactive({
- // 涓婁紶鐨勫湴鍧�
- url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
- // 璁剧疆涓婁紶鐨勮姹傚ご閮�
- headers: { Authorization: "Bearer " + getToken() },
-});
+const detailProductList = ref([]);
// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
const operationType = ref("");
@@ -297,24 +261,23 @@
expressNumber: "", // 蹇�掑崟鍙�
},
rules: {
- salesContractNo: [{ required: true, message: "璇烽�夋嫨閿�鍞鍗�", trigger: "change" }],
- customerName: [{ required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur" }],
+ salesContractNo: [{required: true, message: "璇烽�夋嫨閿�鍞鍗�", trigger: "change"}],
+ customerName: [{required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur"}],
type: [
- { required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change" }
+ {required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change"}
],
- shippingDate: [{ required: true, message: "璇烽�夋嫨鍙戣揣鏃堕棿", trigger: "change" }],
+ shippingDate: [{required: true, message: "璇烽�夋嫨鍙戣揣鏃堕棿", trigger: "change"}],
shippingCarNumber: [
- { validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur" }
+ {validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur"}
],
expressCompany: [
- { validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur" }
+ {validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur"}
],
},
});
-const { form, rules } = toRefs(data);
-const { searchForm } = toRefs(data);
+const {form, rules} = toRefs(data);
+const {searchForm} = toRefs(data);
-
// 鏌ヨ鍒楄〃
const handleQuery = () => {
@@ -330,15 +293,15 @@
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;
- });
+ deliveryLedgerListPage({...searchForm.value, ...page})
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records || [];
+ total.value = res.data.total || 0;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
};
// 閿�鍞鍗曞彉鍖栨椂鑷姩濉厖瀹㈡埛鍚嶇О
@@ -361,10 +324,9 @@
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,
@@ -376,69 +338,99 @@
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) => {
- const fileUrl = normalizeFileUrl(file.url || '');
-
- 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 = [];
+ deliveryFileList.value = row.storageBlobVOs || [];
}
-
+
dialogFormVisible.value = true;
};
// 鎵撳紑璇︽儏寮规
-const openDetail = (row) => {
+const openDetail = async (row) => {
detailRow.value = row || null;
- const list = Array.isArray(row?.commonFileList) ? row.commonFileList : [];
- detailImages.value = list
- .map((f) => ({ url: normalizeFileUrl(f?.url || '') }))
- .filter((i) => !!i.url);
+ detailProductList.value = [];
detailDialogVisible.value = true;
+ if (!row?.id) return;
+ try {
+ const res = await getDeliveryDetail(row.id);
+ const detailData = res?.data;
+ detailRow.value = {
+ ...row,
+ ...(Array.isArray(detailData) ? {} : detailData || {}),
+ };
+ detailProductList.value = resolveDeliveryDetailList(detailData);
+ } catch (error) {
+ proxy.$modal.msgError("鍔犺浇鍙戣揣鍙拌处璇︽儏澶辫触");
+ }
+};
+const resolveDeliveryDetailList = data => {
+ if (Array.isArray(data)) return data;
+ if (!data || typeof data !== "object") return [];
+ return [
+ data.batchNoDetailList,
+ data.batchNoList,
+ data.shippingBatchList,
+ data.shippingInfoDetailList,
+ data.detailList,
+ data.batchDetailList,
+ data.rows,
+ data.records,
+ data.list,
+ data.data,
+ ].find(value => Array.isArray(value) && value.length) || [];
+};
+const getDeliveryProductInfoList = () => {
+ const row = detailRow.value;
+ if (!row) return [];
+ const normalizeBatchNoList = value => {
+ if (Array.isArray(value)) return value;
+ if (typeof value === "string" && value.includes(",")) {
+ return value.split(",").map(item => item.trim()).filter(Boolean);
+ }
+ return value ? [value] : [];
+ };
+ const detailList = detailProductList.value.length ? detailProductList.value : [
+ row.batchNoDetailList,
+ row.batchNoList,
+ row.shippingBatchList,
+ row.shippingInfoDetailList,
+ row.detailList,
+ row.batchDetailList,
+ ].find(value => Array.isArray(value) && value.length);
+ const batchNoList = normalizeBatchNoList(row.batchNo);
+ const toTableRow = (item = {}) => ({
+ batchNo:
+ typeof item === "string" || typeof item === "number"
+ ? item
+ : item.batchNo ?? item.batchNumber ?? row.batchNo ?? "--",
+ productName: item.productName ?? row.productName ?? "--",
+ specificationModel:
+ item.specificationModel ?? item.model ?? row.specificationModel ?? "--",
+ deliveryQuantity:
+ item.deliveryQuantity ??
+ item.quantity ??
+ item.shippingQuantity ??
+ row.deliveryQuantity ??
+ row.quantity ??
+ "--",
+ });
+ if (detailList?.length) {
+ return detailList.map(toTableRow);
+ }
+ if (batchNoList.length) {
+ return batchNoList.map(batchNo => toTableRow({batchNo}));
+ }
+ return [toTableRow()];
};
const closeDetail = () => {
detailDialogVisible.value = false;
detailRow.value = null;
- detailImages.value = [];
+ detailProductList.value = [];
};
// 鎻愪氦琛ㄥ崟
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,
@@ -446,9 +438,9 @@
shippingCarNumber: form.value.type === "璐ц溅" ? form.value.shippingCarNumber : "",
expressCompany: form.value.type === "蹇��" ? form.value.expressCompany : "",
expressNumber: form.value.type === "蹇��" ? form.value.expressNumber : "",
- tempFileIds: tempFileIds,
+ storageBlobDTOs: deliveryFileList.value || [],
};
- deductStock(payload).then((res) => {
+ deductStock(payload).then((res) => {
proxy.$modal.msgSuccess("鎿嶄綔鎴愬姛");
closeDia();
getList();
@@ -471,12 +463,12 @@
cancelButtonText: "鍙栨秷",
type: "warning",
})
- .then(() => {
- proxy.download("/shippingInfo/export", {}, "鍙戣揣鍙拌处.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ .then(() => {
+ proxy.download("/shippingInfo/export", {}, "鍙戣揣鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鎵归噺鍒犻櫎
@@ -485,29 +477,29 @@
proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
return;
}
-
+
// 妫�鏌ラ�変腑鐨勮鏄惁鏈�"瀹℃牳涓�"鐘舵��
const approvingRows = selectedRows.value.filter(row => isApproving(row.status));
if (approvingRows.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();
+ .then(() => {
+ delDeliveryLedger(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
});
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
};
// 鍗曚釜鍒犻櫎
@@ -517,21 +509,21 @@
proxy.$modal.msgWarning("瀹℃牳涓殑鏁版嵁涓嶈兘鍒犻櫎");
return;
}
-
+
ElMessageBox.confirm("姝ゆ搷浣滃皢鍒犻櫎璇ヨ褰曪紝鏄惁纭锛�", "鍒犻櫎", {
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "warning",
})
- .then(() => {
- delDeliveryLedger([row.id]).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
+ .then(() => {
+ delDeliveryLedger([row.id]).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
});
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
};
// 鍙戣揣绫诲瀷鏍¢獙锛氳揣杞︽椂瑕佹眰杞︾墝锛屽揩閫掓椂瑕佹眰蹇�掑叕鍙�
@@ -565,11 +557,13 @@
proxy.$modal.loading("姝e湪涓婁紶鍥剧墖锛岃绋嶅��...");
return true;
}
+
// 鍙戣揣鍥剧墖涓婁紶澶辫触
function handleDeliveryUploadError(err) {
proxy.$modal.msgError("涓婁紶鍥剧墖澶辫触");
proxy.$modal.closeLoading();
}
+
// 鍙戣揣鍥剧墖涓婁紶鎴愬姛鍥炶皟
function handleDeliveryUploadSuccess(res, file, uploadFiles) {
proxy.$modal.closeLoading();
@@ -581,6 +575,7 @@
proxy.$refs.deliveryFileUpload.handleRemove(file);
}
}
+
// 绉婚櫎鍙戣揣鍥剧墖
function handleDeliveryRemove(file) {
console.log('file--', file)
@@ -729,17 +724,21 @@
display: none;
}
}
+
.detail-wrapper {
padding: 8px 0;
}
+
.detail-images {
margin-top: 16px;
}
+
.detail-images-title {
font-weight: 600;
margin-bottom: 10px;
color: #303133;
}
+
.detail-image {
width: 120px;
height: 120px;
@@ -747,6 +746,7 @@
margin-bottom: 10px;
border-radius: 6px;
}
+
.detail-images-empty {
margin-top: 16px;
color: #909399;
diff --git a/src/views/salesManagement/indicatorStats/index.vue b/src/views/salesManagement/indicatorStats/index.vue
index 6101920..8ae15ed 100644
--- a/src/views/salesManagement/indicatorStats/index.vue
+++ b/src/views/salesManagement/indicatorStats/index.vue
@@ -406,7 +406,6 @@
<style scoped lang="scss">
.indicator-stats {
padding: 20px;
- background: #f5f7fa;
min-height: calc(100vh - 84px);
}
diff --git a/src/views/salesManagement/invoiceLedger/index.vue b/src/views/salesManagement/invoiceLedger/index.vue
index b35023f..444560d 100644
--- a/src/views/salesManagement/invoiceLedger/index.vue
+++ b/src/views/salesManagement/invoiceLedger/index.vue
@@ -43,9 +43,9 @@
<el-table-column label="寮�绁ㄦ棩鏈�" prop="invoiceDate" show-overflow-tooltip width="120" />
<el-table-column fixed="right" label="鎿嶄綔" width="150" 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="downLoadFile(scope.row)">闄勪欢</el-button>
- <el-button link type="primary" size="small" @click="delInvoiceLedger(scope.row)">鍒犻櫎</el-button>
+ <el-button link type="primary" @click="openForm(scope.row)">缂栬緫</el-button>
+ <el-button link type="primary" @click="openFileDialog(scope.row)">闄勪欢</el-button>
+ <el-button link type="primary" @click="delInvoiceLedger(scope.row)">鍒犻櫎</el-button>
</template>
</el-table-column>
</el-table>
@@ -134,7 +134,7 @@
</div>
</template>
</el-dialog>
- <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" />
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="invoice_registration_product" :record-id="recordId" />
</div>
</template>
@@ -155,8 +155,8 @@
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 FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
const { proxy } = getCurrentInstance();
const tableData = ref([]);
@@ -422,17 +422,14 @@
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
- }
- });
+// 鎵撳紑闄勪欢寮圭獥
+const recordId =ref(0)
+const fileDialogVisible = ref(false)
+
+// 鎵撳紑闄勪欢寮规
+const openFileDialog = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
}
onMounted(() => {
diff --git a/src/views/salesManagement/invoiceRegistration/index.vue b/src/views/salesManagement/invoiceRegistration/index.vue
index 2f6e60c..44e1c4e 100644
--- a/src/views/salesManagement/invoiceRegistration/index.vue
+++ b/src/views/salesManagement/invoiceRegistration/index.vue
@@ -803,7 +803,7 @@
.justify-between {
justify-content: space-between;
}
-::v-deep(.el-checkbox__label) {
+:deep(.el-checkbox__label) {
font-weight: bold;
}
</style>
diff --git a/src/views/salesManagement/orderManagement/index.vue b/src/views/salesManagement/orderManagement/index.vue
index 6107966..f44c53f 100644
--- a/src/views/salesManagement/orderManagement/index.vue
+++ b/src/views/salesManagement/orderManagement/index.vue
@@ -69,7 +69,7 @@
</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="handleView(scope.row)" style="color: #67C23A">鏌ョ湅</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>
diff --git a/src/views/salesManagement/receiptPayment/index.vue b/src/views/salesManagement/receiptPayment/index.vue
index 83cf942..25bd280 100644
--- a/src/views/salesManagement/receiptPayment/index.vue
+++ b/src/views/salesManagement/receiptPayment/index.vue
@@ -102,7 +102,6 @@
<el-button
link
type="primary"
- size="small"
@click="changeEditType(scope.row)"
v-if="!scope.row.editType"
>缂栬緫</el-button
@@ -110,7 +109,6 @@
<el-button
link
type="primary"
- size="small"
@click="saveReceiptPayment(scope.row)"
v-if="scope.row.editType"
>淇濆瓨</el-button
@@ -118,7 +116,6 @@
<el-button
link
type="primary"
- size="small"
@click="delReceiptRecord(scope.row)"
>鍒犻櫎</el-button
>
@@ -592,7 +589,7 @@
.table_list {
margin-top: unset;
}
-::v-deep(.el-checkbox__label) {
+:deep(.el-checkbox__label) {
font-weight: bold;
}
.actions {
diff --git a/src/views/salesManagement/receiptPaymentHistory/index.vue b/src/views/salesManagement/receiptPaymentHistory/index.vue
index f66bed7..11a59a6 100644
--- a/src/views/salesManagement/receiptPaymentHistory/index.vue
+++ b/src/views/salesManagement/receiptPaymentHistory/index.vue
@@ -54,7 +54,6 @@
<el-button
type="primary"
link
- size="small"
@click="handleDelete(row)"
>
鍒犻櫎
diff --git a/src/views/salesManagement/receiptPaymentLedger/index.vue b/src/views/salesManagement/receiptPaymentLedger/index.vue
index 7029cfc..e41e24f 100644
--- a/src/views/salesManagement/receiptPaymentLedger/index.vue
+++ b/src/views/salesManagement/receiptPaymentLedger/index.vue
@@ -1,6 +1,6 @@
<template>
<div class="app-container">
- <div class="search_form">
+ <div class="search_form" style="margin-bottom: 20px;">
<div>
<span class="search_title">瀹㈡埛鍚嶇О锛�</span>
<el-input
diff --git a/src/views/salesManagement/returnOrder/components/formDia.vue b/src/views/salesManagement/returnOrder/components/formDia.vue
index 9f1bb9a..7a35ea9 100644
--- a/src/views/salesManagement/returnOrder/components/formDia.vue
+++ b/src/views/salesManagement/returnOrder/components/formDia.vue
@@ -168,10 +168,10 @@
<script setup>
import { reactive, ref, toRefs, getCurrentInstance } from "vue";
import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById } from "@/api/salesManagement/returnOrder.js";
-import { getAllCustomerList } from "@/api/customerService/index.js";
import useUserStore from "@/store/modules/user.js";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { listProject } from "@/api/oaSystem/projectManagement.js";
+import {listCustomer} from "@/api/basicData/customer.js";
const { proxy } = getCurrentInstance();
const emit = defineEmits(['close'])
@@ -354,15 +354,14 @@
};
const initCustomers = async () => {
- const res = await getAllCustomerList({});
- if (res?.records) {
- customerNameOptions.value = res.records.map(item => ({
- label: item.customerName,
- value: item.customerName, // Keep value as name if needed for other logic, but request says customerId
- id: item.id,
- code: item.customerCode
- }));
- }
+ listCustomer({current: -1,size:-1, type: 0}).then((res) => {
+ customerNameOptions.value = res.data.records.map(item => ({
+ label: item.customerName,
+ value: item.customerName, // Keep value as name if needed for other logic, but request says customerId
+ id: item.id,
+ code: item.customerCode
+ }));
+ });
};
const initUsers = async () => {
@@ -406,7 +405,7 @@
}).then(res => {
if(res.code === 200){
outboundOptions.value = res.data.map(item => ({
- label: item.salesContractNo, // Or whatever the outbound number field is
+ label: item.shippingNo, // Or whatever the outbound number field is
value: item.id,
}))
}
diff --git a/src/views/salesManagement/returnOrder/index.vue b/src/views/salesManagement/returnOrder/index.vue
index 5a5aa05..d41d4bf 100644
--- a/src/views/salesManagement/returnOrder/index.vue
+++ b/src/views/salesManagement/returnOrder/index.vue
@@ -1,41 +1,29 @@
<template>
<div class="app-container">
- <div class="search-wrapper">
- <el-form :model="searchForm" class="demo-form-inline">
- <el-row :gutter="20">
- <el-col :span="4">
- <el-form-item label="閫�璐у崟鍙�">
- <el-input v-model="searchForm.returnNo" placeholder="璇疯緭鍏ラ��璐у崟鍙�" clearable />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="瀹㈡埛鍚嶇О">
- <el-input v-model="searchForm.customerName" placeholder="瀹㈡埛鍚嶇О" clearable />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="閿�鍞崟鍙�">
- <el-input v-model="searchForm.salesContractNo" placeholder="閿�鍞崟鍙�" clearable />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="鍏宠仈鍑哄簱鍗曞彿">
- <el-input v-model="searchForm.shippingNo" placeholder="鍏宠仈鍑哄簱鍗曞彿" clearable />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item>
- <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
- <el-button @click="handleReset">閲嶇疆</el-button>
- </el-form-item>
- </el-col>
- </el-row>
+ <div class="search_form">
+ <el-form :model="searchForm" class="demo-form-inline" :inline="true">
+ <el-form-item label="閫�璐у崟鍙�">
+ <el-input v-model="searchForm.returnNo" placeholder="璇疯緭鍏ラ��璐у崟鍙�" clearable />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <el-input v-model="searchForm.customerName" placeholder="瀹㈡埛鍚嶇О" clearable />
+ </el-form-item>
+ <el-form-item label="閿�鍞崟鍙�">
+ <el-input v-model="searchForm.salesContractNo" placeholder="閿�鍞崟鍙�" clearable />
+ </el-form-item>
+ <el-form-item label="鍏宠仈鍑哄簱鍗曞彿">
+ <el-input v-model="searchForm.shippingNo" placeholder="鍏宠仈鍑哄簱鍗曞彿" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ </el-form-item>
</el-form>
</div>
<div class="table_list">
<div class="table_header" style="display: flex;justify-content: flex-end;margin-bottom: 10px;">
<el-button type="primary" @click="openForm('add')">鏂板缓閿�鍞��璐�</el-button>
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ <el-button type="danger" :disabled="selectedRows.length === 0 || selectedRows.some(row => row.status !== 0)" @click="handleDelete">鍒犻櫎</el-button>
</div>
<PIMTable
rowKey="id"
@@ -225,10 +213,7 @@
</script>
<style scoped lang="scss">
-.search-wrapper {
- background: white;
- padding: 1rem 1rem 0 1rem;
- border: 8px;
- border-radius: 16px;
+.table_list {
+ margin-top: unset;
}
</style>
diff --git a/src/views/salesManagement/salesLedger/fileList.vue b/src/views/salesManagement/salesLedger/fileList.vue
index da37db2..57c4332 100644
--- a/src/views/salesManagement/salesLedger/fileList.vue
+++ b/src/views/salesManagement/salesLedger/fileList.vue
@@ -4,8 +4,8 @@
<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>
+ <el-button link type="primary" @click="downLoadFile(scope.row)">涓嬭浇</el-button>
+ <el-button link type="primary" @click="lookFile(scope.row)">棰勮</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index e40cfb5..5340d99 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -1,25 +1,42 @@
<template>
<div class="app-container">
<div class="search_form">
- <el-form :model="searchForm" :inline="true">
+ <el-form :model="searchForm"
+ :inline="true">
<el-form-item label="瀹㈡埛鍚嶇О锛�">
- <el-input v-model="searchForm.customerName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
- @change="handleQuery" />
+ <el-input v-model="searchForm.customerName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ @change="handleQuery"/>
</el-form-item>
<el-form-item label="閿�鍞悎鍚屽彿锛�">
- <el-input v-model="searchForm.salesContractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
- @change="handleQuery" />
+ <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"
- @change="handleQuery" />
+ <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>
@@ -27,78 +44,141 @@
<div class="actions">
<div></div>
<div>
- <el-button type="primary" @click="openForm('add')">
+ <el-button type="primary"
+ @click="openForm('add')">
鏂板鍙拌处
</el-button>
- <el-button type="primary" plain @click="handleImport">瀵煎叆</el-button>
+ <el-button type="primary"
+ plain
+ @click="handleImport">瀵煎叆
+ </el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
- <el-button type="primary" plain @click="handlePrint">鎵撳嵃</el-button>
+ <el-button type="danger"
+ plain
+ @click="handleDelete">鍒犻櫎
+ </el-button>
+ <el-button type="primary"
+ plain
+ @click="handlePrint">鎵撳嵃
+ </el-button>
</div>
</div>
- <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
- :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" :row-class-name="tableRowClassName" show-summary style="width: 100%"
- :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">
+ <el-table :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.id"
+ :row-class-name="tableRowClassName"
+ show-summary
+ style="width: 100%"
+ :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">
- <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">
+ <el-table :data="props.row.children"
+ border
+ show-summary
+ :summary-method="(param) => summarizeChildrenTable(param, props.row)">
+ <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>
+ <el-tag
+ v-if="scope.row.approveStatus === 1 && scope.row.noQuantity !== 0"
+ type="success">鍏呰冻
+ </el-tag>
+ <el-tag
+ v-else-if="scope.row.approveStatus === 0 && scope.row.noQuantity === 0"
+ type="success">宸插嚭搴�
+ </el-tag>
+ <el-tag v-else
+ type="danger">涓嶈冻
+ </el-tag>
</template>
</el-table-column>
- <el-table-column label="鍙戣揣鐘舵��" width="140" align="center">
- <template #default="scope">
- <el-tag :type="getShippingStatusType(scope.row)" size="small">
- {{ getShippingStatusText(scope.row) }}
- </el-tag>
- </template>
- </el-table-column>
- <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">
+ <el-table-column label="鍙戣揣鐘舵��"
+ width="140"
+ align="center">
+ <template #default="scope">
+ <el-tag :type="getShippingStatusType(scope.row)"
+ size="small">
+ {{ getShippingStatusText(scope.row) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <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>
+ <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">
+ <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>
+ <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">
+ <el-table-column label="鏁伴噺"
+ prop="quantity"/>
+ <el-table-column label="寰呭彂璐ф暟閲�"
+ prop="noQuantity"/>
+ <el-table-column label="绋庣巼(%)"
+ prop="taxRate"/>
+ <el-table-column label="鍚◣鍗曚环(鍏�)"
+ prop="taxInclusiveUnitPrice"
+ :formatter="sensitiveAmountFormatter"/>
+ <el-table-column label="鍚◣鎬讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ :formatter="sensitiveAmountFormatter"/>
+ <el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
+ prop="taxExclusiveTotalPrice"
+ :formatter="sensitiveAmountFormatter"/>
+ <!--鎿嶄綔-->
+ <el-table-column Width="60px"
+ label="鎿嶄綔"
+ align="center">
<template #default="scope">
- <el-button
- link
- type="primary"
- size="small"
- :disabled="!canShip(scope.row)"
- @click="openDeliveryForm(scope.row)">
+ <el-button link
+ type="primary"
+ :disabled="!canShip(scope.row)"
+ @click="openDeliveryForm(scope.row)">
鍙戣揣
</el-button>
</template>
@@ -106,62 +186,141 @@
</el-table>
</template>
</el-table-column>
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
- <el-table-column label="閿�鍞悎鍚屽彿" prop="salesContractNo" width="180" show-overflow-tooltip />
- <el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" width="300" show-overflow-tooltip />
- <el-table-column label="涓氬姟鍛�" prop="salesman" width="100" show-overflow-tooltip />
- <el-table-column label="椤圭洰鍚嶇О" prop="projectName" width="180" show-overflow-tooltip />
- <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="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 label="浜や粯鏃ユ湡" prop="deliveryDate" width="120" show-overflow-tooltip />
- <el-table-column label="澶囨敞" prop="remarks" width="200" 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="salesContractNo"
+ width="180"
+ show-overflow-tooltip/>
+ <el-table-column label="瀹㈡埛鍚嶇О"
+ prop="customerName"
+ width="300"
+ show-overflow-tooltip/>
+ <el-table-column label="涓氬姟鍛�"
+ prop="salesman"
+ width="100"
+ show-overflow-tooltip/>
+ <el-table-column label="椤圭洰鍚嶇О"
+ prop="projectName"
+ width="180"
+ show-overflow-tooltip/>
+ <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="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 label="浜や粯鏃ユ湡"
+ prop="deliveryDate"
+ width="120"
+ show-overflow-tooltip/>
+ <el-table-column label="澶囨敞"
+ prop="remarks"
+ width="200"
+ show-overflow-tooltip/>
+ <el-table-column fixed="right"
+ label="鎿嶄綔"
+ width="130"
+ align="center">
<template #default="scope">
- <el-button link type="primary" size="small" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit">缂栬緫</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>-->
+ <el-button link
+ type="primary"
+ @click="openForm('edit', scope.row)"
+ :disabled="!scope.row.isEdit || scope.row.hasProductionRecord || !canEditLedger(scope.row)">缂栬緫
+ </el-button>
+ <el-button link
+ type="primary"
+ @click="openFileDialog(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>
- <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">
- <!-- 鎶ヤ环鍗曞鍏ュ叆鍙o細鏀惧湪琛ㄥ崟椤堕儴锛岄�夋嫨鍚庡弽鏄惧鎴�/涓氬姟鍛樼瓑 -->
- <el-row v-if="operationType === 'add'" style="margin-bottom: 10px;">
- <el-col :span="24" style="text-align: right;">
- <el-button type="primary" plain @click="openQuotationDialog">
- 浠庨攢鍞姤浠峰鍏�
- </el-button>
- </el-col>
- </el-row>
+ <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">
+ <!-- 鎶ヤ环鍗曞鍏ュ叆鍙o細鏀惧湪琛ㄥ崟椤堕儴锛岄�夋嫨鍚庡弽鏄惧鎴�/涓氬姟鍛樼瓑 -->
+ <el-row v-if="operationType === 'add'"
+ style="margin-bottom: 10px;">
+ <el-col :span="24"
+ style="text-align: right;">
+ <el-button type="primary"
+ plain
+ @click="openQuotationDialog">
+ 浠庨攢鍞姤浠峰鍏�
+ </el-button>
+ </el-col>
+ </el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="閿�鍞悎鍚屽彿锛�" prop="salesContractNo">
- <el-input v-model="form.salesContractNo" placeholder="鑷姩鐢熸垚" clearable disabled />
+ <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="salesman">
- <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-form-item label="涓氬姟鍛橈細"
+ prop="salesman">
+ <el-select v-model="form.salesman"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="operationType === 'view'"
+ filterable>
+ <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="30">
<el-col :span="12">
- <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerId">
- <el-select v-model="form.customerId" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'view'">
- <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�"
+ prop="customerId">
+ <el-select v-model="form.customerId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="operationType === 'view'"
+ filterable>
+ <el-option v-for="item in customerOption"
+ :key="item.id"
+ :label="item.customerName"
+ :value="item.id">
{{
item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
}}
@@ -169,520 +328,674 @@
</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 :disabled="operationType === 'view'" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
- <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 :disabled="operationType === 'view'" />
- </el-form-item>
- </el-col>
- <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 :gutter="30">
- <el-col :span="12">
- <el-form-item label="褰曞叆浜猴細" prop="entryPerson">
- <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 />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="浜よ揣鏃ユ湡锛�" prop="entryDate">
- <el-date-picker style="width: 100%" v-model="form.deliveryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
- type="date" placeholder="璇烽�夋嫨" clearable />
+ <el-form-item label="椤圭洰鍚嶇О锛�"
+ prop="projectName">
+ <el-input v-model="form.projectName"
+ 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
- :summary-method="summarizeMainTable">
- <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'"
- :selectable="(row) => !isProductShipped(row)" />
- <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"
- :disabled="isProductShipped(scope.row)"
- @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="remarks">
- <el-input v-model="form.remarks" 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="salesLedgerFiles">
- <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>
-
- <pagination
- v-show="quotationPage.total > 0"
- :total="quotationPage.total"
- layout="total, sizes, prev, pager, next, jumper"
- :page="quotationPage.current"
- :limit="quotationPage.size"
- @pagination="quotationPaginationChange"
- />
-
- <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">
- <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 :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%"
- :precision="2"
- placeholder="璇疯緭鍏�" clearable @change="calculateFromUnitPrice" />
- </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
- :precision="2"
- @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-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>
- <!-- 瀵煎叆寮圭獥 -->
- <FormDialog
- v-model="importUpload.open"
- :title="importUpload.title"
- :width="'600px'"
- @close="importUpload.open = false"
- @confirm="submitImportFile"
- @cancel="importUpload.open = false"
- >
- <el-upload
- ref="importUploadRef"
- :limit="1"
- accept=".xlsx,.xls"
- :action="importUpload.url"
- :headers="importUpload.headers"
- :before-upload="importUpload.beforeUpload"
- :on-success="importUpload.onSuccess"
- :on-error="importUpload.onError"
- :on-progress="importUpload.onProgress"
- :on-change="importUpload.onChange"
- :auto-upload="false"
- drag
- >
- <i class="el-icon-upload"></i>
- <div class="el-upload__text">
- 灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em>
- </div>
- <template #tip>
- <div class="el-upload__tip">
- 浠呮敮鎸� xls/xlsx锛屽ぇ灏忎笉瓒呰繃 10MB銆�
- <el-button link type="primary" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
- </div>
- </template>
- </el-upload>
- </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 :gutter="30">
+ <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
+ :disabled="operationType === 'view'"/>
+ </el-form-item>
+ </el-col>
+ <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 :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="褰曞叆浜猴細"
+ prop="entryPerson">
+ <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/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浜よ揣鏃ユ湡锛�"
+ prop="entryDate">
+ <el-date-picker style="width: 100%"
+ v-model="form.deliveryDate"
+ 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'"
+ :selectable="(row) => !isProductShipped(row)"/>
+ <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="isProduction"
+ width="150">
+ <template #default="scope">
+ <el-tag :type="scope.row.isProduction ? 'success' : 'info'">
+ {{ scope.row.isProduction ? '鏄�' : '鍚�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <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"
+ :disabled="isProductShipped(scope.row)"
+ @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>
- <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;"
- >
+ <el-form-item label="澶囨敞锛�"
+ prop="remarks">
+ <el-input v-model="form.remarks"
+ 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="salesLedgerFiles">
+ <FileUpload v-model:file-list="fileList"/>
+ </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>
+ <pagination v-show="quotationPage.total > 0"
+ :total="quotationPage.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="quotationPage.current"
+ :limit="quotationPage.size"
+ @pagination="quotationPaginationChange"/>
+ <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-tree-select v-model="productForm.productCategory"
+ placeholder="璇烽�夋嫨"
+ clearable
+ filterable
+ 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">
+ <el-option
+ v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�"
+ prop="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-col :span="12">
+ <el-form-item label="鏁伴噺锛�"
+ prop="quantity">
+ <el-input-number :step="0.1"
+ :min="0"
+ v-model="productForm.quantity"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :precision="2"
+ @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-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="isProduction">
+ <el-radio-group v-model="productForm.isProduction">
+ <el-radio label="鏄�"
+ :value="true"/>
+ <el-radio label="鍚�"
+ :value="false"/>
+ </el-radio-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
+ <!-- 瀵煎叆寮圭獥 -->
+ <FormDialog v-model="importUpload.open"
+ :title="importUpload.title"
+ :width="'600px'"
+ @close="importUpload.open = false"
+ @confirm="submitImportFile"
+ @cancel="importUpload.open = false">
+ <el-upload ref="importUploadRef"
+ :limit="1"
+ accept=".xlsx,.xls"
+ :action="importUpload.url"
+ :headers="importUpload.headers"
+ :before-upload="importUpload.beforeUpload"
+ :on-success="importUpload.onSuccess"
+ :on-error="importUpload.onError"
+ :on-progress="importUpload.onProgress"
+ :on-change="importUpload.onChange"
+ :auto-upload="false"
+ drag>
+ <i class="el-icon-upload"></i>
+ <div class="el-upload__text">
+ 灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em>
+ </div>
+ <template #tip>
+ <div class="el-upload__tip">
+ 浠呮敮鎸� xls/xlsx锛屽ぇ灏忎笉瓒呰繃 10MB銆�
+ <el-button link
+ type="primary"
+ @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘
+ </el-button>
+ </div>
+ </template>
+ </el-upload>
+ </FormDialog>
+ <!-- // todo 闄勪欢棰勮鐩稿叧 -->
+ <!-- 闄勪欢鍒楄〃寮圭獥 -->
+ <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="sales_ledger"
+ :record-id="recordId"/>
+ <!-- 鎵撳嵃棰勮寮圭獥 -->
+ <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="document-title">闆跺敭鍙戣揣鍗�</div>
+ </div>
+ <div class="info-section">
+ <div class="info-row">
<div>
- <span>瀹℃壒浜�</span>
- 鈫�
+ <span class="label">鍙戣揣鏃ユ湡锛�</span>
+ <span class="value">{{ formatDate(item.createTime) }}</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>
+ <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-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>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="鎵瑰彿锛�"
+ prop="batchNo">
+ <el-table :data="deliveryForm.batchNoList"
+ border
+ size="small"
+ max-height="260"
+ style="width: 100%;">
+ <el-table-column label="鎵瑰彿"
+ prop="batchNo"
+ min-width="180"/>
+ <el-table-column label="鏁伴噺"
+ min-width="120"
+ align="center">
+ <template #default="scope">
+ {{ getDeliveryBatchQuantity(scope.row) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙戣揣鏁伴噺"
+ min-width="160"
+ align="center">
+ <template #default="scope">
+ <el-input-number v-model="scope.row.deliveryQuantity"
+ :min="0"
+ :max="getDeliveryBatchDeliveryMax(scope.row)"
+ :precision="2"
+ :step="0.01"
+ controls-position="right"
+ @change="handleDeliveryBatchQuantityChange(scope.row)"
+ style="width: 100%;"/>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </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 {getToken} from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
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 {addShippingInfo} from "@/api/salesManagement/deliveryLedger.js";
+import {ElMessageBox, ElMessage} from "element-plus";
import useUserStore from "@/store/modules/user";
-import { userListNoPage } from "@/api/system/user.js";
-import FileListDialog from '@/components/Dialog/FileListDialog.vue';
-import FormDialog from '@/components/Dialog/FormDialog.vue';
-import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
+import {userListNoPage} from "@/api/system/user.js";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {getQuotationList} from "@/api/salesManagement/salesQuotation.js";
import {
- ledgerListPage,
- productList,
- customerList,
- addOrUpdateSalesLedger,
- getSalesLedgerWithProducts,
- delLedger,
- addOrUpdateSalesLedgerProduct,
- delProduct,
- delLedgerFile, getProductInventory,
+ ledgerListPage,
+ productList,
+ customerList,
+ addOrUpdateSalesLedger,
+ getSalesLedgerWithProducts,
+ delLedger,
+ addOrUpdateSalesLedgerProduct,
+ delProduct,
+ delLedgerFile,
+ getProductInventory,
} from "@/api/salesManagement/salesLedger.js";
-import { modelList, productTreeList } from "@/api/basicData/product.js";
+import {getStockInventoryByModelId} from "@/api/inventoryManagement/stockInventory.js";
+import {modelList, productTreeList} from "@/api/basicData/product.js";
import useFormData from "@/hooks/useFormData.js";
import dayjs from "dayjs";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
import { getCurrentDate } from "@/utils/index.js";
+import {listCustomer} from "@/api/basicData/customer.js";
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
+
+const router = useRouter();
+const route = useRoute();
const userStore = useUserStore();
-const { proxy } = getCurrentInstance();
+const {proxy} = getCurrentInstance();
+const { tax_rate } = proxy.useDict("tax_rate");
const tableData = ref([]);
const productData = ref([]);
const selectedRows = ref([]);
@@ -693,8 +1006,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([]);
@@ -703,81 +1016,78 @@
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
- searchForm: {
- customerName: "", // 瀹㈡埛鍚嶇О
- salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
- entryDate: null, // 褰曞叆鏃ユ湡
- entryDateStart: undefined,
- entryDateEnd: undefined,
- },
- form: {
- salesContractNo: "",
- salesman: "",
- customerId: "",
- entryPerson: "",
- entryDate: "",
+ searchForm: {
+ customerName: "", // 瀹㈡埛鍚嶇О
+ salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
+ entryDate: null, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
+ },
+ form: {
+ salesContractNo: "",
+ salesman: "",
+ customerId: "",
+ entryPerson: "",
+ entryDate: "",
deliveryDate: "",
- 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" }],
- deliveryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- },
+ maintenanceTime: "",
+ productData: [],
+ executionDate: "",
+ hasProductionRecord: false,
+ },
+ 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"}],
+ deliveryDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
+ executionDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
+ },
});
-const { form, rules } = toRefs(data);
-const { form: searchForm } = useFormData(data.searchForm);
+const {form, rules} = toRefs(data);
+const {form: searchForm} = useFormData(data.searchForm);
// 浜у搧琛ㄥ崟寮规鏁版嵁
const productFormVisible = ref(false);
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" }],
- 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" }],
- },
+ productForm: {
+ productCategory: "",
+ specificationModel: "",
+ unit: "",
+ quantity: "",
+ taxInclusiveUnitPrice: "",
+ taxRate: "",
+ taxInclusiveTotalPrice: "",
+ taxExclusiveTotalPrice: "",
+ invoiceType: "",
+ isProduction: false,
+ },
+ 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"}],
+ isProduction: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
+ },
});
-const { productForm, productRules } = toRefs(productFormData);
+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() },
-});
// 鎵撳嵃鐩稿叧
const printPreviewVisible = ref(false);
const printData = ref([]);
@@ -787,1284 +1097,1450 @@
const quotationLoading = ref(false);
const quotationList = ref([]);
const quotationSearchForm = reactive({
- quotationNo: "",
- customer: "",
+ quotationNo: "",
+ customer: "",
});
// 鎶ヤ环鍗曞脊妗嗗垎椤�
const quotationPage = reactive({
- current: 1,
- size: 10,
- total: 0,
+ current: 1,
+ size: 10,
+ total: 0,
});
const selectedQuotation = ref(null);
// 鍙戣揣鐩稿叧
const deliveryFormVisible = ref(false);
const currentDeliveryRow = ref(null);
+const getDeliveryBatchQuantity = item => {
+ const quantity = item?.qualitity
+ ?? item?.quantity
+ ?? item?.unLockedQuantity
+ ?? item?.qualifiedUnLockedQuantity
+ ?? item?.qualifiedQuantity
+ ?? item?.stockQuantity;
+ return quantity ?? 0;
+};
+const getCurrentDeliveryRowQuantity = () => {
+ return Number(currentDeliveryRow.value?.noQuantity || 0);
+};
+const getDeliveryBatchDeliveryMax = row => {
+ const productQuantity = getCurrentDeliveryRowQuantity();
+ const batchQuantity = Number(getDeliveryBatchQuantity(row) || 0);
+ const otherBatchTotal = (deliveryForm.value.batchNoList || []).reduce(
+ (sum, item) => {
+ if (item?.id === row?.id) return sum;
+ return sum + Number(item?.deliveryQuantity || 0);
+ },
+ 0
+ );
+ const remainingProductQuantity = Math.max(
+ 0,
+ productQuantity - otherBatchTotal
+ );
+ return Math.max(0, Math.min(batchQuantity, remainingProductQuantity));
+};
+const handleDeliveryBatchQuantityChange = row => {
+ const max = getDeliveryBatchDeliveryMax(row);
+ const currentValue = Number(row?.deliveryQuantity || 0);
+ if (currentValue > max) {
+ row.deliveryQuantity = max;
+ proxy.$modal.msgWarning("鍙戣揣鏁伴噺涓嶈兘瓒呰繃杩欎釜浜у搧鐨勬暟閲�");
+ }
+};
+const getSelectedDeliveryBatchRows = () => {
+ return (deliveryForm.value.batchNoList || []).filter(
+ item => Number(item?.deliveryQuantity || 0) > 0
+ );
+};
+const getDeliveryBatchNoList = async productModelId => {
+ if (!productModelId) return [];
+ const res = await getStockInventoryByModelId(productModelId);
+ const rawList = Array.isArray(res?.data)
+ ? res.data
+ : res?.data?.records || res?.data?.rows || [];
+ const seenIds = new Set();
+ return rawList.filter(item => {
+ if (!item?.id || !item?.batchNo || seenIds.has(item.id)) {
+ return false;
+ }
+ seenIds.add(item.id);
+ return true;
+ }).map(item => ({
+ ...item,
+ deliveryQuantity: 0,
+ }));
+};
const deliveryFormData = reactive({
deliveryForm: {
type: "璐ц溅", // 璐ц溅, 蹇��
},
deliveryRules: {
- type: [
- { required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change" }
- ]
+ 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 {deliveryForm, deliveryRules} = toRefs(deliveryFormData);
// 瀵煎叆鐩稿叧
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("瀵煎叆澶辫触锛岃閲嶈瘯");
- },
+ 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();
+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 handleQuery = () => {
- // 鍙湁鍦ㄧ偣鍑绘悳绱㈡寜閽椂鎵嶉噸缃〉鐮佸埌绗竴椤�
- // 閬垮厤琛ㄥ崟瀛楁change浜嬩欢骞叉壈鍒嗛〉
- if (arguments.length === 0) {
- page.current = 1;
- }
- expandedRowKeys.value = [];
- getList();
+ // 鍙湁鍦ㄧ偣鍑绘悳绱㈡寜閽椂鎵嶉噸缃〉鐮佸埌绗竴椤�
+ // 閬垮厤琛ㄥ崟瀛楁change浜嬩欢骞叉壈鍒嗛〉
+ if (arguments.length === 0) {
+ page.current = 1;
+ }
+ expandedRowKeys.value = [];
+ getList();
};
-const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
+const paginationChange = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
};
const getList = () => {
- 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;
- });
+ 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 = () => {
- // 杩斿洖 Promise锛屼究浜庡湪缂栬緫浜у搧鏃剁瓑寰呭姞杞藉畬鎴�
- return productTreeList().then((res) => {
- productOptions.value = convertIdToValue(res);
- return productOptions.value;
- });
+ // 杩斿洖 Promise锛屼究浜庡湪缂栬緫浜у搧鏃剁瓑寰呭姞杞藉畬鎴�
+ return productTreeList().then(res => {
+ productOptions.value = convertIdToValue(res);
+ return productOptions.value;
+ });
};
const formattedNumber = (row, column, cellValue) => {
- return parseFloat(cellValue).toFixed(2);
+ if (cellValue === undefined || cellValue === null || cellValue === "") {
+ return "0.00";
+ }
+ return parseFloat(cellValue).toFixed(2);
+};
+const findLedgerRecordByRow = row => {
+ if (!row) return null;
+ if (
+ row.maintainer !== undefined ||
+ row.maintainerName !== undefined ||
+ row.entryPerson !== undefined ||
+ row.entryPersonName !== undefined
+ ) {
+ return row;
+ }
+ if (row.salesLedgerId !== undefined && row.salesLedgerId !== null) {
+ return (
+ tableData.value.find(
+ item => String(item.id) === String(row.salesLedgerId)
+ ) || null
+ );
+ }
+ return null;
+};
+const isCurrentUserMaintainer = row => {
+ const ledgerRecord = findLedgerRecordByRow(row);
+ if (!ledgerRecord) return true;
+ const currentUserId = String(userStore.id ?? "");
+ const currentNickName = String(userStore.nickName ?? "").trim();
+ const maintainerId = ledgerRecord.maintainerId ?? ledgerRecord.entryPerson;
+ const maintainerName =
+ ledgerRecord.maintainerName ??
+ ledgerRecord.maintainer ??
+ ledgerRecord.entryPersonName;
+ if (
+ maintainerId !== undefined &&
+ maintainerId !== null &&
+ String(maintainerId) !== ""
+ ) {
+ return String(maintainerId) === currentUserId;
+ }
+ if (
+ maintainerName !== undefined &&
+ maintainerName !== null &&
+ String(maintainerName).trim() !== ""
+ ) {
+ return String(maintainerName).trim() === currentNickName;
+ }
+ return true;
+};
+const canEditLedger = row => isCurrentUserMaintainer(row);
+const canDeleteLedger = row => isCurrentUserMaintainer(row);
+const sensitiveAmountFormatter = (row, column, cellValue) => {
+ if (!isCurrentUserMaintainer(row)) {
+ return "*****";
+ }
+ return formattedNumber(row, column, cellValue);
};
// 鑾峰彇tree瀛愭暟鎹�
-const getModels = (value) => {
- productForm.value.productCategory = findNodeById(productOptions.value, value);
- modelList({ id: value }).then((res) => {
- modelOptions.value = res;
- });
+const getModels = value => {
+ productForm.value.productCategory = findNodeById(productOptions.value, value);
+ modelList({id: value}).then(res => {
+ modelOptions.value = res;
+ });
};
-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 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; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
- }
- 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;
+ 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);
+const handleSelectionChange = selection => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter(item => item.children !== undefined);
+ console.log("selection", selectedRows.value);
};
-const productSelected = (selectedRows) => {
- productSelectedRows.value = selectedRows;
+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: 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 tableRowClassName = ({ row }) => {
- if (!row.deliveryDate) return '';
- if (row.isFh) return '';
+const tableRowClassName = ({row}) => {
+ if (!row.deliveryDate) return "";
+ if (row.isFh) return "";
const diff = row.deliveryDaysDiff;
if (diff === 15) {
- return 'yellow';
+ return "yellow";
} else if (diff === 10) {
- return 'pink';
+ return "pink";
} else if (diff === 2) {
- return 'purple';
+ return "purple";
} else if (diff < 2) {
- return 'red';
+ return "red";
}
};
// 涓昏〃鍚堣鏂规硶
-const summarizeMainTable = (param) => {
- return proxy.summarizeTable(param, [
- "contractAmount",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- ]);
+const summarizeMainTable = param => {
+ return proxy.summarizeTable(param, [
+ "contractAmount",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
};
// 瀛愯〃鍚堣鏂规硶
-const summarizeChildrenTable = (param) => {
- return proxy.summarizeTable(param, [
- "taxInclusiveUnitPrice",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- ]);
+const summarizeChildrenTable = (param, parentRow) => {
+ if (!isCurrentUserMaintainer(parentRow)) {
+ const {columns} = param;
+ return columns.map((column, index) => {
+ if (index === 0) {
+ return "鍚堣";
+ }
+ if (
+ [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ].includes(column.property)
+ ) {
+ return "*****";
+ }
+ return "";
+ });
+ }
+ return proxy.summarizeTable(param, [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
};
// 鎵撳紑寮规
const openForm = async (type, row) => {
- 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;
+ if (type === "edit" && row && !canEditLedger(row)) {
+ proxy.$modal.msgWarning("褰撳墠绯荤粺鐧诲綍浜轰笉鏄淮鎶や汉锛屼笉鑳界紪杈戞暟鎹�");
+ return;
+ }
+ operationType.value = type;
+ form.value = {};
+ productData.value = [];
+ selectedQuotation.value = null;
+ let userLists = await userListNoPage();
+ userList.value = userLists.data;
+ listCustomer({current: -1, size: -1}).then(res => {
+ customerOption.value = res.data.records;
+ });
+ 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.storageBlobVOs;
+ });
+ }
+ // 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;
- // 鎵撳紑寮圭獥鏃堕噸缃垎椤靛埌绗竴椤�
- quotationPage.current = 1;
- // 鍏堢‘淇濆鎴峰垪琛ㄥ凡鍔犺浇锛屼究浜庡悗缁洖濉� customerId
- if (!customerOption.value || customerOption.value.length === 0) {
- try {
- const res = await customerList();
- customerOption.value = res;
- } catch (e) {
- // ignore锛屽厑璁哥敤鎴峰悗缁墜鍔ㄩ�夋嫨瀹㈡埛
- }
- }
- await fetchQuotationList();
+ if (operationType.value === "view") return;
+ quotationDialogVisible.value = true;
+ // 鎵撳紑寮圭獥鏃堕噸缃垎椤靛埌绗竴椤�
+ quotationPage.current = 1;
+ // 鍏堢‘淇濆鎴峰垪琛ㄥ凡鍔犺浇锛屼究浜庡悗缁洖濉� customerId
+ if (!customerOption.value || customerOption.value.length === 0) {
+ try {
+ listCustomer({current: -1, size: -1}).then(res => {
+ customerOption.value = res.data.records;
+ });
+ } catch (e) {
+ // ignore锛屽厑璁哥敤鎴峰悗缁墜鍔ㄩ�夋嫨瀹㈡埛
+ }
+ }
+ await fetchQuotationList();
};
const fetchQuotationList = async () => {
- quotationLoading.value = true;
- try {
- const params = {
- // 鍚庣鍒嗛〉瀛楁锛歝urrent / size
- current: quotationPage.current,
- size: quotationPage.size,
- ...quotationSearchForm,
- status: "閫氳繃",
- };
- const res = await getQuotationList(params);
- quotationList.value = res?.data?.records || [];
- quotationPage.total = res?.data?.total || 0;
- } finally {
- quotationLoading.value = false;
- }
+ quotationLoading.value = true;
+ try {
+ const params = {
+ // 鍚庣鍒嗛〉瀛楁锛歝urrent / size
+ current: quotationPage.current,
+ size: quotationPage.size,
+ ...quotationSearchForm,
+ status: "閫氳繃",
+ };
+ const res = await getQuotationList(params);
+ quotationList.value = res?.data?.records || [];
+ quotationPage.total = res?.data?.total || 0;
+ } finally {
+ quotationLoading.value = false;
+ }
};
const resetQuotationSearch = async () => {
- quotationSearchForm.quotationNo = "";
- quotationSearchForm.customer = "";
- quotationPage.current = 1;
- await fetchQuotationList();
+ quotationSearchForm.quotationNo = "";
+ quotationSearchForm.customer = "";
+ quotationPage.current = 1;
+ await fetchQuotationList();
};
// 鎶ヤ环鍗曞脊妗嗗垎椤靛垏鎹�
-const quotationPaginationChange = (obj) => {
- quotationPage.current = obj.page;
- quotationPage.size = obj.limit;
- fetchQuotationList();
+const quotationPaginationChange = obj => {
+ quotationPage.current = obj.page;
+ quotationPage.size = obj.limit;
+ fetchQuotationList();
};
// 閫変腑鎶ヤ环鍗曞悗鍥炲~鍒板彴璐﹁〃鍗�
-const applyQuotation = (row) => {
- if (!row) return;
- selectedQuotation.value = row;
-
- // 涓氬姟鍛�
- form.value.salesman = (row.salesperson || "").trim();
-
- // 瀹㈡埛鍚嶇О -> customerId
- const qCustomerName = String(row.customer || "").trim();
- const customer = (customerOption.value || []).find((c) => {
- const name = String(c.customerName || "").trim();
- return name === qCustomerName || name.includes(qCustomerName) || qCustomerName.includes(name);
- });
- 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;
+const applyQuotation = row => {
+ if (!row) return;
+ selectedQuotation.value = row;
+
+ // 涓氬姟鍛�
+ form.value.salesman = (row.salesperson || "").trim();
+
+ // 瀹㈡埛鍚嶇О -> customerId
+ const qCustomerName = String(row.customer || "").trim();
+ const customer = (customerOption.value || []).find(c => {
+ const name = String(c.customerName || "").trim();
+ return (
+ name === qCustomerName ||
+ name.includes(qCustomerName) ||
+ qCustomerName.includes(name)
+ );
+ });
+ 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: "澧炴櫘绁�",
+ isProduction: true,
+ };
+ });
+
+ 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;
-}
-// 涓婁紶澶辫触
-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) {
- 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) {
- 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();
- });
- }
- });
+ 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;
+ }
+ form.value.storageBlobDTOs = fileList;
+ form.value.type = 1;
+ addOrUpdateSalesLedger(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ expandedRowKeys.value = [];
+ getList();
+ });
+ }
+ });
};
// 鍏抽棴寮规
const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
};
const productIndex = ref(0);
// 鎵撳紑浜у搧寮规
const openProductForm = async (type, row, index) => {
- // 缂栬緫鏃舵鏌ヤ骇鍝佹槸鍚﹀凡鍙戣揣鎴栧鏍搁�氳繃
- if (type === "edit" && isProductShipped(row)) {
- proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳界紪杈�");
- return;
- }
-
- 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);
- }
- } else {
- getProductOptions()
- }
- productFormVisible.value = true;
+ // 缂栬緫鏃舵鏌ヤ骇鍝佹槸鍚﹀凡鍙戣揣鎴栧鏍搁�氳繃
+ if (type === "edit" && isProductShipped(row)) {
+ proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳界紪杈�");
+ return;
+ }
+
+ productOperationType.value = type;
+ productForm.value = {};
+ if (type === "add") {
+ productForm.value.isProduction = true;
+ }
+ 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);
+ }
+ } else {
+ getProductOptions();
+ }
+ 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;
- }
-
- // 妫�鏌ユ槸鍚︽湁宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝�
- const shippedProducts = productSelectedRows.value.filter(row => isProductShipped(row));
- if (shippedProducts.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;
+ }
+
+ // 妫�鏌ユ槸鍚︽湁宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝�
+ const shippedProducts = productSelectedRows.value.filter(row =>
+ isProductShipped(row)
+ );
+ if (shippedProducts.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();
- }
+ importUpload.title = "瀵煎叆閿�鍞彴璐�";
+ importUpload.open = true;
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ }
};
// 涓嬭浇瀵煎叆妯℃澘
const downloadTemplate = () => {
- proxy.download("/sales/ledger/exportTemplate", {}, "閿�鍞彴璐﹀鍏ユā鏉�.xlsx");
+ proxy.download("/sales/ledger/exportTemplate", {}, "閿�鍞彴璐﹀鍏ユā鏉�.xlsx");
};
// 鎻愪氦瀵煎叆鏂囦欢
const submitImportFile = () => {
- importUpload.isUploading = true;
- proxy.$refs["importUploadRef"].submit();
+ 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("宸插彇娑�");
+ });
};
/** 鍒ゆ柇鍗曚釜浜у搧鏄惁宸插彂璐э紙鏍规嵁shippingStatus鍒ゆ柇锛屽凡鍙戣揣鎴栧鏍搁�氳繃涓嶅彲缂栬緫鍜屽垹闄わ級 */
-const isProductShipped = (product) => {
- if (!product) return false;
- const status = String(product.shippingStatus || "").trim();
- // 濡傛灉鍙戣揣鐘舵�佹槸"宸插彂璐�"鎴�"瀹℃牳閫氳繃"锛屽垯涓嶅彲缂栬緫鍜屽垹闄�
- return status === "宸插彂璐�" || status === "瀹℃牳閫氳繃";
+const isProductShipped = product => {
+ if (!product) return false;
+ const status = String(product.shippingStatus || "").trim();
+ // 濡傛灉鍙戣揣鐘舵�佹槸"宸插彂璐�"鎴�"瀹℃牳閫氳繃"锛屽垯涓嶅彲缂栬緫鍜屽垹闄�
+ return status === "宸插彂璐�" || status === "瀹℃牳閫氳繃";
};
/** 鍒ゆ柇閿�鍞鍗曚笅鏄惁瀛樺湪宸插彂璐�/鍙戣揣瀹屾垚鐨勪骇鍝侊紙涓嶅彲鍒犻櫎锛� */
-const hasShippedProducts = (products) => {
- if (!products || !products.length) return false;
- return products.some((p) => {
- const status = String(p.shippingStatus || "").trim();
- // 鏈夊彂璐ф棩鏈熸垨杞︾墝鍙疯涓哄凡鍙戣揣
- if (p.shippingDate || p.shippingCarNumber) return true;
- // 宸茶繘琛屽彂璐с�佸彂璐у畬鎴愩�佸凡鍙戣揣 鍧囦笉鍙垹闄�
- return status === "宸茶繘琛屽彂璐�" || status === "鍙戣揣瀹屾垚" || status === "宸插彂璐�";
- });
+const hasShippedProducts = products => {
+ if (!products || !products.length) return false;
+ return products.some(p => {
+ const status = String(p.shippingStatus || "").trim();
+ // 鏈夊彂璐ф棩鏈熸垨杞︾墝鍙疯涓哄凡鍙戣揣
+ if (p.shippingDate || p.shippingCarNumber) return true;
+ // 宸茶繘琛屽彂璐с�佸彂璐у畬鎴愩�佸凡鍙戣揣 鍧囦笉鍙垹闄�
+ return (
+ status === "宸茶繘琛屽彂璐�" || status === "鍙戣揣瀹屾垚" || status === "宸插彂璐�"
+ );
+ });
};
// 鍒犻櫎
const handleDelete = async () => {
- if (selectedRows.value.length === 0) {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
- const ids = selectedRows.value.map((item) => item.id);
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ const unauthorizedRows = selectedRows.value.filter(
+ row => !canDeleteLedger(row)
+ );
+ if (unauthorizedRows.length > 0) {
+ proxy.$modal.msgWarning("褰撳墠鐧诲綍鐢ㄦ埛涓嶆槸褰曞叆浜猴紝涓嶈兘鍒犻櫎璇ユ暟鎹�");
+ return;
+ }
+ const ids = selectedRows.value.map(item => item.id);
- // 妫�鏌ユ槸鍚︽湁宸茶繘琛屽彂璐ф垨鍙戣揣瀹屾垚鐨勯攢鍞鍗曪紝鑻ユ湁鍒欎笉鍏佽鍒犻櫎
- const cannotDeleteNames = [];
- for (const row of selectedRows.value) {
- let products = row.children && row.children.length > 0 ? row.children : null;
- if (!products) {
- try {
- const res = await productList({ salesLedgerId: row.id, type: 1 });
- products = res.data || [];
- } catch {
- products = [];
- }
- }
- if (hasShippedProducts(products)) {
- cannotDeleteNames.push(row.salesContractNo || `ID:${row.id}`);
- }
- }
- if (cannotDeleteNames.length > 0) {
- proxy.$modal.msgWarning("宸茶繘琛屽彂璐ф垨鍙戣揣瀹屾垚鐨勯攢鍞鍗曚笉鑳藉垹闄わ細" + cannotDeleteNames.join("銆�"));
- return;
- }
+ // 妫�鏌ユ槸鍚︽湁宸茶繘琛屽彂璐ф垨鍙戣揣瀹屾垚鐨勯攢鍞鍗曪紝鑻ユ湁鍒欎笉鍏佽鍒犻櫎
+ const cannotDeleteNames = [];
+ for (const row of selectedRows.value) {
+ let products =
+ row.children && row.children.length > 0 ? row.children : null;
+ if (!products) {
+ try {
+ const res = await productList({salesLedgerId: row.id, type: 1});
+ products = res.data || [];
+ } catch {
+ products = [];
+ }
+ }
+ if (hasShippedProducts(products)) {
+ cannotDeleteNames.push(row.salesContractNo || `ID:${row.id}`);
+ }
+ }
+ if (cannotDeleteNames.length > 0) {
+ proxy.$modal.msgWarning(
+ "宸茶繘琛屽彂璐ф垨鍙戣揣瀹屾垚鐨勯攢鍞鍗曚笉鑳藉垹闄わ細" + cannotDeleteNames.join("銆�")
+ );
+ return;
+ }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- delLedger(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delLedger(ids).then(res => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鎵撳嵃鍔熻兘
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();
- }
+ 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>
+ console.log("寮�濮嬫墽琛屾墦鍗帮紝鏁版嵁鏉℃暟:", printData.value.length);
+ console.log("鎵撳嵃鏁版嵁:", printData.value);
- <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>
+ // 鍒涘缓涓�涓柊鐨勬墦鍗扮獥鍙�
+ const printWindow = window.open("", "_blank", "width=800,height=600");
- <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);
- };
+ // 鏋勫缓鎵撳嵃鍐呭
+ 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="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 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 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 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 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 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 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;
};
// 鏍规嵁涓嶅惈绋庢�讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
const calculateFromExclusiveTotalPrice = () => {
- if (!productForm.value.taxRate) {
- 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 (!productForm.value.taxRate) {
+ 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;
};
// 鏍规嵁鏁伴噺鍙樺寲璁$畻鎬讳环
const calculateFromQuantity = () => {
- if (!productForm.value.taxRate) {
- 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 (!productForm.value.taxRate) {
+ 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;
};
// 鏍规嵁鍚◣鍗曚环鍙樺寲璁$畻鎬讳环
const calculateFromUnitPrice = () => {
- if (!productForm.value.taxRate) {
- 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 (!productForm.value.taxRate) {
+ 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;
};
// 鏍规嵁绋庣巼鍙樺寲璁$畻涓嶅惈绋庢�讳环
const calculateFromTaxRate = () => {
- if (!productForm.value.taxRate) {
- 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 (!productForm.value.taxRate) {
+ 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;
};
/**
* 鑾峰彇鍙戣揣鐘舵�佹枃鏈�
* @param row 琛屾暟鎹�
*/
-const getShippingStatusText = (row) => {
- // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屾樉绀�"宸插彂璐�"
- if (row.shippingDate || row.shippingCarNumber) {
- return '宸插彂璐�';
- }
-
- // 鑾峰彇鍙戣揣鐘舵�佸瓧娈�
- const status = row.shippingStatus;
-
- // 濡傛灉鐘舵�佷负绌烘垨鏈畾涔夛紝榛樿涓�"寰呭彂璐�"
- if (status === null || status === undefined || status === '') {
- return '寰呭彂璐�';
- }
-
- // 鐘舵�佹槸瀛楃涓�
- const statusStr = String(status).trim();
- const statusTextMap = {
- '寰呭彂璐�': '寰呭彂璐�',
- '寰呭鏍�': '寰呭鏍�',
- '瀹℃牳涓�': '瀹℃牳涓�',
- '瀹℃牳鎷掔粷': '瀹℃牳鎷掔粷',
- '瀹℃牳閫氳繃': '瀹℃牳閫氳繃',
- '宸插彂璐�': '宸插彂璐�'
- };
- return statusTextMap[statusStr] || '寰呭彂璐�';
+const getShippingStatusText = row => {
+ // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屾樉绀�"宸插彂璐�"
+ // if (row.shippingDate || row.shippingCarNumber) {
+ // return "宸插彂璐�";
+ // }
+
+ // 鑾峰彇鍙戣揣鐘舵�佸瓧娈�
+ const status = row.shippingStatus;
+
+ // 濡傛灉鐘舵�佷负绌烘垨鏈畾涔夛紝榛樿涓�"寰呭彂璐�"
+ if (status === null || status === undefined || status === "") {
+ return "寰呭彂璐�";
+ }
+
+ // 鐘舵�佹槸瀛楃涓�
+ const statusStr = String(status).trim();
+ const statusTextMap = {
+ 寰呭彂璐�: "寰呭彂璐�",
+ 寰呭鏍�: "寰呭鏍�",
+ 瀹℃牳涓�: "瀹℃牳涓�",
+ 瀹℃牳鎷掔粷: "瀹℃牳鎷掔粷",
+ 瀹℃牳閫氳繃: "瀹℃牳閫氳繃",
+ 宸插彂璐�: "宸插彂璐�",
+ };
+ return statusTextMap[statusStr] || "寰呭彂璐�";
};
/**
* 鑾峰彇鍙戣揣鐘舵�佹爣绛剧被鍨嬶紙棰滆壊锛�
* @param row 琛屾暟鎹�
*/
-const getShippingStatusType = (row) => {
- // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屾樉绀虹豢鑹�
- if (row.shippingDate || row.shippingCarNumber) {
- return 'success';
- }
-
- // 鑾峰彇鍙戣揣鐘舵�佸瓧娈�
- const status = row.shippingStatus;
-
- // 濡傛灉鐘舵�佷负绌烘垨鏈畾涔夛紝榛樿涓虹伆鑹诧紙寰呭彂璐э級
- if (status === null || status === undefined || status === '') {
- return 'info';
- }
-
- // 鐘舵�佹槸瀛楃涓�
- const statusStr = String(status).trim();
- const typeTextMap = {
- '寰呭彂璐�': 'info',
- '寰呭鏍�': 'info',
- '瀹℃牳涓�': 'warning',
- '瀹℃牳鎷掔粷': 'danger',
- '瀹℃牳閫氳繃': 'success',
- '宸插彂璐�': 'success'
- };
- return typeTextMap[statusStr] || 'info';
+const getShippingStatusType = row => {
+ // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屾樉绀虹豢鑹�
+ if (row.shippingStatus === "宸插彂璐�") {
+ return "success";
+ }
+
+ // 鑾峰彇鍙戣揣鐘舵�佸瓧娈�
+ const status = row.shippingStatus;
+
+ // 濡傛灉鐘舵�佷负绌烘垨鏈畾涔夛紝榛樿涓虹伆鑹诧紙寰呭彂璐э級
+ if (status === null || status === undefined || status === "") {
+ return "info";
+ }
+
+ // 鐘舵�佹槸瀛楃涓�
+ const statusStr = String(status).trim();
+ const typeTextMap = {
+ 寰呭彂璐�: "info",
+ 寰呭鏍�: "info",
+ 瀹℃牳涓�: "warning",
+ 瀹℃牳鎷掔粷: "danger",
+ 瀹℃牳閫氳繃: "success",
+ 宸插彂璐�: "success",
+ };
+ return typeTextMap[statusStr] || "info";
};
/**
@@ -2072,101 +2548,118 @@
* 鍙湁鍦ㄤ骇鍝佺姸鎬佹槸鍏呰冻锛屽彂璐х姸鎬佹槸寰呭彂璐у拰瀹℃牳鎷掔粷鐨勬椂鍊欐墠鍙互鍙戣揣
* @param row 琛屾暟鎹�
*/
-const canShip = (row) => {
- // 浜у搧鐘舵�佸繀椤绘槸鍏呰冻锛坅pproveStatus === 1锛�
- if (row.approveStatus !== 1) {
- return false;
- }
-
- // 鑾峰彇鍙戣揣鐘舵��
- const shippingStatus = row.shippingStatus;
-
- // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屼笉鑳藉啀娆″彂璐�
- if (row.shippingDate || row.shippingCarNumber) {
- return false;
- }
-
- // 鍙戣揣鐘舵�佸繀椤绘槸"寰呭彂璐�"鎴�"瀹℃牳鎷掔粷"
- const statusStr = shippingStatus ? String(shippingStatus).trim() : '';
- return statusStr === '寰呭彂璐�' || statusStr === '瀹℃牳鎷掔粷';
+const canShip = row => {
+ // 浜у搧鐘舵�佸繀椤绘槸鍏呰冻锛坅pproveStatus === 1锛�
+ if (row.approveStatus !== 1) {
+ return false;
+ }
+
+ // 鑾峰彇鍙戣揣鐘舵��
+ const shippingStatus = row.shippingStatus;
+
+ // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屼笉鑳藉啀娆″彂璐�
+ if (shippingStatus === "宸插彂璐�") {
+ return false;
+ }
+
+ // 鍙戣揣鐘舵�佸繀椤绘槸"寰呭彂璐�"鎴�"瀹℃牳鎷掔粷"
+ const statusStr = shippingStatus ? String(shippingStatus).trim() : "";
+ return statusStr === "寰呭彂璐�" || statusStr === "瀹℃牳鎷掔粷";
};
-/**
- * 涓嬭浇鏂囦欢
- *
- * @param row 涓嬭浇鏂囦欢鐨勭浉鍏充俊鎭璞�
- */
-const fileListRef = ref(null)
-const fileListDialogVisible = ref(false)
-const downLoadFile = (row) => {
- getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
- if (fileListRef.value) {
- fileListRef.value.open(res.salesLedgerFiles)
- }
- });
+// 鎵撳紑闄勪欢寮圭獥
+const recordId = ref(0)
+const fileDialogVisible = ref(false)
+
+// 鎵撳紑闄勪欢寮规
+const openFileDialog = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
}
// 鎵撳紑鍙戣揣寮规
-const openDeliveryForm = (row) => {
- // 妫�鏌ユ槸鍚﹀彲浠ュ彂璐�
- if (!canShip(row)) {
- proxy.$modal.msgWarning("鍙湁鍦ㄤ骇鍝佺姸鎬佹槸鍏呰冻锛屽彂璐х姸鎬佹槸寰呭彂璐ф垨瀹℃牳鎷掔粷鐨勬椂鍊欐墠鍙互鍙戣揣");
- return;
- }
-
- currentDeliveryRow.value = row;
+const openDeliveryForm = async row => {
+ // 妫�鏌ユ槸鍚﹀彲浠ュ彂璐�
+ if (!canShip(row)) {
+ proxy.$modal.msgWarning(
+ "鍙湁鍦ㄤ骇鍝佺姸鎬佹槸鍏呰冻锛屽彂璐х姸鎬佹槸寰呭彂璐ф垨瀹℃牳鎷掔粷鐨勬椂鍊欐墠鍙互鍙戣揣"
+ );
+ return;
+ }
+
+ currentDeliveryRow.value = row;
+ const batchNoList = await getDeliveryBatchNoList(
+ row.productModelId || row.modelId
+ );
deliveryForm.value = {
type: "璐ц溅",
+ batchNo: [],
+ batchNoList,
};
- // 閲嶇疆瀹℃壒浜鸿妭鐐癸紙榛樿涓�涓┖鑺傜偣锛�
- approverNodes.value = [{ id: 1, userId: null }];
- nextApproverId = 2;
- deliveryFormVisible.value = true;
+ deliveryFormVisible.value = true;
};
// 鎻愪氦鍙戣揣琛ㄥ崟
const submitDelivery = () => {
- proxy.$refs["deliveryFormRef"].validate((valid) => {
+ proxy.$refs["deliveryFormRef"].validate(valid => {
if (valid) {
- // 瀹℃壒浜哄繀濉牎楠岋紙鎵�鏈夎妭鐐归兘瑕侀�変汉锛�
- const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
- if (hasEmptyApprover) {
- proxy.$modal.msgError("璇蜂负鎵�鏈夊鎵硅妭鐐归�夋嫨瀹℃壒浜猴紒");
+ const selectedBatchRows = getSelectedDeliveryBatchRows();
+ if (selectedBatchRows.length === 0) {
+ proxy.$modal.msgWarning("璇疯嚦灏戝~鍐欎竴涓壒鍙风殑鍙戣揣鏁伴噺");
return;
}
- const approveUserIds = approverNodes.value.map(node => node.userId).join(",");
+ const totalDeliveryQuantity = selectedBatchRows.reduce(
+ (sum, item) => sum + Number(item.deliveryQuantity || 0),
+ 0
+ );
+ const currentRowQuantity = Number(currentDeliveryRow.value?.quantity || 0);
+ if (currentRowQuantity > 0 && totalDeliveryQuantity > currentRowQuantity) {
+ proxy.$modal.msgWarning("鎵瑰彿鍙戣揣鎬绘暟涓嶈兘瓒呰繃褰撳墠浜у搧鏁伴噺");
+ return;
+ }
// 淇濆瓨褰撳墠灞曞紑鐨勮ID锛屼互渚垮彂璐у悗閲嶆柊鍔犺浇瀛愯〃鏍兼暟鎹�
const currentExpandedKeys = [...expandedRowKeys.value];
const salesLedgerId = currentDeliveryRow.value.salesLedgerId;
+ deliveryForm.value.batchNo = selectedBatchRows.map(item => item.id);
+ const productModelId = currentDeliveryRow.value.productModelId || currentDeliveryRow.value.modelId;
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;
+ batchNo: deliveryForm.value.batchNo,
+ batchNoDetailList: selectedBatchRows.map(item => ({
+ stockInventoryId: item.id,
+ batchNo: item.batchNo,
+ quantity: Number(item.deliveryQuantity || 0),
+ productModelId: productModelId,
+ })),
+ }).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;
- });
- }
- });
- })
+ );
+ });
+ Promise.all(loadPromises).then(() => {
+ // 鎭㈠灞曞紑鐘舵��
+ expandedRowKeys.value = currentExpandedKeys;
+ });
+ }
+ });
+ });
}
});
};
@@ -2179,212 +2672,215 @@
};
const currentFactoryName = ref("");
const getCurrentFactoryName = async () => {
- let res = await userStore.getInfo();
- currentFactoryName.value = res.user.currentFactoryName;
+ let res = await userStore.getInfo();
+ currentFactoryName.value = res.user.currentFactoryName;
};
onMounted(() => {
- getList();
- userListNoPage().then(res => {
- userList.value = res.data;
- })
- getCurrentFactoryName();
+ searchForm.salesContractNo = route.query.salesContractNo;
+ getList();
+ userListNoPage().then(res => {
+ userList.value = res.data;
+ });
+ getCurrentFactoryName();
});
</script>
<style scoped lang="scss">
.ml-10 {
- margin-left: 10px;
+ margin-left: 10px;
}
-::v-deep .yellow {
- background-color: #FAF0DE;
+:deep(.yellow) {
+ background-color: #faf0de;
}
-::v-deep .pink {
- background-color: #FAE1DE;
+:deep(.pink) {
+ background-color: #fae1de;
}
-::v-deep .red {
- background-color: #FAE1DE;
+:deep(.red) {
+ background-color: #fae1de;
}
-::v-deep .purple{
- background-color: #F4DEFA;
+:deep(.purple) {
+ background-color: #f4defa;
}
.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;
- }
+ .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-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;
+ 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;
+ 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;
- }
+ 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;
- }
- }
+ 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;
- }
- }
+ 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;
- }
- }
- }
- }
+ .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;
- }
+ .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
index 5bd5cef..2237b72 100644
--- a/src/views/salesManagement/salesQuotation/index.vue
+++ b/src/views/salesManagement/salesQuotation/index.vue
@@ -16,8 +16,8 @@
</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">
+ <el-select v-model="searchForm.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable>
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id">
{{
item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
}}
@@ -70,8 +70,8 @@
</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="primary" @click="handleView(scope.row)" style="color: #67C23A">鏌ョ湅</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
</template>
</el-table-column>
@@ -102,19 +102,15 @@
<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-form-item label="瀹㈡埛鍚嶇О" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%" clearable filterable>
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"></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-select v-model="form.salesperson" placeholder="璇烽�夋嫨涓氬姟鍛�" style="width: 100%" clearable filterable>
<el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
:value="item.nickName" />
</el-select>
@@ -153,62 +149,6 @@
<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>
@@ -356,25 +296,26 @@
<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 { Search, Document, Box, EditPen, Plus } 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";
+import {listCustomer} from "@/api/basicData/customer.js";
+import { userListNoPage } from "@/api/system/user.js";
// 鍝嶅簲寮忔暟鎹�
const loading = ref(false)
const searchForm = reactive({
quotationNo: '',
- customer: '',
+ customerId: '',
status: ''
})
const quotationList = ref([])
+const userList = ref([])
const productOptions = ref([]);
-const modelOptions = ref([]);
+const modelOptions = ref([]);
const pagination = reactive({
total: 3,
currentPage: 1,
@@ -386,6 +327,7 @@
const dialogTitle = ref('鏂板鎶ヤ环')
const form = reactive({
quotationNo: '',
+ customerId: undefined,
customer: '',
salesperson: '',
quotationDate: '',
@@ -426,29 +368,12 @@
})
return r
})
-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(() => {
@@ -480,26 +405,16 @@
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 || ''
- }))
- });
+ getProductOptions();
+ fetchCustomerOptions()
+}
+
+const fetchCustomerOptions = () => {
+ if (customerOption.value.length > 0) return
+ listCustomer({current: -1,size:-1, type: 0}).then((res) => {
+ customerOption.value = res.data.records;
+ });
}
const getProductOptions = () => {
// 杩斿洖 Promise锛屼究浜庣紪杈戞椂 await 纭繚鑳藉弽鏄�
@@ -627,10 +542,12 @@
form.id = row.id || form.id || null
// 鍏堝姞杞戒骇鍝佹爲鏁版嵁锛屽惁鍒� el-tree-select 鏃犳硶鍙嶆樉浜у搧鍚嶇О
await getProductOptions()
+ await fetchCustomerOptions()
// 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
form.quotationNo = row.quotationNo || ''
form.customer = row.customer || ''
+ form.customerId = row.customerId || undefined
form.salesperson = row.salesperson || ''
form.quotationDate = row.quotationDate || ''
form.validDate = row.validDate || ''
@@ -683,28 +600,7 @@
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
}
@@ -783,10 +679,6 @@
form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount
}
-const handleCustomerChange = () => {
- // 鍙互鏍规嵁瀹㈡埛淇℃伅鑷姩濉厖涓�浜涢粯璁ゅ��
-}
-
const handleSubmit = () => {
formRef.value.validate((valid) => {
if (valid) {
@@ -795,22 +687,13 @@
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)
-
+
+ form.customer = customerOption.value.find(item => item.id === form.customerId)?.customerName || ''
if (isEdit.value) {
// 缂栬緫
const index = quotationList.value.findIndex(item => item.id === editId.value)
@@ -834,7 +717,7 @@
}
})
}
-
+
}
})
}
@@ -860,6 +743,7 @@
id: item.id,
quotationNo: item.quotationNo || '',
customer: item.customer || '',
+ customerId: item.customerId || undefined,
salesperson: item.salesperson || '',
quotationDate: item.quotationDate || '',
validDate: item.validDate || '',
@@ -888,18 +772,25 @@
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 || ''
- }))
- });
+ // customerList().then((res) => {
+ // customerOption.value = res;
+ // });
+}
+
+const getUserList = async () => {
+ try {
+ const res = await userListNoPage()
+ userList.value = Array.isArray(res?.data) ? res.data : []
+ } catch (error) {
+ userList.value = []
+ ElMessage.error('鍔犺浇涓氬姟鍛樺垪琛ㄥけ璐�')
+ }
}
onMounted(()=>{
+ getUserList()
handleSearch()
+ fetchCustomerOptions()
})
</script>
@@ -991,71 +882,6 @@
}
}
-.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;
@@ -1082,14 +908,4 @@
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/strategyControl/index.vue b/src/views/salesManagement/strategyControl/index.vue
index 629d255..dbdae38 100644
--- a/src/views/salesManagement/strategyControl/index.vue
+++ b/src/views/salesManagement/strategyControl/index.vue
@@ -75,7 +75,7 @@
</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="handleViewPriceStrategy(scope.row)" style="color: #67C23A">鏌ョ湅</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>
@@ -254,7 +254,7 @@
</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>
+ <el-button link type="primary" @click="handleViewContract(scope.row)" style="color: #67C23A">鏌ョ湅璇︽儏</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/src/views/system/appVersion/index.vue b/src/views/system/appVersion/index.vue
new file mode 100644
index 0000000..6ff9ae6
--- /dev/null
+++ b/src/views/system/appVersion/index.vue
@@ -0,0 +1,270 @@
+<template>
+ <div class="app-container">
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" plain icon="Upload" @click="openUploadDialog">涓婁紶APK</el-button>
+ </el-col>
+ </el-row>
+
+ <el-table v-loading="loading" :data="versionList">
+ <el-table-column label="ID" prop="id" align="center" width="80"/>
+ <el-table-column label="搴旂敤鍚嶇О" prop="name" align="center" min-width="150"/>
+ <el-table-column label="鐗堟湰鍙�" prop="version" align="center" width="120"/>
+ <el-table-column label="鍒涘缓鏃堕棿" prop="createTime" align="center" width="170">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏇存柊鏃堕棿" prop="updateTime" align="center" width="170">
+ <template #default="scope">
+ <span>{{ parseTime(scope.row.updateTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒涘缓浜�" prop="createUser" align="center" width="100"/>
+ <el-table-column label="鎿嶄綔" align="center" width="180" class-name="small-padding fixed-width">
+ <template #default="scope">
+ <el-button link type="primary" @click="downloadAttachment(scope.row)">涓嬭浇</el-button>
+ <el-button link type="success" @click="openQrDialog(scope.row)">鎵爜涓嬭浇</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ v-model:page="queryParams.current"
+ v-model:limit="queryParams.size"
+ @pagination="getList"
+ />
+
+ <el-dialog title="涓婁紶APK" v-model="uploadOpen" width="560px" append-to-body @close="resetUploadForm">
+ <el-form ref="uploadRef" :model="uploadForm" :rules="uploadRules" label-width="90px">
+ <el-form-item label="搴旂敤鍚嶇О" prop="name">
+ <el-input v-model="uploadForm.name" placeholder="璇疯緭鍏ュ簲鐢ㄥ悕绉�"/>
+ </el-form-item>
+ <el-form-item label="鐗堟湰鍙�" prop="version">
+ <el-input v-model="uploadForm.version" placeholder="璇疯緭鍏ョ増鏈彿"/>
+ </el-form-item>
+ <el-form-item label="APK鏂囦欢" prop="storageBlobDTOList">
+ <FileUpload v-model:file-list="uploadForm.storageBlobDTOList" :limit="1" :file-type="['apk']"
+ :file-size="200"/>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" :loading="uploading" @click="submitUpload">纭� 瀹�</el-button>
+ <el-button @click="uploadOpen = false">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <el-dialog
+ v-model="qrOpen"
+ title="鎵爜涓嬭浇"
+ width="460px"
+ append-to-body
+ class="download-qr-dialog"
+ >
+ <div class="download-qr-content">
+ <div class="app-meta-card">
+ <div class="meta-row">
+ <span class="meta-label">搴旂敤鍚嶇О</span>
+ <span class="meta-value">{{ qrCurrentRow?.name || "-" }}</span>
+ </div>
+ <div class="meta-row">
+ <span class="meta-label">鐗堟湰缂栧彿</span>
+ <span class="meta-value">{{ qrCurrentRow?.version || "-" }}</span>
+ </div>
+ </div>
+ <div class="qr-box">
+ <img v-if="qrCodeUrl" :src="qrCodeUrl" alt="download qr code" class="qr-image" />
+ <div class="qr-tip">璇蜂娇鐢ㄦ墜鏈烘壂鐮佷笅杞藉簲鐢�</div>
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="qrOpen = false">鍏� 闂�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup name="SystemAppVersion">
+import {listAppVersion, add} from "@/api/system/appVersion";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
+import QRCode from "qrcode";
+
+const {proxy} = getCurrentInstance();
+
+const loading = ref(false);
+const versionList = ref([]);
+const total = ref(0);
+
+const queryParams = reactive({
+ current: 1,
+ size: 10,
+});
+
+const uploadOpen = ref(false);
+const uploading = ref(false);
+const qrOpen = ref(false);
+const qrCodeUrl = ref("");
+const qrCurrentRow = ref(null);
+const uploadForm = reactive({
+ name: "",
+ version: "",
+ storageBlobDTOList: null,
+});
+
+const uploadRules = {
+ name: [{required: true, message: "璇疯緭鍏ュ簲鐢ㄥ悕绉�", trigger: "blur"}],
+ version: [{required: true, message: "璇疯緭鍏ョ増鏈彿", trigger: "blur"}],
+ storageBlobDTOList: [{required: true, message: "璇蜂笂浼燗PK鏂囦欢", trigger: "change"}],
+};
+
+function normalizeListResp(res) {
+ const data = res?.data || {};
+ const records = data.records || res?.rows || [];
+ const totalNum = Number(data.total ?? res?.total ?? 0);
+ return {
+ records: Array.isArray(records) ? records : [],
+ total: Number.isNaN(totalNum) ? 0 : totalNum,
+ };
+}
+
+function getList() {
+ loading.value = true;
+ listAppVersion(queryParams)
+ .then(res => {
+ const result = normalizeListResp(res);
+ versionList.value = result.records;
+ total.value = result.total;
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+}
+
+function openUploadDialog() {
+ resetUploadForm();
+ uploadOpen.value = true;
+}
+
+function resetUploadForm() {
+ uploadForm.name = "";
+ uploadForm.version = "";
+ uploadForm.storageBlobDTOList = null;
+ proxy.resetForm("uploadRef");
+}
+
+
+function downloadAttachment(row) {
+ window.open(row.downloadURL, "_blank");
+}
+
+async function openQrDialog(row) {
+ if (!row?.downloadURL) {
+ proxy.$modal.msgError("褰撳墠璁板綍缂哄皯涓嬭浇鍦板潃锛屾棤娉曠敓鎴愪簩缁寸爜");
+ return;
+ }
+ try {
+ qrCodeUrl.value = await QRCode.toDataURL(row.downloadURL, {
+ width: 220,
+ margin: 2,
+ errorCorrectionLevel: "M",
+ color: {
+ dark: "#1f2937",
+ light: "#ffffff",
+ },
+ });
+ qrCurrentRow.value = row;
+ qrOpen.value = true;
+ } catch (error) {
+ proxy.$modal.msgError("浜岀淮鐮佺敓鎴愬け璐ワ紝璇风◢鍚庨噸璇�");
+ }
+}
+
+function submitUpload() {
+ proxy.$refs.uploadRef.validate(valid => {
+ if (!valid) return;
+ uploading.value = true;
+ add(uploadForm)
+ .then(() => {
+ proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+ uploadOpen.value = false;
+ getList();
+ })
+ .finally(() => {
+ uploading.value = false;
+ });
+ });
+}
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped>
+.download-qr-content {
+ padding: 4px 4px 8px;
+}
+
+.app-meta-card {
+ border-radius: 10px;
+ padding: 12px 14px;
+ margin-bottom: 16px;
+ background: linear-gradient(135deg, #f0f7ff 0%, #ecfdf5 100%);
+ border: 1px solid #dbeafe;
+}
+
+.meta-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ line-height: 26px;
+}
+
+.meta-row + .meta-row {
+ margin-top: 6px;
+}
+
+.meta-label {
+ font-size: 13px;
+ color: #64748b;
+}
+
+.meta-value {
+ max-width: 260px;
+ font-size: 14px;
+ color: #0f172a;
+ font-weight: 600;
+ word-break: break-all;
+ text-align: right;
+}
+
+.qr-box {
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ padding: 18px 12px 14px;
+ text-align: center;
+ background: #ffffff;
+ box-shadow: 0 6px 20px rgba(15, 23, 42, 0.06);
+}
+
+.qr-image {
+ width: 220px;
+ height: 220px;
+ border-radius: 8px;
+ border: 1px solid #e5e7eb;
+ padding: 8px;
+ background: #fff;
+}
+
+.qr-tip {
+ margin-top: 10px;
+ font-size: 13px;
+ color: #64748b;
+}
+</style>
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
index d960674..85ecf28 100644
--- a/src/views/system/role/index.vue
+++ b/src/views/system/role/index.vue
@@ -273,7 +273,7 @@
/** 鏁版嵁鑼冨洿閫夐」*/
const dataScopeOptions = ref([
{ value: "1", label: "鍏ㄩ儴鏁版嵁鏉冮檺" },
- { value: "2", label: "鑷畾鏁版嵁鏉冮檺" },
+ // { value: "2", label: "鑷畾鏁版嵁鏉冮檺" },
{ value: "3", label: "鏈儴闂ㄦ暟鎹潈闄�" },
{ value: "4", label: "鏈儴闂ㄥ強浠ヤ笅鏁版嵁鏉冮檺" },
{ value: "5", label: "浠呮湰浜烘暟鎹潈闄�" }
diff --git a/src/views/systemArchitecture/index.vue b/src/views/systemArchitecture/index.vue
new file mode 100644
index 0000000..1eb37bb
--- /dev/null
+++ b/src/views/systemArchitecture/index.vue
@@ -0,0 +1,436 @@
+<template>
+ <div class="architecture-page">
+ <section ref="canvasRef" class="canvas">
+ <svg
+ v-if="linkPath"
+ class="link-overlay"
+ :width="svgSize.width"
+ :height="svgSize.height"
+ aria-hidden="true"
+ >
+ <defs>
+ <marker
+ id="erp-link-arrow"
+ markerWidth="10"
+ markerHeight="10"
+ refX="7"
+ refY="3.5"
+ orient="auto"
+ markerUnits="strokeWidth"
+ >
+ <path d="M0,0 L0,7 L7,3.5 z" fill="#94a3b8" />
+ </marker>
+ </defs>
+ <path
+ :d="linkPath"
+ class="link-overlay__path"
+ marker-end="url(#erp-link-arrow)"
+ />
+ </svg>
+
+ <section
+ v-for="row in architectureRows"
+ :key="row.key"
+ class="lane"
+ >
+ <aside class="lane__aside">
+ <svg-icon :icon-class="row.icon" class="lane__icon" />
+ <h2>{{ row.label }}</h2>
+ <span class="lane__arrow"></span>
+ </aside>
+
+ <div class="lane__flow">
+ <div
+ v-for="(item, index) in row.items"
+ :key="item.name"
+ class="node-wrap"
+ >
+ <article
+ class="node"
+ :class="{ 'node--accent': item.accent }"
+ :ref="setNodeRef(item)"
+ >
+ <span class="node__mark">
+ <span class="node__cap"></span>
+ <span class="node__base"></span>
+ <svg-icon :icon-class="item.icon" class="node__icon" />
+ </span>
+ <h3>{{ item.name }}</h3>
+ </article>
+ <span v-if="index < row.items.length - 1" class="flow-arrow"></span>
+ </div>
+ </div>
+ </section>
+ </section>
+ </div>
+</template>
+
+<script setup>
+import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'
+
+const architectureRows = [
+ {
+ key: 'basic',
+ label: '鍩虹閰嶇疆',
+ icon: 'system',
+ items: [
+ { name: '瑙掕壊鐢ㄦ埛绠$悊', icon: 'user' },
+ { name: '浜у搧缁存姢', icon: 'monitor' },
+ { name: '瀹℃壒绠$悊', icon: 'tree', accent: true }
+ ]
+ },
+ {
+ key: 'sale',
+ label: '閿�鍞�',
+ icon: 'chart',
+ items: [
+ { name: '瀹㈡埛妗f', icon: 'peoples' },
+ { name: '閿�鍞姤浠�', icon: 'form' },
+ { name: '閿�鍞彴璐�', icon: 'table' },
+ { name: '鍙戣揣鍙拌处', icon: 'clipboard', accent: true, linkSource: true },
+ { name: '閿�鍞��璐�', icon: 'nested' },
+ { name: '瀹㈡埛寰�鏉�', icon: 'money' },
+ { name: '鎸囨爣缁熻', icon: 'chart' }
+ ]
+ },
+ {
+ key: 'purchase',
+ label: '閲囪喘',
+ icon: 'shopping',
+ items: [
+ { name: '渚涘簲鍟嗘。妗�', icon: 'people' },
+ { name: '閲囪喘鍙拌处', icon: 'table', accent: true },
+ { name: '閲囪喘閫�璐�', icon: 'nested' },
+ { name: '渚涘簲鍟嗗線鏉�', icon: 'money' },
+ { name: '閲囪喘鎶ヨ〃', icon: 'chart' }
+ ]
+ },
+ {
+ key: 'produce',
+ label: '鐢熶骇',
+ icon: 'build',
+ items: [
+ { name: '宸ュ簭', icon: 'tree' },
+ { name: 'BOM', icon: 'list' },
+ { name: '宸ヨ壓璺嚎', icon: 'guide' },
+ { name: '鐢熶骇璁㈠崟', icon: 'form' },
+ { name: '鐢熶骇鎺掍骇', icon: 'date' },
+ { name: '鐢熶骇鎶ュ伐', icon: 'edit' },
+ { name: '鎶ュ伐鍙拌处', icon: 'clipboard' },
+ { name: '鐢熶骇鏍哥畻', icon: 'money', accent: true }
+ ]
+ },
+ {
+ key: 'store',
+ label: '浠撳偍鐗╂祦',
+ icon: 'redis',
+ items: [
+ { name: '鍏ュ簱绠$悊', icon: 'download', accent: true },
+ { name: '鍑哄簱绠$悊', icon: 'upload', accent: true, linkTarget: true },
+ { name: '搴撳瓨绠$悊', icon: 'redis-list' }
+ ]
+ }
+]
+
+const canvasRef = ref(null)
+const sourceNodeRef = ref(null)
+const targetNodeRef = ref(null)
+const linkPath = ref('')
+const svgSize = ref({ width: 0, height: 0 })
+let resizeObserver = null
+
+function setNodeRef(item) {
+ return (el) => {
+ if (item.linkSource) sourceNodeRef.value = el
+ if (item.linkTarget) targetNodeRef.value = el
+ }
+}
+
+function resetLine() {
+ linkPath.value = ''
+}
+
+function updateLinkLine() {
+ if (!canvasRef.value || !sourceNodeRef.value || !targetNodeRef.value || window.innerWidth <= 768) {
+ resetLine()
+ return
+ }
+
+ const canvasRect = canvasRef.value.getBoundingClientRect()
+ const sourceRect = sourceNodeRef.value.getBoundingClientRect()
+ const targetRect = targetNodeRef.value.getBoundingClientRect()
+
+ svgSize.value = {
+ width: Math.ceil(canvasRef.value.scrollWidth),
+ height: Math.ceil(canvasRef.value.scrollHeight)
+ }
+
+ const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left
+ const startY = sourceRect.bottom - canvasRect.top + 2
+ const endX = targetRect.left + targetRect.width / 2 - canvasRect.left
+ const endY = targetRect.top - canvasRect.top - 6
+ const middleY = startY + Math.max(24, (endY - startY) / 2)
+
+ linkPath.value = `M ${startX} ${startY} L ${startX} ${middleY} L ${endX} ${middleY} L ${endX} ${endY}`
+}
+
+function handleResize() {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(updateLinkLine)
+ })
+}
+
+onMounted(async () => {
+ await nextTick()
+ handleResize()
+ window.addEventListener('resize', handleResize)
+ if (window.ResizeObserver && canvasRef.value) {
+ resizeObserver = new ResizeObserver(handleResize)
+ resizeObserver.observe(canvasRef.value)
+ }
+})
+
+onUpdated(() => {
+ nextTick(handleResize)
+})
+
+onBeforeUnmount(() => {
+ window.removeEventListener('resize', handleResize)
+ if (resizeObserver) resizeObserver.disconnect()
+})
+</script>
+
+<style scoped>
+.architecture-page {
+ min-height: calc(100vh - 84px);
+ padding: 18px;
+ background: #f6f7f9;
+}
+
+.canvas {
+ position: relative;
+ display: grid;
+ gap: 10px;
+}
+
+.lane {
+ display: grid;
+ grid-template-columns: 94px 1fr;
+ gap: 10px;
+ align-items: stretch;
+}
+
+.lane__aside {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 8px 6px;
+}
+
+.lane__icon {
+ font-size: 18px;
+ color: #2563eb;
+ flex-shrink: 0;
+}
+
+.lane__aside h2 {
+ margin: 0;
+ width: 34px;
+ font-size: 12px;
+ line-height: 1.15;
+ font-weight: 600;
+ color: #1f2937;
+ text-align: center;
+}
+
+.lane__arrow {
+ position: relative;
+ width: 16px;
+ height: 1px;
+ background: #cbd5e1;
+ flex-shrink: 0;
+}
+
+.lane__arrow::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ right: -1px;
+ width: 6px;
+ height: 6px;
+ border-top: 1px solid #cbd5e1;
+ border-right: 1px solid #cbd5e1;
+ transform: translateY(-50%) rotate(45deg);
+}
+
+.lane__flow {
+ position: relative;
+ z-index: 2;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 14px;
+ background: #f1f3f5;
+}
+
+.node-wrap {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ position: relative;
+}
+
+.node {
+ width: 100px;
+ min-height: 72px;
+ padding: 4px 2px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+}
+
+.node__mark {
+ position: relative;
+ width: 34px;
+ height: 28px;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+}
+
+.node__cap {
+ position: absolute;
+ top: 0;
+ width: 18px;
+ height: 14px;
+ border-radius: 4px 4px 3px 3px;
+ background: linear-gradient(180deg, #60a5fa, #2563eb);
+ box-shadow: 0 3px 6px rgba(37, 99, 235, 0.16);
+}
+
+.node__base {
+ position: absolute;
+ bottom: 0;
+ width: 30px;
+ height: 12px;
+ border-radius: 4px;
+ background: linear-gradient(180deg, #ffffff, #e8edf3);
+ border: 1px solid #d7dee7;
+}
+
+.node__icon {
+ position: relative;
+ z-index: 1;
+ margin-top: 2px;
+ font-size: 15px;
+ color: #ffffff;
+}
+
+.node--accent .node__cap {
+ background: linear-gradient(180deg, #2dd4bf, #0f766e);
+ box-shadow: 0 3px 6px rgba(15, 118, 110, 0.16);
+}
+
+.node h3 {
+ margin: 0;
+ font-size: 12px;
+ line-height: 1.3;
+ font-weight: 600;
+ color: #111827;
+ text-align: center;
+}
+
+.flow-arrow {
+ width: 18px;
+ height: 1px;
+ background: #b6c1ce;
+ position: relative;
+ flex-shrink: 0;
+}
+
+.flow-arrow::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ right: -1px;
+ width: 6px;
+ height: 6px;
+ border-top: 1px solid #94a3b8;
+ border-right: 1px solid #94a3b8;
+ transform: translateY(-50%) rotate(45deg);
+}
+
+.link-overlay {
+ position: absolute;
+ inset: 0;
+ z-index: 20;
+ pointer-events: none;
+ overflow: visible;
+}
+
+.link-overlay__path {
+ fill: none;
+ stroke: #94a3b8;
+ stroke-width: 1;
+ stroke-dasharray: 4 4;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
+
+@media (max-width: 1180px) {
+ .lane {
+ grid-template-columns: 1fr;
+ gap: 6px;
+ }
+
+ .lane__aside {
+ justify-content: flex-start;
+ }
+}
+
+@media (max-width: 768px) {
+ .architecture-page {
+ padding: 12px;
+ }
+
+ .lane__flow {
+ display: grid;
+ gap: 8px;
+ padding: 12px;
+ }
+
+ .node-wrap {
+ display: grid;
+ gap: 8px;
+ }
+
+ .node {
+ width: 100%;
+ min-height: 52px;
+ flex-direction: row;
+ justify-content: flex-start;
+ gap: 10px;
+ }
+
+ .node h3 {
+ text-align: left;
+ }
+
+ .flow-arrow {
+ width: 1px;
+ height: 16px;
+ margin: 0 auto;
+ }
+
+ .flow-arrow::after {
+ top: auto;
+ bottom: -1px;
+ right: 50%;
+ transform: translateX(50%) rotate(135deg);
+ }
+}
+</style>
diff --git a/src/views/tool/build/CodeTypeDialog.vue b/src/views/tool/build/CodeTypeDialog.vue
index de0beb7..1a75789 100644
--- a/src/views/tool/build/CodeTypeDialog.vue
+++ b/src/views/tool/build/CodeTypeDialog.vue
@@ -14,8 +14,8 @@
</el-form>
<template #footer>
- <el-button @click="onClose">鍙栨秷</el-button>
<el-button type="primary" @click="handelConfirm">纭畾</el-button>
+ <el-button @click="onClose">鍙栨秷</el-button>
</template>
</el-dialog>
</template>
diff --git a/vite.config.js b/vite.config.js
index dc687a8..03311be 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -8,11 +8,11 @@
const { VITE_APP_ENV } = env;
const baseUrl =
env.VITE_APP_ENV === "development"
- ? "http://1.15.17.182:9003"
+ ? "http://1.15.17.182:9048"
: env.VITE_BASE_API;
const javaUrl =
env.VITE_APP_ENV === "development"
- ? "http://1.15.17.182:9002"
+ ? "http://1.15.17.182:9049"
: env.VITE_JAVA_API;
return {
define:{
--
Gitblit v1.9.3