From 6e173210a874ff7601aa68eab56e912f5619c79c Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 21 五月 2026 15:32:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_宁夏_英泽防锈' into dev_宁夏_英泽防锈
---
src/assets/styles/index.scss | 87
src/layout/index.vue | 85
multiple/assets/logo/KSLogo.png | 0
src/views/lavorissue/ledger/filesDia.vue | 2
src/api/inventoryManagement/stockInventory.js | 8
src/views/collaborativeApproval/sealManagement/index.vue | 82
multiple/assets/favicon/XLZBico.ico | 0
src/views/salesManagement/invoiceLedger/index.vue | 6
src/assets/images/head.svg | 1
multiple/assets/logo/HYLQLogo.png | 0
src/views/financialManagement/voucher/generalLedger.vue | 382
src/api/login.js | 17
src/settings.js | 4
src/api/basicData/customer.js | 8
src/views/productionManagement/workOrderEdit/index.vue | 2
src/components/Breadcrumb/index.vue | 5
src/views/financialManagement/receivable/salesReturn.vue | 308
src/layout/components/NotificationCenter/index.vue | 19
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue | 9
multiple/assets/favicon/KSfavicon.ico | 0
src/api/viewIndex.js | 69
src/views/procurementManagement/purchaseReturnOrder/New.vue | 92
src/components/filePreview/index.vue | 16
src/views/equipmentManagement/upkeep/Form/PlanModal.vue | 13
src/views/reportAnalysis/dataDashboard/index.vue | 16
src/views/salesManagement/returnOrder/components/detailDia.vue | 260
multiple/assets/favicon/YSJXico.ico | 0
src/assets/styles/variables.module.scss | 193
src/views/aiIndustrialBrain/MAINTAIN_RULES.md | 7
multiple/assets/logo/DZZBLogo.png | 0
multiple/assets/favicon/XCDQfavicon.ico | 0
src/views/financialManagement/voucher/detailLedger.vue | 434
src/components/AIChatSidebar/assistants/salesAssistant.js | 28
src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue | 9
multiple/assets/favicon/YTJZfavicon.ico | 0
src/views/equipmentManagement/inspectionManagement/index.vue | 58
multiple/assets/logo/WTXCLogo.png | 0
multiple/assets/logo/KYHGLogo.png | 0
src/views/equipmentManagement/upkeep/Form/formDia.vue | 34
src/plugins/download.js | 10
multiple/assets/favicon/JXJHfavicon.ico | 0
src/views/productionManagement/productionProcess/index.vue | 36
src/views/financialManagement/receivable/salesOut.vue | 272
src/assets/AI/待办助手.png | 0
src/api/procurementManagement/purchase_return_order.js | 9
src/api/salesManagement/deliveryLedger.js | 9
src/views/salesManagement/returnOrder/index.vue | 24
multiple/assets/favicon/HYLQfavicon.ico | 0
src/layout/components/Sidebar/Logo.vue | 94
src/assets/AI/采购助手.png | 0
multiple/assets/logo/SDJCLogo.png | 0
src/views/inventoryManagement/receiptManagement/Record.vue | 175
src/views/equipmentManagement/upkeep/index.vue | 38
src/views/collaborativeApproval/shipmentReview/fileList.vue | 3
src/views/salesManagement/deliveryLedger/index.vue | 654
src/views/equipmentManagement/operationManagement/index.vue | 132
src/views/procurementManagement/procurementLedger/fileList.vue | 3
src/assets/AI/生产助手.png | 0
src/views/personnelManagement/employeeRecord/index.vue | 65
src/views/aiIndustrialBrain/index.vue | 1500 ++
multiple/assets/favicon/JHYfavicon.ico | 0
multiple/assets/logo/BTYXLogo.png | 0
src/router/index.js | 94
src/views/basicData/supplierManage/filesDia.vue | 2
multiple/assets/logo/QXYLogo.png | 0
multiple/assets/favicon/DZZBfavicon.ico | 0
src/views/qualityManagement/nonconformingManagement/components/formDia.vue | 22
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue | 86
src/views/qualityManagement/processInspection/components/filesDia.vue | 2
src/components/PurchaseAIChatSidebar/index.vue | 20
src/views/customerService/feedbackRegistration/components/formDia.vue | 895
src/components/AIChatSidebar/assistants/index.js | 15
src/views/inventoryManagement/dispatchLog/Record.vue | 953
multiple/assets/logo/XLZBLogo.png | 0
multiple/assets/logo/JXJHLogo.png | 0
src/views/procurementManagement/purchaseReturnOrder/index.vue | 489
src/components/HeaderSearch/index.vue | 83
multiple/assets/logo/YTJZLogo.png | 0
src/api/financialManagement/accountPurchase.js | 19
src/views/basicData/supplierManage/components/HomeTab.vue | 21
src/views/productionManagement/productionCosting/index.vue | 5
multiple/assets/favicon/WTXCfavicon.ico | 0
multiple/assets/logo/YSJXLogo.png | 0
src/views/productionManagement/productStructure/Detail/index.vue | 115
src/views/login.vue | 1040 +
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue | 9
package.json | 3
multiple/assets/favicon/ZXZNfavicon.ico | 0
src/views/personnelManagement/contractManagement/filesDia.vue | 2
src/views/basicData/supplierManage/components/BlacklistTab.vue | 21
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue | 9
multiple/assets/favicon/ZQSYfavicon.ico | 0
src/views/basicData/product/index.vue | 91
src/views/financialManagement/generalLedger/index.vue | 671
src/assets/AI/质量助手.png | 0
src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue | 144
src/views/qualityManagement/rawMaterialInspection/index.vue | 26
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue | 9
src/views/qualityManagement/finalInspection/index.vue | 21
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue | 9
src/views/qualityManagement/processInspection/index.vue | 25
multiple/assets/favicon/KYHGfavicon.ico | 0
src/views/customerService/expiryAfterSales/components/formDia.vue | 92
src/views/productionManagement/workOrderManagement/index.vue | 40
multiple/assets/logo/ZQSYLogo.png | 0
src/views/reportAnalysis/productionAnalysis/components/right-top.vue | 9
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 36
multiple/assets/logo/JHHGLogo.png | 0
src/views/productionManagement/productionTraceability/index.vue | 27
src/components/Dialog/FileList.vue | 70
src/views/equipmentManagement/ledger/index.vue | 29
multiple/assets/logo/JHYLogo.png | 0
src/api/financialManagement/accountSubject.js | 46
src/store/modules/user.js | 92
multiple/assets/screen/login-background.png | 0
multiple/multiple-build.js | 208
src/assets/aiIndustrialBrain/reference-chat.png | 0
multiple/assets/favicon/YZFXfavicon.ico | 0
src/utils/theme.js | 45
src/views/financialManagement/assets/fixedAssets.vue | 184
src/components/AIChatSidebar/assistants/generalAssistant.js | 33
src/components/Hamburger/index.vue | 40
src/api/financialManagement/intangibleAsset.js | 50
src/views/collaborativeApproval/approvalProcess/fileList.vue | 3
src/views/inventoryManagement/stockReport/index.vue | 1251 +-
src/views/qualityManagement/nonconformingManagement/index.vue | 2
multiple/assets/favicon/HYZCfavicon.ico | 0
src/views/productionManagement/processRoute/index.vue | 4
multiple/assets/favicon/JHHGfavicon.ico | 0
src/api/financialManagement/voucher.js | 54
src/views/productionManagement/processRoute/processRouteItem/index.vue | 284
src/views/collaborativeApproval/approvalProcess/index.vue | 23
src/views/financialManagement/voucher/index.vue | 534
src/views/salesManagement/salesLedger/index.vue | 3741 +++---
src/views/reportAnalysis/PSIDataAnalysis/index.vue | 16
multiple/assets/favicon/QXYfavicon.ico | 0
src/layout/components/Sidebar/index.vue | 242
src/views/financialManagement/assets/intangibleAssets.vue | 192
src/views/inventoryManagement/stockManagement/Record.vue | 402
src/views/salesManagement/salesLedger/fileList.vue | 2
src/assets/AI/销售助手.png | 0
multiple/assets/logo/HYZCLogo.png | 0
src/assets/styles/sidebar.scss | 765 +
src/api/financialManagement/fixedAsset.js | 50
src/assets/styles/element-ui.scss | 60
src/views/basicData/customerFileOpenSea/index.vue | 2
src/layout/components/TagsView/index.vue | 34
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue | 5
src/views/qualityManagement/finalInspection/components/filesDia.vue | 2
src/utils/request.js | 25
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue | 9
src/api/equipmentManagement/repair.js | 13
src/views/customerService/expiryAfterSales/index.vue | 6
src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue | 227
src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue | 198
multiple/assets/favicon/DYKJfavicon.ico | 0
src/layout/components/AppMain.vue | 47
src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue | 27
multiple/assets/favicon/SDJCfavicon.ico | 0
src/views/financialManagement/payable/purchaseReturn.vue | 239
multiple/assets/favicon/HYJCfavicon.ico | 0
src/views/index.vue | 3212 ++++-
src/views/reportAnalysis/productionAnalysis/index.vue | 16
src/views/equipmentManagement/measurementEquipment/components/formDia.vue | 34
src/views/reportAnalysis/productionAnalysis/components/left-top.vue | 9
src/api/financialManagement/ledger.js | 19
src/views/personnelManagement/contractManagement/index.vue | 22
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue | 9
src/views/basicData/customerFile/index.vue | 37
multiple/assets/logo/JXSMLogo.png | 0
public/favicon.ico | 0
src/views/procurementManagement/purchaseReturnOrder/ProductList.vue | 91
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue | 680 +
src/views/qualityManagement/processInspection/components/formDia.vue | 932
src/components/AIChatSidebar/assistants/productionAssistant.js | 27
src/views/equipmentManagement/repair/Modal/RepairModal.vue | 38
src/assets/AI/财务助手.png | 0
src/views/salesManagement/salesQuotation/index.vue | 77
FINANCIAL_MANAGEMENT_BACKEND_SPEC.md | 233
src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue | 9
src/views/reportAnalysis/productionAnalysis/components/center-top.vue | 9
src/views/procurementManagement/procurementLedger/index.vue | 1
src/views/equipmentManagement/measurementEquipment/index.vue | 48
src/views/productionManagement/productionReporting/index.vue | 5
src/views/financialManagement/payable/purchaseIn.vue | 326
src/layout/components/TagsView/ScrollPane.vue | 10
src/views/productionManagement/productionOrder/index.vue | 7
src/views/safeProduction/safeWorkApproval/fileList.vue | 2
src/api/financialManagement/accountSales.js | 19
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue | 357
src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue | 9
src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue | 6
multiple/assets/logo/XCDQLogo.png | 0
multiple/assets/logo/XSWHLogo.png | 0
src/views/projectManagement/projectType/index.vue | 9
multiple/config.json | 24
src/components/AIChatSidebar/assistants/purchaseAssistant.js | 25
src/views/productionManagement/workOrderManagement/components/filesDia.vue | 2
src/layout/components/Navbar.vue | 488
src/views/equipmentManagement/inspectionManagement/components/formDia.vue | 19
src/components/ProcessParamListDialog.vue | 34
multiple/assets/favicon/JXSMico.ico | 0
src/views/reportAnalysis/productionAnalysis/components/center-center.vue | 9
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue | 852
multiple/assets/favicon/favicon.ico | 0
src/views/equipmentManagement/measurementEquipment/filesDia.vue | 2
src/views/salesManagement/returnOrder/components/formDia.vue | 234
src/views/projectManagement/Management/components/formDia.vue | 4
src/assets/AI/仓储助手.png | 0
src/layout/components/Sidebar/SidebarItem.vue | 66
multiple/assets/favicon/XSWHfavicon.ico | 0
multiple/assets/logo/Logo.png | 0
src/assets/aiIndustrialBrain/reference-cards.png | 0
src/views/qualityManagement/finalInspection/components/formDia.vue | 180
src/views/equipmentManagement/repair/index.vue | 36
src/store/modules/permission.js | 35
src/assets/logo/logo.png | 0
src/components/AIChatSidebar/index.vue | 2740 ++++
src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue | 2
multiple/assets/logo/DYKJLogo.png | 0
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue | 335
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue | 9
/dev/null | 565 -
src/views/personnelManagement/dimission/components/formDia.vue | 17
multiple/assets/logo/ZXZNLogo.png | 0
src/views/productionManagement/workOrder/components/filesDia.vue | 2
src/views/systemArchitecture/index.vue | 961 +
src/views/productionPlan/productionPlan/index.vue | 22
src/views/projectManagement/Management/projectDetail.vue | 12
multiple/assets/logo/HYJCLogo.png | 0
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue | 9
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue | 44
multiple/assets/favicon/BTYXfavicon.ico | 0
233 files changed, 21,742 insertions(+), 10,878 deletions(-)
diff --git a/FINANCIAL_MANAGEMENT_BACKEND_SPEC.md b/FINANCIAL_MANAGEMENT_BACKEND_SPEC.md
new file mode 100644
index 0000000..ee7e5b7
--- /dev/null
+++ b/FINANCIAL_MANAGEMENT_BACKEND_SPEC.md
@@ -0,0 +1,233 @@
+# 璐㈠姟绠$悊鍚庣鏂囨。锛堜粎璐熻矗妯″潡锛�
+
+鏇存柊鏃堕棿锛�2026-05-12
+閫傜敤鑼冨洿锛堜粎浠ヤ笅 6 涓ā鍧楋級锛�
+1. 鍥哄畾璧勪骇锛坄/financial/fixed-assets`锛�
+2. 鏃犲舰璧勪骇锛坄/financial/intangible-assets`锛�
+3. 鎬昏处绉戠洰锛坄/financial/general-ledger`锛�
+4. 鍑瘉锛坄/financial/voucher`锛�
+5. 绉戠洰鎬昏处锛坄/financial/voucher-general-ledger`锛�
+6. 绉戠洰鏄庣粏璐︼紙`/financial/voucher-detail-ledger`锛�
+
+---
+
+## 1. 缁熶竴绾﹀畾
+
+### 1.1 鍝嶅簲缁撴瀯
+```json
+{
+ "code": 200,
+ "msg": "success",
+ "data": {}
+}
+```
+
+### 1.2 鍒嗛〉缁撴瀯锛堝鏋滄槸鍒嗛〉鎺ュ彛锛�
+璇锋眰鍙傛暟寤鸿锛�
+- `current`锛堥〉鐮侊級
+- `size`锛堟瘡椤垫潯鏁帮級
+
+鍝嶅簲寤鸿锛�
+```json
+{
+ "code": 200,
+ "data": {
+ "records": [],
+ "total": 0
+ }
+}
+```
+
+### 1.3 閲戦涓庣簿搴�
+- 閲戦瀛楁寤鸿 `decimal(18,2)`銆�
+- 鍓嶅悗绔粺涓�淇濈暀涓や綅灏忔暟銆�
+
+---
+
+## 2. 妯″潡涓�锛氭�昏处绉戠洰锛堝凡鎺ョ湡瀹� API锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/generalLedger/index.vue`
+API 鏂囦欢锛歚src/api/financialManagement/accountSubject.js`
+
+### 2.1 鎺ュ彛鐜扮姸
+- `GET /accountSubject/list`
+- `POST /accountSubject/add`
+- `PUT /accountSubject/edit`
+- `DELETE /accountSubject/remove/{ids}`
+- `POST /accountSubject/export`
+
+### 2.2 瀛楁妯″瀷
+- `id`
+- `subjectCode`锛堢鐩紪鐮侊級
+- `subjectName`锛堢鐩悕绉帮級
+- `subjectType`锛堢鐩被鍨嬶級
+- `balanceDirection`锛堜綑棰濇柟鍚戯細鍊熸柟/璐锋柟锛�
+- `status`锛�0 鍚敤锛�1 绂佺敤锛�
+- `remark`
+
+### 2.3 涓氬姟瑙勫垯
+- `subjectCode`銆乣subjectName`銆乣subjectType` 蹇呭~銆�
+- 鍒犻櫎闇�瑕佸仛寮曠敤鏍¢獙锛堣嫢宸茶鍑瘉鍒嗗綍寮曠敤锛屼笉鍏佽鍒犻櫎锛夈��
+
+---
+
+## 3. 妯″潡浜岋細鍥哄畾璧勪骇锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/assets/fixedAssets.vue`
+
+### 3.1 寤鸿鎺ュ彛
+- `GET /financial/fixedAsset/page`
+- `POST /financial/fixedAsset/add`
+- `PUT /financial/fixedAsset/update`
+- `DELETE /financial/fixedAsset/delete`
+- `POST /financial/fixedAsset/depreciate`锛堟寜鏈堣鎻愶級
+
+### 3.2 瀛楁妯″瀷
+- `id, assetCode, assetName, category, specification`
+- `purchaseDate, originalValue, usefulLife, residualRate`
+- `accumulatedDepreciation, netValue`
+- `location, department, keeper, status, remark`
+
+### 3.3 鏍稿績鍏紡锛堝繀椤讳竴鑷达級
+- `monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12)`
+- `accumulatedDepreciation += monthlyDepreciation`
+- `netValue = originalValue - accumulatedDepreciation`
+
+### 3.4 鐘舵�佸缓璁�
+- `in_use`锛堝湪鐢級
+- `idle`锛堥棽缃級
+- `repair`锛堢淮淇腑锛�
+- `scrapped`锛堟姤搴燂級
+
+---
+
+## 4. 妯″潡涓夛細鏃犲舰璧勪骇锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/assets/intangibleAssets.vue`
+
+### 4.1 寤鸿鎺ュ彛
+- `GET /financial/intangibleAsset/page`
+- `POST /financial/intangibleAsset/add`
+- `PUT /financial/intangibleAsset/update`
+- `DELETE /financial/intangibleAsset/delete`
+- `POST /financial/intangibleAsset/amortize`锛堟寜鏈堟憡閿�锛�
+
+### 4.2 瀛楁妯″瀷
+- `id, assetCode, assetName, category, certificateNo`
+- `acquisitionDate, originalValue, amortizationPeriod, residualRate`
+- `accumulatedAmortization, netValue`
+- `validityDate, status, description, remark`
+
+### 4.3 鏍稿績鍏紡锛堝繀椤讳竴鑷达級
+- `monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)`
+- `accumulatedAmortization += monthlyAmortization`
+- `netValue = originalValue - accumulatedAmortization`
+- 褰� `netValue <= 0`锛�
+ - `netValue = 0`
+ - `status = amortized`
+
+### 4.4 鐘舵�佸缓璁�
+- `in_use`锛堝湪鐢級
+- `expired`锛堝埌鏈燂級
+- `amortized`锛堝凡鎽婇攢瀹岋級
+
+---
+
+## 5. 妯″潡鍥涳細鍑瘉锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/voucher/index.vue`
+
+### 5.1 寤鸿鎺ュ彛
+- `GET /financial/voucher/page`
+- `POST /financial/voucher/add`
+- `PUT /financial/voucher/update`
+- `POST /financial/voucher/post`锛堣繃璐︼級
+- `POST /financial/voucher/cancel`锛堜綔搴燂級
+- `GET /financial/voucher/detail/{id}`
+
+### 5.2 涓昏〃瀛楁
+- `id, voucherNo, voucherDate, summary`
+- `debit, credit, creator, status, attachmentCount, remark`
+
+### 5.3 鍒嗗綍瀛楁
+- `subjectCode, subjectName, summary, debit, credit`
+
+### 5.4 鍏抽敭鏍¢獙
+- 鍒嗗綍鑷冲皯涓�鏉℃湁鏁堣锛堢鐩笉绌猴紝涓斿�熸柟鎴栬捶鏂� > 0锛夈��
+- 鍊熻捶骞宠 锛歚sum(debit) == sum(credit)` 涓� > 0锛屼笉婊¤冻绂佹淇濆瓨銆�
+
+### 5.5 鐘舵�佹祦杞�
+- `unposted -> posted`
+- `unposted -> cancelled`
+
+---
+
+## 6. 妯″潡浜旓細绉戠洰鎬昏处锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/voucher/generalLedger.vue`
+
+### 6.1 寤鸿鎺ュ彛
+- `GET /financial/ledger/general`
+
+### 6.2 璇锋眰鍙傛暟
+- `subjectCode`锛堟湯绾ф垨鎸囧畾绉戠洰锛�
+- `startMonth`锛圷YYY-MM锛�
+- `endMonth`锛圷YYY-MM锛�
+
+### 6.3 鍝嶅簲瀛楁
+- `date, voucherNo, summary`
+- `debit, credit, direction, balance`
+
+### 6.4 瑙勫垯
+- 浠呭湪閫夋嫨绉戠洰鍚庤繑鍥炴暟鎹��
+- 鏀寔鈥滄湡鍒濅綑棰� / 鏈湀鍚堣 / 鏈勾绱鈥濊锛堝彲閫氳繃 `rowType` 瀛楁鍖哄垎锛夈��
+
+---
+
+## 7. 妯″潡鍏細绉戠洰鏄庣粏璐︼紙褰撳墠鍓嶇涓� mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/voucher/detailLedger.vue`
+
+### 7.1 寤鸿鎺ュ彛
+- `GET /financial/ledger/detail`
+
+### 7.2 璇锋眰鍙傛暟
+- `subjectCode`
+- `auxiliaryType`锛坈ustomer/supplier/department/employee/project锛�
+- `auxiliaryId`
+- `startMonth`锛圷YYY-MM锛�
+- `endMonth`锛圷YYY-MM锛�
+
+### 7.3 鍝嶅簲瀛楁
+- `date, voucherNo, summary`
+- `debit, credit, direction, balance`
+
+### 7.4 瑙勫垯
+- 鍏堥�夌鐩紝鍐嶆煡鏄庣粏銆�
+- 杈呭姪鏍哥畻鏉′欢涓哄彲閫夛紝浣嗗缓璁悗绔敮鎸佺淮搴﹁繃婊ゃ��
+
+---
+
+## 8. 鎺ㄨ崘鏈�灏忚〃璁捐锛堜粎鏈寖鍥达級
+
+- `fin_account_subject`
+- `fin_fixed_asset`
+- `fin_intangible_asset`
+- `fin_voucher`
+- `fin_voucher_entry`
+- `fin_ledger_snapshot_general`锛堝彲閫夛紝鍋氭�ц兘浼樺寲锛�
+- `fin_ledger_snapshot_detail`锛堝彲閫夛紝鍋氭�ц兘浼樺寲锛�
+
+---
+
+## 9. AI 鐢熸垚鍚庣浠诲姟椤哄簭锛堝缓璁級
+
+1. 鍏堝畬鎴� **鎬昏处绉戠洰**锛堝凡鏈� API锛屾渶绋冲畾锛夈��
+2. 瀹屾垚 **鍑瘉 + 鍒嗗綍 + 鍊熻捶骞宠 鏍¢獙 + 鐘舵�佹祦杞�**銆�
+3. 瀹炵幇 **绉戠洰鎬昏处 / 绉戠洰鏄庣粏璐�** 鏌ヨ銆�
+4. 瀹炵幇 **鍥哄畾璧勪骇鎶樻棫** 涓� **鏃犲舰璧勪骇鎽婇攢**銆�
+5. 琛ユ祴璇曪細
+ - 鍊熻捶骞宠 鏍¢獙
+ - 鎶樻棫/鎽婇攢鍏紡
+ - 绉戠洰琚紩鐢ㄧ姝㈠垹闄�
+
diff --git a/multiple/assets/favicon/BTYXfavicon.ico b/multiple/assets/favicon/BTYXfavicon.ico
new file mode 100644
index 0000000..8da0f2a
--- /dev/null
+++ b/multiple/assets/favicon/BTYXfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/DYKJfavicon.ico b/multiple/assets/favicon/DYKJfavicon.ico
new file mode 100644
index 0000000..8437010
--- /dev/null
+++ b/multiple/assets/favicon/DYKJfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/DZZBfavicon.ico b/multiple/assets/favicon/DZZBfavicon.ico
new file mode 100644
index 0000000..63bf216
--- /dev/null
+++ b/multiple/assets/favicon/DZZBfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/HYJCfavicon.ico b/multiple/assets/favicon/HYJCfavicon.ico
new file mode 100644
index 0000000..e52f096
--- /dev/null
+++ b/multiple/assets/favicon/HYJCfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/HYLQfavicon.ico b/multiple/assets/favicon/HYLQfavicon.ico
new file mode 100644
index 0000000..9a2dbf3
--- /dev/null
+++ b/multiple/assets/favicon/HYLQfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/HYZCfavicon.ico b/multiple/assets/favicon/HYZCfavicon.ico
new file mode 100644
index 0000000..996ab43
--- /dev/null
+++ b/multiple/assets/favicon/HYZCfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/JHHGfavicon.ico b/multiple/assets/favicon/JHHGfavicon.ico
new file mode 100644
index 0000000..54bbe64
--- /dev/null
+++ b/multiple/assets/favicon/JHHGfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/JHYfavicon.ico b/multiple/assets/favicon/JHYfavicon.ico
new file mode 100644
index 0000000..f818f6f
--- /dev/null
+++ b/multiple/assets/favicon/JHYfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/JXJHfavicon.ico b/multiple/assets/favicon/JXJHfavicon.ico
new file mode 100644
index 0000000..0c90589
--- /dev/null
+++ b/multiple/assets/favicon/JXJHfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/JXSMico.ico b/multiple/assets/favicon/JXSMico.ico
new file mode 100644
index 0000000..f5681e8
--- /dev/null
+++ b/multiple/assets/favicon/JXSMico.ico
Binary files differ
diff --git a/multiple/assets/favicon/KSfavicon.ico b/multiple/assets/favicon/KSfavicon.ico
new file mode 100644
index 0000000..57ebd54
--- /dev/null
+++ b/multiple/assets/favicon/KSfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/KYHGfavicon.ico b/multiple/assets/favicon/KYHGfavicon.ico
new file mode 100644
index 0000000..8178896
--- /dev/null
+++ b/multiple/assets/favicon/KYHGfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/PCDZico.ico b/multiple/assets/favicon/PCDZico.ico
deleted file mode 100644
index 1d74c17..0000000
--- a/multiple/assets/favicon/PCDZico.ico
+++ /dev/null
Binary files differ
diff --git a/multiple/assets/favicon/QXYfavicon.ico b/multiple/assets/favicon/QXYfavicon.ico
new file mode 100644
index 0000000..e56847e
--- /dev/null
+++ b/multiple/assets/favicon/QXYfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/SDJCfavicon.ico b/multiple/assets/favicon/SDJCfavicon.ico
new file mode 100644
index 0000000..5e80af3
--- /dev/null
+++ b/multiple/assets/favicon/SDJCfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/WTXCfavicon.ico b/multiple/assets/favicon/WTXCfavicon.ico
new file mode 100644
index 0000000..ce783dc
--- /dev/null
+++ b/multiple/assets/favicon/WTXCfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/XCDQfavicon.ico b/multiple/assets/favicon/XCDQfavicon.ico
new file mode 100644
index 0000000..69a2280
--- /dev/null
+++ b/multiple/assets/favicon/XCDQfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/XLZBico.ico b/multiple/assets/favicon/XLZBico.ico
new file mode 100644
index 0000000..63ff233
--- /dev/null
+++ b/multiple/assets/favicon/XLZBico.ico
Binary files differ
diff --git a/multiple/assets/favicon/XSWHfavicon.ico b/multiple/assets/favicon/XSWHfavicon.ico
new file mode 100644
index 0000000..f6cd72d
--- /dev/null
+++ b/multiple/assets/favicon/XSWHfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/YSJXico.ico b/multiple/assets/favicon/YSJXico.ico
new file mode 100644
index 0000000..ce3c0c3
--- /dev/null
+++ b/multiple/assets/favicon/YSJXico.ico
Binary files differ
diff --git a/multiple/assets/favicon/YTJZfavicon.ico b/multiple/assets/favicon/YTJZfavicon.ico
new file mode 100644
index 0000000..ad7b03b
--- /dev/null
+++ b/multiple/assets/favicon/YTJZfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/YZFXfavicon.ico b/multiple/assets/favicon/YZFXfavicon.ico
new file mode 100644
index 0000000..1a2e256
--- /dev/null
+++ b/multiple/assets/favicon/YZFXfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/ZQSYfavicon.ico b/multiple/assets/favicon/ZQSYfavicon.ico
new file mode 100644
index 0000000..9e3422f
--- /dev/null
+++ b/multiple/assets/favicon/ZQSYfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/ZXZNfavicon.ico b/multiple/assets/favicon/ZXZNfavicon.ico
new file mode 100644
index 0000000..73bc9dd
--- /dev/null
+++ b/multiple/assets/favicon/ZXZNfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/favicon.ico b/multiple/assets/favicon/favicon.ico
index e263760..2fa05d5 100644
--- a/multiple/assets/favicon/favicon.ico
+++ b/multiple/assets/favicon/favicon.ico
Binary files differ
diff --git a/multiple/assets/logo/BTYXLogo.png b/multiple/assets/logo/BTYXLogo.png
new file mode 100644
index 0000000..0890259
--- /dev/null
+++ b/multiple/assets/logo/BTYXLogo.png
Binary files differ
diff --git a/multiple/assets/logo/DYKJLogo.png b/multiple/assets/logo/DYKJLogo.png
new file mode 100644
index 0000000..b96b61c
--- /dev/null
+++ b/multiple/assets/logo/DYKJLogo.png
Binary files differ
diff --git a/multiple/assets/logo/DZZBLogo.png b/multiple/assets/logo/DZZBLogo.png
new file mode 100644
index 0000000..5b3f56b
--- /dev/null
+++ b/multiple/assets/logo/DZZBLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HYJCLogo.png b/multiple/assets/logo/HYJCLogo.png
new file mode 100644
index 0000000..367f64d
--- /dev/null
+++ b/multiple/assets/logo/HYJCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HYLQLogo.png b/multiple/assets/logo/HYLQLogo.png
new file mode 100644
index 0000000..8eda2f7
--- /dev/null
+++ b/multiple/assets/logo/HYLQLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HYZCLogo.png b/multiple/assets/logo/HYZCLogo.png
new file mode 100644
index 0000000..e8e4cad
--- /dev/null
+++ b/multiple/assets/logo/HYZCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JHHGLogo.png b/multiple/assets/logo/JHHGLogo.png
new file mode 100644
index 0000000..3373e60
--- /dev/null
+++ b/multiple/assets/logo/JHHGLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JHYLogo.png b/multiple/assets/logo/JHYLogo.png
new file mode 100644
index 0000000..edf5921
--- /dev/null
+++ b/multiple/assets/logo/JHYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JXJHLogo.png b/multiple/assets/logo/JXJHLogo.png
new file mode 100644
index 0000000..1d47288
--- /dev/null
+++ b/multiple/assets/logo/JXJHLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JXSMLogo.png b/multiple/assets/logo/JXSMLogo.png
new file mode 100644
index 0000000..f1da7e9
--- /dev/null
+++ b/multiple/assets/logo/JXSMLogo.png
Binary files differ
diff --git a/multiple/assets/logo/KSLogo.png b/multiple/assets/logo/KSLogo.png
new file mode 100644
index 0000000..39d52a0
--- /dev/null
+++ b/multiple/assets/logo/KSLogo.png
Binary files differ
diff --git a/multiple/assets/logo/KYHGLogo.png b/multiple/assets/logo/KYHGLogo.png
new file mode 100644
index 0000000..0186c87
--- /dev/null
+++ b/multiple/assets/logo/KYHGLogo.png
Binary files differ
diff --git a/multiple/assets/logo/Logo.png b/multiple/assets/logo/Logo.png
new file mode 100644
index 0000000..a5831b8
--- /dev/null
+++ b/multiple/assets/logo/Logo.png
Binary files differ
diff --git a/multiple/assets/logo/PCDZLogo.png b/multiple/assets/logo/PCDZLogo.png
deleted file mode 100644
index b296504..0000000
--- a/multiple/assets/logo/PCDZLogo.png
+++ /dev/null
Binary files differ
diff --git a/multiple/assets/logo/QXYLogo.png b/multiple/assets/logo/QXYLogo.png
new file mode 100644
index 0000000..45318ed
--- /dev/null
+++ b/multiple/assets/logo/QXYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/SDJCLogo.png b/multiple/assets/logo/SDJCLogo.png
new file mode 100644
index 0000000..139e398
--- /dev/null
+++ b/multiple/assets/logo/SDJCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/WTXCLogo.png b/multiple/assets/logo/WTXCLogo.png
new file mode 100644
index 0000000..0a486cd
--- /dev/null
+++ b/multiple/assets/logo/WTXCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/XCDQLogo.png b/multiple/assets/logo/XCDQLogo.png
new file mode 100644
index 0000000..d10580a
--- /dev/null
+++ b/multiple/assets/logo/XCDQLogo.png
Binary files differ
diff --git a/multiple/assets/logo/XLZBLogo.png b/multiple/assets/logo/XLZBLogo.png
new file mode 100644
index 0000000..d79c119
--- /dev/null
+++ b/multiple/assets/logo/XLZBLogo.png
Binary files differ
diff --git a/multiple/assets/logo/XSWHLogo.png b/multiple/assets/logo/XSWHLogo.png
new file mode 100644
index 0000000..d0c32f5
--- /dev/null
+++ b/multiple/assets/logo/XSWHLogo.png
Binary files differ
diff --git a/multiple/assets/logo/YSJXLogo.png b/multiple/assets/logo/YSJXLogo.png
new file mode 100644
index 0000000..945463b
--- /dev/null
+++ b/multiple/assets/logo/YSJXLogo.png
Binary files differ
diff --git a/multiple/assets/logo/YTJZLogo.png b/multiple/assets/logo/YTJZLogo.png
new file mode 100644
index 0000000..ea0c489
--- /dev/null
+++ b/multiple/assets/logo/YTJZLogo.png
Binary files differ
diff --git a/multiple/assets/logo/ZQSYLogo.png b/multiple/assets/logo/ZQSYLogo.png
new file mode 100644
index 0000000..fe865b6
--- /dev/null
+++ b/multiple/assets/logo/ZQSYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/ZXZNLogo.png b/multiple/assets/logo/ZXZNLogo.png
new file mode 100644
index 0000000..83df55b
--- /dev/null
+++ b/multiple/assets/logo/ZXZNLogo.png
Binary files differ
diff --git a/multiple/assets/screen/PCDZView.png b/multiple/assets/screen/PCDZView.png
deleted file mode 100644
index e8574c8..0000000
--- a/multiple/assets/screen/PCDZView.png
+++ /dev/null
Binary files differ
diff --git a/multiple/assets/screen/login-background.png b/multiple/assets/screen/login-background.png
index ace9d53..b935d71 100644
--- a/multiple/assets/screen/login-background.png
+++ b/multiple/assets/screen/login-background.png
Binary files differ
diff --git a/multiple/config.json b/multiple/config.json
index 1ccf350..a8b8df8 100644
--- a/multiple/config.json
+++ b/multiple/config.json
@@ -3,31 +3,27 @@
"env": {
"VITE_APP_TITLE": "鑺浜戯紙绠$悊淇℃伅绯荤粺锛�"
},
- "screen": "screen/PCDZView.png",
- "logo": "logo/PCDZLogo.png",
- "favicon": "favicon/PCDZico.ico"
+ "logo": "logo/Logo.png",
+ "favicon": "favicon/favicon.ico"
},
"TEST": {
"env": {
- "VITE_APP_TITLE": "淇℃伅绠$悊",
- "VITE_BASE_API": "http://1.15.17.182:9003",
- "VITE_JAVA_API": "http://1.15.17.182:9002"
+ "VITE_APP_TITLE": "宸ュ巶鏁板瓧鍖朚OM绯荤粺",
+ "VITE_BASE_API": "http://1.15.17.182:9048",
+ "VITE_JAVA_API": "http://1.15.17.182:9049"
},
- "screen": "screen/login-background.png",
- "logo": "logo/PCDZLogo.png",
+ "logo": "logo/Logo.png",
"favicon": "favicon/favicon.ico"
},
"YZFX": {
"env": {
- "VITE_APP_TITLE": "鑻辨辰闃查攬鏂版潗鏂欎俊鎭鐞�",
- "VITE_BASE_API": "http://1.15.17.182:9021",
- "VITE_JAVA_API": "http://1.15.17.182:9020"
+ "VITE_APP_TITLE": "瀹佸鑻辨辰闃查攬鏂版潗鏂欐湁闄愬叕鍙�",
+ "VITE_BASE_API": "http://42.63.70.231:9000",
+ "VITE_JAVA_API": "http://42.63.70.231:9001"
},
- "screen": "screen/login-background.png",
"logo": "logo/YZFXLogo.png",
- "favicon": "favicon/YZFXico.ico"
+ "favicon": "favicon/YZFXfavicon.ico"
},
- "screen": "/src/assets/images/login-background.png",
"logo": "/src/assets/logo/logo.png",
"favicon": "/public/favicon.ico"
}
diff --git a/multiple/multiple-build.js b/multiple/multiple-build.js
index 8e078e1..afcd4d5 100644
--- a/multiple/multiple-build.js
+++ b/multiple/multiple-build.js
@@ -1,86 +1,152 @@
-import fs from 'fs/promises';
-import fsSync from 'fs';
-import path from 'path';
-import { fileURLToPath } from 'url';
+import fs from "fs/promises";
+import fsSync from "fs";
+import path from "path";
+import { fileURLToPath } from "url";
import { execSync } from "child_process";
-// 鑾峰彇 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
-// 璇诲彇 JSON 閰嶇疆
-const data = await fs.readFile(path.join(__dirname, 'config.json'), 'utf-8');
+const data = await fs.readFile(path.join(__dirname, "config.json"), "utf-8");
const config = JSON.parse(data);
-// 椤圭洰璺緞
-const rootPath = path.resolve(__dirname, '..');
-const resourcePath = path.join(rootPath, 'multiple', 'assets');
-const replacePath = path.join(rootPath, 'replace');
+const rootPath = path.resolve(__dirname, "..");
+const resourcePath = path.join(rootPath, "multiple", "assets");
+const replacePath = path.join(rootPath, "replace");
+const envFilePath = path.join(rootPath, ".env.production.local");
-// 鑾峰彇鍛戒护琛屽弬鏁�
const params = parseArgs(process.argv);
-const company = params["company"] ?? "default";
+const company = resolveCompany(params);
const companyMap = config[company];
-const envFilePath = path.join(process.cwd(), '.env.production.local');
-
-try {
- // 1锔忊儯 鐢熸垚 .env
- console.log("=======鐢熸垚.env=======");
- const envContent = Object.entries(companyMap.env)
- .map(([key, value]) => `${key}='${value}'`)
- .join('\n') + '\n';
- await fs.writeFile(envFilePath, envContent, 'utf-8');
-
- // 2锔忊儯 澶囦唤鍘熷璧勬簮骞舵浛鎹�
- console.log("=======淇敼璧勬簮=======");
- for (const [key, value] of Object.entries(companyMap)) {
- if (key === 'env') continue;
-
- const originFile = path.join(rootPath, config[key]);
- const backupFile = path.join(replacePath, config[key]);
- const replaceFile = path.join(resourcePath, companyMap[key]);
-
- await fs.mkdir(path.dirname(backupFile), { recursive: true });
- await fs.copyFile(originFile, backupFile);
- await fs.copyFile(replaceFile, originFile);
- }
-
- console.log("=====寮�濮嬫墦鍖�======");
- execSync("vite build", { stdio: "inherit" });
- console.log("=====鎵撳寘瀹屾垚======");
-} finally {
- console.log("=====鎭㈠璧勬簮======");
-
- // 鍒犻櫎涓存椂 .env 鏂囦欢
- if (fsSync.existsSync(envFilePath)) {
- await fs.unlink(envFilePath);
- console.log(`馃棏锔� 宸插垹闄� ${envFilePath}`);
- }
-
- // 鎭㈠璧勬簮鏂囦欢
- if (fsSync.existsSync(replacePath)) {
- for (const [key, value] of Object.entries(companyMap)) {
- if (key === 'env') continue;
-
- const originFile = path.join(rootPath, config[key]);
- const backupFile = path.join(replacePath, config[key]);
-
- await fs.copyFile(backupFile, originFile);
- }
- await fs.rm(replacePath, { recursive: true, force: true });
- console.log(`馃棏锔� 宸插垹闄� ${replacePath}`);
- }
+if (!companyMap) {
+ const availableCompanies = Object.entries(config)
+ .filter(([, value]) => value && typeof value === "object" && value.env)
+ .map(([key]) => key)
+ .sort();
+ throw new Error(
+ `鏈煡 company: "${company}"銆傚彲閫夊��: ${availableCompanies.join(", ")}`
+ );
}
-// 绠�鍗曞懡浠よ鍙傛暟瑙f瀽
-function parseArgs(argv) {
- const params = {};
- for (const arg of argv.slice(2)) {
- if (arg.startsWith('--')) {
- const [key, value] = arg.slice(2).split('=');
- params[key] = value ?? true;
- }
+console.log(`褰撳墠 company: ${company}`);
+
+async function copyFileWithOverwrite(src, dest) {
+ await fs.mkdir(path.dirname(dest), { recursive: true });
+ if (fsSync.existsSync(dest)) {
+ try {
+ await fs.chmod(dest, 0o666);
+ } catch {
+ // Ignore chmod failure and continue.
}
- return params;
+ await fs.rm(dest, { force: true });
+ }
+ await fs.copyFile(src, dest);
+}
+
+try {
+ console.log("=======鐢熸垚.env=======");
+ const envContent =
+ Object.entries(companyMap.env)
+ .map(([key, value]) => `${key}='${value}'`)
+ .join("\n") + "\n";
+ await fs.writeFile(envFilePath, envContent, "utf-8");
+
+ console.log("=======淇敼璧勬簮=======");
+ for (const [key] of Object.entries(companyMap)) {
+ if (key === "env") continue;
+
+ const originFile = path.join(rootPath, config[key]);
+ const backupFile = path.join(replacePath, config[key]);
+ const replaceFile = path.join(resourcePath, companyMap[key]);
+
+ await copyFileWithOverwrite(originFile, backupFile);
+ await copyFileWithOverwrite(replaceFile, originFile);
+ }
+
+ console.log("=====寮�濮嬫墦鍖�=====");
+ const buildEnv = createBuildEnv(companyMap.env);
+ execSync("vite build", { stdio: "inherit", cwd: rootPath, env: buildEnv });
+ console.log("=====鎵撳寘瀹屾垚======");
+} finally {
+ console.log("=====鎭㈠璧勬簮======");
+
+ if (fsSync.existsSync(envFilePath)) {
+ await fs.unlink(envFilePath);
+ console.log(`馃棏锔� 宸插垹闄� ${envFilePath}`);
+ }
+
+ if (fsSync.existsSync(replacePath)) {
+ for (const [key] of Object.entries(companyMap)) {
+ if (key === "env") continue;
+
+ const originFile = path.join(rootPath, config[key]);
+ const backupFile = path.join(replacePath, config[key]);
+ await copyFileWithOverwrite(backupFile, originFile);
+ }
+ await fs.rm(replacePath, { recursive: true, force: true });
+ console.log(`馃棏锔� 宸插垹闄� ${replacePath}`);
+ }
+}
+
+function parseArgs(argv) {
+ const params = {};
+ for (let index = 2; index < argv.length; index++) {
+ const arg = argv[index];
+ if (!arg.startsWith("--")) continue;
+
+ const normalized = arg.slice(2);
+ const equalIndex = normalized.indexOf("=");
+ if (equalIndex >= 0) {
+ const key = normalized.slice(0, equalIndex);
+ const value = normalized.slice(equalIndex + 1);
+ params[key] = value || true;
+ continue;
+ }
+
+ const nextArg = argv[index + 1];
+ if (nextArg && !nextArg.startsWith("--")) {
+ params[normalized] = nextArg;
+ index += 1;
+ continue;
+ }
+
+ params[normalized] = true;
+ }
+ return params;
+}
+
+function resolveCompany(parsedParams) {
+ const fromArg = parseValue(parsedParams.company);
+ if (fromArg) return fromArg;
+
+ const fromNpmConfig = parseValue(process.env.npm_config_company);
+ if (fromNpmConfig) return fromNpmConfig;
+
+ const fromEnv = parseValue(process.env.COMPANY ?? process.env.company);
+ if (fromEnv) return fromEnv;
+
+ return "default";
+}
+
+function parseValue(value) {
+ if (value == null || value === true) return undefined;
+ if (typeof value !== "string") return undefined;
+ const trimmed = value.trim();
+ if (!trimmed) return undefined;
+ return trimmed.replace(/^["']|["']$/g, "");
+}
+
+function createBuildEnv(companyEnv) {
+ const env = { ...process.env };
+ for (const key of Object.keys(env)) {
+ if (key.startsWith("VITE_")) {
+ delete env[key];
+ }
+ }
+ return {
+ ...env,
+ ...companyEnv,
+ VITE_APP_ENV: "production",
+ };
}
diff --git a/package.json b/package.json
index e80e1e7..11fa01d 100644
--- a/package.json
+++ b/package.json
@@ -56,5 +56,6 @@
},
"overrides": {
"quill": "2.0.2"
- }
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
diff --git a/public/favicon.ico b/public/favicon.ico
index e263760..ce783dc 100644
--- a/public/favicon.ico
+++ b/public/favicon.ico
Binary files differ
diff --git a/src/api/basicData/customer.js b/src/api/basicData/customer.js
index 28f2eab..0934193 100644
--- a/src/api/basicData/customer.js
+++ b/src/api/basicData/customer.js
@@ -26,6 +26,14 @@
})
}
+// 娴佸叆鍏捣
+export function backCustomer(id) {
+ return request({
+ url: '/basic/customer/back/' + id,
+ method: 'post'
+ })
+}
+
export function shareCustomer(data) {
return request({
url: '/basic/customer/together',
diff --git a/src/api/equipmentManagement/repair.js b/src/api/equipmentManagement/repair.js
index 0233ae6..16bfd28 100644
--- a/src/api/equipmentManagement/repair.js
+++ b/src/api/equipmentManagement/repair.js
@@ -70,3 +70,16 @@
data,
});
};
+
+/**
+ * @desc 楠屾敹瀹℃壒
+ * @param {楠屾敹鍙傛暟} data
+ * @returns
+ */
+export const repairAcceptance = (data) => {
+ return request({
+ url: `/device/repair/acceptance`,
+ method: "post",
+ data,
+ });
+};
diff --git a/src/api/financialManagement/accountPurchase.js b/src/api/financialManagement/accountPurchase.js
new file mode 100644
index 0000000..9e2d508
--- /dev/null
+++ b/src/api/financialManagement/accountPurchase.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+/** 閲囪喘鍏ュ簱鍒嗛〉鍒楄〃 */
+export const listPageAccountPurchase = (params) => {
+ return request({
+ url: "/accountPurchase/listPageAccountPurchase",
+ method: "get",
+ params,
+ });
+};
+
+/** 閲囪喘閫�璐у垎椤靛垪琛� */
+export const listPageAccountPurchaseReturn = (params) => {
+ return request({
+ url: "/accountPurchase/listPageAccountPurchaseReturn",
+ method: "get",
+ params,
+ });
+};
diff --git a/src/api/financialManagement/accountSales.js b/src/api/financialManagement/accountSales.js
new file mode 100644
index 0000000..e56d50f
--- /dev/null
+++ b/src/api/financialManagement/accountSales.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+/** 閿�鍞嚭搴撳垎椤靛垪琛� */
+export const listPageAccountSales = (params) => {
+ return request({
+ url: "/accountSales/listPageAccountSales",
+ method: "get",
+ params,
+ });
+};
+
+/** 閿�鍞��璐у垎椤靛垪琛� */
+export const listPageAccountSalesReturn = (params) => {
+ return request({
+ url: "/accountSales/listPageAccountSalesReturn",
+ method: "get",
+ params,
+ });
+};
diff --git a/src/api/financialManagement/accountSubject.js b/src/api/financialManagement/accountSubject.js
new file mode 100644
index 0000000..e54de63
--- /dev/null
+++ b/src/api/financialManagement/accountSubject.js
@@ -0,0 +1,46 @@
+import request from "@/utils/request";
+
+// 鏌ヨ鎬诲笎绉戠洰鍒楄〃
+export function listAccountSubject(query) {
+ return request({
+ url: "/accountSubject/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板鎬诲笎绉戠洰
+export function addAccountSubject(data) {
+ return request({
+ url: "/accountSubject/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼鎬诲笎绉戠洰
+export function updateAccountSubject(data) {
+ return request({
+ url: "/accountSubject/edit",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鍒犻櫎鎬诲笎绉戠洰
+export function delAccountSubject(ids) {
+ return request({
+ url: "/accountSubject/remove/" + ids,
+ method: "delete",
+ });
+}
+
+// 瀵煎嚭鎬诲笎绉戠洰
+export function exportAccountSubject(data) {
+ return request({
+ url: "/accountSubject/export",
+ method: "post",
+ data: data,
+ responseType: "blob",
+ });
+}
diff --git a/src/api/financialManagement/fixedAsset.js b/src/api/financialManagement/fixedAsset.js
new file mode 100644
index 0000000..5c28db4
--- /dev/null
+++ b/src/api/financialManagement/fixedAsset.js
@@ -0,0 +1,50 @@
+import request from "@/utils/request";
+
+// 鍥哄畾璧勪骇鍒嗛〉鏌ヨ锛坈urrent/size锛�
+export function listFixedAssetPage(params) {
+ return request({
+ url: "/financial/fixedAsset/page",
+ method: "get",
+ params,
+ });
+}
+
+// 鏂板鍥哄畾璧勪骇
+export function addFixedAsset(data) {
+ return request({
+ url: "/financial/fixedAsset/add",
+ method: "post",
+ data,
+ });
+}
+
+// 淇敼鍥哄畾璧勪骇
+export function updateFixedAsset(data) {
+ return request({
+ url: "/financial/fixedAsset/update",
+ method: "put",
+ data,
+ });
+}
+
+// 鍒犻櫎鍥哄畾璧勪骇锛堝悗绔姹� ids=1&ids=2 褰㈠紡锛�
+export function deleteFixedAsset(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter(id => id !== undefined && id !== null && id !== "")
+ .map(id => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/financial/fixedAsset/delete?${query}`,
+ method: "delete",
+ });
+}
+
+// 鎶樻棫璁℃彁锛坽} 琛ㄧず鍏ㄩ儴鍦ㄧ敤璧勪骇锛�
+export function depreciateFixedAsset(data = {}) {
+ return request({
+ url: "/financial/fixedAsset/depreciate",
+ method: "post",
+ data,
+ });
+}
diff --git a/src/api/financialManagement/intangibleAsset.js b/src/api/financialManagement/intangibleAsset.js
new file mode 100644
index 0000000..802e649
--- /dev/null
+++ b/src/api/financialManagement/intangibleAsset.js
@@ -0,0 +1,50 @@
+import request from "@/utils/request";
+
+// 鏃犲舰璧勪骇鍒嗛〉鏌ヨ锛坈urrent/size锛�
+export function listIntangibleAssetPage(params) {
+ return request({
+ url: "/financial/intangibleAsset/page",
+ method: "get",
+ params,
+ });
+}
+
+// 鏂板鏃犲舰璧勪骇
+export function addIntangibleAsset(data) {
+ return request({
+ url: "/financial/intangibleAsset/add",
+ method: "post",
+ data,
+ });
+}
+
+// 淇敼鏃犲舰璧勪骇
+export function updateIntangibleAsset(data) {
+ return request({
+ url: "/financial/intangibleAsset/update",
+ method: "put",
+ data,
+ });
+}
+
+// 鍒犻櫎鏃犲舰璧勪骇锛堝悗绔姹� ids=1&ids=2 褰㈠紡锛�
+export function deleteIntangibleAsset(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter(id => id !== undefined && id !== null && id !== "")
+ .map(id => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/financial/intangibleAsset/delete?${query}`,
+ method: "delete",
+ });
+}
+
+// 鎽婇攢璁℃彁锛坽} 琛ㄧず鍏ㄩ儴鍦ㄧ敤璧勪骇锛�
+export function amortizeIntangibleAsset(data = {}) {
+ return request({
+ url: "/financial/intangibleAsset/amortize",
+ method: "post",
+ data,
+ });
+}
diff --git a/src/api/financialManagement/ledger.js b/src/api/financialManagement/ledger.js
new file mode 100644
index 0000000..17e62fc
--- /dev/null
+++ b/src/api/financialManagement/ledger.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+// 绉戠洰鎬昏处
+export function getGeneralLedger(params) {
+ return request({
+ url: "/financial/ledger/general",
+ method: "get",
+ params,
+ });
+}
+
+// 绉戠洰鏄庣粏璐�
+export function getDetailLedger(params) {
+ return request({
+ url: "/financial/ledger/detail",
+ method: "get",
+ params,
+ });
+}
diff --git a/src/api/financialManagement/voucher.js b/src/api/financialManagement/voucher.js
new file mode 100644
index 0000000..ccb0908
--- /dev/null
+++ b/src/api/financialManagement/voucher.js
@@ -0,0 +1,54 @@
+import request from "@/utils/request";
+
+// 鍑瘉鍒嗛〉鏌ヨ锛坈urrent/size + 杩囨护鏉′欢锛�
+export function listVoucherPage(params) {
+ return request({
+ url: "/financial/voucher/page",
+ method: "get",
+ params,
+ });
+}
+
+// 鏂板鍑瘉
+export function addVoucher(data) {
+ return request({
+ url: "/financial/voucher/add",
+ method: "post",
+ data,
+ });
+}
+
+// 淇敼鍑瘉锛堜粎鏈繃璐︼級
+export function updateVoucher(data) {
+ return request({
+ url: "/financial/voucher/update",
+ method: "put",
+ data,
+ });
+}
+
+// 杩囪处
+export function postVoucher(data) {
+ return request({
+ url: "/financial/voucher/post",
+ method: "post",
+ data,
+ });
+}
+
+// 浣滃簾
+export function cancelVoucher(data) {
+ return request({
+ url: "/financial/voucher/cancel",
+ method: "post",
+ data,
+ });
+}
+
+// 璇︽儏
+export function getVoucherDetail(id) {
+ return request({
+ url: `/financial/voucher/detail/${id}`,
+ method: "get",
+ });
+}
diff --git a/src/api/inventoryManagement/stockInventory.js b/src/api/inventoryManagement/stockInventory.js
index 0ba0943..539eedc 100644
--- a/src/api/inventoryManagement/stockInventory.js
+++ b/src/api/inventoryManagement/stockInventory.js
@@ -17,6 +17,14 @@
});
};
+export const getStockInventoryBatchNoQty = (params) => {
+ return request({
+ url: "/stockInventory/getBatchNoQty",
+ method: "get",
+ params,
+ });
+};
+
// 鍒涘缓搴撳瓨璁板綍
export const createStockInventory = (params) => {
return request({
diff --git a/src/api/login.js b/src/api/login.js
index fea9d46..4cc0a5d 100644
--- a/src/api/login.js
+++ b/src/api/login.js
@@ -1,4 +1,5 @@
-import request from '@/utils/request'
+import request from '@/utils/request'
+import { getToken } from '@/utils/auth'
// 鐧诲綍鏂规硶
export function login(username, password, code, uuid) {
@@ -32,12 +33,14 @@
}
// 鑾峰彇鐢ㄦ埛璇︾粏淇℃伅
-export function getInfo() {
- return request({
- url: '/getInfo',
- method: 'get'
- })
-}
+export function getInfo() {
+ const token = getToken()
+ return request({
+ url: '/getInfo',
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
+ method: 'get'
+ })
+}
// 閫�鍑烘柟娉�
export function logout() {
diff --git a/src/api/oaSystem/projectManagement.js b/src/api/oaSystem/projectManagement.js
deleted file mode 100644
index 56c9287..0000000
--- a/src/api/oaSystem/projectManagement.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import request from "@/utils/request";
-import { parseStrEmpty } from "@/utils/ruoyi";
-
-// 鏌ヨ椤圭洰鍒楄〃
-export function listProject(query) {
- return request({
- url: "/oA/project/listPage",
- method: "get",
- params: query
- });
-}
-
-// 鏌ヨ椤圭洰鍒楄〃璇︾粏
-export function getProject(query) {
- return request({
- url: "oA/project/getList",
- method: "get"
- });
-}
-
-// 鏂板椤圭洰
-export function addProject(data) {
- return request({
- url: "/oA/project/add",
- method: "post",
- data: data
- });
-}
-
-// 淇敼椤圭洰
-export function updateProject(data) {
- return request({
- url: "/oA/project/update",
- method: "post",
- data: data
- });
-}
-
-// 鍒犻櫎椤圭洰
-export function delProject(projectId) {
- return request({
- url: "/oA/project/delete/" + projectId,
- method: "delete"
- });
-}
-// 瀵煎嚭椤圭洰
-export function exportProject(data) {
- return request({
- url: "/oA/project/export",
- method: "post",
- data: data
- });
-}
-// // 鎵归噺鍒犻櫎椤圭洰
-// export function delProjectBatch(projectIds) {
-// return request({
-// url: "/oaSystem/project/batch",
-// method: "delete",
-// data: projectIds
-// });
-// }
-
-// 鏍规嵁椤圭洰闃舵id鏌ヨ椤圭洰闃舵浠诲姟鍒楄〃
-export function listProjectTask(phaseId) {
- return request({
- url: "/oA/projectPhaseTask/listByPhaseId/"+ phaseId,
- method: "get"
- });
-}
-
-// // 鏌ヨ椤圭洰浠诲姟璇︾粏
-// export function getProjectTask(taskId) {
-// return request({
-// url: "/oaSystem/project/task/" + taskId,
-// method: "get"
-// });
-// }
-
-// 鏂板椤圭洰闃舵浠诲姟
-export function addProjectTask(data) {
- return request({
- url: "/oA/projectPhaseTask/add",
- method: "post",
- data: data
- });
-}
-
-// 淇敼椤圭洰闃舵浠诲姟
-export function updateProjectTask(data) {
- return request({
- url: "/oA/projectPhaseTask/update",
- method: "post",
- data: data
- });
-}
-
-// 鍒犻櫎椤圭洰闃舵浠诲姟
-export function delProjectTask(taskId) {
- return request({
- url: "/oA/projectPhaseTask/delete/" + taskId,
- method: "delete"
- });
-}
-
-// 椤圭洰id鏌ヨ椤圭洰闃舵鍒楄〃
-export function listProjectPhase(projectId) {
- return request({
- url: "/oA/projectPhase/listByProjectId/" + projectId,
- method: "get"
- });
-}
-// 鏂板椤圭洰闃舵
-export function addProjectPhase(data) {
- return request({
- url: "/oA/projectPhase/add",
- method: "post",
- data: data
- });
-}
-
-// 淇敼椤圭洰闃舵
-export function updateProjectPhase(data) {
- return request({
- url: "/oA/projectPhase/update",
- method: "post",
- data: data
- });
-
-}
-// 鍒犻櫎椤圭洰闃舵
-export function delProjectPhase(phaseId) {
- return request({
- url: "/oA/projectPhase/delete/" + phaseId,
- method: "delete"
- })
-}
-//
-
-// // 鏌ヨ椤圭洰閲岀▼纰戝垪琛�
-// export function listProjectMilestone(query) {
-// return request({
-// url: "/oaSystem/project/milestone/list",
-// method: "get",
-// params: query
-// });
-// }
-
-// // 椤圭洰缁熻淇℃伅
-// export function getProjectStatistics() {
-// return request({
-// url: "/oaSystem/project/statistics",
-// method: "get"
-// });
-// }
\ No newline at end of file
diff --git a/src/api/procurementManagement/purchase_return_order.js b/src/api/procurementManagement/purchase_return_order.js
index aeb380d..2705dde 100644
--- a/src/api/procurementManagement/purchase_return_order.js
+++ b/src/api/procurementManagement/purchase_return_order.js
@@ -19,6 +19,15 @@
});
}
+// 鏍规嵁閲囪喘鍙拌处 ID 鏌ヨ鍙��浜у搧绛変俊鎭�
+export function getPurchaseReturnOrderByPurchaseLedgerId(query) {
+ return request({
+ url: "/purchaseReturnOrders/getByPurchaseLedgerId",
+ method: "get",
+ params: query,
+ });
+}
+
// 鏌ョ湅璇︽儏
// purchaseReturnOrders/selectById/xxx
export function getPurchaseReturnOrderDetail(id) {
diff --git a/src/api/salesManagement/deliveryLedger.js b/src/api/salesManagement/deliveryLedger.js
index b85582c..bcc47a3 100644
--- a/src/api/salesManagement/deliveryLedger.js
+++ b/src/api/salesManagement/deliveryLedger.js
@@ -17,6 +17,14 @@
method: "get",
});
}
+// 淇敼鍙戣揣鍙拌处
+export function getDeliveryDetailByShippingNo(query) {
+ return request({
+ url: "/shippingInfo/getDateilByShippingNo",
+ method: "get",
+ params: query,
+ });
+}
export function addOrUpdateDeliveryLedger(query) {
return request({
@@ -51,4 +59,3 @@
data,
});
}
-
diff --git a/src/api/viewIndex.js b/src/api/viewIndex.js
index 8b5d144..dafe6e8 100644
--- a/src/api/viewIndex.js
+++ b/src/api/viewIndex.js
@@ -326,3 +326,72 @@
method: "get",
});
};
+
+export const productionOverview = () => {
+ return request({
+ url: "/home/productionOverview",
+ method: "get",
+ headers: {
+ handleAuthError: false,
+ },
+ });
+};
+
+export const productionRealtimeBoard = () => {
+ return request({
+ url: "/home/productionRealtimeBoard",
+ method: "get",
+ headers: {
+ handleAuthError: false,
+ },
+ });
+};
+
+const HOME_PROGRESS_STATUS_LIST = ["all", "waiting", "inProgress", "completed", "paused", "1", "2", "3", "4"];
+const HOME_PROGRESS_TAB_LIST = ["all", "inProgress", "completed", "paused"];
+const HOME_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
+
+const normalizeDateParam = (value) => {
+ const dateText = typeof value === "string" ? value.trim() : "";
+ return HOME_DATE_PATTERN.test(dateText) ? dateText : undefined;
+};
+
+export const productionOrderProgress = (params = {}) => {
+ const safePageNum = Math.max(1, Number(params.pageNum || 1));
+ const safePageSize = Math.min(50, Math.max(1, Number(params.pageSize || 10)));
+ const rawStatus = String(params.status ?? "").trim();
+ const safeStatus = HOME_PROGRESS_STATUS_LIST.includes(rawStatus) ? rawStatus : undefined;
+ const safeTab = HOME_PROGRESS_TAB_LIST.includes(params.tab) ? params.tab : "all";
+ const normalizedTab = safeStatus && HOME_PROGRESS_TAB_LIST.includes(safeStatus) ? safeStatus : safeTab;
+ return request({
+ url: "/home/productionOrderProgress",
+ method: "get",
+ params: {
+ ...params,
+ status: safeStatus,
+ tab: normalizedTab,
+ bizDate: normalizeDateParam(params.bizDate),
+ pageNum: safePageNum,
+ pageSize: safePageSize,
+ },
+ headers: {
+ handleAuthError: false,
+ },
+ });
+};
+
+export const todayProductionPlan = (params = {}) => {
+ const safeLimit = Math.min(20, Math.max(1, Number(params.limit || 4)));
+ return request({
+ url: "/home/todayProductionPlan",
+ method: "get",
+ params: {
+ ...params,
+ limit: safeLimit,
+ planDate: normalizeDateParam(params.planDate),
+ },
+ headers: {
+ handleAuthError: false,
+ },
+ });
+};
diff --git "a/src/assets/AI/\344\273\223\345\202\250\345\212\251\346\211\213.png" "b/src/assets/AI/\344\273\223\345\202\250\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..3aae283
--- /dev/null
+++ "b/src/assets/AI/\344\273\223\345\202\250\345\212\251\346\211\213.png"
Binary files differ
diff --git "a/src/assets/AI/\345\276\205\345\212\236\345\212\251\346\211\213.png" "b/src/assets/AI/\345\276\205\345\212\236\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..54f4d32
--- /dev/null
+++ "b/src/assets/AI/\345\276\205\345\212\236\345\212\251\346\211\213.png"
Binary files differ
diff --git "a/src/assets/AI/\347\224\237\344\272\247\345\212\251\346\211\213.png" "b/src/assets/AI/\347\224\237\344\272\247\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..998b9df
--- /dev/null
+++ "b/src/assets/AI/\347\224\237\344\272\247\345\212\251\346\211\213.png"
Binary files differ
diff --git "a/src/assets/AI/\350\264\242\345\212\241\345\212\251\346\211\213.png" "b/src/assets/AI/\350\264\242\345\212\241\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..8932bff
--- /dev/null
+++ "b/src/assets/AI/\350\264\242\345\212\241\345\212\251\346\211\213.png"
Binary files differ
diff --git "a/src/assets/AI/\350\264\250\351\207\217\345\212\251\346\211\213.png" "b/src/assets/AI/\350\264\250\351\207\217\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..e3ab7e1
--- /dev/null
+++ "b/src/assets/AI/\350\264\250\351\207\217\345\212\251\346\211\213.png"
Binary files differ
diff --git "a/src/assets/AI/\351\207\207\350\264\255\345\212\251\346\211\213.png" "b/src/assets/AI/\351\207\207\350\264\255\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..aea0fd0
--- /dev/null
+++ "b/src/assets/AI/\351\207\207\350\264\255\345\212\251\346\211\213.png"
Binary files differ
diff --git "a/src/assets/AI/\351\224\200\345\224\256\345\212\251\346\211\213.png" "b/src/assets/AI/\351\224\200\345\224\256\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..b8087db
--- /dev/null
+++ "b/src/assets/AI/\351\224\200\345\224\256\345\212\251\346\211\213.png"
Binary files differ
diff --git a/src/assets/aiIndustrialBrain/reference-cards.png b/src/assets/aiIndustrialBrain/reference-cards.png
new file mode 100644
index 0000000..72fdb93
--- /dev/null
+++ b/src/assets/aiIndustrialBrain/reference-cards.png
Binary files differ
diff --git a/src/assets/aiIndustrialBrain/reference-chat.png b/src/assets/aiIndustrialBrain/reference-chat.png
new file mode 100644
index 0000000..d026388
--- /dev/null
+++ b/src/assets/aiIndustrialBrain/reference-chat.png
Binary files differ
diff --git a/src/assets/images/head.svg b/src/assets/images/head.svg
new file mode 100644
index 0000000..e49d541
--- /dev/null
+++ b/src/assets/images/head.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="452" height="285" viewBox="0 0 452 285"><defs><pattern x="0" y="80.0157699584961" width="452" height="204.98422241210938" patternUnits="userSpaceOnUse" id="master_svg0_143_34844"><image x="-0.11198217826978407" y="0" width="452.22396435653957" height="204.98422241210938" xlink:href="data:image/png;base64,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"/></pattern><pattern x="111.42742919921875" y="0" width="206.67991638183594" height="206.78233337402344" patternUnits="userSpaceOnUse" id="master_svg1_143_34837"><image x="-0.05120849609375" y="0" width="206.78233337402344" height="206.78233337402344" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAMgCAYAAADbcAZoAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAACAASURBVHic7N17nN1ldS/+z2c9371nkpAwyVxygWpQERWrVdSD1nNUilYuggZjJdTaXyNQ5CZarWJfnrHnVw6tRe4WIrRVC1gwKAjGFk6hN6UKre0RBbVCAXOZS+7JzOz9fdY6fzzfPbMnJJm990wyk8x6v155JZnZ+7uf/d179jzr+zxrLcA555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzzjnnnHPOOeecc84555xzE+B0D8A559zM0H3xc1URyWr/t5j+Jglw/K8LM9v7QWT/v1ZIpuMBUNXnfQ0AiDDu+GYGkjAzWN3xzQxUg4ik/+/xvfq/SULNlg1c071h4jPhnHPuQPIAxDnnHLo+0T9fdo9sh9QFA1r3K6IWANRN6PdqggCkFiygLgDBnkGIyWjQQRKqmgIQ4fMDi7oApDbcWrCCuuCldmzStmx8prMbdzE2d4acc85NFQ9AnHPuENB9/n8eSwlXAjhZVUHwQUA/2X/zi386VY+x8Pf/88jS7iOWI68YQ8lopgCw8calj0/VY0yHro/0Lw0ivwDAWmCiJl8ZuGbRb0332JxzbjbyAMQ552a4rgt+fhyVj1DYgWLlgCSi6VYRnjjwZy96crrHeCjo/tjAB6n4S9StoIhkZ276/MJ7p3tszjk3m3gA4pybFTpW/+BrGXEaRKAKTRt2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"/></pattern></defs><rect x="0" y="80.0157699584961" width="452" height="204.98422241210938" rx="0" fill="url(#master_svg0_143_34844)" fill-opacity="1"/><rect x="111.42742919921875" y="0" width="206.67991638183594" height="206.78233337402344" rx="0" fill="url(#master_svg1_143_34837)" fill-opacity="1"/><g><path d="M171.28799999,112L197.784,47.968002L213.431999,47.968002L239.928001,112L223.99200100000002,112L217.751999,97.216003L193.464001,97.216003L187.224001,112L171.28799999,112ZM205.656002,63.424L196.535999,85.792L214.776001,85.792L205.656002,63.424ZM246.840004,112L246.840004,47.968002L260.472,47.968002L260.472,112L246.840004,112Z" fill="#FFFFFF" fill-opacity="1"/><path d="M217.751999,97.216003L223.99200100000002,112L239.928001,112L239.514206,111L213.431999,47.968002L197.784002,47.968002L171.70179313,111L171.28799999,112L187.224003,112L193.464001,97.216003L217.751999,97.216003ZM186.560656,111L192.800652,96.216003L218.41535199999998,96.216003L224.655346,111L238.43197600000002,111L212.763565,48.968002L198.452438,48.968002L172.7840257,111L186.560656,111ZM246.840012,111L246.840012,112L260.472008,112L260.472008,47.968002L246.840012,47.968002L246.840012,111ZM247.840012,111L259.472008,111L259.472008,48.968002L247.840012,48.968002L247.840012,111ZM206.581993,63.046455L204.730011,63.046455L195.048351,86.79200399999999L216.263653,86.79200399999999L206.581993,63.046455ZM205.116039,64.748333L205.656002,63.424004L206.195965,64.748333L214.368279,84.79200399999999L214.776001,85.79200399999999L196.536001,85.79200399999999L196.943726,84.79200399999999L205.116039,64.748333Z" fill-rule="evenodd" fill="#E2E6F7" fill-opacity="1"/></g></svg>
\ No newline at end of file
diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png
index 91a5896..a5831b8 100644
--- a/src/assets/logo/logo.png
+++ b/src/assets/logo/logo.png
Binary files differ
diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss
index f296f9d..75c83ab 100644
--- a/src/assets/styles/element-ui.scss
+++ b/src/assets/styles/element-ui.scss
@@ -52,17 +52,17 @@
left: 0;
position: relative;
margin: 0 auto;
- border-radius: 24px;
+ border-radius: 16px;
padding: 0 !important;
border: 1px solid var(--surface-border);
box-shadow: var(--shadow-md);
- background: rgba(255, 255, 255, 0.96);
+ background: rgba(255, 255, 255, 0.95);
}
.el-dialog__header {
- background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88));
+ background: linear-gradient(180deg, rgba(248, 251, 255, 1), rgba(242, 247, 255, 0.98));
padding: 18px 24px 14px;
border-bottom: 1px solid var(--surface-border);
- border-radius: 24px 24px 0 0;
+ border-radius: 14px 14px 0 0;
}
.el-dialog__title {
font-weight: 600;
@@ -80,15 +80,16 @@
}
.el-message-box {
padding: 0 !important;
- border-radius: 22px;
+ border-radius: 16px;
border: 1px solid var(--surface-border);
box-shadow: var(--shadow-md);
+ background: rgba(255, 255, 255, 0.96);
}
.el-message-box__header {
- background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88));
+ background: linear-gradient(180deg, rgba(248, 251, 255, 1), rgba(242, 247, 255, 0.98));
padding: 18px 24px 14px;
border-bottom: 1px solid var(--surface-border);
- border-radius: 22px 22px 0 0;
+ border-radius: 14px 14px 0 0;
}
.el-message-box__title {
font-weight: 600;
@@ -161,7 +162,7 @@
}
.el-button {
- border-radius: 12px;
+ border-radius: 8px;
font-weight: 600;
box-shadow: none !important;
}
@@ -180,41 +181,44 @@
.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);
+ border-radius: 10px;
+ box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.28) inset !important;
+ background: rgba(255, 255, 255, 0.92);
+ color: var(--text-primary);
}
.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;
+ box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.56) inset !important;
}
.el-card {
border: 1px solid var(--surface-border);
box-shadow: var(--shadow-sm);
- background: rgba(255, 255, 255, 0.88);
+ background: var(--panel-mask);
}
.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-header-bg-color: #f2f7ff;
+ --el-table-row-hover-bg-color: #f8fbff;
+ --el-table-current-row-bg-color: #edf4ff;
+ border-radius: 12px;
+ background: rgba(255, 255, 255, 0.94) !important;
}
.el-table th.el-table__cell {
- background: var(--surface-soft) !important;
- color: var(--text-secondary);
+ background: #f2f7ff !important;
+ color: #3b4f6c;
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;
+ background: rgba(255, 255, 255, 0.92) !important;
+ color: var(--text-secondary);
}
.el-table .el-table__body tr:hover > td.el-table__cell {
@@ -239,3 +243,19 @@
.el-pagination {
margin-top: 18px;
}
+
+.el-empty__description p,
+.el-form-item__label,
+.el-radio-button__inner,
+.el-checkbox__label,
+.el-tabs__item,
+.el-select-dropdown__item,
+.el-dropdown-menu__item {
+ color: var(--text-secondary);
+}
+
+.el-date-editor .el-range-input,
+.el-input__inner,
+.el-textarea__inner {
+ color: var(--text-primary);
+}
diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss
index f905a36..39d03cf 100644
--- a/src/assets/styles/index.scss
+++ b/src/assets/styles/index.scss
@@ -6,18 +6,19 @@
@import './btn.scss';
@import './ruoyi.scss';
-body {
- height: 100%;
- margin: 0;
+body {
+ height: 100%;
+ margin: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
- text-rendering: optimizeLegibility;
- 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);
-}
+ text-rendering: optimizeLegibility;
+ font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
+ background:
+ radial-gradient(circle at 9% -6%, rgba(59, 130, 246, 0.14), transparent 36%),
+ radial-gradient(circle at 88% -8%, rgba(56, 189, 248, 0.12), transparent 30%),
+ linear-gradient(165deg, #f3f7fc 0%, #eef5ff 54%, #f8fbff 100%);
+ color: var(--text-primary);
+}
label {
font-weight: 600;
@@ -33,11 +34,11 @@
height: 100%;
}
-html,
-body,
-#app {
- background-color: var(--app-bg);
-}
+html,
+body,
+#app {
+ background-color: var(--app-bg);
+}
*,
*:before,
@@ -109,16 +110,17 @@
}
}
-aside {
- background: #eef1f6;
- padding: 8px 24px;
- margin-bottom: 20px;
- border-radius: 2px;
+aside {
+ background: rgba(255, 255, 255, 0.84);
+ padding: 8px 24px;
+ margin-bottom: 20px;
+ border-radius: 12px;
+ border: 1px solid var(--surface-border);
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
- color: #2c3e50;
+ color: var(--text-secondary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -136,24 +138,25 @@
.app-container {
padding: 20px 24px 24px;
}
-.search_form {
+.search_form {
display: flex;
align-items: center;
justify-content: space-between;
- .search_title {
+ .search_title {
font-size: 14px;
font-weight: 600;
letter-spacing: 0.04em;
- color: var(--text-secondary);
- }
-}
-.table_list {
- 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;
-}
+ color: var(--text-secondary);
+ }
+}
+.table_list {
+ background: var(--panel-mask);
+ border: 1px solid var(--surface-border);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-sm);
+ backdrop-filter: blur(12px);
+ padding: 18px;
+}
.components-container {
margin: 30px 50px;
position: relative;
@@ -187,15 +190,15 @@
}
}
-.link-type,
-.link-type:focus {
- color: var(--el-color-primary);
- cursor: pointer;
-
- &:hover {
- color: #165e57;
- }
-}
+.link-type,
+.link-type:focus {
+ color: var(--accent-light);
+ cursor: pointer;
+
+ &:hover {
+ color: #2563eb;
+ }
+}
.filter-container {
padding-bottom: 10px;
diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss
index be7b7a7..b3fdff7 100644
--- a/src/assets/styles/sidebar.scss
+++ b/src/assets/styles/sidebar.scss
@@ -1,8 +1,8 @@
#app {
.main-container {
- min-height: 100%;
- transition: margin-left 0.28s;
- margin-left: $base-sidebar-width;
+ min-height: 100vh;
+ margin-left: var(--sidebar-width);
+ transition: margin-left 0.25s ease;
position: relative;
background: transparent;
}
@@ -12,32 +12,98 @@
}
.sidebar-container {
- transition: width 0.28s;
- width: $base-sidebar-width !important;
- height: 100%;
+ transition: width 0.25s ease;
+ width: var(--sidebar-width) !important;
+ height: 100vh;
position: fixed;
- font-size: 0px;
top: 0;
- bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
- padding: 12px 0 16px 16px;
- background: transparent;
- box-shadow: none;
+ padding: 0;
+ font-size: 0;
+ background: var(--sidebar-bg);
+ border-right: 1px solid rgba(255, 255, 255, 0.08);
+ box-shadow: 8px 0 24px rgba(15, 23, 42, 0.08);
+ isolation: isolate;
- // reset element-ui css
+ &::before {
+ content: "";
+ position: absolute;
+ inset: -28% -52% -18% -38%;
+ z-index: 0;
+ pointer-events: none;
+ background:
+ radial-gradient(circle at 9% 12%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.62), transparent 44%),
+ radial-gradient(circle at 87% 18%, rgba(56, 189, 248, 0.4), transparent 48%),
+ radial-gradient(circle at 20% 82%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3), transparent 43%),
+ radial-gradient(circle at 66% 62%, rgba(125, 211, 252, 0.24), transparent 50%),
+ conic-gradient(
+ from 210deg at 58% 38%,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 0deg,
+ rgba(56, 189, 248, 0.05) 76deg,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.16) 180deg,
+ rgba(125, 211, 252, 0.04) 290deg,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 360deg
+ );
+ filter: blur(7px) saturate(1.24) contrast(1.05);
+ opacity: 0.96;
+ transform: translate3d(0, 0, 0);
+ transform-origin: 44% 58%;
+ animation:
+ sidebarAuroraDrift 17.9s cubic-bezier(0.31, 0.03, 0.18, 0.99) infinite,
+ sidebarAuroraBreath 9.7s ease-in-out infinite,
+ sidebarAuroraSkew 6.9s steps(23, end) infinite;
+ }
+
+ &::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ pointer-events: none;
+ background:
+ linear-gradient(
+ 108deg,
+ transparent 10%,
+ rgba(255, 255, 255, 0.17) 34%,
+ rgba(255, 255, 255, 0.04) 48%,
+ transparent 72%
+ ),
+ linear-gradient(
+ 202deg,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24) 0%,
+ transparent 34%,
+ rgba(56, 189, 248, 0.18) 66%,
+ transparent 100%
+ ),
+ radial-gradient(circle at 74% 12%, rgba(125, 211, 252, 0.25), transparent 50%),
+ radial-gradient(circle at 22% 84%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14), transparent 56%);
+ background-size: 236% 100%, 186% 186%, 164% 164%, 180% 180%;
+ background-position: 224% 0, 14% 16%, 78% 10%, 18% 82%;
+ opacity: 0.52;
+ transform: translate3d(0, 0, 0);
+ animation:
+ sidebarSheenSweep 13.1s linear infinite,
+ sidebarSheenJitter 4.7s steps(31, end) infinite;
+ }
+
+ > * {
+ position: relative;
+ z-index: 1;
+ }
+
.horizontal-collapse-transition {
- transition: 0s width ease-in-out, 0s padding-left ease-in-out,
- 0s padding-right ease-in-out;
+ transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
+ overflow-y: auto !important;
}
.el-scrollbar__bar.is-vertical {
- right: 0px;
+ right: 2px;
}
.el-scrollbar {
@@ -46,8 +112,7 @@
&.has-logo {
.el-scrollbar {
- height: calc(100% - 72px);
- margin-top: 10px;
+ height: calc(100% - 78px);
}
}
@@ -61,22 +126,19 @@
overflow: hidden;
}
- .svg-icon {
- margin-right: 16px;
+ .el-menu {
+ border: none !important;
+ height: 100%;
+ width: 100% !important;
+ padding: 10px 0 16px;
+ border-radius: 0;
+ background: transparent !important;
+ box-shadow: none;
+ backdrop-filter: none;
}
- .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,
+ .el-sub-menu__title,
.menu-title {
overflow: hidden !important;
text-overflow: ellipsis !important;
@@ -84,181 +146,223 @@
}
.el-menu-item .el-menu-tooltip__trigger {
- display: inline-block !important;
+ display: inline-flex !important;
+ width: 100%;
+ align-items: center;
}
- // 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: var(--current-color) !important;
+ .submenu-title-noDropdown,
+ .el-sub-menu__title,
+ .el-menu-item {
+ min-width: 0 !important;
+ width: calc(100% - 24px) !important;
+ margin: 0 12px 8px !important;
+ height: 50px;
+ line-height: 50px;
+ border-radius: 14px;
+ padding-left: 16px !important;
+ padding-right: 36px !important;
+ box-sizing: border-box;
+ transition: all 0.28s ease;
+ color: var(--sidebar-text);
+ background: linear-gradient(128deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01));
+ border: 1px solid rgba(255, 255, 255, 0.06) !important;
+ position: relative;
+ overflow: hidden;
}
- & .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;
- }
- }
+ .submenu-title-noDropdown::after,
+ .el-sub-menu__title::after,
+ .el-menu-item::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(115deg, transparent 12%, rgba(255, 255, 255, 0.16), transparent 78%);
+ transform: translateX(-100%);
+ opacity: 0;
+ transition: transform 0.45s ease, opacity 0.26s ease;
+ pointer-events: none;
+ }
- & .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;
- }
- }
- }
+ .submenu-title-noDropdown:hover,
+ .el-sub-menu__title:hover,
+ .el-menu-item:hover {
+ background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.28), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.08)) !important;
+ border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.32) !important;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 8px 18px rgba(8, 36, 76, 0.24);
+ transform: translateX(3px);
+ }
- .hideSidebar {
- .sidebar-container {
- width: 68px !important;
- padding-left: 0;
- padding-right: 0;
- }
-
- .main-container {
- margin-left: 84px;
- }
+ .submenu-title-noDropdown:hover::after,
+ .el-sub-menu__title:hover::after,
+ .el-menu-item:hover::after,
+ .el-menu-item.is-active::after,
+ .el-sub-menu.is-active > .el-sub-menu__title::after {
+ transform: translateX(100%);
+ opacity: 1;
+ }
- .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;
+ & .theme-light .is-active > .el-sub-menu__title,
+ & .theme-dark .is-active > .el-sub-menu__title,
+ & .el-menu-item.is-active {
+ color: #fff !important;
+ background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important;
+ background-size: 180% 180%;
+ box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34));
+ border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.5) !important;
+ animation: sidebarActiveFlow 4.6s ease infinite;
+ }
+
+ & .nest-menu .el-sub-menu > .el-sub-menu__title,
+ & .el-sub-menu .el-menu-item {
+ min-width: 0 !important;
+ width: calc(100% - 24px) !important;
+ margin: 0 12px 8px !important;
+ height: 46px;
+ line-height: 46px;
+ padding-left: 14px !important;
+ padding-right: 14px !important;
+ border-radius: 12px;
+ transition: all 0.24s ease;
+ color: var(--sidebar-text);
+ border: 1px solid rgba(255, 255, 255, 0.06) !important;
+ background: linear-gradient(128deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
+
+ &:hover {
+ background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.07)) !important;
+ border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3) !important;
+ transform: translateX(2px);
+ }
+
+ &.is-active {
+ background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important;
+ background-size: 180% 180%;
+ color: #fff !important;
+ font-weight: 500;
+ box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34));
+ animation: sidebarActiveFlow 4.6s ease infinite;
+ }
+ }
+ }
+
+ .hideSidebar {
+ .sidebar-container {
+ width: var(--sidebar-collapsed-width) !important;
+ }
+
+ .main-container {
+ margin-left: var(--sidebar-collapsed-width);
+ }
+
+ .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: 22px;
+ height: 22px;
+ 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: 12px 0 16px;
+
+ > .el-menu-item,
+ .el-sub-menu {
+ & > .el-sub-menu__title,
+ &.el-menu-item {
+ width: calc(100% - 12px) !important;
+ margin: 0 6px 8px !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ box-sizing: border-box;
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+
+ .svg-icon {
+ width: 22px;
+ height: 22px;
+ margin-right: 0;
+ flex-shrink: 0;
}
- & > i {
+
+ & > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
- }
- }
- }
- }
- }
-
- .el-menu--collapse .el-menu .el-sub-menu {
- min-width: $base-sidebar-width !important;
+ }
+ }
+ }
+ }
}
- // mobile responsive
+ .el-menu--collapse .el-menu .el-sub-menu {
+ min-width: var(--sidebar-width) !important;
+ }
+
.mobile {
.main-container {
- margin-left: 0px;
+ margin-left: 0;
}
.sidebar-container {
- transition: transform 0.28s;
- width: $base-sidebar-width !important;
+ transition: transform 0.25s;
+ width: var(--sidebar-width) !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
- transform: translate3d(-$base-sidebar-width, 0, 0);
+ transform: translate3d(calc(-1 * var(--sidebar-width)), 0, 0);
}
}
}
@@ -271,53 +375,290 @@
}
}
-// when menu collapsed
.el-menu--vertical {
& > .el-menu {
.svg-icon {
- margin-right: 16px;
+ margin-right: 10px;
}
}
- .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;
- }
- }
+ .nest-menu .el-sub-menu > .el-sub-menu__title,
+ .el-menu-item {
+ min-width: 0 !important;
+ margin: 0 10px 8px;
+ width: calc(100% - 20px);
+ height: 46px;
+ line-height: 46px;
+ padding-left: 12px !important;
+ padding-right: 12px !important;
+ box-sizing: border-box;
+ border-radius: 12px;
+ color: var(--sidebar-text);
+ border: 1px solid rgba(255, 255, 255, 0.06) !important;
+ background: linear-gradient(128deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01));
+ transition: all 0.24s ease;
- // the scroll bar appears when the sub-menu is too long
+ &:hover {
+ background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.07)) !important;
+ border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3) !important;
+ transform: translateX(2px);
+ }
+
+ &.is-active {
+ background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important;
+ background-size: 180% 180%;
+ color: #fff !important;
+ border-radius: 12px;
+ box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34));
+ border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.5) !important;
+ animation: sidebarActiveFlow 4.6s ease infinite;
+ }
+ }
+
> .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);
+ overflow: hidden;
+ padding: 10px;
+ border-radius: 14px;
+ position: relative;
+ isolation: isolate;
+ border: 1px solid rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.26);
+ box-shadow:
+ 0 18px 40px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.16),
+ var(--shadow-md);
+ background: var(--sidebar-bg);
+ backdrop-filter: blur(16px);
- &::-webkit-scrollbar-track-piece {
- background: #dfe7e1;
+ &::before {
+ content: "";
+ position: absolute;
+ inset: -28% -52% -18% -38%;
+ z-index: 0;
+ pointer-events: none;
+ background:
+ radial-gradient(circle at 9% 12%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.62), transparent 44%),
+ radial-gradient(circle at 87% 18%, rgba(56, 189, 248, 0.4), transparent 48%),
+ radial-gradient(circle at 20% 82%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3), transparent 43%),
+ radial-gradient(circle at 66% 62%, rgba(125, 211, 252, 0.24), transparent 50%),
+ conic-gradient(
+ from 210deg at 58% 38%,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 0deg,
+ rgba(56, 189, 248, 0.05) 76deg,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.16) 180deg,
+ rgba(125, 211, 252, 0.04) 290deg,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 360deg
+ );
+ filter: blur(7px) saturate(1.24) contrast(1.05);
+ opacity: 0.96;
+ transform: translate3d(0, 0, 0);
+ transform-origin: 44% 58%;
+ animation:
+ sidebarAuroraDrift 17.9s cubic-bezier(0.31, 0.03, 0.18, 0.99) infinite,
+ sidebarAuroraBreath 9.7s ease-in-out infinite,
+ sidebarAuroraSkew 6.9s steps(23, end) infinite;
}
- &::-webkit-scrollbar {
- width: 6px;
+ &::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ pointer-events: none;
+ background:
+ linear-gradient(
+ 108deg,
+ transparent 10%,
+ rgba(255, 255, 255, 0.17) 34%,
+ rgba(255, 255, 255, 0.04) 48%,
+ transparent 72%
+ ),
+ linear-gradient(
+ 202deg,
+ rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24) 0%,
+ transparent 34%,
+ rgba(56, 189, 248, 0.18) 66%,
+ transparent 100%
+ ),
+ radial-gradient(circle at 74% 12%, rgba(125, 211, 252, 0.25), transparent 50%),
+ radial-gradient(circle at 22% 84%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14), transparent 56%);
+ background-size: 236% 100%, 186% 186%, 164% 164%, 180% 180%;
+ background-position: 224% 0, 14% 16%, 78% 10%, 18% 82%;
+ opacity: 0.52;
+ transform: translate3d(0, 0, 0);
+ animation:
+ sidebarSheenSweep 13.1s linear infinite,
+ sidebarSheenJitter 4.7s steps(31, end) infinite;
}
- &::-webkit-scrollbar-thumb {
- background: #9aa79e;
- border-radius: 20px;
+ > * {
+ position: relative;
+ z-index: 1;
}
+
+ > .el-menu {
+ max-height: calc(100vh - 20px);
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ &::-webkit-scrollbar-track-piece {
+ background: var(--surface-muted);
+ }
+
+ &::-webkit-scrollbar {
+ width: 5px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: var(--accent-light);
+ border-radius: 10px;
+ }
+ }
+ }
+}
+
+@keyframes sidebarActiveFlow {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+@keyframes sidebarAuroraDrift {
+ 0% {
+ transform: translate3d(-6.3%, -1.8%, 0) scale(1.05) rotate(-1.8deg);
+ }
+ 6% {
+ transform: translate3d(2.2%, -4.6%, 0) scale(1.08) rotate(0.7deg);
+ }
+ 17% {
+ transform: translate3d(-3.7%, 4.4%, 0) scale(1.11) rotate(2deg);
+ }
+ 27% {
+ transform: translate3d(5.6%, 1.2%, 0) scale(1.03) rotate(-1deg);
+ }
+ 39% {
+ transform: translate3d(-4.8%, -3.1%, 0) scale(1.09) rotate(1.5deg);
+ }
+ 52% {
+ transform: translate3d(2.9%, 4.8%, 0) scale(1.04) rotate(-1.4deg);
+ }
+ 64% {
+ transform: translate3d(-6.4%, 0.3%, 0) scale(1.08) rotate(0.5deg);
+ }
+ 73% {
+ transform: translate3d(4.8%, -3.9%, 0) scale(1.05) rotate(1.6deg);
+ }
+ 81% {
+ transform: translate3d(-2.4%, 2.9%, 0) scale(1.1) rotate(-0.8deg);
+ }
+ 92% {
+ transform: translate3d(3.7%, -1.7%, 0) scale(1.06) rotate(-1.6deg);
+ }
+ 100% {
+ transform: translate3d(-5.9%, 0.8%, 0) scale(1.08) rotate(1.2deg);
+ }
+}
+
+@keyframes sidebarAuroraBreath {
+ 0% {
+ opacity: 0.76;
+ filter: blur(5px) saturate(1.08);
+ }
+ 15% {
+ opacity: 1;
+ filter: blur(7px) saturate(1.28);
+ }
+ 37% {
+ opacity: 0.84;
+ filter: blur(8px) saturate(1.12);
+ }
+ 61% {
+ opacity: 0.98;
+ filter: blur(6px) saturate(1.24);
+ }
+ 83% {
+ opacity: 0.86;
+ filter: blur(7px) saturate(1.16);
+ }
+ 100% {
+ opacity: 0.94;
+ filter: blur(6px) saturate(1.2);
+ }
+}
+
+@keyframes sidebarAuroraSkew {
+ 0% {
+ transform-origin: 44% 58%;
+ }
+ 21% {
+ transform-origin: 62% 42%;
+ }
+ 43% {
+ transform-origin: 31% 66%;
+ }
+ 66% {
+ transform-origin: 68% 74%;
+ }
+ 100% {
+ transform-origin: 39% 45%;
+ }
+}
+
+@keyframes sidebarSheenSweep {
+ 0% {
+ background-position: 232% 0, 10% 18%, 80% 12%, 20% 82%;
+ }
+ 8% {
+ background-position: 186% 0, 16% 30%, 74% 18%, 28% 74%;
+ }
+ 21% {
+ background-position: 116% 0, 34% 10%, 62% 26%, 18% 64%;
+ }
+ 37% {
+ background-position: 52% 0, 50% 24%, 46% 12%, 32% 58%;
+ }
+ 52% {
+ background-position: -4% 0, 34% 54%, 22% 22%, 12% 46%;
+ }
+ 69% {
+ background-position: -62% 0, 14% 36%, 32% 34%, 24% 56%;
+ }
+ 84% {
+ background-position: -106% 0, 20% 20%, 46% 20%, 34% 70%;
+ }
+ 100% {
+ background-position: -136% 0, 10% 18%, 80% 12%, 20% 82%;
+ }
+}
+
+@keyframes sidebarSheenJitter {
+ 0% {
+ opacity: 0.28;
+ transform: translate3d(0, 0, 0);
+ }
+ 17% {
+ opacity: 0.56;
+ transform: translate3d(1.8%, -0.5%, 0);
+ }
+ 38% {
+ opacity: 0.34;
+ transform: translate3d(-1.2%, 0.8%, 0);
+ }
+ 63% {
+ opacity: 0.6;
+ transform: translate3d(2.3%, -0.3%, 0);
+ }
+ 81% {
+ opacity: 0.3;
+ transform: translate3d(-1.6%, 0.7%, 0);
+ }
+ 100% {
+ opacity: 0.52;
+ transform: translate3d(2%, -0.1%, 0);
}
}
diff --git a/src/assets/styles/variables.module.scss b/src/assets/styles/variables.module.scss
index 55b3822..461b545 100644
--- a/src/assets/styles/variables.module.scss
+++ b/src/assets/styles/variables.module.scss
@@ -8,31 +8,32 @@
$yellow: #fec171;
$panGreen: #30b08f;
-// menu palette
-$menuText: #677287;
-$menuActiveText: #1f7a72;
-$menuBg: #f4f7f4;
-$menuHover: #e7eeea;
+// menu palette - 浣跨敤涓婚鑹�
+$menuText: #5a6478;
+$menuActiveText: #ffffff;
+$menuBg: #f8fafb;
+$menuHover: rgba(var(--el-color-primary-rgb, 13, 148, 136), 0.08);
-// light theme
-$menuLightBg: #f4f7f4;
-$menuLightHover: #e7eeea;
-$menuLightText: #3b4658;
-$menuLightActiveText: #1f7a72;
+// light theme - 浣跨敤涓婚鑹�
+$menuLightBg: #f8fafb;
+$menuLightHover: rgba(var(--el-color-primary-rgb, 13, 148, 136), 0.08);
+$menuLightText: #3d4858;
+$menuLightActiveText: #ffffff;
// layout
$base-sidebar-width: 216px;
+$base-sidebar-collapsed-width: 72px;
$sideBarWidth: 216px;
-// sidebar
-$base-menu-color: #677287;
-$base-menu-color-active: #1f7a72;
-$base-menu-background: #f4f7f4;
-$base-sub-menu-background: #eef3ef;
+// sidebar - 浼樺寲鍚庣殑渚ц竟鏍忛厤鑹�
+$base-menu-color: #5a6478;
+$base-menu-color-active: #0d9488;
+$base-menu-background: #f8fafb;
+$base-sub-menu-background: #f0f5f4;
$base-sub-menu-hover: #ffffff;
-// component
-$--color-primary: #1f7a72;
+// component - 浼樺寲鍚庣殑涓婚鑹�
+$--color-primary: #0d9488;
$--color-success: #67c23a;
$--color-warning: #d89b41;
$--color-danger: #d25b52;
@@ -64,88 +65,130 @@
}
: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);
+ --sidebar-width: 216px;
+ --sidebar-collapsed-width: 72px;
+ --topbar-height: 64px;
+ --tagsbar-height: 40px;
+ --content-gap: 16px;
+ --content-radius: 16px;
+ --layout-header-z: 20;
- --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;
+ --el-color-primary: #2563eb;
+ --el-color-primary-rgb: 37, 99, 235;
+ --el-color-success: #14b8a6;
+ --el-color-warning: #f59e0b;
+ --el-color-danger: #ef4444;
+
+ --sidebar-bg: linear-gradient(180deg, #0e2a4f 0%, #123e69 55%, #0e2a4f 100%);
+ --sidebar-text: rgba(234, 242, 255, 0.82);
+ --sidebar-muted: rgba(234, 242, 255, 0.82);
+ --menu-hover: rgba(147, 197, 253, 0.2);
+ --menu-active-bg: linear-gradient(135deg, #2f80ff 0%, #38bdf8 100%);
+ --menu-active-text: #f8fbff;
+ --menu-surface: linear-gradient(180deg, rgba(13, 43, 79, 0.97) 0%, rgba(8, 28, 52, 0.94) 100%);
+ --menu-active-glow: 0 8px 18px rgba(56, 139, 255, 0.28);
+
+ --app-bg: #f3f7fc;
+ --app-bg-accent: #eef5ff;
+ --surface-base: rgba(255, 255, 255, 0.92);
+ --surface-soft: rgba(255, 255, 255, 0.88);
+ --surface-muted: #f5f9ff;
+ --surface-border: rgba(148, 163, 184, 0.18);
+ --surface-border-strong: rgba(96, 165, 250, 0.34);
+ --text-primary: #1e293b;
+ --text-secondary: #334155;
+ --text-tertiary: #64748b;
+ --shadow-sm: 0 12px 32px rgba(15, 23, 42, 0.06);
+ --shadow-md: 0 20px 42px rgba(15, 23, 42, 0.1);
+ --shadow-menu: 0 16px 36px rgba(8, 27, 58, 0.26);
+ --radius-lg: 20px;
+ --radius-md: 16px;
--radius-sm: 12px;
+ --radius-xs: 10px;
- --navbar-bg: rgba(255, 255, 255, 0.78);
- --navbar-text: #21313f;
- --navbar-hover: rgba(31, 122, 114, 0.08);
+ --navbar-bg: rgba(255, 255, 255, 0.86);
+ --navbar-text: #1f3658;
+ --navbar-hover: rgba(37, 99, 235, 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);
+ --tags-item-bg: rgba(255, 255, 255, 0.9);
+ --tags-item-border: rgba(148, 163, 184, 0.22);
+ --tags-item-text: #334155;
+ --tags-item-hover: #f4f8ff;
+ --tags-close-hover: rgba(37, 99, 235, 0.16);
- --splitpanes-default-bg: #ffffff;
+ --accent-primary: #2563eb;
+ --accent-light: #3b82f6;
+ --accent-lighter: #60a5fa;
+
+ --panel-mask: rgba(255, 255, 255, 0.88);
+ --panel-glow: inset 0 1px 0 rgba(255, 255, 255, 0.58);
+ --splitpanes-default-bg: #f3f7fc;
}
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;
+ --el-bg-color: #f8fbff;
+ --el-bg-color-overlay: #f3f7fd;
+ --el-text-color-primary: #1e293b;
+ --el-text-color-regular: #475569;
+ --el-border-color: rgba(148, 163, 184, 0.2);
+ --el-border-color-light: rgba(148, 163, 184, 0.18);
- --sidebar-bg: #141414;
- --sidebar-text: #ffffff;
- --menu-hover: #2d2d2d;
- --menu-active-text: #{$menuActiveText};
+ --sidebar-bg: linear-gradient(180deg, #0e2a4f 0%, #123e69 55%, #0e2a4f 100%);
+ --sidebar-text: rgba(234, 242, 255, 0.82);
+ --sidebar-muted: rgba(234, 242, 255, 0.82);
+ --menu-hover: rgba(147, 197, 253, 0.2);
+ --menu-active-bg: linear-gradient(135deg, #2f80ff 0%, #38bdf8 100%);
+ --menu-active-text: #f8fbff;
+ --menu-surface: linear-gradient(180deg, rgba(13, 43, 79, 0.97) 0%, rgba(8, 28, 52, 0.94) 100%);
- --navbar-bg: #141414;
- --navbar-text: #ffffff;
- --navbar-hover: #141414;
+ --text-primary: #1e293b;
+ --text-secondary: #334155;
+ --text-tertiary: #64748b;
+ --accent-primary: #2563eb;
+ --accent-light: #3b82f6;
- --tags-bg: #141414;
- --tags-item-bg: #1d1e1f;
- --tags-item-border: #303030;
- --tags-item-text: #d0d0d0;
- --tags-item-hover: #2d2d2d;
- --tags-close-hover: #64666a;
+ --navbar-bg: rgba(255, 255, 255, 0.86);
+ --navbar-text: #1f3658;
+ --navbar-hover: rgba(37, 99, 235, 0.08);
- --splitpanes-bg: #141414;
- --splitpanes-border: #303030;
- --splitpanes-splitter-bg: #1d1e1f;
- --splitpanes-splitter-hover-bg: #2d2d2d;
+ --tags-bg: transparent;
+ --tags-item-bg: rgba(255, 255, 255, 0.9);
+ --tags-item-border: rgba(148, 163, 184, 0.22);
+ --tags-item-text: #334155;
+ --tags-item-hover: #f4f8ff;
+ --tags-close-hover: rgba(37, 99, 235, 0.16);
- --blockquote-bg: #1d1e1f;
- --blockquote-border: #303030;
- --blockquote-text: #d0d0d0;
- --cron-border: #303030;
- --splitpanes-default-bg: #141414;
+ --splitpanes-bg: #f3f7fc;
+ --splitpanes-border: rgba(148, 163, 184, 0.22);
+ --splitpanes-splitter-bg: #e7eef8;
+ --splitpanes-splitter-hover-bg: #d9e6f7;
+
+ --blockquote-bg: #f3f7ff;
+ --blockquote-border: rgba(59, 130, 246, 0.36);
+ --blockquote-text: #334155;
+ --cron-border: rgba(148, 163, 184, 0.22);
+ --splitpanes-default-bg: #f3f7fc;
.sidebar-container {
.el-menu-item,
.menu-title {
- color: var(--el-text-color-regular);
+ color: var(--sidebar-text);
+ }
+
+ .el-menu-item.is-active,
+ .el-menu-item.is-active .menu-title {
+ color: var(--menu-active-text) !important;
}
& .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;
}
+
+ & .theme-dark .el-sub-menu .el-menu-item.is-active {
+ background-color: var(--menu-active-bg) !important;
+ }
}
.el-menu--horizontal {
diff --git a/src/components/AIChatSidebar/assistants/generalAssistant.js b/src/components/AIChatSidebar/assistants/generalAssistant.js
new file mode 100644
index 0000000..b5610e3
--- /dev/null
+++ b/src/components/AIChatSidebar/assistants/generalAssistant.js
@@ -0,0 +1,33 @@
+import { Cpu } from '@element-plus/icons-vue'
+
+export const generalAssistant = {
+ key: 'general',
+ label: '寰呭姙鍔╃悊',
+ title: '寰呭姙鏅鸿兘鍔╃悊',
+ tooltip: '寰呭姙鍔╂墜',
+ icon: Cpu,
+ apiBase: '/xiaozhi',
+ storageKey: 'ai_chat_uuid',
+ placeholder: '璇疯緭鍏ユ偍鐨勯棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+ welcomeMessage: '浣犲ソ',
+ description: '鎴戝彲浠ュ洖绛斾綘鐨勯棶棰橈紝涓轰綘鎻愪緵涓氬姟鏁版嵁瑙h淇℃伅銆佸鐞嗗缓璁拰杈呭姪鍐崇瓥鏀寔銆�',
+ allowFileUpload: true,
+ emptySessionText: '鏆傛棤鍘嗗彶浼氳瘽',
+ quickPrompts: [
+ '鎴戝綋鍓嶆湁鍝簺瀹℃壒寰呭姙闇�瑕佸鐞嗭紵',
+ '甯垜鍒楀嚭浠婂ぉ鏂板鐨勫鎵瑰緟鍔炪��',
+ '褰撳墠寰呮垜瀹℃壒鐨勫崟鎹紝鎸夋椂闂村�掑簭鍒楀嚭鏉ャ��',
+ '鎴戝彂璧风殑瀹℃壒閲岋紝鍝簺杩樺湪澶勭悊涓紵',
+ '鏌ヨ娴佺▼缂栧彿 XXX 鐨勫鎵硅鎯呫��',
+ '娴佺▼缂栧彿 XXX 鐜板湪鍗″湪鍝釜瀹℃壒鑺傜偣锛熷綋鍓嶅鎵逛汉鏄皝锛�',
+ '甯垜鏌ョ湅娴佺▼缂栧彿 XXX 鐨勫鎵规祦杞褰曘��',
+ '杩�7澶╂垜鐨勫鎵瑰緟鍔炵粺璁℃儏鍐垫�庝箞鏍凤紵',
+ '鏈湀鎴戠殑瀹℃壒涓紝閫氳繃銆侀┏鍥炪�佸鐞嗕腑鍚勬湁澶氬皯锛�',
+ '杩�30澶╁悇绫诲瀷瀹℃壒鏁伴噺鍒嗗竷鏄粈涔堬紵',
+ '甯垜瀹℃壒閫氳繃娴佺▼缂栧彿 XXX锛屽娉ㄢ�滃悓鎰忊�濄��',
+ '甯垜椹冲洖娴佺▼缂栧彿 XXX锛屽娉ㄢ�滆琛ュ厖璇存槑鈥濄��',
+ '鎾ら攢鎴戝垰鍒氬娴佺▼缂栧彿 XXX 鐨勫鎵规搷浣溿��',
+ '甯垜淇敼娴佺▼缂栧彿 XXX 鐨勫娉ㄤ负鈥滃凡琛ュ厖闄勪欢鈥濄��',
+ '鍒犻櫎鎴戝彂璧风殑娴佺▼缂栧彿 XXX銆�'
+ ]
+}
diff --git a/src/components/AIChatSidebar/assistants/index.js b/src/components/AIChatSidebar/assistants/index.js
new file mode 100644
index 0000000..d7081b4
--- /dev/null
+++ b/src/components/AIChatSidebar/assistants/index.js
@@ -0,0 +1,15 @@
+import { generalAssistant } from './generalAssistant'
+import { purchaseAssistant } from './purchaseAssistant'
+import { productionAssistant } from './productionAssistant'
+import { salesAssistant } from './salesAssistant'
+
+export { generalAssistant, purchaseAssistant, productionAssistant, salesAssistant }
+
+export const assistantRegistry = {
+ general: generalAssistant,
+ sales: salesAssistant,
+ purchase: purchaseAssistant,
+ production: productionAssistant
+}
+
+export const builtInAssistants = [generalAssistant, salesAssistant, purchaseAssistant, productionAssistant]
diff --git a/src/components/AIChatSidebar/assistants/productionAssistant.js b/src/components/AIChatSidebar/assistants/productionAssistant.js
new file mode 100644
index 0000000..fb2d737
--- /dev/null
+++ b/src/components/AIChatSidebar/assistants/productionAssistant.js
@@ -0,0 +1,27 @@
+import { Operation } from '@element-plus/icons-vue'
+
+export const productionAssistant = {
+ key: 'production',
+ label: '鐢熶骇鍔╃悊',
+ title: '鐢熶骇鏅鸿兘鍔╃悊',
+ tooltip: '鐢熶骇鏅鸿兘鍔╂墜',
+ icon: Operation,
+ apiBase: '/manufacturing-ai',
+ storageKey: 'production_ai_chat_uuid',
+ placeholder: '璇疯緭鍏ョ敓浜х浉鍏抽棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+ welcomeMessage: '浣犲ソ',
+ description: '鎴戝彲浠ュ洿缁曠敓浜х幇鍦恒�佽鍒掋�佸伐鍗曘�佽澶囥�佽川閲忋�佺墿鏂欍�佸紓甯稿鐞嗘彁渚涙煡璇€�侀璀︺�佸垎鏋愬拰鍔炵悊寤鸿銆�',
+ allowFileUpload: false,
+ emptySessionText: '鏆傛棤鐢熶骇浼氳瘽',
+ quickPrompts: [
+ '鏌ヨ鏈湀鐢熶骇璁″垝',
+ '鏌ョ湅鏈�杩�10鏉″伐鍗�',
+ '鏌ヨ澶嘇-01鐨勭淮淇儏鍐�',
+ '鏌ヨ川閲忎笉鍚堟牸璁板綍',
+ '鏌ヤ綆搴撳瓨鐗╂枡',
+ '鏌ヨ繎7澶╁紓甯稿鐞�',
+ '鐢熸垚鍒堕�犻璀︾湅鏉�',
+ '鍒嗘瀽鏈湀鐢熶骇瀹屾垚鐜囧拰寮傚父鐜�',
+ '缁欏嚭宸ュ崟閫炬湡鍜岃澶囧緟淇殑鍔炵悊寤鸿'
+ ]
+}
diff --git a/src/components/AIChatSidebar/assistants/purchaseAssistant.js b/src/components/AIChatSidebar/assistants/purchaseAssistant.js
new file mode 100644
index 0000000..4f2e72c
--- /dev/null
+++ b/src/components/AIChatSidebar/assistants/purchaseAssistant.js
@@ -0,0 +1,25 @@
+import { ShoppingCart } from '@element-plus/icons-vue'
+
+export const purchaseAssistant = {
+ 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: '鏆傛棤閲囪喘浼氳瘽',
+ quickPrompts: [
+ '鏈湀閲囪喘閲戦鎺掑悕鍓嶅崄鐨勭墿鏂欐湁鍝簺锛�',
+ '鍝簺閲囪喘璁㈠崟杩樻湭鍏ュ簱锛�',
+ '鏈�杩�7澶╀緵搴斿晢鍒拌揣寮傚父鏈夊摢浜涳紵',
+ '甯垜缁熻寰呬粯娆鹃噰璐崟锛�',
+ '鍒楀嚭鏈湀閲囪喘閫�璐ф儏鍐�'
+ ]
+}
diff --git a/src/components/AIChatSidebar/assistants/salesAssistant.js b/src/components/AIChatSidebar/assistants/salesAssistant.js
new file mode 100644
index 0000000..03cb102
--- /dev/null
+++ b/src/components/AIChatSidebar/assistants/salesAssistant.js
@@ -0,0 +1,28 @@
+import { TrendCharts } from '@element-plus/icons-vue'
+
+export const salesAssistant = {
+ key: 'sales',
+ label: '閿�鍞姪鎵�',
+ title: '閿�鍞櫤鑳藉姪鎵�',
+ tooltip: '閿�鍞櫤鑳藉姪鎵�',
+ icon: TrendCharts,
+ apiBase: '/sales-ai',
+ storageKey: 'sales_ai_chat_uuid',
+ placeholder: '璇疯緭鍏ラ攢鍞浉鍏抽棶棰�... (Enter 鍙戦�� / Shift+Enter 鎹㈣)',
+ welcomeMessage: '浣犲ソ',
+ description: '鎴戝彲浠ュ崗鍔╀綘鏌ヨ瀹㈡埛妗f銆侀攢鍞姤浠枫�侀攢鍞彴璐︺�侀攢鍞��璐с�佸鎴峰線鏉ャ�佸彂璐у彴璐︼紝骞堕噸鐐瑰垎鏋愬鎴锋祦澶遍闄╁強鍥炴/鎶ヤ环绛栫暐銆�',
+ allowFileUpload: false,
+ emptySessionText: '鏆傛棤閿�鍞細璇�',
+ quickPrompts: [
+ '鏌ヨ绉佹捣瀹㈡埛妗f鍓�10鏉�',
+ '鏌ヨ鍏捣瀹㈡埛妗f',
+ '鏌ヨ鏈湀閿�鍞姤浠�',
+ '鏌ヨ鏈湀閿�鍞彴璐�',
+ '鏌ヨ杩�30澶╅攢鍞��璐�',
+ '鏌ヨ杩�30澶╁鎴峰洖娆惧線鏉�',
+ '鏌ヨ鏈湀鍙戣揣鍙拌处',
+ '鏌ョ湅閿�鍞寚鏍囩粺璁�',
+ '甯垜鍋氬鎴锋祦澶遍闄╁垎鏋愶紝杩�30澶╋紝鍓�20鏉�',
+ '鐢熸垚鍥炴涓庢姤浠风瓥鐣ュ缓璁紝浼樺厛楂橀闄╁鎴�'
+ ]
+}
diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index 369d75d..c9db563 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -1,10 +1,10 @@
<template>
<div class="ai-chat-sidebar-wrapper">
<!-- 鎮诞鍥炬爣 -->
- <div class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
+ <div v-if="!hideTrigger" 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>
+ <el-icon :size="22" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
</div>
</el-tooltip>
</div>
@@ -12,14 +12,16 @@
<!-- 渚ц竟鏍忓璇濇 -->
<el-drawer
v-model="visible"
- :size="drawerSize"
- direction="rtl"
+ :size="computedDrawerSize"
+ :direction="drawerDirection"
:with-header="true"
class="ai-chat-drawer"
:modal="false"
modal-class="ai-chat-overlay"
:show-close="false"
:append-to-body="false"
+ :close-on-press-escape="!hideTrigger"
+ :close-on-click-modal="!hideTrigger"
@close="handleClose"
>
<template #header>
@@ -50,8 +52,16 @@
<el-icon :size="18"><Plus /></el-icon>
</el-button>
</el-tooltip>
- <div class="action-divider"></div>
- <el-tooltip content="鍏抽棴" placement="bottom">
+ <el-button
+ v-if="headerExtraActionText"
+ link
+ class="header-action-btn header-action-btn--text"
+ @click="handleHeaderExtraAction"
+ >
+ {{ headerExtraActionText }}
+ </el-button>
+ <div v-if="!hideTrigger" class="action-divider"></div>
+ <el-tooltip v-if="!hideTrigger" content="鍏抽棴" placement="bottom">
<el-button link class="header-action-btn close-btn" @click="handleManualClose">
<el-icon :size="18"><Close /></el-icon>
</el-button>
@@ -105,23 +115,17 @@
<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 class="assistant-model-shell">
+ <div class="assistant-model-cut">
+ <img
+ v-if="currentAssistantAvatar"
+ class="assistant-model-img"
+ :src="currentAssistantAvatar"
+ :alt="currentAssistant.label"
+ />
+ <div v-else class="assistant-model-fallback">
+ <el-icon :size="30"><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">
@@ -169,10 +173,6 @@
</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"
@@ -186,6 +186,37 @@
<div class="message-content">
<!-- 鏂囨湰鍐呭 -->
<div class="text-box" v-html="message.htmlContent"></div>
+
+ <div v-if="message.localUploadFiles?.length" class="message-local-file-list">
+ <div
+ v-for="(file, fileIndex) in message.localUploadFiles"
+ :key="`${file.previewId || file.name}-${fileIndex}`"
+ :class="['message-local-file-item', { clickable: !!file.accessUrl && !file.isImage }]"
+ @click="handleMessageFileClick(file)"
+ >
+ <el-image
+ v-if="file.isImage && file.previewUrl"
+ :src="file.previewUrl"
+ :preview-src-list="getImagePreviewList(message.localUploadFiles)"
+ :initial-index="getImagePreviewInitialIndex(message.localUploadFiles, file.previewUrl)"
+ :z-index="4000"
+ preview-teleported
+ fit="cover"
+ class="message-local-file-thumb"
+ />
+ <el-icon v-else class="message-local-file-icon"><Document /></el-icon>
+ <div class="message-local-file-meta">
+ <span
+ :class="['message-local-file-name', { clickable: !!file.accessUrl }]"
+ :title="file.name"
+ @click.stop="openMessageAttachment(file)"
+ >
+ {{ file.name }}
+ </span>
+ <small v-if="Number(file.size) > 0" class="message-local-file-size">{{ formatFileSize(file.size) }}</small>
+ </div>
+ </div>
+ </div>
<!-- 鍥捐〃鍐呭 -->
<div v-if="message.chartOptions && message.chartRenderReady" class="charts-wrapper">
@@ -211,7 +242,303 @@
</el-table>
</div>
- <!-- 鎵撳瓧涓姩鐢� -->
+ <div v-if="message.manufacturingData" class="manufacturing-card">
+ <div class="manufacturing-card__title">{{ getManufacturingTypeLabel(message.type) }}</div>
+
+ <div
+ v-if="message.manufacturingData.summaryEntries?.length || message.manufacturingData.coreMetrics?.length"
+ class="manufacturing-summary-grid"
+ >
+ <div
+ v-for="(entry, entryIndex) in message.manufacturingData.summaryEntries"
+ :key="`summary-${entry.key}-${entryIndex}`"
+ class="manufacturing-summary-item"
+ >
+ <span class="manufacturing-summary-label">{{ entry.label }}</span>
+ <strong class="manufacturing-summary-value">{{ entry.value }}</strong>
+ </div>
+ <div
+ v-for="(metric, metricIndex) in message.manufacturingData.coreMetrics"
+ :key="`core-${metric.key}-${metricIndex}`"
+ class="manufacturing-summary-item manufacturing-summary-item--core"
+ >
+ <span class="manufacturing-summary-label">{{ metric.label }}</span>
+ <strong class="manufacturing-summary-value">{{ metric.value }}</strong>
+ </div>
+ </div>
+
+ <div v-if="message.manufacturingData.warningItems?.length" class="manufacturing-warning-list">
+ <div
+ v-for="(warning, warningIndex) in message.manufacturingData.warningItems"
+ :key="`warning-${warning.title || warningIndex}`"
+ class="manufacturing-warning-item"
+ >
+ <div class="manufacturing-warning-item__head">
+ <el-tag size="small" :type="getManufacturingWarningLevelType(warning.level)">
+ {{ getManufacturingWarningLevelLabel(warning.level) }}
+ </el-tag>
+ <strong>{{ warning.title || `棰勮 ${warningIndex + 1}` }}</strong>
+ <span v-if="warning.count !== '' && warning.count !== null && warning.count !== undefined" class="manufacturing-warning-count">
+ {{ warning.count }}
+ </span>
+ </div>
+ <p v-if="warning.detail" class="manufacturing-warning-detail">{{ warning.detail }}</p>
+ </div>
+ </div>
+
+ <div
+ v-if="message.manufacturingData.listItems?.length && message.manufacturingData.columns?.length"
+ class="table-wrapper manufacturing-table-wrapper"
+ >
+ <el-table :data="message.manufacturingData.listItems" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.manufacturingData.columns"
+ :key="col"
+ :label="getStructuredFieldLabel(col)"
+ min-width="140"
+ show-overflow-tooltip
+ >
+ <template #default="{ row }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <div v-if="message.manufacturingData.actionCards?.length" class="manufacturing-action-list">
+ <div
+ v-for="(card, cardIndex) in message.manufacturingData.actionCards"
+ :key="card.runtimeKey || `${card.code}-${card.targetApi}-${cardIndex}`"
+ class="manufacturing-action-card"
+ >
+ <div class="manufacturing-action-card__head">
+ <strong>{{ card.name || `鍔ㄤ綔 ${cardIndex + 1}` }}</strong>
+ <el-tag size="small" type="info">{{ getNormalizedRequestMethod(card.method) }}</el-tag>
+ </div>
+ <div class="manufacturing-action-card__meta">
+ <span>{{ card.code || '--' }}</span>
+ <span>{{ card.targetApi || '--' }}</span>
+ </div>
+ <p v-if="card.description" class="manufacturing-action-card__desc">{{ card.description }}</p>
+ <div v-if="card.requiredFields?.length" class="manufacturing-required-fields">
+ <span>蹇呭~瀛楁</span>
+ <el-tag
+ v-for="field in card.requiredFields"
+ :key="field"
+ size="small"
+ type="warning"
+ >
+ {{ getStructuredPathLabel(field) }}
+ </el-tag>
+ </div>
+ <el-input
+ v-model="card.payloadText"
+ type="textarea"
+ :rows="6"
+ resize="vertical"
+ :disabled="card.executing"
+ placeholder="璇疯緭鍏� JSON 璇锋眰鍙傛暟"
+ />
+ <div class="manufacturing-action-footer">
+ <span
+ v-if="card.executeResult"
+ :class="['manufacturing-action-result', card.executeError ? 'error' : 'success']"
+ >
+ {{ card.executeResult }}
+ </span>
+ <el-button
+ type="primary"
+ size="small"
+ :loading="card.executing"
+ @click="executeManufacturingAction(message, card, cardIndex)"
+ >
+ 纭骞舵墽琛�
+ </el-button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div v-if="message.salesData" class="sales-structured-card">
+ <div class="sales-structured-card__title">{{ getSalesTypeLabel(message.type) }}</div>
+
+ <div v-if="message.salesData.summaryEntries?.length" class="sales-summary-grid">
+ <div
+ v-for="(entry, entryIndex) in message.salesData.summaryEntries"
+ :key="`sales-summary-${entry.key}-${entryIndex}`"
+ class="sales-summary-item"
+ >
+ <span class="sales-summary-label">{{ entry.label }}</span>
+ <strong class="sales-summary-value">{{ entry.value }}</strong>
+ </div>
+ </div>
+
+ <div v-if="message.type === 'sales_customer_churn_risk' && message.salesData.listItems?.length" class="sales-focus-list">
+ <div
+ v-for="(item, itemIndex) in message.salesData.listItems"
+ :key="`risk-${item.customerName || itemIndex}`"
+ class="sales-focus-item"
+ >
+ <div class="sales-focus-item__head">
+ <strong>{{ formatStructuredValue(item.customerName) }}</strong>
+ <div class="sales-focus-tags">
+ <el-tag size="small" :type="getSalesLevelTagType(item.riskLevel)">
+ {{ getSalesLevelLabel(item.riskLevel, 'risk') }}
+ </el-tag>
+ <el-tag size="small" type="warning">椋庨櫓鍒� {{ formatStructuredValue(item.riskScore) }}</el-tag>
+ </div>
+ </div>
+ <div class="sales-focus-metrics">
+ <span>寰呭洖娆撅細{{ formatStructuredValue(item.pendingAmount) }}</span>
+ <span>寰呭洖娆惧崰姣旓細{{ formatStructuredValue(item.pendingRate) }}</span>
+ <span>璺濅笂娆′笅鍗曪細{{ formatStructuredValue(item.daysSinceLastOrder) }}</span>
+ </div>
+ <div v-if="toStructuredStringArray(item.riskReasons).length" class="sales-focus-reasons">
+ <el-tag
+ v-for="(reason, reasonIndex) in toStructuredStringArray(item.riskReasons)"
+ :key="`${item.customerName || itemIndex}-reason-${reasonIndex}`"
+ size="small"
+ type="danger"
+ effect="plain"
+ >
+ {{ reason }}
+ </el-tag>
+ </div>
+ </div>
+ </div>
+
+ <div v-if="message.type === 'sales_collection_quote_strategy' && message.salesData.listItems?.length" class="sales-focus-list">
+ <div
+ v-for="(item, itemIndex) in message.salesData.listItems"
+ :key="`strategy-${item.customerName || itemIndex}`"
+ class="sales-focus-item sales-focus-item--strategy"
+ >
+ <div class="sales-focus-item__head">
+ <strong>{{ formatStructuredValue(item.customerName) }}</strong>
+ <div class="sales-focus-tags">
+ <el-tag size="small" :type="getSalesLevelTagType(item.priority)">
+ {{ getSalesLevelLabel(item.priority, 'priority') }}
+ </el-tag>
+ <el-tag size="small" type="success">杞寲鐜� {{ formatStructuredValue(item.quoteConversionRate) }}</el-tag>
+ </div>
+ </div>
+ <div class="sales-focus-metrics">
+ <span>寰呭洖娆撅細{{ formatStructuredValue(item.pendingAmount) }}</span>
+ <span v-if="item.nextAction">涓嬩竴姝ワ細{{ formatStructuredValue(item.nextAction) }}</span>
+ </div>
+ <p v-if="item.collectionStrategy" class="sales-strategy-line">
+ <strong>鍥炴绛栫暐锛�</strong>{{ formatStructuredValue(item.collectionStrategy) }}
+ </p>
+ <p v-if="item.quotationStrategy" class="sales-strategy-line">
+ <strong>鎶ヤ环绛栫暐锛�</strong>{{ formatStructuredValue(item.quotationStrategy) }}
+ </p>
+ </div>
+ </div>
+
+ <div
+ v-if="message.salesData.listItems?.length && message.salesData.columns?.length && !isSalesFocusType(message.type)"
+ class="table-wrapper manufacturing-table-wrapper"
+ >
+ <el-table :data="message.salesData.listItems" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.salesData.columns"
+ :key="col"
+ :label="getStructuredFieldLabel(col)"
+ min-width="140"
+ show-overflow-tooltip
+ >
+ <template #default="{ row }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <div v-if="message.salesData.topCustomers?.length && message.salesData.topCustomerColumns?.length" class="table-wrapper manufacturing-table-wrapper">
+ <div class="sales-section-title">閲嶇偣瀹㈡埛</div>
+ <el-table :data="message.salesData.topCustomers" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.salesData.topCustomerColumns"
+ :key="`top-customer-${col}`"
+ :label="getStructuredFieldLabel(col)"
+ min-width="120"
+ show-overflow-tooltip
+ >
+ <template #default="{ row }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <div v-if="message.salesData.contractTrend?.length && message.salesData.contractTrendColumns?.length" class="table-wrapper manufacturing-table-wrapper">
+ <div class="sales-section-title">鍚堝悓瓒嬪娍</div>
+ <el-table :data="message.salesData.contractTrend" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.salesData.contractTrendColumns"
+ :key="`contract-trend-${col}`"
+ :label="getStructuredFieldLabel(col)"
+ min-width="120"
+ show-overflow-tooltip
+ >
+ <template #default="{ row }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+
+ <div v-if="message.purchaseData" class="sales-structured-card">
+ <div class="sales-structured-card__title">{{ getPurchaseTypeLabel(message.type) }}</div>
+
+ <div v-if="message.purchaseData.summaryEntries?.length" class="sales-summary-grid">
+ <div
+ v-for="(entry, entryIndex) in message.purchaseData.summaryEntries"
+ :key="`purchase-summary-${entry.key}-${entryIndex}`"
+ class="sales-summary-item"
+ >
+ <span class="sales-summary-label">{{ entry.label }}</span>
+ <strong class="sales-summary-value">{{ entry.value }}</strong>
+ </div>
+ </div>
+
+ <div
+ v-if="message.purchaseData.listItems?.length && message.purchaseData.columns?.length"
+ class="table-wrapper manufacturing-table-wrapper"
+ >
+ <el-table :data="message.purchaseData.listItems" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.purchaseData.columns"
+ :key="col"
+ :label="getStructuredFieldLabel(col)"
+ min-width="140"
+ show-overflow-tooltip
+ >
+ <template #default="{ row }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+
+ <div v-if="message.purchaseIntentData?.quickPrompts?.length" class="purchase-intent-quick-prompt-wrap">
+ <div class="purchase-intent-quick-prompt-title">璇曡瘯浠ヤ笅鎻愰棶</div>
+ <div class="quick-prompt-list purchase-intent-quick-prompt-list">
+ <button
+ v-for="prompt in message.purchaseIntentData.quickPrompts"
+ :key="`purchase-intent-${prompt}`"
+ type="button"
+ class="quick-prompt-btn"
+ :disabled="isSending"
+ @click="sendQuickPrompt(prompt)"
+ >
+ {{ prompt }}
+ </button>
+ </div>
+ </div>
+
<div v-if="message.purchaseAnalysisData" class="purchase-confirm-card">
<div class="purchase-confirm-header">
<span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '閲囪喘涓氬姟' }}</span>
@@ -443,9 +770,22 @@
</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>
+ <div v-for="(file, fileIndex) in selectedFileSnapshots" :key="`${file.previewId || file.name}-${fileIndex}`" class="selected-file-tag">
+ <el-image
+ v-if="file.isImage && file.previewUrl"
+ :src="file.previewUrl"
+ :preview-src-list="getImagePreviewList(selectedFileSnapshots)"
+ :initial-index="getImagePreviewInitialIndex(selectedFileSnapshots, file.previewUrl)"
+ :z-index="4000"
+ preview-teleported
+ fit="cover"
+ class="selected-file-thumb"
+ />
+ <el-icon v-else><Document /></el-icon>
+ <div class="selected-file-meta">
+ <span class="file-name">{{ file.name }}</span>
+ <small class="file-size">{{ formatFileSize(file.size) }}</small>
+ </div>
<el-icon class="remove-file" @click="removeSelectedFile(fileIndex)"><Close /></el-icon>
</div>
</div>
@@ -478,8 +818,17 @@
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'
+import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, Promotion, RefreshRight } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { builtInAssistants, generalAssistant } from './assistants'
+import todoAssistantAvatar from '@/assets/AI/寰呭姙鍔╂墜.png'
+import salesAssistantAvatar from '@/assets/AI/閿�鍞姪鎵�.png'
+import purchaseAssistantAvatar from '@/assets/AI/閲囪喘鍔╂墜.png'
+import productionAssistantAvatar from '@/assets/AI/鐢熶骇鍔╂墜.png'
+import financeAssistantAvatar from '@/assets/AI/璐㈠姟鍔╂墜.png'
+import bossAssistantAvatar from '@/assets/AI/寰呭姙鍔╂墜.png'
+
+const emit = defineEmits(['header-extra-action'])
const props = defineProps({
assistants: {
@@ -489,72 +838,51 @@
defaultAssistant: {
type: String,
default: ''
+ },
+ hideTrigger: {
+ type: Boolean,
+ default: false
+ },
+ autoOpen: {
+ type: Boolean,
+ default: false
+ },
+ drawerSize: {
+ type: [String, Number],
+ default: ''
+ },
+ drawerDirection: {
+ type: String,
+ default: 'rtl'
+ },
+ headerExtraActionText: {
+ 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 hideTrigger = computed(() => props.hideTrigger)
+const headerExtraActionText = computed(() => String(props.headerExtraActionText || '').trim())
+const drawerDirection = computed(() => (props.drawerDirection === 'ttb' || props.drawerDirection === 'btt' || props.drawerDirection === 'ltr' || props.drawerDirection === 'rtl')
+ ? props.drawerDirection
+ : 'rtl')
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 assistantAvatarByKey = {
+ general: todoAssistantAvatar,
+ todo: todoAssistantAvatar,
+ purchase: purchaseAssistantAvatar,
+ sales: salesAssistantAvatar,
+ production: productionAssistantAvatar,
+ finance: financeAssistantAvatar,
+ boss: bossAssistantAvatar
}
+const currentAssistantAvatar = computed(() => {
+ const assistant = currentAssistant.value || {}
+ return assistant.avatar || assistantAvatarByKey[assistant.key] || ''
+})
+const showAssistantSwitch = computed(() => assistants.value.length > 1)
const quickPromptLimit = 3
const quickPromptStart = ref(0)
const quickPrompts = computed(() => {
@@ -562,7 +890,7 @@
if (Array.isArray(assistant.quickPrompts) && assistant.quickPrompts.length) {
return assistant.quickPrompts
}
- return assistantQuickPromptMap[assistant.key] || assistantQuickPromptMap.general
+ return generalAssistant.quickPrompts || []
})
const displayedQuickPrompts = computed(() => {
const prompts = quickPrompts.value || []
@@ -578,17 +906,19 @@
const visible = ref(false)
const windowWidth = ref(window.innerWidth)
-const drawerSize = computed(() => {
+const responsiveDrawerSize = computed(() => {
if (windowWidth.value < 768) return '100%'
if (windowWidth.value < 1200) return '50%'
return '50%'
})
+const computedDrawerSize = computed(() => props.drawerSize || responsiveDrawerSize.value)
const messageListRef = ref(null)
const isSending = ref(false)
const currentAbortController = ref(null)
const inputMessage = ref('')
const selectedFiles = ref([])
const uploadFileList = ref([])
+const selectedFileSnapshots = ref([])
const messages = ref([])
const uuid = ref('')
const chartInstances = ref({})
@@ -600,6 +930,127 @@
purchase_return_order: '閲囪喘閫�璐у崟',
unknown: '鏈煡閲囪喘涓氬姟'
}
+const salesStructuredTypeSet = new Set([
+ 'sales_customer_profile_list',
+ 'sales_quotation_list',
+ 'sales_ledger_list',
+ 'sales_return_list',
+ 'sales_customer_interaction_list',
+ 'sales_shipping_list',
+ 'sales_dashboard',
+ 'sales_customer_churn_risk',
+ 'sales_collection_quote_strategy'
+])
+const salesFocusTypeSet = new Set([
+ 'sales_customer_churn_risk',
+ 'sales_collection_quote_strategy'
+])
+const salesTypeLabelMap = {
+ sales_customer_profile_list: '瀹㈡埛妗f',
+ sales_quotation_list: '閿�鍞姤浠�',
+ sales_ledger_list: '閿�鍞彴璐�',
+ sales_return_list: '閿�鍞��璐�',
+ sales_customer_interaction_list: '瀹㈡埛寰�鏉�',
+ sales_shipping_list: '鍙戣揣鍙拌处',
+ sales_dashboard: '閿�鍞寚鏍囩粺璁�',
+ sales_customer_churn_risk: '瀹㈡埛娴佸け椋庨櫓鍒嗘瀽',
+ sales_collection_quote_strategy: '鍥炴涓庢姤浠风瓥鐣ュ缓璁�'
+}
+const purchaseTypeLabelMap = {
+ purchase_material_rank: '閲囪喘鐗╂枡閲戦鎺掕',
+ purchase_pending_payment_list: '寰呬粯娆鹃噰璐崟'
+}
+const manufacturingStructuredTypeSet = new Set([
+ 'manufacturing_site_snapshot',
+ 'manufacturing_plan_list',
+ 'manufacturing_workorder_list',
+ 'manufacturing_device_list',
+ 'manufacturing_device_repair_list',
+ 'manufacturing_quality_list',
+ 'manufacturing_material_list',
+ 'manufacturing_exception_list',
+ 'manufacturing_warning',
+ 'manufacturing_analysis',
+ 'manufacturing_action_plan'
+])
+const manufacturingListTypeSet = new Set([
+ 'manufacturing_plan_list',
+ 'manufacturing_workorder_list',
+ 'manufacturing_device_list',
+ 'manufacturing_device_repair_list',
+ 'manufacturing_quality_list',
+ 'manufacturing_material_list',
+ 'manufacturing_exception_list'
+])
+const manufacturingTypeLabelMap = {
+ manufacturing_site_snapshot: '鐢熶骇鐜板満姒傝',
+ manufacturing_plan_list: '璁″垝鏌ヨ',
+ manufacturing_workorder_list: '宸ュ崟鏌ヨ',
+ manufacturing_device_list: '璁惧鏌ヨ',
+ manufacturing_device_repair_list: '璁惧缁翠慨璁板綍鏌ヨ',
+ manufacturing_quality_list: '璐ㄩ噺鏌ヨ',
+ manufacturing_material_list: '鐗╂枡鏌ヨ',
+ manufacturing_exception_list: '寮傚父鏌ヨ',
+ manufacturing_warning: '棰勮鐪嬫澘',
+ manufacturing_analysis: '缁忚惀鍒嗘瀽',
+ manufacturing_action_plan: '鍔炵悊寤鸿'
+}
+const structuredFieldLabelMap = {
+ workOrderNo: '宸ュ崟鍙�',
+ planEndTime: '璁″垝缁撴潫鏃堕棿',
+ planStartTime: '璁″垝寮�濮嬫椂闂�',
+ timeRange: '鏃堕棿鑼冨洿',
+ startDate: '寮�濮嬫棩鏈�',
+ endDate: '缁撴潫鏃ユ湡',
+ warningCount: '棰勮鏁伴噺',
+ overduePlanCount: '閫炬湡璁″垝鏁�',
+ overdueWorkOrderCount: '閫炬湡宸ュ崟鏁�',
+ actionCount: '寤鸿鍔ㄤ綔鏁�',
+ qualityOpenCount: '璐ㄩ噺寰呭鐞嗘暟',
+ lowStockCount: '浣庡簱瀛樻暟',
+ exceptionCount: '寮傚父鏁�',
+ userId: '鐢ㄦ埛ID',
+ tenantId: '绉熸埛ID',
+ status: '鐘舵��',
+ deviceName: '璁惧鍚嶇О',
+ deviceModel: '璁惧鍨嬪彿',
+ pendingRepairCount: '寰呯淮淇暟',
+ repairTime: '缁翠慨鏃堕棿',
+ repairName: '鎶ヤ慨浜�',
+ maintenanceName: '缁翠慨浜哄憳',
+ level: '棰勮绛夌骇',
+ title: '鏍囬',
+ count: '鏁伴噺',
+ detail: '璇︽儏',
+ remark: '澶囨敞',
+ createTime: '鍒涘缓鏃堕棿',
+ updateTime: '鏇存柊鏃堕棿',
+ exceptionType: '寮傚父绫诲瀷',
+ materialName: '鐗╂枡鍚嶇О',
+ stockQty: '搴撳瓨閲�'
+}
+Object.assign(structuredFieldLabelMap, {
+ customerName: '瀹㈡埛鍚嶇О',
+ riskLevel: '椋庨櫓绛夌骇',
+ riskScore: '椋庨櫓璇勫垎',
+ riskReasons: '椋庨櫓鍘熷洜',
+ pendingAmount: '寰呭洖娆鹃噾棰�',
+ pendingRate: '寰呭洖娆惧崰姣�',
+ daysSinceLastOrder: '璺濅笂娆′笅鍗曞ぉ鏁�',
+ priority: '浼樺厛绾�',
+ quoteConversionRate: '鎶ヤ环杞寲鐜�',
+ collectionStrategy: '鍥炴绛栫暐',
+ quotationStrategy: '鎶ヤ环绛栫暐',
+ nextAction: '涓嬩竴姝ュ姩浣�',
+ contractAmountTotal: '鍚堝悓鎬婚',
+ receivedAmountTotal: '宸插洖娆鹃噾棰�',
+ pendingAmountTotal: '寰呭洖娆炬�婚',
+ shipRate: '鍙戣揣鐜�',
+ pendingOrderCount: '寰呬粯娆捐鍗曟暟',
+ totalContractAmount: '寰呬粯娆惧悎鍚屾�婚',
+ totalPaidAmount: '宸蹭粯娆炬�婚',
+ totalPendingAmount: '寰呬粯娆炬�婚'
+})
const purchasePayloadFieldLabelMap = {
purchaseLedgers: '閲囪喘鍙拌处',
productData: '浜у搧鏄庣粏',
@@ -752,6 +1203,463 @@
isInspected: 'isInspected',
isChecked: 'isInspected'
}
+const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
+
+const stringifyStructuredPayload = (value, spaces = 2) => {
+ if (typeof value === 'string') return value
+ try {
+ return JSON.stringify(value ?? {}, null, spaces)
+ } catch (err) {
+ return '{}'
+ }
+}
+
+const structuredFieldTokenLabelMap = {
+ time: '鏃堕棿',
+ range: '鑼冨洿',
+ start: '寮�濮�',
+ end: '缁撴潫',
+ date: '鏃ユ湡',
+ warning: '棰勮',
+ overdue: '閫炬湡',
+ plan: '璁″垝',
+ work: '宸�',
+ order: '鍗�',
+ workorder: '宸ュ崟',
+ count: '鏁伴噺',
+ quality: '璐ㄩ噺',
+ low: '浣�',
+ stock: '搴撳瓨',
+ exception: '寮傚父',
+ action: '鍔ㄤ綔',
+ user: '鐢ㄦ埛',
+ tenant: '绉熸埛',
+ id: 'ID',
+ no: '缂栧彿',
+ number: '缂栧彿',
+ code: '缂栫爜',
+ name: '鍚嶇О',
+ status: '鐘舵��',
+ level: '绛夌骇',
+ title: '鏍囬',
+ detail: '璇︽儏',
+ total: '鎬绘暟',
+ rate: '姣旂巼',
+ type: '绫诲瀷',
+ pending: '寰�',
+ repair: '缁翠慨',
+ device: '璁惧',
+ material: '鐗╂枡'
+}
+
+const convertStructuredFieldKeyToChinese = (fieldKey = '') => {
+ const key = String(fieldKey || '').trim()
+ if (!key) return '-'
+ if (structuredFieldLabelMap[key]) return structuredFieldLabelMap[key]
+ if (/[\u4e00-\u9fa5]/.test(key)) return key
+
+ const rawTokens = key
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
+ .replace(/[_-]+/g, ' ')
+ .trim()
+ .split(/\s+/)
+ .filter(Boolean)
+ .map(token => token.toLowerCase())
+
+ if (!rawTokens.length) return '瀛楁'
+
+ const mappedTokens = rawTokens
+ .map(token => structuredFieldTokenLabelMap[token] || '')
+ .filter(Boolean)
+
+ if (mappedTokens.length) {
+ return mappedTokens.join('')
+ }
+
+ return '瀛楁'
+}
+
+const getStructuredFieldLabel = (fieldKey = '') => {
+ return convertStructuredFieldKeyToChinese(fieldKey)
+}
+
+const getStructuredPathLabel = (fieldPath = '') => {
+ const path = String(fieldPath || '').trim()
+ if (!path) return '-'
+
+ const segments = path
+ .replace(/\[(\d+)]/g, '.$1')
+ .split('.')
+ .filter(Boolean)
+
+ if (!segments.length) return getStructuredFieldLabel(path)
+
+ return segments.map((segment) => {
+ if (/^\d+$/.test(segment)) {
+ return `绗�${Number(segment) + 1}椤筦
+ }
+ return getStructuredFieldLabel(segment)
+ }).join(' / ')
+}
+
+const formatStructuredValue = (value) => {
+ if (value === null || value === undefined || value === '') return '-'
+ if (Array.isArray(value)) {
+ const preview = value.slice(0, 3).map(item => formatStructuredValue(item)).join('銆�')
+ return value.length > 3 ? `${preview} 绛�${value.length}椤筦 : preview
+ }
+ if (isPlainObject(value)) return stringifyStructuredPayload(value, 0)
+ return String(value)
+}
+
+const normalizeManufacturingSummaryEntries = (summary) => {
+ if (!isPlainObject(summary)) return []
+ return Object.entries(summary)
+ .filter(([, value]) => value !== undefined && value !== null && `${value}`.trim() !== '')
+ .map(([key, value]) => ({
+ key,
+ label: getStructuredFieldLabel(key),
+ value: formatStructuredValue(value)
+ }))
+}
+
+const normalizeManufacturingCoreMetrics = (coreMetrics) => {
+ if (Array.isArray(coreMetrics)) {
+ return coreMetrics.map((item, index) => {
+ if (isPlainObject(item)) {
+ const label = item.label || item.name || item.key || `鎸囨爣${index + 1}`
+ const metricValue = item.value ?? item.metricValue ?? item.data ?? '-'
+ const unit = item.unit ? ` ${item.unit}` : ''
+ return {
+ key: String(item.key || item.name || index),
+ label,
+ value: `${formatStructuredValue(metricValue)}${unit}`.trim()
+ }
+ }
+ return {
+ key: String(index),
+ label: `鎸囨爣${index + 1}`,
+ value: formatStructuredValue(item)
+ }
+ })
+ }
+
+ if (isPlainObject(coreMetrics)) {
+ return Object.entries(coreMetrics).map(([key, value]) => ({
+ key,
+ label: getStructuredFieldLabel(key),
+ value: formatStructuredValue(value)
+ }))
+ }
+
+ return []
+}
+
+const normalizeManufacturingWarningItems = (items = []) => {
+ if (!Array.isArray(items)) return []
+ return items
+ .filter(item => isPlainObject(item))
+ .map(item => ({
+ level: String(item.level || '').toLowerCase(),
+ title: item.title || '',
+ count: item.count ?? '',
+ detail: item.detail ?? ''
+ }))
+}
+
+const inferManufacturingColumns = (items = []) => {
+ if (!Array.isArray(items) || !items.length) return []
+ const fieldSet = new Set()
+ items.forEach((item) => {
+ if (!isPlainObject(item)) return
+ Object.keys(item).forEach((key) => fieldSet.add(key))
+ })
+ return Array.from(fieldSet)
+}
+
+const getManufacturingActionCardRuntimeKey = (card = {}, index = 0) => {
+ const code = String(card?.code || '').trim()
+ const api = String(card?.targetApi || '').trim()
+ const name = String(card?.name || '').trim()
+ return `${code}::${api}::${name}::${index}`
+}
+
+const normalizeManufacturingActionCards = (actionCards = [], previousCards = []) => {
+ const previousMap = new Map()
+ if (Array.isArray(previousCards)) {
+ previousCards.forEach((card, index) => {
+ previousMap.set(getManufacturingActionCardRuntimeKey(card, index), card)
+ })
+ }
+
+ return (Array.isArray(actionCards) ? actionCards : [])
+ .filter(card => isPlainObject(card))
+ .map((card, index) => {
+ const runtimeKey = getManufacturingActionCardRuntimeKey(card, index)
+ const previousCard = previousMap.get(runtimeKey)
+ const fallbackPayloadText = stringifyStructuredPayload(card.examplePayload, 2)
+ return {
+ ...card,
+ runtimeKey,
+ payloadText: previousCard?.payloadText ?? fallbackPayloadText,
+ executing: Boolean(previousCard?.executing),
+ executed: Boolean(previousCard?.executed),
+ executeResult: previousCard?.executeResult || '',
+ executeError: Boolean(previousCard?.executeError)
+ }
+ })
+}
+
+const buildManufacturingStructuredData = (parsedData, previousData = null) => {
+ const type = String(parsedData?.type || '')
+ if (!manufacturingStructuredTypeSet.has(type)) return null
+
+ const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+ const items = Array.isArray(rawData.items) ? rawData.items.filter(item => isPlainObject(item)) : []
+ const warningItems = type === 'manufacturing_warning' ? normalizeManufacturingWarningItems(items) : []
+ const listItems = manufacturingListTypeSet.has(type) ? items : []
+ const actionCards = type === 'manufacturing_action_plan'
+ ? normalizeManufacturingActionCards(rawData.actionCards, previousData?.actionCards)
+ : []
+
+ return {
+ type,
+ summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+ coreMetrics: normalizeManufacturingCoreMetrics(rawData.coreMetrics),
+ listItems,
+ columns: inferManufacturingColumns(listItems),
+ warningItems,
+ actionCards
+ }
+}
+
+const getManufacturingTypeLabel = (type = '') => manufacturingTypeLabelMap[String(type || '')] || '鍒堕�犵粨鏋�'
+
+const inferSalesColumns = (items = []) => {
+ if (!Array.isArray(items) || !items.length) return []
+ const fieldSet = new Set()
+ items.forEach((item) => {
+ if (!isPlainObject(item)) return
+ Object.keys(item).forEach((key) => fieldSet.add(key))
+ })
+ return Array.from(fieldSet)
+}
+
+const normalizeSalesListItems = (items) => {
+ if (!Array.isArray(items)) return []
+ return items.filter(item => isPlainObject(item))
+}
+
+const buildSalesStructuredData = (parsedData) => {
+ const type = String(parsedData?.type || '')
+ if (!salesStructuredTypeSet.has(type)) return null
+
+ const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+ const listItems = normalizeSalesListItems(rawData.items)
+ const topCustomers = normalizeSalesListItems(rawData.topCustomers)
+ const contractTrend = normalizeSalesListItems(rawData.contractTrend)
+
+ return {
+ type,
+ summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+ listItems,
+ columns: inferSalesColumns(listItems),
+ topCustomers,
+ topCustomerColumns: inferSalesColumns(topCustomers),
+ contractTrend,
+ contractTrendColumns: inferSalesColumns(contractTrend)
+ }
+}
+
+const getSalesTypeLabel = (type = '') => salesTypeLabelMap[String(type || '')] || '閿�鍞煡璇㈢粨鏋�'
+
+const buildPurchaseStructuredData = (parsedData) => {
+ if (parsedData?.success !== true) return null
+
+ const type = String(parsedData?.type || '')
+ if (!type.startsWith('purchase_')) return null
+
+ const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+ const listItems = normalizeSalesListItems(rawData.items)
+
+ return {
+ type,
+ summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+ listItems,
+ columns: inferSalesColumns(listItems)
+ }
+}
+
+const getPurchaseTypeLabel = (type = '') => purchaseTypeLabelMap[String(type || '')] || '閲囪喘鏌ヨ缁撴灉'
+
+const isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || ''))
+
+const getSalesLevelTagType = (level = '') => {
+ const normalizedLevel = String(level || '').toLowerCase()
+ if (normalizedLevel === 'high') return 'danger'
+ if (normalizedLevel === 'medium') return 'warning'
+ if (normalizedLevel === 'low') return 'success'
+ return 'info'
+}
+
+const getSalesLevelLabel = (level = '', mode = 'risk') => {
+ const normalizedLevel = String(level || '').toLowerCase()
+ const suffix = mode === 'priority' ? '浼樺厛绾�' : '椋庨櫓'
+ if (normalizedLevel === 'high') return `楂�${suffix}`
+ if (normalizedLevel === 'medium') return `涓�${suffix}`
+ if (normalizedLevel === 'low') return `浣�${suffix}`
+ if (!normalizedLevel) return mode === 'priority' ? '鏈垎绾�' : '鏈瘎浼�'
+ return normalizedLevel.toUpperCase()
+}
+
+const toStructuredStringArray = (value) => {
+ if (Array.isArray(value)) {
+ return value.map(item => String(item || '').trim()).filter(Boolean)
+ }
+ if (typeof value === 'string') {
+ return value
+ .split(/[,\uFF0C\u3001;\uFF1B\n]/)
+ .map(item => item.trim())
+ .filter(Boolean)
+ }
+ return []
+}
+
+const normalizePurchaseIntentNotRecognizedData = (parsedData) => {
+ if (String(parsedData?.type || '') !== 'purchase_intent_not_recognized') return null
+ const quickPrompts = toStructuredStringArray(parsedData?.data?.quickPrompts)
+ if (!quickPrompts.length) return null
+ return { quickPrompts }
+}
+
+const getManufacturingWarningLevelType = (level = '') => {
+ const normalizedLevel = String(level || '').toLowerCase()
+ if (normalizedLevel === 'high') return 'danger'
+ if (normalizedLevel === 'medium') return 'warning'
+ return 'info'
+}
+
+const getManufacturingWarningLevelLabel = (level = '') => {
+ const normalizedLevel = String(level || '').toLowerCase()
+ if (normalizedLevel === 'high') return '楂�'
+ if (normalizedLevel === 'medium') return '涓�'
+ return normalizedLevel ? normalizedLevel.toUpperCase() : '涓�鑸�'
+}
+
+const normalizeRequestMethod = (method = 'POST') => {
+ const normalized = String(method || 'POST').trim().toUpperCase()
+ if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(normalized)) return normalized
+ return 'POST'
+}
+
+const getNormalizedRequestMethod = (method) => normalizeRequestMethod(method)
+
+const getPayloadValueByPath = (payload, fieldPath = '') => {
+ const normalizedPath = String(fieldPath || '').trim()
+ if (!normalizedPath || !isPlainObject(payload)) return undefined
+
+ const pathSegments = normalizedPath
+ .replace(/\[(\d+)]/g, '.$1')
+ .split('.')
+ .filter(Boolean)
+
+ return pathSegments.reduce((current, segment) => {
+ if (current === null || current === undefined) return undefined
+ if (!['object', 'function'].includes(typeof current)) return undefined
+ return current[segment]
+ }, payload)
+}
+
+const getMissingRequiredFields = (requiredFields = [], payload = {}) => {
+ if (!Array.isArray(requiredFields) || !requiredFields.length) return []
+ return requiredFields.filter((fieldPath) => {
+ const value = getPayloadValueByPath(payload, fieldPath)
+ return !hasMeaningfulPayloadValue(value)
+ })
+}
+
+const parseManufacturingActionPayload = (payloadText = '') => {
+ const text = String(payloadText ?? '').trim()
+ if (!text) return {}
+ return JSON.parse(text)
+}
+
+const executeManufacturingAction = async (message, actionCard, cardIndex = 0) => {
+ if (!message?.manufacturingData || !actionCard || actionCard.executing) return
+
+ const actionName = actionCard.name || `鍔ㄤ綔 ${cardIndex + 1}`
+ const targetApi = String(actionCard.targetApi || '').trim()
+ if (!targetApi) {
+ actionCard.executeError = true
+ actionCard.executeResult = '缂哄皯 targetApi锛屾棤娉曟墽琛屽姩浣�'
+ return
+ }
+
+ let payload = {}
+ try {
+ payload = parseManufacturingActionPayload(actionCard.payloadText)
+ } catch (err) {
+ actionCard.executeError = true
+ actionCard.executeResult = '璇锋眰鍙傛暟涓嶆槸鍚堟硶 JSON锛岃妫�鏌ュ悗閲嶈瘯'
+ return
+ }
+
+ const requiredFields = Array.isArray(actionCard.requiredFields) ? actionCard.requiredFields : []
+ if (requiredFields.length && !isPlainObject(payload)) {
+ actionCard.executeError = true
+ actionCard.executeResult = '蹇呭~瀛楁鏍¢獙澶辫触锛氳姹傚弬鏁板繀椤绘槸 JSON 瀵硅薄'
+ return
+ }
+
+ const missingFields = getMissingRequiredFields(requiredFields, payload)
+ if (missingFields.length) {
+ actionCard.executeError = true
+ const missingFieldLabels = missingFields.map(field => getStructuredPathLabel(field))
+ actionCard.executeResult = `缂哄皯蹇呭~瀛楁锛�${missingFieldLabels.join('銆�')}`
+ return
+ }
+
+ try {
+ await ElMessageBox.confirm(`纭鎵ц銆�${actionName}銆嶅悧锛焋, '鎵ц纭', {
+ confirmButtonText: '纭鎵ц',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ } catch (err) {
+ return
+ }
+
+ actionCard.executing = true
+ actionCard.executeError = false
+ actionCard.executeResult = ''
+
+ const method = normalizeRequestMethod(actionCard.method)
+ const requestConfig = {
+ url: targetApi,
+ method: method.toLowerCase()
+ }
+
+ if (method === 'GET') {
+ requestConfig.params = payload
+ } else {
+ requestConfig.data = payload
+ }
+
+ try {
+ const res = await request(requestConfig)
+ const successMsg = res?.msg || `${actionName}鎵ц鎴愬姛`
+ actionCard.executed = true
+ actionCard.executeError = false
+ actionCard.executeResult = successMsg
+ ElMessage.success(successMsg)
+ } catch (err) {
+ actionCard.executed = false
+ actionCard.executeError = true
+ actionCard.executeResult = err?.message || `${actionName}鎵ц澶辫触锛岃绋嶅悗閲嶈瘯`
+ } finally {
+ actionCard.executing = false
+ }
+}
// 鍘嗗彶浼氳瘽鐩稿叧
const purchaseValueTypeOptions = [
@@ -791,6 +1699,142 @@
const showHistory = ref(false)
const sessions = ref([])
const loadingSessions = ref(false)
+
+const isImageFileType = (fileType = '') => String(fileType || '').toLowerCase().startsWith('image/')
+const imageFilePathPattern = /\.(png|jpe?g|gif|webp|bmp|svg)$/i
+
+const getPathnameFromFilePath = (filePath = '') => {
+ const rawPath = String(filePath || '').trim()
+ if (!rawPath) return ''
+ try {
+ const baseOrigin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'
+ return new URL(rawPath, baseOrigin).pathname || ''
+ } catch (err) {
+ return rawPath.split('?')[0]
+ }
+}
+
+const isImageFilePath = (filePath = '') => {
+ const pathname = getPathnameFromFilePath(filePath).toLowerCase()
+ return imageFilePathPattern.test(pathname)
+}
+
+const getHistoryFileName = (filePath = '', index = 0) => {
+ const pathname = getPathnameFromFilePath(filePath)
+ const fileName = pathname.split('/').filter(Boolean).pop()
+ if (!fileName) return `file-${index + 1}`
+ try {
+ return decodeURIComponent(fileName)
+ } catch (err) {
+ return fileName
+ }
+}
+
+const getImagePreviewList = (files = []) => {
+ if (!Array.isArray(files)) return []
+ return files
+ .filter(item => item?.isImage && item?.previewUrl)
+ .map(item => item.previewUrl)
+}
+
+const getImagePreviewInitialIndex = (files = [], previewUrl = '') => {
+ const list = getImagePreviewList(files)
+ const index = list.indexOf(previewUrl)
+ return index >= 0 ? index : 0
+}
+
+const formatFileSize = (size) => {
+ const bytes = Number(size)
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
+ if (bytes < 1024) return `${bytes} B`
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1).replace(/\.0$/, '')} KB`
+ return `${(bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, '')} MB`
+}
+
+const createLocalFileSnapshot = (file, index = 0) => {
+ const rawFile = typeof File !== 'undefined' && file instanceof File ? file : null
+ const fileType = rawFile?.type || ''
+ const isImage = isImageFileType(fileType)
+ const canCreateObjectURL = typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'
+ const previewUrl = isImage && rawFile && canCreateObjectURL ? URL.createObjectURL(rawFile) : ''
+ return {
+ previewId: `${rawFile?.name || 'file'}-${rawFile?.size || 0}-${rawFile?.lastModified || Date.now()}-${index}`,
+ name: rawFile?.name || `file-${index + 1}`,
+ size: rawFile?.size || 0,
+ type: fileType,
+ isImage,
+ previewUrl,
+ accessUrl: '',
+ rawFile,
+ isObjectUrl: !!previewUrl
+ }
+}
+
+const createHistoryFileSnapshot = (filePath, memoryId = '', messageIndex = 0, fileIndex = 0) => {
+ const normalizedPath = String(filePath || '').trim()
+ if (!normalizedPath) return null
+ const isImage = isImageFilePath(normalizedPath)
+ return {
+ previewId: `${memoryId || 'history'}-${messageIndex}-${fileIndex}`,
+ name: getHistoryFileName(normalizedPath, fileIndex),
+ size: 0,
+ type: '',
+ isImage,
+ previewUrl: isImage ? normalizedPath : '',
+ accessUrl: normalizedPath,
+ rawFile: null,
+ isObjectUrl: false
+ }
+}
+
+const revokeLocalFileSnapshots = (snapshots = []) => {
+ const canRevokeObjectURL = typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function'
+ if (!canRevokeObjectURL) return
+ if (!Array.isArray(snapshots)) return
+ snapshots.forEach((snapshot) => {
+ if (snapshot?.isObjectUrl && snapshot?.previewUrl) {
+ URL.revokeObjectURL(snapshot.previewUrl)
+ }
+ })
+}
+
+const mapHistoryFilePathsToSnapshots = (filePaths = [], memoryId = '', messageIndex = 0) => {
+ if (!Array.isArray(filePaths)) return []
+ return filePaths
+ .map((filePath, fileIndex) => createHistoryFileSnapshot(filePath, memoryId, messageIndex, fileIndex))
+ .filter(Boolean)
+}
+
+const openMessageAttachment = (file) => {
+ const accessUrl = String(file?.accessUrl || '').trim()
+ if (!accessUrl) return
+ if (typeof window === 'undefined' || typeof window.open !== 'function') return
+ window.open(accessUrl, '_blank', 'noopener,noreferrer')
+}
+
+const handleMessageFileClick = (file) => {
+ if (!file?.accessUrl || file?.isImage) return
+ openMessageAttachment(file)
+}
+
+const revokeMessageLocalFileSnapshots = (messageList = []) => {
+ if (!Array.isArray(messageList)) return
+ messageList.forEach((msg) => {
+ if (Array.isArray(msg?.localUploadFiles)) {
+ revokeLocalFileSnapshots(msg.localUploadFiles)
+ msg.localUploadFiles = []
+ }
+ })
+}
+
+const clearSelectedFiles = ({ releaseSnapshots = true } = {}) => {
+ if (releaseSnapshots) {
+ revokeLocalFileSnapshots(selectedFileSnapshots.value)
+ }
+ selectedFiles.value = []
+ uploadFileList.value = []
+ selectedFileSnapshots.value = []
+}
const abortCurrentRequest = () => {
if (!currentAbortController.value) return
@@ -835,6 +1879,7 @@
const selectSession = async (session) => {
showHistory.value = false
+ clearSelectedFiles()
uuid.value = session.memoryId
localStorage.setItem(currentAssistant.value.storageKey, uuid.value)
@@ -842,6 +1887,7 @@
try {
const res = await request.get(`${currentAssistant.value.apiBase}/history/messages/${uuid.value}`)
if (res.code === 200) {
+ revokeMessageLocalFileSnapshots(messages.value)
disposeCharts()
messages.value = []
const historyMsgs = res.data || []
@@ -853,7 +1899,7 @@
const messageObj = {
isUser,
- content: msg.content,
+ content: msg.content || '',
htmlContent: '',
isTyping: false,
chartOptions: null,
@@ -861,7 +1907,13 @@
type: '',
tableData: null,
payloadTreeData: null,
- payloadHiddenData: null
+ payloadHiddenData: null,
+ purchaseAnalysisData: null,
+ manufacturingData: null,
+ salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null,
+ localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
}
messages.value.push(messageObj)
@@ -876,15 +1928,15 @@
}
// 瑙f瀽鍘嗗彶娑堟伅涓殑 JSON
- const extracted = extractEmbeddedSuccessJson(msg.content)
+ const extracted = extractEmbeddedSuccessJson(msg.content || '')
if (extracted) {
applyStructuredMessageData(messageObj, extracted.data, botMsgIndex)
}
- updateOutputState(msg.content, botMsgIndex)
- messageObj.htmlContent = convertStreamOutput(msg.content, botMsgIndex)
+ updateOutputState(msg.content || '', botMsgIndex)
+ messageObj.htmlContent = convertStreamOutput(msg.content || '', botMsgIndex)
} else {
- messageObj.htmlContent = convertTextToHtml(msg.content)
+ messageObj.htmlContent = convertTextToHtml(msg.content || '')
}
})
scrollToBottom()
@@ -919,15 +1971,16 @@
}
onMounted(() => {
- initUUID()
- // 鍒濆娆㈣繋
- if (messages.value.length === 0) {
- hello()
+ if (props.autoOpen) {
+ visible.value = true
}
+ initUUID()
window.addEventListener('resize', handleWindowResize)
})
onUnmounted(() => {
+ revokeMessageLocalFileSnapshots(messages.value)
+ clearSelectedFiles()
disposeCharts()
window.removeEventListener('resize', handleWindowResize)
})
@@ -936,21 +1989,36 @@
if (!prevKey || nextKey === prevKey) return
abortCurrentRequest()
+ revokeMessageLocalFileSnapshots(messages.value)
disposeCharts()
messages.value = []
outputState.value = {}
sessions.value = []
showHistory.value = false
- selectedFiles.value = []
- uploadFileList.value = []
+ clearSelectedFiles()
inputMessage.value = ''
quickPromptStart.value = 0
initUUID()
- hello()
+})
+
+watch(() => props.defaultAssistant, (nextKey) => {
+ if (!nextKey || nextKey === selectedAssistantKey.value) return
+ if (!assistants.value.some(item => item.key === nextKey)) return
+ selectedAssistantKey.value = nextKey
+})
+
+watch(() => props.autoOpen, (nextValue) => {
+ if (nextValue) {
+ visible.value = true
+ }
})
const handleWindowResize = () => {
windowWidth.value = window.innerWidth
+}
+
+const handleHeaderExtraAction = () => {
+ emit('header-extra-action')
}
const toggleSidebar = () => {
@@ -961,10 +2029,12 @@
}
const handleClose = () => {
+ if (hideTrigger.value) return
visible.value = false
}
const handleManualClose = () => {
+ if (hideTrigger.value) return
if (isSending.value) {
abortCurrentRequest()
}
@@ -980,22 +2050,17 @@
uuid.value = storedUUID
}
-const hello = () => {
- sendRequest(currentAssistant.value.welcomeMessage || '浣犲ソ')
-}
-
const newChat = () => {
+ revokeMessageLocalFileSnapshots(messages.value)
disposeCharts()
messages.value = []
outputState.value = {}
sessions.value = []
showHistory.value = false
- selectedFiles.value = []
- uploadFileList.value = []
+ clearSelectedFiles()
quickPromptStart.value = 0
localStorage.removeItem(currentAssistant.value.storageKey)
initUUID()
- hello()
}
const handleNewChat = () => {
@@ -1066,7 +2131,7 @@
const candidate = text.slice(i, j + 1)
try {
const parsed = JSON.parse(candidate)
- if (parsed?.success === true) {
+ if (typeof parsed?.success === 'boolean') {
return {
data: parsed,
startIdx: i,
@@ -1085,17 +2150,50 @@
}
const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
- if (!messageObj || !parsedData?.success) return
+ const isPurchaseIntentNotRecognized = String(parsedData?.type || '') === 'purchase_intent_not_recognized'
+ if (!messageObj || (parsedData?.success !== true && !isPurchaseIntentNotRecognized)) return
+ const previousManufacturingData = messageObj.manufacturingData
messageObj.type = parsedData.type || ''
+ messageObj.tableData = null
+ messageObj.purchaseAnalysisData = null
+ messageObj.manufacturingData = null
+ messageObj.salesData = null
+ messageObj.purchaseData = null
+ messageObj.purchaseIntentData = null
+
+ if (isPurchaseIntentNotRecognized) {
+ messageObj.purchaseIntentData = normalizePurchaseIntentNotRecognizedData(parsedData)
+ messageObj.chartOptions = null
+ messageObj.chartRenderReady = false
+ return
+ }
if (messageObj.type === 'todo_list' && parsedData.data) {
messageObj.tableData = parsedData.data
}
+ const salesData = buildSalesStructuredData(parsedData)
+ if (salesData) {
+ messageObj.salesData = salesData
+ }
+
+ const manufacturingData = buildManufacturingStructuredData(parsedData, previousManufacturingData)
+ if (manufacturingData) {
+ messageObj.manufacturingData = manufacturingData
+ }
+
+ const purchaseData = buildPurchaseStructuredData(parsedData)
+ if (purchaseData) {
+ messageObj.purchaseData = purchaseData
+ }
+
if (parsedData.action === 'confirm_required' && parsedData.businessType) {
messageObj.type = 'purchase_analysis_confirm'
messageObj.purchaseAnalysisData = parsedData
+ messageObj.manufacturingData = null
+ messageObj.salesData = null
+ messageObj.purchaseData = null
if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
initializePurchasePayloadTree(messageObj, parsedData.payload || {})
}
@@ -1135,6 +2233,27 @@
}
return null
+}
+
+const getStructuredFallbackText = (parsedData) => {
+ if (!parsedData) return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+ if (parsedData.type === 'todo_list') return '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+ if (parsedData.type === 'purchase_intent_not_recognized') return '鏈瘑鍒埌鍙墽琛岀殑閲囪喘鏌ヨ鏉′欢锛岃琛ュ厖鏌ヨ鏉′欢鍚庡啀璇曘��'
+ if (salesStructuredTypeSet.has(parsedData.type)) {
+ if (parsedData.type === 'sales_customer_churn_risk') return '宸蹭负鎮ㄧ敓鎴愬鎴锋祦澶遍闄╁垎鏋愩��'
+ if (parsedData.type === 'sales_collection_quote_strategy') return '宸蹭负鎮ㄧ敓鎴愬洖娆句笌鎶ヤ环绛栫暐寤鸿銆�'
+ if (parsedData.type === 'sales_dashboard') return '宸蹭负鎮ㄧ敓鎴愰攢鍞寚鏍囩粺璁°��'
+ return '宸茶繑鍥為攢鍞煡璇㈢粨鏋溿��'
+ }
+ if (manufacturingStructuredTypeSet.has(parsedData.type)) {
+ if (parsedData.type === 'manufacturing_action_plan') return '宸蹭负鎮ㄧ敓鎴愬姙鐞嗗缓璁紝璇风‘璁ゅ姩浣滃悗鎵ц銆�'
+ if (parsedData.type === 'manufacturing_warning') return '宸蹭负鎮ㄧ敓鎴愬埗閫犻璀︾湅鏉裤��'
+ if (parsedData.type === 'manufacturing_analysis') return '宸蹭负鎮ㄧ敓鎴愬埗閫犲垎鏋愮粨鏋溿��'
+ return '宸茶繑鍥炲埗閫犳煡璇㈢粨鏋溿��'
+ }
+ if (String(parsedData.type || '').startsWith('purchase_')) return '宸茶繑鍥為噰璐煡璇㈢粨鏋溿��'
+ if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
+ return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
}
const buildPurchaseMaterialRankCharts = (parsedData) => {
@@ -1343,12 +2462,7 @@
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
+ return visibleValue.map((item, index) => mergePurchasePayloadWithHidden(item, hiddenValue[index]))
}
if (
@@ -1667,6 +2781,76 @@
'瀹℃壒鐢ㄦ埛ID鍒楄〃'
])
+const purchaseIntegerFieldKeys = new Set([
+ 'id',
+ 'supplierId',
+ 'recorderId',
+ 'salesContractNoId',
+ 'salesLedgerId',
+ 'Type',
+ 'businessPersonId',
+ 'productId',
+ 'productModelId',
+ 'ticketRegistrationId',
+ 'type',
+ 'approvalStatus',
+ 'inventoryWarningQuantity'
+])
+
+const purchaseDecimalFieldKeys = new Set([
+ 'invoiceAmount',
+ 'contractAmount',
+ 'receiptPaymentAmount',
+ 'unReceiptPaymentAmount',
+ 'quantity',
+ 'taxRate',
+ 'taxInclusiveUnitPrice',
+ 'taxInclusiveTotalPrice',
+ 'taxExclusiveTotalPrice',
+ 'priceWithTax',
+ 'totalPriceWithTax'
+])
+
+const purchaseBooleanFieldKeys = new Set([
+ 'hasChildren',
+ 'isWhite',
+ 'isInspected',
+ 'isChecked'
+])
+
+const purchaseStringFieldKeys = new Set([
+ 'entryDateStart',
+ 'entryDateEnd',
+ 'purchaseContractNumber',
+ 'supplierName',
+ 'recorderName',
+ 'salesContractNo',
+ 'projectName',
+ 'entryDate',
+ 'executionDate',
+ 'remarks',
+ 'attachmentMaterials',
+ 'createdAt',
+ 'updatedAt',
+ 'phoneNumber',
+ 'invoiceNumber',
+ 'paymentMethod',
+ 'templateName',
+ 'productCategory',
+ 'specificationModel',
+ 'unit',
+ 'invoiceType'
+])
+
+const purchaseGenericArrayFieldKeys = new Set([
+ 'purchaseLedgers',
+ 'productData'
+])
+
+const purchaseStringArrayFieldKeys = new Set(['tempFileIds'])
+
+const purchaseObjectArrayFieldKeys = new Set(['SalesLedgerFiles'])
+
const normalizePurchaseProductRecord = (record) => {
if (!record || typeof record !== 'object' || Array.isArray(record)) return record
return mapPayloadKeys(record, purchasePayloadFieldKeyMap)
@@ -1681,6 +2865,23 @@
''
}
+const prunePurchaseProductRecord = (record) => {
+ if (!record || typeof record !== 'object' || Array.isArray(record)) return null
+ const normalizedRecord = normalizePurchaseProductRecord(record)
+ const hasVisibleFieldValue = Object.entries(normalizedRecord).some(([key, value]) => {
+ if (shouldHidePurchaseField(key)) return false
+ return hasMeaningfulPayloadValue(value)
+ })
+ return hasVisibleFieldValue ? normalizedRecord : null
+}
+
+const normalizeAndFilterPurchaseProductData = (value) => {
+ if (!Array.isArray(value)) return value
+ return value
+ .map(item => prunePurchaseProductRecord(item))
+ .filter(Boolean)
+}
+
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) {
@@ -1689,13 +2890,11 @@
const ledgers = payload.purchaseLedgers.map(ledger => ({
...ledger,
- productData: Array.isArray(ledger.productData)
- ? ledger.productData.map(normalizePurchaseProductRecord)
- : []
+ productData: normalizeAndFilterPurchaseProductData(ledger.productData) || []
}))
const unmatchedProducts = []
- payload.productData.map(normalizePurchaseProductRecord).forEach(product => {
+ normalizeAndFilterPurchaseProductData(payload.productData).forEach(product => {
const productMatchKey = getPurchaseProductMatchKey(product)
const matchedLedger = ledgers.find(ledger => {
const ledgerKeys = [
@@ -1734,9 +2933,7 @@
if (!record || typeof record !== 'object' || Array.isArray(record)) return record
const normalizedRecord = {
...record,
- productData: Array.isArray(record.productData)
- ? record.productData.map(normalizePurchaseProductRecord)
- : record.productData
+ productData: normalizeAndFilterPurchaseProductData(record.productData)
}
return Object.entries(normalizedRecord).reduce((result, [key, value]) => {
if (purchaseLedgerAllowedFieldKeys.has(key)) {
@@ -1746,12 +2943,199 @@
}, {})
}
+const normalizeAttachmentMaterialsValue = (value) => {
+ if (value === null || value === undefined) return value
+ if (typeof value === 'string') return value
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value)
+ try {
+ return JSON.stringify(value)
+ } catch (err) {
+ return String(value)
+ }
+}
+
+const normalizePurchaseAttachmentMaterialsField = (value) => {
+ if (Array.isArray(value)) {
+ return value.map(item => normalizePurchaseAttachmentMaterialsField(item))
+ }
+ if (value && typeof value === 'object') {
+ return Object.entries(value).reduce((result, [key, item]) => {
+ if (key === 'attachmentMaterials') {
+ result[key] = normalizeAttachmentMaterialsValue(item)
+ } else {
+ result[key] = normalizePurchaseAttachmentMaterialsField(item)
+ }
+ return result
+ }, {})
+ }
+ return value
+}
+
+const normalizePurchaseNumericText = (text = '') => {
+ return String(text)
+ .replace(/[,\s锛宂/g, '')
+ .replace(/[楼锟ュ厓%]/g, '')
+}
+
+const parsePurchaseNumberValue = (value) => {
+ if (typeof value === 'number') {
+ return Number.isFinite(value) ? value : null
+ }
+ if (typeof value === 'boolean') {
+ return value ? 1 : 0
+ }
+ if (typeof value !== 'string') {
+ return null
+ }
+ const text = normalizePurchaseNumericText(value.trim())
+ if (!text) return null
+ if (!/^[-+]?\d+(\.\d+)?$/.test(text)) return null
+ const numberValue = Number(text)
+ return Number.isFinite(numberValue) ? numberValue : null
+}
+
+const normalizePurchaseIntegerFieldValue = (value) => {
+ if (value === null || value === undefined) return value
+ if (value === '') return null
+ const numberValue = parsePurchaseNumberValue(value)
+ if (numberValue === null) return null
+ return Math.trunc(numberValue)
+}
+
+const normalizePurchaseDecimalFieldValue = (value) => {
+ if (value === null || value === undefined) return value
+ if (value === '') return null
+ const numberValue = parsePurchaseNumberValue(value)
+ return numberValue === null ? null : numberValue
+}
+
+const normalizePurchaseBooleanFieldValue = (value) => {
+ if (value === null || value === undefined) return value
+ if (value === '') return null
+ if (typeof value === 'boolean') return value
+ if (typeof value === 'number') return value !== 0
+ if (typeof value !== 'string') return null
+
+ const text = value.trim().toLowerCase()
+ if (!text) return null
+ if (['true', '1', 'yes', 'y', '鏄�', '宸�', 'checked'].includes(text)) return true
+ if (['false', '0', 'no', 'n', '鍚�', '鏈�', 'unchecked'].includes(text)) return false
+ return null
+}
+
+const normalizePurchaseStringFieldValue = (value) => {
+ if (value === null || value === undefined) return value
+ if (typeof value === 'string') return value
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value)
+ try {
+ return JSON.stringify(value)
+ } catch (err) {
+ return String(value)
+ }
+}
+
+const normalizePurchaseStringArrayFieldValue = (value) => {
+ if (Array.isArray(value)) {
+ return value
+ .map(item => normalizePurchaseStringFieldValue(item))
+ .filter(item => item !== null && item !== undefined && item !== '')
+ }
+ if (value === null || value === undefined || value === '') return []
+ if (typeof value === 'string') {
+ const text = value.trim()
+ if (!text) return []
+ if (/^\[.*\]$/.test(text)) {
+ try {
+ const parsedValue = JSON.parse(text)
+ if (Array.isArray(parsedValue)) {
+ return parsedValue
+ .map(item => normalizePurchaseStringFieldValue(item))
+ .filter(item => item !== null && item !== undefined && item !== '')
+ }
+ } catch (err) {
+ // Keep as plain text when not valid JSON array.
+ }
+ }
+ const splitValues = text
+ .split(/[,\n锛宂/)
+ .map(item => item.trim())
+ .filter(Boolean)
+ return splitValues.length > 1 ? splitValues : [text]
+ }
+ const normalizedValue = normalizePurchaseStringFieldValue(value)
+ return normalizedValue === null || normalizedValue === undefined || normalizedValue === ''
+ ? []
+ : [normalizedValue]
+}
+
+const normalizePurchaseObjectArrayFieldValue = (value) => {
+ if (Array.isArray(value)) {
+ return value.filter(item => item && typeof item === 'object')
+ }
+ if (value && typeof value === 'object') {
+ return [value]
+ }
+ return []
+}
+
+const normalizePurchaseValueByFieldKey = (fieldKey, value) => {
+ if (fieldKey === 'attachmentMaterials') return normalizeAttachmentMaterialsValue(value)
+ if (purchaseIntegerFieldKeys.has(fieldKey)) return normalizePurchaseIntegerFieldValue(value)
+ if (purchaseDecimalFieldKeys.has(fieldKey)) return normalizePurchaseDecimalFieldValue(value)
+ if (purchaseBooleanFieldKeys.has(fieldKey)) return normalizePurchaseBooleanFieldValue(value)
+ if (purchaseStringArrayFieldKeys.has(fieldKey)) return normalizePurchaseStringArrayFieldValue(value)
+ if (purchaseObjectArrayFieldKeys.has(fieldKey)) return normalizePurchaseObjectArrayFieldValue(value)
+ if (purchaseStringFieldKeys.has(fieldKey)) return normalizePurchaseStringFieldValue(value)
+ return value
+}
+
+const normalizePurchasePayloadFieldTypes = (value, fieldKey = '') => {
+ if (purchaseGenericArrayFieldKeys.has(fieldKey)) {
+ if (Array.isArray(value)) {
+ return value.map(item => normalizePurchasePayloadFieldTypes(item))
+ }
+ if (value && typeof value === 'object') {
+ return [normalizePurchasePayloadFieldTypes(value)]
+ }
+ return []
+ }
+
+ if (purchaseStringArrayFieldKeys.has(fieldKey)) {
+ return normalizePurchaseStringArrayFieldValue(value)
+ }
+
+ if (purchaseObjectArrayFieldKeys.has(fieldKey)) {
+ return normalizePurchaseObjectArrayFieldValue(value)
+ }
+
+ if (Array.isArray(value)) {
+ return value.map(item => normalizePurchasePayloadFieldTypes(item))
+ }
+
+ if (value && typeof value === 'object') {
+ return Object.entries(value).reduce((result, [key, item]) => {
+ result[key] = normalizePurchasePayloadFieldTypes(item, key)
+ return result
+ }, {})
+ }
+
+ return normalizePurchaseValueByFieldKey(fieldKey, value)
+}
+
const sanitizePurchasePayloadForSubmit = (payload, businessType) => {
if (businessType !== 'purchase_ledger' || !payload || typeof payload !== 'object') return payload
- const sanitized = mergeLegacyProductDataIntoLedgers(Array.isArray(payload) ? [...payload] : { ...payload })
+ let sanitized = mergeLegacyProductDataIntoLedgers(Array.isArray(payload) ? [...payload] : { ...payload })
+ sanitized = normalizePurchaseAttachmentMaterialsField(sanitized)
+ sanitized = normalizePurchasePayloadFieldTypes(sanitized)
if (Array.isArray(sanitized.purchaseLedgers)) {
sanitized.purchaseLedgers = sanitized.purchaseLedgers.map(filterPurchaseLedgerRecord)
+ }
+ if (Array.isArray(sanitized.productData)) {
+ sanitized.productData = normalizeAndFilterPurchaseProductData(sanitized.productData)
+ }
+ if (Array.isArray(sanitized.productData) && !sanitized.productData.length) {
+ delete sanitized.productData
}
purchaseApprovalFieldKeys.forEach(key => {
@@ -1954,16 +3338,20 @@
return isLt10M
})
+ clearSelectedFiles()
selectedFiles.value = validFiles
uploadFileList.value = fileList.filter(item => item.raw && validFiles.includes(item.raw))
+ selectedFileSnapshots.value = validFiles.map((rawFile, index) => createLocalFileSnapshot(rawFile, index))
}
const removeSelectedFile = (index) => {
+ const [removedSnapshot] = selectedFileSnapshots.value.splice(index, 1)
+ revokeLocalFileSnapshots(removedSnapshot ? [removedSnapshot] : [])
selectedFiles.value.splice(index, 1)
uploadFileList.value.splice(index, 1)
}
-const analyzeFiles = async (files, message = '') => {
+const analyzeFiles = async (files, message = '', localFileSnapshots = []) => {
const uploadFiles = Array.isArray(files) ? files : [files].filter(Boolean)
if (!uploadFiles.length) return
if (isSending.value) return
@@ -1976,7 +3364,8 @@
isUser: true,
content: userMsg,
htmlContent: convertTextToHtml(userMsg),
- isTyping: false
+ isTyping: false,
+ localUploadFiles: Array.isArray(localFileSnapshots) ? localFileSnapshots : []
})
const botMsgIndex = messages.value.length
@@ -1990,7 +3379,12 @@
type: '',
tableData: null,
payloadTreeData: null,
- payloadHiddenData: null
+ payloadHiddenData: null,
+ purchaseAnalysisData: null,
+ manufacturingData: null,
+ salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null
})
outputState.value[botMsgIndex] = {
@@ -2075,9 +3469,9 @@
const msg = inputMessage.value?.trim() || ''
if ((msg || selectedFiles.value.length) && !isSending.value) {
if (selectedFiles.value.length) {
- analyzeFiles([...selectedFiles.value], msg)
- selectedFiles.value = []
- uploadFileList.value = []
+ const localFileSnapshots = selectedFileSnapshots.value
+ analyzeFiles([...selectedFiles.value], msg, localFileSnapshots)
+ clearSelectedFiles({ releaseSnapshots: false })
} else {
sendRequest(msg)
}
@@ -2113,7 +3507,12 @@
type: '',
tableData: null,
payloadTreeData: null,
- payloadHiddenData: null
+ payloadHiddenData: null,
+ purchaseAnalysisData: null,
+ manufacturingData: null,
+ salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null
}
messages.value.push(botMsg)
@@ -2145,28 +3544,6 @@
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)
@@ -2184,24 +3561,6 @@
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') {
@@ -2279,13 +3638,7 @@
}
if (!display) {
- if (parsed.type === 'todo_list') {
- display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
- } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
- display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
- } else {
- display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
- }
+ display = getStructuredFallbackText(parsed)
}
} else if (startIdx !== -1) {
const lastBraceIdx = output.lastIndexOf('}')
@@ -2307,13 +3660,7 @@
}
if (!display) {
- if (parsed.type === 'todo_list') {
- display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁锛�'
- } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
- display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛細'
- } else {
- display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
- }
+ display = getStructuredFallbackText(parsed)
}
} catch (e) {
// 瑙f瀽澶辫触锛岃鏄� JSON 杩樺湪浼犺緭涓垨鏍煎紡涓嶆纭�
@@ -2571,30 +3918,30 @@
.ai-chat-trigger {
pointer-events: auto;
position: fixed;
- right: 24px;
- bottom: 100px;
- width: 56px;
- height: 56px;
+ right: 10px;
+ bottom: 12px;
+ width: 40px;
+ height: 40px;
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;
+ box-shadow: 0 8px 18px rgba(0, 68, 170, 0.32), 0 0 0 1px rgba(0, 136, 232, 0.32) inset;
+ transition: transform 0.22s ease, box-shadow 0.22s ease, opacity 0.22s ease;
+ z-index: 1020;
+ opacity: 0.9;
&::before {
content: '';
position: absolute;
- inset: -6px;
+ inset: -4px;
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;
+ filter: blur(10px);
+ opacity: 0.35;
}
&::after {
@@ -2607,16 +3954,12 @@
}
&: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;
- }
+ transform: scale(1.05) translateY(-1px);
+ box-shadow: 0 10px 22px rgba(0, 68, 170, 0.38), 0 0 0 1px rgba(0, 136, 232, 0.42) inset;
.trigger-icon {
- transform: rotate(-8deg) scale(1.05);
- filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5));
+ transform: scale(1.03);
+ filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.42));
}
}
@@ -2659,8 +4002,9 @@
height: 100%;
}
:deep(.el-drawer__header) {
- margin-bottom: 0;
- padding: 0;
+ margin-bottom: 0 !important;
+ padding: 0 !important;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.12);
background: $gradient-dark;
color: #fff;
}
@@ -2671,7 +4015,7 @@
justify-content: space-between;
align-items: center;
width: 100%;
- padding: 18px 20px;
+ padding: 12px 18px;
background: $gradient-dark;
position: relative;
overflow: hidden;
@@ -2803,6 +4147,15 @@
opacity: 1;
}
}
+
+ :deep(.header-action-btn--text) {
+ width: auto !important;
+ min-width: 104px;
+ padding: 8px 14px !important;
+ font-size: 14px;
+ font-weight: 600;
+ white-space: nowrap;
+ }
}
.assistant-switcher {
@@ -2885,7 +4238,7 @@
top: 0;
left: 0;
right: 0;
- height: 240px;
+ height: 128px;
background: linear-gradient(180deg, rgba(0, 85, 212, 0.06) 0%, transparent 100%);
pointer-events: none;
}
@@ -3125,6 +4478,87 @@
border-radius: 2px;
}
}
+
+ .message-local-file-list {
+ margin-top: 8px;
+ display: grid;
+ gap: 8px;
+ max-width: 100%;
+ }
+
+ .message-local-file-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 10px;
+ border-radius: 10px;
+ border: 1px solid rgba(88, 117, 255, 0.2);
+ background: rgba(255, 255, 255, 0.9);
+ max-width: 100%;
+
+ &.clickable {
+ cursor: pointer;
+ transition: all 0.2s ease;
+
+ &:hover {
+ border-color: rgba(44, 109, 255, 0.38);
+ background: rgba(243, 247, 255, 0.96);
+ }
+ }
+ }
+
+ .message-local-file-thumb {
+ width: 40px;
+ height: 40px;
+ border-radius: 6px;
+ overflow: hidden;
+ flex-shrink: 0;
+ border: 1px solid rgba(124, 148, 255, 0.26);
+ background: #f4f7ff;
+ cursor: zoom-in;
+
+ :deep(.el-image__inner) {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .message-local-file-icon {
+ font-size: 20px;
+ color: $primary-blue;
+ flex-shrink: 0;
+ }
+
+ .message-local-file-meta {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ }
+
+ .message-local-file-name {
+ font-size: 12px;
+ color: #1f2a44;
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &.clickable {
+ color: $primary-blue;
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .message-local-file-size {
+ font-size: 11px;
+ color: #7f8ba1;
+ line-height: 1.2;
+ }
}
&.bot-message {
@@ -3243,6 +4677,297 @@
--el-table-border-color: rgba(0, 122, 255, 0.08);
--el-table-header-bg-color: $ice-white;
}
+}
+
+.manufacturing-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;
+}
+
+.manufacturing-card__title {
+ font-size: 14px;
+ font-weight: 700;
+ color: $deep-blue;
+ margin-bottom: 10px;
+}
+
+.manufacturing-summary-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.manufacturing-summary-item {
+ border-radius: 10px;
+ padding: 10px 12px;
+ border: 1px solid rgba(0, 85, 212, 0.08);
+ background: linear-gradient(180deg, #f8fbff, #f1f7ff);
+ min-height: 66px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 6px;
+}
+
+.manufacturing-summary-item--core {
+ border-color: rgba(30, 91, 255, 0.24);
+}
+
+.manufacturing-summary-label {
+ font-size: 12px;
+ color: #4b5563;
+}
+
+.manufacturing-summary-value {
+ font-size: 15px;
+ color: #1f2937;
+ line-height: 1.4;
+ word-break: break-all;
+}
+
+.manufacturing-warning-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.manufacturing-warning-item {
+ border-radius: 10px;
+ border: 1px solid rgba(245, 158, 11, 0.22);
+ background: linear-gradient(135deg, rgba(255, 247, 237, 0.9), rgba(255, 255, 255, 0.98));
+ padding: 10px 12px;
+}
+
+.manufacturing-warning-item__head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: #92400e;
+ font-size: 13px;
+}
+
+.manufacturing-warning-count {
+ margin-left: auto;
+ font-weight: 700;
+ color: #c2410c;
+}
+
+.manufacturing-warning-detail {
+ margin: 8px 0 0;
+ font-size: 12px;
+ line-height: 1.6;
+ color: #7c2d12;
+ word-break: break-all;
+}
+
+.manufacturing-table-wrapper {
+ margin-top: 10px;
+}
+
+.manufacturing-action-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-top: 12px;
+}
+
+.manufacturing-action-card {
+ border: 1px solid rgba(0, 85, 212, 0.1);
+ border-radius: 10px;
+ padding: 10px 12px;
+ background: #f8fbff;
+}
+
+.manufacturing-action-card__head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ font-size: 13px;
+ color: #1f2937;
+ margin-bottom: 8px;
+}
+
+.manufacturing-action-card__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 8px;
+ font-size: 12px;
+ color: #64748b;
+ word-break: break-all;
+}
+
+.manufacturing-action-card__desc {
+ margin: 0 0 8px;
+ font-size: 12px;
+ line-height: 1.6;
+ color: #475467;
+}
+
+.manufacturing-required-fields {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 8px;
+ font-size: 12px;
+ color: #7c2d12;
+}
+
+.manufacturing-action-footer {
+ margin-top: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 12px;
+}
+
+.manufacturing-action-result {
+ flex: 1;
+ font-size: 12px;
+ line-height: 1.5;
+
+ &.success {
+ color: #1f9d55;
+ }
+
+ &.error {
+ color: #d93025;
+ }
+}
+
+.sales-structured-card {
+ margin-top: 12px;
+ width: 100%;
+ background: #fff;
+ border: 1px solid rgba(31, 122, 114, 0.2);
+ border-radius: 12px;
+ box-shadow: $shadow-card;
+ padding: 14px;
+}
+
+.sales-structured-card__title {
+ font-size: 14px;
+ font-weight: 700;
+ color: #1f5ddf;
+ margin-bottom: 10px;
+}
+
+.sales-summary-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.sales-summary-item {
+ border-radius: 10px;
+ padding: 10px 12px;
+ border: 1px solid rgba(30, 91, 255, 0.12);
+ background: linear-gradient(180deg, #f7fbff, #edf6ff);
+ min-height: 66px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 6px;
+}
+
+.sales-summary-label {
+ font-size: 12px;
+ color: #4b5563;
+}
+
+.sales-summary-value {
+ font-size: 15px;
+ color: #1f2937;
+ line-height: 1.4;
+ word-break: break-all;
+}
+
+.sales-focus-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.sales-focus-item {
+ border-radius: 10px;
+ border: 1px solid rgba(30, 91, 255, 0.14);
+ background: #f8fbff;
+ padding: 10px 12px;
+}
+
+.sales-focus-item--strategy {
+ border-color: rgba(31, 122, 114, 0.22);
+ background: linear-gradient(180deg, #f7fcfb, #edf9f6);
+}
+
+.sales-focus-item__head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 10px;
+ font-size: 13px;
+ color: #1f2937;
+}
+
+.sales-focus-tags {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+}
+
+.sales-focus-metrics {
+ margin-top: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ font-size: 12px;
+ color: #475467;
+}
+
+.sales-focus-reasons {
+ margin-top: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.sales-strategy-line {
+ margin: 8px 0 0;
+ font-size: 12px;
+ line-height: 1.6;
+ color: #334155;
+}
+
+.sales-section-title {
+ margin: 4px 0 8px;
+ font-size: 13px;
+ font-weight: 700;
+ color: $deep-blue;
+}
+
+.purchase-intent-quick-prompt-wrap {
+ margin-top: 12px;
+}
+
+.purchase-intent-quick-prompt-title {
+ font-size: 12px;
+ color: #4b5563;
+}
+
+.purchase-intent-quick-prompt-list {
+ margin-top: 8px;
}
.purchase-confirm-card {
@@ -3537,6 +5262,28 @@
font-size: 18px;
}
+ .selected-file-thumb {
+ width: 30px;
+ height: 30px;
+ border-radius: 6px;
+ overflow: hidden;
+ border: 1px solid rgba(0, 85, 212, 0.2);
+ flex-shrink: 0;
+ cursor: zoom-in;
+
+ :deep(.el-image__inner) {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .selected-file-meta {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ }
+
.file-name {
font-size: 13px;
color: $deep-blue;
@@ -3544,6 +5291,12 @@
overflow: hidden;
text-overflow: ellipsis;
font-weight: 600;
+ }
+
+ .file-size {
+ font-size: 11px;
+ color: #5f86b4;
+ line-height: 1.1;
}
.remove-file {
@@ -3703,75 +5456,59 @@
.chat-hero {
display: grid;
- grid-template-columns: 164px minmax(0, 1fr);
- gap: 18px;
- align-items: start;
- padding: 14px 18px 6px;
+ grid-template-columns: 176px minmax(0, 1fr);
+ gap: 14px;
+ align-items: stretch;
+ padding: 8px 18px 4px;
&.compact {
- grid-template-columns: 122px minmax(0, 1fr);
- gap: 12px;
- padding: 8px 18px 2px;
+ grid-template-columns: 132px minmax(0, 1fr);
+ gap: 10px;
+ padding: 4px 18px 2px;
}
}
.assistant-stand {
position: relative;
- min-height: 252px;
+ min-height: 206px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
- padding-top: 18px;
+ padding-top: 8px;
overflow: hidden;
&.compact {
- min-height: 176px;
- padding-top: 8px;
+ min-height: 160px;
+ padding-top: 4px;
}
&.thinking {
.assistant-halo {
opacity: 1;
- transform: scale(1.08);
- filter: blur(8px);
+ transform: scale(1.12);
+ filter: blur(9px);
}
.assistant-scan-ring {
- opacity: 1;
- animation-duration: 1.6s;
+ opacity: 0.95;
+ animation-duration: 1.5s;
}
.assistant-orbit {
- opacity: 1;
+ opacity: 0.76;
}
- .assistant-bot {
- transform: translateY(-4px) scale(1.02);
+ .assistant-model-shell {
+ transform: translateY(-5px) scale(1.02);
}
- .assistant-bot-head {
- box-shadow: 0 0 30px rgba(80, 157, 255, 0.36);
+ .assistant-model-cut {
+ animation-duration: 2.2s;
}
- .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-model-img {
+ filter: saturate(1.06) drop-shadow(0 18px 20px rgba(22, 48, 80, 0.22));
}
.assistant-status {
@@ -3785,219 +5522,174 @@
animation: thinkingDot 1s ease-in-out infinite;
}
+ .assistant-base-lg {
+ animation-duration: 1.8s;
+ }
+
+ .assistant-base-md {
+ animation-duration: 1.5s;
+ }
+
.assistant-base-sm {
- box-shadow: 0 0 24px rgba(255, 93, 122, 0.48);
+ box-shadow: 0 0 24px rgba(30, 91, 255, 0.36);
+ animation-duration: 1.25s;
}
}
}
.assistant-halo {
position: absolute;
- top: 22px;
- width: 130px;
- height: 130px;
+ top: 24px;
+ width: 146px;
+ height: 146px;
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%);
+ background: radial-gradient(circle, rgba(31, 122, 114, 0.26) 0%, rgba(30, 91, 255, 0.2) 42%, rgba(109, 65, 237, 0.12) 66%, transparent 80%);
filter: blur(6px);
- opacity: 0.82;
+ opacity: 0.78;
transition: all 0.35s ease;
}
.assistant-scan-ring {
position: absolute;
- top: 40px;
- width: 132px;
- height: 132px;
+ top: 44px;
+ width: 136px;
+ height: 136px;
border-radius: 50%;
- border: 1px solid rgba(90, 159, 224, 0.22);
+ border: 1px solid rgba(67, 145, 223, 0.24);
box-shadow: inset 0 0 16px rgba(255, 255, 255, 0.25);
- opacity: 0.55;
+ opacity: 0.52;
animation: scanRing 4s linear infinite;
}
.assistant-orbit {
position: absolute;
top: 52px;
- width: 150px;
- height: 150px;
+ width: 156px;
+ height: 156px;
border-radius: 50%;
- border: 1px dashed rgba(92, 135, 255, 0.22);
- opacity: 0.45;
+ border: 1px dashed rgba(92, 135, 255, 0.24);
+ opacity: 0.42;
}
.assistant-orbit-a {
- animation: orbitRotate 8s linear infinite;
+ animation: orbitRotate 8.6s linear infinite;
}
.assistant-orbit-b {
- width: 118px;
- height: 118px;
+ width: 124px;
+ height: 124px;
top: 68px;
- border-color: rgba(255, 108, 150, 0.22);
- animation: orbitRotateReverse 5.6s linear infinite;
+ border-color: rgba(31, 122, 114, 0.24);
+ animation: orbitRotateReverse 6.2s linear infinite;
}
-.assistant-bot {
+.assistant-model-shell {
position: relative;
z-index: 1;
+ width: 148px;
+ height: 178px;
display: flex;
- flex-direction: column;
- align-items: center;
+ align-items: flex-end;
justify-content: center;
- margin-top: 12px;
+ margin-top: 4px;
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%;
+ bottom: 2px;
+ width: 164px;
+ height: 42px;
transform: translateX(-50%);
- background: linear-gradient(135deg, #54bfff, #7a41ff);
- box-shadow: 0 0 14px rgba(84, 191, 255, 0.65);
+ border-radius: 50%;
+ background: radial-gradient(
+ ellipse at center,
+ rgba(43, 126, 211, 0.32) 0%,
+ rgba(43, 126, 211, 0.14) 46%,
+ rgba(43, 126, 211, 0) 74%
+ );
+ filter: blur(2.6px);
+ animation: baseGlow 4.6s ease-in-out infinite;
+ z-index: 1;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: 50%;
+ bottom: 10px;
+ width: 138px;
+ height: 28px;
+ transform: translateX(-50%);
+ border-radius: 50%;
+ border: 1px solid rgba(36, 116, 198, 0.6);
+ box-shadow:
+ inset 0 0 0 1px rgba(255, 255, 255, 0.58),
+ 0 0 22px rgba(42, 116, 196, 0.24);
+ animation: basePulse 3.2s ease-in-out infinite;
+ z-index: 4;
}
}
-.assistant-bot-antenna-left {
- left: 36px;
- transform: rotate(-14deg);
-}
-
-.assistant-bot-antenna-right {
- right: 36px;
- transform: rotate(14deg);
-}
-
-.assistant-bot-head {
+.assistant-model-cut {
position: relative;
+ width: 132px;
+ height: 178px;
+ z-index: 6;
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ transform-origin: center 84%;
+ animation: avatarFloat 3.2s ease-in-out infinite;
+}
+
+.assistant-model-img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ object-position: center bottom;
+ display: block;
+ filter: saturate(1.03) drop-shadow(0 14px 18px rgba(22, 49, 79, 0.2));
+ transition: filter 0.35s ease;
+}
+
+.assistant-model-fallback {
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);
+ height: 92px;
+ border-radius: 24px;
+ color: #fff;
+ background: linear-gradient(145deg, rgba(31, 122, 114, 0.9), rgba(30, 91, 255, 0.9));
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ box-shadow: 0 12px 24px rgba(31, 85, 173, 0.22);
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
-.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 {
+.assistant-base {
position: absolute;
left: 50%;
- bottom: 16px;
- width: 22px;
- height: 4px;
+ bottom: 8px;
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);
+ border: 1px solid rgba(36, 116, 198, 0.28);
+ background: radial-gradient(
+ ellipse at center,
+ rgba(255, 255, 255, 0.94) 0%,
+ rgba(81, 164, 233, 0.16) 58%,
+ rgba(30, 91, 255, 0.06) 100%
+ );
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
}
.assistant-status {
position: relative;
z-index: 1;
- margin-top: 14px;
- padding: 6px 12px;
+ margin-top: 7px;
+ padding: 5px 10px;
border-radius: 999px;
- font-size: 12px;
+ font-size: 11px;
font-weight: 600;
color: $deep-blue;
background: rgba(255, 255, 255, 0.95);
@@ -4017,81 +5709,25 @@
}
.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;
+ pointer-events: none;
}
.assistant-base-md {
- bottom: 6px;
- width: 88px;
- height: 20px;
- border-color: rgba(255, 93, 122, 0.34);
+ bottom: 15px;
+ width: 104px;
+ height: 22px;
+ border-color: rgba(36, 116, 198, 0.48);
+ animation: basePulse 2.8s ease-in-out infinite;
}
.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));
+ bottom: 20px;
+ width: 68px;
+ height: 14px;
+ background: linear-gradient(90deg, rgba(31, 122, 114, 0.82), rgba(45, 124, 255, 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);
- }
+ box-shadow: 0 0 18px rgba(45, 124, 255, 0.34);
+ animation: basePulse 2.2s ease-in-out infinite;
}
@keyframes orbitRotate {
@@ -4132,9 +5768,85 @@
}
}
+.assistant-base-lg {
+ width: 142px;
+ height: 32px;
+ animation: basePulse 3.4s ease-in-out infinite;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 130px;
+ height: 130px;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ background: conic-gradient(
+ from 180deg,
+ transparent 0deg,
+ rgba(36, 116, 198, 0.65) 48deg,
+ transparent 114deg,
+ rgba(36, 116, 198, 0.55) 212deg,
+ transparent 286deg,
+ rgba(31, 122, 114, 0.45) 334deg,
+ transparent 360deg
+ );
+ -webkit-mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%);
+ mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%);
+ opacity: 0.62;
+ animation: baseSpin 9s linear infinite;
+ }
+}
+
+@keyframes avatarFloat {
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-7px);
+ }
+}
+
+@keyframes basePulse {
+ 0%,
+ 100% {
+ transform: translateX(-50%) scale(1);
+ opacity: 0.88;
+ }
+ 50% {
+ transform: translateX(-50%) scale(1.05);
+ opacity: 0.98;
+ }
+}
+
+@keyframes baseSpin {
+ from {
+ transform: translate(-50%, -50%) rotate(0deg);
+ }
+ to {
+ transform: translate(-50%, -50%) rotate(360deg);
+ }
+}
+
+@keyframes baseGlow {
+ 0%,
+ 100% {
+ transform: translateX(-50%) scaleX(1);
+ opacity: 0.82;
+ }
+ 50% {
+ transform: translateX(-50%) scaleX(1.06);
+ opacity: 0.96;
+ }
+}
+
.welcome-card {
position: relative;
- padding: 14px 14px 12px;
+ align-self: stretch;
+ min-height: 206px;
+ padding: 9px 10px 8px;
border-radius: 16px;
background:
linear-gradient(#fff, #fff) padding-box,
@@ -4143,7 +5855,8 @@
box-shadow: 0 16px 36px rgba(0, 85, 212, 0.12);
&.compact {
- padding: 10px 12px;
+ min-height: 160px;
+ padding: 8px 9px 7px;
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0, 85, 212, 0.07);
@@ -4152,8 +5865,8 @@
}
.welcome-title {
- font-size: 17px;
- line-height: 1.3;
+ font-size: 16px;
+ line-height: 1.25;
br {
display: none;
@@ -4161,65 +5874,69 @@
}
.welcome-desc {
- margin-top: 6px;
- font-size: 12px;
- line-height: 1.55;
+ margin-top: 4px;
+ font-size: 11px;
+ line-height: 1.5;
}
.quick-prompt-list {
- margin-top: 10px;
- gap: 6px;
+ margin-top: 8px;
+ gap: 5px;
}
.quick-prompt-btn {
- padding: 8px 10px;
- font-size: 12px;
+ padding: 7px 9px;
+ font-size: 11px;
border-radius: 7px;
}
.more-prompts-btn {
- margin-top: 8px;
- font-size: 12px;
+ margin-top: 6px;
+ font-size: 11px;
}
}
}
.welcome-eyebrow {
- font-size: 11px;
+ font-size: 10px;
font-weight: 700;
letter-spacing: 2px;
color: rgba(0, 85, 212, 0.58);
- margin-bottom: 8px;
+ margin-bottom: 5px;
}
.welcome-title {
margin: 0;
- font-size: 26px;
- line-height: 1.2;
+ font-size: 20px;
+ line-height: 1.15;
font-weight: 800;
color: #172033;
+
+ br {
+ display: none;
+ }
}
.welcome-desc {
- margin: 10px 0 0;
- font-size: 13px;
- line-height: 1.7;
+ margin: 5px 0 0;
+ font-size: 12px;
+ line-height: 1.5;
color: #5f6980;
}
.quick-prompt-list {
display: grid;
- gap: 8px;
- margin-top: 14px;
+ gap: 6px;
+ margin-top: 8px;
}
.quick-prompt-btn {
width: 100%;
border: none;
- border-radius: 10px;
- padding: 11px 14px;
+ border-radius: 9px;
+ padding: 7px 10px;
text-align: left;
- font-size: 13px;
+ font-size: 12px;
font-weight: 600;
color: #fff;
cursor: pointer;
@@ -4267,14 +5984,14 @@
}
.more-prompts-btn {
- margin-top: 10px;
- padding: 0 12px;
- height: 32px;
+ margin-top: 6px;
+ padding: 0 10px;
+ height: 26px;
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-size: 12px;
font-weight: 600;
cursor: pointer;
display: inline-flex;
@@ -4289,21 +6006,6 @@
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));
}
}
@@ -4399,12 +6101,6 @@
.welcome-title {
font-size: 21px;
- }
-
- .hero-dot-grid {
- grid-template-columns: repeat(12, 1fr);
- gap: 6px;
- padding: 0 14px 12px;
}
.message-list {
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
index 0ff79ef..3ea63b5 100644
--- a/src/components/Breadcrumb/index.vue
+++ b/src/components/Breadcrumb/index.vue
@@ -86,9 +86,10 @@
<style lang='scss' scoped>
.app-breadcrumb.el-breadcrumb {
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
font-size: 14px;
- line-height: 56px;
+ line-height: 1;
margin-left: 8px;
:deep(.el-breadcrumb__inner) {
diff --git a/src/components/Dialog/FileList.vue b/src/components/Dialog/FileList.vue
index 6e0ca23..b0e78cf 100644
--- a/src/components/Dialog/FileList.vue
+++ b/src/components/Dialog/FileList.vue
@@ -36,9 +36,16 @@
<el-table-column v-if="showActions"
fixed="right"
label="鎿嶄綔"
- :width="120"
+ :width="150"
align="center">
<template #default="scope">
+ <el-button link
+ type="primary"
+ size="small"
+ class="download-link"
+ @click="previewFile(scope.row.previewURL)">
+ 棰勮
+ </el-button>
<el-button link
type="primary"
size="small"
@@ -58,9 +65,11 @@
</el-table>
</div>
</el-dialog>
+ <filePreview ref="filePreviewRef" />
</template>
<script setup>
+import { ElMessage } from 'element-plus'
import { ref, computed, getCurrentInstance, onMounted, watch } from "vue";
import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue";
import {
@@ -68,6 +77,8 @@
deleteAttachment,
createAttachment,
} from "@/api/basicData/storageAttachment.js";
+ import filePreview from '@/components/filePreview/index.vue'
+ const filePreviewRef = ref()
const props = defineProps({
visible: {
@@ -122,6 +133,15 @@
isShow.value = false;
};
+ // 棰勮鏂囦欢
+ const previewFile = (url) => {
+ if (url) {
+ filePreviewRef.value.open(url)
+ } else {
+ ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曢瑙�')
+ }
+ }
+
const handleUpload = () => {
uploadDialogVisible.value = true;
};
@@ -129,22 +149,23 @@
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("涓婁紶澶辫触");
- }
+ createAttachment({
+ application: "file",
+ recordType: props.recordType,
+ recordId: props.recordId,
+ storageBlobDTOs: [...newFileList.value, ...tableData.value],
+ }).then((res) => {
+ if (res && res.code === 200) {
+ proxy?.$modal?.msgSuccess("涓婁紶鎴愬姛");
+ newFileList.value = [];
+ // 鍒锋柊鍒楄〃
+ setList();
+ }
+ }).finally(() => {
+ uploadDialogVisible.value = false;
+ })
}
- uploadDialogVisible.value = false;
- };
+ }
const closeUpload = () => {
newFileList.value = [];
@@ -152,13 +173,12 @@
};
const handleDelete = async (row, index) => {
- try {
- await deleteAttachment([row.storageAttachmentId]);
- proxy?.$modal?.msgSuccess("鍒犻櫎鎴愬姛");
- setList();
- } catch (error) {
- proxy?.$modal?.msgError("鍒犻櫎澶辫触");
- }
+ deleteAttachment([row.storageAttachmentId]).then((res) => {
+ if (res && res.code === 200) {
+ proxy?.$modal?.msgSuccess("鍒犻櫎鎴愬姛");
+ setList();
+ }
+ })
};
const setList = () => {
@@ -166,9 +186,7 @@
recordType: props.recordType,
recordId: props.recordId,
}).then(res => {
- if (res && res.data) {
- tableData.value = res.data || [];
- }
+ tableData.value = (res && res.data) || [];
});
};
diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue
index 3ea9388..c35557f 100644
--- a/src/components/Hamburger/index.vue
+++ b/src/components/Hamburger/index.vue
@@ -1,8 +1,8 @@
-<template>
- <div style="padding: 0 15px;" @click="toggleClick">
- <svg
- :class="{'is-active':isActive}"
- class="hamburger"
+<template>
+ <div class="hamburger-wrap" @click="toggleClick">
+ <svg
+ :class="{'is-active':isActive}"
+ class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
@@ -28,15 +28,23 @@
}
</script>
-<style scoped>
-.hamburger {
- display: inline-block;
- vertical-align: middle;
- width: 20px;
- height: 20px;
-}
-
-.hamburger.is-active {
- transform: rotate(180deg);
-}
+<style scoped>
+.hamburger-wrap {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.hamburger {
+ display: inline-block;
+ vertical-align: middle;
+ width: 22px;
+ height: 22px;
+}
+
+.hamburger.is-active {
+ transform: rotate(180deg);
+}
</style>
diff --git a/src/components/HeaderSearch/index.vue b/src/components/HeaderSearch/index.vue
index b57fe69..a81dd98 100644
--- a/src/components/HeaderSearch/index.vue
+++ b/src/components/HeaderSearch/index.vue
@@ -1,13 +1,13 @@
<template>
<div class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
- <el-dialog
- v-model="show"
- width="600"
- @close="close"
- :show-close="false"
- append-to-body
- >
+ <el-dialog
+ v-model="show"
+ width="600"
+ @close="close"
+ :show-close="true"
+ append-to-body
+ >
<el-input
v-model="search"
ref="headerSearchSelectRef"
@@ -40,28 +40,51 @@
</div>
</template>
-<script setup>
-import Fuse from 'fuse.js'
-import { getNormalPath } from '@/utils/ruoyi'
-import { isHttp } from '@/utils/validate'
-import usePermissionStore from '@/store/modules/permission'
-
-const search = ref('')
-const options = ref([])
-const searchPool = ref([])
-const show = ref(false)
+<script setup>
+import Fuse from 'fuse.js'
+import { getNormalPath } from '@/utils/ruoyi'
+import { isHttp } from '@/utils/validate'
+import usePermissionStore from '@/store/modules/permission'
+
+const props = defineProps({
+ keyword: {
+ type: String,
+ default: ''
+ }
+})
+
+const search = ref('')
+const options = ref([])
+const searchPool = ref([])
+const show = ref(false)
const fuse = ref(undefined)
const headerSearchSelectRef = ref(null)
const router = useRouter()
const routes = computed(() => usePermissionStore().defaultRoutes)
-function click() {
- show.value = !show.value
- if (show.value) {
- headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
- options.value = searchPool.value
- }
-}
+function click() {
+ show.value = !show.value
+ if (show.value) {
+ syncSearchFromKeyword()
+ nextTick(() => {
+ headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
+ })
+ }
+}
+
+function syncSearchFromKeyword() {
+ search.value = props.keyword?.trim?.() ?? ''
+ querySearch(search.value)
+}
+
+function open(keyword = props.keyword) {
+ show.value = true
+ search.value = keyword?.trim?.() ?? ''
+ querySearch(search.value)
+ nextTick(() => {
+ headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
+ })
+}
function close() {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
@@ -160,10 +183,14 @@
searchPool.value = generateRoutes(routes.value)
})
-watch(searchPool, (list) => {
- initFuse(list)
-})
-</script>
+watch(searchPool, (list) => {
+ initFuse(list)
+})
+
+defineExpose({
+ open
+})
+</script>
<style lang='scss' scoped>
.header-search {
diff --git a/src/components/ProcessParamListDialog.vue b/src/components/ProcessParamListDialog.vue
index 38e892d..deee249 100644
--- a/src/components/ProcessParamListDialog.vue
+++ b/src/components/ProcessParamListDialog.vue
@@ -130,6 +130,7 @@
</el-form-item>
<el-form-item label="鏍囧噯鍊�">
<el-input v-model="selectedParam.standardValue"
+ @input="val => onStandardValueInput(val, selectedParam)"
placeholder="璇疯緭鍏ラ粯璁ゅ��" />
</el-form-item>
<el-form-item label="鏄惁蹇呭~">
@@ -144,7 +145,8 @@
</div>
</div>
<template #footer>
- <el-button type="primary" @click="handleParamSelectSubmit">纭畾</el-button>
+ <el-button type="primary"
+ @click="handleParamSelectSubmit">纭畾</el-button>
<el-button @click="selectParamDialogVisible = false">鍙栨秷</el-button>
</template>
</el-dialog>
@@ -174,11 +176,13 @@
<el-form-item label="鏍囧噯鍊�"
prop="standardValue">
<el-input v-model="editParamForm.standardValue"
+ @input="val => onStandardValueInput(val, editParamForm)"
placeholder="璇疯緭鍏ユ爣鍑嗗��" />
</el-form-item>
</el-form>
<template #footer>
- <el-button type="primary" @click="handleEditParamSubmit">纭畾</el-button>
+ <el-button type="primary"
+ @click="handleEditParamSubmit">纭畾</el-button>
<el-button @click="editParamDialogVisible = false">鍙栨秷</el-button>
</template>
</el-dialog>
@@ -266,8 +270,32 @@
paramFormat: "",
unit: "",
});
+
+ const onStandardValueInput = (val, target) => {
+ const data = target.value || target;
+ const type = data.paramType || data.parameterType;
+ if (type === 1) {
+ // 鏁板�兼牸寮忥細涓嶈兘杈撳叆涓枃鎴栬嫳鏂囧瓧绗�
+ data.standardValue = val.replace(/[a-zA-Z\u4e00-\u9fa5]/g, "");
+ }
+ };
+
const editParamRules = ref({
- // standardValue: [{ required: true, message: "璇疯緭鍏ユ爣鍑嗗��", trigger: "blur" }],
+ standardValue: [
+ {
+ validator: (rule, value, callback) => {
+ const type =
+ editParamForm.value.paramType || editParamForm.value.parameterType;
+ if (type === 1 && value) {
+ if (/[a-zA-Z\u4e00-\u9fa5]/.test(value)) {
+ return callback(new Error("鏁板�兼牸寮忎笉鑳藉寘鍚腑鑻辨枃瀛楃"));
+ }
+ }
+ callback();
+ },
+ trigger: "blur",
+ },
+ ],
});
const editParamFormRef = ref(null);
diff --git a/src/components/PurchaseAIChatSidebar/index.vue b/src/components/PurchaseAIChatSidebar/index.vue
index ecc46be..428f84f 100644
--- a/src/components/PurchaseAIChatSidebar/index.vue
+++ b/src/components/PurchaseAIChatSidebar/index.vue
@@ -3,24 +3,8 @@
</template>
<script setup>
-import { ShoppingCart } from '@element-plus/icons-vue'
import AIChatSidebar from '@/components/AIChatSidebar/index.vue'
+import { purchaseAssistant } from '@/components/AIChatSidebar/assistants'
-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: '鏆傛棤閲囪喘浼氳瘽'
- }
-]
+const assistants = [purchaseAssistant]
</script>
diff --git a/src/components/filePreview/index.vue b/src/components/filePreview/index.vue
index cda5b56..d8720c3 100644
--- a/src/components/filePreview/index.vue
+++ b/src/components/filePreview/index.vue
@@ -78,9 +78,9 @@
transformData: (workbookData) => workbookData,
});
-// 璁$畻灞炴�� - 鍒ゆ柇鏂囦欢绫诲瀷
+// 璁$畻灞炴�� - 鍒ゆ柇鏂囦欢绫诲瀷锛堟敮鎸乁RL甯︽煡璇㈠弬鏁帮級
const isImage = computed(() => {
- const state = /\.(jpg|jpeg|png|gif)$/i.test(fileUrl.value);
+ const state = /\.(jpg|jpeg|png|gif)(\?.*)?$/i.test(fileUrl.value);
if (state) {
imgUrl.value = fileUrl.value.replaceAll('word', 'img');
}
@@ -89,23 +89,23 @@
const isPdf = computed(() => {
console.log(fileUrl.value)
- return /\.pdf$/i.test(fileUrl.value);
+ return /\.pdf(\?.*)?$/i.test(fileUrl.value);
});
const isDoc = computed(() => {
- return /\.(doc|docx)$/i.test(fileUrl.value);
+ return /\.(doc|docx)(\?.*)?$/i.test(fileUrl.value);
});
const isXls = computed(() => {
- const state = /\.(xls|xlsx)$/i.test(fileUrl.value);
+ const state = /\.(xls|xlsx)(\?.*)?$/i.test(fileUrl.value);
if (state) {
- options.value.xls = /\.(xls)$/i.test(fileUrl.value);
+ options.value.xls = /\.(xls)(\?.*)?$/i.test(fileUrl.value);
}
return state;
});
const isZipOrRar = computed(() => {
- return /\.(zip|rar)$/i.test(fileUrl.value);
+ return /\.(zip|rar)(\?.*)?$/i.test(fileUrl.value);
});
const isSupported = computed(() => {
@@ -164,7 +164,7 @@
};
const open = (url) => {
- fileUrl.value = window.location.protocol+'//'+window.location.host+ url;
+ fileUrl.value = url;
dialogVisible.value = true;
};
const handleClose = () => {
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
index 011e118..a511014 100644
--- a/src/layout/components/AppMain.vue
+++ b/src/layout/components/AppMain.vue
@@ -37,30 +37,29 @@
</script>
<style lang="scss" scoped>
-.app-main {
- /* 50= navbar 50 */
- min-height: calc(100vh - 50px);
- width: 100%;
- position: relative;
- overflow: hidden;
- background: transparent;
-}
-
-.route-view-wrapper {
- width: 100%;
- height: 100%;
- padding: 120px 16px 24px 0;
-}
-
-.fixed-header + .app-main {
- padding-top: 0;
-}
-
-.hasTagsView {
- .app-main {
- /* 84 = navbar + tags-view = 50 + 34 */
- min-height: calc(100vh - 84px);
- }
+.app-main {
+ min-height: calc(100vh - var(--topbar-height));
+ width: 100%;
+ position: relative;
+ overflow: visible;
+ background: transparent;
+}
+
+.route-view-wrapper {
+ width: 100%;
+ height: 100%;
+ padding: var(--content-gap);
+ padding-top: 0;
+}
+
+.fixed-header + .app-main {
+ padding-top: 0;
+}
+
+.hasTagsView {
+ .app-main {
+ min-height: calc(100vh - var(--topbar-height) - var(--tagsbar-height));
+ }
.fixed-header + .app-main {
padding-top: 0;
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
index cb08221..206205d 100644
--- a/src/layout/components/Navbar.vue
+++ b/src/layout/components/Navbar.vue
@@ -1,13 +1,35 @@
<template>
<div class="navbar">
- <div>
- <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
- @toggleClick="toggleSideBar" />
- <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
+ <div class="left-zone">
+ <hamburger
+ id="hamburger-container"
+ :is-active="appStore.sidebar.opened"
+ class="hamburger-container"
+ @toggleClick="toggleSideBar"
+ />
+ <breadcrumb
+ v-if="!settingsStore.topNav"
+ id="breadcrumb-container"
+ class="breadcrumb-container"
+ />
</div>
- <!-- <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
+
+ <div class="center-zone">
+ <el-icon class="search-icon" @click="openHeaderSearch"><Search /></el-icon>
+ <el-input
+ v-model="topSearchKeyword"
+ placeholder="鎼滅储鑿滃崟 / 鍔熻兘 / 鏁版嵁"
+ clearable
+ @keyup.enter="openHeaderSearch"
+ />
+ <header-search
+ ref="headerSearchRef"
+ :keyword="topSearchKeyword"
+ class="search-popup-trigger"
+ />
+ </div>
+
<div class="right-menu">
- <!-- 娑堟伅閫氱煡 -->
<el-popover
v-model:visible="notificationVisible"
:width="500"
@@ -19,20 +41,26 @@
<template #reference>
<div class="notification-container right-menu-item hover-effect">
<el-badge :value="unreadCount" :hidden="unreadCount === 0" class="notification-badge">
- <el-icon :size="20" style="cursor: pointer;">
+ <el-icon :size="18">
<Bell />
</el-icon>
</el-badge>
</div>
</template>
- <NotificationCenter
- @unreadCountChange="handleUnreadCountChange"
- ref="notificationCenterRef"
- />
+ <NotificationCenter @unreadCountChange="handleUnreadCountChange" ref="notificationCenterRef" />
</el-popover>
+
+ <div class="right-menu-item hover-effect screenfull-container">
+ <screenfull />
+ </div>
+
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
+ <div class="user-summary">
+ <div class="user-name">{{ userStore.nickName || userStore.name || "绠$悊鍛�" }}</div>
+ <div class="user-role">{{ userStore.roleName || "绯荤粺鐢ㄦ埛" }}</div>
+ </div>
<img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon>
</div>
@@ -56,261 +84,299 @@
</template>
<script setup>
-import { ElMessageBox } from 'element-plus'
-import { Bell } from '@element-plus/icons-vue'
-import Breadcrumb from '@/components/Breadcrumb'
-import TopNav from '@/components/TopNav'
-import Hamburger from '@/components/Hamburger'
-import Screenfull from '@/components/Screenfull'
-import SizeSelect from '@/components/SizeSelect'
-import HeaderSearch from '@/components/HeaderSearch'
-import RuoYiGit from '@/components/RuoYi/Git'
-import RuoYiDoc from '@/components/RuoYi/Doc'
-import NotificationCenter from './NotificationCenter/index.vue'
-import useAppStore from '@/store/modules/app'
-import useUserStore from '@/store/modules/user'
-import useSettingsStore from '@/store/modules/settings'
+import { ElMessageBox } from "element-plus";
+import { Bell, Search } from "@element-plus/icons-vue";
+import Breadcrumb from "@/components/Breadcrumb";
+import Hamburger from "@/components/Hamburger";
+import Screenfull from "@/components/Screenfull";
+import HeaderSearch from "@/components/HeaderSearch";
+import NotificationCenter from "./NotificationCenter/index.vue";
+import useAppStore from "@/store/modules/app";
+import useUserStore from "@/store/modules/user";
+import useSettingsStore from "@/store/modules/settings";
-const appStore = useAppStore()
-const userStore = useUserStore()
-const settingsStore = useSettingsStore()
-const notificationVisible = ref(false)
-const notificationCenterRef = ref(null)
-const unreadCount = ref(0)
+const appStore = useAppStore();
+const userStore = useUserStore();
+const settingsStore = useSettingsStore();
+
+const topSearchKeyword = ref("");
+const headerSearchRef = ref(null);
+const notificationVisible = ref(false);
+const notificationCenterRef = ref(null);
+const unreadCount = ref(0);
+
function toggleSideBar() {
- appStore.toggleSideBar()
+ appStore.toggleSideBar();
}
-// const redirect = ref(undefined)
-// watch(route, (newRoute) => {
-// redirect.value = newRoute.query && newRoute.query.redirect
-// }, { immediate: true })
+
+function openHeaderSearch() {
+ headerSearchRef.value?.open(topSearchKeyword.value);
+}
function handleCommand(command) {
switch (command) {
case "setLayout":
- setLayout()
- break
+ setLayout();
+ break;
case "logout":
- logout()
- break
+ logout();
+ break;
default:
- break
+ break;
}
}
function logout() {
- ElMessageBox.confirm('纭畾娉ㄩ攢骞堕��鍑虹郴缁熷悧锛�', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).then(() => {
- userStore.logOut().then(() => {
- location.href = '/index'
+ ElMessageBox.confirm("纭畾娉ㄩ攢骞堕��鍑虹郴缁熷悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ userStore.logOut().then(() => {
+ location.href = "/index";
+ });
})
- }).catch(() => { })
+ .catch(() => {});
}
-const emits = defineEmits(['setLayout'])
+const emits = defineEmits(["setLayout"]);
function setLayout() {
- emits('setLayout')
+ emits("setLayout");
}
-function toggleTheme() {
- settingsStore.toggleTheme()
-}
-
-// 娑堟伅閫氱煡鐩稿叧
function handleUnreadCountChange(count) {
- unreadCount.value = count
+ unreadCount.value = count;
}
-// 缁勪欢鎸傝浇鏃跺姞杞芥湭璇绘暟閲忓拰瀹氭椂鍒锋柊
-let unreadCountTimer = null
+let unreadCountTimer = null;
onMounted(() => {
- // 寤惰繜鍔犺浇锛岀‘淇濈粍浠跺凡娓叉煋
nextTick(() => {
if (notificationCenterRef.value) {
- notificationCenterRef.value.loadUnreadCount()
+ notificationCenterRef.value.loadUnreadCount();
}
- })
- // 瀹氭椂鍒锋柊鏈鏁伴噺锛堟瘡30绉掞級
+ });
+
unreadCountTimer = setInterval(() => {
if (notificationCenterRef.value) {
- notificationCenterRef.value.loadUnreadCount()
+ notificationCenterRef.value.loadUnreadCount();
}
- }, 30000)
-})
+ }, 30000);
+});
-// 鐩戝惉 popover 鏄剧ず鐘舵�侊紝鎵撳紑鏃跺姞杞芥秷鎭垪琛�
watch(notificationVisible, (val) => {
if (val && notificationCenterRef.value) {
nextTick(() => {
- notificationCenterRef.value.loadMessages()
- })
+ notificationCenterRef.value.loadMessages();
+ });
}
-})
+});
onUnmounted(() => {
if (unreadCountTimer) {
- clearInterval(unreadCountTimer)
+ clearInterval(unreadCountTimer);
}
-})
+});
</script>
-<style lang='scss' scoped>
-.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;
+<style lang="scss" scoped>
+.navbar {
+ height: var(--topbar-height);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 14px;
+ background: rgba(255, 255, 255, 0.86);
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ border-radius: var(--content-radius);
+ backdrop-filter: blur(16px);
+ box-shadow: 0 8px 20px rgba(15, 23, 42, 0.05);
+ padding: 0 18px;
+}
- .hamburger-container {
- line-height: 52px;
- height: 100%;
- float: left;
- cursor: pointer;
- transition: background 0.3s;
- -webkit-tap-highlight-color: transparent;
+.left-zone {
+ flex: 0 1 420px;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
- &:hover {
- background: var(--navbar-hover);
- }
- }
+.hamburger-container {
+ line-height: 36px;
+ height: 36px;
+ width: 36px;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--navbar-text);
- .breadcrumb-container {
- float: left;
- }
-
- .topmenu-container {
- position: absolute;
- left: 50px;
- }
-
- .errLog-container {
- display: inline-block;
- vertical-align: top;
- }
-
- .right-menu {
- float: right;
- height: 100%;
- align-items: center;
- display: flex;
-
- &:focus {
- outline: none;
- }
-
- .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: var(--navbar-hover);
- }
- }
-
- &.theme-switch-wrapper {
- display: flex;
- align-items: center;
-
- svg {
- transition: transform 0.3s;
-
- &:hover {
- transform: scale(1.15);
- }
- }
- }
- }
-
- .notification-container {
- margin-right: 12px;
- display: flex;
- align-items: center;
- cursor: pointer;
-
- .notification-badge {
- :deep(.el-badge__content) {
- border: none;
- }
- }
- }
-
- .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;
- }
- }
- }
+ &:hover {
+ background: var(--navbar-hover);
}
}
+.breadcrumb-container {
+ min-width: 0;
+}
+
+.center-zone {
+ width: clamp(360px, 34vw, 560px);
+ min-width: 320px;
+ height: 38px;
+ border-radius: 999px;
+ border: 1px solid rgba(148, 163, 184, 0.24);
+ background: rgba(248, 251, 255, 0.92);
+ display: flex;
+ align-items: center;
+ padding: 0 12px;
+ gap: 8px;
+}
+
+.search-icon {
+ color: #5b86c9;
+ cursor: pointer;
+}
+
+.center-zone :deep(.el-input__wrapper) {
+ border: 0;
+ box-shadow: none !important;
+ background: transparent;
+ padding: 0;
+}
+
+.center-zone :deep(.el-input__inner) {
+ color: #334155;
+ font-size: 13px;
+}
+
+.search-popup-trigger :deep(.search-icon) {
+ color: #5b86c9;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.right-menu {
+ height: 100%;
+ align-items: center;
+ display: flex;
+ gap: 14px;
+}
+
+.right-menu-item {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--navbar-text);
+ border-radius: 8px;
+}
+
+.hover-effect {
+ cursor: pointer;
+ transition: background 0.2s;
+
+ &:hover {
+ background: var(--navbar-hover);
+ }
+}
+
+.notification-container,
+.screenfull-container {
+ width: 36px;
+ height: 36px;
+}
+
+.notification-badge :deep(.el-badge__content) {
+ border: none;
+}
+
+.screenfull-container :deep(.svg-icon) {
+ width: 16px;
+ height: 16px;
+ color: var(--navbar-text);
+}
+
+.avatar-container {
+ height: 100%;
+ display: flex;
+ align-items: center;
+}
+
+.avatar-container :deep(.el-dropdown) {
+ height: 100%;
+ display: flex;
+ align-items: center;
+}
+
+.avatar-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 4px 10px 4px 8px;
+ height: 44px;
+ border-radius: 22px;
+ background: rgba(255, 255, 255, 0.9);
+ border: 1px solid rgba(148, 163, 184, 0.22);
+}
+
+.user-summary {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 2px;
+}
+
+.user-name {
+ color: var(--text-primary);
+ font-size: 13px;
+ line-height: 1;
+}
+
+.user-role {
+ color: var(--text-tertiary);
+ font-size: 11px;
+ line-height: 1;
+}
+
+.user-avatar {
+ cursor: pointer;
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: 1px solid rgba(148, 163, 184, 0.3);
+}
+
+@media (max-width: 1200px) {
+ .center-zone {
+ display: none;
+ }
+
+ .user-summary {
+ display: none;
+ }
+}
</style>
<style lang="scss">
-.notification-popover {
- padding: 0 !important;
- border-radius: 20px !important;
- border: 1px solid var(--surface-border) !important;
- box-shadow: var(--shadow-md) !important;
-
+.notification-popover {
+ padding: 0 !important;
+ border-radius: 16px !important;
+ border: 1px solid rgba(148, 163, 184, 0.22) !important;
+ box-shadow: 0 18px 40px rgba(15, 23, 42, 0.12) !important;
+ background: rgba(255, 255, 255, 0.94) !important;
+ backdrop-filter: blur(16px);
+
.el-popover__title {
display: none;
}
-
+
.el-popover__body {
padding: 0 !important;
}
}
-.el-badge__content.is-fixed{
- top: 12px;
+
+.el-badge__content.is-fixed {
+ top: 8px;
}
</style>
diff --git a/src/layout/components/NotificationCenter/index.vue b/src/layout/components/NotificationCenter/index.vue
index 2517c22..66f489a 100644
--- a/src/layout/components/NotificationCenter/index.vue
+++ b/src/layout/components/NotificationCenter/index.vue
@@ -255,6 +255,7 @@
flex-direction: column;
width: 500px;
padding: 16px;
+ background: rgba(255, 255, 255, 0.92);
}
.popover-header {
@@ -264,12 +265,12 @@
width: 100%;
margin-bottom: 16px;
padding-bottom: 12px;
- border-bottom: 1px solid #f0f0f0;
+ border-bottom: 1px solid var(--surface-border);
.popover-title {
font-size: 18px;
font-weight: 500;
- color: #303133;
+ color: var(--text-primary);
}
}
@@ -315,11 +316,11 @@
.notification-item {
display: flex;
padding: 12px 0;
- border-bottom: 1px solid #f0f0f0;
+ border-bottom: 1px solid rgba(148, 163, 184, 0.18);
transition: background-color 0.3s;
&:hover {
- background-color: #f5f7fa;
+ background-color: #f8fbff;
}
&.read {
@@ -333,7 +334,7 @@
display: flex;
align-items: center;
justify-content: center;
- background-color: #f0f9ff;
+ background-color: rgba(59, 130, 246, 0.12);
border-radius: 50%;
margin-right: 12px;
}
@@ -345,13 +346,13 @@
.notification-title {
font-size: 14px;
font-weight: 500;
- color: #303133;
+ color: var(--text-primary);
margin-bottom: 8px;
}
.notification-detail {
font-size: 13px;
- color: #606266;
+ color: var(--text-secondary);
line-height: 1.5;
margin-bottom: 8px;
word-break: break-all;
@@ -359,7 +360,7 @@
.notification-time {
font-size: 12px;
- color: #909399;
+ color: var(--text-tertiary);
}
}
@@ -375,7 +376,7 @@
.pagination-wrapper {
margin-top: 16px;
padding-top: 16px;
- border-top: 1px solid #f0f0f0;
+ border-top: 1px solid var(--surface-border);
display: flex;
justify-content: center;
padding-left: 0;
diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue
index 57c2b4b..1a66c4a 100644
--- a/src/layout/components/Sidebar/Logo.vue
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -4,7 +4,7 @@
<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="/">
+ <router-link v-else key="expand" class="sidebar-logo-link" :style="expandLogoLinkStyle" 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>
@@ -35,6 +35,14 @@
})
const logoUrl = ref('')
+
+const expandLogoLinkStyle = computed(() => {
+ if (!logoUrl.value) {
+ return { '--logo-bg-image': 'none' }
+ }
+ const escaped = String(logoUrl.value).replace(/"/g, '\\"')
+ return { '--logo-bg-image': `url("${escaped}")` }
+})
const updateLogoUrl = () => {
if (!cleanFactoryName.value) {
@@ -82,32 +90,63 @@
.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;
+ height: 78px !important;
+ line-height: 78px;
+ background: transparent;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+ text-align: left;
overflow: hidden;
- box-shadow: var(--shadow-sm);
+ box-shadow: none;
+ backdrop-filter: none;
+ transition: all 0.3s ease;
.sidebar-logo-link {
+ position: relative;
+ isolation: isolate;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
- padding: 0 18px 0 14px;
+ padding: 0;
+
+ &::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background-image: var(--logo-bg-image);
+ background-size: cover;
+ background-position: center;
+ opacity: 0.26;
+ filter: blur(8px) saturate(0.9);
+ transform: scale(1.06);
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ &::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, rgba(16, 49, 89, 0.18), rgba(16, 49, 89, 0.08));
+ pointer-events: none;
+ z-index: 0;
+ }
}
.sidebar-logo {
- width: auto;
- max-width: 250px;
- max-height: 50px;
- height: auto;
+ width: 100%;
+ height: 100%;
+ max-width: none;
+ max-height: none;
+ padding: 6px 10px;
vertical-align: middle;
object-fit: contain;
object-position: center;
+ filter: none;
+ display: block;
+ position: relative;
+ z-index: 1;
}
.sidebar-title {
@@ -119,23 +158,40 @@
font-size: 14px;
font-family: "Segoe UI", "PingFang SC", sans-serif;
vertical-align: middle;
+ position: relative;
+ z-index: 1;
}
&.collapse {
.sidebar-logo-link {
+ display: flex;
+ align-items: center;
+ justify-content: center;
padding: 0;
+
+ &::before,
+ &::after {
+ display: none;
+ }
}
.sidebar-logo {
- max-width: 30px;
- max-height: 30px;
+ width: calc(100% - 8px);
+ height: calc(100% - 8px);
+ max-width: none;
+ max-height: none;
+ padding: 4px;
+ margin: 0 auto;
+ filter: none;
+ object-fit: contain;
+ object-position: center;
}
.sidebar-favicon {
- width: 24px;
- height: 24px;
- max-width: 24px;
- max-height: 24px;
+ width: calc(100% - 8px);
+ height: calc(100% - 8px);
+ max-width: none;
+ max-height: none;
}
}
}
diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue
index f395d95..003b489 100644
--- a/src/layout/components/Sidebar/SidebarItem.vue
+++ b/src/layout/components/Sidebar/SidebarItem.vue
@@ -1,17 +1,19 @@
<template>
- <div v-if="!item.hidden">
+ <div v-if="!item.hidden" class="sidebar-item-wrapper">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
- <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
- <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
+ <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" class="menu-icon"/>
+ <template #title>
+ <span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
+ </template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<template v-if="item.meta" #title>
- <svg-icon :icon-class="item.meta && item.meta.icon" />
+ <svg-icon :icon-class="item.meta && item.meta.icon" class="menu-icon" />
<span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
</template>
@@ -98,3 +100,59 @@
}
}
</script>
+
+<style lang="scss" scoped>
+.sidebar-item-wrapper {
+ :deep(.menu-icon) {
+ width: 26px;
+ height: 26px;
+ margin-right: 12px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: color 0.2s ease;
+ color: var(--sidebar-text);
+ opacity: 0.88;
+ }
+
+ :deep(.el-menu-item:hover .menu-icon),
+ :deep(.el-sub-menu__title:hover .menu-icon) {
+ color: #ffffff;
+ opacity: 1;
+ }
+
+ :deep(.el-menu-item.is-active .menu-icon) {
+ color: var(--menu-active-text) !important;
+ opacity: 1;
+ }
+
+ :deep(.menu-title) {
+ font-weight: 500;
+ transition: color 0.2s ease;
+ color: var(--sidebar-text);
+ opacity: 0.82;
+ }
+
+ :deep(.el-menu-item:hover .menu-title),
+ :deep(.el-sub-menu__title:hover .menu-title) {
+ color: #ffffff;
+ opacity: 1;
+ }
+
+ :deep(.el-menu-item.is-active .menu-title) {
+ color: var(--menu-active-text) !important;
+ opacity: 1;
+ }
+
+ :deep(.nest-menu) {
+ .menu-icon {
+ width: 22px;
+ height: 22px;
+ margin-right: 10px;
+ }
+
+ .menu-title { font-size: 13px; }
+ }
+}
+</style>
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
index 0692dda..2a58c29 100644
--- a/src/layout/components/Sidebar/index.vue
+++ b/src/layout/components/Sidebar/index.vue
@@ -1,142 +1,166 @@
<template>
- <div :class="{ 'has-logo': showLogo }"
- class="sidebar-container">
- <logo v-if="showLogo"
- :collapse="isCollapse" />
+ <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
+ :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";
+import Logo from "./Logo";
+import SidebarItem from "./SidebarItem";
+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 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 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 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 getMenuTextColor = computed(() => "var(--sidebar-text)");
- const activeMenu = computed(() => {
- const { meta, path } = route;
- if (meta.activeMenu) {
- return meta.activeMenu;
- }
- return path;
- });
+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;
+.sidebar-container {
+ background: transparent;
+ border-radius: 0;
+ overflow: hidden;
- .scrollbar-wrapper {
- background-color: v-bind(getMenuBackground);
+ .scrollbar-wrapper {
+ background: transparent;
+ }
+
+ .el-menu {
+ border: none !important;
+ height: 100%;
+ width: 100% !important;
+ border-radius: 0;
+ background: transparent !important;
+
+ .el-menu-item,
+ .el-sub-menu__title {
+ margin-bottom: 8px;
+ border-radius: 14px;
+ color: v-bind(getMenuTextColor);
+ font-size: 14px;
+ letter-spacing: 0;
+ transition:
+ transform 0.18s ease,
+ background 0.2s ease,
+ box-shadow 0.2s ease,
+ color 0.2s ease;
+ border: none !important;
+ display: flex;
+ align-items: center;
+
+ &:hover {
+ background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.28), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.08)) !important;
+ transform: translate3d(2px, 0, 0);
+ }
}
- .el-menu {
- border: none;
- height: 100%;
- width: 100% !important;
- border-radius: 22px;
+ .el-menu-item {
+ color: var(--sidebar-text);
- .el-menu-item,
- .el-sub-menu__title {
- margin-bottom: 6px;
+ &.is-active {
+ background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important;
+ color: var(--menu-active-text) !important;
+ font-weight: 500;
border-radius: 14px;
- color: v-bind(getMenuTextColor);
+ box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34));
- &:hover {
- background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
- border-radius: 14px;
+ .svg-icon {
+ color: var(--menu-active-text) !important;
}
}
+ }
- .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);
+ }
- .el-sub-menu__title {
- color: v-bind(getMenuTextColor);
- }
+ :deep(.el-sub-menu__icon-arrow) {
+ display: inline-flex !important;
+ align-items: center;
+ justify-content: center;
+ width: 14px;
+ height: 14px;
+ margin-top: -7px;
+ right: 14px;
+ font-size: 14px !important;
+ color: currentColor !important;
+ opacity: 0.7;
+ transition: transform 0.2s ease, opacity 0.2s ease;
+ }
- :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-sub-menu.is-opened .el-sub-menu__icon-arrow) {
+ transform: rotate(180deg);
+ }
- :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) {
+ color: var(--menu-active-text) !important;
+ font-weight: 500;
+ border-radius: 12px;
+ margin: 0 12px 6px !important;
+ padding-left: 14px !important;
+ padding-right: 34px !important;
+ box-sizing: border-box;
+ overflow: hidden;
+ background-clip: padding-box;
+ background: var(--menu-active-bg) !important;
+ box-shadow: var(--menu-active-glow);
+ border: none !important;
+ }
- :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-menu-item.is-active) {
+ margin: 0 12px 6px !important;
+ width: calc(100% - 24px) !important;
+ padding-left: 14px !important;
+ padding-right: 34px !important;
+ box-sizing: border-box;
+ overflow: hidden;
+ background-clip: padding-box;
+ border-radius: 12px;
+ }
- :deep(.el-sub-menu__title:hover),
- :deep(.el-menu-item:hover) {
- 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) {
+ color: var(--menu-active-text) !important;
}
}
+}
</style>
diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue
index 9067108..71fa478 100644
--- a/src/layout/components/TagsView/ScrollPane.vue
+++ b/src/layout/components/TagsView/ScrollPane.vue
@@ -100,8 +100,8 @@
:deep(.el-scrollbar__bar) {
bottom: 0px;
}
- :deep(.el-scrollbar__wrap) {
- height: 42px;
- }
-}
-</style>
\ No newline at end of file
+ :deep(.el-scrollbar__wrap) {
+ height: var(--tagsbar-height);
+ }
+}
+</style>
diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue
index 8fe064a..66b6014 100644
--- a/src/layout/components/TagsView/index.vue
+++ b/src/layout/components/TagsView/index.vue
@@ -259,20 +259,20 @@
<style lang="scss" scoped>
.tags-view-container {
- height: 42px;
+ height: var(--tagsbar-height);
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);
+ margin-top: 0;
+ padding: 0 2px;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ backdrop-filter: none;
+ box-shadow: none;
.tags-view-wrapper {
display: flex;
align-items: center;
- min-height: 42px;
+ min-height: var(--tagsbar-height);
.tags-view-item {
display: inline-flex;
@@ -280,29 +280,29 @@
justify-content: center;
position: relative;
cursor: pointer;
- height: 34px;
+ height: 30px;
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;
+ padding: 0 14px;
font-size: 12px;
- margin-right: 8px;
+ margin-right: 6px;
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);
+ border-color: rgba(96, 165, 250, 0.36);
}
&.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;
+ background-image: linear-gradient(132deg, rgba(47, 128, 255, 0.95), rgba(56, 189, 248, 0.9));
+ color: #fff;
+ box-shadow: 0 10px 20px rgba(37, 99, 235, 0.22);
+ border-color: rgba(147, 197, 253, 0.72) !important;
}
}
}
diff --git a/src/layout/index.vue b/src/layout/index.vue
index 03c13ba..11949df 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -7,21 +7,22 @@
@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 }">
+ <div :class="{ hasTagsView: showTagsView, sidebarHide: sidebar.hide }"
+ class="main-container main-layout">
+ <div :class="{ 'fixed-header': fixedHeader, 'with-tags': showTagsView }">
<navbar @setLayout="setLayout" />
- <tags-view v-if="needTagsView" />
+ <tags-view v-if="showTagsView" />
</div>
<app-main />
<settings ref="settingRef" />
</div>
- <AIChatSidebar v-if="aiEnabled" />
+ <AIChatSidebar v-if="showGlobalAiChat" />
</div>
</template>
<script setup>
import { useWindowSize } from "@vueuse/core";
+ import { useRoute } from "vue-router";
import Sidebar from "./components/Sidebar/index.vue";
import { AppMain, Navbar, Settings, TagsView } from "./components";
import AIChatSidebar from "@/components/AIChatSidebar/index.vue";
@@ -30,16 +31,24 @@
import useAppStore from "@/store/modules/app";
import useUserStore from "@/store/modules/user";
import useSettingsStore from "@/store/modules/settings";
+ import useTagsViewStore from "@/store/modules/tagsView";
const settingsStore = useSettingsStore();
+ const tagsViewStore = useTagsViewStore();
const userStore = useUserStore();
+ const route = useRoute();
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 showTagsView = computed(() => needTagsView.value && tagsViewStore.visitedViews.length > 1);
const fixedHeader = computed(() => settingsStore.fixedHeader);
const aiEnabled = computed(() => Number(userStore.aiEnabled) === 1);
+ const showGlobalAiChat = computed(() => {
+ const isIndustrialBrainRoute = String(route.path || "").startsWith("/ai-industrial-brain");
+ return !isIndustrialBrainRoute && aiEnabled.value;
+ });
const classObj = computed(() => ({
hideSidebar: !sidebar.value.opened,
@@ -81,19 +90,16 @@
<style lang="scss" scoped>
@import "@/assets/styles/mixin.scss";
- @import "@/assets/styles/variables.module.scss";
.app-wrapper {
@include clearfix;
position: relative;
- height: 100%;
+ min-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%);
+ background:
+ radial-gradient(circle at 14% -8%, rgba(59, 130, 246, 0.14), transparent 36%),
+ radial-gradient(circle at 88% -12%, rgba(56, 189, 248, 0.1), transparent 30%),
+ linear-gradient(165deg, #f3f7fc 0%, #eef5ff 56%, #f8fbff 100%);
&.mobile.openSidebar {
position: fixed;
@@ -102,8 +108,7 @@
}
.drawer-bg {
- background: #000;
- opacity: 0.3;
+ background: rgba(15, 23, 42, 0.22);
width: 100%;
top: 0;
height: 100%;
@@ -111,26 +116,46 @@
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);
+ .main-layout {
+ min-height: 100vh;
+ margin-left: var(--sidebar-width);
+ transition: margin-left 0.25s ease;
+ display: flex;
+ flex-direction: column;
}
- .sidebarHide .fixed-header {
- width: calc(100% - 32px);
+ .fixed-header {
+ position: sticky;
+ top: 0;
+ z-index: var(--layout-header-z);
+ width: 100%;
+ padding: 8px var(--content-gap) 0;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ background: transparent;
+ backdrop-filter: none;
+ }
+
+ .fixed-header.with-tags {
+ padding-bottom: 6px;
+ }
+
+ .hideSidebar .fixed-header {
+ width: 100%;
+ }
+
+ .hideSidebar .main-layout {
+ margin-left: var(--sidebar-collapsed-width);
}
.mobile .fixed-header {
width: 100%;
+ padding: 8px 10px 0;
+ }
+
+ .mobile .main-layout,
+ .sidebarHide.main-layout {
+ margin-left: 0;
}
</style>
diff --git a/src/plugins/download.js b/src/plugins/download.js
index 2705310..35ac92f 100644
--- a/src/plugins/download.js
+++ b/src/plugins/download.js
@@ -82,6 +82,16 @@
saveAs(text, name, opts) {
saveAs(text, name, opts);
},
+ byUrl(url, filename) {
+ // 灏哢RL涓殑preview鏇挎崲鎴恉ownload
+ const downloadUrl = url.replace(/preview/g, 'download')
+ const link = document.createElement('a')
+ link.href = downloadUrl
+ link.download = filename || ''
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ },
async printErrMsg(data) {
const resText = await data.text();
const rspObj = JSON.parse(resText);
diff --git a/src/router/index.js b/src/router/index.js
index 2bb5586..7be3668 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -47,20 +47,6 @@
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"),
@@ -85,6 +71,32 @@
},
],
},
+ // 绯荤粺鏋舵瀯鍥�
+ {
+ 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: "/ai-industrial-brain",
+ component: Layout,
+ children: [
+ {
+ path: "index",
+ component: () => import("@/views/aiIndustrialBrain/index.vue"),
+ name: "AiIndustrialBrain",
+ meta: { title: "AI宸ヤ笟澶ц剳", icon: "skill" },
+ },
+ ],
+ },
{
path: "/user",
component: Layout,
@@ -105,20 +117,6 @@
hidden: true,
name: "DeviceInfo",
meta: { title: "璁惧淇℃伅", icon: "monitor" },
- },
- // 娣诲姞椤圭洰璇︽儏椤甸潰璺敱閰嶇疆
- {
- path: "/oaSystem/projectManagement/projectDetail",
- component: Layout,
- hidden: true,
- children: [
- {
- path: ":projectId",
- component: () => import("@/views/oaSystem/projectManagement/projectDetail.vue"),
- name: "ProjectDetail",
- meta: { title: "椤圭洰璇︽儏", activeMenu: "/oaSystem/projectManagement" },
- },
- ],
},
{
path: "/projectManagement/Management/detail",
@@ -143,12 +141,6 @@
// 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",
@@ -160,12 +152,7 @@
// 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"),
@@ -185,16 +172,22 @@
// meta: { title: "鏀舵鍗�" },
// },
// {
+ // path: "receivable-reconciliation",
+ // component: () => import("@/views/financialManagement/receivable/reconciliation.vue"),
+ // name: "ReceivableReconciliation",
+ // 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: "purchase-return",
+ // component: () => import("@/views/financialManagement/payable/purchaseReturn.vue"),
+ // name: "PurchaseReturn",
+ // meta: { title: "閲囪喘閫�璐�" },
// },
// {
// path: "input-invoice",
@@ -208,11 +201,18 @@
// name: "PaymentApply",
// meta: { title: "浠樻鐢宠" },
// },
+ //
// {
// path: "payment",
// component: () => import("@/views/financialManagement/payable/payment.vue"),
// name: "Payment",
// meta: { title: "浠樻鍗�" },
+ // },
+ // {
+ // path: "payable-reconciliation",
+ // component: () => import("@/views/financialManagement/payable/reconciliation.vue"),
+ // name: "PayableReconciliation",
+ // meta: { title: "搴斾粯瀵硅处" },
// },
// {
// path: "fixed-assets",
@@ -227,6 +227,12 @@
// meta: { title: "鏃犲舰璧勪骇" },
// },
// {
+ // path: "general-ledger",
+ // component: () => import("@/views/financialManagement/generalLedger/index.vue"),
+ // name: "GeneralLedger",
+ // meta: { title: "鎬诲笎绉戠洰" },
+ // },
+ // {
// path: "voucher",
// component: () => import("@/views/financialManagement/voucher/index.vue"),
// name: "Voucher",
diff --git a/src/settings.js b/src/settings.js
index 4a1c8bb..276b2f6 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -6,7 +6,7 @@
/**
* 渚ц竟鏍忎富棰� 娣辫壊涓婚theme-dark锛屾祬鑹蹭富棰榯heme-light
*/
- sideTheme: 'theme-light',
+ sideTheme: 'theme-dark',
/**
* 鏄惁绯荤粺甯冨眬閰嶇疆
*/
@@ -39,7 +39,7 @@
/**
* 涓婚妯″紡 auto璺熼殢绯荤粺锛宭ight娴呰壊锛宒ark娣辫壊
*/
- darkMode: "auto",
+ darkMode: "light",
/**
* @type {string | array} 'production' | ['production', 'development']
diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js
index d3f0594..0b11d2e 100644
--- a/src/store/modules/permission.js
+++ b/src/store/modules/permission.js
@@ -44,17 +44,18 @@
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)
- resolve(rewriteRoutes)
- })
- })
+ const defaultRoutes = filterAsyncRouter(defaultData)
+ const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
+ asyncRoutes.forEach(route => { router.addRoute(route) })
+ this.setRoutes(rewriteRoutes)
+ const constantSidebarRoutes = filterAiFeatureRoutes(constantRoutes, aiEnabled)
+ // 灏嗚储鍔$鐞嗚矾鐢卞悎骞跺埌渚ц竟鏍�
+ this.setSidebarRouters(constantSidebarRoutes.concat(sidebarRoutes))
+ this.setDefaultRoutes(sidebarRoutes)
+ this.setTopbarRoutes(defaultRoutes)
+ resolve(rewriteRoutes)
+ })
+ })
}
}
})
@@ -118,7 +119,7 @@
})
}
-function filterChildren(childrenMap, lastRouter = false) {
+function filterChildren(childrenMap, lastRouter = false) {
var children = []
childrenMap.forEach(el => {
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path
@@ -128,11 +129,11 @@
children.push(el)
}
})
- return children
-}
-
-// 鍔ㄦ�佽矾鐢遍亶鍘嗭紝楠岃瘉鏄惁鍏峰鏉冮檺
-export function filterDynamicRoutes(routes) {
+ return children
+}
+
+// 鍔ㄦ�佽矾鐢遍亶鍘嗭紝楠岃瘉鏄惁鍏峰鏉冮檺
+export function filterDynamicRoutes(routes) {
const res = []
routes.forEach(route => {
if (route.permissions) {
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index ea358d1..ac97976 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -7,14 +7,14 @@
const useUserStore = defineStore(
'user',
{
- state: () => ({
- token: getToken(),
- id: '',
- name: '',
- avatar: '',
- roles: [],
- permissions: [],
- aiEnabled: 0
+ state: () => ({
+ token: getToken(),
+ id: '',
+ name: '',
+ avatar: '',
+ roles: [],
+ permissions: [],
+ aiEnabled: 0
}),
actions: {
// 鐧诲綍
@@ -25,8 +25,13 @@
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
- setToken(res.token)
- this.token = res.token
+ const token = res?.token || res?.data?.token
+ if (!token) {
+ reject(new Error('鏈幏鍙栧埌鐧诲綍浠ょ墝'))
+ return
+ }
+ setToken(token)
+ this.token = token
resolve()
}).catch(error => {
reject(error)
@@ -47,7 +52,8 @@
getInfo() {
return new Promise((resolve, reject) => {
getInfo().then(res => {
- const user = res.user
+ res = res.data
+ const user = res.user || {}
let avatar = user.avatar || ""
avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
if (res.roles && res.roles.length > 0) { // 楠岃瘉杩斿洖鐨剅oles鏄惁鏄竴涓潪绌烘暟缁�
@@ -56,34 +62,34 @@
} else {
this.roles = ['ROLE_DEFAULT']
}
- this.id = user.userId
- this.name = user.userName
+ 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()
- this.aiEnabled = Number(res.aiEnabled) === 1 ? 1 : 0
- resolve(res)
- }).catch(error => {
- reject(error)
- })
- })
+ this.currentFactoryName = user.currentFactoryName || ''
+ this.nickName = user.nickName || ''
+ this.roleName = Array.isArray(user.roles) && user.roles.length > 0 ? (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 = []
- this.aiEnabled = 0
- removeToken()
- resolve()
- }).catch(error => {
- reject(error)
- })
+ this.token = ''
+ this.roles = []
+ this.permissions = []
+ this.aiEnabled = 0
+ removeToken()
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
})
},
// 鐧诲綍鏍¢獙
@@ -104,8 +110,13 @@
const password = userInfo.password
return new Promise((resolve, reject) => {
loginCheckFactory(username, password).then(res => {
- setToken(res.token)
- this.token = res.token
+ const token = res?.token || res?.data?.token
+ if (!token) {
+ reject(new Error('鏈幏鍙栧埌鐧诲綍浠ょ墝'))
+ return
+ }
+ setToken(token)
+ this.token = token
resolve()
}).catch(error => {
reject(error)
@@ -116,10 +127,15 @@
return new Promise((resolve, reject) => {
tideLogin(code)
.then((res) => {
- setToken(res.token);
- this.token = res.token
+ const token = res?.token || res?.data?.token
+ if (!token) {
+ reject(new Error('鏈幏鍙栧埌鐧诲綍浠ょ墝'))
+ return
+ }
+ setToken(token);
+ this.token = token
Vue.prototype.uploadHeader = {
- Authorization: "Bearer " + res.token,
+ Authorization: "Bearer " + token,
};
resolve();
})
diff --git a/src/utils/request.js b/src/utils/request.js
index 9cfcf5b..672bed8 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -21,11 +21,12 @@
})
// request鎷︽埅鍣�
-service.interceptors.request.use(config => {
+service.interceptors.request.use(config => {
+ config.headers = config.headers || {}
// 鏄惁闇�瑕佽缃� token
- const isToken = (config.headers || {}).isToken === false
+ const isToken = config.headers.isToken === false
// 鏄惁闇�瑕侀槻姝㈡暟鎹噸澶嶆彁浜�
- const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
+ const isRepeatSubmit = config.headers.repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 璁╂瘡涓姹傛惡甯﹁嚜瀹氫箟token 璇锋牴鎹疄闄呮儏鍐佃嚜琛屼慨鏀�
}
@@ -66,23 +67,27 @@
}
}
return config
-}, error => {
- console.log(error)
- Promise.reject(error)
-})
+}, error => {
+ console.log(error)
+ return Promise.reject(error)
+})
// 鍝嶅簲鎷︽埅鍣�
service.interceptors.response.use(res => {
// 鏈缃姸鎬佺爜鍒欓粯璁ゆ垚鍔熺姸鎬�
- const code = res.data.code || 200
+ const code = res.data.code || 200
+ const handleAuthError = (res.config && res.config.headers && res.config.headers.handleAuthError) !== false
// 鑾峰彇閿欒淇℃伅
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 浜岃繘鍒舵暟鎹垯鐩存帴杩斿洖
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
- if (code === 401) {
- if (!isRelogin.show) {
+ if (code === 401) {
+ if (!handleAuthError) {
+ return Promise.reject(new Error(msg))
+ }
+ if (!isRelogin.show) {
isRelogin.show = true
ElMessageBox.confirm('鐧诲綍鐘舵�佸凡杩囨湡锛屾偍鍙互缁х画鐣欏湪璇ラ〉闈紝鎴栬�呴噸鏂扮櫥褰�', '绯荤粺鎻愮ず', { confirmButtonText: '閲嶆柊鐧诲綍', cancelButtonText: '鍙栨秷', type: 'warning' }).then(() => {
isRelogin.show = false
diff --git a/src/utils/theme.js b/src/utils/theme.js
index f4badc6..90a511e 100644
--- a/src/utils/theme.js
+++ b/src/utils/theme.js
@@ -1,29 +1,44 @@
// 澶勭悊涓婚鏍峰紡
export function handleThemeStyle(theme) {
- document.documentElement.style.setProperty('--el-color-primary', theme)
+ const primary = normalizeHex(theme)
+ const [r, g, b] = hexToRgb(primary)
+ const light2 = getLightColor(primary, 0.2)
+ const light3 = getLightColor(primary, 0.3)
+ const light5 = getLightColor(primary, 0.5)
+
+ document.documentElement.style.setProperty('--el-color-primary', primary)
+ document.documentElement.style.setProperty('--el-color-primary-rgb', `${r}, ${g}, ${b}`)
for (let i = 1; i <= 9; i++) {
- document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`)
+ document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(primary, i / 10)}`)
}
for (let i = 1; i <= 9; i++) {
- document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`)
+ document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(primary, i / 10)}`)
}
+
+ // 绯荤粺涓婚鑱斿姩鍒颁晶杈规爮閫変腑鎬佷笌楂樹寒
+ document.documentElement.style.setProperty('--menu-active-bg', `linear-gradient(135deg, ${primary} 0%, ${light3} 100%)`)
+ document.documentElement.style.setProperty('--menu-active-glow', `0 10px 24px rgba(${r}, ${g}, ${b}, 0.32)`)
+ document.documentElement.style.setProperty('--menu-hover', `rgba(${r}, ${g}, ${b}, 0.2)`)
+ document.documentElement.style.setProperty('--accent-primary', primary)
+ document.documentElement.style.setProperty('--accent-light', light2)
+ document.documentElement.style.setProperty('--accent-lighter', light5)
}
// hex棰滆壊杞瑀gb棰滆壊
export function hexToRgb(str) {
- str = str.replace('#', '')
- let hexs = str.match(/../g)
+ str = normalizeHex(str).replace('#', '')
+ const hexs = str.match(/../g) || ['40', '9e', 'ff']
for (let i = 0; i < 3; i++) {
hexs[i] = parseInt(hexs[i], 16)
}
return hexs
}
-// rgb棰滆壊杞琀ex棰滆壊
+// rgb棰滆壊杞琱ex棰滆壊
export function rgbToHex(r, g, b) {
- let hexs = [r.toString(16), g.toString(16), b.toString(16)]
+ const hexs = [r.toString(16), g.toString(16), b.toString(16)]
for (let i = 0; i < 3; i++) {
- if (hexs[i].length == 1) {
+ if (hexs[i].length === 1) {
hexs[i] = `0${hexs[i]}`
}
}
@@ -32,7 +47,7 @@
// 鍙樻祬棰滆壊鍊�
export function getLightColor(color, level) {
- let rgb = hexToRgb(color)
+ const rgb = hexToRgb(color)
for (let i = 0; i < 3; i++) {
rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i])
}
@@ -41,9 +56,19 @@
// 鍙樻繁棰滆壊鍊�
export function getDarkColor(color, level) {
- let rgb = hexToRgb(color)
+ const rgb = hexToRgb(color)
for (let i = 0; i < 3; i++) {
rgb[i] = Math.floor(rgb[i] * (1 - level))
}
return rgbToHex(rgb[0], rgb[1], rgb[2])
}
+
+function normalizeHex(color) {
+ if (!color || typeof color !== 'string') return '#409eff'
+ let value = color.trim().replace('#', '')
+ if (value.length === 3) {
+ value = value.split('').map((s) => s + s).join('')
+ }
+ if (!/^[0-9a-fA-F]{6}$/.test(value)) return '#409eff'
+ return `#${value.toLowerCase()}`
+}
diff --git a/src/views/aiIndustrialBrain/MAINTAIN_RULES.md b/src/views/aiIndustrialBrain/MAINTAIN_RULES.md
new file mode 100644
index 0000000..97b9a5c
--- /dev/null
+++ b/src/views/aiIndustrialBrain/MAINTAIN_RULES.md
@@ -0,0 +1,7 @@
+# AI宸ヤ笟澶ц剳缁存姢瑙勫垯
+
+1. 褰� `src/views/aiIndustrialBrain/index.vue` 鏂板鏅鸿兘浣擄紙`agents`锛夐�昏緫鏃讹紝蹇呴』鍚屾纭寮圭獥鍔╂墜鍙敤鎬с��
+2. 寮圭獥鍔╂墜鐢� `src/components/AIChatSidebar/assistants/index.js` 鐨� `assistantRegistry` 缁熶竴娉ㄥ唽銆�
+3. 鏂板鏅鸿兘浣撶殑 `key` 鑻ヨ鍦ㄥ脊绐椾腑鍙敤锛屽繀椤诲湪 `assistantRegistry` 涓彁渚涘悓鍚嶉厤缃��
+4. 鏈湪 `assistantRegistry` 娉ㄥ唽鐨勬櫤鑳戒綋浼氬湪寮圭獥涓樉绀轰负 `pending`锛堝紑鍙戜腑锛夋�併��
+
diff --git a/src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue b/src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue
new file mode 100644
index 0000000..1eb5e7b
--- /dev/null
+++ b/src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue
@@ -0,0 +1,198 @@
+<template>
+ <transition name="fade">
+ <section v-if="visible" class="assistant-workspace">
+ <div class="assistant-workspace__panel">
+ <button
+ v-if="assistantMode === 'pending'"
+ type="button"
+ class="workspace-back-btn"
+ @click="$emit('close')"
+ >
+ <el-icon><ArrowLeftBold /></el-icon>
+ <span>杩斿洖宸ヤ笟澶у睆</span>
+ </button>
+
+ <div class="assistant-workspace__body">
+ <AIChatSidebar
+ v-if="assistantMode !== 'pending'"
+ :key="assistantMode"
+ class="workspace-chat"
+ :assistants="resolvedAssistants"
+ :default-assistant="assistantMode"
+ :hide-trigger="true"
+ :auto-open="true"
+ drawer-size="100%"
+ drawer-direction="ttb"
+ header-extra-action-text="杩斿洖宸ヤ笟澶у睆"
+ @header-extra-action="$emit('close')"
+ />
+
+ <div v-else class="workspace-pending">
+ <div class="workspace-pending__content">
+ <h3>{{ agentTitle }}</h3>
+ <p>姝e湪寮�鍙戯紝鏁鏈熷緟......</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+ </transition>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { ArrowLeftBold } from "@element-plus/icons-vue";
+import AIChatSidebar from "@/components/AIChatSidebar/index.vue";
+import { assistantRegistry } from "@/components/AIChatSidebar/assistants";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ default: false,
+ },
+ agent: {
+ type: Object,
+ default: () => ({}),
+ },
+});
+
+defineEmits(["close"]);
+
+const agentKey = computed(() => String(props.agent?.key || ""));
+const agentTitle = computed(() => String(props.agent?.name || "AI鍔╂墜"));
+
+/**
+ * 缁存姢瑙勫垯锛�
+ * AI宸ヤ笟澶ц剳鏂板鏅鸿兘浣撴椂锛岃嫢甯屾湜鍙充晶寮圭獥鍙敤锛岄渶淇濊瘉鏅鸿兘浣� key 鍦� assistantRegistry 涓湁鍚屽悕閰嶇疆銆�
+ * 鏈厤缃椂浼氳繘鍏� pending锛堝紑鍙戜腑锛夋�侊紝浣滀负鏄惧紡鎻愰啋銆�
+ */
+const resolvedAssistant = computed(() => assistantRegistry[agentKey.value] || null);
+const assistantMode = computed(() => {
+ return resolvedAssistant.value ? agentKey.value : "pending";
+});
+const resolvedAssistants = computed(() => (resolvedAssistant.value ? [resolvedAssistant.value] : []));
+</script>
+
+<style scoped>
+.assistant-workspace {
+ position: fixed;
+ inset: 0;
+ z-index: 2100;
+ padding: 12px;
+ background: rgba(33, 49, 63, 0.24);
+ backdrop-filter: blur(2px);
+}
+
+.assistant-workspace__panel {
+ position: relative;
+ height: 100%;
+ border-radius: 22px;
+ border: 1px solid var(--surface-border);
+ background: linear-gradient(180deg, #f9fcfb 0%, #f0f5f2 100%);
+ box-shadow: var(--shadow-md);
+ overflow: hidden;
+}
+
+.assistant-workspace__body {
+ height: 100%;
+ min-height: 100%;
+}
+
+.workspace-back-btn {
+ position: absolute;
+ top: 16px;
+ right: 20px;
+ z-index: 5;
+ height: 36px;
+ padding: 0 14px;
+ border: 1px solid rgba(38, 112, 183, 0.3);
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.92);
+ color: #25528f;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.workspace-back-btn:hover {
+ border-color: rgba(31, 122, 114, 0.45);
+ color: #1f5ddf;
+ box-shadow: 0 8px 16px rgba(31, 122, 114, 0.14);
+ transform: translateY(-1px);
+}
+
+.workspace-chat {
+ width: 100%;
+ height: 100%;
+}
+
+.workspace-chat :deep(.ai-chat-sidebar-wrapper) {
+ height: 100%;
+}
+
+.workspace-chat :deep(.ai-chat-drawer) {
+ height: 100%;
+}
+
+.workspace-chat :deep(.el-drawer) {
+ height: 100% !important;
+ width: 100% !important;
+}
+
+.workspace-pending {
+ height: 100%;
+ display: grid;
+ place-items: center;
+ padding: 20px;
+ color: var(--text-secondary);
+}
+
+.workspace-pending__content {
+ display: grid;
+ gap: 12px;
+ text-align: center;
+}
+
+.workspace-pending__content h3 {
+ margin: 0;
+ font-size: 36px;
+ color: var(--text-primary);
+}
+
+.workspace-pending__content p {
+ margin: 0;
+ font-size: 24px;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.2s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+ opacity: 0;
+}
+
+@media (max-width: 1600px) {
+ .workspace-back-btn {
+ top: 12px;
+ right: 14px;
+ height: 32px;
+ padding: 0 12px;
+ font-size: 13px;
+ }
+
+ .workspace-pending__content h3 {
+ font-size: 30px;
+ }
+
+ .workspace-pending__content p {
+ font-size: 20px;
+ }
+}
+</style>
diff --git a/src/views/aiIndustrialBrain/index.vue b/src/views/aiIndustrialBrain/index.vue
new file mode 100644
index 0000000..4afafa5
--- /dev/null
+++ b/src/views/aiIndustrialBrain/index.vue
@@ -0,0 +1,1500 @@
+<template>
+ <div ref="screenRef" class="ai-brain-screen">
+ <section class="brain-stage">
+ <header class="brain-head">
+ <div class="head-date">
+ <p>{{ weekLabel }}</p>
+ <p>{{ dateLabel }}</p>
+ </div>
+
+ <div class="head-title">
+ <span>AI宸ヤ笟澶ц剳</span>
+ </div>
+
+ <div class="head-actions">
+ <button type="button" class="head-back-btn" @click="goBack">
+ <el-icon><ArrowLeftBold /></el-icon>
+ <span>杩斿洖</span>
+ </button>
+ </div>
+ </header>
+
+ <section class="brain-intro">
+ <h2>宸ヤ笟AI鏁板瓧鍛樺伐锛岃祴鑳芥櫤閫犳柊绾厓</h2>
+ <p>鍏ぇAI鍔╂墜鍗忓悓浼佷笟绠$悊銆侀攢鍞�侀噰璐�佺敓浜с�佽储鍔″強鏁版嵁鍏ㄩ摼璺�</p>
+ <div class="intro-sign">闃块噷浜� 脳 鍗冮棶澶фā鍨� 脳 鏅鸿兘浣揂I</div>
+ </section>
+
+ <section class="carousel-area">
+ <button type="button" class="nav-btn nav-btn--left" @click="prevCard">
+ <el-icon><ArrowLeftBold /></el-icon>
+ </button>
+
+ <div class="carousel-track">
+ <article
+ v-for="card in visibleCards"
+ :key="card.agent.key"
+ class="agent-card"
+ :class="{ 'agent-card--active': card.offset === 0 }"
+ :style="getCardStyle(card.offset)"
+ @click="openAssistant(card.realIndex)"
+ >
+ <div class="agent-card__head" :class="{ 'agent-card__head--active': card.offset === 0 }">
+ {{ card.agent.name }}
+ </div>
+
+ <div class="agent-card__body" :class="{ 'agent-card__body--active': card.offset === 0 }">
+ <div class="avatar-shell" :class="{ 'avatar-shell--active': card.offset === 0 }">
+ <div class="avatar-base"></div>
+ <div class="avatar-cut">
+ <img v-if="card.agent.avatar" class="avatar-cut__img" :src="card.agent.avatar" :alt="card.agent.name" />
+ </div>
+ </div>
+ <div v-if="card.offset === 0" class="highlight-list">
+ <div
+ v-for="highlight in card.agent.highlights"
+ :key="highlight"
+ class="highlight-item"
+ >
+ {{ highlight }}
+ </div>
+ </div>
+ </div>
+ </article>
+ </div>
+
+ <button type="button" class="nav-btn nav-btn--right" @click="nextCard">
+ <el-icon><ArrowRightBold /></el-icon>
+ </button>
+ </section>
+
+ <section class="brain-footer">
+ <div class="footer-grid-overlay"></div>
+
+ <div class="footer-metrics">
+ <article class="footer-metric">
+ <span class="footer-metric__label">鍦ㄧ嚎鏅鸿兘浣�</span>
+ <strong class="footer-metric__value">{{ agents.length }}涓�</strong>
+ <small class="footer-metric__hint">鍏ㄩ摼璺崗鍚岃繍琛�</small>
+ </article>
+
+ <article class="footer-metric footer-metric--focus">
+ <span class="footer-metric__label">褰撳墠鐒︾偣</span>
+ <strong class="footer-metric__value">{{ getFooterAgentName(focusAgent.name) }}</strong>
+ <small class="footer-metric__hint">{{ focusAgent.highlights?.[0] || "鏅鸿兘鍒嗘瀽鑱斿姩" }}</small>
+ </article>
+
+ <article class="footer-metric footer-metric--period">
+ <span class="footer-metric__label">杞挱鍛ㄦ湡</span>
+ <strong class="footer-metric__value">{{ carouselSecondsText }}</strong>
+ <div class="footer-period-control">
+ <button type="button" class="period-btn" @click="adjustCarouselSeconds(-0.5)">-</button>
+ <input
+ v-model.number="carouselSeconds"
+ class="period-input"
+ type="number"
+ min="2"
+ max="12"
+ step="0.5"
+ />
+ <span class="period-unit">s</span>
+ <button type="button" class="period-btn" @click="adjustCarouselSeconds(0.5)">+</button>
+ </div>
+ <input
+ v-model.number="carouselSeconds"
+ class="footer-period-slider"
+ type="range"
+ min="2"
+ max="12"
+ step="0.5"
+ />
+ <small class="footer-metric__hint">鍙墜鍔ㄨ缃� 2.0s - 12.0s</small>
+ </article>
+ </div>
+
+ <div class="footer-rail">
+ <div class="footer-rail__line">
+ <span class="footer-rail__flow"></span>
+ </div>
+ <div class="footer-rail__nodes">
+ <button
+ v-for="node in footerNodes"
+ :key="node.key"
+ type="button"
+ class="footer-node"
+ :class="{ 'footer-node--active': node.index === carouselIndex }"
+ @click="openAssistant(node.index)"
+ >
+ <span class="footer-node__dot"></span>
+ <span class="footer-node__name">{{ getFooterAgentName(node.name) }}</span>
+ </button>
+ </div>
+ </div>
+ </section>
+ </section>
+
+ <AiAssistantWorkspace
+ :visible="fullscreenVisible"
+ :agent="currentAgent"
+ @close="closeFullscreen"
+ />
+ </div>
+</template>
+
+<script setup>
+import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
+import { useRouter } from "vue-router";
+import { ArrowLeftBold, ArrowRightBold } from "@element-plus/icons-vue";
+import AiAssistantWorkspace from "./components/AiAssistantWorkspace.vue";
+import todoAvatar from "@/assets/AI/寰呭姙鍔╂墜.png";
+import salesAvatar from "@/assets/AI/閿�鍞姪鎵�.png";
+import purchaseAvatar from "@/assets/AI/閲囪喘鍔╂墜.png";
+import productionAvatar from "@/assets/AI/鐢熶骇鍔╂墜.png";
+import financeAvatar from "@/assets/AI/璐㈠姟鍔╂墜.png";
+
+const router = useRouter();
+
+// 缁存姢绾﹀畾瑙侊細src/views/aiIndustrialBrain/MAINTAIN_RULES.md
+const agents = [
+ {
+ key: "general",
+ name: "AI寰呭姙鍔╂墜",
+ highlights: ["璺ㄦā鍧楁祦绋嬭瘖鏂�", "缁忚惀椋庨櫓鏅鸿兘鎻愰啋"],
+ },
+ {
+ key: "sales",
+ name: "AI閿�鍞姪鎵�",
+ highlights: ["瀹㈡埛娴佸け椋庨櫓鍒嗘瀽", "鍥炴涓庢姤浠风瓥鐣ュ缓璁�"],
+ },
+ {
+ key: "purchase",
+ name: "AI閲囪喘鍔╂墜",
+ highlights: ["渚涘簲閾炬寚鏍囧垎鏋�", "閲囪喘璁㈠崟鏅鸿兘鐢熸垚"],
+ },
+ {
+ key: "production",
+ name: "AI鐢熶骇鍔╂墜",
+ highlights: ["宸ュ簭鐡堕瀹氫綅", "浜ц兘涓庢姤搴熸櫤鑳介璀�"],
+ },
+ {
+ key: "finance",
+ name: "AI璐㈠姟鍔╂墜",
+ highlights: ["鐜伴噾娴佸帇鍔涢鍒�", "璐圭敤缁撴瀯鏅鸿兘鍒嗘瀽"],
+ },
+];
+
+const avatarByAgentKey = {
+ general: todoAvatar,
+ sales: salesAvatar,
+ purchase: purchaseAvatar,
+ production: productionAvatar,
+ finance: financeAvatar,
+};
+
+for (let i = agents.length - 1; i >= 0; i -= 1) {
+ const agent = agents[i];
+ const avatar = avatarByAgentKey[agent.key];
+ if (!avatar) {
+ agents.splice(i, 1);
+ continue;
+ }
+ agent.avatar = avatar;
+}
+
+const carouselIndex = ref(Math.min(2, Math.max(agents.length - 1, 0)));
+const fullscreenVisible = ref(false);
+const screenRef = ref(null);
+const carouselIntervalMs = ref(4500);
+
+let carouselTimer = null;
+
+const fallbackAgent = {
+ key: "fallback",
+ name: "AI鍔╂墜",
+ avatar: "",
+ highlights: [],
+};
+
+const currentAgent = computed(() => agents[carouselIndex.value] || agents[0] || fallbackAgent);
+const focusAgent = computed(() => currentAgent.value || fallbackAgent);
+const footerNodes = computed(() =>
+ agents.map((agent, index) => ({
+ key: agent.key,
+ name: agent.name,
+ index,
+ }))
+);
+const carouselSeconds = computed({
+ get: () => Number((carouselIntervalMs.value / 1000).toFixed(1)),
+ set: (value) => {
+ const next = Number(value);
+ if (!Number.isFinite(next)) return;
+ const clamped = Math.max(2, Math.min(12, Math.round(next * 2) / 2));
+ carouselIntervalMs.value = Math.round(clamped * 1000);
+ },
+});
+const carouselSecondsText = computed(() => `${carouselSeconds.value.toFixed(1)}s`);
+
+const weekLabel = computed(() => {
+ const weekMap = ["鏄熸湡鏃�", "鏄熸湡涓�", "鏄熸湡浜�", "鏄熸湡涓�", "鏄熸湡鍥�", "鏄熸湡浜�", "鏄熸湡鍏�"];
+ return weekMap[new Date().getDay()];
+});
+
+const dateLabel = computed(() => {
+ 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");
+ return `${year}骞�${month}鏈�${day}鏃;
+});
+
+const visibleCards = computed(() => {
+ const total = agents.length;
+ return agents
+ .map((agent, index) => {
+ let offset = index - carouselIndex.value;
+ if (offset > total / 2) offset -= total;
+ if (offset < -total / 2) offset += total;
+ return { agent, offset, realIndex: index };
+ })
+ .filter((item) => Math.abs(item.offset) <= 2)
+ .sort((a, b) => a.offset - b.offset);
+});
+
+function getCardStyle(offset) {
+ const distance = Math.abs(offset);
+ const scale = distance === 0 ? 1 : distance === 1 ? 0.88 : 0.78;
+ const opacity = distance === 0 ? 1 : distance === 1 ? 0.92 : 0.76;
+ return {
+ transform: `translateX(${offset * 340}px) scale(${scale})`,
+ zIndex: String(50 - distance),
+ opacity,
+ };
+}
+
+function getFooterAgentName(name) {
+ return String(name || "AI鍔╂墜").replace(/^AI/, "");
+}
+
+function adjustCarouselSeconds(delta) {
+ carouselSeconds.value = carouselSeconds.value + delta;
+}
+
+function prevCard() {
+ const total = agents.length;
+ if (!total) return;
+ carouselIndex.value = (carouselIndex.value - 1 + total) % total;
+}
+
+function nextCard() {
+ const total = agents.length;
+ if (!total) return;
+ carouselIndex.value = (carouselIndex.value + 1) % total;
+}
+
+async function enterBrowserFullscreen() {
+ if (document.fullscreenElement) return;
+ const target = screenRef.value || document.documentElement;
+ if (!target || typeof target.requestFullscreen !== "function") return;
+ try {
+ await target.requestFullscreen();
+ } catch (error) {
+ // Ignore: browser may block fullscreen when there is no direct user activation.
+ }
+}
+
+async function exitBrowserFullscreen() {
+ if (!document.fullscreenElement || typeof document.exitFullscreen !== "function") return;
+ try {
+ await document.exitFullscreen();
+ } catch (error) {
+ // Ignore fullscreen exit failures.
+ }
+}
+
+function goBack() {
+ closeFullscreen();
+ exitBrowserFullscreen();
+ if (window.history.length > 1) {
+ router.back();
+ return;
+ }
+ router.push("/index");
+}
+
+function openAssistant(index) {
+ if (!agents.length) return;
+ carouselIndex.value = index;
+ fullscreenVisible.value = true;
+}
+
+function closeFullscreen() {
+ fullscreenVisible.value = false;
+}
+
+function startCarousel() {
+ stopCarousel();
+ if (fullscreenVisible.value) return;
+ carouselTimer = window.setInterval(() => {
+ nextCard();
+ }, carouselIntervalMs.value);
+}
+
+function stopCarousel() {
+ if (carouselTimer) {
+ window.clearInterval(carouselTimer);
+ carouselTimer = null;
+ }
+}
+
+function handleEscClose(event) {
+ if (event.key === "Escape" && fullscreenVisible.value) {
+ closeFullscreen();
+ }
+}
+
+watch(
+ () => fullscreenVisible.value,
+ (opened) => {
+ if (opened) {
+ stopCarousel();
+ } else {
+ startCarousel();
+ }
+ }
+);
+
+watch(
+ () => carouselIntervalMs.value,
+ () => {
+ if (!fullscreenVisible.value) {
+ startCarousel();
+ }
+ }
+);
+
+onMounted(() => {
+ startCarousel();
+ window.addEventListener("keydown", handleEscClose);
+ window.requestAnimationFrame(() => {
+ enterBrowserFullscreen();
+ });
+});
+
+onBeforeUnmount(() => {
+ stopCarousel();
+ window.removeEventListener("keydown", handleEscClose);
+ exitBrowserFullscreen();
+});
+</script>
+
+<style scoped>
+.ai-brain-screen {
+ position: fixed;
+ inset: 0;
+ z-index: 1900;
+ padding: 10px;
+ overflow: hidden;
+ background: var(--app-bg);
+}
+
+.brain-stage {
+ position: relative;
+ height: 100%;
+ min-height: 100%;
+ border-radius: 22px;
+ border: 1px solid var(--surface-border);
+ background:
+ radial-gradient(circle at 14% 8%, rgba(31, 122, 114, 0.14), transparent 40%),
+ radial-gradient(circle at 86% 12%, rgba(30, 91, 255, 0.1), transparent 42%),
+ linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(245, 249, 247, 0.94)),
+ repeating-linear-gradient(
+ 135deg,
+ rgba(255, 255, 255, 0.05) 0,
+ rgba(255, 255, 255, 0.05) 14px,
+ rgba(31, 122, 114, 0.03) 14px,
+ rgba(31, 122, 114, 0.03) 28px
+ );
+ box-shadow: var(--shadow-sm);
+}
+
+.brain-head {
+ display: grid;
+ grid-template-columns: 220px minmax(0, 1fr) 180px;
+ align-items: center;
+ padding: 12px 18px 0;
+}
+
+.head-date {
+ color: var(--text-secondary);
+ font-size: 24px;
+ font-weight: 600;
+}
+
+.head-date p {
+ margin: 0;
+ line-height: 1.2;
+}
+
+.head-title {
+ justify-self: center;
+ width: min(760px, 95%);
+ height: 68px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0 0 46px 46px;
+ color: #fff;
+ font-size: 42px;
+ font-style: italic;
+ font-weight: 700;
+ letter-spacing: 1px;
+ background: linear-gradient(135deg, #1f7a72 0%, #1e5bff 100%);
+ box-shadow: 0 16px 30px rgba(31, 122, 114, 0.24);
+}
+
+.head-actions {
+ justify-self: end;
+}
+
+.head-back-btn {
+ height: 40px;
+ padding: 0 14px;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ border: none;
+ border-radius: 999px;
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--colorPrimary);
+ background: var(--surface-base);
+ box-shadow: 0 8px 18px rgba(31, 49, 38, 0.12);
+ cursor: pointer;
+}
+
+.brain-intro {
+ text-align: center;
+ margin-top: 34px;
+}
+
+.brain-intro h2 {
+ margin: 0;
+ font-size: 44px;
+ font-style: italic;
+ font-weight: 700;
+ color: var(--text-primary);
+}
+
+.brain-intro p {
+ margin: 12px 0 10px;
+ font-size: 28px;
+ color: var(--text-secondary);
+}
+
+.intro-sign {
+ display: inline-block;
+ padding: 6px 18px;
+ border-radius: 999px;
+ font-size: 24px;
+ font-weight: 700;
+ color: #1e5bff;
+ background: rgba(255, 255, 255, 0.82);
+ border: 1px solid rgba(30, 91, 255, 0.18);
+}
+
+.carousel-area {
+ position: relative;
+ margin-top: 34px;
+ padding: 0 72px 12px;
+}
+
+.carousel-track {
+ position: relative;
+ height: 500px;
+ overflow: hidden;
+}
+
+.brain-footer {
+ position: relative;
+ margin: 0 72px;
+ height: clamp(226px, 25vh,0);
+ border-radius: 18px;
+ border: 1px solid rgba(31, 122, 114, 0.28);
+ background:
+ linear-gradient(120deg, rgba(31, 122, 114, 0.14), rgba(30, 91, 255, 0.14)),
+ linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(236, 244, 249, 0.9));
+ box-shadow:
+ 0 16px 34px rgba(31, 81, 131, 0.12),
+ inset 0 1px 0 rgba(255, 255, 255, 0.72);
+ overflow: hidden;
+}
+
+.brain-footer::before {
+ content: "";
+ position: absolute;
+ left: -22%;
+ bottom: -120%;
+ width: 52%;
+ height: 260%;
+ background: radial-gradient(ellipse at center, rgba(30, 91, 255, 0.2) 0%, rgba(30, 91, 255, 0) 72%);
+ pointer-events: none;
+}
+
+.brain-footer::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(110deg, transparent 12%, rgba(255, 255, 255, 0.24) 38%, transparent 64%);
+ transform: translateX(-120%);
+ animation: footerSweep 5.8s linear infinite;
+ pointer-events: none;
+}
+
+.footer-grid-overlay {
+ position: absolute;
+ inset: 0;
+ background:
+ repeating-linear-gradient(
+ 90deg,
+ rgba(31, 122, 114, 0.07) 0,
+ rgba(31, 122, 114, 0.07) 1px,
+ transparent 1px,
+ transparent 36px
+ ),
+ repeating-linear-gradient(
+ 0deg,
+ rgba(30, 91, 255, 0.06) 0,
+ rgba(30, 91, 255, 0.06) 1px,
+ transparent 1px,
+ transparent 28px
+ );
+ opacity: 0.72;
+ pointer-events: none;
+}
+
+.footer-metrics {
+ position: relative;
+ z-index: 2;
+ padding: 14px 20px 72px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.footer-metric {
+ min-height: 76px;
+ border-radius: 12px;
+ padding: 10px 14px;
+ border: 1px solid rgba(37, 124, 188, 0.2);
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(245, 250, 255, 0.82));
+ box-shadow: 0 10px 18px rgba(29, 83, 134, 0.08);
+ display: grid;
+ grid-template-rows: auto auto 1fr;
+ gap: 4px;
+}
+
+.footer-metric--focus {
+ border-color: rgba(38, 122, 194, 0.34);
+ box-shadow:
+ 0 12px 22px rgba(30, 91, 255, 0.12),
+ inset 0 0 0 1px rgba(85, 148, 232, 0.2);
+}
+
+.footer-metric__label {
+ font-size: 14px;
+ color: rgba(38, 72, 108, 0.88);
+ font-weight: 600;
+}
+
+.footer-metric__value {
+ font-size: 30px;
+ line-height: 1;
+ font-style: italic;
+ font-weight: 700;
+ color: #1f5ddf;
+ text-shadow: 0 3px 10px rgba(30, 91, 255, 0.18);
+}
+
+.footer-metric__hint {
+ margin-top: auto;
+ font-size: 13px;
+ color: rgba(52, 89, 128, 0.82);
+}
+
+.footer-metric--period .footer-metric__hint {
+ margin-top: 0;
+ line-height: 1.25;
+}
+
+.footer-metric--period {
+ min-height: 122px;
+ grid-template-rows: auto auto auto auto auto;
+ gap: 6px;
+}
+
+.footer-period-control {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.period-btn {
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ border: 1px solid rgba(38, 112, 183, 0.28);
+ background: rgba(255, 255, 255, 0.9);
+ color: #2054c9;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 1;
+ cursor: pointer;
+}
+
+.period-input {
+ width: 56px;
+ height: 24px;
+ border-radius: 8px;
+ border: 1px solid rgba(38, 112, 183, 0.24);
+ background: rgba(255, 255, 255, 0.94);
+ color: #1f5ddf;
+ font-size: 13px;
+ font-weight: 600;
+ text-align: center;
+ padding: 0 4px;
+}
+
+.period-unit {
+ font-size: 12px;
+ font-weight: 600;
+ color: rgba(40, 80, 117, 0.86);
+}
+
+.footer-period-slider {
+ width: min(250px, 100%);
+ height: 3px;
+ accent-color: #2a6ded;
+ cursor: pointer;
+}
+
+.footer-rail {
+ position: absolute;
+ left: 20px;
+ right: 20px;
+ bottom: 18px;
+ z-index: 2;
+}
+
+.footer-rail__line {
+ position: relative;
+ height: 2px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, rgba(31, 122, 114, 0.12), rgba(30, 91, 255, 0.6), rgba(31, 122, 114, 0.12));
+ overflow: hidden;
+}
+
+.footer-rail__flow {
+ position: absolute;
+ top: -1px;
+ left: -18%;
+ width: 22%;
+ height: 4px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, rgba(31, 122, 114, 0), rgba(59, 146, 244, 0.92), rgba(30, 91, 255, 0));
+ filter: blur(0.2px);
+ animation: railFlow 3.1s ease-in-out infinite;
+}
+
+.footer-rail__nodes {
+ margin-top: 12px;
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 8px;
+}
+
+.footer-node {
+ height: 34px;
+ border: 1px solid rgba(38, 112, 183, 0.18);
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.76);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ color: rgba(40, 80, 117, 0.92);
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.26s ease;
+}
+
+.footer-node:hover {
+ transform: translateY(-1px);
+ border-color: rgba(31, 122, 114, 0.34);
+ box-shadow: 0 8px 14px rgba(31, 122, 114, 0.14);
+}
+
+.footer-node--active {
+ color: #fff;
+ border-color: transparent;
+ background: linear-gradient(135deg, rgba(31, 122, 114, 0.94), rgba(30, 91, 255, 0.94));
+ box-shadow: 0 10px 18px rgba(30, 91, 255, 0.28);
+}
+
+.footer-node__dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: rgba(30, 91, 255, 0.72);
+ box-shadow: 0 0 10px rgba(30, 91, 255, 0.52);
+}
+
+.footer-node--active .footer-node__dot {
+ background: #fff;
+ box-shadow: 0 0 12px rgba(255, 255, 255, 0.72);
+ animation: nodePulse 1.4s ease-in-out infinite;
+}
+
+.agent-card {
+ position: absolute;
+ left: 50%;
+ top: 0;
+ width: 460px;
+ margin-left: -230px;
+ cursor: pointer;
+ transform-origin: center bottom;
+ transition: transform 0.35s ease, opacity 0.35s ease;
+}
+
+.agent-card__head {
+ height: 56px;
+ border-radius: 12px 12px 0 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 28px;
+ color: #fff;
+ font-weight: 700;
+ background: linear-gradient(135deg, #1f7a72 0%, #1e5bff 100%);
+}
+
+.agent-card__head--active {
+ box-shadow:
+ 0 12px 22px rgba(30, 91, 255, 0.26),
+ inset 0 0 0 1px rgba(255, 255, 255, 0.28);
+ position: relative;
+}
+
+.agent-card__head--active::after {
+ content: "";
+ position: absolute;
+ left: 12px;
+ right: 12px;
+ bottom: 6px;
+ height: 3px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.96), rgba(255, 255, 255, 0.22));
+}
+
+.agent-card__body {
+ position: relative;
+ height: 430px;
+ border: 1px solid var(--surface-border-strong);
+ border-top: none;
+ border-radius: 0 0 20px 20px;
+ background: rgba(255, 255, 255, 0.96);
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+ isolation: isolate;
+ box-shadow: 0 12px 24px rgba(31, 49, 38, 0.1);
+}
+
+.agent-card__body--active {
+ background: linear-gradient(180deg, rgba(248, 252, 251, 0.96), rgba(225, 241, 250, 0.9));
+ border-color: rgba(31, 122, 114, 0.35);
+}
+
+.agent-card__body--active::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(108deg, transparent 28%, rgba(255, 255, 255, 0.34) 50%, transparent 72%);
+ transform: translateX(-125%);
+ animation: bodySweep 3.6s linear infinite;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.avatar-shell {
+ position: relative;
+ width: 248px;
+ height: 430px;
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ --base-core: rgba(53, 143, 222, 0.4);
+ --base-ring: rgba(39, 122, 201, 0.62);
+ --base-glow: rgba(46, 133, 214, 0.28);
+}
+
+.avatar-shell::before {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: -10px;
+ width: 268px;
+ height: 58px;
+ transform: translateX(-50%);
+ border-radius: 50%;
+ background: radial-gradient(
+ ellipse at center,
+ rgba(55, 140, 219, 0.22) 0%,
+ rgba(55, 140, 219, 0.11) 46%,
+ rgba(55, 140, 219, 0) 74%
+ );
+ filter: blur(2.4px);
+ animation: baseGlow 4.6s ease-in-out infinite;
+ z-index: 1;
+ pointer-events: none;
+}
+
+.avatar-shell::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: 0;
+ width: 248px;
+ height: 46px;
+ transform: translateX(-50%);
+ border-radius: 50%;
+ border: 2px solid var(--base-ring);
+ box-shadow:
+ inset 0 0 0 1px rgba(255, 255, 255, 0.64),
+ 0 0 24px var(--base-glow);
+ animation: basePulse 3.1s ease-in-out infinite;
+ z-index: 4;
+}
+
+.avatar-base {
+ position: absolute;
+ left: 50%;
+ bottom: 2px;
+ width: 224px;
+ height: 38px;
+ transform: translateX(-50%);
+ z-index: 2;
+ pointer-events: none;
+}
+
+.avatar-base::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: 50%;
+ background:
+ radial-gradient(
+ ellipse at center,
+ rgba(255, 255, 255, 0.96) 0%,
+ rgba(255, 255, 255, 0.92) 36%,
+ var(--base-core) 68%,
+ rgba(38, 118, 195, 0.08) 100%
+ );
+ box-shadow:
+ 0 0 30px var(--base-core),
+ 0 0 10px rgba(255, 255, 255, 0.34) inset;
+ z-index: 3;
+}
+
+.avatar-base::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 194px;
+ height: 194px;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ background:
+ conic-gradient(
+ from 180deg,
+ transparent 0deg,
+ var(--base-ring) 48deg,
+ transparent 112deg,
+ var(--base-ring) 208deg,
+ transparent 284deg,
+ rgba(33, 114, 191, 0.48) 332deg,
+ transparent 360deg
+ );
+ -webkit-mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%);
+ mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%);
+ opacity: 0.62;
+ animation: baseRotate 10.5s linear infinite;
+ z-index: 2;
+}
+
+.avatar-shell--active {
+ --base-core: rgba(50, 141, 217, 0.52);
+ --base-ring: rgba(42, 127, 205, 0.76);
+ --base-glow: rgba(38, 130, 211, 0.38);
+}
+
+.avatar-cut {
+ position: relative;
+ width: 220px;
+ height: 430px;
+ z-index: 6;
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+ filter: saturate(1.04) drop-shadow(0 14px 18px rgba(24, 44, 66, 0.14));
+ transform-origin: center 82%;
+ animation: avatarFloat 3.2s ease-in-out infinite;
+}
+
+.avatar-cut__img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ object-position: center bottom;
+ display: block;
+}
+
+.agent-card--active .avatar-cut {
+ animation-duration: 2.6s;
+}
+
+.highlight-list {
+ position: absolute;
+ right: 10px;
+ top: 14px;
+ display: grid;
+ gap: 8px;
+ width: 220px;
+ z-index: 16;
+}
+
+.highlight-item {
+ border-radius: 10px;
+ padding: 8px 10px;
+ font-size: 18px;
+ line-height: 1.4;
+ color: #fff;
+ background: rgba(33, 49, 63, 0.92);
+ box-shadow: 0 8px 16px rgba(21, 30, 40, 0.22);
+}
+
+.agent-card--active .highlight-item {
+ background: rgba(31, 122, 114, 0.9);
+}
+
+.nav-btn {
+ position: absolute;
+ top: 212px;
+ z-index: 80;
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ border: none;
+ font-size: 30px;
+ color: var(--colorPrimary);
+ background: var(--surface-base);
+ box-shadow: 0 10px 20px rgba(31, 49, 38, 0.16);
+ cursor: pointer;
+}
+
+.nav-btn--left {
+ left: 14px;
+}
+
+.nav-btn--right {
+ right: 14px;
+}
+
+.ai-fullscreen {
+ position: fixed;
+ inset: 0;
+ z-index: 2100;
+ padding: 12px;
+ background: rgba(33, 49, 63, 0.24);
+ backdrop-filter: blur(2px);
+}
+
+.ai-panel {
+ height: 100%;
+ border-radius: 22px;
+ border: 1px solid var(--surface-border);
+ background: linear-gradient(180deg, #f9fcfb 0%, #f0f5f2 100%);
+ display: grid;
+ grid-template-rows: 62px minmax(0, 1fr) 110px;
+ box-shadow: var(--shadow-md);
+}
+
+.ai-panel__top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 24px;
+}
+
+.ai-brand {
+ font-size: 34px;
+ color: var(--text-primary);
+ font-weight: 700;
+}
+
+.ai-close {
+ width: 40px;
+ height: 40px;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ font-size: 30px;
+ color: var(--text-secondary);
+ cursor: pointer;
+}
+
+.ai-panel__center {
+ padding: 8px 20px 10px;
+ display: grid;
+ grid-template-rows: 120px 290px minmax(0, 1fr);
+ gap: 10px;
+ min-height: 0;
+}
+
+.welcome-card {
+ border-radius: 14px;
+ background: linear-gradient(135deg, rgba(232, 244, 242, 0.95), rgba(230, 237, 250, 0.9));
+ padding: 16px 18px;
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+ border: 1px solid var(--surface-border);
+}
+
+.welcome-card__text h3 {
+ margin: 0;
+ font-size: 28px;
+ color: var(--text-primary);
+}
+
+.welcome-card__text p {
+ margin: 8px 0 0;
+ font-size: 20px;
+ color: var(--text-secondary);
+}
+
+.mini-avatar {
+ width: 120px;
+ height: 120px;
+ border-radius: 14px;
+ border: 1px solid var(--surface-border);
+ background-color: #fff;
+ background-clip: border-box;
+ overflow: hidden;
+}
+
+.mini-avatar__img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ object-position: center bottom;
+ display: block;
+}
+
+.recommend-card {
+ border-radius: 14px;
+ border: 1px solid var(--surface-border);
+ background: rgba(255, 255, 255, 0.86);
+ padding: 12px 14px;
+}
+
+.recommend-card__head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ color: var(--text-primary);
+ font-size: 24px;
+ font-weight: 700;
+}
+
+.refresh-btn {
+ border: none;
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: 18px;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ cursor: pointer;
+}
+
+.recommend-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px 18px;
+}
+
+.recommend-item {
+ border: 1px solid var(--surface-border);
+ border-radius: 8px;
+ text-align: left;
+ padding: 8px 10px;
+ font-size: 18px;
+ color: var(--text-secondary);
+ background: #fff;
+ cursor: pointer;
+}
+
+.recommend-item:hover {
+ background: rgba(31, 122, 114, 0.08);
+ color: var(--colorPrimary);
+}
+
+.chat-card {
+ border-radius: 14px;
+ border: 1px solid var(--surface-border);
+ background: #fff;
+ min-height: 0;
+ overflow: hidden;
+}
+
+.chat-empty {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--text-tertiary);
+ font-size: 18px;
+}
+
+.chat-messages {
+ height: 100%;
+ overflow-y: auto;
+ padding: 14px;
+ display: grid;
+ gap: 10px;
+}
+
+.chat-row {
+ display: flex;
+}
+
+.chat-row--assistant {
+ justify-content: flex-start;
+}
+
+.chat-row--user {
+ justify-content: flex-end;
+}
+
+.chat-bubble {
+ max-width: 72%;
+ border-radius: 12px;
+ padding: 10px 12px;
+ font-size: 18px;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ color: var(--text-primary);
+ background: var(--surface-soft);
+ border: 1px solid var(--surface-border);
+}
+
+.chat-row--user .chat-bubble {
+ color: #fff;
+ background: linear-gradient(135deg, #1f7a72 0%, #1e5bff 100%);
+ border: none;
+}
+
+.ai-panel__input {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 130px;
+ gap: 12px;
+ padding: 14px 20px 18px;
+}
+
+.ask-input :deep(.el-input__wrapper) {
+ height: 74px;
+ border-radius: 18px;
+ box-shadow: 0 0 0 1px var(--surface-border) inset;
+ background: #fff;
+}
+
+.ask-input :deep(.el-input__inner) {
+ font-size: 20px;
+}
+
+.send-btn {
+ align-self: center;
+ height: 56px;
+ font-size: 20px;
+ min-width: 98px;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.2s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+ opacity: 0;
+}
+
+@keyframes avatarFloat {
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-8px);
+ }
+}
+
+@keyframes basePulse {
+ 0%,
+ 100% {
+ transform: translateX(-50%) scale(1);
+ opacity: 0.88;
+ }
+ 50% {
+ transform: translateX(-50%) scale(1.045);
+ opacity: 0.95;
+ }
+}
+
+@keyframes baseRotate {
+ from {
+ transform: translate(-50%, -50%) rotate(0deg);
+ }
+ to {
+ transform: translate(-50%, -50%) rotate(360deg);
+ }
+}
+
+@keyframes baseGlow {
+ 0%,
+ 100% {
+ transform: translateX(-50%) scaleX(1);
+ opacity: 0.84;
+ }
+ 50% {
+ transform: translateX(-50%) scaleX(1.06);
+ opacity: 0.96;
+ }
+}
+
+@keyframes bodySweep {
+ 0% {
+ transform: translateX(-125%);
+ }
+ 100% {
+ transform: translateX(135%);
+ }
+}
+
+@keyframes footerSweep {
+ 0% {
+ transform: translateX(-120%);
+ }
+ 100% {
+ transform: translateX(140%);
+ }
+}
+
+@keyframes railFlow {
+ 0% {
+ transform: translateX(0);
+ opacity: 0;
+ }
+ 20% {
+ opacity: 1;
+ }
+ 80% {
+ opacity: 1;
+ }
+ 100% {
+ transform: translateX(520%);
+ opacity: 0;
+ }
+}
+
+@keyframes nodePulse {
+ 0%,
+ 100% {
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale(1.25);
+ }
+}
+
+@media (max-width: 1600px) {
+ .head-title {
+ font-size: 34px;
+ height: 60px;
+ }
+
+ .brain-intro h2 {
+ font-size: 36px;
+ }
+
+ .brain-intro p {
+ font-size: 22px;
+ }
+
+ .intro-sign {
+ font-size: 20px;
+ }
+
+ .agent-card {
+ width: 380px;
+ margin-left: -190px;
+ }
+
+ .agent-card__head {
+ font-size: 24px;
+ height: 54px;
+ }
+
+ .agent-card__body {
+ height: 390px;
+ }
+
+ .highlight-list {
+ width: 184px;
+ }
+
+ .highlight-item {
+ font-size: 15px;
+ }
+
+ .avatar-shell {
+ width: 220px;
+ height: 390px;
+ }
+
+ .avatar-cut {
+ width: 202px;
+ height: 390px;
+ }
+
+ .avatar-base {
+ width: 194px;
+ height: 34px;
+ }
+
+ .avatar-base::after {
+ width: 164px;
+ height: 164px;
+ }
+
+ .avatar-shell::before {
+ width: 236px;
+ height: 48px;
+ bottom: -9px;
+ }
+
+ .avatar-shell::after {
+ width: 220px;
+ height: 40px;
+ bottom: 0;
+ }
+
+ .brain-footer {
+ margin: 0 52px;
+ height: clamp(210px, 23vh, 264px);
+ }
+
+ .footer-metrics {
+ padding: 12px 14px 66px;
+ gap: 8px;
+ }
+
+ .footer-metric {
+ min-height: 66px;
+ padding: 8px 10px;
+ }
+
+ .footer-metric--period {
+ min-height: 108px;
+ gap: 4px;
+ }
+
+ .footer-metric__label {
+ font-size: 12px;
+ }
+
+ .footer-metric__value {
+ font-size: 24px;
+ }
+
+ .footer-metric__hint {
+ font-size: 11px;
+ }
+
+ .footer-period-control {
+ gap: 6px;
+ }
+
+ .period-btn {
+ width: 20px;
+ height: 20px;
+ font-size: 12px;
+ }
+
+ .period-input {
+ width: 50px;
+ height: 22px;
+ font-size: 12px;
+ }
+
+ .footer-period-slider {
+ width: 100%;
+ }
+
+ .footer-rail {
+ left: 14px;
+ right: 14px;
+ bottom: 14px;
+ }
+
+ .footer-rail__nodes {
+ margin-top: 10px;
+ gap: 6px;
+ }
+
+ .footer-node {
+ height: 30px;
+ font-size: 12px;
+ gap: 6px;
+ }
+
+ .footer-node__dot {
+ width: 7px;
+ height: 7px;
+ }
+
+ .ai-brand {
+ font-size: 28px;
+ }
+
+ .welcome-card__text h3,
+ .recommend-card__head {
+ font-size: 22px;
+ }
+
+ .welcome-card__text p,
+ .recommend-item,
+ .chat-bubble,
+ .refresh-btn,
+ .chat-empty,
+ .ask-input :deep(.el-input__inner),
+ .send-btn {
+ font-size: 16px;
+ }
+}
+</style>
diff --git a/src/views/basicData/customerFile/index.vue b/src/views/basicData/customerFile/index.vue
index a080bd9..7091a02 100644
--- a/src/views/basicData/customerFile/index.vue
+++ b/src/views/basicData/customerFile/index.vue
@@ -27,6 +27,9 @@
<div>
<el-button type="primary"
@click="openForm('add')">鏂板瀹㈡埛</el-button>
+ <el-button type="primary"
+ plain
+ @click="back">娴佸叆鍏捣</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
<el-button type="info"
plain
@@ -619,7 +622,7 @@
addReturnVisit,
getReturnVisit,
} from "@/api/basicData/customerFile.js";
- import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer} from "@/api/basicData/customer.js";
+ import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, backCustomer} from "@/api/basicData/customer.js";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import useUserStore from "@/store/modules/user";
@@ -1126,6 +1129,36 @@
});
};
+ const back = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ const ids = selectedRows.value.map(item => item.id);
+ ElMessageBox.confirm("閫変腑鐨勫鎴峰皢娴佸叆鍏捣锛屾槸鍚︾‘璁わ紵", "娴佸叆鍏捣鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ return Promise.all(ids.map(id => backCustomer(id)))
+ .then(() => {
+ proxy.$modal.msgSuccess("娴佸叆鍏捣鎴愬姛");
+ selectedRows.value = [];
+ getList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(error => {
+ if (error === "cancel" || error === "close") {
+ proxy.$modal.msg("宸插彇娑�");
+ }
+ });
+ };
+
// 鎵撳紑鍥炶鎻愰啋寮圭獥
const openReminderDialog = row => {
currentCustomerId.value = row.id;
@@ -1413,7 +1446,7 @@
const downloadAttachment = row => {
if (row.url) {
// proxy.download(row.url, {}, row.name);
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
} else {
proxy.$modal.msgError("涓嬭浇閾炬帴涓嶅瓨鍦�");
}
diff --git a/src/views/basicData/customerFileOpenSea/index.vue b/src/views/basicData/customerFileOpenSea/index.vue
index 2598f48..bad28e1 100644
--- a/src/views/basicData/customerFileOpenSea/index.vue
+++ b/src/views/basicData/customerFileOpenSea/index.vue
@@ -1651,7 +1651,7 @@
const downloadAttachment = row => {
if (row.url) {
// proxy.download(row.url, {}, row.name);
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
} else {
proxy.$modal.msgError("涓嬭浇閾炬帴涓嶅瓨鍦�");
}
diff --git a/src/views/basicData/product/index.vue b/src/views/basicData/product/index.vue
index 99ab028..b05b215 100644
--- a/src/views/basicData/product/index.vue
+++ b/src/views/basicData/product/index.vue
@@ -5,7 +5,7 @@
<el-input v-model="search"
style="width: 210px"
placeholder="杈撳叆鍏抽敭瀛楄繘琛屾悳绱�"
- @change="searchFilter"
+ @input="debouncedSearch"
@clear="searchFilter"
clearable
prefix-icon="Search" />
@@ -43,12 +43,12 @@
<el-button type="primary"
link
:disabled="isTopLevelNode(data, node)"
- @click="openProDia('edit', data)">
+ @click="openProDia('edit', data, node)">
缂栬緫
</el-button>
<el-button type="primary"
link
- @click="openProDia('add', data)">
+ @click="openProDia('add', data, node)">
娣诲姞浜у搧
</el-button>
<el-button v-if="!node.childNodes.length"
@@ -285,6 +285,8 @@
const search = ref("");
const currentId = ref("");
const currentParentId = ref("");
+ /** 浜у搧寮圭獥锛歛dd 瀛樼埗鑺傜偣 id锛沞dit 瀛樺綋鍓嶈妭鐐� id 涓� parentId锛堜笉渚濊禆鏍戦�変腑椤癸級 */
+ const productDialogTarget = ref(null);
const operationType = ref("");
const treeLoad = ref(false);
const list = ref([]);
@@ -388,17 +390,28 @@
return [null, undefined, "", 0, "0"].includes(data?.parentId);
};
// 鎵撳紑浜у搧寮规
- const openProDia = (type, data) => {
- if (data && type === "edit" && isTopLevelNode(data)) {
+ const openProDia = (type, data, node) => {
+ if (data && type === "edit" && isTopLevelNode(data, node)) {
proxy.$modal.msgWarning("涓�绾ц妭鐐逛笉鑳界紪杈戞垨鍒犻櫎");
return;
}
operationType.value = type;
- productDia.value = true;
- form.value.productName = "";
- if (type === "edit") {
- form.value.productName = data.productName;
+ productDialogTarget.value = null;
+ if (type === "add" && data) {
+ productDialogTarget.value = { parentId: data.id };
+ } else if (type === "edit" && data) {
+ let parentId = data.parentId;
+ if (
+ [null, undefined, ""].includes(parentId) &&
+ node?.parent?.data?.id != null
+ ) {
+ parentId = node.parent.data.id;
+ }
+ productDialogTarget.value = { id: data.id, parentId };
}
+ productDia.value = true;
+ form.value.productName =
+ type === "edit" && data ? data.productName : "";
};
// 鎵撳紑瑙勬牸鍨嬪彿寮规
const openModelDia = (type, data) => {
@@ -417,14 +430,16 @@
proxy.$refs.formRef.validate(valid => {
if (valid) {
if (operationType.value === "add") {
- form.value.parentId = currentId.value;
+ form.value.parentId =
+ productDialogTarget.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 = "";
+ form.value.id =
+ productDialogTarget.value?.id ?? currentId.value;
+ form.value.parentId = productDialogTarget.value?.parentId ?? "";
}
addOrEditProduct(form.value).then(res => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
@@ -437,6 +452,7 @@
// 鍏抽棴浜у搧寮规
const closeProDia = () => {
proxy.$refs.formRef.resetFields();
+ productDialogTarget.value = null;
productDia.value = false;
};
@@ -549,40 +565,31 @@
proxy.$modal.msg("宸插彇娑�");
});
};
- // 璋冪敤tree杩囨护鏂规硶 涓枃鑻辫繃婊�
- const filterNode = (value, data, node) => {
- if (!value) {
- //濡傛灉鏁版嵁涓虹┖锛屽垯杩斿洖true,鏄剧ず鎵�鏈夌殑鏁版嵁椤�
- return true;
- }
- // 鏌ヨ鍒楄〃鏄惁鏈夊尮閰嶆暟鎹紝灏嗗�煎皬鍐欙紝鍖归厤鑻辨枃鏁版嵁
- let val = value.toLowerCase();
- return chooseNode(val, data, node); // 璋冪敤杩囨护浜屽眰鏂规硶
+ const debounce = (fn, delay = 300) => {
+ let timer;
+ return (...args) => {
+ clearTimeout(timer);
+ timer = setTimeout(() => fn(...args), delay);
+ };
};
- // 杩囨护鐖惰妭鐐� / 瀛愯妭鐐� (濡傛灉杈撳叆鐨勫弬鏁版槸鐖惰妭鐐逛笖鑳藉尮閰嶏紝鍒欒繑鍥炶鑺傜偣浠ュ強鍏朵笅鐨勬墍鏈夊瓙鑺傜偣锛涘鏋滃弬鏁版槸瀛愯妭鐐癸紝鍒欒繑鍥炶鑺傜偣鐨勭埗鑺傜偣銆俷ame鏄腑鏂囧瓧绗︼紝enName鏄嫳鏂囧瓧绗�.
- const chooseNode = (value, data, node) => {
- if (data.label.indexOf(value) !== -1) {
+
+ const debouncedSearch = debounce(() => {
+ searchFilter();
+ }, 300);
+
+ const filterNode = (value, data) => {
+ if (!value) return true;
+ return chooseNode(value.toLowerCase(), data);
+ };
+
+ const chooseNode = (value, data) => {
+ const label = (data.label || '').toLowerCase();
+ if (label.indexOf(value) !== -1) {
return true;
}
- const level = node.level;
- // 濡傛灉浼犲叆鐨勮妭鐐规湰韬氨鏄竴绾ц妭鐐瑰氨涓嶇敤鏍¢獙浜�
- if (level === 1) {
- return false;
+ if (data.children && data.children.length > 0) {
+ return data.children.some(child => chooseNode(value, child));
}
- // 鍏堝彇褰撳墠鑺傜偣鐨勭埗鑺傜偣
- 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();
diff --git a/src/views/basicData/supplierManage/components/BlacklistTab.vue b/src/views/basicData/supplierManage/components/BlacklistTab.vue
index 8f6204b..206ba62 100644
--- a/src/views/basicData/supplierManage/components/BlacklistTab.vue
+++ b/src/views/basicData/supplierManage/components/BlacklistTab.vue
@@ -231,7 +231,10 @@
</div>
</template>
</el-dialog>
- <files-dia ref="filesDia"></files-dia>
+ <FileList v-if="fileListDialogVisible"
+ v-model:visible="fileListDialogVisible"
+ record-type="supplier_manage"
+ :record-id="recordId" />
</div>
</template>
@@ -249,7 +252,9 @@
} from "@/api/basicData/supplierManageFile.js";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth.js";
-import FilesDia from "../filesDia.vue";
+const FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+);
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
@@ -327,7 +332,7 @@
name: "璧勮川鏂囦欢",
type: "text",
clickFun: (row) => {
- openFilesFormDia(row)
+ openFileDialog(row)
}
}
],
@@ -342,7 +347,8 @@
size: 100,
total: 0,
});
-const filesDia = ref()
+const fileListDialogVisible = ref(false);
+const recordId = ref();
// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
const operationType = ref("");
const dialogFormVisible = ref(false);
@@ -567,10 +573,9 @@
return `${year}-${month}-${day}`;
}
// 鎵撳紑闄勪欢寮规
-const openFilesFormDia = (row) => {
- nextTick(() => {
- filesDia.value?.openDialog(row)
- })
+const openFileDialog = async row => {
+ recordId.value = row.id;
+ fileListDialogVisible.value = true;
};
onMounted(() => {
diff --git a/src/views/basicData/supplierManage/components/HomeTab.vue b/src/views/basicData/supplierManage/components/HomeTab.vue
index 47dce00..da62ca4 100644
--- a/src/views/basicData/supplierManage/components/HomeTab.vue
+++ b/src/views/basicData/supplierManage/components/HomeTab.vue
@@ -237,7 +237,10 @@
</div>
</template>
</el-dialog>
- <files-dia ref="filesDia"></files-dia>
+ <FileList v-if="fileListDialogVisible"
+ v-model:visible="fileListDialogVisible"
+ record-type="supplier_manage"
+ :record-id="recordId" />
</div>
</template>
@@ -255,7 +258,9 @@
} from "@/api/basicData/supplierManageFile.js";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth.js";
-import FilesDia from "../filesDia.vue";
+const FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+);
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
@@ -333,7 +338,7 @@
name: "璧勮川鏂囦欢",
type: "text",
clickFun: (row) => {
- openFilesFormDia(row)
+ openFileDialog(row)
}
}
],
@@ -343,12 +348,13 @@
const selectedRows = ref([]);
const userList = ref([]);
const tableLoading = ref(false);
+const fileListDialogVisible = ref(false);
+const recordId = ref();
const page = reactive({
current: 1,
size: 100,
total: 0,
});
-const filesDia = ref()
// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
const operationType = ref("");
const dialogFormVisible = ref(false);
@@ -573,10 +579,9 @@
return `${year}-${month}-${day}`;
}
// 鎵撳紑闄勪欢寮规
-const openFilesFormDia = (row) => {
- nextTick(() => {
- filesDia.value?.openDialog(row)
- })
+const openFileDialog = async row => {
+ recordId.value = row.id;
+ fileListDialogVisible.value = true;
};
onMounted(() => {
diff --git a/src/views/basicData/supplierManage/filesDia.vue b/src/views/basicData/supplierManage/filesDia.vue
index 7943185..ef41985 100644
--- a/src/views/basicData/supplierManage/filesDia.vue
+++ b/src/views/basicData/supplierManage/filesDia.vue
@@ -164,7 +164,7 @@
}
// 涓嬭浇闄勪欢
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
// 鍒犻櫎
const handleDelete = () => {
diff --git a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
index 1269621..0605fe5 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -1,85 +1,103 @@
<template>
<div>
- <el-dialog
- v-model="dialogFormVisible"
- :title="operationType === 'approval' ? '瀹℃壒' : '璇︽儏'"
- width="700px"
- @close="closeDia"
- >
- <el-form :model="form" label-width="140px" label-position="top" ref="formRef">
- <el-row>
- <el-col :span="24">
- <el-form-item label="娴佺▼缂栧彿锛�" prop="approveId">
- <el-input v-model="form.approveId" placeholder="鑷姩缂栧彿" clearable disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鐢宠閮ㄩ棬锛�">
- <el-select
- disabled
- v-model="form.approveDeptId"
- placeholder="閫夋嫨閮ㄩ棬"
- >
- <el-option
- v-for="user in productOptions"
- :key="user.deptId"
- :label="user.deptName"
- :value="user.deptId"
- />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row v-if="!isQuotationApproval && !isPurchaseApproval">
- <el-col :span="24">
- <el-form-item :label="props.approveType == 5 ? '閲囪喘鍚堝悓鍙凤細' : '瀹℃壒浜嬬敱锛�'" prop="approveReason">
- <el-input v-model="form.approveReason" placeholder="璇疯緭鍏�" clearable type="textarea" disabled/>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
-
+ <el-dialog v-model="dialogFormVisible"
+ :title="operationType === 'approval' ? '瀹℃壒' : '璇︽儏'"
+ width="700px"
+ @close="closeDia">
+ <el-form :model="form"
+ label-width="140px"
+ label-position="top"
+ ref="formRef">
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="娴佺▼缂栧彿锛�"
+ prop="approveId">
+ <el-input v-model="form.approveId"
+ placeholder="鑷姩缂栧彿"
+ clearable
+ disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鐢宠閮ㄩ棬锛�">
+ <el-select disabled
+ v-model="form.approveDeptId"
+ placeholder="閫夋嫨閮ㄩ棬">
+ <el-option v-for="user in productOptions"
+ :key="user.deptId"
+ :label="user.deptName"
+ :value="user.deptId" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row v-if="!isQuotationApproval && !isPurchaseApproval">
+ <el-col :span="24">
+ <el-form-item :label="props.approveType == 5 ? '閲囪喘鍚堝悓鍙凤細' : '瀹℃壒浜嬬敱锛�'"
+ prop="approveReason">
+ <el-input v-model="form.approveReason"
+ placeholder="璇疯緭鍏�"
+ clearable
+ type="textarea"
+ disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
<!-- 鎶ヤ环瀹℃壒锛氬睍绀烘姤浠疯鎯咃紙澶嶇敤閿�鍞姤浠�"鏌ョ湅璇︽儏瀵硅瘽妗�"鍐呭缁撴瀯锛� -->
- <div v-if="isQuotationApproval" style="margin: 10px 0 18px;">
+ <div v-if="isQuotationApproval"
+ style="margin: 10px 0 18px;">
<el-divider content-position="left">鎶ヤ环璇︽儏</el-divider>
- <el-skeleton :loading="quotationLoading" animated>
+ <el-skeleton :loading="quotationLoading"
+ animated>
<template #template>
- <el-skeleton-item variant="h3" style="width: 30%" />
- <el-skeleton-item variant="text" style="width: 100%" />
- <el-skeleton-item variant="text" style="width: 100%" />
+ <el-skeleton-item variant="h3"
+ style="width: 30%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
</template>
<template #default>
- <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="鏈煡璇㈠埌瀵瑰簲鎶ヤ环璇︽儏" />
+ <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo"
+ description="鏈煡璇㈠埌瀵瑰簲鎶ヤ环璇︽儏" />
<template v-else>
- <el-descriptions :column="2" border>
+ <el-descriptions :column="2"
+ border>
<el-descriptions-item label="鎶ヤ环鍗曞彿">{{ currentQuotation.quotationNo }}</el-descriptions-item>
<el-descriptions-item label="瀹㈡埛鍚嶇О">{{ currentQuotation.customer }}</el-descriptions-item>
<el-descriptions-item label="涓氬姟鍛�">{{ currentQuotation.salesperson }}</el-descriptions-item>
<el-descriptions-item label="鎶ヤ环鏃ユ湡">{{ currentQuotation.quotationDate }}</el-descriptions-item>
<el-descriptions-item label="鏈夋晥鏈熻嚦">{{ currentQuotation.validDate }}</el-descriptions-item>
<el-descriptions-item label="浠樻鏂瑰紡">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
- <el-descriptions-item label="鎶ヤ环鎬婚" :span="2">
+ <el-descriptions-item label="鎶ヤ环鎬婚"
+ :span="2">
<span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
楼{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }}
</span>
</el-descriptions-item>
</el-descriptions>
-
<div style="margin-top: 20px;">
<h4>浜у搧鏄庣粏</h4>
- <el-table :data="currentQuotation.products || []" border style="width: 100%">
- <el-table-column prop="product" label="浜у搧鍚嶇О" />
- <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" />
- <el-table-column prop="unit" label="鍗曚綅" />
- <el-table-column prop="unitPrice" label="鍗曚环">
+ <el-table :data="currentQuotation.products || []"
+ border
+ style="width: 100%">
+ <el-table-column prop="product"
+ label="浜у搧鍚嶇О" />
+ <el-table-column prop="specification"
+ label="瑙勬牸鍨嬪彿" />
+ <el-table-column prop="unit"
+ label="鍗曚綅" />
+ <el-table-column prop="unitPrice"
+ label="鍗曚环">
<template #default="scope">楼{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template>
</el-table-column>
</el-table>
</div>
-
- <div v-if="currentQuotation.remark" style="margin-top: 20px;">
+ <div v-if="currentQuotation.remark"
+ style="margin-top: 20px;">
<h4>澶囨敞</h4>
<p>{{ currentQuotation.remark }}</p>
</div>
@@ -87,20 +105,26 @@
</template>
</el-skeleton>
</div>
-
<!-- 閲囪喘瀹℃壒锛氬睍绀洪噰璐鎯� -->
- <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;">
+ <div v-if="isPurchaseApproval"
+ style="margin: 10px 0 18px;">
<el-divider content-position="left">閲囪喘璇︽儏</el-divider>
- <el-skeleton :loading="purchaseLoading" animated>
+ <el-skeleton :loading="purchaseLoading"
+ animated>
<template #template>
- <el-skeleton-item variant="h3" style="width: 30%" />
- <el-skeleton-item variant="text" style="width: 100%" />
- <el-skeleton-item variant="text" style="width: 100%" />
+ <el-skeleton-item variant="h3"
+ style="width: 30%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
</template>
<template #default>
- <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" description="鏈煡璇㈠埌瀵瑰簲閲囪喘璇︽儏" />
+ <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber"
+ description="鏈煡璇㈠埌瀵瑰簲閲囪喘璇︽儏" />
<template v-else>
- <el-descriptions :column="2" border>
+ <el-descriptions :column="2"
+ border>
<el-descriptions-item label="閲囪喘鍚堝悓鍙�">{{ currentPurchase.purchaseContractNumber }}</el-descriptions-item>
<el-descriptions-item label="渚涘簲鍟嗗悕绉�">{{ currentPurchase.supplierName }}</el-descriptions-item>
<el-descriptions-item label="椤圭洰鍚嶇О">{{ currentPurchase.projectName }}</el-descriptions-item>
@@ -108,24 +132,32 @@
<el-descriptions-item label="绛捐鏃ユ湡">{{ currentPurchase.executionDate }}</el-descriptions-item>
<el-descriptions-item label="褰曞叆鏃ユ湡">{{ currentPurchase.entryDate }}</el-descriptions-item>
<el-descriptions-item label="浠樻鏂瑰紡">{{ currentPurchase.paymentMethod }}</el-descriptions-item>
- <el-descriptions-item label="鍚堝悓閲戦" :span="2">
+ <el-descriptions-item label="鍚堝悓閲戦"
+ :span="2">
<span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
楼{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }}
</span>
</el-descriptions-item>
</el-descriptions>
-
<div style="margin-top: 20px;">
<h4>浜у搧鏄庣粏</h4>
- <el-table :data="currentPurchase.productData || []" border style="width: 100%">
- <el-table-column prop="productCategory" label="浜у搧鍚嶇О" />
- <el-table-column prop="specificationModel" label="瑙勬牸鍨嬪彿" />
- <el-table-column prop="unit" label="鍗曚綅" />
- <el-table-column prop="quantity" label="鏁伴噺" />
- <el-table-column prop="taxInclusiveUnitPrice" label="鍚◣鍗曚环">
+ <el-table :data="currentPurchase.productData || []"
+ border
+ style="width: 100%">
+ <el-table-column prop="productCategory"
+ label="浜у搧鍚嶇О" />
+ <el-table-column prop="specificationModel"
+ label="瑙勬牸鍨嬪彿" />
+ <el-table-column prop="unit"
+ label="鍗曚綅" />
+ <el-table-column prop="quantity"
+ label="鏁伴噺" />
+ <el-table-column prop="taxInclusiveUnitPrice"
+ label="鍚◣鍗曚环">
<template #default="scope">楼{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template>
</el-table-column>
- <el-table-column prop="taxInclusiveTotalPrice" label="鍚◣鎬讳环">
+ <el-table-column prop="taxInclusiveTotalPrice"
+ label="鍚◣鎬讳环">
<template #default="scope">楼{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template>
</el-table-column>
</el-table>
@@ -134,52 +166,138 @@
</template>
</el-skeleton>
</div>
-
- <el-form :model="{ activities }" ref="formRef" label-position="top">
- <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical">
- <el-step
- v-for="(activity, index) in activities"
- :key="index"
- finish-status="success"
- :title="getNodeTitle(index, activities.length)"
- :description="activity.approveNodeUser"
- :icon="getNodeIcon(activity, index)"
- >
- <template #icon>
- <el-icon v-if="activity.approveNodeStatus === 2" color="red" :size="22"><WarningFilled /></el-icon>
- <el-icon v-else-if="activity.isShen" color="#1890ff" :size="22"><Edit /></el-icon>
- <el-icon v-else-if="activity.approveNodeStatus === 1" color="#67C23A" :size="26"><Check /></el-icon>
- <el-icon v-else color="#C0C4CC" :size="22"><MoreFilled /></el-icon>
- </template>
+ <!-- 鍙戣揣瀹℃壒锛氬睍绀哄彂璐ц鎯� -->
+ <div v-if="isDeliveryApproval"
+ style="margin: 10px 0 18px;">
+ <el-divider content-position="left">鍙戣揣璇︽儏</el-divider>
+ <el-skeleton :loading="deliveryLoading"
+ animated>
+ <template #template>
+ <el-skeleton-item variant="h3"
+ style="width: 30%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ </template>
+ <template #default>
+ <el-empty v-if="!currentDelivery || !currentDelivery.shippingInfo"
+ description="鏈煡璇㈠埌瀵瑰簲鍙戣揣璇︽儏" />
+ <template v-else>
+ <el-descriptions :column="2"
+ border>
+ <el-descriptions-item label="閿�鍞鍗�">{{ currentDelivery.shippingInfo.salesContractNo || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣璁㈠崟鍙�">{{ currentDelivery.shippingInfo.shippingNo || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="瀹㈡埛鍚嶇О">{{ currentDelivery.shippingInfo.customerName || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣绫诲瀷">{{ currentDelivery.shippingInfo.type || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣鏃ユ湡">{{ currentDelivery.shippingInfo.shippingDate || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="瀹℃牳鐘舵��">{{ currentDelivery.shippingInfo.status || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣杞︾墝鍙�">{{ currentDelivery.shippingInfo.shippingCarNumber || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="蹇�掑叕鍙�">{{ currentDelivery.shippingInfo.expressCompany || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="蹇�掑崟鍙�"
+ :span="2">{{ currentDelivery.shippingInfo.expressNumber || '--' }}</el-descriptions-item>
+ </el-descriptions>
+ <div style="margin-top: 20px;">
+ <h4>浜у搧鏄庣粏</h4>
+ <el-table :data="deliveryProductList"
+ border
+ size="small"
+ style="width: 100%">
+ <el-table-column prop="batchNo"
+ label="鎵瑰彿"
+ show-overflow-tooltip />
+ <el-table-column prop="productName"
+ label="浜у搧鍚嶇О"
+ show-overflow-tooltip />
+ <el-table-column prop="specificationModel"
+ label="瑙勬牸鍨嬪彿"
+ show-overflow-tooltip />
+ <el-table-column prop="deliveryQuantity"
+ label="鍙戣揣鏁伴噺"
+ align="center" />
+ </el-table>
+ </div>
+ <div v-if="currentDelivery.shippingInfo.storageBlobVOs && currentDelivery.shippingInfo.storageBlobVOs.length"
+ style="margin-top: 20px;">
+ <h4>鍙戣揣鍥剧墖</h4>
+ <ImagePreview :file-list="currentDelivery.shippingInfo.storageBlobVOs" />
+ </div>
+ </template>
+ </template>
+ </el-skeleton>
+ </div>
+ <el-form :model="{ activities }"
+ ref="formRef"
+ label-position="top">
+ <el-steps :active="getActiveStep()"
+ finish-status="success"
+ process-status="process"
+ align-center
+ direction="vertical">
+ <el-step v-for="(activity, index) in activities"
+ :key="index"
+ finish-status="success"
+ :title="getNodeTitle(index, activities.length)"
+ :description="activity.approveNodeUser"
+ :icon="getNodeIcon(activity, index)">
+ <template #icon>
+ <el-icon v-if="activity.approveNodeStatus === 2"
+ color="red"
+ :size="22">
+ <WarningFilled />
+ </el-icon>
+ <el-icon v-else-if="activity.isShen"
+ color="#1890ff"
+ :size="22">
+ <Edit />
+ </el-icon>
+ <el-icon v-else-if="activity.approveNodeStatus === 1"
+ color="#67C23A"
+ :size="26">
+ <Check />
+ </el-icon>
+ <el-icon v-else
+ color="#C0C4CC"
+ :size="22">
+ <MoreFilled />
+ </el-icon>
+ </template>
<template #title>
<span style="color: #000000">{{ getNodeTitle(index, activities.length) }}</span>
</template>
<template #description>
<div class="node-user">
<div class="avatar-wrapper">
- <img :src="userStore.avatar" class="user-avatar" alt=""/>
+ <img :src="userStore.avatar"
+ class="user-avatar"
+ alt="" />
</div>
<span style="color: #000000">{{ activity.approveNodeUser }}-{{activity.isApproval}}</span>
</div>
- <div v-if="!activity.isShen" class="node-reason">
+ <div v-if="!activity.isShen"
+ class="node-reason">
<span>瀹℃壒鎰忚锛�</span>{{ activity.approveNodeReason }}
</div>
<div v-else-if="activity.isShen">
- <el-form-item
- :prop="'activities.' + index + '.approveNodeReason'"
- :rules="[{ required: true, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]"
- >
- <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input>
+ <el-form-item :prop="'activities.' + index + '.approveNodeReason'"
+ :rules="[{ required: true, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]">
+ <el-input v-model="activity.approveNodeReason"
+ clearable
+ type="textarea"
+ :disabled="operationType === 'view'"></el-input>
</el-form-item>
</div>
</template>
</el-step>
</el-steps>
</el-form>
- <template #footer v-if="operationType === 'approval'">
+ <template #footer
+ v-if="operationType === 'approval'">
<div class="dialog-footer">
- <el-button type="primary" @click="submitForm(2)">涓嶉�氳繃</el-button>
- <el-button type="primary" @click="submitForm(1)">閫氳繃</el-button>
+ <el-button type="primary"
+ @click="submitForm(2)">涓嶉�氳繃</el-button>
+ <el-button type="primary"
+ @click="submitForm(1)">閫氳繃</el-button>
<el-button @click="closeDia">鍙栨秷</el-button>
</div>
</template>
@@ -188,221 +306,329 @@
</template>
<script setup>
-import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue";
-import {
- approveProcessDetails,
- getDept,
- updateApproveNode
-} from "@/api/collaborativeApproval/approvalProcess.js";
-import useUserStore from "@/store/modules/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";
-const emit = defineEmits(['close'])
-const { proxy } = getCurrentInstance()
+ import {
+ computed,
+ getCurrentInstance,
+ nextTick,
+ reactive,
+ ref,
+ toRefs,
+ } from "vue";
+ import {
+ approveProcessDetails,
+ getDept,
+ updateApproveNode,
+ } from "@/api/collaborativeApproval/approvalProcess.js";
+ import useUserStore from "@/store/modules/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";
+ import { getDeliveryDetailByShippingNo } from "@/api/salesManagement/deliveryLedger.js";
+ import ImagePreview from "@/components/AttachmentPreview/image/index.vue";
+ const emit = defineEmits(["close"]);
+ const { proxy } = getCurrentInstance();
-const props = defineProps({
- approveType: {
- type: [Number, String],
- default: 0
- }
-})
-
-const dialogFormVisible = ref(false);
-const operationType = ref('')
-const activities = ref([])
-const formRef = ref(null);
-const userStore = useUserStore()
-const productOptions = ref([]);
-const quotationLoading = ref(false)
-const currentQuotation = ref({})
-const purchaseLoading = ref(false)
-const currentPurchase = ref({})
-const isQuotationApproval = computed(() => Number(props.approveType) === 6)
-const isPurchaseApproval = computed(() => Number(props.approveType) === 5)
-
-const data = reactive({
- form: {
- approveId: "",
- approveDeptId: "",
- approveReason: "",
- checkResult: "",
- },
-});
-const { form } = toRefs(data);
-
-// 鑺傜偣鏍囬
-const getNodeTitle = (index, len) => {
- if (index === len - 1) return '缁撴潫';
- return '瀹℃壒';
-};
-
-// 鑾峰彇褰撳墠婵�娲绘楠�
-const getActiveStep = () => {
- // 濡傛灉鎵�鏈� isShen 閮戒负 false锛岃繑鍥炴渶鍚庝竴涓楠わ紙鍏ㄩ儴瀹屾垚锛�
- const hasActive = activities.value.some(a => a.isShen === true);
- if (!hasActive) return activities.value.length;
- // 褰撳墠鑺傜偣绱㈠紩
- return activities.value.findIndex(a => a.isShen == true);
-};
-// 姝ラicon
-const getNodeIcon = (activity, index) => {
- if (activity.approveNodeStatus === 2) return 'el-icon-warning'; // 涓嶉�氳繃
- if (activity.isShen) return 'Edit';
- return '';
-};
-
-// 鎵撳紑寮规
-const openDialog = (type, row) => {
- operationType.value = type;
- dialogFormVisible.value = true;
- currentQuotation.value = {}
- currentPurchase.value = {}
- form.value = {...row}
- // 绔嬪嵆娓呴櫎琛ㄥ崟楠岃瘉鐘舵�侊紙鍥犱负瀛楁鏄痙isabled鐨勶紝涓嶉渶瑕侀獙璇侊級
- nextTick(() => {
- if (formRef.value) {
- formRef.value.clearValidate();
- }
- });
- // 纭繚閫夐」鍔犺浇瀹屾垚鍚庡啀鍖归厤鍊肩被鍨�
- getProductOptions().then(() => {
- // 纭繚鍊肩被鍨嬪尮閰嶏紙濡傛灉閫夐」宸插姞杞斤級
- if (productOptions.value.length > 0 && form.value.approveDeptId) {
- const matchedOption = productOptions.value.find(opt =>
- opt.deptId == form.value.approveDeptId ||
- String(opt.deptId) === String(form.value.approveDeptId)
- );
- if (matchedOption) {
- form.value.approveDeptId = matchedOption.deptId;
- }
- }
- // 鍐嶆娓呴櫎楠岃瘉锛岀‘淇濋�夐」鍔犺浇鍚庡�煎尮閰嶆纭�
- nextTick(() => {
- if (formRef.value) {
- formRef.value.clearValidate();
- }
- });
- });
-
- // 鎶ヤ环瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"鎶ヤ环鍗曞彿"鍘绘煡鎶ヤ环鍒楄〃
- if (isQuotationApproval.value) {
- const quotationNo = row?.approveReason;
- if (quotationNo) {
- quotationLoading.value = true
- getQuotationList({ quotationNo }).then((res) => {
- const records = res?.data?.records || []
- currentQuotation.value = records[0] || {}
- }).finally(() => {
- quotationLoading.value = false
- })
- }
- }
-
- // 閲囪喘瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"閲囪喘鍚堝悓鍙�"鍘绘煡閲囪喘璇︽儏
- if (isPurchaseApproval.value) {
- const purchaseContractNumber = row?.approveReason;
- if (purchaseContractNumber) {
- purchaseLoading.value = true
- getPurchaseByCode({ purchaseContractNumber }).then((res) => {
- currentPurchase.value = res
- }).catch((err) => {
- console.error('鏌ヨ閲囪喘璇︽儏澶辫触:', err)
- proxy.$modal.msgError('鏌ヨ閲囪喘璇︽儏澶辫触')
- }).finally(() => {
- purchaseLoading.value = false
- })
- }
- }
-
- approveProcessDetails(row.approveId).then((res) => {
- activities.value = res.data
- // 澧炲姞isApproval瀛楁
- activities.value.forEach(item => {
- if (item.url && item.url.includes('word')) {
- item.urlTem = item.url.replaceAll('word', 'img')
- } else {
- item.urlTem = item.url
- }
- if (item.approveNodeStatus === 2) {
- item.isApproval = '宸查┏鍥�';
- } else if (item.approveNodeStatus === 1) {
- item.isApproval = '宸插悓鎰�';
- } else {
- item.isApproval = '鏈鎵�';
- }
- })
- })
-}
-const getProductOptions = () => {
- return getDept().then((res) => {
- productOptions.value = res.data;
- });
-};
-// 鎻愪氦瀹℃壒
-const submitForm = (status) => {
- const filteredActivities = activities.value.filter(activity => activity.isShen);
- if (!filteredActivities || filteredActivities.length === 0) {
- proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
- return;
- }
- const currentActivity = filteredActivities[0];
- if (!currentActivity) {
- proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
- return;
- }
- currentActivity.approveNodeStatus = status;
- // 鍒ゆ柇鏄惁涓烘渶鍚庝竴姝�
- const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1;
- updateApproveNode({ ...currentActivity, isLast }).then(() => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
+ const props = defineProps({
+ approveType: {
+ type: [Number, String],
+ default: 0,
+ },
});
-};
-// 鍏抽棴寮规
-const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
- quotationLoading.value = false
- currentQuotation.value = {}
- purchaseLoading.value = false
- currentPurchase.value = {}
- emit('close')
-};
-defineExpose({
- openDialog,
-});
+
+ const dialogFormVisible = ref(false);
+ const operationType = ref("");
+ const activities = ref([]);
+ const formRef = ref(null);
+ const userStore = useUserStore();
+ const productOptions = ref([]);
+ const quotationLoading = ref(false);
+ const currentQuotation = ref({});
+ const purchaseLoading = ref(false);
+ const currentPurchase = ref({});
+ const deliveryLoading = ref(false);
+ const currentDelivery = ref({});
+ const deliveryProductList = ref([]);
+ const isQuotationApproval = computed(() => Number(props.approveType) === 6);
+ const isPurchaseApproval = computed(() => Number(props.approveType) === 5);
+ const isDeliveryApproval = computed(() => Number(props.approveType) === 7);
+
+ const data = reactive({
+ form: {
+ approveId: "",
+ approveDeptId: "",
+ approveReason: "",
+ checkResult: "",
+ },
+ });
+ const { form } = toRefs(data);
+
+ // 鑺傜偣鏍囬
+ const getNodeTitle = (index, len) => {
+ if (index === len - 1) return "缁撴潫";
+ return "瀹℃壒";
+ };
+
+ // 鑾峰彇褰撳墠婵�娲绘楠�
+ const getActiveStep = () => {
+ // 濡傛灉鎵�鏈� isShen 閮戒负 false锛岃繑鍥炴渶鍚庝竴涓楠わ紙鍏ㄩ儴瀹屾垚锛�
+ const hasActive = activities.value.some(a => a.isShen === true);
+ if (!hasActive) return activities.value.length;
+ // 褰撳墠鑺傜偣绱㈠紩
+ return activities.value.findIndex(a => a.isShen == true);
+ };
+ // 姝ラicon
+ const getNodeIcon = (activity, index) => {
+ if (activity.approveNodeStatus === 2) return "el-icon-warning"; // 涓嶉�氳繃
+ if (activity.isShen) return "Edit";
+ return "";
+ };
+
+ // 鎵撳紑寮规
+ const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ currentQuotation.value = {};
+ currentPurchase.value = {};
+ form.value = { ...row };
+ // 绔嬪嵆娓呴櫎琛ㄥ崟楠岃瘉鐘舵�侊紙鍥犱负瀛楁鏄痙isabled鐨勶紝涓嶉渶瑕侀獙璇侊級
+ nextTick(() => {
+ if (formRef.value) {
+ formRef.value.clearValidate();
+ }
+ });
+ // 纭繚閫夐」鍔犺浇瀹屾垚鍚庡啀鍖归厤鍊肩被鍨�
+ getProductOptions().then(() => {
+ // 纭繚鍊肩被鍨嬪尮閰嶏紙濡傛灉閫夐」宸插姞杞斤級
+ if (productOptions.value.length > 0 && form.value.approveDeptId) {
+ const matchedOption = productOptions.value.find(
+ opt =>
+ opt.deptId == form.value.approveDeptId ||
+ String(opt.deptId) === String(form.value.approveDeptId)
+ );
+ if (matchedOption) {
+ form.value.approveDeptId = matchedOption.deptId;
+ }
+ }
+ // 鍐嶆娓呴櫎楠岃瘉锛岀‘淇濋�夐」鍔犺浇鍚庡�煎尮閰嶆纭�
+ nextTick(() => {
+ if (formRef.value) {
+ formRef.value.clearValidate();
+ }
+ });
+ });
+
+ // 鎶ヤ环瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"鎶ヤ环鍗曞彿"鍘绘煡鎶ヤ环鍒楄〃
+ if (isQuotationApproval.value) {
+ const quotationNo = row?.approveReason;
+ if (quotationNo) {
+ quotationLoading.value = true;
+ getQuotationList({ quotationNo })
+ .then(res => {
+ const records = res?.data?.records || [];
+ currentQuotation.value = records[0] || {};
+ })
+ .finally(() => {
+ quotationLoading.value = false;
+ });
+ }
+ }
+
+ // 閲囪喘瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"閲囪喘鍚堝悓鍙�"鍘绘煡閲囪喘璇︽儏
+ if (isPurchaseApproval.value) {
+ const purchaseContractNumber = row?.approveReason;
+ if (purchaseContractNumber) {
+ purchaseLoading.value = true;
+ getPurchaseByCode({ purchaseContractNumber })
+ .then(res => {
+ currentPurchase.value = res;
+ })
+ .catch(err => {
+ console.error("鏌ヨ閲囪喘璇︽儏澶辫触:", err);
+ proxy.$modal.msgError("鏌ヨ閲囪喘璇︽儏澶辫触");
+ })
+ .finally(() => {
+ purchaseLoading.value = false;
+ });
+ }
+ }
+ // 鍙戣揣瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"鍙戣揣鍗曞彿"鍘绘煡鍙戣揣璇︽儏
+ if (isDeliveryApproval.value) {
+ const deliveryNo = row?.approveReason;
+ if (deliveryNo) {
+ deliveryLoading.value = true;
+ currentDelivery.value = {};
+ deliveryProductList.value = [];
+ getDeliveryDetailByShippingNo({ shippingNo: deliveryNo })
+ .then(res => {
+ const detailData = res?.data || res || {};
+ currentDelivery.value = detailData;
+ deliveryProductList.value =
+ detailData.shippingProductDetailDtoList || [];
+ })
+ .catch(err => {
+ console.error("鏌ヨ鍙戣揣璇︽儏澶辫触:", err);
+ proxy.$modal.msgError("鏌ヨ鍙戣揣璇︽儏澶辫触");
+ })
+ .finally(() => {
+ deliveryLoading.value = false;
+ });
+ }
+ }
+
+ approveProcessDetails(row.approveId).then(res => {
+ activities.value = res.data;
+ // 澧炲姞isApproval瀛楁
+ activities.value.forEach(item => {
+ if (item.url && item.url.includes("word")) {
+ item.urlTem = item.url.replaceAll("word", "img");
+ } else {
+ item.urlTem = item.url;
+ }
+ if (item.approveNodeStatus === 2) {
+ item.isApproval = "宸查┏鍥�";
+ } else if (item.approveNodeStatus === 1) {
+ item.isApproval = "宸插悓鎰�";
+ } else {
+ item.isApproval = "鏈鎵�";
+ }
+ });
+ });
+ };
+
+ const getDeliveryProductInfoList = () => {
+ const row = currentDelivery.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 = deliveryProductList.value.length
+ ? deliveryProductList.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 getApprovalStatusText = status => {
+ const statusMap = {
+ 0: "寰呭鏍�",
+ 1: "瀹℃牳閫氳繃",
+ 2: "瀹℃牳鎷掔粷",
+ 3: "瀹℃牳涓�",
+ };
+ return statusMap[status] || "寰呭鏍�";
+ };
+
+ const getProductOptions = () => {
+ return getDept().then(res => {
+ productOptions.value = res.data;
+ });
+ };
+ // 鎻愪氦瀹℃壒
+ const submitForm = status => {
+ const filteredActivities = activities.value.filter(
+ activity => activity.isShen
+ );
+ if (!filteredActivities || filteredActivities.length === 0) {
+ proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
+ return;
+ }
+ const currentActivity = filteredActivities[0];
+ if (!currentActivity) {
+ proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
+ return;
+ }
+ currentActivity.approveNodeStatus = status;
+ // 鍒ゆ柇鏄惁涓烘渶鍚庝竴姝�
+ const isLast =
+ activities.value.findIndex(a => a.isShen) === activities.value.length - 1;
+ updateApproveNode({ ...currentActivity, isLast }).then(() => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ });
+ };
+ // 鍏抽棴寮规
+ const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ quotationLoading.value = false;
+ currentQuotation.value = {};
+ purchaseLoading.value = false;
+ currentPurchase.value = {};
+ emit("close");
+ };
+ defineExpose({
+ openDialog,
+ });
</script>
<style scoped>
-
-.node-user {
- margin: 10px 0;
- font-size: 16px;
- font-weight: 600;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-.node-status {
- color: #1890ff;
- margin-left: 8px;
- font-size: 14px;
-}
-.node-reason {
- font-size: 15px;
- color: #333;
- margin: 10px 0;
-}
-.user-avatar {
- cursor: pointer;
- width: 30px;
- height: 30px;
- border-radius: 50px;
-}
-.signImg {
- cursor: pointer;
- width: 200px;
- height: 60px;
-}
+ .node-user {
+ margin: 10px 0;
+ font-size: 16px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ .node-status {
+ color: #1890ff;
+ margin-left: 8px;
+ font-size: 14px;
+ }
+ .node-reason {
+ font-size: 15px;
+ color: #333;
+ margin: 10px 0;
+ }
+ .user-avatar {
+ cursor: pointer;
+ width: 30px;
+ height: 30px;
+ border-radius: 50px;
+ }
+ .signImg {
+ cursor: pointer;
+ width: 200px;
+ height: 60px;
+ }
</style>
\ No newline at end of file
diff --git a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
index 6461b2d..5bd7a3e 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -152,7 +152,8 @@
startDate: "", // 璇峰亣寮�濮嬫椂闂�
endDate: "", // 璇峰亣缁撴潫鏃堕棿
price: null, // 鎶ラ攢閲戦
- location: "" // 鍑哄樊鍦扮偣
+ location: "", // 鍑哄樊鍦扮偣
+ storageBlobDTOS: []
},
rules: {
approveId: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
@@ -270,7 +271,7 @@
return
}
}
- form.value.storageBlobDTOList = fileList.value
+ form.value.storageBlobDTOS = fileList.value
proxy.$refs.formRef.validate(valid => {
if (valid) {
diff --git a/src/views/collaborativeApproval/approvalProcess/fileList.vue b/src/views/collaborativeApproval/approvalProcess/fileList.vue
index 498f474..1f1b671 100644
--- a/src/views/collaborativeApproval/approvalProcess/fileList.vue
+++ b/src/views/collaborativeApproval/approvalProcess/fileList.vue
@@ -32,8 +32,7 @@
tableData.value = list
}
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
-
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
const lookFile = (row) => {
filePreviewRef.value.open(row.url)
diff --git a/src/views/collaborativeApproval/approvalProcess/index.vue b/src/views/collaborativeApproval/approvalProcess/index.vue
index dba6bc1..11a2869 100644
--- a/src/views/collaborativeApproval/approvalProcess/index.vue
+++ b/src/views/collaborativeApproval/approvalProcess/index.vue
@@ -126,20 +126,23 @@
<!-- 寮圭獥缁勪欢 -->
<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" />
+ <FileList v-if="fileDialogVisible"
+ v-model:visible="fileDialogVisible"
+ record-type="approve_process"
+ :record-id="recordId" />
</div>
</template>
<script setup>
-import FileList from "./fileList.vue";
import { Search, Plus, Delete, Download, RefreshRight, DocumentChecked } from "@element-plus/icons-vue";
-import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue";
+import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance, defineAsyncComponent} from "vue";
import {ElMessageBox} from "element-plus";
import { useRoute } from 'vue-router';
import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user";
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
const userStore = useUserStore();
const route = useRoute();
@@ -337,7 +340,7 @@
name: "闄勪欢",
type: "text",
clickFun: (row) => {
- downLoadFile(row);
+ openFilesFormDia(row);
},
});
}
@@ -371,11 +374,17 @@
page.current = 1;
getList();
};
-const fileListRef = ref(null)
-const downLoadFile = (row) => {
- fileListRef.value.open(row.commonFileList)
+// 鎵撳紑闄勪欢寮圭獥
+const recordId =ref(0)
+const fileDialogVisible = ref(false)
+
+// 鎵撳紑闄勪欢寮规
+const openFilesFormDia = async (row) => {
+ recordId.value = row.id
+ fileDialogVisible.value = true
}
+
const pagination = (obj) => {
page.current = obj.page;
page.size = obj.limit;
diff --git a/src/views/collaborativeApproval/sealManagement/index.vue b/src/views/collaborativeApproval/sealManagement/index.vue
index a6232c2..9d68848 100644
--- a/src/views/collaborativeApproval/sealManagement/index.vue
+++ b/src/views/collaborativeApproval/sealManagement/index.vue
@@ -87,10 +87,18 @@
</el-form-item>
<el-form-item label="绱ф�ョ▼搴�" prop="urgency">
<el-radio-group v-model="sealForm.urgency">
- <el-radio label="normal">鏅��</el-radio>
- <el-radio label="urgent">绱ф��</el-radio>
- <el-radio label="very-urgent">鐗规��</el-radio>
+ <el-radio value="normal">鏅��</el-radio>
+ <el-radio value="urgent">绱ф��</el-radio>
+ <el-radio value="very-urgent">鐗规��</el-radio>
</el-radio-group>
+ </el-form-item>
+ <el-form-item label="闄勪欢涓婁紶">
+ <AttachmentUploadFile
+ v-model:fileList="sealForm.storageBlobDTOs"
+ :limit="10"
+ :fileSize="50"
+ buttonText="鐐瑰嚮涓婁紶闄勪欢"
+ />
</el-form-item>
</el-form>
</FormDialog>
@@ -119,8 +127,27 @@
</el-descriptions-item>
<el-descriptions-item label="鐢宠鍘熷洜" :span="2">{{ currentSealDetail.reason }}</el-descriptions-item>
</el-descriptions>
+ <!-- 闄勪欢鍒楄〃 -->
+ <div v-if="currentSealDetail.storageBlobVOList?.length || currentSealDetail.storageBlobDTOs?.length" class="attachment-section">
+ <div class="attachment-title">闄勪欢鍒楄〃锛�</div>
+ <el-table :data="currentSealDetail.storageBlobVOList || currentSealDetail.storageBlobDTOs" border class="attachment-table">
+ <el-table-column label="闄勪欢鍚嶇О" show-overflow-tooltip>
+ <template #default="scope">
+ {{ scope.row.originalFilename || scope.row.name || scope.row.fileName || '鏈懡鍚嶆枃浠�' }}
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="previewFile(scope.row)">棰勮</el-button>
+ <el-button link type="primary" size="small" @click="downloadFile(scope.row)">涓嬭浇</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
</div>
</FormDialog>
+ <!-- 鏂囦欢棰勮缁勪欢 -->
+ <FilePreview ref="filePreviewRef" />
</div>
</template>
@@ -134,6 +161,9 @@
import useUserStore from '@/store/modules/user'
import FormDialog from '@/components/Dialog/FormDialog.vue'
import PIMTable from '@/components/PIMTable/PIMTable.vue'
+import AttachmentUploadFile from '@/components/AttachmentUpload/file/index.vue'
+import FilePreview from '@/components/filePreview/index.vue'
+import download from '@/plugins/download.js'
// 鍝嶅簲寮忔暟鎹�
// 鐢ㄥ嵃鐢宠鐩稿叧
@@ -143,6 +173,7 @@
const tableLoading = ref(false)
const showSealDetailDialog = ref(false)
const currentSealDetail = ref(null)
+const filePreviewRef = ref(null)
const sealFormRef = ref()
const userList = ref([])
const sealForm = reactive({
@@ -152,7 +183,8 @@
reason: '',
approveUserId: '',
urgency: 'normal',
- status: 'pending'
+ status: 'pending',
+ storageBlobDTOs: []
})
const sealRules = {
@@ -281,7 +313,8 @@
reason: '',
approveUserId: '',
urgency: 'normal',
- status: 'pending'
+ status: 'pending',
+ storageBlobDTOs: []
})
}
}).catch(err => {
@@ -301,7 +334,8 @@
reason: '',
approveUserId: '',
urgency: 'normal',
- status: 'pending'
+ status: 'pending',
+ storageBlobDTOs: []
})
// 娓呴櫎琛ㄥ崟楠岃瘉鐘舵��
if (sealFormRef.value) {
@@ -318,6 +352,27 @@
const viewSealDetail = (row) => {
currentSealDetail.value = row
showSealDetailDialog.value = true
+}
+
+// 棰勮鏂囦欢
+const previewFile = (row) => {
+ const url = row.previewURL || row.previewUrl || row.url
+ if (url && filePreviewRef.value) {
+ filePreviewRef.value.open(url)
+ } else {
+ ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曢瑙�')
+ }
+}
+
+// 涓嬭浇鏂囦欢
+const downloadFile = (row) => {
+ const url = row.downloadURL || row.downloadUrl || row.url
+ if (url) {
+ const filename = row.originalFilename || row.name || row.fileName || 'download'
+ download.byUrl(url, filename)
+ } else {
+ ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曚笅杞�')
+ }
}
// 瀹℃壒鐢ㄥ嵃鐢宠
const approveSeal = (row) => {
@@ -421,4 +476,19 @@
.ml-10 {
margin-left: 10px;
}
+
+.attachment-section {
+ margin-top: 20px;
+}
+
+.attachment-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 10px;
+ font-weight: 500;
+}
+
+.attachment-table {
+ border-radius: 4px;
+}
</style>
diff --git a/src/views/collaborativeApproval/shipmentReview/fileList.vue b/src/views/collaborativeApproval/shipmentReview/fileList.vue
index da37db2..555312a 100644
--- a/src/views/collaborativeApproval/shipmentReview/fileList.vue
+++ b/src/views/collaborativeApproval/shipmentReview/fileList.vue
@@ -29,8 +29,7 @@
tableData.value = list
}
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
-
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
const lookFile = (row) => {
filePreviewRef.value.open(row.url)
diff --git a/src/views/customerService/expiryAfterSales/components/formDia.vue b/src/views/customerService/expiryAfterSales/components/formDia.vue
index 2fe603b..d899b8d 100644
--- a/src/views/customerService/expiryAfterSales/components/formDia.vue
+++ b/src/views/customerService/expiryAfterSales/components/formDia.vue
@@ -20,7 +20,7 @@
v-model="form.productName"
placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('productName')"
/>
</el-form-item>
</el-col>
@@ -30,7 +30,7 @@
v-model="form.batchNumber"
placeholder="璇疯緭鍏ヤ骇鍝佹壒鍙�"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('batchNumber')"
/>
</el-form-item>
</el-col>
@@ -46,7 +46,7 @@
type="date"
placeholder="璇烽�夋嫨涓存湡鏃ユ湡"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('expiryDate')"
/>
</el-form-item>
</el-col>
@@ -57,7 +57,7 @@
:min="0"
placeholder="璇疯緭鍏ュ簱瀛樻暟閲�"
style="width: 100%"
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('stockQuantity')"
/>
</el-form-item>
</el-col>
@@ -69,7 +69,7 @@
v-model="form.customerName"
placeholder="璇疯緭鍏ュ鎴峰悕绉�"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('customerName')"
/>
</el-form-item>
</el-col>
@@ -79,7 +79,7 @@
v-model="form.contactPhone"
placeholder="璇疯緭鍏ヨ仈绯荤數璇�"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('contactPhone')"
/>
</el-form-item>
</el-col>
@@ -91,7 +91,7 @@
v-model="form.problemDesc"
placeholder="璇疯緭鍏ラ棶棰樻弿杩�"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('problemDesc')"
type="textarea"
:rows="3"
/>
@@ -105,7 +105,7 @@
v-model="form.handlerId"
placeholder="璇烽�夋嫨澶勭悊浜�"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('handlerId')"
style="width: 100%"
>
<el-option
@@ -127,7 +127,7 @@
type="date"
placeholder="璇烽�夋嫨澶勭悊鏃ユ湡"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('handleDate')"
/>
</el-form-item>
</el-col>
@@ -139,7 +139,7 @@
v-model="form.handleResult"
placeholder="璇疯緭鍏ュ鐞嗙粨鏋�"
clearable
- :disabled="operationType === 'view'"
+ :disabled="isFieldDisabled('handleResult')"
type="textarea"
:rows="3"
/>
@@ -175,6 +175,8 @@
return '鏂板涓存湡鍞悗';
case 'edit':
return '缂栬緫涓存湡鍞悗';
+ case 'handle':
+ return '澶勭悊涓存湡鍞悗';
case 'view':
return '鏌ョ湅涓存湡鍞悗';
default:
@@ -212,6 +214,13 @@
})
const { form, rules } = toRefs(data);
const userList = ref([])
+const handleEditableFields = ["handlerId", "handleDate", "handleResult"];
+
+const isFieldDisabled = (field) => {
+ if (operationType.value === "view") return true;
+ if (operationType.value === "handle") return !handleEditableFields.includes(field);
+ return false;
+};
// 鎵撳紑寮规
const openDialog = (type, row) => {
@@ -242,7 +251,7 @@
} else {
// 缂栬緫鎴栨煡鐪嬫椂濉厖鏁版嵁
form.value = { ...row };
- if (type === 'edit' && !form.value.handlerId) {
+ if (type === 'handle' && !form.value.handlerId) {
form.value.handlerId = userStore.id;
form.value.handleDate = getCurrentDate();
}
@@ -250,36 +259,49 @@
}
const submitForm = () => {
+ if (operationType.value === "handle") {
+ if (!form.value.handlerId || !form.value.handleDate || !form.value.handleResult) {
+ proxy.$modal.msgWarning("璇峰~鍐欏鐞嗕汉銆佸鐞嗘棩鏈熷拰澶勭悊缁撴灉");
+ return;
+ }
+ handleSubmit();
+ return;
+ }
proxy.$refs["formRef"].validate(valid => {
if (valid) {
- const submitData = {
- id: form.value.id,
- productName: form.value.productName,
- batchNumber: form.value.batchNumber,
- expireDate: form.value.expiryDate,
- stockQuantity: form.value.stockQuantity,
- customerName: form.value.customerName,
- contactPhone: form.value.contactPhone,
- disRes: form.value.problemDesc,
- status: form.value.status,
- disposeUserId: form.value.handlerId,
- disposeNickName: userList.value.find(item => item.userId === form.value.handlerId)?.nickName,
- disposeResult: form.value.handleResult,
- disDate: form.value.handleDate
- };
-
- const apiCall = operationType.value === 'add' ? expiryAfterSalesAdd : expiryAfterSalesUpdate;
- apiCall(submitData).then(() => {
- proxy.$modal.msgSuccess(operationType.value === 'add' ? "鏂板鎴愬姛" : "鏇存柊鎴愬姛");
- closeDia();
- }).catch(error => {
- console.error('鎻愪氦鏁版嵁澶辫触:', error);
- proxy.$modal.msgError('鎻愪氦鏁版嵁澶辫触锛岃绋嶅悗閲嶈瘯');
- });
+ handleSubmit();
}
});
}
+const handleSubmit = () => {
+ const submitData = {
+ id: form.value.id,
+ productName: form.value.productName,
+ batchNumber: form.value.batchNumber,
+ expireDate: form.value.expiryDate,
+ stockQuantity: form.value.stockQuantity,
+ customerName: form.value.customerName,
+ contactPhone: form.value.contactPhone,
+ disRes: form.value.problemDesc,
+ status: operationType.value === "handle" ? 2 : form.value.status,
+ disposeUserId: form.value.handlerId,
+ disposeNickName: userList.value.find(item => item.userId === form.value.handlerId)?.nickName,
+ disposeResult: form.value.handleResult,
+ disDate: form.value.handleDate
+ };
+
+ const apiCall = operationType.value === 'add' ? expiryAfterSalesAdd : expiryAfterSalesUpdate;
+ apiCall(submitData).then(() => {
+ const successText = operationType.value === "add" ? "鏂板鎴愬姛" : operationType.value === "handle" ? "澶勭悊鎴愬姛" : "鏇存柊鎴愬姛";
+ proxy.$modal.msgSuccess(successText);
+ closeDia();
+ }).catch(error => {
+ console.error('鎻愪氦鏁版嵁澶辫触:', error);
+ proxy.$modal.msgError('鎻愪氦鏁版嵁澶辫触锛岃绋嶅悗閲嶈瘯');
+ });
+}
+
// 鍏抽棴寮规
const closeDia = () => {
proxy.resetForm("formRef");
diff --git a/src/views/customerService/expiryAfterSales/index.vue b/src/views/customerService/expiryAfterSales/index.vue
index c94e3dd..9966785 100644
--- a/src/views/customerService/expiryAfterSales/index.vue
+++ b/src/views/customerService/expiryAfterSales/index.vue
@@ -39,7 +39,7 @@
<el-button type="danger" @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
-
+
<div class="table_list">
<PIMTable
rowKey="id"
@@ -60,7 +60,7 @@
<template #operation="{ row }">
<el-button type="primary" link @click="openForm('view', row)">鏌ョ湅</el-button>
- <el-button type="primary" link @click="openForm('edit', row)" v-if="row.status === 1">缂栬緫</el-button>
+ <el-button type="primary" link @click="openForm('handle', row)" v-if="row.status === 1">澶勭悊</el-button>
</template>
</PIMTable>
</div>
@@ -201,7 +201,7 @@
current: page.value.current,
size: page.value.size
};
-
+
expiryAfterSalesListPage(queryParams).then(res => {
// 鏄犲皠鍚庣杩斿洖鏁版嵁鍒板墠绔〃鏍�
tableData.value = res.data.records.map(item => ({
diff --git a/src/views/customerService/feedbackRegistration/components/formDia.vue b/src/views/customerService/feedbackRegistration/components/formDia.vue
index 5c9e565..790ddbe 100644
--- a/src/views/customerService/feedbackRegistration/components/formDia.vue
+++ b/src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -1,490 +1,507 @@
<template>
<div>
- <el-dialog
- v-model="dialogFormVisible"
- title="鏂板鍞悗鍗�"
- width="90%"
- @close="closeDia"
- >
+ <el-dialog v-model="dialogFormVisible"
+ title="鏂板鍞悗鍗�"
+ width="90%"
+ @close="closeDia">
<div>
<span class="descriptions">鍩虹璧勬枡</span>
- <el-form
- :model="form"
- label-width="140px"
- label-position="top"
- :rules="rules"
- ref="formRef"
- >
+ <el-form :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef">
<el-row :gutter="30">
<el-col :span="4">
- <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
- <el-select
- v-model="form.customerName"
- filterable
- @change="customerNameChange"
- >
- <el-option
- v-for="item in customerNameOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
+ <el-form-item label="瀹㈡埛鍚嶇О锛�"
+ prop="customerName">
+ <el-select v-model="form.customerName"
+ filterable
+ @change="customerNameChange">
+ <el-option v-for="item in customerNameOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
- <el-form-item label="鍞悗绫诲瀷锛�" prop="serviceType">
- <el-select
- v-model="form.serviceType"
- filterable
- >
- <el-option
- v-for="dict in serviceTypeOptions"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
+ <el-form-item label="鍞悗绫诲瀷锛�"
+ prop="serviceType">
+ <el-select v-model="form.serviceType"
+ filterable>
+ <el-option v-for="dict in serviceTypeOptions"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
- <el-form-item label="鍏宠仈閿�鍞崟鍙凤細" prop="salesContractNo">
- <el-select
- v-model="form.salesContractNo"
- @change="associatedSalesOrderNumberChange"
- filterable
- >
- <el-option
- v-for="item in associatedSalesOrderNumberOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
+ <el-form-item label="鍏宠仈閿�鍞崟鍙凤細"
+ prop="salesContractNo">
+ <el-select v-model="form.salesContractNo"
+ @change="associatedSalesOrderNumberChange"
+ filterable>
+ <el-option v-for="item in associatedSalesOrderNumberOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
- <el-form-item label="绱ф�ョ▼搴︼細" prop="urgency">
- <el-select
- v-model="form.urgency"
- filterable
- >
- <el-option
- v-for="dict in urgencyOptions"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
+ <el-form-item label="绱ф�ョ▼搴︼細"
+ prop="urgency">
+ <el-select v-model="form.urgency"
+ filterable>
+ <el-option v-for="dict in urgencyOptions"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
- <el-form-item label="闂鎻忚堪锛�" prop="proDesc">
- <el-input
- v-model="form.proDesc"
- placeholder="璇疯緭鍏ラ棶棰樻弿杩�"
- />
+ <el-form-item label="闂鎻忚堪锛�"
+ prop="proDesc">
+ <el-input v-model="form.proDesc"
+ placeholder="璇疯緭鍏ラ棶棰樻弿杩�" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<hr>
- <div style="padding-top: 20px">
- <div style="display: flex; justify-content: space-between">
- <span class="descriptions">鍏宠仈浜у搧</span>
- <el-button
- type="primary"
- style="margin-right: 12px; margin-bottom: 10px"
- @click="isShowProductSelectDialog = true"
- >
+ <div style="padding-top: 20px">
+ <div style="display: flex; justify-content: space-between">
+ <span class="descriptions">鍏宠仈浜у搧</span>
+ <el-button type="primary"
+ style="margin-right: 12px; margin-bottom: 10px"
+ @click="isShowProductSelectDialog = true">
閫夋嫨浜у搧
</el-button>
- </div>
- <PIMTable
- :isShowPagination="false"
- rowKey="id"
- :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) }}
- </el-tag>
- </template>
- </PIMTable>
</div>
+ <PIMTable :isShowPagination="false"
+ rowKey="id"
+ :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) }}
+ </el-tag>
+ </template>
+ </PIMTable>
+ </div>
</div>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
- </div>
- </template>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
</el-dialog>
<!-- 閫夋嫨浜у搧寮圭獥 -->
- <ProductSelectDialog
- v-model="isShowProductSelectDialog"
- :products="currentSalesOrderProducts"
- :selected-ids="currentSelectedProductIds"
- @confirm="handleSelectProducts"
- />
+ <ProductSelectDialog v-model="isShowProductSelectDialog"
+ :products="currentSalesOrderProducts"
+ :selected-ids="currentSelectedProductIds"
+ @confirm="handleSelectProducts" />
</div>
</template>
<script setup>
-import { ref, reactive, toRefs, getCurrentInstance, computed } from "vue";
-import ProductSelectDialog from "./ProductSelectDialog.vue";
-import useUserStore from "@/store/modules/user.js";
-import {userListNoPageByTenantId} from "@/api/system/user.js";
-import {afterSalesServiceAdd, afterSalesServiceUpdate, getAllCustomerList, getSalesLedger } from "@/api/customerService/index.js";
-import { getCurrentDate } from "@/utils/index.js";
-const { proxy } = getCurrentInstance()
-const emit = defineEmits(['close'])
-const dialogFormVisible = ref(false);
-const operationType = ref('')
-const formRef = ref(null)
-const customerNameOptions = ref([])
-const userStore = useUserStore();
+ import { ref, reactive, toRefs, getCurrentInstance, computed } from "vue";
+ import ProductSelectDialog from "./ProductSelectDialog.vue";
+ import useUserStore from "@/store/modules/user.js";
+ import { userListNoPageByTenantId } from "@/api/system/user.js";
+ import {
+ afterSalesServiceAdd,
+ afterSalesServiceUpdate,
+ getAllCustomerList,
+ getSalesLedger,
+ } from "@/api/customerService/index.js";
+ import { getCurrentDate } from "@/utils/index.js";
+ const { proxy } = getCurrentInstance();
+ const emit = defineEmits(["close"]);
+ const dialogFormVisible = ref(false);
+ const operationType = ref("");
+ const formRef = ref(null);
+ const customerNameOptions = ref([]);
+ const userStore = useUserStore();
-const data = reactive({
- form: {
- topic: "",
- serviceType: "",
- urgency: "",
- salesLedgerId: null,
- productModelIds: "",
- customerId: null,
- salesContractNo: "",
- proDesc: "",
- customerName: ""
- },
- rules: {
- customerName: [{required: true, message: "璇烽�夋嫨瀹㈡埛鍚嶇О", trigger: "change"}],
- serviceType: [{required: true, message: "璇烽�夋嫨鍞悗绫诲瀷", trigger: "change"}],
- urgency: [{required: true, message: "璇烽�夋嫨绱ф�ョ▼搴�", trigger: "change"}],
- feedbackDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
- }
-})
-
-// 鑷畾涔夋牎楠屽嚱鏁帮細鍒ゆ柇鏄惁闇�瑕佹牎楠屽敭鍚庣紪鍙�
-
-const { form, rules } = toRefs(data);
-const userList = ref([])
-
-const formatCurrency = (val) => {
- if (val === null || val === undefined || val === '') return '-'
- const num = Number(val)
- return Number.isFinite(num) ? num.toFixed(2) : '-'
-}
-
-const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict(
- "post_sale_waiting_list",
- "degree_of_urgency"
-);
-
-const serviceTypeOptions = computed(() => post_sale_waiting_list?.value || []);
-const urgencyOptions = computed(() => degree_of_urgency?.value || []);
-
-const getProductRowId = (row) => {
- return row?.id ?? row?.productModelId ?? row?.modelId ?? `${row?.productCategory || row?.productName || ""}-${row?.specificationModel || row?.model || ""}-${row?.unit || ""}`
-}
-
-const normalizeProductRow = (row) => {
- return {
- ...row,
- id: getProductRowId(row),
- productCategory: row?.productCategory ?? row?.productName ?? '',
- specificationModel: row?.specificationModel ?? row?.model ?? '',
- unit: row?.unit ?? '',
- approveStatus: row?.approveStatus ?? null,
- shippingStatus: row?.shippingStatus ?? '',
- expressCompany: row?.expressCompany ?? '',
- expressNumber: row?.expressNumber ?? '',
- shippingCarNumber: row?.shippingCarNumber ?? '',
- shippingDate: row?.shippingDate ?? '',
- quantity: row?.quantity ?? 0,
- taxRate: row?.taxRate ?? 0,
- taxInclusiveUnitPrice: row?.taxInclusiveUnitPrice ?? 0,
- taxInclusiveTotalPrice: row?.taxInclusiveTotalPrice ?? 0,
- taxExclusiveTotalPrice: row?.taxExclusiveTotalPrice ?? 0,
- noQuantity: row?.noQuantity ?? 0,
- }
-}
-
-const tableColumn = ref([
- { label: "浜у搧澶х被", prop: "productCategory" },
- { label: "瑙勬牸鍨嬪彿", prop: "specificationModel" },
- { label: "鍗曚綅", prop: "unit" },
- {
- label: "浜у搧鐘舵��",
- prop: "approveStatus",
- width: 100,
- align: "center",
- dataType: "slot",
- slot: "approveStatus",
- },
- {
- label: "鍙戣揣鐘舵��",
- align: "center",
- width: 140,
- dataType: "slot",
- slot: "shippingStatus",
- },
- { label: "蹇�掑叕鍙�", prop: "expressCompany", width: 140 },
- { label: "蹇�掑崟鍙�", prop: "expressNumber", width: 160 },
- { label: "鍙戣揣杞︾墝", prop: "shippingCarNumber", minWidth: 100, align: "center" },
- { label: "鍙戣揣鏃ユ湡", prop: "shippingDate", minWidth: 100, align: "center" },
- { label: "鏁伴噺", prop: "quantity", width: 100 },
- { label: "绋庣巼(%)", prop: "taxRate", width: 100 },
- {
- label: "鍚◣鍗曚环(鍏�)",
- prop: "taxInclusiveUnitPrice",
- width: 160,
- formatData: formatCurrency,
- },
- {
- label: "鍚◣鎬讳环(鍏�)",
- prop: "taxInclusiveTotalPrice",
- width: 160,
- formatData: formatCurrency,
- },
- {
- label: "涓嶅惈绋庢�讳环(鍏�)",
- prop: "taxExclusiveTotalPrice",
- width: 160,
- formatData: formatCurrency,
- },
- {
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
- fixed: 'right',
- operation: [
- {
- name: "鍒犻櫎",
- type: "text",
- clickFun: (row) => {
- tableData.value = tableData.value.filter(i => getProductRowId(i) !== getProductRowId(row))
- },
-
- },
- ],
- },
-])
-const tableData = ref([])
-// 閫夋嫨浜у搧寮圭獥
-const isShowProductSelectDialog = ref(false)
-const handleSelectProducts = (rows) => {
- if (!Array.isArray(rows)) return
- const existingIds = new Set(tableData.value.map(i => String(getProductRowId(i))))
- const mapped = rows
- .map(normalizeProductRow)
- .filter(r => !existingIds.has(String(getProductRowId(r))))
- tableData.value = tableData.value.concat(mapped)
-}
-const currentSelectedProductIds = computed(() => {
- return tableData.value.map(item => getProductRowId(item)).filter(item => item !== undefined && item !== null && item !== '')
-})
-
-const associatedSalesOrderNumberChange = () => {
- const opt = associatedSalesOrderNumberOptions.value.find(
- (item) => item.value === form.value.salesContractNo
- )
- tableData.value = (opt?.productData || []).map(normalizeProductRow)
- form.value.salesLedgerId = opt?.id || null
-}
-
-const associatedSalesOrderNumberOptions = ref([])
-
-const currentSalesOrderProducts = computed(() => {
- const opt = associatedSalesOrderNumberOptions.value.find(
- (item) => item.value === form.value.salesContractNo
- )
- return (opt?.productData || []).map(normalizeProductRow)
-})
-
-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
- }).then(res => {
- if(res.code === 200){
- associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({
- label: item.salesContractNo,
- value: item.salesContractNo,
- productData:item.productData,
- id: item.id
- }))
- }
- })
-}
-
-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) => {
- if (!row) return '寰呭彂璐�'
- if (row.shippingDate || row.shippingCarNumber) {
- return '宸插彂璐�'
- }
- const status = row.shippingStatus
- if (status === null || status === undefined || status === '') {
- return '寰呭彂璐�'
- }
- const map = {
- '寰呭彂璐�': '寰呭彂璐�',
- '寰呭鏍�': '寰呭鏍�',
- '瀹℃牳涓�': '瀹℃牳涓�',
- '瀹℃牳鎷掔粷': '瀹℃牳鎷掔粷',
- '瀹℃牳閫氳繃': '瀹℃牳閫氳繃',
- '宸插彂璐�': '宸插彂璐�'
- }
- return map[String(status).trim()] || '寰呭彂璐�'
-}
-
-const getShippingStatusType = (row) => {
- if (!row) return 'info'
- if (row.shippingDate || row.shippingCarNumber) {
- return 'success'
- }
- const status = row.shippingStatus
- if (status === null || status === undefined || status === '') {
- return 'info'
- }
- const map = {
- '寰呭彂璐�': 'info',
- '寰呭鏍�': 'warning',
- '瀹℃牳涓�': 'warning',
- '瀹℃牳鎷掔粷': 'danger',
- '瀹℃牳閫氳繃': 'success',
- '宸插彂璐�': 'success'
- }
- return map[String(status).trim()] || 'info'
-}
-
-// 鎵撳紑寮规
-const openDialog =async (type, row) => {
- // 璇锋眰澶氫釜鎺ュ彛锛岃幏鍙栨暟鎹�
- let res = await getAllCustomerList({
- current: 1,
- size: 1000,
- total: 0,
+ const data = reactive({
+ form: {
+ topic: "",
+ serviceType: "",
+ urgency: "",
+ salesLedgerId: null,
+ productModelIds: "",
+ customerId: null,
+ salesContractNo: "",
+ proDesc: "",
+ customerName: "",
+ },
+ rules: {
+ customerName: [
+ { required: true, message: "璇烽�夋嫨瀹㈡埛鍚嶇О", trigger: "change" },
+ ],
+ serviceType: [
+ { required: true, message: "璇烽�夋嫨鍞悗绫诲瀷", trigger: "change" },
+ ],
+ urgency: [{ required: true, message: "璇烽�夋嫨绱ф�ョ▼搴�", trigger: "change" }],
+ feedbackDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
});
- if(res.records){
- customerNameOptions.value = res.records.map(item => ({
- label: item.customerName,
- value: item.customerName,
- id: item.id
- }));
- }
+ // 鑷畾涔夋牎楠屽嚱鏁帮細鍒ゆ柇鏄惁闇�瑕佹牎楠屽敭鍚庣紪鍙�
- operationType.value = type;
- dialogFormVisible.value = true;
- form.value = {}
- proxy.resetForm("formRef");
- form.value.checkUserId = userStore.id;
- form.value.feedbackDate = getCurrentDate();
- // 鏂板鏃舵竻绌哄凡閫夊叧鑱斾骇鍝�
- if (type === "add") {
- tableData.value = []
- }
- userListNoPageByTenantId().then((res) => {
- userList.value = res.data;
- });
- if (type === "edit") {
- form.value = {...row}
- if (form.value.customerName) {
- const res = await getSalesLedger({ customerName: form.value.customerName })
- if (res?.code === 200) {
- console.log(res)
- associatedSalesOrderNumberOptions.value = (res.data?.records || []).map(item => ({
+ const { form, rules } = toRefs(data);
+ const userList = ref([]);
+
+ const formatCurrency = val => {
+ if (val === null || val === undefined || val === "") return "-";
+ const num = Number(val);
+ return Number.isFinite(num) ? num.toFixed(2) : "-";
+ };
+
+ const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict(
+ "post_sale_waiting_list",
+ "degree_of_urgency"
+ );
+
+ const serviceTypeOptions = computed(() => post_sale_waiting_list?.value || []);
+ const urgencyOptions = computed(() => degree_of_urgency?.value || []);
+
+ const getProductRowId = row => {
+ return (
+ row?.id ??
+ row?.productModelId ??
+ row?.modelId ??
+ `${row?.productCategory || row?.productName || ""}-${
+ row?.specificationModel || row?.model || ""
+ }-${row?.unit || ""}`
+ );
+ };
+
+ const normalizeProductRow = row => {
+ return {
+ ...row,
+ id: getProductRowId(row),
+ productCategory: row?.productCategory ?? row?.productName ?? "",
+ specificationModel: row?.specificationModel ?? row?.model ?? "",
+ unit: row?.unit ?? "",
+ approveStatus: row?.approveStatus ?? null,
+ shippingStatus: row?.shippingStatus ?? "",
+ expressCompany: row?.expressCompany ?? "",
+ expressNumber: row?.expressNumber ?? "",
+ shippingCarNumber: row?.shippingCarNumber ?? "",
+ shippingDate: row?.shippingDate ?? "",
+ quantity: row?.quantity ?? 0,
+ taxRate: row?.taxRate ?? 0,
+ taxInclusiveUnitPrice: row?.taxInclusiveUnitPrice ?? 0,
+ taxInclusiveTotalPrice: row?.taxInclusiveTotalPrice ?? 0,
+ taxExclusiveTotalPrice: row?.taxExclusiveTotalPrice ?? 0,
+ noQuantity: row?.noQuantity ?? 0,
+ };
+ };
+
+ const tableColumn = ref([
+ { label: "浜у搧澶х被", prop: "productCategory" },
+ { label: "瑙勬牸鍨嬪彿", prop: "specificationModel" },
+ { label: "鍗曚綅", prop: "unit" },
+ {
+ label: "浜у搧鐘舵��",
+ prop: "approveStatus",
+ width: 100,
+ align: "center",
+ dataType: "slot",
+ slot: "approveStatus",
+ },
+ {
+ label: "鍙戣揣鐘舵��",
+ align: "center",
+ width: 140,
+ dataType: "slot",
+ slot: "shippingStatus",
+ },
+ { label: "蹇�掑叕鍙�", prop: "expressCompany", width: 140 },
+ { label: "蹇�掑崟鍙�", prop: "expressNumber", width: 160 },
+ {
+ label: "鍙戣揣杞︾墝",
+ prop: "shippingCarNumber",
+ minWidth: 100,
+ align: "center",
+ },
+ { label: "鍙戣揣鏃ユ湡", prop: "shippingDate", minWidth: 100, align: "center" },
+ { label: "鏁伴噺", prop: "quantity", width: 100 },
+ { label: "绋庣巼(%)", prop: "taxRate", width: 100 },
+ {
+ label: "鍚◣鍗曚环(鍏�)",
+ prop: "taxInclusiveUnitPrice",
+ width: 160,
+ formatData: formatCurrency,
+ },
+ {
+ label: "鍚◣鎬讳环(鍏�)",
+ prop: "taxInclusiveTotalPrice",
+ width: 160,
+ formatData: formatCurrency,
+ },
+ {
+ label: "涓嶅惈绋庢�讳环(鍏�)",
+ prop: "taxExclusiveTotalPrice",
+ width: 160,
+ formatData: formatCurrency,
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ operation: [
+ {
+ name: "鍒犻櫎",
+ type: "text",
+ clickFun: row => {
+ tableData.value = tableData.value.filter(
+ i => getProductRowId(i) !== getProductRowId(row)
+ );
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ // 閫夋嫨浜у搧寮圭獥
+ const isShowProductSelectDialog = ref(false);
+ const handleSelectProducts = rows => {
+ if (!Array.isArray(rows)) return;
+ const existingIds = new Set(
+ tableData.value.map(i => String(getProductRowId(i)))
+ );
+ const mapped = rows
+ .map(normalizeProductRow)
+ .filter(r => !existingIds.has(String(getProductRowId(r))));
+ tableData.value = tableData.value.concat(mapped);
+ };
+ const currentSelectedProductIds = computed(() => {
+ return tableData.value
+ .map(item => getProductRowId(item))
+ .filter(item => item !== undefined && item !== null && item !== "");
+ });
+
+ const associatedSalesOrderNumberChange = () => {
+ const opt = associatedSalesOrderNumberOptions.value.find(
+ item => item.value === form.value.salesContractNo
+ );
+ tableData.value = (opt?.productData || []).map(normalizeProductRow);
+ form.value.salesLedgerId = opt?.id || null;
+ };
+
+ const associatedSalesOrderNumberOptions = ref([]);
+
+ const currentSalesOrderProducts = computed(() => {
+ const opt = associatedSalesOrderNumberOptions.value.find(
+ item => item.value === form.value.salesContractNo
+ );
+ return (opt?.productData || []).map(normalizeProductRow);
+ });
+
+ 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,
+ }).then(res => {
+ if (res.code === 200) {
+ associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({
label: item.salesContractNo,
value: item.salesContractNo,
productData: item.productData,
- id: item.id
- }))
+ id: item.id,
+ }));
}
+ });
+ };
+
+ const getApproveStatusText = row => {
+ if (!row) return "涓嶈冻";
+ if (
+ row.approveStatus === 1 &&
+ (!row.shippingDate || !row.shippingCarNumber)
+ ) {
+ return "鍏呰冻";
}
- console.log(form.value)
- }
-}
-const submitForm = () => {
- proxy.$refs["formRef"].validate(valid => {
- if (valid) {
- // 鍖归厤浜у搧鍨嬪彿IDs
- form.value.productModelIds = tableData.value.map(item => item.id).join(",")
- if (operationType.value === "add") {
- afterSalesServiceAdd(form.value).then(response => {
- proxy.$modal.msgSuccess("鏂板鎴愬姛")
- closeDia()
- })
- } else {
- afterSalesServiceUpdate(form.value).then(response => {
- proxy.$modal.msgSuccess("淇敼鎴愬姛")
- closeDia()
- })
- }
- }
- })
-}
-// 鍏抽棴寮规
-const closeDia = () => {
- proxy.resetForm("formRef");
- dialogFormVisible.value = false;
- emit('close')
-};
-defineExpose({
- openDialog,
-});
+ if (row.approveStatus === 0 && (row.shippingDate || row.shippingCarNumber)) {
+ return "宸插嚭搴�";
+ }
+ return "涓嶈冻";
+ };
+
+ const getApproveStatusType = row => {
+ const statusText = getApproveStatusText(row);
+ return statusText === "涓嶈冻" ? "danger" : "success";
+ };
+
+ const getShippingStatusText = row => {
+ if (!row) return "寰呭彂璐�";
+ if (row.shippingDate || row.shippingCarNumber) {
+ return "宸插彂璐�";
+ }
+ const status = row.shippingStatus;
+ if (status === null || status === undefined || status === "") {
+ return "寰呭彂璐�";
+ }
+ const map = {
+ 寰呭彂璐�: "寰呭彂璐�",
+ 寰呭鏍�: "寰呭鏍�",
+ 瀹℃牳涓�: "瀹℃牳涓�",
+ 瀹℃牳鎷掔粷: "瀹℃牳鎷掔粷",
+ 瀹℃牳閫氳繃: "瀹℃牳閫氳繃",
+ 宸插彂璐�: "宸插彂璐�",
+ };
+ return map[String(status).trim()] || "寰呭彂璐�";
+ };
+
+ const getShippingStatusType = row => {
+ if (!row) return "info";
+ if (row.shippingDate || row.shippingCarNumber) {
+ return "success";
+ }
+ const status = row.shippingStatus;
+ if (status === null || status === undefined || status === "") {
+ return "info";
+ }
+ const map = {
+ 寰呭彂璐�: "info",
+ 寰呭鏍�: "warning",
+ 瀹℃牳涓�: "warning",
+ 瀹℃牳鎷掔粷: "danger",
+ 瀹℃牳閫氳繃: "success",
+ 宸插彂璐�: "success",
+ };
+ return map[String(status).trim()] || "info";
+ };
+
+ // 鎵撳紑寮规
+ const openDialog = async (type, row) => {
+ // 璇锋眰澶氫釜鎺ュ彛锛岃幏鍙栨暟鎹�
+ let res = await getAllCustomerList({
+ current: 1,
+ size: 1000,
+ total: 0,
+ });
+ console.log(res, "res");
+
+ if (res.data.records) {
+ customerNameOptions.value = res.data.records.map(item => ({
+ label: item.customerName,
+ value: item.customerName,
+ id: item.id,
+ }));
+ } else {
+ }
+
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ form.value = {};
+ proxy.resetForm("formRef");
+ form.value.checkUserId = userStore.id;
+ form.value.feedbackDate = getCurrentDate();
+ // 鏂板鏃舵竻绌哄凡閫夊叧鑱斾骇鍝�
+ if (type === "add") {
+ tableData.value = [];
+ }
+ userListNoPageByTenantId().then(res => {
+ userList.value = res.data;
+ });
+ if (type === "edit") {
+ form.value = { ...row };
+ if (form.value.customerName) {
+ const res = await getSalesLedger({
+ customerName: form.value.customerName,
+ });
+ if (res?.code === 200) {
+ console.log(res);
+ associatedSalesOrderNumberOptions.value = (res.data?.records || []).map(
+ item => ({
+ label: item.salesContractNo,
+ value: item.salesContractNo,
+ productData: item.productData,
+ id: item.id,
+ })
+ );
+ }
+ }
+ console.log(form.value);
+ }
+ };
+ const submitForm = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ // 鍖归厤浜у搧鍨嬪彿IDs
+ form.value.productModelIds = tableData.value
+ .map(item => item.id)
+ .join(",");
+ if (operationType.value === "add") {
+ afterSalesServiceAdd(form.value).then(response => {
+ proxy.$modal.msgSuccess("鏂板鎴愬姛");
+ closeDia();
+ });
+ } else {
+ afterSalesServiceUpdate(form.value).then(response => {
+ proxy.$modal.msgSuccess("淇敼鎴愬姛");
+ closeDia();
+ });
+ }
+ }
+ });
+ };
+ // 鍏抽棴寮规
+ const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ emit("close");
+ };
+ defineExpose({
+ openDialog,
+ });
</script>
<style scoped lang="scss">
-.descriptions {
- margin-bottom: 20px;
- display: inline-block;
- font-size: 1rem;
- font-weight: 600;
- padding-left: 12px;
- position: relative;
-}
+ .descriptions {
+ margin-bottom: 20px;
+ display: inline-block;
+ font-size: 1rem;
+ font-weight: 600;
+ padding-left: 12px;
+ position: relative;
+ }
-.descriptions::before {
- content: "";
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 4px;
- height: 1rem;
- background-color: #002FA7; /* Element 榛樿绾㈣壊 */
- border-radius: 2px;
-}
+ .descriptions::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 4px;
+ height: 1rem;
+ background-color: #002fa7; /* Element 榛樿绾㈣壊 */
+ border-radius: 2px;
+ }
</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
index 9f509b1..3e7f1ca 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -26,6 +26,21 @@
</el-row>
<el-row>
<el-col :span="12">
+ <el-form-item label="宸℃椤圭洰" prop="inspectionProject">
+ <el-input v-model="form.inspectionProject" placeholder="璇疯緭鍏ュ贰妫�椤圭洰" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏄惁鍚敤" prop="isEnabled">
+ <el-radio-group v-model="form.isEnabled">
+ <el-radio :value="1">鏄�</el-radio>
+ <el-radio :value="0">鍚�</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
<el-form-item label="澶囨敞" prop="remarks">
<el-input v-model="form.remarks" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
</el-form-item>
@@ -118,6 +133,8 @@
taskName: undefined,
inspector: '',
inspectorIds: '',
+ inspectionProject: '',
+ isEnabled: 1,
remarks: '',
frequencyType: '',
frequencyDetail: '',
@@ -245,6 +262,8 @@
taskName: undefined,
inspector: '',
inspectorIds: '',
+ inspectionProject: '',
+ isEnabled: 1,
remarks: '',
frequencyType: '',
frequencyDetail: '',
diff --git a/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
new file mode 100644
index 0000000..36256c8
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
@@ -0,0 +1,680 @@
+<template>
+ <FormDialog
+ v-model="dialogVisible"
+ title="涓婁紶宸℃璁板綍"
+ width="980px"
+ @close="handleClose"
+ @cancel="handleClose"
+ >
+ <main class="upload-content">
+ <el-card v-if="taskInfo" class="section-card">
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="宸℃浠诲姟鍚嶇О">
+ {{ taskInfo.taskName || "-" }}
+ </el-descriptions-item>
+ <el-descriptions-item label="宸℃椤圭洰">
+ {{ taskInfo.inspectionProject || "-" }}
+ </el-descriptions-item>
+ <el-descriptions-item label="澶囨敞">
+ {{ taskInfo.remarks || "-" }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <el-card class="section-card">
+ <h3>宸℃鐘舵��</h3>
+ <el-radio-group v-model="hasException">
+ <el-radio-button :value="false">姝e父</el-radio-button>
+ <el-radio-button :value="true">瀛樺湪寮傚父</el-radio-button>
+ </el-radio-group>
+ </el-card>
+
+ <el-card v-if="hasException === true" class="section-card">
+ <h3>寮傚父鎻忚堪</h3>
+ <el-input
+ v-model="abnormalDescription"
+ type="textarea"
+ maxlength="500"
+ show-word-limit
+ :rows="4"
+ placeholder="璇锋弿杩板紓甯告儏鍐�..."
+ />
+ </el-card>
+
+ <el-card v-if="hasException === true" class="section-card">
+ <el-tabs v-model="currentUploadType">
+ <el-tab-pane label="鐢熶骇鍓�" name="before" />
+ <el-tab-pane label="鐢熶骇涓�" name="after" />
+ <el-tab-pane label="鐢熶骇鍚�" name="issue" />
+ </el-tabs>
+
+ <div class="upload-buttons">
+ <el-upload
+ :show-file-list="false"
+ :http-request="uploadFile"
+ :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
+ accept="image/*"
+ >
+ <el-button type="primary" :loading="uploading">
+ <el-icon><Camera /></el-icon>
+ 閫夋嫨鍥剧墖
+ </el-button>
+ </el-upload>
+
+ <el-upload
+ :show-file-list="false"
+ :http-request="uploadFile"
+ :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
+ accept="video/*"
+ >
+ <el-button type="success" :loading="uploading">
+ <el-icon><VideoCamera /></el-icon>
+ 閫夋嫨瑙嗛
+ </el-button>
+ </el-upload>
+ </div>
+
+ <el-progress
+ v-if="uploading"
+ :percentage="uploadProgress"
+ class="upload-progress"
+ />
+
+ <div v-if="getCurrentFiles().length" class="file-list">
+ <div
+ v-for="(file, index) in getCurrentFiles()"
+ :key="file.uid || file.id || index"
+ class="file-item"
+ >
+ <div class="file-preview-container">
+ <el-image
+ v-if="file.type === 'image' || !file.type"
+ :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
+ fit="cover"
+ class="file-preview"
+ :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]"
+ preview-teleported
+ />
+
+ <div v-else class="video-preview" @click="previewVideo(file)">
+ <el-icon><VideoCamera /></el-icon>
+ <span>瑙嗛</span>
+ </div>
+
+ <el-button
+ class="delete-btn"
+ type="danger"
+ circle
+ size="small"
+ @click="removeFile(index)"
+ >
+ <el-icon><Close /></el-icon>
+ </el-button>
+ </div>
+
+ <div class="file-info">
+ <div class="file-name">
+ {{ file.bucketFilename || file.name || (file.type === "image" ? "鍥剧墖" : "瑙嗛") }}
+ </div>
+ <div class="file-size">{{ formatFileSize(file.size) }}</div>
+ </div>
+ </div>
+ </div>
+
+ <el-empty
+ v-else
+ :description="`璇烽�夋嫨瑕佷笂浼犵殑${getUploadTypeText()}鍥剧墖鎴栬棰慲"
+ />
+
+ <el-alert
+ class="upload-summary"
+ type="info"
+ :closable="false"
+ :title="`鐢熶骇鍓嶏細${beforeModelValue.length}涓枃浠� | 鐢熶骇涓細${afterModelValue.length}涓枃浠� | 鐢熶骇鍚庯細${issueModelValue.length}涓枃浠禶"
+ />
+ </el-card>
+
+ <el-result
+ v-if="hasException === false"
+ icon="success"
+ title="璁惧杩愯姝e父"
+ sub-title="鏃犻渶涓婁紶鐓х墖"
+ />
+ </main>
+
+ <template #footer>
+ <footer class="footer-buttons">
+ <el-button type="primary" @click="submitUpload">鎻愪氦</el-button>
+ <el-button v-if="hasException === true" type="warning" @click="goToRepair">
+ 鏂板鎶ヤ慨
+ </el-button>
+ <el-button @click="handleClose">鍙栨秷</el-button>
+ </footer>
+ </template>
+ </FormDialog>
+
+ <el-dialog
+ v-model="showVideoDialog"
+ :title="currentVideoFile?.originalFilename || currentVideoFile?.name || '瑙嗛棰勮'"
+ width="720px"
+ >
+ <video
+ v-if="currentVideoFile"
+ :src="currentVideoFile.url || currentVideoFile.downloadUrl"
+ class="video-player"
+ controls
+ autoplay
+ />
+ </el-dialog>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { useRouter } from "vue-router";
+import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
+import { Camera, Close, VideoCamera } from "@element-plus/icons-vue";
+import axios from "axios";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { uploadInspectionTask } from "@/api/inspectionManagement/index.js";
+import { getToken } from "@/utils/auth";
+
+const emit = defineEmits(["closeDia", "success"]);
+const router = useRouter();
+
+const dialogVisible = ref(false);
+const taskInfo = ref(null);
+const uploading = ref(false);
+const uploadProgress = ref(0);
+
+const beforeModelValue = ref([]);
+const afterModelValue = ref([]);
+const issueModelValue = ref([]);
+
+const currentUploadType = ref("before");
+const hasException = ref(null);
+const abnormalDescription = ref("");
+
+const showVideoDialog = ref(false);
+const currentVideoFile = ref(null);
+
+const uploadConfig = {
+ action: "/common/upload",
+ limit: 10,
+ fileSize: 50,
+ fileType: ["jpg", "jpeg", "png", "mp4", "mov"],
+};
+
+const uploadFileUrl = computed(
+ () => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}`
+);
+
+const processFileUrl = fileUrl => {
+ if (!fileUrl) return "";
+
+ let currentUrl = String(fileUrl);
+ if (currentUrl.includes("\\")) {
+ const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads");
+ if (uploadsIndex > -1) {
+ currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`;
+ } else {
+ const fileName = currentUrl.split("\\").pop();
+ currentUrl = `/uploads/${fileName}`;
+ }
+ }
+
+ if (currentUrl && !currentUrl.startsWith("http")) {
+ if (!currentUrl.startsWith("/")) {
+ currentUrl = `/${currentUrl}`;
+ }
+ currentUrl = __BASE_API__ + currentUrl;
+ }
+
+ return currentUrl;
+};
+
+const normalizeList = (list, fileType) => {
+ if (!Array.isArray(list)) return [];
+
+ return list.filter(Boolean).map(item => {
+ let currentType = item.type;
+ if (!currentType && item.contentType) {
+ currentType = item.contentType.startsWith("video") ? "video" : "image";
+ } else if (!currentType) {
+ currentType = fileType || "image";
+ }
+
+ return {
+ ...item,
+ url: processFileUrl(item.url || item.previewURL || item.downloadUrl || item.path || ""),
+ downloadUrl: processFileUrl(
+ item.downloadUrl || item.url || item.previewURL || item.path || ""
+ ),
+ name: item.name || item.originalFilename || item.bucketFilename,
+ tempId: item.tempId || item.id || item.tempFileId,
+ tempFileId: item.tempFileId || item.tempId || item.id,
+ size: item.size || item.byteSize || 0,
+ type: currentType,
+ status: "success",
+ uid: item.uid || `${Date.now()}-${Math.random()}`,
+ };
+ });
+};
+
+const resetState = () => {
+ taskInfo.value = null;
+ beforeModelValue.value = [];
+ afterModelValue.value = [];
+ issueModelValue.value = [];
+ currentUploadType.value = "before";
+ hasException.value = null;
+ abnormalDescription.value = "";
+ uploading.value = false;
+ uploadProgress.value = 0;
+ showVideoDialog.value = false;
+ currentVideoFile.value = null;
+};
+
+const openDialog = row => {
+ const raw = JSON.parse(JSON.stringify(row?.__raw || row || {}));
+ taskInfo.value = raw;
+
+ beforeModelValue.value = normalizeList(
+ raw.commonFileListBeforeVO || raw.commonFileListBefore || [],
+ "image"
+ );
+ afterModelValue.value = normalizeList(
+ raw.commonFileListVO || raw.commonFileList || [],
+ "image"
+ );
+ issueModelValue.value = normalizeList(
+ raw.commonFileListAfterVO || raw.commonFileListAfter || [],
+ "image"
+ );
+
+ abnormalDescription.value = raw.abnormalDescription || "";
+
+ if (raw.hasException !== undefined && raw.hasException !== null) {
+ hasException.value = raw.hasException;
+ } else if (raw.inspectionResult !== undefined && raw.inspectionResult !== null) {
+ hasException.value = String(raw.inspectionResult) === "0";
+ } else {
+ hasException.value = null;
+ }
+
+ if (
+ hasException.value !== true &&
+ (beforeModelValue.value.length || afterModelValue.value.length || issueModelValue.value.length)
+ ) {
+ hasException.value = true;
+ }
+
+ dialogVisible.value = true;
+};
+
+const handleClose = () => {
+ dialogVisible.value = false;
+ resetState();
+ emit("closeDia");
+};
+
+const getCurrentFiles = () => {
+ if (currentUploadType.value === "before") return beforeModelValue.value;
+ if (currentUploadType.value === "after") return afterModelValue.value;
+ if (currentUploadType.value === "issue") return issueModelValue.value;
+ return [];
+};
+
+const getUploadTypeText = () => {
+ if (currentUploadType.value === "before") return "鐢熶骇鍓�";
+ if (currentUploadType.value === "after") return "鐢熶骇涓�";
+ if (currentUploadType.value === "issue") return "鐢熶骇鍚�";
+ return "";
+};
+
+const getTabType = () => {
+ if (currentUploadType.value === "before") return 10;
+ if (currentUploadType.value === "after") return 11;
+ if (currentUploadType.value === "issue") return 12;
+ return 10;
+};
+
+const previewVideo = file => {
+ currentVideoFile.value = file;
+ showVideoDialog.value = true;
+};
+
+const uploadFile = async uploadRequest => {
+ const rawFile = uploadRequest.file;
+
+ if (getCurrentFiles().length >= uploadConfig.limit) {
+ ElMessage.warning(`鏈�澶氬彧鑳介�夋嫨${uploadConfig.limit}涓枃浠禶);
+ return;
+ }
+
+ const ext = rawFile.name.split(".").pop()?.toLowerCase();
+ if (!uploadConfig.fileType.includes(ext)) {
+ ElMessage.warning(`鏂囦欢鏍煎紡涓嶆敮鎸侊紝璇蜂笂浼� ${uploadConfig.fileType.join("/")} 鏍煎紡`);
+ return;
+ }
+
+ if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) {
+ ElMessage.warning(`鏂囦欢澶у皬涓嶈兘瓒呰繃 ${uploadConfig.fileSize}MB`);
+ return;
+ }
+
+ const token = getToken();
+ if (!token) {
+ ElMessage.warning("鐢ㄦ埛鏈櫥褰�");
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append("files", rawFile);
+ formData.append("type", getTabType());
+
+ uploading.value = true;
+ uploadProgress.value = 0;
+
+ try {
+ const { data } = await axios.post(uploadFileUrl.value, formData, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "multipart/form-data",
+ },
+ onUploadProgress: event => {
+ if (event.total) {
+ uploadProgress.value = Math.round((event.loaded / event.total) * 100);
+ }
+ },
+ });
+
+ if (data.code !== 200) {
+ ElMessage.error(data.msg || "涓婁紶澶辫触");
+ return;
+ }
+
+ const resultData = Array.isArray(data.data) ? data.data[0] : data.data;
+ const finalUrl = processFileUrl(
+ resultData.url || resultData.previewURL || resultData.downloadUrl || ""
+ );
+ const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename;
+ const finalId = resultData.tempId || resultData.id || resultData.tempFileId;
+
+ const uploadedFile = {
+ ...resultData,
+ url: finalUrl,
+ downloadUrl: finalUrl,
+ name: finalName,
+ tempId: finalId,
+ tempFileId: resultData.tempFileId || finalId,
+ size: rawFile.size || resultData.size || resultData.byteSize || 0,
+ type: rawFile.type?.startsWith("video") ? "video" : "image",
+ status: "success",
+ uid: `${Date.now()}-${Math.random()}`,
+ };
+
+ getCurrentFiles().push(uploadedFile);
+ ElMessage.success("涓婁紶鎴愬姛");
+ } catch (error) {
+ ElMessage.error(error?.message || "涓婁紶澶辫触");
+ } finally {
+ uploading.value = false;
+ }
+};
+
+const buildFileItem = item => ({
+ id: item?.id,
+ tempId: item?.tempId,
+ tempFileId: item?.tempFileId,
+ url: item?.downloadUrl || item?.url || "",
+ downloadUrl: item?.downloadUrl || item?.url || "",
+ name: item?.name,
+ bucketFilename: item?.bucketFilename || item?.name,
+ originalFilename: item?.originalFilename || item?.name,
+ size: item?.size || 0,
+ byteSize: item?.byteSize || item?.size || 0,
+ contentType: item?.contentType || "",
+ type: item?.type,
+});
+
+const submitUpload = async () => {
+ if (hasException.value === null) {
+ ElMessage.warning("璇烽�夋嫨宸℃鐘舵��");
+ return;
+ }
+
+ if (hasException.value === true) {
+ const totalFiles =
+ beforeModelValue.value.length +
+ afterModelValue.value.length +
+ issueModelValue.value.length;
+
+ if (!totalFiles) {
+ ElMessage.warning("璇蜂笂浼犲紓甯哥収鐗囨垨瑙嗛");
+ return;
+ }
+
+ if (!abnormalDescription.value.trim()) {
+ ElMessage.warning("璇峰~鍐欏紓甯告弿杩�");
+ return;
+ }
+ }
+
+ const loading = ElLoading.service({
+ text: "鎻愪氦涓�...",
+ background: "rgba(0, 0, 0, 0.3)",
+ });
+
+ try {
+ const allFiles = [
+ ...beforeModelValue.value,
+ ...afterModelValue.value,
+ ...issueModelValue.value,
+ ];
+
+ const tempFileIds = allFiles
+ .map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
+ .filter(Boolean);
+
+ const {
+ createTime,
+ updateTime,
+ storageBlobDTO,
+ commonFileListAfterVO,
+ commonFileListVO,
+ commonFileListBeforeVO,
+ commonFileListAfter,
+ commonFileList,
+ commonFileListBefore,
+ __raw,
+ ...baseTaskInfo
+ } = taskInfo.value || {};
+
+ const submitData = {
+ ...baseTaskInfo,
+ commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
+ commonFileListDTO: afterModelValue.value.map(buildFileItem),
+ commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
+ hasException: hasException.value,
+ inspectionResult: hasException.value ? 0 : 1,
+ abnormalDescription: abnormalDescription.value,
+ tempFileIds,
+ };
+
+ const result = await uploadInspectionTask(submitData);
+
+ if (result && (result.code === 200 || result.success)) {
+ ElMessage.success("鎻愪氦鎴愬姛");
+ dialogVisible.value = false;
+ resetState();
+ emit("success");
+ emit("closeDia");
+ } else {
+ ElMessage.error(result?.msg || result?.message || "鎻愪氦澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error(error?.message || "鎻愪氦澶辫触");
+ } finally {
+ loading.close();
+ }
+};
+
+const removeFile = async index => {
+ try {
+ await ElMessageBox.confirm("纭畾瑕佸垹闄よ繖涓枃浠跺悧锛�", "纭鍒犻櫎", {
+ type: "warning",
+ });
+ getCurrentFiles().splice(index, 1);
+ } catch {}
+};
+
+const goToRepair = () => {
+ const taskData = {
+ taskId: taskInfo.value?.taskId || taskInfo.value?.id,
+ taskName: taskInfo.value?.taskName,
+ inspectionLocation: taskInfo.value?.inspectionLocation,
+ inspector: taskInfo.value?.inspector,
+ hasException: hasException.value,
+ inspectionResult: hasException.value ? 0 : 1,
+ commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
+ commonFileListDTO: afterModelValue.value.map(buildFileItem),
+ commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
+ uploadedFiles: {
+ before: beforeModelValue.value,
+ after: afterModelValue.value,
+ issue: issueModelValue.value,
+ },
+ };
+
+ sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData));
+ router.push("/equipmentManagement/repair/add");
+};
+
+const formatFileSize = size => {
+ if (!size) return "0 B";
+
+ const units = ["B", "KB", "MB", "GB"];
+ let index = 0;
+ let fileSize = size;
+
+ while (fileSize >= 1024 && index < units.length - 1) {
+ fileSize /= 1024;
+ index += 1;
+ }
+
+ return `${fileSize.toFixed(2)} ${units[index]}`;
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+.inspection-upload-page {
+ min-height: 70vh;
+ background: #f5f7fa;
+ padding: 20px 20px 90px;
+ box-sizing: border-box;
+}
+
+.upload-content {
+ max-width: 960px;
+ margin: 20px auto 0;
+}
+
+.section-card {
+ margin-bottom: 16px;
+}
+
+.section-card h3 {
+ margin: 0 0 16px;
+ font-size: 16px;
+}
+
+.upload-buttons {
+ display: flex;
+ gap: 12px;
+ margin: 16px 0;
+}
+
+.upload-progress {
+ margin-bottom: 16px;
+}
+
+.file-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 12px;
+}
+
+.file-preview-container {
+ position: relative;
+ aspect-ratio: 1;
+ border-radius: 8px;
+ overflow: hidden;
+ background: #f2f3f5;
+}
+
+.file-preview {
+ width: 100%;
+ height: 100%;
+}
+
+.video-preview {
+ width: 100%;
+ height: 100%;
+ background: #303133;
+ color: #fff;
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.delete-btn {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+}
+
+.file-info {
+ margin-top: 6px;
+ font-size: 12px;
+}
+
+.file-name {
+ color: #606266;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.file-size {
+ color: #909399;
+ margin-top: 2px;
+}
+
+.upload-summary {
+ margin-top: 16px;
+}
+
+.footer-buttons {
+ position: sticky;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 14px 20px 0;
+ background: #f5f7fa;
+ display: flex;
+ justify-content: center;
+ gap: 12px;
+}
+
+.video-player {
+ width: 100%;
+ max-height: 70vh;
+ background: #000;
+}
+</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
index 66867e3..b5604fe 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,210 +1,226 @@
<template>
<div>
- <el-dialog title="鏌ョ湅闄勪欢"
- v-model="dialogVisitable" width="800px" @close="cancel">
+ <el-dialog title="鏌ョ湅闄勪欢" v-model="dialogVisitable" width="800px" @close="cancel">
<div class="upload-container">
- <!-- 鐢熶骇鍓� -->
<div class="form-container">
<div class="title">鐢熶骇鍓�</div>
-
- <!-- 鍥剧墖鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <img v-for="(item, index) in beforeProductionImgs" :key="index"
- @click="showMedia(beforeProductionImgs, index, 'image')"
- :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+
+ <div class="media-list">
+ <img
+ v-for="(item, index) in beforeProductionImgs"
+ :key="`before-img-${index}`"
+ :src="item"
+ alt=""
+ class="media-image"
+ @click="showMedia(beforeProductionImgs, index, 'image')"
+ />
</div>
-
- <!-- 瑙嗛鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
+
+ <div class="media-list">
<div
- v-for="(videoUrl, index) in beforeProductionVideos"
- :key="index"
- @click="showMedia(beforeProductionVideos, index, 'video')"
- style="position: relative; margin: 10px; cursor: pointer;"
+ v-for="(videoUrl, index) in beforeProductionVideos"
+ :key="`before-video-${index}`"
+ class="video-item"
+ @click="showMedia(beforeProductionVideos, index, 'video')"
>
- <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
- <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ <div class="video-thumb">
+ <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
</div>
- <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ <div class="video-text">鐐瑰嚮鎾斁</div>
</div>
</div>
</div>
-
- <!-- 鐢熶骇鍚� -->
+
+ <div class="form-container">
+ <div class="title">鐢熶骇涓�</div>
+
+ <div class="media-list">
+ <img
+ v-for="(item, index) in afterProductionImgs"
+ :key="`during-img-${index}`"
+ :src="item"
+ alt=""
+ class="media-image"
+ @click="showMedia(afterProductionImgs, index, 'image')"
+ />
+ </div>
+
+ <div class="media-list">
+ <div
+ v-for="(videoUrl, index) in afterProductionVideos"
+ :key="`during-video-${index}`"
+ class="video-item"
+ @click="showMedia(afterProductionVideos, index, 'video')"
+ >
+ <div class="video-thumb">
+ <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
+ </div>
+ <div class="video-text">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+
<div class="form-container">
<div class="title">鐢熶骇鍚�</div>
-
- <!-- 鍥剧墖鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <img v-for="(item, index) in afterProductionImgs" :key="index"
- @click="showMedia(afterProductionImgs, index, 'image')"
- :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+
+ <div class="media-list">
+ <img
+ v-for="(item, index) in productionIssuesImgs"
+ :key="`after-img-${index}`"
+ :src="item"
+ alt=""
+ class="media-image"
+ @click="showMedia(productionIssuesImgs, index, 'image')"
+ />
</div>
-
- <!-- 瑙嗛鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
+
+ <div class="media-list">
<div
- v-for="(videoUrl, index) in afterProductionVideos"
- :key="index"
- @click="showMedia(afterProductionVideos, index, 'video')"
- style="position: relative; margin: 10px; cursor: pointer;"
+ v-for="(videoUrl, index) in productionIssuesVideos"
+ :key="`after-video-${index}`"
+ class="video-item"
+ @click="showMedia(productionIssuesVideos, index, 'video')"
>
- <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
- <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ <div class="video-thumb">
+ <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
</div>
- <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
- </div>
- </div>
- </div>
-
- <!-- 鐢熶骇闂 -->
- <div class="form-container">
- <div class="title">鐢熶骇闂</div>
-
- <!-- 鍥剧墖鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <img v-for="(item, index) in productionIssuesImgs" :key="index"
- @click="showMedia(productionIssuesImgs, index, 'image')"
- :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
- </div>
-
- <!-- 瑙嗛鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <div
- v-for="(videoUrl, index) in productionIssuesVideos"
- :key="index"
- @click="showMedia(productionIssuesVideos, index, 'video')"
- style="position: relative; margin: 10px; cursor: pointer;"
- >
- <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
- <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
- </div>
- <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ <div class="video-text">鐐瑰嚮鎾斁</div>
</div>
</div>
</div>
</div>
</el-dialog>
-
- <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+
<div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
<div class="media-viewer-content" @click.stop>
- <!-- 鍥剧墖 -->
<vue-easy-lightbox
- v-if="mediaType === 'image'"
- :visible="isMediaViewerVisible"
- :imgs="mediaList"
- :index="currentMediaIndex"
- @hide="closeMediaViewer"
- ></vue-easy-lightbox>
-
- <!-- 瑙嗛 -->
- <div v-else-if="mediaType === 'video'" style="position: relative;">
- <video
- :src="mediaList[currentMediaIndex]"
- autoplay
- controls
- style="max-width: 90vw; max-height: 80vh;"
- />
+ v-if="mediaType === 'image'"
+ :visible="isMediaViewerVisible"
+ :imgs="mediaList"
+ :index="currentMediaIndex"
+ @hide="closeMediaViewer"
+ />
+
+ <div v-else-if="mediaType === 'video'" class="video-player-wrap">
+ <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" />
</div>
</div>
</div>
</div>
</template>
-<script setup>
-import { ref } from 'vue';
-import VueEasyLightbox from 'vue-easy-lightbox';
-const { proxy } = getCurrentInstance();
-// 鎺у埗寮圭獥鏄剧ず
+<script setup>
+import { ref } from "vue";
+import VueEasyLightbox from "vue-easy-lightbox";
+
const dialogVisitable = ref(false);
-// 鍥剧墖鏁扮粍
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
-// 瑙嗛鏁扮粍
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = ref([]);
-// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
-const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
-const mediaType = ref('image'); // image | video
+const mediaList = ref([]);
+const mediaType = ref("image");
-// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
-function processItems(items) {
+const processFileUrl = fileUrl => {
+ if (!fileUrl) return "";
+
+ let currentUrl = String(fileUrl);
+ if (currentUrl.includes("\\")) {
+ const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads");
+ if (uploadsIndex > -1) {
+ currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`;
+ } else {
+ const fileName = currentUrl.split("\\").pop();
+ currentUrl = `/uploads/${fileName}`;
+ }
+ }
+
+ if (currentUrl && !currentUrl.startsWith("http")) {
+ if (!currentUrl.startsWith("/")) {
+ currentUrl = `/${currentUrl}`;
+ }
+ currentUrl = __BASE_API__ + currentUrl;
+ }
+
+ return currentUrl;
+};
+
+const processItems = items => {
const images = [];
const videos = [];
-
- // 妫�鏌� items 鏄惁瀛樺湪涓斾负鏁扮粍
- if (!items || !Array.isArray(items)) {
+
+ if (!Array.isArray(items)) {
return { images, videos };
}
-
+
items.forEach(item => {
- if (!item || !item.previewURL || !item.contentType) return;
+ if (!item) return;
-
- // 澶勭悊鏂囦欢 URL
- const fileUrl = item.previewURL;
- const contentType = String(item.contentType).toLowerCase();
+ const fileUrl = processFileUrl(
+ item.previewURL || item.url || item.downloadUrl || item.path || ""
+ );
+ const contentType = String(item.contentType || "").toLowerCase();
- // 鏍规嵁 contentType 鍒ゆ柇鏄浘鐗囪繕鏄棰�
- if (contentType.startsWith('image/')) {
- images.push(fileUrl);
- } else if (contentType.startsWith('video/')) {
+ if (!fileUrl) return;
+
+ if (contentType.startsWith("video/")) {
videos.push(fileUrl);
+ return;
}
- });
-
- return { images, videos };
-}
-// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
-const openDialog = async (row) => {
- // 浣跨敤姝g‘鐨勫瓧娈靛悕锛歝ommonFileListBefore, commonFileListAfter
- const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBeforeVO || []);
- const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfterVO || []);
- const { images: issueImgs, videos: issueVids } = processItems(row.commonFileListVO || []);
-
+ images.push(fileUrl);
+ });
+
+ return { images, videos };
+};
+
+const openDialog = row => {
+ const { images: beforeImgs, videos: beforeVids } = processItems(
+ row.commonFileListBeforeVO || []
+ );
+ const { images: afterImgs, videos: afterVids } = processItems(
+ row.commonFileListVO || []
+ );
+ const { images: issueImgs, videos: issueVids } = processItems(
+ row.commonFileListAfterVO || []
+ );
+
beforeProductionImgs.value = beforeImgs;
beforeProductionVideos.value = beforeVids;
-
afterProductionImgs.value = afterImgs;
afterProductionVideos.value = afterVids;
-
productionIssuesImgs.value = issueImgs;
productionIssuesVideos.value = issueVids;
-
dialogVisitable.value = true;
};
-// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
-function showMedia(mediaArray, index, type) {
- mediaList.value = mediaArray;
+const showMedia = (items, index, type) => {
+ mediaList.value = items;
currentMediaIndex.value = index;
mediaType.value = type;
isMediaViewerVisible.value = true;
-}
+};
-// 鍏抽棴濯掍綋鏌ョ湅鍣�
-function closeMediaViewer() {
+const closeMediaViewer = () => {
isMediaViewerVisible.value = false;
mediaList.value = [];
- mediaType.value = 'image';
-}
+ mediaType.value = "image";
+};
-// 琛ㄥ崟鍏抽棴鏂规硶
const cancel = () => {
dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
+
<style scoped lang="scss">
.upload-container {
display: flex;
@@ -213,7 +229,7 @@
padding: 20px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
-
+
.form-container {
flex: 1;
width: 100%;
@@ -229,7 +245,7 @@
padding-left: 10px;
position: relative;
margin: 6px 0;
-
+
&::before {
content: "";
position: absolute;
@@ -241,12 +257,48 @@
}
}
+.media-list {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.media-image {
+ max-width: 100px;
+ height: 100px;
+ margin: 5px;
+ cursor: pointer;
+}
+
+.video-item {
+ position: relative;
+ margin: 10px;
+ cursor: pointer;
+}
+
+.video-thumb {
+ width: 160px;
+ height: 90px;
+ background-color: #333;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.video-icon {
+ width: 30px;
+ height: 30px;
+ opacity: 0.8;
+}
+
+.video-text {
+ text-align: center;
+ font-size: 12px;
+ color: #666;
+}
+
.media-viewer-overlay {
position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
+ inset: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 9999;
display: flex;
@@ -260,4 +312,13 @@
max-height: 90vh;
overflow: hidden;
}
-</style>
\ No newline at end of file
+
+.video-player-wrap {
+ position: relative;
+}
+
+.video-player {
+ max-width: 90vw;
+ max-height: 80vh;
+}
+</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index 35f82d5..fa531f1 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -70,12 +70,21 @@
class="no-data">--</span>
</div>
</template>
+ <template #isEnabled="{ row }">
+ <el-tag :type="row.isEnabled === 1 ? 'success' : 'danger'"
+ size="small">
+ {{ row.isEnabled == 1 ? '鏄�' : '鍚�' }}
+ </el-tag>
+ </template>
</PIMTable>
</div>
</el-card>
<form-dia ref="formDia"
@closeDia="handleQuery"></form-dia>
<view-files ref="viewFiles"></view-files>
+ <upload-files ref="uploadFiles"
+ @success="handleQuery"
+ @closeDia="handleQuery"></upload-files>
</div>
</template>
@@ -87,6 +96,7 @@
// 缁勪欢寮曞叆
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
+ import UploadFiles from "@/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue";
import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
// 鎺ュ彛寮曞叆
@@ -100,6 +110,7 @@
const { proxy } = getCurrentInstance();
const formDia = ref();
const viewFiles = ref();
+ const uploadFiles = ref();
// 鏌ヨ鍙傛暟
const queryParams = reactive({
@@ -126,8 +137,16 @@
// 鍒楅厤缃�
const columns = ref([
{ prop: "taskName", label: "宸℃浠诲姟鍚嶇О", minWidth: 160 },
+ { prop: "inspectionProject", label: "宸℃椤圭洰", minWidth: 150 },
{ prop: "remarks", label: "澶囨敞", minWidth: 150 },
{ prop: "inspector", label: "鎵ц宸℃浜�", minWidth: 150, slot: "inspector" },
+ {
+ prop: "isEnabled",
+ label: "鏄惁鍚敤",
+ minWidth: 100,
+ dataType: "slot",
+ slot: "isEnabled",
+ },
{
prop: "frequencyType",
label: "棰戞",
@@ -176,6 +195,19 @@
},
{ prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
{ prop: "createTime", label: "鐧昏鏃ユ湡", minWidth: 100 },
+ {
+ prop: "inspectionResult",
+ label: "宸℃缁撴灉",
+ minWidth: 100,
+ dataType: "tag",
+ formatData: val => {
+ return val == 1 ? "姝e父" : "寮傚父";
+ },
+ formatType: val => {
+ return val == 1 ? "success" : "danger";
+ },
+ },
+ { prop: "abnormalDescription", label: "寮傚父鎻忚堪", minWidth: 100 },
]);
// 鎿嶄綔鍒楅厤缃�
@@ -184,8 +216,9 @@
const operationConfig = {
label: "鎿嶄綔",
- width: 130,
+ width: operations.length > 1 ? 180 : 130,
fixed: "right",
+ align: 'center',
dataType: "action",
operation: operations
.map(op => {
@@ -194,6 +227,12 @@
return {
name: "缂栬緫",
clickFun: handleAdd,
+ color: "#409EFF",
+ };
+ case "upload":
+ return {
+ name: "涓婁紶",
+ clickFun: openUploadDialog,
color: "#409EFF",
};
case "viewFile":
@@ -226,12 +265,14 @@
];
operationsArr.value = ["edit"];
} else if (value === "task") {
- const operationColumn = getOperationColumn(["viewFile"]);
+ const operationColumn = getOperationColumn(["upload", "viewFile"]);
+ // 瀹氭椂浠诲姟璁板綍涓嶅睍绀�"鏄惁鍚敤"鍒�
+ const taskColumns = columns.value.filter(col => col.prop !== "isEnabled");
tableColumns.value = [
- ...columns.value,
+ ...taskColumns,
...(operationColumn ? [operationColumn] : []),
];
- operationsArr.value = ["viewFile"];
+ operationsArr.value = ["upload", "viewFile"];
}
pageNum.value = 1;
pageSize.value = 10;
@@ -273,6 +314,7 @@
// 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
tableData.value = rawData.map(item => {
const processedItem = { ...item };
+ processedItem.__raw = { ...item };
// 澶勭悊 inspector 瀛楁
if (processedItem.inspector) {
@@ -322,6 +364,12 @@
const viewFile = row => {
nextTick(() => {
viewFiles.value?.openDialog(row);
+ });
+ };
+
+ const openUploadDialog = row => {
+ nextTick(() => {
+ uploadFiles.value?.openDialog(row);
});
};
@@ -390,4 +438,4 @@
color: #909399;
font-size: 14px;
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index 7ba9401..93b9e6d 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -298,7 +298,34 @@
const showQRCode = async (row) => {
// 鐩存帴浣跨敤URL锛屼笉瑕佺敤JSON.stringify鍖呰
const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
- qrCodeUrl.value = await QRCode.toDataURL(qrContent);
+ const qrDataUrl = await QRCode.toDataURL(qrContent, { width: 200, margin: 2 });
+
+ // 鍒涘缓canvas鍚堟垚甯﹀悕绉扮殑浜岀淮鐮佸浘鐗�
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ const qrSize = 200;
+ const textHeight = 30;
+ const padding = 10;
+ canvas.width = qrSize + padding * 2;
+ canvas.height = qrSize + textHeight + padding * 2;
+
+ // 濉厖鐧借壊鑳屾櫙
+ ctx.fillStyle = '#ffffff';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // 缁樺埗浜岀淮鐮�
+ const qrImg = new Image();
+ qrImg.src = qrDataUrl;
+ await new Promise((resolve) => { qrImg.onload = resolve; });
+ ctx.drawImage(qrImg, padding, padding, qrSize, qrSize);
+
+ // 缁樺埗璁惧鍚嶇О
+ ctx.fillStyle = '#333333';
+ ctx.font = 'bold 14px Arial';
+ ctx.textAlign = 'center';
+ ctx.fillText(row.deviceName || '', canvas.width / 2, qrSize + padding + 20);
+
+ qrCodeUrl.value = canvas.toDataURL('image/png');
qrRowData.value = row;
qrDialogVisible.value = true;
};
diff --git a/src/views/equipmentManagement/measurementEquipment/components/formDia.vue b/src/views/equipmentManagement/measurementEquipment/components/formDia.vue
index 6b7feec..16ac41f 100644
--- a/src/views/equipmentManagement/measurementEquipment/components/formDia.vue
+++ b/src/views/equipmentManagement/measurementEquipment/components/formDia.vue
@@ -36,15 +36,6 @@
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="瀹夎浣嶇疆锛�" prop="instationLocation">
- <el-input
- v-model="form.instationLocation"
- placeholder="璇疯緭鍏�"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
<el-form-item label="妫�瀹氬崟浣嶏細" prop="unit">
<el-input
v-model="form.unit"
@@ -53,17 +44,17 @@
/>
</el-form-item>
</el-col>
- </el-row>
- <el-row :gutter="30">
- <el-col :span="12">
- <el-form-item label="璇佷功缂栧彿锛�" prop="model">
+ <el-col :span="12">
+ <el-form-item label="璇佷功缂栧彿锛�" prop="model">
<el-input
v-model="form.model"
placeholder="璇疯緭鍏�"
clearable
/>
</el-form-item>
- </el-col>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
<el-col :span="12">
<el-form-item label="鏈�鏂伴壌瀹氭棩鏈燂細" prop="mostDate">
<el-date-picker
@@ -77,8 +68,6 @@
/>
</el-form-item>
</el-col>
- </el-row>
- <el-row :gutter="30">
<el-col :span="12">
<el-form-item label="鏈夋晥鏃ユ湡(澶�)锛�" prop="valid">
<el-input
@@ -91,15 +80,6 @@
>
<template #append>鏃�</template>
</el-input>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="妫�瀹氬懆鏈燂細" prop="cycle">
- <el-input
- v-model="form.cycle"
- placeholder="璇疯緭鍏ユ瀹氬懆鏈�"
- clearable
- />
</el-form-item>
</el-col>
</el-row>
@@ -184,10 +164,8 @@
form: {
code: "",
name: "",
- instationLocation: "",
mostDate:"",
model: "",
- cycle:"",
validDate: "",
nextDate: "",
userId: "",
@@ -203,9 +181,7 @@
nextDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
userId: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
recordDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
- instationLocation: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
mostDate: [{required: true, message: "璇烽�夋嫨", trigger: "change"}],
- cycle: [{required: true, message: "璇烽�夋嫨", trigger: "blur"}],
valid: [
{required: true, message: "璇疯緭鍏�", trigger: "blur"},
{
diff --git a/src/views/equipmentManagement/measurementEquipment/filesDia.vue b/src/views/equipmentManagement/measurementEquipment/filesDia.vue
index ee9dc3a..16c026e 100644
--- a/src/views/equipmentManagement/measurementEquipment/filesDia.vue
+++ b/src/views/equipmentManagement/measurementEquipment/filesDia.vue
@@ -157,7 +157,7 @@
}
// 涓嬭浇闄勪欢
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
// 鍒犻櫎
const handleDelete = () => {
diff --git a/src/views/equipmentManagement/measurementEquipment/index.vue b/src/views/equipmentManagement/measurementEquipment/index.vue
index c1d5379..d2ec2d7 100644
--- a/src/views/equipmentManagement/measurementEquipment/index.vue
+++ b/src/views/equipmentManagement/measurementEquipment/index.vue
@@ -42,6 +42,7 @@
:tableLoading="tableLoading"
@pagination="pagination"
:dbRowClick="dbRowClick"
+ :rowClassName="rowClassName"
></PIMTable>
</div>
<form-dia ref="formDia" @close="handleQuery"></form-dia>
@@ -89,12 +90,6 @@
align: "center",
},
{
- label: "瀹夎浣嶇疆",
- prop: "instationLocation",
- width: 150,
- align:"center"
- },
- {
label: "妫�瀹氬崟浣�",
prop: "unit",
width: 200,
@@ -130,12 +125,6 @@
width: 130,
align:"center"
},
- {
- label: "妫�瀹氬懆鏈�(澶�)",
- prop: "cycle",
- width: 130,
- align:"center"
- },
{
label: "鐘舵��",
prop: "status",
@@ -193,6 +182,31 @@
const dbRowClick = (row)=>{
rowClickData.value?.openDialog(row)
+}
+
+// 琛屾牱寮忥細蹇埌鏈燂紙7澶╁唴锛夋垨閫炬湡鏍囩孩
+const rowClassName = ({ row }) => {
+ console.log('rowClassName called:', row);
+ // valid 鏄湁鏁堝ぉ鏁帮紝mostDate 鏄渶鏂版瀹氭棩鏈�
+ if (row.valid && row.mostDate) {
+ const mostDate = new Date(row.mostDate);
+ // 璁$畻鍒版湡鏃ユ湡 = 妫�瀹氭棩鏈� + 鏈夋晥澶╂暟
+ const validDays = parseInt(row.valid) || 0;
+ const expireDate = new Date(mostDate);
+ expireDate.setDate(expireDate.getDate() + validDays);
+
+ const now = new Date();
+ const diffDays = Math.ceil((expireDate - now) / (1000 * 60 * 60 * 24));
+ console.log('row:', row.code, 'validDays:', validDays, 'expireDate:', expireDate, 'diffDays:', diffDays);
+ // 7澶╁唴鍒版湡鎴栧凡閫炬湡閮芥爣绾�
+ if (diffDays <= 7) {
+ console.log('return warning-row');
+ return 'warning-row';
+ }
+ } else {
+ console.log('row missing valid or mostDate:', row.valid, row.mostDate);
+ }
+ return '';
}
// 琛ㄦ牸閫夋嫨鏁版嵁
@@ -294,5 +308,13 @@
</script>
<style scoped>
-
+:deep(.el-table .warning-row) {
+ background-color: #fef0f0 !important;
+}
+:deep(.el-table .warning-row:hover > td) {
+ background-color: #f9d5d5 !important;
+}
+:deep(.el-table .el-table__body tr.warning-row td) {
+ background-color: #fef0f0 !important;
+}
</style>
\ No newline at end of file
diff --git a/src/views/equipmentManagement/operationManagement/index.vue b/src/views/equipmentManagement/operationManagement/index.vue
index 008cc2c..99c3bd1 100644
--- a/src/views/equipmentManagement/operationManagement/index.vue
+++ b/src/views/equipmentManagement/operationManagement/index.vue
@@ -104,7 +104,7 @@
align="center"
>
<template #default="scope">
- {{ scope.row.runtimeDuration || '-' }}
+ {{ getRuntimeDurationDisplay(scope.row) }}
</template>
</el-table-column>
<el-table-column
@@ -154,7 +154,8 @@
</template>
<script setup>
-import { ref, onMounted, computed } from 'vue'
+import { ref, onMounted, onUnmounted, computed } from 'vue'
+import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import {
VideoPlay,
@@ -193,6 +194,98 @@
return filtered
})
+
+// 杩愯涓棤缁撴潫鏃堕棿鏃讹紝杩愯鏃堕暱闇�闅忓綋鍓嶆椂闂村彉鍖栵紝鐢� tick 瑙﹀彂妯℃澘閲嶇畻
+const runtimeDisplayTick = ref(0)
+
+/** 鍙栧悗绔彲鑳戒娇鐢ㄧ殑寮�濮�/缁撴潫鏃堕棿瀛楁 */
+const pickStartTime = (row) => row?.startRuntimeTime ?? row?.startTime ?? row?.start_time
+const pickEndTime = (row) => row?.endRuntimeTime ?? row?.endTime ?? row?.end_time
+
+/**
+ * 瑙f瀽鎺ュ彛/鍓嶇鍐欏叆鐨勫悇绫绘椂闂达細鏃堕棿鎴炽�両SO 瀛楃涓层�亂yyy-MM-dd HH:mm:ss銆丣ackson 鏁扮粍 [y,M,d,h,m,s]銆佸惈涓枃鐨� toLocaleString 绛�
+ */
+const parseDeviceTime = (input) => {
+ if (input === null || input === undefined || input === '') return null
+ if (typeof input === 'number' && !Number.isNaN(input)) {
+ const d = dayjs(input)
+ return d.isValid() ? d.toDate() : null
+ }
+ if (Array.isArray(input)) {
+ const [y, mo, day, h = 0, mi = 0, se = 0] = input
+ if (y == null || y === '') return null
+ const d = dayjs()
+ .year(Number(y))
+ .month(Number(mo || 1) - 1)
+ .date(Number(day || 1))
+ .hour(Number(h) || 0)
+ .minute(Number(mi) || 0)
+ .second(Number(se) || 0)
+ return d.isValid() ? d.toDate() : null
+ }
+ const s = String(input).trim()
+ if (!s || s === '-') return null
+ let d = dayjs(s)
+ if (d.isValid()) return d.toDate()
+ d = dayjs(s.replace(/-/g, '/'))
+ if (d.isValid()) return d.toDate()
+ d = dayjs(s.replace(/\//g, '-'))
+ if (d.isValid()) return d.toDate()
+ return null
+}
+
+const formatDurationMs = (durationMs) => {
+ if (durationMs == null || Number.isNaN(durationMs) || durationMs < 0) return '-'
+ const hours = Math.floor(durationMs / (1000 * 60 * 60))
+ const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60))
+ if (hours === 0 && minutes === 0) return '涓嶈冻1鍒嗛挓'
+ return `${hours}灏忔椂${minutes}鍒嗛挓`
+}
+
+const hasMeaningfulEnd = (endRaw) =>
+ endRaw !== null &&
+ endRaw !== undefined &&
+ String(endRaw).trim() !== '' &&
+ String(endRaw).trim() !== '-'
+
+const formatStoredDuration = (row) => {
+ const rd = row?.runtimeDuration
+ if (rd === null || rd === undefined) return ''
+ const t = String(rd).trim()
+ return t === '' || t === '-' ? '' : String(rd)
+}
+
+/** 杩愯涓細濮嬬粓鐢ㄣ�屽綋鍓嶆椂闂� - 寮�濮嬫椂闂淬�嶏紱宸插仠姝細浼樺厛鎺ュ彛 runtimeDuration锛屽惁鍒欑敤缁撴潫-寮�濮嬶紱鏃犵粨鏉熷彲鐪嬪凡瀛樻椂闀挎垨鍔ㄦ�佹帹绠� */
+const getRuntimeDurationDisplay = (row) => {
+ void runtimeDisplayTick.value
+ const start = parseDeviceTime(pickStartTime(row))
+ if (!start) {
+ return formatStoredDuration(row) || '-'
+ }
+
+ const statusStr = String(row?.status ?? '').trim()
+ const isRunning = statusStr === '杩愯涓�' || statusStr === '1'
+ const endRaw = pickEndTime(row)
+ const hasEnd = hasMeaningfulEnd(endRaw)
+
+ // 鏃犵粨鏉熸椂闂达細杩愯涓竴瀹氬姩鎬佺畻锛涘凡鍋滄鍒欎紭鍏堝睍绀哄悗绔凡瀛樻椂闀匡紝娌℃湁鍐嶆寜褰撳墠鏃堕棿鎺ㄧ畻
+ if (!hasEnd) {
+ if (isRunning) return formatDurationMs(Date.now() - start.getTime())
+ const stored = formatStoredDuration(row)
+ if (stored) return stored
+ return formatDurationMs(Date.now() - start.getTime())
+ }
+
+ if (isRunning) {
+ return formatDurationMs(Date.now() - start.getTime())
+ }
+
+ const end = parseDeviceTime(endRaw)
+ const stored = formatStoredDuration(row)
+ if (stored) return stored
+ if (end) return formatDurationMs(end.getTime() - start.getTime())
+ return '-'
+}
// 妫�鏌ヨ澶囨槸鍚﹁秴鏃舵湭鍚姩
const isOverdue = (device) => {
@@ -246,12 +339,11 @@
device.endRuntimeTime = currentTime
// 璁$畻杩愯鏃堕暱
if (device.startRuntimeTime) {
- const startTime = new Date(device.startRuntimeTime)
- const endTime = new Date(currentTime)
- const duration = endTime - startTime
- const hours = Math.floor(duration / (1000 * 60 * 60))
- const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60))
- device.runtimeDuration = `${hours}灏忔椂${minutes}鍒嗛挓`
+ const startTime = parseDeviceTime(device.startRuntimeTime)
+ const endTime = parseDeviceTime(currentTime)
+ if (startTime && endTime) {
+ device.runtimeDuration = formatDurationMs(endTime.getTime() - startTime.getTime())
+ }
}
}
const params = {
@@ -297,9 +389,31 @@
-// 缁勪欢鎸傝浇鏃跺垵濮嬪寲鏁版嵁
+const POLL_MS = 60 * 1000
+const RUNTIME_TICK_MS = 30 * 1000
+let listPollTimer = null
+let runtimeTickTimer = null
+
+// 缁勪欢鎸傝浇鏃舵媺鍙栨暟鎹紝骞舵瘡鍒嗛挓鍒锋柊涓�娆″垪琛紱杩愯涓椂闀挎瘡 30 绉掑埛鏂版樉绀�
onMounted(() => {
getList()
+ listPollTimer = setInterval(() => {
+ getList()
+ }, POLL_MS)
+ runtimeTickTimer = setInterval(() => {
+ runtimeDisplayTick.value++
+ }, RUNTIME_TICK_MS)
+})
+
+onUnmounted(() => {
+ if (listPollTimer != null) {
+ clearInterval(listPollTimer)
+ listPollTimer = null
+ }
+ if (runtimeTickTimer != null) {
+ clearInterval(runtimeTickTimer)
+ runtimeTickTimer = null
+ }
})
</script>
diff --git a/src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue b/src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue
new file mode 100644
index 0000000..6d61a9f
--- /dev/null
+++ b/src/views/equipmentManagement/repair/Modal/AcceptanceModal.vue
@@ -0,0 +1,144 @@
+<template>
+ <FormDialog
+ v-model="visible"
+ title="楠屾敹瀹℃壒"
+ width="500px"
+ @confirm="submitForm"
+ @cancel="handleCancel"
+ @close="handleCancel"
+ >
+ <el-form :model="form" :rules="rules" label-width="100px">
+ <el-form-item label="楠屾敹浜�" prop="acceptanceName">
+ <el-select
+ v-model="form.acceptanceName"
+ placeholder="璇烽�夋嫨楠屾敹浜�"
+ filterable
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in userList"
+ :key="item.userId"
+ :label="item.nickName"
+ :value="item.nickName"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="楠屾敹鏃堕棿" prop="acceptanceTime">
+ <el-date-picker
+ v-model="form.acceptanceTime"
+ type="datetime"
+ placeholder="璇烽�夋嫨楠屾敹鏃堕棿"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="楠屾敹澶囨敞" prop="acceptanceRemark">
+ <el-input
+ v-model="form.acceptanceRemark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ラ獙鏀跺娉�"
+ />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+</template>
+
+<script setup>
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { ref, reactive } from "vue";
+import { ElMessage } from "element-plus";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+import { repairAcceptance } from "@/api/equipmentManagement/repair";
+import dayjs from "dayjs";
+
+defineOptions({
+ name: "楠屾敹瀹℃壒寮圭獥",
+});
+
+const emits = defineEmits(["ok"]);
+
+const visible = ref(false);
+const loading = ref(false);
+const repairId = ref(null);
+const userList = ref([]);
+
+const form = reactive({
+ acceptanceName: undefined,
+ acceptanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+ acceptanceRemark: undefined,
+});
+
+const rules = {
+ acceptanceName: [
+ { required: true, message: "璇烽�夋嫨楠屾敹浜�", trigger: "change" },
+ ],
+ acceptanceTime: [
+ { required: true, message: "璇烽�夋嫨楠屾敹鏃堕棿", trigger: "change" },
+ ],
+ acceptanceRemark: [
+ { required: true, message: "璇疯緭鍏ラ獙鏀跺娉�", trigger: "blur" },
+ ],
+};
+
+// 鍔犺浇鐢ㄦ埛鍒楄〃
+const loadUserList = async () => {
+ const { data } = await userListNoPageByTenantId();
+ userList.value = data;
+};
+
+// 鎵撳紑寮圭獥
+const open = async (row) => {
+ repairId.value = row.id;
+ visible.value = true;
+ // 閲嶇疆琛ㄥ崟
+ form.acceptanceName = undefined;
+ form.acceptanceTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
+ form.acceptanceRemark = undefined;
+ await loadUserList();
+};
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = async () => {
+ if (!form.acceptanceName) {
+ ElMessage.warning("璇烽�夋嫨楠屾敹浜�");
+ return;
+ }
+ if (!form.acceptanceTime) {
+ ElMessage.warning("璇烽�夋嫨楠屾敹鏃堕棿");
+ return;
+ }
+ if (!form.acceptanceRemark) {
+ ElMessage.warning("璇疯緭鍏ラ獙鏀跺娉�");
+ return;
+ }
+
+ loading.value = true;
+ try {
+ const { code } = await repairAcceptance({
+ id: repairId.value,
+ acceptanceName: form.acceptanceName,
+ acceptanceTime: form.acceptanceTime,
+ acceptanceRemark: form.acceptanceRemark,
+ });
+ if (code === 200) {
+ ElMessage.success("楠屾敹閫氳繃");
+ visible.value = false;
+ emits("ok");
+ }
+ } finally {
+ loading.value = false;
+ }
+};
+
+const handleCancel = () => {
+ visible.value = false;
+};
+
+defineExpose({
+ open,
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 5e31943..4e73833 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -49,19 +49,44 @@
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="椤圭洰">
- <el-input v-model="form.machineryCategory" placeholder="璇疯緭鍏ラ」鐩�" />
+ <el-form-item label="鎶ヤ慨鎶ヤ慨椤圭洰">
+ <el-input v-model="form.machineryCategory" placeholder="璇疯緭鍏ユ姤淇姤淇」鐩�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="缁翠慨浜�">
+ <el-input v-model="form.maintenanceName" placeholder="璇疯緭鍏ョ淮淇汉濮撳悕" />
</el-form-item>
</el-col>
</el-row>
<el-row v-if="id">
<el-col :span="12">
<el-form-item label="鎶ヤ慨鐘舵��">
- <el-select v-model="form.status">
+ <el-select v-model="form.status" disabled>
<el-option label="寰呯淮淇�" :value="0"></el-option>
- <el-option label="瀹岀粨" :value="1"></el-option>
+ <el-option label="宸查獙鏀�" :value="1"></el-option>
<el-option label="澶辫触" :value="2"></el-option>
</el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <!-- 楠屾敹淇℃伅灞曠ず -->
+ <el-row v-if="id && form.status === 1">
+ <el-col :span="12">
+ <el-form-item label="楠屾敹浜�">
+ <el-input v-model="form.acceptanceName" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="楠屾敹鏃堕棿">
+ <el-input v-model="form.acceptanceTime" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="楠屾敹澶囨敞">
+ <el-input v-model="form.acceptanceRemark" type="textarea" :rows="2" disabled />
</el-form-item>
</el-col>
</el-row>
@@ -131,6 +156,7 @@
status: 0, // 鎶ヤ慨鐘舵��
machineryCategory: undefined,
storageBlobDTOs: [],
+ maintenanceName: undefined, // 缁翠慨浜�
});
const setDeviceModel = (deviceId) => {
@@ -148,6 +174,10 @@
form.status = data.status;
form.machineryCategory = data.machineryCategory;
form.storageBlobDTOs = data.storageBlobVOs || [];
+ form.maintenanceName = data.maintenanceName;
+ form.acceptanceName = data.acceptanceName;
+ form.acceptanceTime = data.acceptanceTime;
+ form.acceptanceRemark = data.acceptanceRemark;
};
const sendForm = async () => {
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index f3a4330..2835356 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -100,13 +100,14 @@
<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 === 3" type="info">寰呴獙鏀�</el-tag>
<el-tag v-if="row.status === 0" type="warning">寰呯淮淇�</el-tag>
</template>
<template #operation="{ row }">
<el-button
type="primary"
link
- :disabled="row.status === 1"
+ :disabled="row.status === 1 || row.status === 3"
@click="editRepair(row.id)"
>
缂栬緫
@@ -114,15 +115,23 @@
<el-button
type="success"
link
- :disabled="row.status === 1"
+ :disabled="row.status !== 0"
@click="addMaintain(row)"
>
缁翠慨
</el-button>
<el-button
+ type="warning"
+ link
+ :disabled="row.status !== 3"
+ @click="openAcceptance(row)"
+ >
+ 楠屾敹
+ </el-button>
+ <el-button
type="danger"
link
- :disabled="row.status === 1"
+ :disabled="row.status === 1 || row.status === 3"
@click="delRepairByIds(row.id)"
>
鍒犻櫎
@@ -139,6 +148,7 @@
</div>
<RepairModal ref="repairModalRef" @ok="getTableData"/>
<MaintainModal ref="maintainModalRef" @ok="getTableData"/>
+ <AcceptanceModal ref="acceptanceModalRef" @ok="getTableData"/>
<FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" :record-type="'device_repair'" :record-id="recordId" />
</div>
</template>
@@ -151,6 +161,7 @@
import {ElMessageBox, ElMessage} from "element-plus";
import dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
+import AcceptanceModal from "./Modal/AcceptanceModal.vue";
const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
defineOptions({
@@ -162,6 +173,7 @@
// 妯℃�佹瀹炰緥
const repairModalRef = ref();
const maintainModalRef = ref();
+const acceptanceModalRef = ref();
// 琛ㄦ牸澶氶�夋閫変腑椤�
const multipleList = ref([]);
@@ -197,7 +209,7 @@
prop: "deviceModel",
},
{
- label: "椤圭洰",
+ label: "鎶ヤ慨椤圭洰",
align: "center",
prop: "machineryCategory",
},
@@ -232,6 +244,17 @@
align: "center",
prop: "maintenanceTime",
formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
+ },
+ {
+ label: "楠屾敹浜�",
+ align: "center",
+ prop: "acceptanceName",
+ },
+ {
+ label: "楠屾敹鏃堕棿",
+ align: "center",
+ prop: "acceptanceTime",
+ formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : ""),
},
{
label: "鐘舵��",
@@ -301,6 +324,11 @@
maintainModalRef.value.open(row.id, row);
};
+// 鎵撳紑楠屾敹寮圭獥
+const openAcceptance = (row) => {
+ acceptanceModalRef.value.open(row);
+};
+
const changePage = ({page, limit}) => {
pagination.currentPage = page;
pagination.pageSize = limit;
diff --git a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
index e86b64a..0fcccb2 100644
--- a/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
+++ b/src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
@@ -1,199 +1,216 @@
<template>
- <FormDialog
- v-model="visible"
- :title="'璁惧淇濆吇'"
- width="500px"
- @confirm="sendForm"
- @cancel="handleCancel"
- @close="handleClose"
- >
- <el-form :model="form" label-width="100px">
+ <FormDialog v-model="visible"
+ :title="'璁惧淇濆吇'"
+ width="500px"
+ @confirm="sendForm"
+ @cancel="handleCancel"
+ @close="handleClose">
+ <el-form :model="form"
+ label-width="100px">
<el-form-item label="瀹為檯淇濆吇浜�">
- <el-input
- v-model="form.maintenanceActuallyName"
- placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
- ></el-input>
+ <el-input v-model="form.maintenanceActuallyName"
+ placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"></el-input>
</el-form-item>
<el-form-item label="瀹為檯淇濆吇鏃ユ湡">
- <el-date-picker
- v-model="form.maintenanceActuallyTime"
- placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- type="datetime"
- clearable
- style="width: 100%"
- />
+ <el-date-picker v-model="form.maintenanceActuallyTime"
+ placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ type="datetime"
+ clearable
+ style="width: 100%" />
</el-form-item>
<el-form-item label="淇濆吇鐘舵��">
<el-select v-model="form.status">
- <el-option label="寰呬繚鍏�" :value="0"></el-option>
- <el-option label="瀹岀粨" :value="1"></el-option>
- <el-option label="澶辫触" :value="2"></el-option>
+ <el-option label="寰呬繚鍏�"
+ :value="0"></el-option>
+ <el-option label="瀹岀粨"
+ :value="1"></el-option>
+ <el-option label="澶辫触"
+ :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="淇濆吇缁撴灉">
- <el-input
- v-model="form.maintenanceResult"
- placeholder="璇疯緭鍏ヤ繚鍏荤粨鏋�"
- type="text" />
+ <el-input v-model="form.maintenanceResult"
+ 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 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="棰嗙敤鏁伴噺">
+ <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 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;">
+ <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"
- />
+ <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-item label="闄勪欢">
+ <FileUpload v-model:file-list="form.storageBlobDTOs" />
</el-form-item>
</el-form>
</FormDialog>
</template>
<script setup>
-import FormDialog from "@/components/Dialog/FormDialog.vue";
-import { addMaintenance } from "@/api/equipmentManagement/upkeep";
-import useFormData from "@/hooks/useFormData";
-import dayjs from "dayjs";
-import useUserStore from "@/store/modules/user";
-import { ElMessage } from "element-plus";
-import {computed, ref} from "vue";
-import {getSparePartsList} from "@/api/equipmentManagement/spareParts.js";
+ import FormDialog from "@/components/Dialog/FormDialog.vue";
+ import FileUpload from "@/components/AttachmentUpload/file/index.vue";
+ import { addMaintenance } from "@/api/equipmentManagement/upkeep";
+ import useFormData from "@/hooks/useFormData";
+ import dayjs from "dayjs";
+ import useUserStore from "@/store/modules/user";
+ import { ElMessage } from "element-plus";
+ import { computed, ref, nextTick, getCurrentInstance } from "vue";
+ import { getSparePartsList } from "@/api/equipmentManagement/spareParts.js";
-defineOptions({
- name: "淇濆吇妯℃�佹",
-});
+ defineOptions({
+ name: "淇濆吇妯℃�佹",
+ });
-const emits = defineEmits(["ok"]);
+ const emits = defineEmits(["ok"]);
-// 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
-const planId = ref();
-const visible = ref(false);
-const loading = ref(false);
-const userStore = useUserStore();
+ const { proxy } = getCurrentInstance();
+ // 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
+ const planId = ref();
+ const visible = ref(false);
+ const loading = ref(false);
+ const userStore = useUserStore();
-const { form, resetForm } = useFormData({
- maintenanceActuallyName: undefined, // 瀹為檯淇濆吇浜�
- maintenanceActuallyTime: undefined, // 瀹為檯淇濆吇鏃ユ湡
- maintenanceResult: undefined, // 淇濆吇缁撴灉
- status: 0, // 淇濆吇鐘舵��
- sparePartsIds: [],
-});
+ const { form, resetForm } = useFormData({
+ maintenanceActuallyName: undefined, // 瀹為檯淇濆吇浜�
+ maintenanceActuallyTime: undefined, // 瀹為檯淇濆吇鏃ユ湡
+ maintenanceResult: undefined, // 淇濆吇缁撴灉
+ status: 0, // 淇濆吇鐘舵��
+ sparePartsIds: [],
+ storageBlobDTOs: [],
+ });
-const sparePartOptions = ref([])
-const loadingSparePartOptions = ref(true)
-const sparePartQtyMap = ref({})
+ 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 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) => {
- form.maintenanceActuallyName =
- data.maintenanceActuallyName ?? userStore.nickName;
- form.maintenanceActuallyTime =
- data.maintenanceActuallyTime
+ const setForm = data => {
+ form.maintenanceActuallyName =
+ data.maintenanceActuallyName ?? userStore.nickName;
+ form.maintenanceActuallyTime = data.maintenanceActuallyTime
? dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss")
: dayjs().format("YYYY-MM-DD HH:mm:ss");
- form.maintenanceResult = data.maintenanceResult;
- form.status = 1; // 榛樿鐘舵�佷负瀹岀粨
- // 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
+ 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 = [];
- }
-};
+ .map(s => Number(String(s).trim()))
+ .filter(v => Number.isFinite(v));
+ } else if (typeof data?.sparePartsIds === "number") {
+ form.sparePartsIds = [data.sparePartsIds];
+ } else {
+ form.sparePartsIds = [];
+ }
+ form.storageBlobDTOs = data.storageBlobVOs || [];
+ };
-/**
- * @desc 淇濆瓨淇濆吇
- */
-const sendForm = async () => {
- loading.value = true;
- try {
- // 棰嗙敤鏁伴噺鏍¢獙
- 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}锛塦);
+ /**
+ * @desc 淇濆瓨淇濆吇
+ */
+ const sendForm = async () => {
+ loading.value = true;
+ try {
+ // 棰嗙敤鏁伴噺鏍¢獙
+ 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(",")
+ 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 }))
+ 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 {
+ loading.value = false;
}
- const { code } = await addMaintenance(data);
- if (code == 200) {
- ElMessage.success("淇濆吇鎴愬姛");
- emits("ok");
- resetForm();
- sparePartQtyMap.value = {};
- visible.value = false;
- }
- } finally {
- loading.value = false;
- }
-};
+ };
-const fetchSparePartOptions = () => {
- loadingSparePartOptions.value = true;
- // 鍜屽浠剁鐞嗛〉涓�鑷达細/spareParts/listPage 鈫� res.data.records
- getSparePartsList({ current: 1, size: 1000 })
- .then((res) => {
+ 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 {
@@ -206,31 +223,31 @@
.finally(() => {
loadingSparePartOptions.value = false;
});
-}
+ };
-const handleCancel = () => {
- resetForm();
- sparePartQtyMap.value = {};
- visible.value = false;
-};
+ const handleCancel = () => {
+ resetForm();
+ sparePartQtyMap.value = {};
+ visible.value = false;
+ };
-const handleClose = () => {
- resetForm();
- sparePartQtyMap.value = {};
- visible.value = false;
-};
+ const handleClose = () => {
+ resetForm();
+ sparePartQtyMap.value = {};
+ visible.value = false;
+ };
-const open = async (id, row) => {
- planId.value = id; // 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
- visible.value = true;
- await nextTick();
- fetchSparePartOptions()
- setForm(row);
-};
+ const open = async (id, row) => {
+ planId.value = id; // 淇濆瓨璁″垝淇濆吇璁板綍鐨刬d
+ visible.value = true;
+ await nextTick();
+ fetchSparePartOptions();
+ setForm(row);
+ };
-defineExpose({
- open,
-});
+ defineExpose({
+ open,
+ });
</script>
<style lang="scss" scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
index ee59ce2..8a9cd98 100644
--- a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
+++ b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
@@ -32,10 +32,10 @@
disabled
/>
</el-form-item>
- <el-form-item label="椤圭洰">
+ <el-form-item label="淇濆吇椤圭洰">
<el-input
v-model="form.machineryCategory"
- placeholder="璇疯緭鍏ラ」鐩�"
+ placeholder="璇疯緭鍏ヤ繚鍏婚」鐩�"
/>
</el-form-item>
<el-form-item label="褰曞叆浜�">
@@ -61,6 +61,13 @@
<el-option label="瀹岀粨" :value="1"></el-option>
<el-option label="澶辫触" :value="2"></el-option>
</el-select>
+ </el-form-item>
+ <el-form-item label="淇濆吇浜�">
+ <el-input
+ v-model="form.maintenancePerson"
+ placeholder="璇疯緭鍏ヤ繚鍏讳汉濮撳悕"
+ clearable
+ />
</el-form-item>
<el-form-item label="璁″垝淇濆吇鏃ユ湡">
<el-date-picker
@@ -124,6 +131,7 @@
status: 0, //淇濅慨鐘舵��
machineryCategory: undefined,
storageBlobDTOs: [],
+ maintenancePerson: undefined, // 淇濆吇浜�
});
const setDeviceModel = (deviceId) => {
@@ -142,6 +150,7 @@
form.createUser = Number(data.createUser);
form.status = data.status;
form.machineryCategory = data.machineryCategory;
+ form.maintenancePerson = data.maintenancePerson;
if (data.maintenancePlanTime) {
form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
"YYYY-MM-DD HH:mm:ss"
diff --git a/src/views/equipmentManagement/upkeep/Form/formDia.vue b/src/views/equipmentManagement/upkeep/Form/formDia.vue
index 66bf067..6856ae1 100644
--- a/src/views/equipmentManagement/upkeep/Form/formDia.vue
+++ b/src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -67,6 +67,28 @@
</el-row>
<el-row>
<el-col :span="12">
+ <el-form-item label="淇濆吇椤圭洰" prop="machineryCategory">
+ <el-input
+ v-model.trim="form.machineryCategory"
+ placeholder="璇疯緭鍏ヤ繚鍏婚」鐩�"
+ maxlength="100"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="淇濆吇浜�" prop="maintenancePerson">
+ <el-input
+ v-model.trim="form.maintenancePerson"
+ placeholder="璇疯緭鍏ヤ繚鍏讳汉濮撳悕"
+ maxlength="100"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
<el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
<el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
<el-option label="姣忔棩" value="DAILY"/>
@@ -154,18 +176,21 @@
taskName: undefined,
// 褰曞叆浜猴細鍗曢�変竴涓敤鎴� id
inspector: undefined,
+ machineryCategory: "",
remarks: '',
frequencyType: '',
frequencyDetail: '',
week: '',
time: '',
deviceModel: undefined, // 瑙勬牸鍨嬪彿
- registrationDate: ''
+ registrationDate: '',
+ maintenancePerson: '' // 淇濆吇浜�
},
rules: {
taskId: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
inspector: [{ required: true, message: "璇烽�夋嫨褰曞叆浜�", trigger: "blur" },],
- registrationDate: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }]
+ registrationDate: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }],
+ machineryCategory: [{ required: true, message: "璇疯緭鍏ヤ繚鍏婚」鐩�", trigger: "blur" }]
}
})
const { form, rules } = toRefs(data)
@@ -238,14 +263,15 @@
taskId: undefined,
taskName: undefined,
inspector: undefined,
- inspector: undefined,
+ machineryCategory: "",
remarks: '',
frequencyType: '',
frequencyDetail: '',
week: '',
time: '',
deviceModel: undefined,
- registrationDate: ''
+ registrationDate: '',
+ maintenancePerson: ''
}
}
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 0ee99eb..ab7cc10 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -2,8 +2,8 @@
<div class="app-container">
<el-tabs v-model="activeTab"
@tab-change="handleTabChange">
- <!-- 瀹氭椂浠诲姟绠$悊tab -->
- <el-tab-pane label="瀹氭椂浠诲姟绠$悊"
+ <!-- 淇濆吇浠诲姟tab -->
+ <el-tab-pane label="淇濆吇浠诲姟"
name="scheduled">
<div class="search_form">
<el-form :model="scheduledFilters"
@@ -37,7 +37,7 @@
<div class="table_list">
<div class="actions">
<el-text class="mx-1"
- size="large">瀹氭椂浠诲姟绠$悊</el-text>
+ size="large">淇濆吇浠诲姟</el-text>
<div>
<el-button type="primary"
icon="Plus"
@@ -84,8 +84,8 @@
</PIMTable>
</div>
</el-tab-pane>
- <!-- 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛� -->
- <el-tab-pane label="浠诲姟璁板綍"
+ <!-- 淇濆吇璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛� -->
+ <el-tab-pane label="淇濆吇璁板綍"
name="record">
<div class="search_form">
<el-form :model="filters"
@@ -130,7 +130,7 @@
<div class="table_list">
<div class="actions">
<el-text class="mx-1"
- size="large">浠诲姟璁板綍</el-text>
+ size="large">淇濆吇璁板綍</el-text>
<div>
<el-button type="success"
icon="Van"
@@ -262,7 +262,7 @@
const fileDialogVisible = ref(false);
const currentMaintenanceTaskId = ref(null);
- // 浠诲姟璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
+ // 淇濆吇璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
const filters = reactive({
deviceName: "",
maintenancePlanTime: "",
@@ -278,7 +278,7 @@
});
const multipleList = ref([]);
- // 瀹氭椂浠诲姟绠$悊tab鐩稿叧鍙橀噺
+ // 淇濆吇浠诲姟tab鐩稿叧鍙橀噺
const scheduledFilters = reactive({
taskName: "",
status: "",
@@ -292,12 +292,18 @@
});
const scheduledMultipleList = ref([]);
- // 瀹氭椂浠诲姟绠$悊琛ㄦ牸鍒楅厤缃�
+ // 淇濆吇浠诲姟琛ㄦ牸鍒楅厤缃�
const scheduledColumns = ref([
{ prop: "taskName", label: "璁惧鍚嶇О" },
{
label: "瑙勬牸鍨嬪彿",
prop: "deviceModel",
+ },
+ {
+ label: "淇濆吇椤圭洰",
+ prop: "machineryCategory",
+ minWidth: 120,
+ formatData: cell => cell || "--",
},
{
prop: "frequencyType",
@@ -336,6 +342,7 @@
);
},
},
+ { prop: "maintenancePerson", label: "淇濆吇浜�", minWidth: 100 },
{ prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
{ prop: "registrationDate", label: "鐧昏鏃ユ湡", minWidth: 100 },
{
@@ -348,7 +355,7 @@
},
]);
- // 浠诲姟璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
+ // 淇濆吇璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
const columns = ref([
{
label: "璁惧鍚嶇О",
@@ -364,7 +371,9 @@
label: "璁″垝淇濆吇鏃ユ湡",
align: "center",
prop: "maintenancePlanTime",
- formatData: cell => dayjs(cell).format("YYYY-MM-DD"),
+ formatData: cell => {
+ return cell == null ? "-" : dayjs(cell).format("YYYY-MM-DD");
+ },
},
{
label: "褰曞叆浜�",
@@ -372,9 +381,10 @@
prop: "createUserName",
},
{
- label: "椤圭洰",
+ label: "淇濆吇椤圭洰",
align: "center",
prop: "machineryCategory",
+ formatData: cell => cell || "--",
},
// {
// label: "褰曞叆鏃ユ湡",
@@ -428,7 +438,7 @@
}
};
- // 瀹氭椂浠诲姟绠$悊鐩稿叧鏂规硶
+ // 淇濆吇浠诲姟鐩稿叧鏂规硶
const getScheduledTableData = async () => {
try {
const params = {
@@ -495,7 +505,7 @@
ElMessage.info("瀵煎嚭瀹氭椂浠诲姟鍔熻兘寰呭疄鐜�");
};
- // 浠诲姟璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
+ // 淇濆吇璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
const getTableData = async () => {
try {
const params = {
diff --git a/src/views/financialManagement/assets/fixedAssets.vue b/src/views/financialManagement/assets/fixedAssets.vue
index 61eb245..24b4cc3 100644
--- a/src/views/financialManagement/assets/fixedAssets.vue
+++ b/src/views/financialManagement/assets/fixedAssets.vue
@@ -38,11 +38,12 @@
<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>
+ <!-- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button> -->
</div>
</div>
<PIMTable
rowKey="id"
+ isSelection
:column="columns"
:tableData="dataList"
:page="{
@@ -50,6 +51,7 @@
size: pagination.pageSize,
total: pagination.total,
}"
+ @selection-change="handleSelectionChange"
@pagination="changePage"
>
<template #originalValue="{ row }">
@@ -76,7 +78,7 @@
</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-form :model="form" :rules="rules" :disabled="isView" ref="formRef" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="璧勪骇缂栧彿" prop="assetCode">
@@ -178,7 +180,7 @@
</el-form-item>
</el-form>
<template #footer>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button v-if="!isView" type="primary" @click="submitForm">纭畾</el-button>
<el-button @click="dialogVisible = false">鍙栨秷</el-button>
</template>
</FormDialog>
@@ -189,6 +191,13 @@
import { ref, reactive, onMounted, computed } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+ listFixedAssetPage,
+ addFixedAsset,
+ updateFixedAsset,
+ deleteFixedAsset,
+ depreciateFixedAsset,
+} from "@/api/financialManagement/fixedAsset";
defineOptions({
name: "鍥哄畾璧勪骇",
@@ -210,23 +219,30 @@
const columns = [
{ label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
{ label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
- { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+ { label: "璧勪骇绫诲埆", prop: "category", dataType: "slot", 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" },
+ { label: "璧勪骇鍘熷��", prop: "originalValue", dataType: "slot", slot: "originalValue" },
+ { label: "绱鎶樻棫", prop: "accumulatedDepreciation", dataType: "slot", slot: "accumulatedDepreciation" },
+ { label: "璧勪骇鍑�鍊�", prop: "netValue", dataType: "slot", slot: "netValue" },
+ { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "180", fixed: "right" },
];
const dataList = ref([]);
+const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
const isEdit = ref(false);
+const isView = ref(false);
const currentId = ref(null);
+const selectedIds = computed(() =>
+ multipleList.value
+ .map(item => item?.id)
+ .filter(id => id !== undefined && id !== null && id !== "")
+);
-const form = reactive({
+const createDefaultForm = () => ({
assetCode: "",
assetName: "",
category: "",
@@ -244,6 +260,10 @@
remark: "",
});
+const form = reactive({
+ ...createDefaultForm(),
+});
+
const rules = {
assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
@@ -251,13 +271,6 @@
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);
@@ -288,35 +301,44 @@
};
const getStatusLabel = (status) => {
- const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", scrapped: "鎶ュ簾" };
- return map[status] || status;
+ const key = String(status || "").toLowerCase();
+ const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", repair: "缁翠慨涓�", scrapped: "鎶ュ簾" };
+ return map[key] || status;
};
const getStatusType = (status) => {
- const map = { in_use: "success", idle: "warning", scrapped: "info" };
- return map[status] || "";
+ const key = String(status || "").toLowerCase();
+ const map = { in_use: "success", idle: "warning", repair: "warning", scrapped: "info" };
+ return map[key] || "";
};
const calculateNetValue = () => {
- form.netValue = Number((form.originalValue - form.accumulatedDepreciation).toFixed(2));
+ const originalValue = Number(form.originalValue || 0);
+ const accumulatedDepreciation = Number(form.accumulatedDepreciation || 0);
+ form.netValue = Number((originalValue - accumulatedDepreciation).toFixed(2));
};
-const getTableData = () => {
- let result = [...mockData];
- if (filters.assetCode) {
- result = result.filter(item => item.assetCode.includes(filters.assetCode));
+// 鑱旇皟绾﹀畾锛氬垎椤靛弬鏁板浐瀹氫负 current/size锛岃繑鍥� data.records/data.total
+const getTableData = async () => {
+ try {
+ const { data } = await listFixedAssetPage({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ assetCode: filters.assetCode,
+ assetName: filters.assetName,
+ category: filters.category,
+ status: filters.status,
+ });
+ dataList.value = data?.records || [];
+ multipleList.value = [];
+ pagination.total = Number(data?.total || 0);
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
- 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 handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
};
const resetFilters = () => {
@@ -334,39 +356,32 @@
getTableData();
};
+const buildAssetCode = () => `GD${Date.now().toString().slice(-10)}`;
+
const add = () => {
isEdit.value = false;
+ isView.value = false;
+ currentId.value = null;
dialogTitle.value = "鏂板鍥哄畾璧勪骇";
- Object.assign(form, {
- assetCode: "GD" + Date.now().toString().slice(-8),
- assetName: "",
- category: "",
- specification: "",
+ Object.assign(form, createDefaultForm(), {
+ assetCode: buildAssetCode(),
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;
+ isView.value = false;
currentId.value = row.id;
dialogTitle.value = "缂栬緫鍥哄畾璧勪骇";
- Object.assign(form, row);
+ Object.assign(form, createDefaultForm(), row);
dialogVisible.value = true;
};
const view = (row) => {
- ElMessage.info(`鏌ョ湅璧勪骇: ${row.assetName}`);
+ edit(row);
+ isView.value = true;
};
const handleDelete = (row) => {
@@ -374,31 +389,30 @@
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData.splice(index, 1);
+ }).then(async () => {
+ // 鑱旇皟绾﹀畾锛氬垹闄ゆ帴鍙d娇鐢� ids=1&ids=2
+ await deleteFixedAsset([row.id]);
+ if (dataList.value.length === 1 && pagination.currentPage > 1) {
+ pagination.currentPage -= 1;
}
ElMessage.success("鍒犻櫎鎴愬姛");
- getTableData();
+ await getTableData();
});
};
const handleDepreciation = () => {
- ElMessageBox.confirm("纭杩涜鏈湀鎶樻棫璁℃彁鍚楋紵", "鎻愮ず", {
+ const ids = selectedIds.value;
+ const confirmText = ids.length
+ ? `纭瀵归�変腑鐨� ${ids.length} 鏉¤祫浜ц繘琛屾湰鏈堟姌鏃ц鎻愬悧锛焋
+ : "纭杩涜鏈湀鎶樻棫璁℃彁鍚楋紵";
+ ElMessageBox.confirm(confirmText, "鎻愮ず", {
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));
- }
- });
+ }).then(async () => {
+ await depreciateFixedAsset({ ids });
ElMessage.success("鎶樻棫璁℃彁瀹屾垚");
- getTableData();
+ await getTableData();
});
};
@@ -407,22 +421,28 @@
};
const submitForm = () => {
- formRef.value.validate((valid) => {
+ if (isView.value) {
+ dialogVisible.value = false;
+ return;
+ }
+ formRef.value.validate(async valid => {
if (valid) {
- calculateNetValue();
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...form };
+ try {
+ calculateNetValue();
+ const payload = { ...form };
+ if (isEdit.value) {
+ payload.id = currentId.value;
+ await updateFixedAsset(payload);
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ await addFixedAsset(payload);
+ ElMessage.success("鏂板鎴愬姛");
}
- 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;
+ await getTableData();
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
- dialogVisible.value = false;
- getTableData();
}
});
};
diff --git a/src/views/financialManagement/assets/intangibleAssets.vue b/src/views/financialManagement/assets/intangibleAssets.vue
index 14dae55..4642166 100644
--- a/src/views/financialManagement/assets/intangibleAssets.vue
+++ b/src/views/financialManagement/assets/intangibleAssets.vue
@@ -39,11 +39,12 @@
<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>
+ <!-- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button> -->
</div>
</div>
<PIMTable
rowKey="id"
+ isSelection
:column="columns"
:tableData="dataList"
:page="{
@@ -51,6 +52,7 @@
size: pagination.pageSize,
total: pagination.total,
}"
+ @selection-change="handleSelectionChange"
@pagination="changePage"
>
<template #originalValue="{ row }">
@@ -77,7 +79,7 @@
</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-form :model="form" :rules="rules" :disabled="isView" ref="formRef" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="璧勪骇缂栧彿" prop="assetCode">
@@ -171,7 +173,7 @@
</el-form-item>
</el-form>
<template #footer>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
+ <el-button v-if="!isView" type="primary" @click="submitForm">纭畾</el-button>
<el-button @click="dialogVisible = false">鍙栨秷</el-button>
</template>
</FormDialog>
@@ -182,6 +184,13 @@
import { ref, reactive, onMounted, computed } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+ listIntangibleAssetPage,
+ addIntangibleAsset,
+ updateIntangibleAsset,
+ deleteIntangibleAsset,
+ amortizeIntangibleAsset,
+} from "@/api/financialManagement/intangibleAsset";
defineOptions({
name: "鏃犲舰璧勪骇",
@@ -203,23 +212,30 @@
const columns = [
{ label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
{ label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
- { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+ { label: "璧勪骇绫诲埆", prop: "category", dataType: "slot", 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" },
+ { label: "璧勪骇鍘熷��", prop: "originalValue", dataType: "slot", slot: "originalValue" },
+ { label: "绱鎽婇攢", prop: "accumulatedAmortization", dataType: "slot", slot: "accumulatedAmortization" },
+ { label: "璧勪骇鍑�鍊�", prop: "netValue", dataType: "slot", slot: "netValue" },
+ { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "180", fixed: "right" },
];
const dataList = ref([]);
+const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
const isEdit = ref(false);
+const isView = ref(false);
const currentId = ref(null);
+const selectedIds = computed(() =>
+ multipleList.value
+ .map(item => item?.id)
+ .filter(id => id !== undefined && id !== null && id !== "")
+);
-const form = reactive({
+const createDefaultForm = () => ({
assetCode: "",
assetName: "",
category: "",
@@ -236,6 +252,10 @@
remark: "",
});
+const form = reactive({
+ ...createDefaultForm(),
+});
+
const rules = {
assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
@@ -243,13 +263,6 @@
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);
@@ -281,35 +294,49 @@
};
const getStatusLabel = (status) => {
- const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", amortized: "宸叉憡閿�瀹屾瘯" };
- return map[status] || status;
+ const key = String(status || "").toLowerCase();
+ const map = {
+ in_use: "鍦ㄧ敤",
+ idle: "闂茬疆",
+ expired: "宸插埌鏈�",
+ amortized: "宸叉憡閿�瀹屾瘯",
+ };
+ return map[key] || status;
};
const getStatusType = (status) => {
- const map = { in_use: "success", idle: "warning", amortized: "info" };
- return map[status] || "";
+ const key = String(status || "").toLowerCase();
+ const map = { in_use: "success", idle: "warning", expired: "warning", amortized: "info" };
+ return map[key] || "";
};
const calculateNetValue = () => {
- form.netValue = Number((form.originalValue - form.accumulatedAmortization).toFixed(2));
+ const originalValue = Number(form.originalValue || 0);
+ const accumulatedAmortization = Number(form.accumulatedAmortization || 0);
+ form.netValue = Number((originalValue - accumulatedAmortization).toFixed(2));
};
-const getTableData = () => {
- let result = [...mockData];
- if (filters.assetCode) {
- result = result.filter(item => item.assetCode.includes(filters.assetCode));
+// 鑱旇皟绾﹀畾锛氬垎椤靛弬鏁板浐瀹氫负 current/size锛岃繑鍥� data.records/data.total
+const getTableData = async () => {
+ try {
+ const { data } = await listIntangibleAssetPage({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ assetCode: filters.assetCode,
+ assetName: filters.assetName,
+ category: filters.category,
+ status: filters.status,
+ });
+ dataList.value = data?.records || [];
+ multipleList.value = [];
+ pagination.total = Number(data?.total || 0);
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
- 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 handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
};
const resetFilters = () => {
@@ -327,38 +354,32 @@
getTableData();
};
+const buildAssetCode = () => `WX${Date.now().toString().slice(-10)}`;
+
const add = () => {
isEdit.value = false;
+ isView.value = false;
+ currentId.value = null;
dialogTitle.value = "鏂板鏃犲舰璧勪骇";
- Object.assign(form, {
- assetCode: "WX" + Date.now().toString().slice(-8),
- assetName: "",
- category: "",
- certificateNo: "",
+ Object.assign(form, createDefaultForm(), {
+ assetCode: buildAssetCode(),
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;
+ isView.value = false;
currentId.value = row.id;
dialogTitle.value = "缂栬緫鏃犲舰璧勪骇";
- Object.assign(form, row);
+ Object.assign(form, createDefaultForm(), row);
dialogVisible.value = true;
};
const view = (row) => {
- ElMessage.info(`鏌ョ湅璧勪骇: ${row.assetName}`);
+ edit(row);
+ isView.value = true;
};
const handleDelete = (row) => {
@@ -366,35 +387,30 @@
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData.splice(index, 1);
+ }).then(async () => {
+ // 鑱旇皟绾﹀畾锛氬垹闄ゆ帴鍙d娇鐢� ids=1&ids=2
+ await deleteIntangibleAsset([row.id]);
+ if (dataList.value.length === 1 && pagination.currentPage > 1) {
+ pagination.currentPage -= 1;
}
ElMessage.success("鍒犻櫎鎴愬姛");
- getTableData();
+ await getTableData();
});
};
const handleAmortization = () => {
- ElMessageBox.confirm("纭杩涜鏈湀鎽婇攢璁℃彁鍚楋紵", "鎻愮ず", {
+ const ids = selectedIds.value;
+ const confirmText = ids.length
+ ? `纭瀵归�変腑鐨� ${ids.length} 鏉¤祫浜ц繘琛屾湰鏈堟憡閿�璁℃彁鍚楋紵`
+ : "纭杩涜鏈湀鎽婇攢璁℃彁鍚楋紵";
+ ElMessageBox.confirm(confirmText, "鎻愮ず", {
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;
- }
- }
- });
+ }).then(async () => {
+ await amortizeIntangibleAsset({ ids });
ElMessage.success("鎽婇攢璁℃彁瀹屾垚");
- getTableData();
+ await getTableData();
});
};
@@ -403,22 +419,28 @@
};
const submitForm = () => {
- formRef.value.validate((valid) => {
+ if (isView.value) {
+ dialogVisible.value = false;
+ return;
+ }
+ formRef.value.validate(async valid => {
if (valid) {
- calculateNetValue();
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...form };
+ try {
+ calculateNetValue();
+ const payload = { ...form };
+ if (isEdit.value) {
+ payload.id = currentId.value;
+ await updateIntangibleAsset(payload);
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ await addIntangibleAsset(payload);
+ ElMessage.success("鏂板鎴愬姛");
}
- 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;
+ await getTableData();
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
- dialogVisible.value = false;
- getTableData();
}
});
};
diff --git a/src/views/financialManagement/generalLedger/index.vue b/src/views/financialManagement/generalLedger/index.vue
index 0570627..a7b1d30 100644
--- a/src/views/financialManagement/generalLedger/index.vue
+++ b/src/views/financialManagement/generalLedger/index.vue
@@ -1,23 +1,39 @@
<template>
<div class="app-container">
- <el-form :model="filters" :inline="true">
+ <el-form :model="filters"
+ :inline="true">
<el-form-item label="绉戠洰缂栫爜:">
- <el-input v-model="filters.subjectCode" placeholder="璇疯緭鍏ョ鐩紪鐮�" clearable style="width: 200px;" />
+ <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-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 v-model="filters.subjectType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ style="width: 200px;">
+ <el-option label="璧勪骇绫�"
+ value="璧勪骇绫�" />
+ <el-option label="璐熷�虹被"
+ value="璐熷�虹被" />
+ <el-option label="鏉冪泭绫�"
+ value="鏉冪泭绫�" />
+ <el-option label="鎴愭湰绫�"
+ value="鎴愭湰绫�" />
+ <el-option label="鎹熺泭绫�"
+ value="鎹熺泭绫�" />
</el-select>
</el-form-item>
<el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button type="primary"
+ @click="getTableData">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
@@ -25,76 +41,131 @@
<div class="actions">
<div></div>
<div>
- <el-button type="primary" @click="add" icon="Plus">鏂板</el-button>
- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ <el-button type="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>
+ <el-table ref="tableRef"
+ v-loading="loading"
+ :data="dataList"
+ row-key="id"
+ :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+ height="calc(100vh - 280px)"
+ border
+ stripe
+ highlight-current-row
+ class="subject-table">
+ <el-table-column label="绉戠洰缂栫爜" prop="subjectCode" width="140">
+ <template #default="scope">
+ <span class="subject-code">{{ scope.row.subjectCode }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="绉戠洰鍚嶇О" prop="subjectName" min-width="180">
+ <template #default="scope">
+ <span class="subject-name" :class="{ 'is-parent': scope.row.children?.length > 0 }">
+ {{ scope.row.subjectName }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="绉戠洰绫诲瀷" prop="subjectType" width="100" align="center">
+ <template #default="scope">
+ <el-tag size="small" :type="getSubjectTypeType(scope.row.subjectType)">
+ {{ scope.row.subjectType }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="浣欓鏂瑰悜" prop="balanceDirection" width="100" align="center">
+ <template #default="scope">
+ <el-tag size="small" :type="scope.row.balanceDirection === '鍊熸柟' ? 'primary' : 'danger'">
+ {{ scope.row.balanceDirection }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" prop="status" width="80" align="center">
+ <template #default="scope">
+ <el-tag size="small" :type="scope.row.status === 0 || scope.row.status === '0' ? 'success' : 'info'">
+ {{ scope.row.status === 0 || scope.row.status === '0' ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶囨敞" prop="remark" show-overflow-tooltip min-width="150" />
+ <el-table-column label="鎿嶄綔" align="center" fixed="right" width="240">
+ <template #default="scope">
+ <el-button link type="primary" icon="Plus" @click="addChild(scope.row)">鏂板</el-button>
+ <el-button link type="primary" icon="Edit" @click="edit(scope.row)">缂栬緫</el-button>
+ <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
</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="璇疯緭鍏ョ鐩紪鐮�" />
+ <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="鐖剁骇绉戠洰">
+ <el-input :model-value="parentSubjectLabel"
+ disabled />
</el-form-item>
- <el-form-item label="绉戠洰鍚嶇О" prop="subjectName">
- <el-input v-model="form.subjectName" placeholder="璇疯緭鍏ョ鐩悕绉�" />
+ <el-form-item label="绉戠洰缂栫爜"
+ prop="subjectCode">
+ <el-input v-model="form.subjectCode"
+ 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-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="璧勪骇绫�" />
+ <el-option label="璐熷�虹被"
+ value="璐熷�虹被" />
+ <el-option label="鏉冪泭绫�"
+ value="鏉冪泭绫�" />
+ <el-option label="鎴愭湰绫�"
+ value="鎴愭湰绫�" />
+ <el-option label="鎹熺泭绫�"
+ value="鎹熺泭绫�" />
</el-select>
</el-form-item>
- <el-form-item label="浣欓鏂瑰悜" prop="balanceDirection">
+ <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 label="鍊熸柟">鍊熸柟</el-radio>
+ <el-radio label="璐锋柟">璐锋柟</el-radio>
</el-radio-group>
</el-form-item>
- <el-form-item label="鐘舵��" prop="status">
+ <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 :label="0">鍚敤</el-radio>
+ <el-radio :label="1">绂佺敤</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 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 type="primary"
+ @click="submitForm">纭畾</el-button>
<el-button @click="dialogVisible = false">鍙栨秷</el-button>
</template>
</FormDialog>
@@ -102,190 +173,326 @@
</template>
<script setup>
-import { ref, reactive, onMounted } from "vue";
-import { ElMessage, ElMessageBox } from "element-plus";
-import FormDialog from "@/components/Dialog/FormDialog.vue";
+ import { ref, reactive, onMounted, getCurrentInstance, nextTick } from "vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import FormDialog from "@/components/Dialog/FormDialog.vue";
+ import {
+ listAccountSubject,
+ addAccountSubject,
+ updateAccountSubject,
+ delAccountSubject,
+ exportAccountSubject,
+ } from "@/api/financialManagement/accountSubject";
-defineOptions({
- name: "鎬诲笎绉戠洰",
-});
+ defineOptions({
+ name: "鎬诲笎绉戠洰",
+ });
-const filters = reactive({
- subjectCode: "",
- subjectName: "",
- subjectType: "",
-});
+ const { proxy } = getCurrentInstance();
-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, {
+ const filters = reactive({
subjectCode: "",
subjectName: "",
subjectType: "",
- balanceDirection: "debit",
- status: "active",
+ });
+
+ const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+ });
+
+ const columns = [
+ { label: "绉戠洰缂栫爜", prop: "subjectCode", width: "120" },
+ { label: "绉戠洰鍚嶇О", prop: "subjectName", width: "150" },
+ { label: "绉戠洰绫诲瀷", prop: "subjectType" },
+ {
+ label: "浣欓鏂瑰悜",
+ prop: "balanceDirection",
+ dataType: "tag",
+ formatData: value => {
+ if (value === "鍊熸柟") {
+ return "鍊熸柟";
+ }
+ return "璐锋柟";
+ },
+ formatType: value => {
+ if (value === "鍊熸柟") {
+ return "primary";
+ }
+ return "danger";
+ },
+ },
+ {
+ label: "鐘舵��",
+ prop: "status",
+ dataType: "tag",
+ formatData: value => {
+ if (value === 0 || value === "0") {
+ return "鍚敤";
+ }
+ return "绂佺敤";
+ },
+ formatType: value => {
+ if (value === 0 || value === "0") {
+ return "success";
+ }
+ return "info";
+ },
+ },
+
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: "220",
+ operation: [
+ {
+ name: "鏂板",
+ type: "primary",
+ clickFun: row => {
+ addChild(row);
+ },
+ },
+ {
+ name: "缂栬緫",
+ type: "primary",
+ clickFun: row => {
+ edit(row);
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ clickFun: row => {
+ handleDelete(row);
+ },
+ },
+ ],
+ },
+ ];
+
+ const dataList = ref([]);
+ const dialogVisible = ref(false);
+ const dialogTitle = ref("");
+ const parentSubjectLabel = ref("椤剁骇绉戠洰");
+ const formRef = ref(null);
+ const tableRef = ref(null);
+ const isEdit = ref(false);
+ const loading = ref(false);
+
+ const form = reactive({
+ id: undefined,
+ parentId: null,
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+ balanceDirection: "鍊熸柟",
+ status: 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 rules = {
+ subjectCode: [{ required: true, message: "璇疯緭鍏ョ鐩紪鐮�", trigger: "blur" }],
+ subjectName: [{ required: true, message: "璇疯緭鍏ョ鐩悕绉�", trigger: "blur" }],
+ subjectType: [
+ { required: true, message: "璇烽�夋嫨绉戠洰绫诲瀷", trigger: "change" },
+ ],
+ };
-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("鏂板鎴愬姛");
+ const getSubjectTypeType = type => {
+ const map = {
+ 璧勪骇绫�: "success",
+ 璐熷�虹被: "danger",
+ 鏉冪泭绫�: "warning",
+ 鎴愭湰绫�: "info",
+ 鎹熺泭绫�: "primary",
+ };
+ return map[type] || "";
+ };
+
+ const getTableData = () => {
+ loading.value = true;
+ const query = {
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ ...filters,
+ };
+ listAccountSubject(query).then(response => {
+ dataList.value = response.data.records || [];
+ loading.value = false;
+ }).catch(() => {
+ loading.value = false;
+ });
+ };
+
+ const resetFilters = () => {
+ filters.subjectCode = "";
+ filters.subjectName = "";
+ filters.subjectType = "";
+ pagination.currentPage = 1;
+ getTableData();
+ };
+
+ const changePage = obj => {
+ pagination.currentPage = obj.page;
+ pagination.pageSize = obj.limit;
+ getTableData();
+ };
+
+ const buildParentSubjectLabel = parentRow => {
+ if (!parentRow) {
+ return "椤剁骇绉戠洰";
+ }
+ const code = parentRow.subjectCode || "";
+ const name = parentRow.subjectName || "";
+ return `${code} ${name}`.trim();
+ };
+
+ const resetForm = ({ parentId = null, parentRow = null } = {}) => {
+ Object.assign(form, {
+ id: undefined,
+ parentId,
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+ balanceDirection: "鍊熸柟",
+ status: 0,
+ remark: "",
+ });
+ parentSubjectLabel.value = buildParentSubjectLabel(parentRow);
+ };
+
+ const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板绉戠洰";
+ resetForm({ parentId: null, parentRow: null });
+ dialogVisible.value = true;
+ };
+
+ const addChild = row => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板瀛愮鐩�";
+ resetForm({ parentId: row.id, parentRow: row });
+ form.subjectType = row.subjectType || "";
+ form.balanceDirection = row.balanceDirection || "鍊熸柟";
+ dialogVisible.value = true;
+ };
+
+ const findSubjectById = (nodes, id) => {
+ for (const item of nodes || []) {
+ if (item.id === id) {
+ return item;
}
- dialogVisible.value = false;
- getTableData();
+ if (item.children && item.children.length > 0) {
+ const found = findSubjectById(item.children, id);
+ if (found) {
+ return found;
+ }
+ }
}
- });
-};
+ return null;
+ };
-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("鍒犻櫎鎴愬姛");
+ const edit = row => {
+ isEdit.value = true;
+ dialogTitle.value = "缂栬緫绉戠洰";
+ Object.assign(form, row);
+ form.parentId = row.parentId ?? null;
+ const parentRow =
+ row.parentId === null || row.parentId === undefined
+ ? null
+ : findSubjectById(dataList.value, row.parentId);
+ parentSubjectLabel.value = parentRow
+ ? buildParentSubjectLabel(parentRow)
+ : row.parentId
+ ? `涓婄骇ID: ${row.parentId}`
+ : buildParentSubjectLabel(null);
+ dialogVisible.value = true;
+ };
+
+ const submitForm = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ if (isEdit.value) {
+ updateAccountSubject(form).then(() => {
+ ElMessage.success("缂栬緫鎴愬姛");
+ dialogVisible.value = false;
+ getTableData();
+ });
+ } else {
+ addAccountSubject(form).then(() => {
+ ElMessage.success("鏂板鎴愬姛");
+ dialogVisible.value = false;
+ getTableData();
+ });
+ }
+ }
+ });
+ };
+
+ const handleDelete = row => {
+ const ids = row.id;
+ ElMessageBox.confirm("纭鍒犻櫎璇ョ鐩悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ return delAccountSubject(ids);
+ })
+ .then(() => {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+ };
+
+ const handleOut = () => {
+ proxy.download(
+ "accountSubject/export",
+ {
+ ...filters,
+ },
+ `account_subject_${new Date().getTime()}.xlsx`
+ );
+ };
+
+ onMounted(() => {
getTableData();
});
-};
-
-const handleOut = () => {
- ElMessage.success("瀵煎嚭鎴愬姛");
-};
-
-onMounted(() => {
- getTableData();
-});
</script>
<style lang="scss" scoped>
-.actions {
- display: flex;
- justify-content: space-between;
- margin-bottom: 15px;
-}
+ .actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+ }
+
+ .subject-table {
+ border-radius: 8px;
+ overflow: hidden;
+
+ :deep(.el-table__row) {
+ transition: background-color 0.3s;
+ }
+
+ :deep(.el-table__row:hover) {
+ background-color: #f5f7fa;
+ }
+
+ .subject-code {
+ color: #606266;
+ }
+
+ .subject-name {
+ font-weight: 500;
+
+ &.is-parent {
+ color: #409eff;
+ }
+ }
+ }
</style>
diff --git a/src/views/financialManagement/payable/purchaseIn.vue b/src/views/financialManagement/payable/purchaseIn.vue
index 4813159..4fadcbb 100644
--- a/src/views/financialManagement/payable/purchaseIn.vue
+++ b/src/views/financialManagement/payable/purchaseIn.vue
@@ -1,19 +1,27 @@
<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-input v-model="filters.inboundBatches" 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-input v-model="filters.supplierName" 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-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 type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
@@ -28,6 +36,7 @@
rowKey="id"
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
:page="{
current: pagination.currentPage,
size: pagination.pageSize,
@@ -35,107 +44,28 @@
}"
@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 #inboundDate="{ row }">
+ {{ row.InboundDate || row.inboundDate || "" }}
</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";
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessage } from "element-plus";
+import { listPageAccountPurchase } from "@/api/financialManagement/accountPurchase";
defineOptions({
name: "閲囪喘鍏ュ簱",
});
+const { proxy } = getCurrentInstance();
+
const filters = reactive({
- inCode: "",
- supplierId: "",
+ inboundBatches: "",
+ supplierName: "",
dateRange: [],
});
@@ -146,170 +76,85 @@
});
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" },
+ { label: "鍏ュ簱鍗曞彿", prop: "inboundBatches", minWidth: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", minWidth: "180" },
+ {
+ label: "鍏ュ簱鏃ユ湡",
+ prop: "InboundDate",
+ minWidth: "170",
+ dataType: "slot",
+ slot: "inboundDate",
+ },
+ { label: "浜у搧鍚嶇О", prop: "productName", minWidth: "140" },
+ { label: "浜у搧瑙勬牸", prop: "specificationModel", minWidth: "140" },
+ { label: "閲囪喘璁㈠崟鍙�", prop: "purchaseContractNumber", minWidth: "150" },
];
const dataList = ref([]);
-const dialogVisible = ref(false);
-const dialogTitle = ref("");
-const formRef = ref(null);
-const isEdit = ref(false);
-const currentId = ref(null);
+const tableLoading = ref(false);
-const supplierList = [
- { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
- { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
- { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
- { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
-];
+function buildFilterParams() {
+ const params = {
+ inboundBatches: filters.inboundBatches || undefined,
+ supplierName: filters.supplierName || undefined,
+ };
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ return params;
+}
-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 onSearch = () => {
+ pagination.currentPage = 1;
+ getTableData();
};
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);
+ tableLoading.value = true;
+ listPageAccountPurchase({
+ ...buildFilterParams(),
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ })
+ .then((res) => {
+ const ok = res.code === 200 || res.code === 0;
+ if (ok && res.data) {
+ pagination.total = res.data.total ?? 0;
+ dataList.value = res.data.records ?? [];
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ dataList.value = [];
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
};
const resetFilters = () => {
- filters.inCode = "";
- filters.supplierId = "";
+ filters.inboundBatches = "";
+ filters.supplierName = "";
filters.dateRange = [];
pagination.currentPage = 1;
getTableData();
};
-const changePage = ({ current, size }) => {
- pagination.currentPage = current;
- pagination.pageSize = size;
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
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();
- }
- });
+ proxy.download(
+ "/accountPurchase/exportAccountPurchaseInbound",
+ buildFilterParams(),
+ `閲囪喘鍏ュ簱_${new Date().getTime()}.xlsx`
+ );
};
onMounted(() => {
@@ -322,10 +167,5 @@
display: flex;
justify-content: space-between;
margin-bottom: 15px;
-}
-
-.text-primary {
- color: #409eff;
- font-weight: bold;
}
</style>
diff --git a/src/views/financialManagement/payable/purchaseReturn.vue b/src/views/financialManagement/payable/purchaseReturn.vue
new file mode 100644
index 0000000..e7ca665
--- /dev/null
+++ b/src/views/financialManagement/payable/purchaseReturn.vue
@@ -0,0 +1,239 @@
+<template>
+ <!-- 閲囪喘閫�璐� -->
+
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="閫�璐у崟鍙�:">
+ <el-input
+ v-model="filters.returnNo"
+ placeholder="璇疯緭鍏ラ��璐у崟鍙�"
+ clearable
+ style="width: 200px"
+ />
+ </el-form-item>
+
+ <el-form-item label="渚涘簲鍟�:">
+ <el-input
+ v-model="filters.supplierName"
+ 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>
+ <el-button type="primary" @click="onSearch">鎼滅储</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"
+ :tableLoading="tableLoading"
+ :page="{
+ current: pagination.currentPage,
+
+ size: pagination.pageSize,
+
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ />
+ </div>
+ </div>
+</template>
+
+
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+
+import { ElMessage } from "element-plus";
+
+import { listPageAccountPurchaseReturn } from "@/api/financialManagement/accountPurchase";
+
+defineOptions({
+ name: "閲囪喘閫�璐�",
+});
+
+const { proxy } = getCurrentInstance();
+
+const filters = reactive({
+ returnNo: "",
+
+ supplierName: "",
+
+ dateRange: [],
+});
+
+const pagination = reactive({
+ currentPage: 1,
+
+ pageSize: 10,
+
+ total: 0,
+});
+
+const columns = [
+ { label: "閫�璐у崟鍙�", prop: "returnNo", minWidth: "150" },
+
+ { label: "渚涘簲鍟�", prop: "supplierName", minWidth: "180" },
+
+ { label: "鍏宠仈鍏ュ簱鍗曞彿", prop: "inboundBatches", minWidth: "150" },
+
+ { label: "閫�璐ф棩鏈�", prop: "preparedAt", minWidth: "170" },
+
+ {
+ label: "閫�娆炬�婚",
+
+ prop: "totalAmount",
+
+ minWidth: "150",
+
+ align: "right",
+
+ formatData: (val) =>
+ val === null || val === undefined || val === ""
+ ? ""
+ : Number(val).toLocaleString("zh-CN", {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ }),
+ },
+
+ { label: "閫�璐ф柟寮�", prop: "returnType", minWidth: "150" },
+
+ { label: "閲囪喘璁㈠崟鍙�", prop: "purchaseContractNumber", minWidth: "150" },
+];
+
+const dataList = ref([]);
+
+const tableLoading = ref(false);
+
+function buildFilterParams() {
+ const params = {
+ returnNo: filters.returnNo || undefined,
+
+ supplierName: filters.supplierName || undefined,
+ };
+
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ params.startDate = filters.dateRange[0];
+
+ params.endDate = filters.dateRange[1];
+ }
+
+ return params;
+}
+
+const onSearch = () => {
+ pagination.currentPage = 1;
+
+ getTableData();
+};
+
+const getTableData = () => {
+ tableLoading.value = true;
+
+ listPageAccountPurchaseReturn({
+ ...buildFilterParams(),
+
+ current: pagination.currentPage,
+
+ size: pagination.pageSize,
+ })
+ .then((res) => {
+ const ok = res.code === 200 || res.code === 0;
+
+ if (ok && res.data) {
+ pagination.total = res.data.total ?? 0;
+
+ dataList.value = res.data.records ?? [];
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+
+ dataList.value = [];
+ }
+ })
+
+ .catch(() => {
+ dataList.value = [];
+ })
+
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+const resetFilters = () => {
+ filters.returnNo = "";
+
+ filters.supplierName = "";
+
+ filters.dateRange = [];
+
+ pagination.currentPage = 1;
+
+ getTableData();
+};
+
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+
+ pagination.pageSize = limit;
+
+ getTableData();
+};
+
+const handleOut = () => {
+ proxy.download(
+ "/accountPurchase/exportAccountPurchaseReturn",
+
+ buildFilterParams(),
+
+ `閲囪喘閫�璐${new Date().getTime()}.xlsx`
+ );
+};
+
+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/salesOut.vue b/src/views/financialManagement/receivable/salesOut.vue
index fce0c20..297a82c 100644
--- a/src/views/financialManagement/receivable/salesOut.vue
+++ b/src/views/financialManagement/receivable/salesOut.vue
@@ -1,19 +1,27 @@
<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-input v-model="filters.outboundBatches" 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 label="瀹㈡埛鍚嶇О:">
+ <el-input v-model="filters.customerName" placeholder="璇疯緭鍏ュ鎴峰悕绉�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="鍑哄簱鏃ユ湡:">
- <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ <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 type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
@@ -28,76 +36,32 @@
rowKey="id"
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
: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";
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessage } from "element-plus";
+import { listPageAccountSales } from "@/api/financialManagement/accountSales";
defineOptions({
name: "閿�鍞嚭搴�",
});
+const { proxy } = getCurrentInstance();
+
const filters = reactive({
- outCode: "",
- customerId: "",
+ outboundBatches: "",
+ customerName: "",
dateRange: [],
});
@@ -108,153 +72,87 @@
});
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" },
+ { label: "鍑哄簱鍗曞彿", prop: "outboundBatches", minWidth: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", minWidth: "180" },
+ { label: "鍑哄簱鏃ユ湡", prop: "shippingDate", width: "170" },
+ { label: "浜у搧鍚嶇О", prop: "productName", minWidth: "140" },
+ { label: "浜у搧瑙勬牸", prop: "specificationModel", minWidth: "140" },
+ {
+ label: "閲戦",
+ prop: "outboundAmount",
+ minWidth: "120",
+ align: "right",
+ formatData: (val) => (val === null || val === undefined || val === "" ? "" : Number(val).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })),
+ },
+ { label: "鍙戣揣缂栧彿", prop: "shippingNo", minWidth: "140" },
+ { label: "閿�鍞鍗曞彿", prop: "salesContractNo", minWidth: "150" },
];
const dataList = ref([]);
-const dialogVisible = ref(false);
-const dialogTitle = ref("");
-const formRef = ref(null);
-const isEdit = ref(false);
-const currentId = ref(null);
+const tableLoading = ref(false);
-const customerList = [
- { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
- { id: 2, name: "涓婃捣璐告槗鍏徃" },
- { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
- { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
-];
+function buildFilterParams() {
+ const params = {
+ outboundBatches: filters.outboundBatches || undefined,
+ customerName: filters.customerName || undefined,
+ };
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ return params;
+}
-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 onSearch = () => {
+ pagination.currentPage = 1;
+ getTableData();
};
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);
+ tableLoading.value = true;
+ listPageAccountSales({
+ ...buildFilterParams(),
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ })
+ .then((res) => {
+ const ok = res.code === 200 || res.code === 0;
+ if (ok && res.data) {
+ pagination.total = res.data.total ?? 0;
+ dataList.value = res.data.records ?? [];
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ dataList.value = [];
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
};
const resetFilters = () => {
- filters.outCode = "";
- filters.customerId = "";
+ filters.outboundBatches = "";
+ filters.customerName = "";
filters.dateRange = [];
pagination.currentPage = 1;
getTableData();
};
-const changePage = ({ current, size }) => {
- pagination.currentPage = current;
- pagination.pageSize = size;
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
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("瀵煎嚭鎴愬姛");
+ proxy.download(
+ "/accountSales/exportAccountSalesOutbound",
+ buildFilterParams(),
+ `閿�鍞嚭搴揰${new Date().getTime()}.xlsx`
+ );
};
onMounted(() => {
diff --git a/src/views/financialManagement/receivable/salesReturn.vue b/src/views/financialManagement/receivable/salesReturn.vue
index 4cf54d6..c58d330 100644
--- a/src/views/financialManagement/receivable/salesReturn.vue
+++ b/src/views/financialManagement/receivable/salesReturn.vue
@@ -1,19 +1,27 @@
<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-input v-model="filters.returnNo" 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 label="瀹㈡埛鍚嶇О:">
+ <el-input v-model="filters.customerName" placeholder="璇疯緭鍏ュ鎴峰悕绉�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="閫�璐ф棩鏈�:">
- <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ <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 type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
@@ -28,90 +36,32 @@
rowKey="id"
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
: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";
+import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ElMessage } from "element-plus";
+import { listPageAccountSalesReturn } from "@/api/financialManagement/accountSales";
defineOptions({
name: "閿�鍞��璐�",
});
+const { proxy } = getCurrentInstance();
+
const filters = reactive({
- returnCode: "",
- customerId: "",
+ returnNo: "",
+ customerName: "",
dateRange: [],
});
@@ -122,173 +72,89 @@
});
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" },
+ { label: "閫�璐у崟鍙�", prop: "returnNo", minWidth: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", minWidth: "180" },
+ { label: "鍏宠仈鍙戣揣鍗曞彿", prop: "shippingNo", minWidth: "150" },
+ { label: "閫�璐ф棩鏈�", prop: "makeTime", minWidth: "170" },
+ {
+ label: "閫�娆炬�婚",
+ prop: "refundAmount",
+ minWidth: "120",
+ align: "right",
+ formatData: (val) =>
+ val === null || val === undefined || val === ""
+ ? ""
+ : Number(val).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
+ },
+ { label: "閫�璐у師鍥�", prop: "returnReason", minWidth: "150", showOverflowTooltip: true },
+ { label: "閿�鍞鍗曞彿", prop: "salesContractNo", minWidth: "150" },
];
const dataList = ref([]);
-const dialogVisible = ref(false);
-const dialogTitle = ref("");
-const formRef = ref(null);
-const isEdit = ref(false);
-const currentId = ref(null);
+const tableLoading = ref(false);
-const customerList = [
- { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
- { id: 2, name: "涓婃捣璐告槗鍏徃" },
- { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
- { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
-];
+function buildFilterParams() {
+ const params = {
+ returnNo: filters.returnNo || undefined,
+ customerName: filters.customerName || undefined,
+ };
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ return params;
+}
-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 onSearch = () => {
+ pagination.currentPage = 1;
+ getTableData();
};
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);
+ tableLoading.value = true;
+ listPageAccountSalesReturn({
+ ...buildFilterParams(),
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ })
+ .then((res) => {
+ const ok = res.code === 200 || res.code === 0;
+ if (ok && res.data) {
+ pagination.total = res.data.total ?? 0;
+ dataList.value = res.data.records ?? [];
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ dataList.value = [];
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
};
const resetFilters = () => {
- filters.returnCode = "";
- filters.customerId = "";
+ filters.returnNo = "";
+ filters.customerName = "";
filters.dateRange = [];
pagination.currentPage = 1;
getTableData();
};
-const changePage = ({ current, size }) => {
- pagination.currentPage = current;
- pagination.pageSize = size;
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
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("瀵煎嚭鎴愬姛");
+ proxy.download(
+ "/accountSales/exportAccountSalesReturn",
+ buildFilterParams(),
+ `閿�鍞��璐${new Date().getTime()}.xlsx`
+ );
};
onMounted(() => {
diff --git a/src/views/financialManagement/voucher/detailLedger.vue b/src/views/financialManagement/voucher/detailLedger.vue
index 7f85790..c07574c 100644
--- a/src/views/financialManagement/voucher/detailLedger.vue
+++ b/src/views/financialManagement/voucher/detailLedger.vue
@@ -1,165 +1,126 @@
<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="app-container ledger-page">
+ <div class="ledger-layout">
+ <aside class="subject-panel">
+ <el-input v-model="subjectKeyword" placeholder="璇疯緭鍏ョ鐩悕绉�/缂栧彿" clearable prefix-icon="Search" />
+ <el-scrollbar class="subject-tree-scroll">
+ <el-tree
+ ref="subjectTreeRef"
+ :data="subjectOptions"
+ node-key="code"
+ :props="{ label: 'name', children: 'children' }"
+ highlight-current
+ default-expand-all
+ :expand-on-click-node="false"
+ :filter-node-method="filterSubjectNode"
+ @node-click="handleSubjectClick"
+ >
+ <template #default="{ data }">
+ <span class="subject-node">{{ data.code }} {{ data.name }}</span>
+ </template>
+ </el-tree>
+ </el-scrollbar>
+ </aside>
- <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>
+ <section class="ledger-content">
+ <el-form :model="filters" :inline="true" class="filter-form">
+ <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="table_list">
+ <el-table :data="dataList" border style="width: 100%">
+ <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 prop="debit" 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 prop="credit" 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;" />
+ </section>
</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 { ref, reactive, onMounted, computed, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
+import { listAccountSubject } from "@/api/financialManagement/accountSubject";
+import { getDetailLedger } from "@/api/financialManagement/ledger";
defineOptions({
name: "绉戠洰鏄庣粏璐�",
});
const filters = reactive({
- subject: [],
- auxiliary: "",
- auxiliaryItem: "",
- startMonth: "2024-01",
- endMonth: "2024-03",
+ subject: "",
+ startMonth: "",
+ endMonth: "",
});
const dataList = ref([]);
+const subjectOptions = ref([]);
+const subjectKeyword = ref("");
+const subjectTreeRef = 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 getPreviousMonth = () => {
+ const date = new Date();
+ date.setDate(1);
+ date.setMonth(date.getMonth() - 1);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ return `${year}-${month}`;
+};
+
+const defaultMonth = getPreviousMonth();
+filters.startMonth = defaultMonth;
+filters.endMonth = defaultMonth;
+
+const fallbackSubjects = [
+ { code: "1122", name: "搴旀敹璐︽" },
+ { code: "2202", name: "搴斾粯璐︽" },
+ { code: "6602", 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 toTree = (nodes = []) =>
+ nodes
+ .filter(item => item.subjectCode && item.subjectName)
+ .map(item => ({
+ code: item.subjectCode,
+ name: item.subjectName,
+ children: toTree(item.children || []),
+ }));
const findSubject = (options, code) => {
for (const item of options) {
@@ -172,9 +133,68 @@
return null;
};
-const getAuxiliaryLabel = () => {
- const item = auxiliaryItems.value.find(i => i.value === filters.auxiliaryItem);
- return item ? item.label : "";
+const currentSubject = computed(() => {
+ if (!filters.subject) return null;
+ return findSubject(subjectOptions.value, filters.subject);
+});
+
+const getFirstSubjectCode = (nodes = []) => {
+ for (const item of nodes) {
+ if (item.code) return item.code;
+ if (item.children && item.children.length > 0) {
+ const childCode = getFirstSubjectCode(item.children);
+ if (childCode) return childCode;
+ }
+ }
+ return "";
+};
+
+const setDefaultSubjectSelection = async () => {
+ const firstCode = getFirstSubjectCode(subjectOptions.value);
+ if (!firstCode) {
+ filters.subject = "";
+ subjectTreeRef.value?.setCurrentKey(null);
+ return;
+ }
+ filters.subject = firstCode;
+ await nextTick();
+ subjectTreeRef.value?.setCurrentKey(firstCode);
+};
+
+const filterSubjectNode = (value, data) => {
+ const keyword = value?.trim();
+ if (!keyword) return true;
+ return `${data.code}${data.name}`.includes(keyword);
+};
+
+watch(subjectKeyword, (value) => {
+ subjectTreeRef.value?.filter(value || "");
+});
+
+const handleSubjectClick = async (data) => {
+ filters.subject = data.code;
+ await getTableData();
+};
+
+const loadSubjectOptions = async () => {
+ let options = [];
+ try {
+ const { data } = await listAccountSubject({
+ current: 1,
+ size: 1000,
+ });
+ options = toTree(data?.records || []);
+ } catch (error) {
+ // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず锛屼笅闈㈣蛋鍏滃簳绉戠洰
+ }
+ if (options.length === 0) {
+ options = fallbackSubjects.map(item => ({ ...item, children: [] }));
+ }
+ subjectOptions.value = options;
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
};
const formatMoney = (value) => {
@@ -182,63 +202,34 @@
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 = () => {
+// 鑱旇皟绾﹀畾锛氭槑缁嗚处鎸夌鐩笌鏈熼棿杩囨护
+const getTableData = async () => {
if (!currentSubject.value) {
dataList.value = [];
return;
}
- dataList.value = [...mockData];
+ try {
+ const { data } = await getDetailLedger({
+ subjectCode: currentSubject.value.code,
+ startMonth: filters.startMonth,
+ endMonth: filters.endMonth,
+ });
+ dataList.value = Array.isArray(data) ? data : data?.records || [];
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
+ }
};
-const resetFilters = () => {
- filters.subject = [];
- filters.auxiliary = "";
- filters.auxiliaryItem = "";
- filters.startMonth = "2024-01";
- filters.endMonth = "2024-03";
+const resetFilters = async () => {
+ filters.startMonth = defaultMonth;
+ filters.endMonth = defaultMonth;
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;
+ subjectKeyword.value = "";
+ subjectTreeRef.value?.filter("");
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
};
const handlePrint = () => {
@@ -249,22 +240,43 @@
ElMessage.success("瀵煎嚭鎴愬姛");
};
-onMounted(() => {
- // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+onMounted(async () => {
+ await loadSubjectOptions();
});
</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;
- }
+.ledger-layout {
+ display: flex;
+ gap: 16px;
+}
+
+.subject-panel {
+ width: 260px;
+ flex-shrink: 0;
+ padding: 12px;
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ background-color: #fff;
+}
+
+.subject-tree-scroll {
+ height: 600px;
+ margin-top: 12px;
+}
+
+.subject-node {
+ display: inline-flex;
+ align-items: center;
+}
+
+.ledger-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.filter-form {
+ margin-bottom: 12px;
}
.text-primary {
@@ -286,4 +298,12 @@
color: #e6a23c;
font-weight: bold;
}
+
+.subject-panel :deep(.el-tree-node__content) {
+ height: 34px;
+}
+
+.subject-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
+ background-color: #f0f7ff;
+}
</style>
diff --git a/src/views/financialManagement/voucher/generalLedger.vue b/src/views/financialManagement/voucher/generalLedger.vue
index 5da2d70..b362279 100644
--- a/src/views/financialManagement/voucher/generalLedger.vue
+++ b/src/views/financialManagement/voucher/generalLedger.vue
@@ -1,114 +1,128 @@
<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="app-container ledger-page">
+ <div class="ledger-layout">
+ <aside class="subject-panel">
+ <el-input v-model="subjectKeyword" placeholder="璇疯緭鍏ョ鐩悕绉�/缂栧彿" clearable prefix-icon="Search" />
+ <el-scrollbar class="subject-tree-scroll">
+ <el-tree
+ ref="subjectTreeRef"
+ :data="subjectOptions"
+ node-key="code"
+ :props="{ label: 'name', children: 'children' }"
+ highlight-current
+ default-expand-all
+ :expand-on-click-node="false"
+ :filter-node-method="filterSubjectNode"
+ @node-click="handleSubjectClick"
+ >
+ <template #default="{ data }">
+ <span class="subject-node">{{ data.code }} {{ data.name }}</span>
+ </template>
+ </el-tree>
+ </el-scrollbar>
+ </aside>
- <div class="ledger-header" v-if="currentSubject">
- <h2>绉戠洰鎬昏处</h2>
- <p>绉戠洰: {{ currentSubject.code }} {{ currentSubject.name }}</p>
- <p>鏈熼棿: {{ filters.startMonth }} 鑷� {{ filters.endMonth }}</p>
+ <section class="ledger-content">
+ <el-form :model="filters" :inline="true" class="filter-form">
+ <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="table_list">
+ <el-table :data="dataList" border style="width: 100%">
+ <el-table-column prop="date" label="鏃ユ湡"/>
+ <!-- <el-table-column prop="voucherNo" label="鍑瘉瀛楀彿" width="120" /> -->
+ <!-- <el-table-column prop="summary" label="鎽樿" min-width="200" show-overflow-tooltip /> -->
+ <el-table-column prop="debit" label="鍊熸柟">
+ <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="璐锋柟">
+ <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="鏂瑰悜">
+ <template #default="{ row }">
+ <el-tag :type="row.direction === '鍊�' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="浣欓">
+ <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;" />
+ </section>
</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 { ref, reactive, onMounted, computed, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
+import { listAccountSubject } from "@/api/financialManagement/accountSubject";
+import { getGeneralLedger } from "@/api/financialManagement/ledger";
defineOptions({
name: "绉戠洰鎬昏处",
});
const filters = reactive({
- subject: [],
- startMonth: "2024-01",
- endMonth: "2024-03",
+ subject: "",
+ startMonth: "",
+ endMonth: "",
});
const dataList = ref([]);
+const subjectOptions = ref([]);
+const subjectKeyword = ref("");
+const subjectTreeRef = 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 getPreviousMonth = () => {
+ const date = new Date();
+ date.setDate(1);
+ date.setMonth(date.getMonth() - 1);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ return `${year}-${month}`;
+};
+
+const defaultMonth = getPreviousMonth();
+filters.startMonth = defaultMonth;
+filters.endMonth = defaultMonth;
+
+const fallbackSubjects = [
+ { code: "1001", name: "搴撳瓨鐜伴噾" },
+ { code: "1002", name: "閾惰瀛樻" },
+ { code: "1122", name: "搴旀敹璐︽" },
+ { code: "2202", name: "搴斾粯璐︽" },
+ { code: "6001", name: "涓昏惀涓氬姟鏀跺叆" },
];
-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 toTree = (nodes = []) =>
+ nodes
+ .filter(item => item.subjectCode && item.subjectName)
+ .map(item => ({
+ code: item.subjectCode,
+ name: item.subjectName,
+ children: toTree(item.children || []),
+ }));
const findSubject = (options, code) => {
for (const item of options) {
@@ -121,65 +135,104 @@
return null;
};
+const currentSubject = computed(() => {
+ if (!filters.subject) return null;
+ return findSubject(subjectOptions.value, filters.subject);
+});
+
+const getFirstSubjectCode = (nodes = []) => {
+ for (const item of nodes) {
+ if (item.code) return item.code;
+ if (item.children && item.children.length > 0) {
+ const childCode = getFirstSubjectCode(item.children);
+ if (childCode) return childCode;
+ }
+ }
+ return "";
+};
+
+const setDefaultSubjectSelection = async () => {
+ const firstCode = getFirstSubjectCode(subjectOptions.value);
+ if (!firstCode) {
+ filters.subject = "";
+ subjectTreeRef.value?.setCurrentKey(null);
+ return;
+ }
+ filters.subject = firstCode;
+ await nextTick();
+ subjectTreeRef.value?.setCurrentKey(firstCode);
+};
+
+const filterSubjectNode = (value, data) => {
+ const keyword = value?.trim();
+ if (!keyword) return true;
+ return `${data.code}${data.name}`.includes(keyword);
+};
+
+watch(subjectKeyword, (value) => {
+ subjectTreeRef.value?.filter(value || "");
+});
+
+const handleSubjectClick = async (data) => {
+ filters.subject = data.code;
+ await getTableData();
+};
+
+const loadSubjectOptions = async () => {
+ let options = [];
+ try {
+ const { data } = await listAccountSubject({
+ current: 1,
+ size: 1000,
+ status: 0,
+ });
+ options = toTree(data?.records || []);
+ } catch (error) {
+ // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず锛屼笅闈㈣蛋鍏滃簳绉戠洰
+ }
+ if (options.length === 0) {
+ options = fallbackSubjects.map(item => ({ ...item, children: [] }));
+ }
+ subjectOptions.value = options;
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
+};
+
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 = () => {
+// 鑱旇皟绾﹀畾锛氭�昏处鎺ュ彛杩斿洖琛屾暟缁勶紙rowType/date/voucherNo/summary/debit/credit/direction/balance锛�
+const getTableData = async () => {
if (!currentSubject.value) {
dataList.value = [];
return;
}
- dataList.value = [...mockData];
+ try {
+ const { data } = await getGeneralLedger({
+ subjectCode: currentSubject.value.code,
+ startMonth: filters.startMonth,
+ endMonth: filters.endMonth,
+ });
+ dataList.value = Array.isArray(data) ? data : data?.records || [];
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
+ }
};
-const resetFilters = () => {
- filters.subject = [];
- filters.startMonth = "2024-01";
- filters.endMonth = "2024-03";
+const resetFilters = async () => {
+ filters.startMonth = defaultMonth;
+ filters.endMonth = defaultMonth;
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;
+ subjectKeyword.value = "";
+ subjectTreeRef.value?.filter("");
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
};
const handlePrint = () => {
@@ -190,22 +243,43 @@
ElMessage.success("瀵煎嚭鎴愬姛");
};
-onMounted(() => {
- // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+onMounted(async () => {
+ await loadSubjectOptions();
});
</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;
- }
+.ledger-layout {
+ display: flex;
+ gap: 16px;
+}
+
+.subject-panel {
+ width: 260px;
+ flex-shrink: 0;
+ padding: 12px;
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ background-color: #fff;
+}
+
+.subject-tree-scroll {
+ height: 600px;
+ margin-top: 12px;
+}
+
+.subject-node {
+ display: inline-flex;
+ align-items: center;
+}
+
+.ledger-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.filter-form {
+ margin-bottom: 12px;
}
.text-primary {
@@ -227,4 +301,12 @@
color: #e6a23c;
font-weight: bold;
}
+
+.subject-panel :deep(.el-tree-node__content) {
+ height: 34px;
+}
+
+.subject-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
+ background-color: #f0f7ff;
+}
</style>
diff --git a/src/views/financialManagement/voucher/index.vue b/src/views/financialManagement/voucher/index.vue
index 817185c..1aa6f69 100644
--- a/src/views/financialManagement/voucher/index.vue
+++ b/src/views/financialManagement/voucher/index.vue
@@ -9,9 +9,12 @@
</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-option
+ v-for="item in creatorOptions"
+ :key="item"
+ :label="item"
+ :value="item"
+ />
</el-select>
</el-form-item>
<el-form-item label="鐘舵��:">
@@ -29,13 +32,13 @@
<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;" />
+ <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>
+ <!-- <el-button @click="handleImport" icon="Upload">瀵煎叆</el-button> -->
+ <!-- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button> -->
</div>
</div>
<PIMTable
@@ -62,9 +65,9 @@
</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>
+ <el-button type="primary" link @click="edit(row)" v-if="canEditVoucher(row.status)">缂栬緫</el-button>
+ <el-button type="success" link @click="handlePost(row)" v-if="canEditVoucher(row.status)">杩囪处</el-button>
+ <el-button type="danger" link @click="handleCancel(row)" v-if="canEditVoucher(row.status)">浣滃簾</el-button>
</template>
</PIMTable>
</div>
@@ -75,25 +78,29 @@
<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">
+ <el-form :model="form" :rules="rules" :disabled="isViewMode" 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-select v-model="form.voucherPrefix" :disabled="isViewMode" style="width: 70px;">
<el-option label="璁�" value="璁�" />
+ <el-option label="鐜�" value="鐜�" />
+ <el-option label="閾�" value="閾�" />
+ <el-option label="杞�" value="杞�" />
+ <el-option label="鏀�" value="鏀�" />
+ <el-option label="浠�" value="浠�" />
</el-select>
- <el-input v-model="form.voucherNum" style="width: 60px;" />
+ <el-input v-model="form.voucherNum" :disabled="isViewMode" 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;" />
+ <el-date-picker v-model="form.voucherDate" :disabled="isViewMode" 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;" />
+ <el-input-number v-model="form.attachmentCount" :disabled="isViewMode" :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">
@@ -134,18 +141,28 @@
<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)" />
+ <el-input v-model="entry.summary" :disabled="isViewMode" 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>
+ <el-tree-select
+ v-model="entry.subjectCode"
+ :data="subjectTreeOptions"
+ :props="subjectTreeSelectProps"
+ :disabled="isViewMode"
+ placeholder="閫夋嫨绉戠洰"
+ filterable
+ check-strictly
+ clearable
+ :render-after-expand="false"
+ @change="(val) => handleSubjectChange(val, rowIndex)"
+ @focus="selectRow(rowIndex)"
+ />
+ <!-- <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" />
+ <el-input-number ref="amountInputRef" v-model="entry.debit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" :value-on-clear="undefined" size="small" @blur="finishEdit" class="full-width-input" />
</td>
</template>
<template v-else>
@@ -156,7 +173,7 @@
<!-- 璐锋柟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" />
+ <el-input-number ref="amountInputRef" v-model="entry.credit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" :value-on-clear="undefined" size="small" @blur="finishEdit" class="full-width-input" />
</td>
</template>
<template v-else>
@@ -165,7 +182,7 @@
</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>
+ <el-button type="danger" link size="small" @click="removeEntry(rowIndex)" icon="Delete" :disabled="isViewMode || form.entries.length <= 2">鍒犻櫎</el-button>
</td>
</tr>
<tr class="total-row">
@@ -182,22 +199,68 @@
</table>
</div>
<div class="voucher-toolbar">
- <el-button type="primary" link @click="addEntry" icon="Plus">鏂板琛�</el-button>
+ <el-button type="primary" link @click="addEntry" icon="Plus" :disabled="isViewMode">鏂板琛�</el-button>
</div>
<div class="voucher-footer">
<div class="creator-section">
- <span class="label">鍒跺崟浜猴細{{ form.creator }}</span>
+ <span class="label">鍒跺崟浜猴細</span>
+ <el-select
+ v-model="form.creator"
+ :disabled="isViewMode"
+ placeholder="璇烽�夋嫨鍒跺崟浜�"
+ filterable
+ clearable
+ style="width: 200px;"
+ >
+ <el-option
+ v-for="item in creatorOptions"
+ :key="item"
+ :label="item"
+ :value="item"
+ />
+ </el-select>
</div>
</div>
+ <!-- 缂栬緫妯″紡锛氫娇鐢� AttachmentUploadFile 涓婁紶缁勪欢 -->
+ <div class="voucher-attachment-upload" v-if="!isViewMode">
+ <div class="attachment-label">闄勪欢涓婁紶锛�</div>
+ <AttachmentUploadFile
+ v-model:fileList="form.attachments"
+ :disabled="isViewMode"
+ :limit="10"
+ :fileSize="50"
+ buttonText="鐐瑰嚮涓婁紶闄勪欢"
+ @change="handleAttachmentChange"
+ />
+ </div>
</el-form>
+ <!-- 鏌ョ湅妯″紡锛氬睍绀洪檮浠跺垪琛紙鏀惧湪 el-form 澶栭潰锛岄伩鍏嶈 disabled锛� -->
+ <div class="voucher-attachment-upload" v-if="isViewMode && form.attachments?.length">
+ <div class="attachment-label">闄勪欢鍒楄〃锛�</div>
+ <el-table :data="form.attachments" border class="attachment-table">
+ <el-table-column label="闄勪欢鍚嶇О" show-overflow-tooltip>
+ <template #default="scope">
+ {{ scope.row.originalFilename || scope.row.name || scope.row.fileName || '鏈懡鍚嶆枃浠�' }}
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="previewFile(scope.row)">棰勮</el-button>
+ <el-button link type="primary" size="small" @click="downloadFile(scope.row)">涓嬭浇</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
</div>
<template #footer>
<div>
- <el-button type="primary" @click="submitForm" :disabled="!isBalanced">淇濆瓨</el-button>
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button v-if="!isViewMode" type="primary" @click="submitForm" :disabled="!isBalanced">淇濆瓨</el-button>
+ <el-button @click="dialogVisible = false">{{ isViewMode ? '鍏抽棴' : '鍙栨秷' }}</el-button>
</div>
</template>
</FormDialog>
+ <!-- 鏂囦欢棰勮缁勪欢 -->
+ <FilePreview ref="filePreviewRef" />
</div>
</template>
@@ -205,10 +268,28 @@
import { ref, reactive, onMounted, computed, nextTick } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import AttachmentUploadFile from "@/components/AttachmentUpload/file/index.vue";
+import FileList from "@/components/Dialog/FileList.vue";
+import FilePreview from "@/components/filePreview/index.vue";
+import download from "@/plugins/download.js";
+import useUserStore from "@/store/modules/user";
+import { userListNoPageByTenantId } from "@/api/system/user";
+import { listAccountSubject } from "@/api/financialManagement/accountSubject";
+import {
+ listVoucherPage,
+ addVoucher,
+ updateVoucher,
+ postVoucher,
+ cancelVoucher,
+ getVoucherDetail,
+} from "@/api/financialManagement/voucher";
defineOptions({
name: "鍑瘉绠$悊",
});
+
+const userStore = useUserStore();
+const getDefaultCreator = () => userStore.nickName || userStore.name || "寮犱笁";
const filters = reactive({
voucherNo: "",
@@ -227,39 +308,94 @@
{ 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: "debit", dataType: "slot", slot: "debit" },
+ { label: "璐锋柟閲戦", prop: "credit", dataType: "slot", slot: "credit" },
{ label: "鍒跺崟浜�", prop: "creator", width: "100" },
- { label: "鐘舵��", prop: "status", slot: "status" },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+ { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "220", fixed: "right" },
];
const dataList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
+const dialogMode = ref("add");
const isEdit = ref(false);
const currentId = ref(null);
+const isViewMode = computed(() => dialogMode.value === "view");
+const filePreviewRef = 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 fallbackSubjectTree = [
+ { subjectCode: "1001", subjectName: "搴撳瓨鐜伴噾", balanceDirection: "鍊熸柟", children: [] },
+ { subjectCode: "1002", subjectName: "閾惰瀛樻", balanceDirection: "鍊熸柟", children: [] },
+ { subjectCode: "1122", subjectName: "搴旀敹璐︽", balanceDirection: "鍊熸柟", children: [] },
+ { subjectCode: "2202", subjectName: "搴斾粯璐︽", balanceDirection: "璐锋柟", children: [] },
+ { subjectCode: "5001", subjectName: "鐢熶骇鎴愭湰", balanceDirection: "鍊熸柟", children: [] },
+ { subjectCode: "6001", subjectName: "涓昏惀涓氬姟鏀跺叆", balanceDirection: "璐锋柟", children: [] },
+ { subjectCode: "6401", subjectName: "涓昏惀涓氬姟鎴愭湰", balanceDirection: "鍊熸柟", children: [] },
];
-const form = reactive({
+const subjectTreeOptions = ref([]);
+const subjectList = ref([]);
+const subjectTreeSelectProps = {
+ children: "children",
+ label: "label",
+ value: "value",
+};
+
+const buildSubjectTreeOptions = (nodes = [], flatList = []) =>
+ (nodes || [])
+ .filter(item => item.subjectCode && item.subjectName)
+ .map(item => {
+ const balanceDirection = item.balanceDirection || "";
+ const flatItem = {
+ code: item.subjectCode,
+ name: item.subjectName,
+ balanceDirection,
+ };
+ flatList.push(flatItem);
+ return {
+ value: flatItem.code,
+ label: `${flatItem.code} ${flatItem.name}${balanceDirection ? ` [${balanceDirection}]` : ""}`,
+ children: buildSubjectTreeOptions(item.children || [], flatList),
+ };
+ });
+
+const createEmptyEntry = () => ({
+ subjectCode: "",
+ subjectName: "",
+ balanceDirection: "",
+ summary: "",
+ debit: undefined,
+ credit: undefined,
+});
+
+const createDefaultForm = () => ({
voucherNo: "",
voucherPrefix: "璁�",
voucherNum: "",
voucherDate: "",
attachmentCount: 0,
- entries: [],
- creator: "寮犱笁",
+ attachments: [],
+ entries: [createEmptyEntry(), createEmptyEntry()],
+ creator: getDefaultCreator(),
remark: "",
+});
+
+const form = reactive({
+ ...createDefaultForm(),
+});
+
+const userOptions = ref([]);
+
+const creatorOptions = computed(() => {
+ const source = [
+ ...userOptions.value.map(item => item.nickName || item.userName || item.name),
+ getDefaultCreator(),
+ form.creator,
+ filters.creator,
+ ];
+ return [...new Set(source.filter(Boolean))];
});
const selectedRowIndex = ref(-1);
@@ -276,12 +412,6 @@
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);
@@ -304,32 +434,79 @@
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
+const normalizeVoucherStatus = status => String(status || "").toLowerCase();
+
+const canEditVoucher = status => {
+ const key = normalizeVoucherStatus(status);
+ return key === "unposted" || status === "鏈繃璐�";
+};
+
const getStatusLabel = (status) => {
+ const key = normalizeVoucherStatus(status);
const map = { unposted: "鏈繃璐�", posted: "宸茶繃璐�", cancelled: "宸蹭綔搴�" };
- return map[status] || status;
+ return map[key] || status;
};
const getStatusType = (status) => {
+ const key = normalizeVoucherStatus(status);
const map = { unposted: "warning", posted: "success", cancelled: "info" };
- return map[status] || "";
+ return map[key] || "";
};
-const getTableData = () => {
- let result = [...mockData];
- if (filters.voucherNo) {
- result = result.filter(item => item.voucherNo.includes(filters.voucherNo));
+// 鑱旇皟绾﹀畾锛氬垎椤靛弬鏁颁娇鐢� current/size锛屾棩鏈熻寖鍥存媶鍒嗕负 startDate/endDate
+const getTableData = async () => {
+ try {
+ const [startDate, endDate] =
+ filters.dateRange && filters.dateRange.length === 2 ? filters.dateRange : ["", ""];
+ const { data } = await listVoucherPage({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ voucherNo: filters.voucherNo,
+ creator: filters.creator,
+ status: filters.status,
+ startDate,
+ endDate,
+ });
+ dataList.value = data?.records || [];
+ pagination.total = Number(data?.total || 0);
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
- if (filters.dateRange && filters.dateRange.length === 2) {
- result = result.filter(item => item.voucherDate >= filters.dateRange[0] && item.voucherDate <= filters.dateRange[1]);
+};
+
+// 鍑瘉鍒嗗綍閲岀殑绉戠洰涓嬫媺涓庢�昏处绉戠洰淇濇寔涓�鑷达紝閬垮厤鎻愪氦涓嶅瓨鍦ㄧ鐩�
+const loadSubjectList = async () => {
+ try {
+ const { data } = await listAccountSubject({
+ current: 1,
+ size: 1000,
+ status: 0
+ });
+ const flatList = [];
+ const treeOptions = buildSubjectTreeOptions(data?.records || [], flatList);
+ if (treeOptions.length > 0) {
+ subjectTreeOptions.value = treeOptions;
+ subjectList.value = flatList;
+ return;
+ }
+ const fallbackFlatList = [];
+ subjectTreeOptions.value = buildSubjectTreeOptions(fallbackSubjectTree, fallbackFlatList);
+ subjectList.value = fallbackFlatList;
+ } catch (error) {
+ // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず閿欒锛岃繖閲屼繚鐣欓粯璁ょ鐩綔涓哄厹搴�
+ const fallbackFlatList = [];
+ subjectTreeOptions.value = buildSubjectTreeOptions(fallbackSubjectTree, fallbackFlatList);
+ subjectList.value = fallbackFlatList;
}
- if (filters.creator) {
- result = result.filter(item => item.creator === filters.creator);
+};
+
+const loadUserOptions = async () => {
+ try {
+ const { data } = await userListNoPageByTenantId();
+ userOptions.value = Array.isArray(data) ? data : [];
+ } catch (error) {
+ userOptions.value = [];
}
- 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 = () => {
@@ -348,7 +525,35 @@
};
const addEntry = () => {
- form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
+ if (isViewMode.value) {
+ return;
+ }
+ form.entries.push(createEmptyEntry());
+};
+
+const handleAttachmentChange = (fileList) => {
+ form.attachmentCount = fileList?.length || 0;
+};
+
+// 浣跨敤椤圭洰灏佽鐨� filePreview 缁勪欢棰勮鏂囦欢
+const previewFile = (row) => {
+ const url = row.previewURL || row.previewUrl || row.url;
+ if (url && filePreviewRef.value) {
+ filePreviewRef.value.open(url);
+ } else {
+ ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曢瑙�');
+ }
+};
+
+// 浣跨敤椤圭洰灏佽鐨� download 鎻掍欢涓嬭浇鏂囦欢
+const downloadFile = (row) => {
+ const url = row.downloadURL || row.downloadUrl || row.url;
+ if (url) {
+ const filename = row.originalFilename || row.name || row.fileName || 'download';
+ download.byUrl(url, filename);
+ } else {
+ ElMessage.warning('鏂囦欢鍦板潃鏃犳晥锛屾棤娉曚笅杞�');
+ }
};
const selectRow = (index) => {
@@ -356,6 +561,9 @@
};
const openAmountInput = (index, type) => {
+ if (isViewMode.value) {
+ return;
+ }
editingCell.row = index;
editingCell.type = type;
nextTick(() => {
@@ -402,65 +610,86 @@
};
const removeEntry = (index) => {
+ if (isViewMode.value) {
+ return;
+ }
+ if (form.entries.length <= 2) {
+ return;
+ }
form.entries.splice(index, 1);
- calculateTotal();
};
const handleSubjectChange = (val, index) => {
- const subject = subjectList.find(item => item.code === val);
+ const subject = subjectList.value.find(item => item.code === val);
if (subject) {
form.entries[index].subjectName = subject.name;
+ form.entries[index].balanceDirection = subject.balanceDirection || "";
+ } else {
+ form.entries[index].subjectName = "";
+ form.entries[index].balanceDirection = "";
}
-};
-
-const calculateTotal = () => {
- // 鑷姩璁$畻锛岀敱computed灞炴�у鐞�
};
const add = () => {
+ dialogMode.value = "add";
isEdit.value = false;
+ currentId.value = null;
dialogTitle.value = "鏂板鍑瘉";
- const nextNum = String(mockData.length + 1).padStart(2, "0");
- Object.assign(form, {
- voucherNo: "璁�-" + nextNum,
+ const nextNum = String((pagination.total || 0) + 1).padStart(4, "0");
+ Object.assign(form, createDefaultForm(), {
voucherPrefix: "璁�",
voucherNum: nextNum,
+ voucherNo: `璁�-${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 });
+const openVoucherDialog = async (row, mode = "edit") => {
+ try {
+ dialogMode.value = mode;
+ isEdit.value = mode === "edit";
+ currentId.value = row.id;
+ dialogTitle.value = mode === "view" ? "鏌ョ湅鍑瘉" : "缂栬緫鍑瘉";
+ const { data } = await getVoucherDetail(row.id);
+ const detail = data || row;
+ const parts = (detail.voucherNo || "").split("-");
+ const attachments = detail.storageBlobVOList || detail.storageBlobDTOs || detail.attachments || [];
+ Object.assign(form, createDefaultForm(), {
+ ...detail,
+ voucherPrefix: parts[0] || "璁�",
+ voucherNum: parts[1] || "",
+ creator: detail.creator || getDefaultCreator(),
+ attachments,
+ entries:
+ detail.entries?.map(item => ({
+ subjectCode: item.subjectCode || "",
+ subjectName: item.subjectName || "",
+ balanceDirection: item.balanceDirection || "",
+ summary: item.summary || "",
+ debit: Number(item.debit || 0),
+ credit: Number(item.credit || 0),
+ })) || [],
+ });
+ if (form.entries.length < 2) {
+ while (form.entries.length < 2) {
+ form.entries.push(createEmptyEntry());
+ }
}
+ selectedRowIndex.value = 0;
+ dialogVisible.value = true;
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
- selectedRowIndex.value = 0;
- dialogVisible.value = true;
};
-const view = (row) => {
- ElMessage.info(`鏌ョ湅鍑瘉: ${row.voucherNo}`);
+const edit = async row => {
+ await openVoucherDialog(row, "edit");
+};
+
+const view = async row => {
+ await openVoucherDialog(row, "view");
};
const handlePost = (row) => {
@@ -468,13 +697,10 @@
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "info",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].status = "posted";
- }
+ }).then(async () => {
+ await postVoucher({ id: row.id });
ElMessage.success("杩囪处鎴愬姛");
- getTableData();
+ await getTableData();
});
};
@@ -483,13 +709,10 @@
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "warning",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].status = "cancelled";
- }
+ }).then(async () => {
+ await cancelVoucher({ id: row.id });
ElMessage.success("浣滃簾鎴愬姛");
- getTableData();
+ await getTableData();
});
};
@@ -502,45 +725,80 @@
};
const submitForm = () => {
- formRef.value.validate((valid) => {
+ if (isViewMode.value) {
+ dialogVisible.value = false;
+ return;
+ }
+ formRef.value.validate(async valid => {
if (valid) {
+ // 鍓嶇疆鏍¢獙锛氫笌鍚庣瑙勫垯瀵归綈锛屽噺灏戞棤鏁堣姹�
if (!isBalanced.value) {
ElMessage.error("鍊熻捶涓嶅钩琛★紝璇锋鏌ュ垎褰�");
return;
}
- const validEntries = form.entries.filter(e => e.subjectCode && (e.debit > 0 || e.credit > 0));
+ const validEntries = form.entries.filter(
+ entry => entry.subjectCode && (Number(entry.debit) > 0 || Number(entry.credit) > 0)
+ );
+ if (validEntries.length === 0) {
+ ElMessage.error("璇疯嚦灏戝~鍐欎竴鏉℃湁鏁堝垎褰�");
+ return;
+ }
+
+ const invalidEntry = validEntries.find(
+ entry => Number(entry.debit) > 0 && Number(entry.credit) > 0
+ );
+ if (invalidEntry) {
+ ElMessage.error("鍚屼竴鍒嗗綍涓嶈兘鍚屾椂濉啓鍊熸柟鍜岃捶鏂�");
+ return;
+ }
+
const summary = validEntries.find(e => e.debit > 0)?.summary || "";
const voucherNo = `${form.voucherPrefix}-${form.voucherNum}`;
const dataToSave = {
- ...form,
voucherNo,
+ voucherDate: form.voucherDate,
summary,
+ creator: form.creator,
+ attachmentCount: Number(form.attachmentCount || 0),
+ remark: form.remark,
debit: totalDebitEntry.value,
credit: totalCreditEntry.value,
- entries: validEntries,
+ storageBlobDTOs: form.attachments || [],
+ entries: validEntries.map(entry => ({
+ subjectCode: entry.subjectCode,
+ subjectName: entry.subjectName,
+ summary: entry.summary,
+ debit: Number(entry.debit || 0),
+ credit: Number(entry.credit || 0),
+ })),
};
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...dataToSave };
+ try {
+ if (isEdit.value) {
+ await updateVoucher({
+ id: currentId.value,
+ ...dataToSave,
+ });
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ await addVoucher(dataToSave);
+ ElMessage.success("鏂板鎴愬姛");
}
- 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;
+ await getTableData();
+ } catch (error) {
+ // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
- dialogVisible.value = false;
- getTableData();
}
});
};
-onMounted(() => {
- getTableData();
+onMounted(async () => {
+ await loadUserOptions();
+ await loadSubjectList();
+ await getTableData();
});
</script>
@@ -610,6 +868,21 @@
.voucher-attachment-section {
display: flex;
align-items: center;
+ }
+}
+
+.voucher-attachment-upload {
+ margin-top: 15px;
+ padding: 0 10px;
+
+ .attachment-label {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 10px;
+ }
+
+ .attachment-table {
+ border-radius: 4px;
}
}
@@ -780,7 +1053,8 @@
.col-subject {
position: relative;
- .el-select {
+ .el-select,
+ .el-tree-select {
.el-input input {
font-size: 12px;
}
diff --git a/src/views/index.vue b/src/views/index.vue
index 00d4312..0d71970 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -1,156 +1,321 @@
<template>
- <div class="dashboard">
- <!-- 椤堕儴妯悜涓ゆ爮 -->
- <div class="dashboard-top">
- <!-- 宸︼細浼佷笟淇℃伅+涓夊ぇ鏁版嵁鍗$墖锛堜笂涓嬫帓鍒楋級 -->
- <div class="top-left">
- <div class="company-info">
- <!-- 椤堕儴闂�欐潯 -->
- <div class="welcome-banner">
- <div class="welcome-title">
- <span class="welcome-user">{{ userStore.nickName || '绯荤粺绠$悊鍛�' }}</span>
- <span> 鎮ㄥソ锛佺鎮ㄥ紑蹇冩瘡涓�澶�</span>
- </div>
- <div class="welcome-time">鐧诲綍浜�: {{ userStore.currentLoginTime }}</div>
+ <div class="dashboard-cockpit">
+ <section class="cockpit-panel welcome-panel">
+ <div class="welcome-user">
+ <el-avatar :src="welcomeAvatar" class="avatar" @error="handleWelcomeAvatarError">
+ <el-icon><UserFilled /></el-icon>
+ </el-avatar>
+ <div class="welcome-text">
+ <div class="welcome-title">
+ {{ greetingText }}锛寋{ userStore.nickName || userStore.name || "瓒呯骇绠$悊鍛�" }} 馃憢
</div>
+ <div class="welcome-subtitle">涓撴敞宸ヤ笟鏁板瓧鍖栵紝鍔╁姏鏅洪�犲崌绾�</div>
+ </div>
+ </div>
+ <div class="welcome-meta">
+ <div class="meta-time digital-number">{{ nowTime }}</div>
+ <div class="meta-tip">MES / MOM 鐢熶骇杩愯惀椹鹃┒鑸�</div>
+ </div>
+ </section>
- <!-- 鐢ㄦ埛淇℃伅鍗$墖 -->
- <div class="user-card">
- <img :src="userStore.avatar" class="avatar" alt="" />
- <div class="user-card-main">
- <div class="user-name">{{ userStore.name }}</div>
- <div class="user-role">{{ userStore.roleName }}</div>
- <div class="user-meta">
- <span>{{ userStore.phoneNumber || '123456789' }}</span>
- <span class="sep">|</span>
- <span>{{ userStore.deptName || '缁勭粐鏋舵瀯' }}</span>
- <span class="sep">|</span>
- <span>{{ userStore.postName || '宀椾綅鍚�' }}</span>
+ <section v-if="dashboardCards.length > 0" class="top-row">
+ <div class="stats-grid">
+ <article
+ v-for="card in dashboardCards"
+ :key="card.key"
+ class="stat-card"
+ :class="card.key"
+ >
+ <div class="stat-header">
+ <div class="stat-title-wrap">
+ <div class="stat-title">{{ card.title }}</div>
+ <div class="stat-desc">{{ card.desc }}</div>
+ </div>
+ <div class="stat-icon-orb">
+ <el-icon>
+ <component :is="card.icon" />
+ </el-icon>
+ </div>
+ </div>
+ <div class="stat-value digital-number">{{ card.value }}</div>
+ <div class="stat-footer">
+ <span>{{ card.subLabel }}</span>
+ <strong class="digital-number">{{ card.subValue }}</strong>
+ </div>
+ <div class="stat-trend">{{ card.trend }}</div>
+ <div class="stat-wave" aria-hidden="true"></div>
+ </article>
+ </div>
+ </section>
+
+ <section v-if="hasVisiblePanels" class="main-grid">
+ <div v-if="hasLeftPanels" class="left-column">
+ <div v-if="visiblePanels.process" class="cockpit-panel process-panel">
+ <div class="panel-title-row">
+ <div class="panel-title">宸ュ簭鏁版嵁鐢熶骇缁熻鏄庣粏</div>
+ <div class="panel-actions">
+ <el-button type="primary" size="small" plain @click="openProcessDialog">閫夋嫨宸ュ簭</el-button>
+ <el-button size="small" plain @click="resetProcessFilter">閲嶇疆</el-button>
+ <el-radio-group v-model="processRange" size="small" @change="refreshProcessStats">
+ <el-radio-button :value="1">鏃�</el-radio-button>
+ <el-radio-button :value="2">鍛�</el-radio-button>
+ <el-radio-button :value="3">鏈�</el-radio-button>
+ </el-radio-group>
+ </div>
+ </div>
+ <div class="process-body">
+ <div class="process-chart" :class="{ empty: !hasProcessData }">
+ <Echarts
+ :options="chartBaseOptions"
+ :chartStyle="{ width: '100%', height: '100%' }"
+ :grid="processGrid"
+ :series="processSeries"
+ :tooltip="processTooltip"
+ :xAxis="processXAxis"
+ :yAxis="processYAxis"
+ :style="{ height: hasProcessData ? '340px' : '280px' }"
+ @click="handleChartClick"
+ />
+ <div v-if="!hasProcessData" class="chart-empty">
+ <el-icon><DataAnalysis /></el-icon>
+ <div class="chart-empty-title">鏆傛棤宸ュ簭鏁版嵁</div>
+ <div class="chart-empty-desc">绛夊緟鐢熶骇鎶ュ伐鏁版嵁鎺ュ叆鍚庤嚜鍔ㄧ敓鎴愮粺璁″浘琛�</div>
+ <div class="chart-empty-actions">
+ <el-button size="small" @click="refreshProcessStats">鍒锋柊鏁版嵁</el-button>
+ <el-button size="small" type="primary" plain @click="openProcessDialog">閫夋嫨宸ュ簭</el-button>
+ </div>
+ </div>
+ </div>
+ <div class="process-aside">
+ <div class="process-legend">
+ <div class="process-name">{{ processAside.processName }}</div>
+ <div class="process-legend-item"><span class="dot dot-blue"></span>鎶曞叆閲�</div>
+ <div class="process-legend-item"><span class="dot dot-orange"></span>鎶ュ簾閲�</div>
+ <div class="process-legend-item"><span class="dot dot-cyan"></span>浜у嚭閲�</div>
+ </div>
+ <div class="process-card">
+ <div class="process-label">绱鎬绘姇鍏�</div>
+ <div class="process-value digital-number">{{ formatAmount(processAside.totalInput) }}</div>
+ </div>
+ <div class="process-card">
+ <div class="process-label">绱鎬绘姤搴�</div>
+ <div class="process-value digital-number">{{ formatAmount(processAside.totalScrap) }}</div>
+ </div>
+ <div class="process-card">
+ <div class="process-label">绱鎬讳骇鍑�</div>
+ <div class="process-value digital-number">{{ formatAmount(processAside.totalOutput) }}</div>
</div>
</div>
</div>
</div>
- </div>
- <div class="data-cards">
- <div class="data-card sales">
- <div class="data-title">閿�鍞暟鎹�</div>
- <div class="data-num">
- <div>
- <div class="data-desc">鏈湀閿�鍞/鍏�</div>
- <div class="data-value">{{ businessInfo.monthSaleMoney }}</div>
- </div>
- <div>
- <div class="data-desc">鏈紑绁ㄩ噾棰�/鍏�</div>
- <div class="data-value">{{ businessInfo.monthSaleHaveMoney }}</div>
- </div>
- </div>
- </div>
- <div class="data-card purchase">
- <div class="data-title">閲囪喘鏁版嵁</div>
- <div class="data-num">
- <div>
- <div class="data-desc">鏈湀閲囪喘棰�/鍏�</div>
- <div class="data-value">{{ businessInfo.monthPurchaseMoney }}</div>
- </div>
- <div>
- <div class="data-desc">寰呬粯娆鹃噾棰�/鍏�</div>
- <div class="data-value">{{ businessInfo.monthPurchaseHaveMoney }}</div>
- </div>
- </div>
- </div>
- <div class="data-card inventory">
- <div class="data-title">搴撳瓨鏁版嵁</div>
- <div class="data-num">
- <div>
- <div class="data-desc">褰撳墠搴撳瓨鎬婚噺/浠�</div>
- <div class="data-value">{{ businessInfo.inventoryNum }}</div>
- </div>
- <div>
- <div class="data-desc">浠婃棩鍏ュ簱/浠�</div>
- <div class="data-value">{{ businessInfo.todayInventoryNum }}</div>
- </div>
- </div>
- </div>
- </div>
- <!-- 鍙筹細寰呭姙浜嬮」 -->
- <div class="todo-panel">
- <div class="section-title">寰呭姙浜嬮」</div>
- <ul class="todo-list" v-if="todoList.length > 0">
- <li v-for="item in todoList" :key="item.id">
- <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">
- <div style="display: flex;justify-content: space-between;align-items: center;">
- <div class="todo-title">寰呭姙缂栧彿锛歿{ item.approveId }}</div>
- <div class="todo-division">閮ㄩ棬锛歿{ item.approveDeptName }}</div>
- <div class="todo-time">{{ item.approveTime }}</div>
- </div>
- <div class="todo-division">寰呭姙浜嬬敱锛歿{ item.approveReason }}</div>
- </div>
- </li>
- </ul>
- <div v-else style="text-align: center">
- 鏆傛棤鏁版嵁
- </div>
- </div>
- </div>
- <div class="dashboard-row">
- <div class="main-panel process-panel">
- <div class="process-panel__header">
- <div class="section-title">宸ュ簭鏁版嵁鐢熶骇缁熻鏄庣粏</div>
- <div style="display: flex; gap: 10px; align-items: center;">
- <el-button type="primary" size="small" plain icon="Filter" @click="openProcessDialog">閫夋嫨宸ュ簭</el-button>
- <el-button type="info" size="small" plain icon="Refresh" @click="resetProcessFilter">閲嶇疆</el-button>
- <el-radio-group v-model="processRange" size="small" @change="refreshProcessStats">
- <el-radio-button :value="1">鏃�</el-radio-button>
- <el-radio-button :value="2">鍛�</el-radio-button>
- <el-radio-button :value="3">鏈�</el-radio-button>
+ <div v-if="visiblePanels.order" class="cockpit-panel order-panel">
+ <div class="panel-title-row">
+ <div class="panel-title">鐢熶骇璁㈠崟杩涘害</div>
+ <el-radio-group v-model="orderFilter" size="small">
+ <el-radio-button label="all">鍏ㄩ儴({{ orderProgressMeta.total }})</el-radio-button>
+ <el-radio-button label="waiting">寰呭紑濮�({{ orderProgressMeta.waitingCount }})</el-radio-button>
+ <el-radio-button label="inProgress">杩涜涓�({{ orderProgressMeta.inProgressCount }})</el-radio-button>
+ <el-radio-button label="completed">宸插畬鎴�({{ orderProgressMeta.completedCount }})</el-radio-button>
+ <el-radio-button label="paused">宸叉殏鍋�({{ orderProgressMeta.pausedCount }})</el-radio-button>
</el-radio-group>
</div>
+ <el-table :data="filteredOrders" stripe>
+ <el-table-column prop="orderNo" label="璁㈠崟缂栧彿" min-width="150" />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="120" />
+ <el-table-column prop="planQty" label="璁″垝鏁伴噺" min-width="90" />
+ <el-table-column prop="completedQty" label="宸插畬鎴�" min-width="90" />
+ <el-table-column label="瀹屾垚鐜�" min-width="180">
+ <template #default="{ row }">
+ <div class="table-progress">
+ <el-progress
+ :stroke-width="8"
+ :percentage="row.completionRate"
+ :show-text="false"
+ status="success"
+ />
+ <span>{{ row.completionRate }}%</span>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column prop="deliveryDate" label="浜ゆ湡" min-width="110" />
+ <el-table-column label="鐘舵��" min-width="90">
+ <template #default="{ row }">
+ <el-tag :type="getOrderStatusType(row.status)" effect="light">
+ {{ row.statusLabel || getOrderStatusText(row.status) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
</div>
- <div class="process-panel__body">
- <div class="process-panel__chart">
- <Echarts :chartStyle="{ width: '100%', height: '100%' }" :grid="processGrid" :series="processSeries"
- :tooltip="processTooltip" :xAxis="processXAxis" :yAxis="processYAxis" style="height: 100%"
- @click="handleChartClick" />
- </div>
-
- <div class="process-panel__aside">
- <div class="process-legend">
- <div class="process-legend__item">
- <span class="dot dot-blue"></span><span>鎶曞叆閲�</span>
- </div>
- <div class="process-legend__item">
- <span class="dot dot-yellow"></span><span>鎶ュ簾閲�</span>
- </div>
- <div class="process-legend__item">
- <span class="dot dot-teal"></span><span>浜у嚭閲�</span>
+ <div v-if="visiblePanels.contract" class="cockpit-panel contract-panel">
+ <div class="panel-title">瀹㈡埛鍚堝悓閲戦鍒嗘瀽</div>
+ <div class="contract-summary">
+ <div class="contract-card">
+ <div class="contract-name">鎬诲悎鍚岄噾棰�(鍏�)</div>
+ <div class="contract-main digital-number">{{ formatNumber(sum) }}</div>
+ <div class="contract-compare">
+ 鍚屾瘮
+ <span class="rise">{{ trendText(yny) }}</span>
+ 鐜瘮
+ <span class="rise">{{ trendText(chain) }}</span>
</div>
</div>
-
- <div class="process-card process-card--name">{{ processAside.processName }}</div>
-
- <div class="process-card">
- <div class="process-card__label">绱鎬绘姇鍏�</div>
- <div class="process-card__value">{{ formatAmount(processAside.totalInput) }}
- </div>
- </div>
- <div class="process-card">
- <div class="process-card__label">绱鎬绘姤搴�</div>
- <div class="process-card__value">{{ formatAmount(processAside.totalScrap) }}
- </div>
- </div>
- <div class="process-card">
- <div class="process-card__label">绱鎬讳骇鍑�</div>
- <div class="process-card__value">{{ formatAmount(processAside.totalOutput) }}
- </div>
+ <div class="contract-chart-wrap">
+ <Echarts
+ :options="chartBaseOptions"
+ :legend="pieLegend"
+ :chartStyle="chartStylePie"
+ :series="materialPieSeries"
+ :tooltip="pieTooltip"
+ />
</div>
</div>
+ <ul class="contract-list">
+ <li v-for="item in materialPieSeries[0].data" :key="item.name">
+ <span class="legend-dot" :style="{ backgroundColor: item.itemStyle?.color }"></span>
+ <span class="contract-item-name">{{ item.name }}</span>
+ <span class="contract-item-rate">{{ item.rate }}%</span>
+ <span class="contract-item-value digital-number">楼{{ formatNumber(item.value) }}</span>
+ </li>
+ </ul>
+ </div>
+
+ <div v-if="visiblePanels.quality" class="cockpit-panel quality-panel">
+ <div class="panel-title-row">
+ <div class="panel-title">璐ㄩ噺缁熻</div>
+ <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo">
+ <el-radio-button :value="1">鍛�</el-radio-button>
+ <el-radio-button :value="2">鏈�</el-radio-button>
+ <el-radio-button :value="3">瀛e害</el-radio-button>
+ </el-radio-group>
+ </div>
+ <div class="quality-cards">
+ <div class="quality-card one">鍘熸潗鏂欏凡妫�鏁伴噺 <span>{{ qualityStatisticsObject.supplierNum }}浠�</span></div>
+ <div class="quality-card two">杩囩▼妫�楠屾暟閲� <span>{{ qualityStatisticsObject.processNum }}浠�</span></div>
+ <div class="quality-card three">鍑哄巶宸叉鏁伴噺 <span>{{ qualityStatisticsObject.factoryNum }}浠�</span></div>
+ </div>
+ <Echarts
+ :options="chartBaseOptions"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="barLegend"
+ :series="barSeries1"
+ :tooltip="tooltip"
+ :xAxis="xAxis1"
+ :yAxis="yAxis1"
+ style="height: 270px"
+ />
</div>
</div>
- </div>
- <!-- 宸ュ簭閫夋嫨寮圭獥 -->
+ <div v-if="hasRightPanels" class="right-column">
+ <div v-if="visiblePanels.todo" class="cockpit-panel todo-panel">
+ <div class="panel-title-row">
+ <div class="panel-title">寰呭姙浜嬮」</div>
+ <span class="panel-more">鏇村</span>
+ </div>
+ <ul class="todo-list" v-if="todoList.length > 0">
+ <li v-for="item in todoList" :key="item.id" class="todo-item">
+ <div class="todo-head">
+ <span class="todo-no">寰呭姙缂栧彿锛歿{ item.approveId }}</span>
+ <span class="todo-time">{{ item.approveTime }}</span>
+ </div>
+ <div class="todo-desc">閮ㄩ棬锛歿{ item.approveDeptName }}</div>
+ <div class="todo-desc">浜嬬敱锛歿{ item.approveReason }}</div>
+ </li>
+ </ul>
+ <div v-else class="panel-empty">鏆傛棤鏁版嵁</div>
+ </div>
+
+ <div v-if="visiblePanels.realtime" class="cockpit-panel realtime-panel">
+ <div class="panel-title-row">
+ <div class="panel-title">鐢熶骇瀹炴椂鐪嬫澘</div>
+ <span class="panel-more">鏇村</span>
+ </div>
+ <div class="realtime-grid">
+ <div class="realtime-item" v-for="item in realtimeBoard" :key="item.key">
+ <el-progress
+ type="circle"
+ :percentage="item.percent"
+ :stroke-width="10"
+ :width="94"
+ :color="item.color"
+ >
+ <template #default>
+ <div class="realtime-value digital-number">{{ item.display }}</div>
+ </template>
+ </el-progress>
+ <div class="realtime-label">{{ item.label }}</div>
+ <div class="realtime-delta" :class="item.trend">{{ item.delta }}</div>
+ </div>
+ </div>
+ </div>
+
+ <div v-if="visiblePanels.quick" class="cockpit-panel quick-panel">
+ <div class="panel-title-row">
+ <div class="panel-title">蹇嵎鍔熻兘</div>
+ </div>
+ <div class="quick-grid">
+ <button
+ v-for="item in quickEntries"
+ :key="item.label"
+ class="quick-item"
+ type="button"
+ @click="goToQuick(item.path)"
+ >
+ <span class="quick-icon">
+ <el-icon>
+ <component :is="item.icon" />
+ </el-icon>
+ </span>
+ <span>{{ item.label }}</span>
+ </button>
+ </div>
+ </div>
+
+ <div v-if="visiblePanels.plan" class="cockpit-panel plan-panel">
+ <div class="panel-title-row">
+ <div class="panel-title">浠婃棩鐢熶骇璁″垝</div>
+ <span class="panel-more">{{ todayPlanTotal }}椤�</span>
+ </div>
+ <ul class="plan-list">
+ <li v-for="item in todayPlanList" :key="item.orderNo" class="plan-item">
+ <div class="plan-main">
+ <span class="plan-order">{{ item.orderNo }}</span>
+ <span class="plan-name">{{ item.productName }}</span>
+ </div>
+ <div class="plan-meta">
+ <span>璁″垝 {{ item.planQty }}</span>
+ <span>浜ゆ湡 {{ item.deliveryDate }}</span>
+ </div>
+ </li>
+ </ul>
+ </div>
+
+ <div v-if="visiblePanels.receipt" class="cockpit-panel receipt-panel">
+ <div class="panel-title">鍥炴涓庡紑绁ㄥ垎鏋�</div>
+ <Echarts
+ :options="chartBaseOptions"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="lineLegend"
+ :series="lineSeries"
+ :tooltip="tooltipLine"
+ :xAxis="xAxis2"
+ :yAxis="yAxis2"
+ style="height: 300px"
+ />
+ </div>
+
+ </div>
+ </section>
+
+ <section v-else class="cockpit-panel empty-home-panel">
+ 褰撳墠璐﹀彿娌℃湁鍙睍绀虹殑棣栭〉妯″潡
+ </section>
+
<el-dialog v-model="processDialogVisible" title="閫夋嫨宸ュ簭" width="500px" append-to-body>
<div class="process-selection-wrapper">
<el-checkbox-group v-model="tempProcessIds">
@@ -168,105 +333,99 @@
</span>
</template>
</el-dialog>
- <!-- 涓儴妯悜涓ゆ爮 -->
- <div class="dashboard-row">
- <div class="main-panel">
- <div class="section-title">瀹㈡埛鍚堝悓閲戦鍒嗘瀽</div>
- <div class="contract-summary">
- <div class="contract-info">
- <img src="../assets/images/khtitle.png" alt="" style="width: 42px" />
- <div class="contract-card">
- <div class="contract-name">鎬诲悎鍚岄噾棰�(鍏�)</div>
- <div class="contract-meta">
- <div class="main-amount">{{ sum }}</div>
- <div>鍛ㄥ悓姣�: <span class="up">{{ yny }}% </span> 鏃ョ幆姣�: <span class="up">{{ chain }}% </span></div>
- </div>
- </div>
- </div>
- </div>
- <div
- style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">
- <div>
- <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie" :series="materialPieSeries"
- :tooltip="pieTooltip"></Echarts>
- </div>
- <ul class="contract-list">
- <li v-for="item in materialPieSeries[0].data" :key="item.name">
- <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">
- <div class="line" :style="{ color: item.itemStyle.color }">鈼弡{ item.name }}</div>
- <div style="width: 70px">{{ item.rate }}%</div>
- <div>锟{ item.value }}</div>
- </div>
- </li>
- </ul>
- </div>
- </div>
- <div class="main-panel">
- <div style="display: flex;justify-content: space-between;">
- <div class="section-title">搴旀敹搴斾粯缁熻</div>
- <!-- <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">-->
- <!-- <el-radio-button label="鎸夊懆" :value="1" />-->
- <!-- <el-radio-button label="鎸夋湀" :value="2" />-->
- <!-- <el-radio-button label="鎸夊搴�" :value="3" />-->
- <!-- </el-radio-group>-->
- </div>
- <Echarts ref="chart" :color="barColors2" :chartStyle="chartStyle" :grid="grid" :series="barSeries"
- :tooltip="tooltip" :xAxis="xAxis" :yAxis="yAxis" style="height: 260px"></Echarts>
- </div>
- </div>
-
- <!-- 搴曢儴妯悜涓ゆ爮 -->
- <div class="dashboard-row">
- <div class="main-panel">
- <div style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;">
- <div class="section-title" style="margin-bottom: 0;">璐ㄩ噺缁熻</div>
- <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo">
- <el-radio-button :value="1">鍛�</el-radio-button>
- <el-radio-button :value="2">鏈�</el-radio-button>
- <el-radio-button :value="3">瀛e害</el-radio-button>
- </el-radio-group>
- </div>
- <div class="quality-cards">
- <div class="quality-card one">鍘熸潗鏂欏凡妫�娴嬫暟 <span>{{ qualityStatisticsObject.supplierNum }}浠�</span></div>
- <div class="quality-card two">杩囩▼妫�楠屾暟閲� <span>{{ qualityStatisticsObject.processNum }}浠�</span></div>
- <div class="quality-card three">鍑哄巶宸叉鏁伴噺 <span>{{ qualityStatisticsObject.factoryNum }}浠�</span></div>
- </div>
- <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="barLegend" :series="barSeries1"
- :tooltip="tooltip" :xAxis="xAxis1" :yAxis="yAxis1" style="height: 260px"></Echarts>
- </div>
-
- <div class="main-panel">
- <div class="section-title">鍥炴涓庡紑绁ㄥ垎鏋�</div>
- <Echarts ref="invoiceChart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"
- :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;" />
- </div>
- </div>
</div>
</template>
<script setup>
-import { ref, onMounted, computed, reactive } from 'vue'
+import { ref, onMounted, onUnmounted, computed, reactive, watch } from "vue";
+import { useRouter } from "vue-router";
+import dayjs from "dayjs";
+import * as echarts from "echarts";
+import {
+ Box,
+ Calendar,
+ Checked,
+ DataAnalysis,
+ DataLine,
+ EditPen,
+ Operation,
+ Search,
+ ShoppingCartFull,
+ Tickets,
+ Tools,
+ UserFilled,
+} from "@element-plus/icons-vue";
import Echarts from "@/components/Echarts/echarts.vue";
-import * as echarts from 'echarts';
+import usePermissionStore from "@/store/modules/permission";
import useUserStore from "@/store/modules/user.js";
import {
- analysisCustomerContractAmounts, getAmountHalfYear,
+ analysisCustomerContractAmounts,
+ getAmountHalfYear,
getBusiness,
homeTodos,
processDataProductionStatistics,
+ productionOrderProgress,
+ productionOverview,
+ productionRealtimeBoard,
+ qualityInspectionStatistics,
statisticsReceivablePayable,
- qualityInspectionStatistics
+ todayProductionPlan,
} from "@/api/viewIndex.js";
-import { list } from '@/api/productionManagement/productionProcess';
+import { list } from "@/api/productionManagement/productionProcess";
+const router = useRouter();
+const userStore = useUserStore();
+const permissionStore = usePermissionStore();
+const defaultWelcomeAvatar = new URL("../assets/images/profile.jpg", import.meta.url).href;
-const userStore = useUserStore()
+const nowTime = ref("");
+const welcomeAvatarLoadFailed = ref(false);
+let clockTimer = null;
+const weatherText = "澶氫簯 28掳C";
-const processOptions = ref([])
-const selectedProcessIds = ref([])
-const tempProcessIds = ref([])
-const processDialogVisible = ref(false)
-const activeProcessIndex = ref(0)
+const nowDate = computed(() => (nowTime.value ? nowTime.value.slice(0, 10) : dayjs().format("YYYY-MM-DD")));
+
+const greetingText = computed(() => {
+ const hour = dayjs().hour();
+ if (hour < 6) return "鍑屾櫒濂�";
+ if (hour < 9) return "鏃╀笂濂�";
+ if (hour < 12) return "涓婂崍濂�";
+ if (hour < 14) return "涓崍濂�";
+ if (hour < 18) return "涓嬪崍濂�";
+ return "鏅氫笂濂�";
+});
+
+const welcomeAvatar = computed(() =>
+ welcomeAvatarLoadFailed.value || !userStore.avatar ? defaultWelcomeAvatar : userStore.avatar
+);
+
+const handleWelcomeAvatarError = () => {
+ if (welcomeAvatar.value !== defaultWelcomeAvatar) {
+ welcomeAvatarLoadFailed.value = true;
+ }
+};
+
+watch(
+ () => userStore.avatar,
+ () => {
+ welcomeAvatarLoadFailed.value = false;
+ }
+);
+
+const axisTextColor = "#5f6f86";
+const axisLineColor = "rgba(148, 163, 184, 0.45)";
+const splitLineColor = "rgba(148, 163, 184, 0.18)";
+
+const chartBaseOptions = reactive({
+ backgroundColor: "transparent",
+ textStyle: { color: axisTextColor },
+});
+
+const processOptions = ref([]);
+const selectedProcessIds = ref([]);
+const tempProcessIds = ref([]);
+const processDialogVisible = ref(false);
+const activeProcessIndex = ref(0);
const businessInfo = ref({
inventoryNum: 0,
@@ -275,988 +434,2033 @@
monthSaleHaveMoney: 0,
monthSaleMoney: 0,
todayInventoryNum: 0,
-})
+});
+
const qualityStatisticsObject = ref({
supplierNum: 0,
processNum: 0,
factoryNum: 0,
-})
-const sum = ref(0)
-const yny = ref(0)
-const chain = ref(0)
+});
-const pieLegend = reactive({
- show: false,
-})
+const productionOverviewData = ref({
+ totalOutput: 0,
+ totalScrap: 0,
+ yieldRate: 0,
+});
+
+const realtimeBoardData = ref({
+ deviceOee: { value: 0, compareYesterday: 0 },
+ orderAchievementRate: { value: 0, compareYesterday: 0 },
+ defectRate: { value: 0, compareYesterday: 0 },
+});
+
+const orderProgressMeta = ref({
+ status: "all",
+ tab: "all",
+ bizDate: null,
+ total: 0,
+ pageNum: 1,
+ pageSize: 10,
+ waitingCount: 0,
+ inProgressCount: 0,
+ completedCount: 0,
+ pausedCount: 0,
+});
+
+const todayPlanList = ref([]);
+const todayPlanTotal = ref(0);
+
+const sum = ref(0);
+const yny = ref(0);
+const chain = ref(0);
+
+const pieLegend = reactive({ show: false });
+const piePalette = ["#2563eb", "#14b8a6", "#7c3aed", "#f97316", "#38bdf8", "#f43f5e"];
+
+const chartStyle = {
+ width: "100%",
+ height: "100%",
+};
+
+const chartStylePie = {
+ width: "100%",
+ height: "200px",
+};
+
+const grid = {
+ left: "3%",
+ right: "4%",
+ bottom: "2%",
+ top: "12%",
+ containLabel: true,
+};
+
+const tooltip = {
+ trigger: "axis",
+ axisPointer: { type: "shadow" },
+ backgroundColor: "rgba(255, 255, 255, 0.97)",
+ borderColor: "rgba(148, 163, 184, 0.26)",
+ textStyle: { color: "#334155" },
+};
+
+const barColors2 = ["#2563eb", "#14b8a6"];
+
+const xAxis = [
+ {
+ type: "value",
+ axisLine: { lineStyle: { color: axisLineColor } },
+ splitLine: { lineStyle: { color: splitLineColor } },
+ axisLabel: { color: axisTextColor },
+ },
+];
+
+const yAxis = [
+ {
+ type: "category",
+ data: ["搴斾粯璐︽", "搴旀敹璐︽"],
+ axisTick: { show: false },
+ axisLine: { lineStyle: { color: axisLineColor } },
+ axisLabel: { color: axisTextColor },
+ },
+];
+
const barSeries = ref([
{
- type: 'bar',
+ type: "bar",
+ barWidth: 22,
+ itemStyle: { borderRadius: [0, 8, 8, 0] },
+ label: { show: true, position: "right", color: "#334155" },
data: [],
- label: {
- show: true,
- }
},
-])
+]);
+
+const barLegend = {
+ show: true,
+ textStyle: { color: axisTextColor },
+ data: ["鍘熸潗鏂欎笉鍚堟牸鏁�", "杩囩▼涓嶅悎鏍兼暟", "鍑哄巶涓嶅悎鏍兼暟"],
+};
+
+const xAxis1 = ref([
+ {
+ type: "category",
+ axisTick: { show: false },
+ axisLine: { lineStyle: { color: axisLineColor } },
+ axisLabel: { color: axisTextColor },
+ data: [],
+ },
+]);
+
+const yAxis1 = [
+ {
+ type: "value",
+ splitLine: { lineStyle: { color: splitLineColor } },
+ axisLine: { lineStyle: { color: axisLineColor } },
+ axisLabel: { color: axisTextColor },
+ },
+];
const barSeries1 = ref([
{
- name: '鍘熸潗鏂欎笉鍚堟牸鏁�',
- type: 'bar',
+ name: "鍘熸潗鏂欎笉鍚堟牸鏁�",
+ type: "bar",
barGap: 0,
- emphasis: {
- focus: 'series'
- },
- data: []
+ itemStyle: { color: "#2563eb", borderRadius: [6, 6, 0, 0] },
+ emphasis: { focus: "series" },
+ data: [],
},
{
- name: '杩囩▼涓嶅悎鏍兼暟',
- type: 'bar',
- emphasis: {
- focus: 'series'
- },
- data: []
+ name: "杩囩▼涓嶅悎鏍兼暟",
+ type: "bar",
+ itemStyle: { color: "#14b8a6", borderRadius: [6, 6, 0, 0] },
+ emphasis: { focus: "series" },
+ data: [],
},
{
- name: '鍑哄巶涓嶅悎鏍兼暟',
- type: 'bar',
- emphasis: {
- focus: 'series'
- },
- data: []
+ name: "鍑哄巶涓嶅悎鏍兼暟",
+ type: "bar",
+ itemStyle: { color: "#f59e0b", borderRadius: [6, 6, 0, 0] },
+ emphasis: { focus: "series" },
+ data: [],
},
-])
-const chartStyle = {
- width: '100%',
- height: '100%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
-}
-const chartStylePie = {
- width: '140%',
- height: '140%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
-}
-const grid = {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
-}
-const barLegend = {
- show: true,
- data: ['鍘熸潗鏂欎笉鍚堟牸鏁�', '杩囩▼涓嶅悎鏍兼暟', '鍑哄巶涓嶅悎鏍兼暟']
-}
-const barLegend1 = {
- show: true,
- data: ['棰勪粯璐︽', '搴斾粯璐︽', '棰勬敹璐︽', '搴旀敹璐︽']
-}
+]);
+
const lineLegend = {
show: true,
- data: ['寮�绁�', '鍥炴']
-}
-const tooltip = {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
-}
-const xAxis = [{
- type: 'value',
-}]
-const xAxis1 = ref([{
- type: 'category',
- axisTick: { show: false },
- data: []
-}])
-const yAxis = [{
- type: 'category',
- data: ['搴斾粯璐︽', '搴旀敹璐︽',]
-}]
-const yAxis1 = [{
- type: 'value'
-}]
-const pieTooltip = reactive({
- trigger: 'item',
- formatter: function (params) {
- // 鍔ㄦ�佺敓鎴愭彁绀轰俊鎭紝鍩轰簬鏁版嵁椤圭殑 name 灞炴��
- const description = params.name === '鏈湀鍥炴閲戦' ? '鏈湀鍥炴閲戦' : '搴旀敹娆鹃噾棰�';
- return `${description} ${formatNumber(params.value)}鍏� ${params.percent}%`;
- },
- position: 'right'
-})
-const materialPieSeries = ref([
- {
- type: 'pie',
- radius: ['66%', '90%'],
- avoidLabelOverlap: false,
- itemStyle: {
- borderColor: '#fff',
- borderWidth: 2
- },
- label: {
- show: false
- },
- data: []
- }
-])
-const lineSeries = ref([
- {
- type: 'line',
- data: [],
- label: {
- show: true
- },
- showSymbol: true, // 鏄剧ず鍦嗙偣
- },
-])
-const tooltipLine = {
- trigger: 'axis',
-}
-const yAxis2 = ref([
- {
- type: 'value',
- }
-])
+ textStyle: { color: axisTextColor },
+ data: ["寮�绁�", "鍥炴"],
+};
+
const xAxis2 = ref([
{
- type: 'category',
+ type: "category",
data: [],
+ axisLine: { lineStyle: { color: axisLineColor } },
axisLabel: {
+ color: axisTextColor,
interval: 0,
- formatter: function (value) {
- return value.replace(/~/g, '\n');
- },
- }
- }
-])
-
-// 寰呭姙浜嬮」
-const todoList = ref([])
-const radio1 = ref(1)
-const qualityRange = ref(1)
-
-// 鍥捐〃寮曠敤
-const barChart = ref(null)
-const lineChart = ref(null)
-const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8']
-
-// 闅忔満棰滆壊鐢熸垚鍑芥暟
-const getRandomColor = () => {
- return '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0');
-}
-
-onMounted(() => {
- getBusinessData()
- analysisCustomer()
- todoInfoS()
- statisticsReceivable()
- qualityStatisticsInfo()
- getAmountHalfYearNum()
- getProcessList()
-})
-// 鏁版嵁缁熻
-const getBusinessData = () => {
- getBusiness().then((res) => {
- businessInfo.value = { ...res.data }
- })
-}
-// 鍚堝悓閲戦
-const analysisCustomer = () => {
- analysisCustomerContractAmounts().then((res) => {
- sum.value = res.data.sum
- yny.value = res.data.yny
- chain.value = res.data.chain
- // 涓烘瘡涓暟鎹」鍒嗛厤闅忔満棰滆壊
- materialPieSeries.value[0].data = res.data.item.map(item => ({
- ...item,
- itemStyle: { color: getRandomColor() }
- }))
- })
-}
-// 寰呭姙浜嬮」
-const todoInfoS = () => {
- homeTodos().then((res) => {
- todoList.value = res.data
- })
-}
-// 鑾峰彇宸ュ簭鍒楄〃
-const getProcessList = () => {
- list().then(res => {
- processOptions.value = res.data
- })
-}
-
-const openProcessDialog = () => {
- tempProcessIds.value = [...selectedProcessIds.value]
- processDialogVisible.value = true
-}
-
-const handleProcessDialogConfirm = () => {
- selectedProcessIds.value = [...tempProcessIds.value]
- processDialogVisible.value = false
- refreshProcessStats()
-}
-
-const resetProcessFilter = () => {
- selectedProcessIds.value = []
- tempProcessIds.value = []
- refreshProcessStats()
-}
-
-const handleChartClick = (params) => {
- if (params && params.dataIndex !== undefined) {
- activeProcessIndex.value = params.dataIndex
- }
-}
-// 搴斾粯搴旀敹缁熻
-const statisticsReceivable = () => {
- statisticsReceivablePayable({ type: radio1.value }).then((res) => {
- barSeries.value[0].data = [
- // { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } },
- { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } },
- // { value: res.data.advanceMoney, itemStyle: { color: barColors2[2] } },
- { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } }
- ]
- })
-}
-// 璐ㄦ缁熻
-const qualityStatisticsInfo = () => {
- qualityInspectionStatistics({ type: qualityRange.value }).then((res) => {
- xAxis1.value[0].data = []
- barSeries1.value[0].data = []
- barSeries1.value[1].data = []
- barSeries1.value[2].data = []
- res.data.item.forEach(item => {
- xAxis1.value[0].data.push(item.date)
- barSeries1.value[0].data.push(item.supplierNum)
- barSeries1.value[1].data.push(item.processNum)
- barSeries1.value[2].data.push(item.factoryNum)
- })
- qualityStatisticsObject.value.supplierNum = res.data.supplierNum
- qualityStatisticsObject.value.processNum = res.data.processNum
- qualityStatisticsObject.value.factoryNum = res.data.factoryNum
- })
-}
-const getAmountHalfYearNum = async () => {
- const res = await getAmountHalfYear()
- console.log(res)
- const monthName = []
- const receiptAmount = []
- const invoiceAmount = []
- res.data.forEach(item => {
- monthName.push(item.month)
- receiptAmount.push(item.receiptAmount)
- invoiceAmount.push(item.invoiceAmount)
- })
- // 姝g‘鍝嶅簲寮忚祴鍊硷細鍒涘缓鏂扮殑 xAxis 鍜� series 瀵硅薄
- xAxis2.value[0].data = monthName
- xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~'));
- lineSeries.value = [
- {
- name: '寮�绁�',
- type: 'line',
- data: invoiceAmount,
- stack: 'Total',
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- {
- offset: 0,
- color: 'rgba(131, 207, 255, 1)'
- },
- {
- offset: 1,
- color: 'rgba(186, 228, 255, 1)'
- }
- ])
- },
- itemStyle: {
- color: '#2D99FF',
- borderColor: '#2D99FF'
- },
- emphasis: {
- focus: 'series'
- },
- lineStyle: {
- width: 0
- },
- showSymbol: true,
+ formatter: (value) => value.replace(/~/g, "\n"),
},
- {
- name: '鍥炴',
- type: 'line',
- data: receiptAmount,
- stack: 'Total',
- lineStyle: {
- width: 0
- },
- itemStyle: {
- color: '#83CFFF',
- borderColor: '#83CFFF'
- },
- showSymbol: true,
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- {
- offset: 0,
- color: 'rgba(54, 153, 255, 1)'
- },
- {
- offset: 1,
- color: 'rgba(89, 169, 254, 1)'
- }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- }
- ]
-}
+ },
+]);
-// 宸ュ簭鏁版嵁鐢熶骇缁熻鏄庣粏锛堝亣鏁版嵁 + 鍥捐〃锛�
-const processRange = ref(1)
-const processChartData = ref([])
+const yAxis2 = ref([
+ {
+ type: "value",
+ splitLine: { lineStyle: { color: splitLineColor } },
+ axisLine: { lineStyle: { color: axisLineColor } },
+ axisLabel: { color: axisTextColor },
+ },
+]);
+
+const tooltipLine = {
+ trigger: "axis",
+ backgroundColor: "rgba(255, 255, 255, 0.97)",
+ borderColor: "rgba(148, 163, 184, 0.26)",
+ textStyle: { color: "#334155" },
+};
+
+const lineSeries = ref([
+ {
+ name: "寮�绁�",
+ type: "line",
+ data: [],
+ smooth: true,
+ itemStyle: { color: "#2563eb" },
+ lineStyle: { width: 2, color: "#2563eb" },
+ showSymbol: true,
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: "rgba(37, 99, 235, 0.24)" },
+ { offset: 1, color: "rgba(37, 99, 235, 0.02)" },
+ ]),
+ },
+ },
+ {
+ name: "鍥炴",
+ type: "line",
+ data: [],
+ smooth: true,
+ itemStyle: { color: "#14b8a6" },
+ lineStyle: { width: 2, color: "#14b8a6" },
+ showSymbol: true,
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: "rgba(20, 184, 166, 0.2)" },
+ { offset: 1, color: "rgba(20, 184, 166, 0.02)" },
+ ]),
+ },
+ },
+]);
+
+const pieTooltip = reactive({
+ trigger: "item",
+ backgroundColor: "rgba(255, 255, 255, 0.97)",
+ borderColor: "rgba(148, 163, 184, 0.26)",
+ textStyle: { color: "#334155" },
+ formatter: (params) => `${params.name} ${formatNumber(params.value)}鍏� ${params.percent}%`,
+});
+
+const materialPieSeries = ref([
+ {
+ type: "pie",
+ radius: ["62%", "88%"],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderColor: "rgba(255, 255, 255, 0.95)",
+ borderWidth: 2,
+ },
+ label: { show: false },
+ data: [],
+ },
+]);
+
+const todoList = ref([]);
+const qualityRange = ref(1);
+const processRange = ref(1);
+const processChartData = ref([]);
const processXAxis = ref([
{
- nameTextStyle: { color: 'rgba(0,0,0,0.35)', fontSize: 12 },
- axisLabel: { color: 'rgba(0,0,0,0.35)' },
- splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)', type: 'dashed' } },
+ type: "value",
+ axisLine: { lineStyle: { color: axisLineColor } },
+ splitLine: { lineStyle: { color: splitLineColor, type: "dashed" } },
+ axisLabel: { color: axisTextColor },
},
-])
+]);
const processYAxis = ref([
{
- type: 'category',
+ type: "category",
axisTick: { show: false },
axisLine: { show: false },
- axisLabel: { color: 'rgba(0,0,0,0.45)' },
+ axisLabel: { color: axisTextColor },
data: [],
},
-])
+]);
-const processGrid = reactive({ left: 0, right: 100, top: 30, bottom: 20, containLabel: true })
+const processGrid = reactive({ left: "4%", right: "4%", top: 20, bottom: 18, containLabel: true });
const processTooltip = reactive({
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
+ trigger: "axis",
+ axisPointer: { type: "shadow" },
+ backgroundColor: "rgba(255, 255, 255, 0.97)",
+ borderColor: "rgba(148, 163, 184, 0.26)",
+ textStyle: { color: "#334155" },
formatter: (params) => {
- const name = params?.[0]?.name ?? ''
- const list = Array.isArray(params) ? params : []
+ const name = params?.[0]?.name ?? "";
+ const list = Array.isArray(params) ? params : [];
const lines = list
- .map((p) => {
- const colorBox = `<span style="display:inline-block;margin-right:6px;border-radius:2px;width:10px;height:10px;background:${p.color}"></span>`
- return `${colorBox}${p.seriesName} <b style="float:right;">${Number(p.value || 0).toFixed(2)}</b>`
- })
- .join('<br/>')
- return `<div style="min-width:140px;"><div style="font-weight:700;margin-bottom:6px;">${name}</div>${lines}</div>`
+ .map((p) => {
+ const colorBox = `<span style="display:inline-block;margin-right:6px;border-radius:2px;width:10px;height:10px;background:${p.color}"></span>`;
+ return `${colorBox}${p.seriesName}<b style="float:right;">${Number(p.value || 0).toFixed(2)}</b>`;
+ })
+ .join("<br/>");
+ return `<div style="min-width:140px;"><div style="font-weight:700;margin-bottom:6px;">${name}</div>${lines}</div>`;
},
-})
+});
const processSeries = computed(() => {
- const input = processChartData.value.map((i) => i.input)
- const scrap = processChartData.value.map((i) => i.scrap)
- const output = processChartData.value.map((i) => i.output)
-
+ const input = processChartData.value.map((item) => item.input);
+ const scrap = processChartData.value.map((item) => item.scrap);
+ const output = processChartData.value.map((item) => item.output);
return [
{
- name: '鎶曞叆閲�',
- type: 'bar',
- stack: 'total',
- barWidth: 22,
- itemStyle: { color: '#1E5BFF', borderRadius: [6, 0, 0, 6] },
+ name: "鎶曞叆閲�",
+ type: "bar",
+ stack: "total",
+ barWidth: 18,
+ itemStyle: { color: "#2563eb", borderRadius: [8, 0, 0, 8] },
data: input,
},
{
- name: '鎶ュ簾閲�',
- type: 'bar',
- stack: 'total',
- barWidth: 22,
- itemStyle: { color: '#F7B500' },
+ name: "鎶ュ簾閲�",
+ type: "bar",
+ stack: "total",
+ barWidth: 18,
+ itemStyle: { color: "#f59e0b" },
data: scrap,
},
{
- name: '浜у嚭閲�',
- type: 'bar',
- stack: 'total',
- barWidth: 22,
- itemStyle: { color: '#19C6C6', borderRadius: [0, 6, 6, 0] },
+ name: "浜у嚭閲�",
+ type: "bar",
+ stack: "total",
+ barWidth: 18,
+ itemStyle: { color: "#14b8a6", borderRadius: [0, 8, 8, 0] },
data: output,
},
- ]
-})
+ ];
+});
const processAside = computed(() => {
- const list = processChartData.value
- const item = list[activeProcessIndex.value] || {}
+ const listData = processChartData.value;
+ const item = listData[activeProcessIndex.value] || {};
return {
- processName: item.name || '鏆傛棤鏁版嵁',
+ processName: item.name || "鏆傛棤鏁版嵁",
totalInput: item.input || 0,
totalScrap: item.scrap || 0,
totalOutput: item.output || 0,
+ };
+});
+
+const processTotals = computed(() =>
+ processChartData.value.reduce(
+ (acc, cur) => {
+ acc.input += Number(cur.input || 0);
+ acc.scrap += Number(cur.scrap || 0);
+ acc.output += Number(cur.output || 0);
+ return acc;
+ },
+ { input: 0, scrap: 0, output: 0 }
+ )
+);
+
+const hasProcessData = computed(() => {
+ if (!processChartData.value.length) return false;
+ return processChartData.value.some((item) => {
+ const input = Number(item.input || 0);
+ const scrap = Number(item.scrap || 0);
+ const output = Number(item.output || 0);
+ return input > 0 || scrap > 0 || output > 0;
+ });
+});
+
+const dashboardCards = computed(() => [
+ {
+ key: "sales",
+ title: "閿�鍞暟鎹�",
+ desc: "鏈湀閿�鍞(鍏�)",
+ value: formatNumber(businessInfo.value.monthSaleMoney),
+ subLabel: "鏈紑绁ㄩ噾棰�",
+ subValue: formatNumber(businessInfo.value.monthSaleHaveMoney),
+ trend: `鍗犳瘮 ${ratioText(businessInfo.value.monthSaleHaveMoney, businessInfo.value.monthSaleMoney)}`,
+ icon: DataLine,
+ visible: visibleModules.value.sales,
+ },
+ {
+ key: "purchase",
+ title: "閲囪喘鏁版嵁",
+ desc: "鏈湀閲囪喘棰�(鍏�)",
+ value: formatNumber(businessInfo.value.monthPurchaseMoney),
+ subLabel: "寰呬粯娆鹃噾棰�",
+ subValue: formatNumber(businessInfo.value.monthPurchaseHaveMoney),
+ trend: `鍗犳瘮 ${ratioText(
+ businessInfo.value.monthPurchaseHaveMoney,
+ businessInfo.value.monthPurchaseMoney
+ )}`,
+ icon: ShoppingCartFull,
+ visible: visibleModules.value.procurement,
+ },
+ {
+ key: "inventory",
+ title: "搴撳瓨鏁版嵁",
+ desc: "褰撳墠搴撳瓨鎬婚噺(浠�)",
+ value: formatNumber(businessInfo.value.inventoryNum),
+ subLabel: "浠婃棩鍏ュ簱",
+ subValue: formatNumber(businessInfo.value.todayInventoryNum),
+ trend: "搴撳瓨缁撴瀯鎸佺画浼樺寲",
+ icon: Box,
+ visible: visibleModules.value.inventory,
+ },
+ {
+ key: "production",
+ title: "鐢熶骇鎬昏",
+ desc: "绱浜у嚭(浠�)",
+ value: formatNumber(productionOverviewData.value.totalOutput),
+ subLabel: "绱鎶ュ簾",
+ subValue: formatNumber(productionOverviewData.value.totalScrap),
+ trend: `鑹巼 ${Number(productionOverviewData.value.yieldRate || 0).toFixed(2)}%`,
+ icon: Operation,
+ visible: visibleModules.value.production,
+ },
+].filter((item) => item.visible));
+
+const productionOrders = ref([]);
+
+const orderFilterOptions = ["all", "waiting", "inProgress", "completed", "paused"];
+const orderFilterAliasMap = {
+ 1: "waiting",
+ 2: "inProgress",
+ 3: "completed",
+ 4: "paused",
+};
+const orderFilter = ref("all");
+const filteredOrders = computed(() => productionOrders.value);
+
+const normalizeOrderFilter = (value, fallback = "all") => {
+ const safeFallback = orderFilterOptions.includes(fallback) ? fallback : "all";
+ const text = String(value ?? "").trim();
+ if (orderFilterAliasMap[text]) {
+ return orderFilterAliasMap[text];
}
-})
+ return orderFilterOptions.includes(text) ? text : safeFallback;
+};
-const formatAmount = (n) => {
- const num = Number(n || 0)
- return num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
-}
+const parseCount = (value) => {
+ if (value === null || value === undefined || value === "") return null;
+ const num = Number(value);
+ return Number.isFinite(num) ? num : null;
+};
-const refreshProcessStats = () => {
- processDataProductionStatistics({
+const resolveProgressCount = (rawValue, currentStatus, targetStatus, total) => {
+ const count = parseCount(rawValue);
+ if (count !== null) return count;
+ return currentStatus === targetStatus ? total : 0;
+};
+
+const getCompareTrend = (value) => {
+ const num = Number(value || 0);
+ if (num > 0) return "up";
+ if (num < 0) return "down";
+ return "flat";
+};
+
+const getCompareText = (value) => {
+ const num = Number(value || 0);
+ const abs = Math.abs(num).toFixed(2);
+ if (num > 0) return `杈冩槰鏃� 鈫� ${abs}%`;
+ if (num < 0) return `杈冩槰鏃� 鈫� ${abs}%`;
+ return "杈冩槰鏃� 鎸佸钩";
+};
+
+const realtimeBoard = computed(() => {
+ const oee = Number(realtimeBoardData.value.deviceOee?.value || 0);
+ const orderAchievement = Number(realtimeBoardData.value.orderAchievementRate?.value || 0);
+ const defectRate = Number(realtimeBoardData.value.defectRate?.value || 0);
+ const oeeCompare = Number(realtimeBoardData.value.deviceOee?.compareYesterday || 0);
+ const orderCompare = Number(realtimeBoardData.value.orderAchievementRate?.compareYesterday || 0);
+ const defectCompare = Number(realtimeBoardData.value.defectRate?.compareYesterday || 0);
+ return [
+ {
+ key: "oee",
+ label: "璁惧 OEE",
+ percent: clampPercent(oee),
+ display: `${oee.toFixed(2)}%`,
+ delta: getCompareText(oeeCompare),
+ trend: getCompareTrend(oeeCompare),
+ color: "#2d8cff",
+ },
+ {
+ key: "order",
+ label: "璁㈠崟杈炬垚鐜�",
+ percent: clampPercent(orderAchievement),
+ display: `${orderAchievement.toFixed(2)}%`,
+ delta: getCompareText(orderCompare),
+ trend: getCompareTrend(orderCompare),
+ color: "#31d2ff",
+ },
+ {
+ key: "defect",
+ label: "涓嶈壇鐜�",
+ percent: clampPercent(defectRate),
+ display: `${defectRate.toFixed(2)}%`,
+ delta: getCompareText(defectCompare),
+ trend: getCompareTrend(defectCompare),
+ color: "#f6a23f",
+ },
+ ];
+});
+
+const warningList = ref([
+ {
+ id: 1,
+ level: "device",
+ title: "璁惧棰勮",
+ desc: "CNC-001 璁惧鎸姩寮傚父",
+ time: "10:24:32",
+ },
+ {
+ id: 2,
+ level: "quality",
+ title: "璐ㄩ噺棰勮",
+ desc: "璁㈠崟 SO-20260517 涓嶈壇鐜囪秴鏍�",
+ time: "09:15:47",
+ },
+ {
+ id: 3,
+ level: "material",
+ title: "鐗╂枡棰勮",
+ desc: "鐗╂枡 1002001 搴撳瓨涓嶈冻",
+ time: "08:47:21",
+ },
+]);
+
+const quickEntryConfigs = [
+ { label: "涓荤敓浜ц鍒�", icon: Calendar, titles: ["涓荤敓浜ц鍒�"], fallbackPath: "/productionPlan/productionPlan" },
+ { label: "鐢熶骇璁㈠崟", icon: Tickets, titles: ["鐢熶骇璁㈠崟"], fallbackPath: "/productionManagement/productionOrder" },
+ { label: "鐢熶骇鎶ュ伐", icon: EditPen, titles: ["鐢熶骇鎶ュ伐"], fallbackPath: "/productionManagement/productionReporting" },
+ { label: "璁惧鍙拌处", icon: Tools, titles: ["璁惧鍙拌处"], fallbackPath: "/equipmentManagement/ledger" },
+ { label: "閿�鍞彴璐�", icon: DataLine, titles: ["閿�鍞彴璐�"], fallbackPath: "/salesManagement/salesLedger" },
+ { label: "閲囪喘鍙拌处", icon: ShoppingCartFull, titles: ["閲囪喘鍙拌处"], fallbackPath: "/procurementManagement/procurementLedger" },
+ { label: "鍛樺伐鍙拌处", icon: UserFilled, titles: ["鍛樺伐鍙拌处", "鍦ㄨ亴鍛樺伐鍙拌处"], fallbackPath: "/personnelManagement/employeeRecord" },
+ { label: "搴撳瓨绠$悊", icon: Box, titles: ["搴撳瓨绠$悊"], fallbackPath: "/inventoryManagement/stockManage" },
+];
+
+const normalizeMenuTitle = (title) => String(title || "").replace(/\s+/g, "").trim();
+const normalizeRoutePath = (path) =>
+ String(path || "")
+ .trim()
+ .replace(/\/+/g, "/")
+ .replace(/\/$/, "")
+ .toLowerCase();
+
+const resolveRoutePath = (route, parentPath = "") => {
+ const currentPath = String(route?.path || "").trim();
+ if (!currentPath) return parentPath || "";
+ if (/^(https?:)?\/\//.test(currentPath)) return currentPath;
+ if (currentPath.startsWith("/")) return currentPath;
+ const basePath = parentPath && parentPath !== "/" ? parentPath.replace(/\/$/, "") : "";
+ return `${basePath}/${currentPath}`.replace(/\/+/g, "/");
+};
+
+const collectAccessibleRoutes = (routes = [], parentPath = "") => {
+ const items = [];
+ (routes || []).forEach((route) => {
+ if (!route) return;
+ const fullPath = resolveRoutePath(route, parentPath);
+ const title = route.meta?.title ?? route.title ?? "";
+ if (title && fullPath && !String(route.redirect || "").includes("noRedirect")) {
+ items.push({
+ title: normalizeMenuTitle(title),
+ path: normalizeRoutePath(fullPath),
+ });
+ }
+ if (Array.isArray(route.children) && route.children.length > 0) {
+ items.push(...collectAccessibleRoutes(route.children, fullPath));
+ }
+ });
+ return items;
+};
+
+const accessibleMenuRoutes = computed(() => {
+ const routePool =
+ permissionStore.defaultRoutes?.length > 0
+ ? permissionStore.defaultRoutes
+ : permissionStore.sidebarRouters?.length > 0
+ ? permissionStore.sidebarRouters
+ : permissionStore.routes;
+ return collectAccessibleRoutes(routePool || []);
+});
+
+const moduleAccessConfig = {
+ sales: {
+ titles: ["閿�鍞鐞�", "閿�鍞彴璐�"],
+ pathPrefixes: ["/salesmanagement"],
+ },
+ procurement: {
+ titles: ["閲囪喘绠$悊", "閲囪喘鍙拌处"],
+ pathPrefixes: ["/procurementmanagement"],
+ },
+ inventory: {
+ titles: ["搴撳瓨绠$悊"],
+ pathPrefixes: ["/inventorymanagement"],
+ },
+ production: {
+ titles: ["鐢熶骇绠$悊", "涓荤敓浜ц鍒�", "鐢熶骇璁㈠崟", "鐢熶骇鎶ュ伐"],
+ pathPrefixes: ["/productionmanagement", "/productionplan"],
+ },
+ quality: {
+ titles: ["璐ㄩ噺绠$悊"],
+ pathPrefixes: ["/qualitymanagement"],
+ },
+ equipment: {
+ titles: ["璁惧绠$悊", "璁惧鍙拌处"],
+ pathPrefixes: ["/equipmentmanagement"],
+ },
+ personnel: {
+ titles: ["浜轰簨绠$悊", "鍛樺伐鍙拌处", "鍦ㄨ亴鍛樺伐鍙拌处"],
+ pathPrefixes: ["/personnelmanagement"],
+ },
+ approval: {
+ titles: ["鍗忓悓瀹℃壒", "寰呭姙浜嬮」"],
+ pathPrefixes: ["/collaborativeapproval"],
+ },
+ finance: {
+ titles: ["璐㈠姟绠$悊", "璐㈠姟鍒嗘瀽", "鍥炴绠$悊", "寮�绁ㄧ鐞�"],
+ pathPrefixes: ["/financesuite", "/financialmanagement"],
+ },
+};
+
+const hasModuleAccess = (config) =>
+ accessibleMenuRoutes.value.some((route) => {
+ const matchedTitle = (config.titles || []).some((title) => route.title === normalizeMenuTitle(title));
+ const matchedPath = (config.pathPrefixes || []).some(
+ (prefix) => route.path === prefix || route.path.startsWith(`${prefix}/`)
+ );
+ return matchedTitle || matchedPath;
+ });
+
+const visibleModules = computed(() => ({
+ sales: hasModuleAccess(moduleAccessConfig.sales),
+ procurement: hasModuleAccess(moduleAccessConfig.procurement),
+ inventory: hasModuleAccess(moduleAccessConfig.inventory),
+ production: hasModuleAccess(moduleAccessConfig.production),
+ quality: hasModuleAccess(moduleAccessConfig.quality),
+ equipment: hasModuleAccess(moduleAccessConfig.equipment),
+ personnel: hasModuleAccess(moduleAccessConfig.personnel),
+ approval: hasModuleAccess(moduleAccessConfig.approval),
+ finance: hasModuleAccess(moduleAccessConfig.finance),
+}));
+
+const visiblePanels = computed(() => ({
+ process: visibleModules.value.production,
+ order: visibleModules.value.production,
+ contract: visibleModules.value.sales,
+ quality: visibleModules.value.quality,
+ todo: visibleModules.value.approval,
+ realtime: visibleModules.value.production,
+ quick: quickEntries.value.length > 0,
+ plan: visibleModules.value.production,
+ receipt: visibleModules.value.sales || visibleModules.value.finance,
+}));
+
+const hasLeftPanels = computed(
+ () => visiblePanels.value.process || visiblePanels.value.order || visiblePanels.value.contract || visiblePanels.value.quality
+);
+const hasRightPanels = computed(
+ () => visiblePanels.value.todo || visiblePanels.value.realtime || visiblePanels.value.quick || visiblePanels.value.plan || visiblePanels.value.receipt
+);
+const hasVisiblePanels = computed(() => hasLeftPanels.value || hasRightPanels.value);
+
+const quickEntries = computed(() =>
+ quickEntryConfigs
+ .map((item) => {
+ const targetRoute = accessibleMenuRoutes.value.find((route) =>
+ item.titles.some((title) => route.title === normalizeMenuTitle(title))
+ );
+ const resolvedPath = targetRoute?.path || "";
+ return resolvedPath
+ ? {
+ label: item.label,
+ icon: item.icon,
+ path: resolvedPath,
+ }
+ : null;
+ })
+ .filter(Boolean)
+);
+
+const updateNowTime = () => {
+ nowTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss");
+};
+
+const formatNumber = (value) => {
+ const num = Number(value || 0);
+ return num.toLocaleString(undefined, {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+};
+
+const formatAmount = (value) => {
+ const num = Number(value || 0);
+ return num.toLocaleString(undefined, {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+};
+
+const trendText = (value) => {
+ const num = Number(value || 0);
+ const flag = num >= 0 ? "鈫�" : "鈫�";
+ return `${flag} ${Math.abs(num).toFixed(1)}%`;
+};
+
+const ratioNumber = (numerator, denominator) => {
+ const n = Number(numerator || 0);
+ const d = Number(denominator || 0);
+ if (!d) return 0;
+ return (n / d) * 100;
+};
+
+const ratioText = (numerator, denominator) => `${ratioNumber(numerator, denominator).toFixed(1)}%`;
+
+const clampPercent = (val) => Math.max(0, Math.min(100, Number(val || 0)));
+
+const getOrderStatusText = (status) => {
+ const mapping = {
+ 1: "寰呭紑濮�",
+ 2: "杩涜涓�",
+ 3: "宸插畬鎴�",
+ 4: "宸叉殏鍋�",
+ };
+ return mapping[status] || "鏈煡";
+};
+
+const getOrderStatusType = (status) => {
+ const mapping = {
+ 1: "info",
+ 2: "success",
+ 3: "primary",
+ 4: "warning",
+ };
+ return mapping[status] || "info";
+};
+
+const formatDueDate = (value) => {
+ if (!value) return "--";
+ const date = dayjs(value);
+ return date.isValid() ? date.format("YYYY-MM-DD") : "--";
+};
+
+const mapOrderProgressRecord = (item = {}) => ({
+ orderNo: item.orderNo || "--",
+ productName: item.productName || "--",
+ planQty: Number(item.plannedQuantity || 0),
+ completedQty: Number(item.completedQuantity || 0),
+ completionRate: clampPercent(Number(item.completionRate || 0)),
+ deliveryDate: formatDueDate(item.dueDate),
+ status: Number(item.status || 0),
+ statusLabel: item.statusLabel || "",
+});
+
+const mapTodayPlanRecord = (item = {}) => ({
+ orderNo: item.orderNo || "--",
+ productName: item.productName || "--",
+ planQty: Number(item.plannedQuantity || 0),
+ deliveryDate: formatDueDate(item.dueDate),
+ status: Number(item.status || 0),
+ statusLabel: item.statusLabel || "",
+});
+
+const refreshProductionOverview = async () => {
+ try {
+ const res = await productionOverview();
+ const data = res?.data || {};
+ productionOverviewData.value = {
+ totalOutput: Number(data.totalOutput || 0),
+ totalScrap: Number(data.totalScrap || 0),
+ yieldRate: Number(data.yieldRate || 0),
+ };
+ } catch {
+ productionOverviewData.value = {
+ totalOutput: 0,
+ totalScrap: 0,
+ yieldRate: 0,
+ };
+ }
+};
+
+const refreshProductionRealtimeBoard = async () => {
+ try {
+ const res = await productionRealtimeBoard();
+ const data = res?.data || {};
+ realtimeBoardData.value = {
+ deviceOee: {
+ value: Number(data.deviceOee?.value || 0),
+ compareYesterday: Number(data.deviceOee?.compareYesterday || 0),
+ },
+ orderAchievementRate: {
+ value: Number(data.orderAchievementRate?.value || 0),
+ compareYesterday: Number(data.orderAchievementRate?.compareYesterday || 0),
+ },
+ defectRate: {
+ value: Number(data.defectRate?.value || 0),
+ compareYesterday: Number(data.defectRate?.compareYesterday || 0),
+ },
+ };
+ } catch {
+ realtimeBoardData.value = {
+ deviceOee: { value: 0, compareYesterday: 0 },
+ orderAchievementRate: { value: 0, compareYesterday: 0 },
+ defectRate: { value: 0, compareYesterday: 0 },
+ };
+ }
+};
+
+const refreshProductionOrderProgress = async () => {
+ try {
+ const res = await productionOrderProgress({
+ status: orderFilter.value,
+ tab: orderFilter.value,
+ pageNum: 1,
+ pageSize: 10,
+ });
+ const data = res?.data || {};
+ const statusValue = normalizeOrderFilter(data.status, orderFilter.value);
+ const total = Number(data.total || 0);
+ orderProgressMeta.value = {
+ status: statusValue,
+ tab: data.tab || orderFilter.value,
+ bizDate: data.bizDate || null,
+ total,
+ pageNum: Number(data.pageNum || 1),
+ pageSize: Number(data.pageSize || 10),
+ waitingCount: resolveProgressCount(data.waitingCount, statusValue, "waiting", total),
+ inProgressCount: resolveProgressCount(data.inProgressCount, statusValue, "inProgress", total),
+ completedCount: resolveProgressCount(data.completedCount, statusValue, "completed", total),
+ pausedCount: resolveProgressCount(data.pausedCount, statusValue, "paused", total),
+ };
+ productionOrders.value = (data.records || []).map(mapOrderProgressRecord);
+ } catch {
+ orderProgressMeta.value = {
+ status: orderFilter.value,
+ tab: orderFilter.value,
+ bizDate: null,
+ total: 0,
+ pageNum: 1,
+ pageSize: 10,
+ waitingCount: 0,
+ inProgressCount: 0,
+ completedCount: 0,
+ pausedCount: 0,
+ };
+ productionOrders.value = [];
+ }
+};
+
+const refreshTodayProductionPlan = async () => {
+ try {
+ const res = await todayProductionPlan({
+ limit: 4,
+ planDate: nowDate.value,
+ });
+ const data = res?.data || {};
+ todayPlanTotal.value = Number(data.total || 0);
+ todayPlanList.value = (data.records || []).map(mapTodayPlanRecord);
+ } catch {
+ todayPlanTotal.value = 0;
+ todayPlanList.value = [];
+ }
+};
+
+const getBusinessData = async () => {
+ const res = await getBusiness();
+ businessInfo.value = { ...res.data };
+};
+
+const analysisCustomer = async () => {
+ const res = await analysisCustomerContractAmounts();
+ sum.value = res.data.sum;
+ yny.value = res.data.yny;
+ chain.value = res.data.chain;
+ materialPieSeries.value[0].data = (res.data.item || []).map((item, index) => ({
+ ...item,
+ rate: Number(item.rate || 0),
+ itemStyle: { color: piePalette[index % piePalette.length] },
+ }));
+};
+
+const todoInfoS = async () => {
+ const res = await homeTodos();
+ todoList.value = res.data || [];
+};
+
+const statisticsReceivable = async () => {
+ const res = await statisticsReceivablePayable({ type: 1 });
+ barSeries.value[0].data = [
+ { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } },
+ { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } },
+ ];
+};
+
+const qualityStatisticsInfo = async () => {
+ const res = await qualityInspectionStatistics({ type: qualityRange.value });
+ xAxis1.value[0].data = [];
+ barSeries1.value[0].data = [];
+ barSeries1.value[1].data = [];
+ barSeries1.value[2].data = [];
+ (res.data.item || []).forEach((item) => {
+ xAxis1.value[0].data.push(item.date);
+ barSeries1.value[0].data.push(item.supplierNum);
+ barSeries1.value[1].data.push(item.processNum);
+ barSeries1.value[2].data.push(item.factoryNum);
+ });
+ qualityStatisticsObject.value.supplierNum = res.data.supplierNum;
+ qualityStatisticsObject.value.processNum = res.data.processNum;
+ qualityStatisticsObject.value.factoryNum = res.data.factoryNum;
+};
+
+const getAmountHalfYearNum = async () => {
+ const res = await getAmountHalfYear();
+ const monthName = [];
+ const receiptAmount = [];
+ const invoiceAmount = [];
+ (res.data || []).forEach((item) => {
+ monthName.push(item.month);
+ receiptAmount.push(item.receiptAmount);
+ invoiceAmount.push(item.invoiceAmount);
+ });
+
+ xAxis2.value[0].data = monthName.map((item) => item.replace(/~/g, "\n~"));
+ lineSeries.value[0].data = invoiceAmount;
+ lineSeries.value[1].data = receiptAmount;
+};
+
+const getProcessList = async () => {
+ const res = await list();
+ processOptions.value = res.data.records || [];
+};
+
+const refreshProcessStats = async () => {
+ const res = await processDataProductionStatistics({
type: processRange.value,
- processIds: selectedProcessIds.value.length > 0 ? selectedProcessIds.value.join(',') : null
- }).then(res => {
- processChartData.value = res.data.map(item => ({
- name: item.processName,
- input: item.totalInput,
- scrap: item.totalScrap,
- output: item.totalOutput
- }))
- processYAxis.value[0].data = processChartData.value.map((i) => i.name)
- activeProcessIndex.value = 0
- })
-}
+ processIds: selectedProcessIds.value.length > 0 ? selectedProcessIds.value.join(",") : null,
+ });
+ processChartData.value = (res.data || []).map((item) => ({
+ name: item.processName,
+ input: item.totalInput,
+ scrap: item.totalScrap,
+ output: item.totalOutput,
+ }));
+ processYAxis.value[0].data = processChartData.value.map((item) => item.name);
+ activeProcessIndex.value = 0;
+};
+
+const openProcessDialog = () => {
+ tempProcessIds.value = [...selectedProcessIds.value];
+ processDialogVisible.value = true;
+};
+
+const handleProcessDialogConfirm = () => {
+ selectedProcessIds.value = [...tempProcessIds.value];
+ processDialogVisible.value = false;
+ refreshProcessStats();
+};
+
+const resetProcessFilter = () => {
+ selectedProcessIds.value = [];
+ tempProcessIds.value = [];
+ refreshProcessStats();
+};
+
+const handleChartClick = (params) => {
+ if (params && params.dataIndex !== undefined) {
+ activeProcessIndex.value = params.dataIndex;
+ }
+};
+
+const goToQuick = (path) => {
+ if (!path) return;
+ router.push(path).catch(() => {});
+};
+
+watch(orderFilter, () => {
+ if (visiblePanels.value.order) {
+ refreshProductionOrderProgress();
+ }
+});
onMounted(() => {
- getBusinessData()
- analysisCustomer()
- todoInfoS()
- statisticsReceivable()
- qualityStatisticsInfo()
- getAmountHalfYearNum()
- refreshProcessStats()
-})
+ updateNowTime();
+ clockTimer = setInterval(updateNowTime, 1000);
+ if (dashboardCards.value.length > 0) {
+ getBusinessData();
+ }
+ if (visibleModules.value.production) {
+ refreshProductionOverview();
+ }
+ if (visiblePanels.value.contract) {
+ analysisCustomer();
+ }
+ if (visiblePanels.value.todo) {
+ todoInfoS();
+ }
+ if (visiblePanels.value.quality) {
+ qualityStatisticsInfo();
+ }
+ if (visiblePanels.value.receipt) {
+ statisticsReceivable();
+ getAmountHalfYearNum();
+ }
+ if (visiblePanels.value.process) {
+ getProcessList();
+ refreshProcessStats();
+ }
+ if (visiblePanels.value.order) {
+ refreshProductionOrderProgress();
+ }
+ if (visiblePanels.value.realtime) {
+ refreshProductionRealtimeBoard();
+ }
+ if (visiblePanels.value.plan) {
+ refreshTodayProductionPlan();
+ }
+});
+
+onUnmounted(() => {
+ if (clockTimer) {
+ clearInterval(clockTimer);
+ }
+});
</script>
<style scoped>
-.dashboard {
- min-height: 100vh;
- padding: 20px;
- box-sizing: border-box;
-}
-
-.dashboard-top {
- display: flex;
- gap: 20px;
- margin-bottom: 20px;
- align-items: flex-start;
- justify-content: space-evenly;
-}
-
-.company-info {
+.dashboard-cockpit {
+ min-height: calc(100vh - var(--topbar-height) - var(--tagsbar-height));
+ width: 100%;
+ min-width: 0;
padding: 0;
- overflow: hidden;
- border-radius: 12px;
- background: #fff;
- height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ overflow-x: clip;
+ margin-top: 10px;
}
-.welcome-banner {
- padding: 10px 10px;
- background: linear-gradient(135deg, rgba(229, 240, 255, 0.9), rgba(214, 232, 255, 0.7), rgba(207, 236, 255, 0.9));
+.digital-number {
+ font-family: "DIN Alternate Bold", "Segoe UI", "PingFang SC", sans-serif;
+ letter-spacing: 0;
}
-.welcome-title {
- font-size: 18px;
- font-weight: 700;
- color: #222;
- line-height: 1.3;
+.cockpit-panel {
+ position: relative;
+ background: rgba(255, 255, 255, 0.9);
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ border-radius: 16px;
+ box-shadow: 0 12px 32px rgba(15, 23, 42, 0.06);
+ backdrop-filter: blur(12px);
+ padding: 16px;
+}
+
+.welcome-panel {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 92px;
+ padding: 18px 22px;
+ background:
+ radial-gradient(circle at 8% 20%, rgba(59, 130, 246, 0.12), transparent 32%),
+ linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(239, 246, 255, 0.88));
}
.welcome-user {
- margin-right: 6px;
-}
-
-.welcome-time {
- margin-top: 10px;
- font-size: 16px;
- color: rgba(0, 0, 0, 0.55);
-}
-
-.user-card {
display: flex;
align-items: center;
- gap: 10px;
- padding: 18px 22px;
-}
-
-.user-card-main {
- display: flex;
- flex-direction: column;
- gap: 5px;
- min-width: 0;
-}
-
-.user-name {
- font-size: 16px;
- font-weight: bold;
- color: #111;
- letter-spacing: 1px;
-}
-
-.user-role {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 20px;
- padding: 5px 10px;
- background: rgba(245, 246, 248, 1);
- color: #333;
- width: fit-content;
- font-weight: 600;
-}
-
-.user-meta {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.55);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.user-meta .sep {
- margin: 0 10px;
- color: rgba(0, 0, 0, 0.25);
+ gap: 12px;
}
.avatar {
- width: 90px;
- height: 90px;
- border-radius: 50%;
+ width: 52px;
+ height: 52px;
+ border: 1px solid rgba(147, 197, 253, 0.42);
+ background: linear-gradient(145deg, #ffffff, #eef5ff);
+ box-shadow: 0 8px 20px rgba(37, 99, 235, 0.14);
+}
+
+.avatar :deep(img) {
+ width: 100%;
+ height: 100%;
object-fit: cover;
- flex: 0 0 auto;
}
-.data-cards {
- width: 50%;
- display: flex;
- gap: 16px;
- justify-content: flex-start;
- background: #ffffff;
- border-radius: 12px;
- padding: 20px;
+.avatar :deep(.el-icon) {
+ font-size: 24px;
+ color: #3b82f6;
}
-.data-title {
+.welcome-title {
+ font-size: 28px;
font-weight: 700;
- font-size: 26px;
- color: #FFFFFF;
+ color: #1e3a5f;
+ line-height: 1.08;
}
-.data-num {
+.welcome-subtitle {
+ margin-top: 6px;
+ color: #64748b;
+ font-size: 14px;
+}
+
+.welcome-meta {
+ text-align: right;
+}
+
+.meta-time {
+ font-size: 28px;
+ color: #1f3658;
+ font-weight: 700;
+}
+
+.meta-extra {
+ margin-top: 6px;
+ display: flex;
+ justify-content: flex-end;
+ gap: 14px;
+ color: #64748b;
+ font-size: 13px;
+}
+
+.meta-tip {
+ margin-top: 4px;
+ color: #7a8ca6;
+ font-size: 13px;
+}
+
+.top-row {
+ display: block;
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.stat-card {
+ position: relative;
+ border-radius: 18px;
+ padding: 18px 20px 22px;
+ min-height: 182px;
+ height: auto;
+ overflow: hidden;
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.06);
+ transition: transform 0.22s ease, box-shadow 0.22s ease;
+}
+
+.stat-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 18px 40px rgba(37, 99, 235, 0.1);
+}
+
+.stat-card.sales {
+ background: linear-gradient(135deg, #ffffff 0%, #eaf3ff 100%);
+}
+
+.stat-card.purchase {
+ background: linear-gradient(135deg, #ffffff 0%, #e8fffb 100%);
+}
+
+.stat-card.inventory {
+ background: linear-gradient(135deg, #ffffff 0%, #f2edff 100%);
+}
+
+.stat-card.production {
+ background: linear-gradient(135deg, #ffffff 0%, #fff4e6 100%);
+}
+
+.stat-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ position: relative;
+ z-index: 2;
+}
+
+.stat-title {
+ font-size: 16px;
+ font-weight: 700;
+}
+
+.stat-card.sales .stat-title {
+ color: #1d4ed8;
+}
+
+.stat-card.purchase .stat-title {
+ color: #0f766e;
+}
+
+.stat-card.inventory .stat-title {
+ color: #6d28d9;
+}
+
+.stat-card.production .stat-title {
+ color: #c2410c;
+}
+
+.stat-desc {
+ margin-top: 6px;
+ color: #64748b;
+ font-size: 13px;
+}
+
+.stat-icon-orb {
+ width: 52px;
+ height: 52px;
+ border-radius: 50%;
display: flex;
align-items: center;
- justify-content: space-between;
- margin-top: 20px;
+ justify-content: center;
+ font-size: 22px;
+ color: #fff;
}
-.data-card {
- background: #fff;
- border-radius: 12px;
- padding: 14px 10px 10px 10px;
- min-width: 160px;
- box-shadow: 0 2px 8px #eee;
- display: flex;
- flex-direction: column;
- width: 32%;
- height: 140px;
+.stat-card.sales .stat-icon-orb {
+ background: linear-gradient(135deg, #2563eb, #60a5fa);
+ box-shadow: 0 10px 24px rgba(37, 99, 235, 0.32);
}
-.data-card.sales {
- background-image: url("../assets/images/xioashoushuju.png");
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
+.stat-card.purchase .stat-icon-orb {
+ background: linear-gradient(135deg, #14b8a6, #5eead4);
+ box-shadow: 0 10px 24px rgba(20, 184, 166, 0.3);
}
-.data-card.purchase {
- background-image: url("../assets/images/caigou.png");
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
+.stat-card.inventory .stat-icon-orb {
+ background: linear-gradient(135deg, #7c3aed, #a78bfa);
+ box-shadow: 0 10px 24px rgba(124, 58, 237, 0.28);
}
-.data-card.inventory {
- background-image: url("../assets/images/kucun.png");
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
+.stat-card.production .stat-icon-orb {
+ background: linear-gradient(135deg, #f97316, #fdba74);
+ box-shadow: 0 10px 24px rgba(249, 115, 22, 0.3);
}
-.data-desc {
- font-weight: 500;
+.stat-value {
+ margin-top: 14px;
+ font-size: 30px;
+ line-height: 1.08;
+ font-weight: 700;
+ color: #0f172a;
+ position: relative;
+ z-index: 2;
+}
+
+.stat-footer {
+ margin-top: 8px;
+ color: #64748b;
font-size: 13px;
- color: #FFFFFF;
-}
-
-.data-value {
- font-size: 18px;
- font-weight: 500;
- margin: 10px 0;
- color: #FFFFFF;
-}
-
-.top-left {
display: flex;
- flex-direction: column;
- gap: 20px;
- height: 180px;
- width: 20%;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
+ z-index: 2;
+}
+
+.stat-trend {
+ margin-top: 6px;
+ color: #0ea5a4;
+ font-size: 13px;
+ line-height: 1.25;
+ font-weight: 600;
+ position: relative;
+ z-index: 2;
+}
+
+.stat-wave {
+ position: absolute;
+ left: 10px;
+ right: 10px;
+ bottom: 6px;
+ height: 30px;
+ opacity: 0.62;
+ z-index: 1;
+ pointer-events: none;
+ background:
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 340 40' preserveAspectRatio='none'%3E%3Cpath d='M0 31C20 16 44 36 66 24C87 12 107 31 129 18C148 8 169 28 193 16C214 5 237 25 259 14C280 3 306 19 340 8' fill='none' stroke='%236ea4ee' stroke-width='1.5'/%3E%3C/svg%3E")
+ center / 100% 100% no-repeat;
}
.todo-panel {
- background: #fff;
- border-radius: 12px;
- padding: 20px;
- height: 180px;
- width: 30%;
+ padding: 12px;
+}
+
+.panel-title-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.panel-title {
+ font-size: 18px;
+ font-weight: 700;
+ color: #1f3658;
+ position: relative;
+ padding-left: 12px;
+}
+
+.panel-title::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 4px;
+ height: 18px;
+ border-radius: 4px;
+ background: linear-gradient(180deg, #2563eb, #38bdf8);
+}
+
+.panel-more {
+ font-size: 14px;
+ color: #2563eb;
+ cursor: pointer;
}
.todo-list {
- height: 100px;
- list-style: none;
+ margin: 10px 0 0;
padding: 0;
- margin: 0;
- font-size: 15px;
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-height: 230px;
overflow-y: auto;
}
-.todo-list li {
- border-radius: 8px;
- margin-bottom: 12px;
- padding: 8px 20px;
- height: 74px;
+.todo-item {
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ border-radius: 12px;
+ background: #f7fbff;
+ padding: 10px 12px;
+}
+
+.todo-head {
display: flex;
justify-content: space-between;
- align-items: center;
- background: rgba(225, 227, 250, 0.62);
-}
-
-.todo-title {
- font-weight: 400;
- font-size: 12px;
- color: #000000;
- position: relative;
-}
-
-.todo-title::before {
- content: '';
- /* 蹇呴渶锛岃〃绀鸿繖閲屾湁涓�涓唴瀹� */
- position: absolute;
- left: -10px;
- /* 瀹氫綅鍒板乏渚� */
- top: 50%;
- /* 鍨傜洿灞呬腑 */
- transform: translateY(-50%);
- /* 寰皟鍨傜洿灞呬腑 */
- width: 6px;
- /* 鍦嗙殑鐩村緞 */
- height: 6px;
- /* 鍦嗙殑鐩村緞 */
- background: #498CEB;
- border-radius: 50%;
- /* 璁╁叾鍙樻垚鍦嗗舰 */
-}
-
-.todo-division {
- font-weight: 400;
- font-size: 12px;
- color: #000000;
-}
-
-.todo-time {
- font-weight: 400;
- font-size: 12px;
- color: #000000;
-}
-
-.todo-meta {
- color: #888;
+ gap: 10px;
+ color: #334155;
font-size: 13px;
}
-.dashboard-row {
- display: flex;
- gap: 20px;
- margin-bottom: 20px;
-}
-
-.main-panel {
- background: #fff;
- border-radius: 12px;
- padding: 20px;
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
-}
-
-.section-title {
- position: relative;
- font-size: 18px;
- color: #333;
- padding-left: 10px;
- margin-bottom: 10px;
- font-weight: 700;
-}
-
-.section-title::before {
- position: absolute;
- left: 0;
- top: 4px;
- content: '';
- width: 4px;
- height: 18px;
- background-color: #002FA7;
- border-radius: 2px;
-}
-
-.contract-info {
- display: flex;
- align-items: center;
- gap: 20px;
- height: 90px;
- background: rgba(245, 245, 245, 0.59);
- width: 100%;
- border-radius: 10px;
- padding: 10px 30px;
-}
-
-.contract-summary {
- display: flex;
- align-items: center;
- gap: 30px;
-}
-
-.contract-card {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.contract-name {
- font-weight: 400;
- font-size: 14px;
- color: #050505;
-}
-
-.contract-meta {
- display: flex;
- align-items: center;
- width: 100%;
- gap: 80px;
-}
-
-.main-amount {
- font-size: 24px;
- color: rgba(51, 50, 50, 0.85);
-}
-
-.up {
- color: #e57373;
-}
-
-.contract-list {
- margin-top: 16px;
- font-size: 14px;
- color: #666;
- list-style: none;
- padding: 0;
- height: 190px;
- overflow-y: auto;
- width: 460px;
-}
-
-.line {
- position: relative;
- width: 230px;
-}
-
-.line::after {
- content: '';
- position: absolute;
- right: 2px;
- top: 0;
- bottom: 0;
- width: 1px;
- background-color: #C9C5C5;
- border-radius: 2px;
-}
-
-.contract-list li {
- margin-top: 10px;
-}
-
-.quality-cards {
- display: flex;
- gap: 12px;
- margin-bottom: 12px;
-}
-
-.quality-card {
- border-radius: 8px;
- padding: 15px 10px 10px 50px;
- font-weight: 400;
+.todo-desc {
+ margin-top: 6px;
+ color: #64748b;
font-size: 12px;
- color: rgba(0, 0, 0, 0.67);
- width: 236px;
- height: 49px;
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
+ line-height: 1.45;
}
-.quality-card.one {
- background-image: url("../assets/images/yuancailiao.png");
+.todo-no {
+ color: #1f3658;
+ font-weight: 600;
}
-.quality-card.two {
- background-image: url("../assets/images/guocheng.png");
+.panel-empty {
+ min-height: 140px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--text-tertiary);
}
-.quality-card.three {
- background-image: url("../assets/images/chuchang.png");
-
+.empty-home-panel {
+ min-height: 220px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #64748b;
+ font-size: 14px;
}
-.quality-card span {
- color: #4fc3f7;
- font-weight: bold;
- margin-left: 6px;
+.main-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 400px;
+ gap: 16px;
+ align-items: start;
}
-.chart {
- width: 100%;
- height: 220px;
- margin-top: 10px;
+.left-column,
+.right-column {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ min-width: 0;
}
.process-panel {
- padding-bottom: 10px;
+ padding-bottom: 12px;
}
-.process-panel__header {
+.panel-actions {
display: flex;
- justify-content: space-between;
+ gap: 8px;
align-items: center;
+ flex-wrap: wrap;
}
-.process-panel__body {
- display: flex;
- gap: 24px;
- align-items: stretch;
+.process-body {
margin-top: 10px;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 232px;
+ gap: 12px;
+ align-items: stretch;
+ min-height: 0;
}
-.process-panel__chart {
- flex: 1;
- min-width: 0;
- padding: 6px 0;
+.process-chart {
+ position: relative;
+ border: 1px solid rgba(148, 163, 184, 0.24);
+ border-radius: 14px;
+ background:
+ linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(244, 249, 255, 0.9)),
+ repeating-linear-gradient(
+ to right,
+ rgba(148, 163, 184, 0.07) 0,
+ rgba(148, 163, 184, 0.07) 1px,
+ transparent 1px,
+ transparent 48px
+ ),
+ repeating-linear-gradient(
+ to bottom,
+ rgba(148, 163, 184, 0.06) 0,
+ rgba(148, 163, 184, 0.06) 1px,
+ transparent 1px,
+ transparent 34px
+ );
+ overflow: hidden;
+ padding: 10px;
}
-.process-panel__aside {
- width: 260px;
+.process-chart.empty :deep(canvas) {
+ opacity: 0.2;
+}
+
+.chart-empty {
+ position: absolute;
+ inset: 0;
display: flex;
flex-direction: column;
- gap: 12px;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ color: #64748b;
+ font-size: 14px;
+ text-align: center;
+ pointer-events: auto;
+ padding: 12px;
+}
+
+.chart-empty .el-icon {
+ color: #2563eb;
+ font-size: 28px;
+}
+
+.chart-empty-title {
+ font-size: 16px;
+ font-weight: 700;
+ color: #334155;
+}
+
+.chart-empty-desc {
+ font-size: 13px;
+ color: #64748b;
+}
+
+.chart-empty-actions {
+ margin-top: 4px;
+ display: flex;
+ gap: 8px;
+}
+
+.process-aside {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.process-legend,
+.process-card {
+ border: 1px solid rgba(148, 163, 184, 0.22);
+ border-radius: 12px;
+ background: rgba(255, 255, 255, 0.84);
+ padding: 10px 12px;
+}
+
+.process-card {
+ min-height: 74px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
}
.process-legend {
+ min-height: 74px;
display: flex;
flex-direction: column;
- gap: 10px;
- align-items: flex-start;
- padding: 8px 6px;
+ gap: 6px;
+ justify-content: center;
}
-.process-legend__item {
+.process-legend-item {
+ color: #475569;
+ font-size: 12px;
display: flex;
align-items: center;
gap: 8px;
- font-size: 13px;
- color: rgba(0, 0, 0, 0.55);
}
.dot {
width: 10px;
height: 10px;
- border-radius: 2px;
+ border-radius: 3px;
display: inline-block;
}
.dot-blue {
- background: #1E5BFF;
+ background: #2563eb;
}
-.dot-yellow {
- background: #F7B500;
+.dot-orange {
+ background: #f59e0b;
}
-.dot-teal {
- background: #19C6C6;
+.dot-cyan {
+ background: #14b8a6;
}
-.process-card {
- background: rgba(245, 247, 250, 0.9);
- border-radius: 10px;
- padding: 16px 16px;
-}
-
-.process-card--name {
- background: rgba(235, 242, 255, 1);
- color: #1E5BFF;
- font-weight: 800;
+.process-name {
+ color: #2563eb;
+ font-weight: 700;
font-size: 14px;
+ line-height: 1.2;
+ margin-bottom: 2px;
}
-.process-card__label {
- font-size: 13px;
- color: rgba(0, 0, 0, 0.55);
- margin-bottom: 10px;
-}
-
-.process-card__value {
- font-size: 24px;
- font-weight: 800;
- color: rgba(0, 0, 0, 0.8);
-}
-
-.process-card__value .unit {
+.process-label {
font-size: 12px;
+ color: #64748b;
+}
+
+.process-value {
+ margin-top: 4px;
+ font-size: 24px;
+ color: #0f172a;
+ font-weight: 700;
+}
+
+.chart-row {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px;
+}
+
+.contract-summary {
+ display: grid;
+ grid-template-columns: 1fr 190px;
+ gap: 12px;
+ align-items: center;
+ margin-top: 8px;
+}
+
+.contract-card {
+ border-radius: 14px;
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ background: #f8fbff;
+ padding: 12px;
+}
+
+.contract-name {
+ color: #64748b;
+ font-size: 13px;
+}
+
+.contract-main {
+ margin-top: 8px;
+ font-size: 34px;
+ font-weight: 700;
+ color: #0f172a;
+}
+
+.contract-compare {
+ margin-top: 8px;
+ font-size: 13px;
+ color: #64748b;
+ display: flex;
+ gap: 10px;
+}
+
+.rise {
+ color: #0ea5a4;
font-weight: 600;
- color: rgba(0, 0, 0, 0.45);
+}
+
+.contract-list {
+ margin: 8px 0 0;
+ padding: 0;
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.contract-list li {
+ display: grid;
+ grid-template-columns: 12px minmax(0, 1fr) 56px auto;
+ align-items: center;
+ gap: 8px;
+ color: #475569;
+ font-size: 13px;
+}
+
+.legend-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+}
+
+.contract-item-name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.contract-item-rate {
+ text-align: right;
+ color: #64748b;
+}
+
+.contract-item-value {
+ color: #1f3658;
+ text-align: right;
+}
+
+.quality-cards {
+ margin: 8px 0 10px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.quality-card {
+ border-radius: 12px;
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ padding: 10px 12px;
+ color: #475569;
+ font-size: 13px;
+}
+
+.quality-card.one {
+ background: linear-gradient(135deg, #ffffff, #edf4ff);
+}
+
+.quality-card.two {
+ background: linear-gradient(135deg, #ffffff, #ebfffc);
+}
+
+.quality-card.three {
+ background: linear-gradient(135deg, #ffffff, #fff3e6);
+}
+
+.quality-card span {
+ color: #0f172a;
+ font-weight: 700;
margin-left: 6px;
}
-@media (max-width: 1200px) {
- .process-panel__body {
- flex-direction: column;
- }
+.realtime-grid {
+ margin-top: 10px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 10px;
+}
- .process-panel__aside {
- width: 100%;
- flex-direction: row;
- flex-wrap: wrap;
- }
+.realtime-item {
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ border-radius: 12px;
+ background: #f8fbff;
+ padding: 10px 8px;
+ text-align: center;
+}
- .process-card {
- flex: 1;
- min-width: 220px;
- }
+.realtime-value {
+ font-size: 26px;
+ color: #0f172a;
+ font-weight: 700;
+}
+
+.realtime-label {
+ margin-top: 8px;
+ font-size: 12px;
+ color: #475569;
+}
+
+.realtime-delta {
+ margin-top: 4px;
+ font-size: 12px;
+}
+
+.realtime-delta.up {
+ color: #0ea5a4;
+}
+
+.realtime-delta.down {
+ color: #f59e0b;
+}
+
+.realtime-delta.flat {
+ color: #64748b;
+}
+
+.warning-list {
+ margin-top: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.warning-item {
+ display: grid;
+ grid-template-columns: 10px minmax(0, 1fr) 72px;
+ align-items: center;
+ gap: 10px;
+ border: 1px solid rgba(148, 163, 184, 0.2);
+ border-radius: 12px;
+ background: #f8fbff;
+ padding: 10px;
+}
+
+.warning-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ display: block;
+}
+
+.warning-dot.device {
+ background: #ef4444;
+}
+
+.warning-dot.quality {
+ background: #f59e0b;
+}
+
+.warning-dot.material {
+ background: #3b82f6;
+}
+
+.warning-title {
+ color: #334155;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.warning-desc {
+ color: #64748b;
+ font-size: 12px;
+ margin-top: 4px;
+}
+
+.warning-time {
+ color: #64748b;
+ font-size: 12px;
+ text-align: right;
+}
+
+.order-panel {
+ min-height: 0;
+}
+
+.quick-panel {
+ min-height: 0;
+}
+
+.table-progress {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.table-progress span {
+ font-size: 12px;
+ color: #475569;
+}
+
+.quick-grid {
+ margin-top: 10px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ grid-auto-rows: 58px;
+ gap: 10px;
+}
+
+.quick-item {
+ border: 1px solid rgba(148, 163, 184, 0.22);
+ background: rgba(255, 255, 255, 0.92);
+ border-radius: 12px;
+ color: #334155;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 10px;
+ font-size: 12px;
+ cursor: pointer;
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
+}
+
+.plan-panel {
+ min-height: 236px;
+}
+
+.quality-panel {
+ min-height: 0;
+}
+
+.receipt-panel {
+ min-height: 340px;
+}
+
+.plan-list {
+ margin: 10px 0 0;
+ padding: 0;
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.plan-item {
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ border-radius: 12px;
+ background: #f8fbff;
+ padding: 10px 12px;
+}
+
+.plan-main {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ align-items: center;
+}
+
+.plan-order {
+ font-size: 13px;
+ color: #1f3658;
+ font-weight: 600;
+}
+
+.plan-name {
+ font-size: 13px;
+ color: #334155;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.plan-meta {
+ margin-top: 6px;
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+ font-size: 12px;
+ color: #64748b;
+}
+
+.quick-item:hover {
+ transform: translateY(-2px);
+ border-color: rgba(96, 165, 250, 0.45);
+ box-shadow: 0 14px 28px rgba(37, 99, 235, 0.1);
+}
+
+.quick-icon {
+ width: 30px;
+ height: 30px;
+ border-radius: 8px;
+ background: linear-gradient(135deg, #2563eb, #38bdf8);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ color: #fff;
+}
+
+.quick-item:nth-child(2) .quick-icon,
+.quick-item:nth-child(6) .quick-icon {
+ background: linear-gradient(135deg, #14b8a6, #5eead4);
+}
+
+.quick-item:nth-child(3) .quick-icon,
+.quick-item:nth-child(5) .quick-icon {
+ background: linear-gradient(135deg, #7c3aed, #a78bfa);
+}
+
+.quick-item:nth-child(4) .quick-icon,
+.quick-item:nth-child(7) .quick-icon {
+ background: linear-gradient(135deg, #f97316, #fdba74);
}
.process-selection-wrapper {
max-height: 400px;
overflow-y: auto;
- padding: 10px;
+ padding: 8px;
}
.process-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
- gap: 12px;
+ gap: 10px;
}
:deep(.el-checkbox.is-bordered) {
margin-left: 0 !important;
width: 100%;
}
-</style>
\ No newline at end of file
+
+:deep(.el-radio-button__inner) {
+ border-radius: 10px !important;
+ background: #f8fbff;
+ border-color: rgba(148, 163, 184, 0.22) !important;
+ color: #475569 !important;
+}
+
+:deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
+ background: linear-gradient(135deg, #2f80ff, #38bdf8) !important;
+ border-color: transparent !important;
+ color: #fff !important;
+}
+
+:deep(.el-progress-circle__track) {
+ stroke: rgba(148, 163, 184, 0.3);
+}
+
+:deep(.el-progress-bar__outer) {
+ background-color: rgba(148, 163, 184, 0.2);
+}
+
+:deep(.order-panel .el-progress-bar__inner) {
+ background: linear-gradient(90deg, #2563eb, #38bdf8) !important;
+}
+
+:deep(.order-panel .el-table) {
+ border-radius: 14px;
+}
+
+:deep(.order-panel .el-table th.el-table__cell) {
+ background: #f2f7ff !important;
+}
+
+:deep(.order-panel .el-table__body tr:hover > td.el-table__cell) {
+ background: #f8fbff !important;
+}
+
+:deep(.order-panel .el-tag--success) {
+ color: #0f766e;
+ background: #e9fdf7;
+ border-color: #b7f2e0;
+}
+
+:deep(.order-panel .el-tag--warning) {
+ color: #b45309;
+ background: #fff6e8;
+ border-color: #fde0b8;
+}
+
+:deep(.order-panel .el-tag--primary) {
+ color: #2563eb;
+ background: #eaf2ff;
+ border-color: #b8d2ff;
+}
+
+@media (max-width: 1600px) {
+ .main-grid {
+ grid-template-columns: minmax(0, 1fr) 380px;
+ }
+}
+
+@media (max-width: 1366px) {
+ .main-grid {
+ grid-template-columns: minmax(0, 1fr) 340px;
+ }
+
+ .stats-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 1200px) {
+ .main-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .right-column {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ }
+
+ .chart-row {
+ grid-template-columns: 1fr;
+ }
+
+ .process-body {
+ grid-template-columns: 1fr;
+ }
+
+ .realtime-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .quality-cards {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 768px) {
+ .welcome-panel {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .welcome-meta {
+ text-align: left;
+ }
+
+ .meta-extra {
+ justify-content: flex-start;
+ }
+
+ .stats-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .right-column {
+ grid-template-columns: 1fr;
+ }
+
+ .realtime-grid {
+ grid-template-columns: 1fr;
+ }
+}
+</style>
diff --git a/src/views/inventoryManagement/dispatchLog/Record.vue b/src/views/inventoryManagement/dispatchLog/Record.vue
index 6060cf5..fb24e40 100644
--- a/src/views/inventoryManagement/dispatchLog/Record.vue
+++ b/src/views/inventoryManagement/dispatchLog/Record.vue
@@ -1,115 +1,153 @@
<template>
- <div>
- <div class="search_form" style="margin-bottom: 10px;">
- <div>
- <span class="search_title ml10">鍑哄簱鏃ユ湡锛�</span>
- <el-date-picker
- v-model="searchForm.timeStr"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- @change="handleQuery"
- />
- <span class="search_title ml10">鏉ユ簮锛�</span>
- <el-select v-model="searchForm.recordType"
- style="width: 240px"
- placeholder="璇烽�夋嫨"
- clearable>
- <el-option v-for="item in stockRecordTypeOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"/>
- </el-select>
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
- >鎼滅储</el-button
- >
- </div>
- <div>
- <el-button 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>
- </div>
- </div>
- <div class="table_list">
- <el-table
- :data="tableData"
- border
- v-loading="tableLoading"
- @selection-change="handleSelectionChange"
- :expand-row-keys="expandedRowKeys"
- :row-key="(row) => row.id"
- style="width: 100%"
- height="calc(100vh - 18.5em)"
- >
- <el-table-column align="center" type="selection" width="55" />
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <div>
+ <div class="search_form" style="margin-bottom: 10px">
+ <el-form
+ ref="searchFormRef"
+ :model="searchForm"
+ class="demo-form-inline"
+ >
+ <el-row :gutter="20">
+ <el-col :span="4">
+ <el-form-item label="鍑哄簱鏃ユ湡" prop="timeStr">
+ <el-date-picker v-model="searchForm.timeStr"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="浜у搧澶х被" prop="productName">
+ <el-input v-model="searchForm.productName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+
+ <el-col :span="4">
+ <el-form-item label="浜у搧瑙勬牸" prop="model">
+ <el-input v-model="searchForm.model"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="鎵瑰彿" prop="batchNo">
+ <el-input v-model="searchForm.batchNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="鏉ユ簮" prop="recordType">
+ <el-select
+ v-model="searchForm.recordType"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option
+ v-for="item in stockRecordTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <!-- 鎸夐挳 -->
+ <el-col :span="4">
+ <el-form-item>
+ <el-button type="primary" @click="getList">
+ 鎼滅储
+ </el-button>
+
+ <el-button @click="resetSearch">
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+ <div class="actions">
+ <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>
+ </div>
+ <div class="table_list">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.id"
+ style="width: 100%"
+ height="calc(100vh - 18.5em)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
<el-table-column
- label="鍑哄簱鎵规"
- prop="outboundBatches"
- min-width="100"
- show-overflow-tooltip
+ label="鍑哄簱鎵规"
+ prop="outboundBatches"
+ min-width="100"
+ show-overflow-tooltip
/>
- <el-table-column
- label="鍑哄簱鏃ユ湡"
- prop="createTime"
- show-overflow-tooltip
- />
- <el-table-column
- label="浜у搧澶х被"
- prop="productName"
- show-overflow-tooltip
- />
- <el-table-column
- label="瑙勬牸鍨嬪彿"
- prop="model"
- show-overflow-tooltip
- />
- <el-table-column
- label="鎵瑰彿"
- prop="batchNo"
- show-overflow-tooltip
- />
- <el-table-column
- label="鍗曚綅"
- prop="unit"
- show-overflow-tooltip
- />
- <el-table-column
- label="鍑哄簱鏁伴噺"
- prop="stockOutNum"
- show-overflow-tooltip
- />
- <el-table-column
- label="鍑哄簱浜�"
- prop="createBy"
- show-overflow-tooltip
- />
- <el-table-column label="鏉ユ簮"
- prop="recordType"
- show-overflow-tooltip>
+ <el-table-column
+ label="鍑哄簱鏃ユ湡"
+ prop="createTime"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="浜у搧澶х被"
+ prop="productName"
+ show-overflow-tooltip
+ />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" show-overflow-tooltip />
+ <el-table-column label="鎵瑰彿" prop="batchNo" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" show-overflow-tooltip />
+ <el-table-column
+ label="鍑哄簱鏁伴噺"
+ prop="stockOutNum"
+ show-overflow-tooltip
+ />
+ <el-table-column label="鍑哄簱浜�" prop="createBy" show-overflow-tooltip />
+ <el-table-column label="鏉ユ簮" prop="recordType" show-overflow-tooltip>
<template #default="scope">
{{ getRecordType(scope.row.recordType) }}
</template>
</el-table-column>
- <el-table-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"
- :total="total"
- layout="total, sizes, prev, pager, next, jumper"
- :page="page.current"
- :limit="page.size"
- @pagination="paginationChange"
- />
- </div>
- </div>
+ <el-table-column
+ label="瀹℃壒鐘舵��"
+ prop="approvalStatus"
+ show-overflow-tooltip
+ >
+ <template #default="scope">
+ <el-tag
+ :type="getApprovalStatusTagType(scope.row.approvalStatus)"
+ size="small"
+ >
+ {{ getApprovalStatusLabel(scope.row.approvalStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+ </div>
</template>
<script setup>
@@ -119,12 +157,13 @@
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import {
- getStockOutPage,
- delPendingStockOut,
- batchApproveStockOutRecords,
+ getStockOutPage,
+ delPendingStockOut,
+ batchApproveStockOutRecords,
} from "@/api/inventoryManagement/stockOut.js";
import {
- findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions,
+ findAllQualifiedStockOutRecordTypeOptions,
+ findAllUnQualifiedStockOutRecordTypeOptions,
} from "@/api/basicData/enum.js";
const userStore = useUserStore();
@@ -135,8 +174,8 @@
// 鏉ユ簮绫诲瀷閫夐」
const stockRecordTypeOptions = ref([]);
const page = reactive({
- current: 1,
- size: 100,
+ current: 1,
+ size: 100,
});
const total = ref(0);
@@ -144,13 +183,13 @@
type: {
type: String,
required: true,
- default: '0'
+ default: "0",
},
topParentProductId: {
type: [String, Number],
- default: undefined
- }
-})
+ default: undefined,
+ },
+});
// 鎵撳嵃鐩稿叧
const printPreviewVisible = ref(false);
@@ -158,186 +197,217 @@
// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
const data = reactive({
- searchForm: {
- supplierName: "",
- timeStr: "",
+ searchForm: {
+ supplierName: "",
+ timeStr: "",
recordType: "",
- }
+ },
});
const { searchForm } = toRefs(data);
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
+const searchFormRef = ref(null);
+
+const resetSearch = () => {
+ searchFormRef.value?.resetFields();
+ page.current = 1;
+ getList();
+}
+
const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
};
const getList = () => {
- tableLoading.value = true;
- getStockOutPage({ ...searchForm.value, ...page, type: props.type, topParentProductId: props.topParentProductId })
- .then((res) => {
- tableLoading.value = false;
- tableData.value = res.data.records;
- tableData.value.map((item) => {
- item.children = [];
- });
- total.value = res.data.total;
- })
- .catch(() => {
- tableLoading.value = false;
- });
+ tableLoading.value = true;
+ getStockOutPage({
+ ...searchForm.value,
+ ...page,
+ topParentProductId: props.topParentProductId,
+ })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ tableData.value.map((item) => {
+ item.children = [];
+ });
+ total.value = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
};
const getRecordType = (recordType) => {
- return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
-}
+ return (
+ stockRecordTypeOptions.value.find((item) => item.value === recordType)
+ ?.label || ""
+ );
+};
const approvalStatusLabelMap = {
- 0: "寰呭鎵�",
- 1: "閫氳繃",
- 2: "椹冲洖",
- pending: "寰呭鎵�",
- approved: "閫氳繃",
- rejected: "椹冲洖",
- PENDING: "寰呭鎵�",
- APPROVED: "閫氳繃",
- REJECTED: "椹冲洖",
+ 0: "寰呭鎵�",
+ 1: "閫氳繃",
+ 2: "椹冲洖",
+ 3: "寰呯‘璁�",
+ pending: "寰呭鎵�",
+ approved: "閫氳繃",
+ rejected: "椹冲洖",
+ PENDING: "寰呭鎵�",
+ APPROVED: "閫氳繃",
+ REJECTED: "椹冲洖",
};
const getApprovalStatusLabel = (status) => {
- if (status === null || status === undefined || status === "") {
- return "寰呭鎵�";
- }
- return approvalStatusLabelMap[status] || "寰呭鎵�";
+ if (status === null || status === undefined || status === "") {
+ return "寰呭鎵�";
+ }
+ return approvalStatusLabelMap[status] || "寰呭鎵�";
+};
+
+// 閫氳繃/椹冲洖鍥哄畾鑹诧紱鍏朵綑锛堝惈寰呭鎵广�佺┖鍊笺�佹湭鏄犲皠浣嗘枃妗堜负寰呭鎵癸級缁熶竴鐢� warning 棰勮鑹�
+const getApprovalStatusTagType = (status) => {
+ if (
+ status === 1 ||
+ status === "1" ||
+ status === "approved" ||
+ status === "APPROVED"
+ )
+ return "success";
+ if (
+ status === 2 ||
+ status === "2" ||
+ status === "rejected" ||
+ status === "REJECTED"
+ )
+ return "danger";
+ return "warning";
};
// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
const fetchStockRecordTypeOptions = () => {
- if (props.type === '0') {
- findAllQualifiedStockOutRecordTypeOptions()
- .then(res => {
- stockRecordTypeOptions.value = res.data;
- })
- return
+ if (props.type === "0") {
+ findAllQualifiedStockOutRecordTypeOptions().then((res) => {
+ stockRecordTypeOptions.value = res.data;
+ });
+ return;
}
- findAllUnQualifiedStockOutRecordTypeOptions()
- .then(res => {
- stockRecordTypeOptions.value = res.data;
- })
-}
+ findAllUnQualifiedStockOutRecordTypeOptions().then((res) => {
+ stockRecordTypeOptions.value = res.data;
+ });
+};
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
- // 杩囨护鎺夊瓙鏁版嵁
- selectedRows.value = selection.filter((item) => item.id);
- console.log("selection", selectedRows.value);
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter((item) => item.id);
+ 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("宸插彇娑�");
- });
+ 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 = () => {
- ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/stockOutRecord/exportStockOutRecord", {type: props.type}, props.type === '0' ? "鍚堟牸鍑哄簱鍙拌处.xlsx" : "涓嶅悎鏍煎嚭搴撳彴璐�.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download(
+ "/stockOutRecord/exportStockOutRecord",
+ { type: props.type },
+ props.type === "0" ? "鍚堟牸鍑哄簱鍙拌处.xlsx" : "涓嶅悎鏍煎嚭搴撳彴璐�.xlsx"
+ );
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鍒犻櫎
const handleDelete = () => {
- let ids = [];
- if (selectedRows.value.length > 0) {
- ids = selectedRows.value.map((item) => item.id);
- } else {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- delPendingStockOut(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delPendingStockOut(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鎵撳嵃鍔熻兘
const handlePrint = () => {
- if (selectedRows.value.length === 0) {
- proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
- return;
- }
- printData.value = [...selectedRows.value];
- console.log('鎵撳嵃鏁版嵁:', printData.value);
- printPreviewVisible.value = true;
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
+ return;
+ }
+ printData.value = [...selectedRows.value];
+ console.log("鎵撳嵃鏁版嵁:", printData.value);
+ printPreviewVisible.value = true;
};
// 鎵ц鎵撳嵃
const executePrint = () => {
- console.log('寮�濮嬫墽琛屾墦鍗帮紝鏁版嵁鏉℃暟:', printData.value.length);
- console.log('鎵撳嵃鏁版嵁:', printData.value);
-
- // 鍒涘缓涓�涓柊鐨勬墦鍗扮獥鍙�
- const printWindow = window.open('', '_blank', 'width=800,height=600');
-
- // 鏋勫缓鎵撳嵃鍐呭
- let printContent = `
+ console.log("寮�濮嬫墽琛屾墦鍗帮紝鏁版嵁鏉℃暟:", printData.value.length);
+ console.log("鎵撳嵃鏁版嵁:", printData.value);
+
+ // 鍒涘缓涓�涓柊鐨勬墦鍗扮獥鍙�
+ const printWindow = window.open("", "_blank", "width=800,height=600");
+
+ // 鏋勫缓鎵撳嵃鍐呭
+ let printContent = `
<!DOCTYPE html>
<html>
<head>
@@ -471,10 +541,10 @@
</head>
<body>
`;
-
- // 涓烘瘡鏉℃暟鎹敓鎴愭墦鍗伴〉闈�
- printData.value.forEach((item, index) => {
- printContent += `
+
+ // 涓烘瘡鏉℃暟鎹敓鎴愭墦鍗伴〉闈�
+ printData.value.forEach((item, index) => {
+ printContent += `
<div class="print-page">
<div class="delivery-note">
<div class="header">
@@ -494,7 +564,7 @@
</div>
<div class="info-row">
<span class="label">鍗曞彿锛�</span>
- <span class="value">${item.code || ''}</span>
+ <span class="value">${item.code || ""}</span>
</div>
</div>
@@ -512,12 +582,12 @@
</thead>
<tbody>
<tr>
- <td>${item.productName || '鐮傜伆鐮�'}</td>
- <td>${item.model || '鏍囧噯'}</td>
- <td>${item.unit || '鍧�'}</td>
- <td>${item.taxInclusiveUnitPrice || '0'}</td>
- <td>${item.inboundNum || '2000'}</td>
- <td>${item.taxInclusiveTotalPrice || '0'}</td>
+ <td>${item.productName || "鐮傜伆鐮�"}</td>
+ <td>${item.model || "鏍囧噯"}</td>
+ <td>${item.unit || "鍧�"}</td>
+ <td>${item.taxInclusiveUnitPrice || "0"}</td>
+ <td>${item.inboundNum || "2000"}</td>
+ <td>${item.taxInclusiveTotalPrice || "0"}</td>
</tr>
</tbody>
<tfoot>
@@ -526,8 +596,10 @@
<td class="total-value"></td>
<td class="total-value"></td>
<td class="total-value"></td>
- <td class="total-value">${item.inboundNum || '2000'}</td>
- <td class="total-value">${item.taxInclusiveTotalPrice || '0'}</td>
+ <td class="total-value">${item.inboundNum || "2000"}</td>
+ <td class="total-value">${
+ item.taxInclusiveTotalPrice || "0"
+ }</td>
</tr>
</tfoot>
</table>
@@ -551,7 +623,7 @@
<div class="footer-row">
<div class="footer-item">
<span class="label">鎿嶄綔鍛橈細</span>
- <span class="value">${userStore.nickName || '鎾曞紑鍓�'}</span>
+ <span class="value">${userStore.nickName || "鎾曞紑鍓�"}</span>
</div>
<div class="footer-item">
<span class="label">鎵撳嵃鏃ユ湡锛�</span>
@@ -562,51 +634,49 @@
</div>
</div>
`;
- });
-
- printContent += `
+ });
+
+ printContent += `
</body>
</html>
`;
-
- // 鍐欏叆鍐呭鍒版柊绐楀彛
- printWindow.document.write(printContent);
- printWindow.document.close();
-
- // 绛夊緟鍐呭鍔犺浇瀹屾垚鍚庢墦鍗�
- printWindow.onload = () => {
- setTimeout(() => {
- printWindow.print();
- printWindow.close();
- printPreviewVisible.value = false;
- }, 500);
- };
+
+ // 鍐欏叆鍐呭鍒版柊绐楀彛
+ 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}`;
+ 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 year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ const hours = String(date.getHours()).padStart(2, "0");
+ const minutes = String(date.getMinutes()).padStart(2, "0");
+ const seconds = String(date.getSeconds()).padStart(2, "0");
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
};
onMounted(() => {
- getList();
+ getList();
fetchStockRecordTypeOptions();
});
@@ -621,169 +691,176 @@
<style scoped lang="scss">
.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;
+ }
+}
+
+.actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 10px;
}
</style>
diff --git a/src/views/inventoryManagement/receiptManagement/Record.vue b/src/views/inventoryManagement/receiptManagement/Record.vue
index 7ca8fea..56e6f74 100644
--- a/src/views/inventoryManagement/receiptManagement/Record.vue
+++ b/src/views/inventoryManagement/receiptManagement/Record.vue
@@ -1,43 +1,82 @@
<template>
<div>
<div class="search_form" style="margin-bottom: 10px;">
- <div>
- <span class="search_title ml10">鍏ュ簱鏃ユ湡锛�</span>
- <el-date-picker v-model="searchForm.timeStr"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- @change="handleQuery"/>
- <span class="search_title ml10">浜у搧澶х被锛�</span>
- <el-input v-model="searchForm.productName"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- clearable/>
- <span class="search_title ml10">鏉ユ簮锛�</span>
- <el-select v-model="searchForm.recordType"
- style="width: 240px"
- placeholder="璇烽�夋嫨"
- clearable>
- <el-option v-for="item in stockRecordTypeOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"/>
- </el-select>
- <el-button type="primary"
- @click="handleQuery"
- style="margin-left: 10px">鎼滅储
- </el-button>
- </div>
- <div>
- <el-button type="primary" @click="handleBatchApprove">瀹℃壒</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button type="danger"
- plain
- @click="handleDelete">鍒犻櫎
- </el-button>
- </div>
+ <el-form
+ ref="searchFormRef"
+ :model="searchForm"
+ class="demo-form-inline"
+ >
+ <el-row :gutter="20">
+ <el-col :span="4">
+ <el-form-item label="鍏ュ簱鏃ユ湡" prop="timeStr">
+ <el-date-picker v-model="searchForm.timeStr"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="浜у搧澶х被" prop="productName">
+ <el-input v-model="searchForm.productName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+
+ <el-col :span="4">
+ <el-form-item label="浜у搧瑙勬牸" prop="model">
+ <el-input v-model="searchForm.model"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="鎵瑰彿" prop="batchNo">
+ <el-input v-model="searchForm.batchNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="鏉ユ簮" prop="recordType">
+ <el-select v-model="searchForm.recordType"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨"
+ clearable>
+ <el-option v-for="item in stockRecordTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <!-- 鎸夐挳 -->
+ <el-col :span="4">
+ <el-form-item>
+ <el-button type="primary" @click="getList">
+ 鎼滅储
+ </el-button>
+
+ <el-button @click="resetSearch">
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+ <div class="actions">
+ <el-button type="primary" @click="handleBatchApprove">瀹℃壒</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger"
+ plain
+ @click="handleDelete">鍒犻櫎
+ </el-button>
</div>
<div class="table_list">
<el-table :data="tableData"
@@ -50,6 +89,7 @@
height="calc(100vh - 18.5em)">
<el-table-column align="center"
type="selection"
+ :selectable="isRowSelectableForApprove"
width="55"/>
<el-table-column align="center"
label="搴忓彿"
@@ -91,7 +131,9 @@
prop="approvalStatus"
show-overflow-tooltip>
<template #default="scope">
- {{ getApprovalStatusLabel(scope.row.approvalStatus) }}
+ <el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" size="small">
+ {{ getApprovalStatusLabel(scope.row.approvalStatus) }}
+ </el-tag>
</template>
</el-table-column>
</el-table>
@@ -121,7 +163,8 @@
batchApproveStockInRecords,
} from "@/api/inventoryManagement/stockInRecord.js";
import {
- findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions,
+ findAllQualifiedStockInRecordTypeOptions,
+ // findAllUnQualifiedStockInRecordTypeOptions,
} from "@/api/basicData/enum.js";
const {proxy} = getCurrentInstance();
@@ -152,17 +195,20 @@
const data = reactive({
searchForm: {
productName: "",
+ batchNo: "",
+ model: "",
timeStr: "",
recordType: "",
},
});
const {searchForm} = toRefs(data);
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
+const searchFormRef = ref(null);
+
+const resetSearch = () => {
+ searchFormRef.value?.resetFields();
page.current = 1;
getList();
-};
+}
const getRecordType = (recordType) => {
return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
@@ -179,12 +225,28 @@
APPROVED: "閫氳繃",
REJECTED: "椹冲洖",
};
+approvalStatusLabelMap[3] = "寰呯‘璁�";
const getApprovalStatusLabel = (status) => {
if (status === null || status === undefined || status === "") {
return "寰呭鎵�";
}
return approvalStatusLabelMap[status] || "寰呭鎵�";
+};
+
+// 閫氳繃/椹冲洖鍥哄畾鑹诧紱鍏朵綑锛堝惈寰呭鎵广�佺┖鍊笺�佹湭鏄犲皠浣嗘枃妗堜负寰呭鎵癸級缁熶竴鐢� warning 棰勮鑹�
+const getApprovalStatusTagType = (status) => {
+ if (status === 1 || status === "1" || status === "approved" || status === "APPROVED") return "success";
+ if (status === 2 || status === "2" || status === "rejected" || status === "REJECTED") return "danger";
+ return "warning";
+};
+
+const isPendingApproval = status => {
+ return status === 0 || status === "0" || status === "pending" || status === "PENDING" || status === null || status === undefined || status === "";
+};
+
+const isRowSelectableForApprove = row => {
+ return isPendingApproval(row?.approvalStatus);
};
const pageProductChange = obj => {
@@ -195,11 +257,7 @@
const getList = () => {
tableLoading.value = true;
- const params = {...page, type: props.type, topParentProductId: props.topParentProductId};
- params.timeStr = searchForm.value.timeStr;
- params.productName = searchForm.value.productName;
- params.recordType = searchForm.value.recordType;
- getStockInRecordListPage(params)
+ getStockInRecordListPage(Object.assign({}, {...searchForm.value, ...page, topParentProductId: props.topParentProductId}))
.then(res => {
tableData.value = res.data.records;
total.value = res.data.total || 0;
@@ -217,15 +275,15 @@
})
return
}
- findAllUnQualifiedStockInRecordTypeOptions()
- .then(res => {
- stockRecordTypeOptions.value = res.data;
- })
+ // findAllUnQualifiedStockInRecordTypeOptions()
+ // .then(res => {
+ // stockRecordTypeOptions.value = res.data;
+ // })
}
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = selection => {
- selectedRows.value = selection.filter(item => item.id);
+ selectedRows.value = selection.filter(item => item.id && isPendingApproval(item.approvalStatus));
};
const expandedRowKeys = ref([]);
@@ -326,7 +384,10 @@
);
</script>
-<style scoped lang="scss"></style>
-
-
-
+<style scoped lang="scss">
+.actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 10px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue b/src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue
new file mode 100644
index 0000000..a835ef4
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue
@@ -0,0 +1,227 @@
+<template>
+ <el-dialog
+ v-model="isShow"
+ title="搴撳瓨璇︽儏"
+ width="90%"
+ top="3vh"
+ class="batch-no-qty-detail-dialog"
+ @close="closeModal"
+ >
+ <div class="detail-content">
+ <div class="detail-table-wrapper">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ style="width: 100%"
+ height="100%"
+ >
+ <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="180" align="center">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ @click="handleSubtract(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="handleFrozen(scope.row)"
+ >鍐荤粨</el-button
+ >
+ <el-button
+ link
+ type="primary"
+ v-if="
+ scope.row.qualifiedLockedQuantity > 0 ||
+ scope.row.unQualifiedLockedQuantity > 0
+ "
+ @click="handleThaw(scope.row)"
+ >瑙e喕</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+ </el-dialog>
+</template>
+
+<script setup>
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { computed, reactive, ref, watch } from "vue";
+import { getStockInventoryBatchNoQty } from "@/api/inventoryManagement/stockInventory.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ record: {
+ type: Object,
+ default: () => ({}),
+ },
+});
+
+const emit = defineEmits(["update:visible", "subtract", "frozen", "thaw"]);
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit("update:visible", val);
+ },
+});
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const page = reactive({
+ current: 1,
+ size: 20,
+});
+
+const getList = () => {
+ if (!props.record?.productId || !props.record?.productModelId) {
+ tableData.value = [];
+ total.value = 0;
+ return;
+ }
+
+ tableLoading.value = true;
+ getStockInventoryBatchNoQty({
+ current: page.current,
+ size: page.size,
+ productId: props.record.productId,
+ productModelId: props.record.productModelId,
+ })
+ .then((res) => {
+ tableData.value = res.data?.records || [];
+ total.value = res.data?.total || 0;
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+
+const handleSubtract = (row) => {
+ emit("subtract", row);
+};
+
+const handleFrozen = (row) => {
+ emit("frozen", row);
+};
+
+const handleThaw = (row) => {
+ emit("thaw", row);
+};
+
+const closeModal = () => {
+ isShow.value = false;
+ page.current = 1;
+ page.size = 20;
+ tableData.value = [];
+ total.value = 0;
+};
+
+watch(
+ () => props.visible,
+ (visible) => {
+ if (!visible) {
+ return;
+ }
+ page.current = 1;
+ getList();
+ },
+ { immediate: true }
+);
+</script>
+
+<style scoped lang="scss">
+.detail-content {
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - 170px);
+ min-height: 520px;
+}
+
+.detail-table-wrapper {
+ flex: 1;
+ min-height: 0;
+}
+
+:deep(.batch-no-qty-detail-dialog .el-dialog) {
+ max-width: calc(100vw - 48px);
+}
+
+:deep(.batch-no-qty-detail-dialog .el-dialog__body) {
+ padding-top: 12px;
+}
+</style>
diff --git a/src/views/inventoryManagement/stockManagement/Record.vue b/src/views/inventoryManagement/stockManagement/Record.vue
index 7c0a461..dee5e66 100644
--- a/src/views/inventoryManagement/stockManagement/Record.vue
+++ b/src/views/inventoryManagement/stockManagement/Record.vue
@@ -1,145 +1,277 @@
<template>
<div>
<div class="search_form mb10">
+ <el-form
+ ref="searchFormRef"
+ :model="searchForm"
+ class="demo-form-inline"
+ >
+ <el-row :gutter="20">
+ <el-col :span="4">
+ <el-form-item label="浜у搧澶х被" prop="productName">
+ <el-input v-model="searchForm.productName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+
+ <el-col :span="4">
+ <el-form-item label="浜у搧瑙勬牸" prop="model">
+ <el-input v-model="searchForm.model"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="鎵瑰彿" prop="batchNo">
+ <el-input v-model="searchForm.batchNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ </el-form-item>
+ </el-col>
+ <!-- 鎸夐挳 -->
+ <el-col :span="4">
+ <el-form-item>
+ <el-button type="primary" @click="getList">
+ 鎼滅储
+ </el-button>
+
+ <el-button @click="resetSearch">
+ 閲嶇疆
+ </el-button>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
<div>
- <span class="search_title ml10">浜у搧澶х被锛�</span>
- <el-input v-model="searchForm.productName"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- clearable/>
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
- </div>
- <div>
- <el-button type="primary" @click="isShowNewModal = true">鏂板搴撳瓨</el-button>
- <el-button type="info" plain icon="Upload" @click="isShowImportModal = true">
+ <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
+ :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="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">
+ <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="80"
+ 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>
+ <el-button
+ link
+ type="primary"
+ @click="showDetailModal(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>
- <new-stock-inventory v-if="isShowNewModal"
- v-model:visible="isShowNewModal"
- :top-product-parent-id="props.productId"
- @completed="handleQuery" />
+ <batch-no-qty-detail
+ v-if="isShowDetailModal"
+ v-model:visible="isShowDetailModal"
+ :record="record"
+ @subtract="handleDetailSubtract"
+ @frozen="handleDetailFrozen"
+ @thaw="handleDetailThaw"
+ />
+ <new-stock-inventory
+ v-if="isShowNewModal"
+ 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" />
+ <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" />
+ <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" />
+ <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";
+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
- }
+ 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 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 BatchNoQtyDetail = defineAsyncComponent(() =>
+ import("@/views/inventoryManagement/stockManagement/BatchNoQtyDetail.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 total = ref(0);
// 鏄惁鏄剧ず鏂板寮规
-const isShowNewModal = ref(false)
+const isShowNewModal = ref(false);
// 鏄惁鏄剧ず棰嗙敤寮规
-const isShowSubtractModal = ref(false)
+const isShowSubtractModal = ref(false);
// 鏄惁鏄剧ず鍐荤粨/瑙e喕寮规
-const isShowFrozenAndThawModal = ref(false)
+const isShowFrozenAndThawModal = ref(false);
+// 鏄惁鏄剧ず璇︽儏寮规
+const isShowDetailModal = ref(false);
// 鎿嶄綔绫诲瀷
-const operationType = ref('frozen')
+const operationType = ref("frozen");
// 鏄惁鏄剧ず瀵煎叆寮规
-const isShowImportModal = ref(false)
+const isShowImportModal = ref(false);
const data = reactive({
searchForm: {
- productName: '',
+ productName: "",
+ model: "",
+ batchNo: "",
topParentProductId: props.productId,
- }
-})
-const { searchForm } = toRefs(data)
+ },
+});
+const { searchForm } = toRefs(data);
+const searchFormRef = ref(null);
+
+const resetSearch = () => {
+ searchFormRef.value?.resetFields();
+ page.current = 1;
+ getList();
+}
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
const handleQuery = () => {
- page.current = 1
- getList()
-}
+ page.current = 1;
+ getList();
+};
const paginationChange = (obj) => {
page.current = obj.page;
page.size = obj.limit;
- getList()
-}
+ 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
- })
-}
+ 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;
@@ -154,61 +286,89 @@
// 鐐瑰嚮棰嗙敤
const showSubtractModal = (row) => {
- record.value = row
- isShowSubtractModal.value = true
-}
+ record.value = row;
+ isShowSubtractModal.value = true;
+};
+
+// 鐐瑰嚮璇︽儏
+const showDetailModal = (row) => {
+ if (!row?.productId || !row?.productModelId) {
+ proxy.$modal.msgError("褰撳墠鏁版嵁缂哄皯浜у搧ID鎴栬鏍煎瀷鍙稩D");
+ return;
+ }
+ record.value = row;
+ isShowDetailModal.value = true;
+};
+
+const handleDetailSubtract = (row) => {
+ isShowDetailModal.value = false;
+ showSubtractModal(row);
+};
+
+const handleDetailFrozen = (row) => {
+ isShowDetailModal.value = false;
+ showFrozenModal(row);
+};
+
+const handleDetailThaw = (row) => {
+ isShowDetailModal.value = false;
+ showThawModal(row);
+};
// 鐐瑰嚮鍐荤粨
const showFrozenModal = (row) => {
- record.value = row
- isShowFrozenAndThawModal.value = true
- operationType.value = 'frozen'
-}
+ record.value = row;
+ isShowFrozenAndThawModal.value = true;
+ operationType.value = "frozen";
+};
// 鐐瑰嚮瑙e喕
const showThawModal = (row) => {
- record.value = row
- isShowFrozenAndThawModal.value = true
- operationType.value = 'thaw'
-}
+ 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([])
+ 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 "";
}
- return stock < warn ? 'row-low-stock' : '';
+ 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("宸插彇娑�")
+ ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
})
-}
+ .then(() => {
+ proxy.download(
+ "/stockInventory/exportStockInventory",
+ { topParentProductId: props.productId },
+ "搴撳瓨淇℃伅.xlsx"
+ );
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
onMounted(() => {
- getList()
-})
+ getList();
+});
</script>
<style scoped lang="scss">
diff --git a/src/views/inventoryManagement/stockReport/index.vue b/src/views/inventoryManagement/stockReport/index.vue
index 89773cf..26bc8a0 100644
--- a/src/views/inventoryManagement/stockReport/index.vue
+++ b/src/views/inventoryManagement/stockReport/index.vue
@@ -4,708 +4,683 @@
<div class="search_form">
<div class="search_left">
<span class="search_title">鎶ヨ〃绫诲瀷锛�</span>
- <el-select
- v-model="searchForm.reportType"
- style="width: 150px;"
- placeholder="璇烽�夋嫨"
- @change="handleReportTypeChange"
- >
- <el-option label="鏃ユ姤" value="daily" />
- <el-option label="鏈堟姤" value="monthly" />
- <el-option label="杩涘嚭瀛樻姤琛�" value="inout" />
+ <el-select v-model="searchForm.reportType"
+ style="width: 150px;"
+ placeholder="璇烽�夋嫨"
+ @change="handleReportTypeChange">
+ <el-option label="鏃ユ姤"
+ value="daily" />
+ <el-option label="鏈堟姤"
+ value="monthly" />
+ <el-option label="杩涘嚭瀛樻姤琛�"
+ value="inout" />
</el-select>
-
<span class="search_title ml10">鏃堕棿鑼冨洿锛�</span>
- <el-date-picker
- v-if="searchForm.reportType === 'daily'"
- v-model="searchForm.singleDate"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 200px;"
- />
- <el-date-picker
- v-else-if="searchForm.reportType === 'monthly'"
- v-model="searchForm.monthRange"
- type="monthrange"
- range-separator="鑷�"
- start-placeholder="寮�濮嬫湀浠�"
- end-placeholder="缁撴潫鏈堜唤"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 240px;"
- />
- <el-date-picker
- v-else
- v-model="searchForm.dateRange"
- type="daterange"
- range-separator="鑷�"
- start-placeholder="寮�濮嬫棩鏈�"
- end-placeholder="缁撴潫鏃ユ湡"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 240px;"
- />
-
- <el-button type="primary" @click="onSearch" style="margin-left: 10px">
+ <el-date-picker v-if="searchForm.reportType === 'daily'"
+ v-model="searchForm.singleDate"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 200px;" />
+ <el-date-picker v-else-if="searchForm.reportType === 'monthly'"
+ v-model="searchForm.monthRange"
+ type="monthrange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫湀浠�"
+ end-placeholder="缁撴潫鏈堜唤"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 240px;" />
+ <el-date-picker v-else
+ v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 240px;" />
+ <el-button type="primary"
+ @click="onSearch"
+ style="margin-left: 10px">
鏌ヨ
</el-button>
<el-button @click="handleReset">閲嶇疆</el-button>
</div>
-
<div class="search_right">
-<!-- <el-button type="success" @click="handleExport" icon="Download">-->
-<!-- 瀵煎嚭鎶ヨ〃-->
-<!-- </el-button>-->
+ <!-- <el-button type="success" @click="handleExport" icon="Download">-->
+ <!-- 瀵煎嚭鎶ヨ〃-->
+ <!-- </el-button>-->
</div>
</div>
-
-<!-- <!– 缁熻鍗$墖 –>-->
-<!-- <div class="stats_cards" v-if="reportData.summary">-->
-<!-- <el-row :gutter="20">-->
-<!-- <el-col :span="6">-->
-<!-- <el-card class="stats_card">-->
-<!-- <div class="stats_content">-->
-<!-- <div class="stats_icon in">-->
-<!-- <el-icon><TrendCharts /></el-icon>-->
-<!-- </div>-->
-<!-- <div class="stats_info">-->
-<!-- <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>-->
-<!-- <div class="stats_label">鎬诲叆搴撻噺</div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </el-card>-->
-<!-- </el-col>-->
-<!-- <el-col :span="6">-->
-<!-- <el-card class="stats_card">-->
-<!-- <div class="stats_content">-->
-<!-- <div class="stats_icon out">-->
-<!-- <el-icon><TrendCharts /></el-icon>-->
-<!-- </div>-->
-<!-- <div class="stats_info">-->
-<!-- <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>-->
-<!-- <div class="stats_label">鎬诲嚭搴撻噺</div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </el-card>-->
-<!-- </el-col>-->
-<!-- <el-col :span="6">-->
-<!-- <el-card class="stats_card">-->
-<!-- <div class="stats_content">-->
-<!-- <div class="stats_icon stock">-->
-<!-- <el-icon><Box /></el-icon>-->
-<!-- </div>-->
-<!-- <div class="stats_info">-->
-<!-- <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>-->
-<!-- <div class="stats_label">褰撳墠搴撳瓨</div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </el-card>-->
-<!-- </el-col>-->
-<!-- <el-col :span="6">-->
-<!-- <el-card class="stats_card">-->
-<!-- <div class="stats_content">-->
-<!-- <div class="stats_icon turnover">-->
-<!-- <el-icon><Refresh /></el-icon>-->
-<!-- </div>-->
-<!-- <div class="stats_info">-->
-<!-- <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>-->
-<!-- <div class="stats_label">鍛ㄨ浆鐜�</div>-->
-<!-- </div>-->
-<!-- </div>-->
-<!-- </el-card>-->
-<!-- </el-col>-->
-<!-- </el-row>-->
-<!-- </div>-->
-
-<!-- <!– 鍥捐〃鍖哄煙 –>-->
-<!-- <div class="chart_section" v-if="reportData.chartData">-->
-<!-- <el-row :gutter="20">-->
-<!-- <el-col :span="12">-->
-<!-- <el-card>-->
-<!-- <template #header>-->
-<!-- <span>搴撳瓨瓒嬪娍鍥�</span>-->
-<!-- </template>-->
-<!-- <div ref="trendChart" style="height: 300px;"></div>-->
-<!-- </el-card>-->
-<!-- </el-col>-->
-<!-- <el-col :span="12">-->
-<!-- <el-card>-->
-<!-- <template #header>-->
-<!-- <span>杩涘嚭搴撳姣�</span>-->
-<!-- </template>-->
-<!-- <div ref="comparisonChart" style="height: 300px;"></div>-->
-<!-- </el-card>-->
-<!-- </el-col>-->
-<!-- </el-row>-->
-<!-- </div>-->
-
+ <!-- <!– 缁熻鍗$墖 –>-->
+ <!-- <div class="stats_cards" v-if="reportData.summary">-->
+ <!-- <el-row :gutter="20">-->
+ <!-- <el-col :span="6">-->
+ <!-- <el-card class="stats_card">-->
+ <!-- <div class="stats_content">-->
+ <!-- <div class="stats_icon in">-->
+ <!-- <el-icon><TrendCharts /></el-icon>-->
+ <!-- </div>-->
+ <!-- <div class="stats_info">-->
+ <!-- <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>-->
+ <!-- <div class="stats_label">鎬诲叆搴撻噺</div>-->
+ <!-- </div>-->
+ <!-- </div>-->
+ <!-- </el-card>-->
+ <!-- </el-col>-->
+ <!-- <el-col :span="6">-->
+ <!-- <el-card class="stats_card">-->
+ <!-- <div class="stats_content">-->
+ <!-- <div class="stats_icon out">-->
+ <!-- <el-icon><TrendCharts /></el-icon>-->
+ <!-- </div>-->
+ <!-- <div class="stats_info">-->
+ <!-- <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>-->
+ <!-- <div class="stats_label">鎬诲嚭搴撻噺</div>-->
+ <!-- </div>-->
+ <!-- </div>-->
+ <!-- </el-card>-->
+ <!-- </el-col>-->
+ <!-- <el-col :span="6">-->
+ <!-- <el-card class="stats_card">-->
+ <!-- <div class="stats_content">-->
+ <!-- <div class="stats_icon stock">-->
+ <!-- <el-icon><Box /></el-icon>-->
+ <!-- </div>-->
+ <!-- <div class="stats_info">-->
+ <!-- <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>-->
+ <!-- <div class="stats_label">褰撳墠搴撳瓨</div>-->
+ <!-- </div>-->
+ <!-- </div>-->
+ <!-- </el-card>-->
+ <!-- </el-col>-->
+ <!-- <el-col :span="6">-->
+ <!-- <el-card class="stats_card">-->
+ <!-- <div class="stats_content">-->
+ <!-- <div class="stats_icon turnover">-->
+ <!-- <el-icon><Refresh /></el-icon>-->
+ <!-- </div>-->
+ <!-- <div class="stats_info">-->
+ <!-- <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>-->
+ <!-- <div class="stats_label">鍛ㄨ浆鐜�</div>-->
+ <!-- </div>-->
+ <!-- </div>-->
+ <!-- </el-card>-->
+ <!-- </el-col>-->
+ <!-- </el-row>-->
+ <!-- </div>-->
+ <!-- <!– 鍥捐〃鍖哄煙 –>-->
+ <!-- <div class="chart_section" v-if="reportData.chartData">-->
+ <!-- <el-row :gutter="20">-->
+ <!-- <el-col :span="12">-->
+ <!-- <el-card>-->
+ <!-- <template #header>-->
+ <!-- <span>搴撳瓨瓒嬪娍鍥�</span>-->
+ <!-- </template>-->
+ <!-- <div ref="trendChart" style="height: 300px;"></div>-->
+ <!-- </el-card>-->
+ <!-- </el-col>-->
+ <!-- <el-col :span="12">-->
+ <!-- <el-card>-->
+ <!-- <template #header>-->
+ <!-- <span>杩涘嚭搴撳姣�</span>-->
+ <!-- </template>-->
+ <!-- <div ref="comparisonChart" style="height: 300px;"></div>-->
+ <!-- </el-card>-->
+ <!-- </el-col>-->
+ <!-- </el-row>-->
+ <!-- </div>-->
<!-- 璇︾粏鏁版嵁琛ㄦ牸 -->
<div class="table_section">
<el-card>
<template #header>
<span>{{ getTableTitle() }}</span>
</template>
- <el-table
- v-loading="tableLoading"
- :data="reportData.tableData"
- border
- height="400"
- style="width: 100%"
- :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
- >
- <el-table-column
- align="center"
- label="搴忓彿"
- type="index"
- width="60"
- />
- <el-table-column
- label="鍏ュ簱鏃堕棿"
- prop="createTime"
- width="200"
- show-overflow-tooltip
- v-if="searchForm.reportType !== 'inout'"
- />
- <el-table-column
- label="鍏ュ簱鎵规"
- prop="inboundBatches"
- width="240"
- show-overflow-tooltip
- v-if="searchForm.reportType !== 'inout'"
- />
- <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="totalStockIn"
- align="center"
- v-if="searchForm.reportType === 'inout'"
- />
- <el-table-column
- label="鍏ュ簱鏁伴噺"
- prop="stockInNum"
- align="center"
- v-else
- />
- <el-table-column
- label="鍑哄簱鏁伴噺"
- prop="totalStockOut"
- width="100"
- align="center"
- v-if="searchForm.reportType === 'inout'"
- />
- <el-table-column
- label="鐜板湪搴撳瓨"
- prop="currentStock"
- align="center"
- />
- <el-table-column label="鏉ユ簮"
- prop="recordType"
- v-if="searchForm.reportType !== 'inout'"
- show-overflow-tooltip>
- <template #default="scope">
- {{ getRecordType(scope.row.recordType) }}
- </template>
- </el-table-column>
- <el-table-column
- label="鍏ュ簱浜�"
- prop="createBy"
- width="80"
- v-if="searchForm.reportType !== 'inout'"
- show-overflow-tooltip
- />
+ <el-table v-loading="tableLoading"
+ :data="reportData.tableData"
+ border
+ height="400"
+ style="width: 100%"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }">
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="鍏ュ簱鏃堕棿"
+ prop="createTime"
+ width="200"
+ show-overflow-tooltip
+ v-if="searchForm.reportType !== 'inout'" />
+ <el-table-column label="鍏ュ簱鎵规"
+ prop="inboundBatches"
+ width="180"
+ show-overflow-tooltip
+ v-if="searchForm.reportType !== 'inout'" />
+ <el-table-column label="鎵瑰彿"
+ prop="batchNo"
+ width="180"
+ show-overflow-tooltip
+ v-if="searchForm.reportType !== 'inout'" />
+ <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="totalStockIn"
+ align="center"
+ v-if="searchForm.reportType === 'inout'" />
+ <el-table-column label="鍏ュ簱鏁伴噺"
+ prop="stockInNum"
+ align="center"
+ v-else />
+ <el-table-column label="鍑哄簱鏁伴噺"
+ prop="totalStockOut"
+ width="100"
+ align="center"
+ v-if="searchForm.reportType === 'inout'" />
+ <el-table-column label="鐜板湪搴撳瓨"
+ prop="currentStock"
+ align="center" />
+ <el-table-column label="鏉ユ簮"
+ prop="recordType"
+ v-if="searchForm.reportType !== 'inout'"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ getRecordType(scope.row.recordType) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏ュ簱浜�"
+ prop="createBy"
+ width="80"
+ v-if="searchForm.reportType !== 'inout'"
+ show-overflow-tooltip />
</el-table>
- <pagination
- :total="total"
- layout="total, sizes, prev, pager, next, jumper"
- :page="page.current"
- :limit="page.size"
- @pagination="paginationChange"
- />
+ <pagination :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange" />
</el-card>
</div>
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue'
-import { ElMessage } from 'element-plus'
-import * as echarts from 'echarts'
-import pagination from '@/components/PIMTable/Pagination.vue'
-import {
- getStockInventoryInAndOutReportList,
- getStockInventoryReportList
-} from "@/api/inventoryManagement/stockInventory.js";
-import {
- findAllQualifiedStockInRecordTypeOptions,findAllUnQualifiedStockInRecordTypeOptions,
-} from "@/api/basicData/enum.js";
+ import { ref, reactive, onMounted, nextTick, getCurrentInstance } from "vue";
+ import { ElMessage } from "element-plus";
+ import * as echarts from "echarts";
+ import pagination from "@/components/PIMTable/Pagination.vue";
+ import {
+ getStockInventoryInAndOutReportList,
+ getStockInventoryReportList,
+ } from "@/api/inventoryManagement/stockInventory.js";
+ import {
+ findAllQualifiedStockInRecordTypeOptions,
+ // findAllUnQualifiedStockInRecordTypeOptions,
+ } from "@/api/basicData/enum.js";
+ const { proxy } = getCurrentInstance();
+ // 鍝嶅簲寮忔暟鎹�
+ const tableLoading = ref(false);
+ const trendChart = ref(null);
+ const comparisonChart = ref(null);
-const { proxy } = getCurrentInstance()
-// 鍝嶅簲寮忔暟鎹�
-const tableLoading = ref(false)
-const trendChart = ref(null)
-const comparisonChart = ref(null)
+ const searchForm = reactive({
+ reportType: "daily",
+ singleDate: "",
+ dateRange: [],
+ monthRange: [],
+ });
-const searchForm = reactive({
- reportType: 'daily',
- singleDate: '',
- dateRange: [],
- monthRange: []
-})
-
-const reportData = ref({
- summary: null,
- chartData: null,
- tableData: []
-})
-
-const page = reactive({
- current: 1,
- size: 10,
-})
-
-const total = ref(0)
-
-const stockRecordTypeOptions = ref([])
-
-const getRecordType = (recordType) => {
- return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
-}
-
-// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
-const fetchStockRecordTypeOptions = () => {
- findAllQualifiedStockInRecordTypeOptions()
- .then(res => {
- stockRecordTypeOptions.value = res.data;
- findAllUnQualifiedStockInRecordTypeOptions()
- .then(res => {
- stockRecordTypeOptions.value = [...stockRecordTypeOptions.value,...res.data];
- })
- })
-}
-
-// 鑾峰彇琛ㄦ牸鏍囬
-const getTableTitle = () => {
- const typeMap = {
- daily: '鏃ユ姤璇︾粏鏁版嵁',
- monthly: '鏈堟姤璇︾粏鏁版嵁',
- inout: '杩涘嚭瀛樻姤琛ㄨ缁嗘暟鎹�'
- }
- return typeMap[searchForm.reportType] || '鎶ヨ〃璇︾粏鏁版嵁'
-}
-
-// 鎶ヨ〃绫诲瀷鏀瑰彉
-const handleReportTypeChange = () => {
- page.current = 1
- reportData.value = {
+ const reportData = ref({
summary: null,
chartData: null,
- tableData: []
- }
-}
+ tableData: [],
+ });
-// 鏌ヨ鏁版嵁
-const handleQuery = async () => {
- if (!validateSearchForm()) {
- return
- }
-
- tableLoading.value = true
- try {
- const baseParams = getQueryParams()
+ const page = reactive({
+ current: 1,
+ size: 10,
+ });
+
+ const total = ref(0);
+
+ const stockRecordTypeOptions = ref([]);
+
+ const getRecordType = recordType => {
+ return (
+ stockRecordTypeOptions.value.find(item => item.value === recordType)
+ ?.label || ""
+ );
+ };
+
+ // 鑾峰彇鏉ユ簮绫诲瀷閫夐」
+ const fetchStockRecordTypeOptions = () => {
+ findAllQualifiedStockInRecordTypeOptions().then(res => {
+ stockRecordTypeOptions.value = res.data;
+ // findAllUnQualifiedStockInRecordTypeOptions().then(res => {
+ // stockRecordTypeOptions.value = [
+ // ...stockRecordTypeOptions.value,
+ // ...res.data,
+ // ];
+ // });
+ });
+ };
+
+ // 鑾峰彇琛ㄦ牸鏍囬
+ const getTableTitle = () => {
+ const typeMap = {
+ daily: "鏃ユ姤璇︾粏鏁版嵁",
+ monthly: "鏈堟姤璇︾粏鏁版嵁",
+ inout: "杩涘嚭瀛樻姤琛ㄨ缁嗘暟鎹�",
+ };
+ return typeMap[searchForm.reportType] || "鎶ヨ〃璇︾粏鏁版嵁";
+ };
+
+ // 鎶ヨ〃绫诲瀷鏀瑰彉
+ const handleReportTypeChange = () => {
+ page.current = 1;
+ reportData.value = {
+ summary: null,
+ chartData: null,
+ tableData: [],
+ };
+ };
+
+ // 鏌ヨ鏁版嵁
+ const handleQuery = async () => {
+ if (!validateSearchForm()) {
+ return;
+ }
+
+ tableLoading.value = true;
+ try {
+ const baseParams = getQueryParams();
+ const params = {
+ ...baseParams,
+ current: page.current,
+ size: page.size,
+ };
+ let response;
+
+ if (searchForm.reportType === "inout") {
+ response = await getStockInventoryInAndOutReportList(params);
+ } else {
+ response = await getStockInventoryReportList(params);
+ }
+ if (response.code === 200) {
+ reportData.value.tableData = response.data.records || [];
+ total.value = response.data.total || 0;
+ // reportData.value.summary = response.data.summary
+ // reportData.value.chartData = response.data.chartData
+ // nextTick(() => {
+ // initCharts()
+ // })
+ }
+ } catch (error) {
+ ElMessage.error("鏌ヨ澶辫触锛�" + error.message);
+ } finally {
+ tableLoading.value = false;
+ }
+ };
+
+ // 鏌ヨ鎸夐挳锛氶噸缃埌绗竴椤靛苟鏌ヨ
+ const onSearch = () => {
+ page.current = 1;
+ handleQuery();
+ };
+
+ // 鍒嗛〉鍙樺寲
+ const paginationChange = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ handleQuery();
+ };
+ // // 鐢熸垚鍋囨暟鎹�
+ // const generateMockData = () => {
+ // // 鐢熸垚缁熻鍗$墖鍋囨暟鎹�
+ // const summary = {
+ // totalIn: 1000,
+ // totalOut: 600,
+ // currentStock: 400,
+ // turnoverRate: 30
+ // }
+
+ // // 鐢熸垚鍥捐〃鍋囨暟鎹�
+ // const trendDates = ['2025-09-15', '2025-09-16', '2025-09-17', '2025-09-18', '2025-09-19']
+ // const trendValues = [300, 350, 400, 380, 420]
+ // const comparisonDates = ['2025-09-15', '2025-09-16', '2025-09-17']
+ // const inValues = [100, 150, 200]
+ // const outValues = [80, 120, 100]
+
+ // const chartData = {
+ // trendDates,
+ // trendValues,
+ // comparisonDates,
+ // inValues,
+ // outValues
+ // }
+
+ // reportData.value = {
+ // summary,
+ // chartData,
+ // tableData: []
+ // }
+ // }
+ // 楠岃瘉鎼滅储琛ㄥ崟
+ const validateSearchForm = () => {
+ if (searchForm.reportType === "daily") {
+ if (!searchForm.singleDate) {
+ ElMessage.warning("璇烽�夋嫨鏃ユ湡");
+ return false;
+ }
+ } else if (searchForm.reportType === "inout") {
+ if (!searchForm.dateRange || searchForm.dateRange.length !== 2) {
+ ElMessage.warning("璇烽�夋嫨鏃ユ湡鑼冨洿");
+ return false;
+ }
+ } else if (searchForm.reportType === "monthly") {
+ if (!searchForm.monthRange || searchForm.monthRange.length !== 2) {
+ ElMessage.warning("璇烽�夋嫨鏈堜唤鑼冨洿");
+ return false;
+ }
+ }
+ return true;
+ };
+
+ // 鑾峰彇鏌ヨ鍙傛暟
+ const getQueryParams = () => {
const params = {
- ...baseParams,
- current: page.current,
- size: page.size,
- }
- let response
+ reportType: searchForm.reportType,
+ reportDate: "",
+ startMonth: "",
+ endMonth: "",
+ startDate: "",
+ endDate: "",
+ };
- if (searchForm.reportType === 'inout') {
- response = await getStockInventoryInAndOutReportList(params)
+ if (searchForm.reportType === "daily") {
+ params.reportDate = searchForm.singleDate;
+ } else if (searchForm.reportType === "monthly") {
+ params.startMonth = searchForm.monthRange[0];
+ params.endMonth = searchForm.monthRange[1];
} else {
- response = await getStockInventoryReportList(params)
+ params.startDate = searchForm.dateRange[0];
+ params.endDate = searchForm.dateRange[1];
}
- if (response.code === 200) {
- reportData.value.tableData = response.data.records || []
- total.value = response.data.total || 0
- // reportData.value.summary = response.data.summary
- // reportData.value.chartData = response.data.chartData
- // nextTick(() => {
- // initCharts()
- // })
-
+
+ return params;
+ };
+
+ // 閲嶇疆鎼滅储
+ const handleReset = () => {
+ searchForm.reportType = "daily";
+ searchForm.singleDate = "";
+ searchForm.dateRange = [];
+ searchForm.monthRange = [];
+ reportData.value = {
+ summary: null,
+ chartData: null,
+ tableData: [],
+ };
+ };
+
+ // 瀵煎嚭鎶ヨ〃
+ const handleExport = async () => {
+ if (!validateSearchForm()) {
+ return;
}
- } catch (error) {
- ElMessage.error('鏌ヨ澶辫触锛�' + error.message)
- } finally {
- tableLoading.value = false
- }
-}
-// 鏌ヨ鎸夐挳锛氶噸缃埌绗竴椤靛苟鏌ヨ
-const onSearch = () => {
- page.current = 1
- handleQuery()
-}
+ try {
+ const params = getQueryParams();
+ // const response = await exportStockReport(params)
+ proxy.download("/stockin/exportCopy", params, "搴撳瓨鎶ヨ〃.xlsx");
+ // 鍒涘缓涓嬭浇閾炬帴
+ // const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+ // const url = window.URL.createObjectURL(blob)
+ // const link = document.createElement('a')
+ // link.href = url
+ // link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx`
+ // document.body.appendChild(link)
+ // link.click()
+ // document.body.removeChild(link)
+ // window.URL.revokeObjectURL(url)
-// 鍒嗛〉鍙樺寲
-const paginationChange = (obj) => {
- page.current = obj.page
- page.size = obj.limit
- handleQuery()
-}
-// // 鐢熸垚鍋囨暟鎹�
-// const generateMockData = () => {
-// // 鐢熸垚缁熻鍗$墖鍋囨暟鎹�
-// const summary = {
-// totalIn: 1000,
-// totalOut: 600,
-// currentStock: 400,
-// turnoverRate: 30
-// }
-
-// // 鐢熸垚鍥捐〃鍋囨暟鎹�
-// const trendDates = ['2025-09-15', '2025-09-16', '2025-09-17', '2025-09-18', '2025-09-19']
-// const trendValues = [300, 350, 400, 380, 420]
-// const comparisonDates = ['2025-09-15', '2025-09-16', '2025-09-17']
-// const inValues = [100, 150, 200]
-// const outValues = [80, 120, 100]
-
-// const chartData = {
-// trendDates,
-// trendValues,
-// comparisonDates,
-// inValues,
-// outValues
-// }
-
-// reportData.value = {
-// summary,
-// chartData,
-// tableData: []
-// }
-// }
-// 楠岃瘉鎼滅储琛ㄥ崟
-const validateSearchForm = () => {
- if (searchForm.reportType === 'daily') {
- if (!searchForm.singleDate) {
- ElMessage.warning('璇烽�夋嫨鏃ユ湡')
- return false
+ // ElMessage.success('瀵煎嚭鎴愬姛')
+ } catch (error) {
+ ElMessage.error("瀵煎嚭澶辫触锛�" + error.message);
}
- } else if (searchForm.reportType === 'inout') {
- if (!searchForm.dateRange || searchForm.dateRange.length !== 2) {
- ElMessage.warning('璇烽�夋嫨鏃ユ湡鑼冨洿')
- return false
- }
- } else if (searchForm.reportType === 'monthly') {
- if (!searchForm.monthRange || searchForm.monthRange.length !== 2) {
- ElMessage.warning('璇烽�夋嫨鏈堜唤鑼冨洿')
- return false
- }
- }
- return true
-}
+ };
-// 鑾峰彇鏌ヨ鍙傛暟
-const getQueryParams = () => {
- const params = {
- reportType: searchForm.reportType,
- reportDate: "",
- startMonth: "",
- endMonth: "",
- startDate: "",
- endDate: ""
- }
-
- if (searchForm.reportType === 'daily') {
- params.reportDate = searchForm.singleDate
- } else if (searchForm.reportType === 'monthly') {
- params.startMonth = searchForm.monthRange[0]
- params.endMonth = searchForm.monthRange[1]
- } else {
- params.startDate = searchForm.dateRange[0]
- params.endDate = searchForm.dateRange[1]
- }
-
- return params
-}
+ // 鍒濆鍖栧浘琛�
+ const initCharts = () => {
+ if (!reportData.value.chartData) return;
-// 閲嶇疆鎼滅储
-const handleReset = () => {
- searchForm.reportType = 'daily'
- searchForm.singleDate = ''
- searchForm.dateRange = []
- searchForm.monthRange = []
- reportData.value = {
- summary: null,
- chartData: null,
- tableData: []
- }
-}
+ initTrendChart();
+ initComparisonChart();
+ };
-// 瀵煎嚭鎶ヨ〃
-const handleExport = async () => {
- if (!validateSearchForm()) {
- return
- }
-
- try {
- const params = getQueryParams()
- // const response = await exportStockReport(params)
- proxy.download("/stockin/exportCopy", params, '搴撳瓨鎶ヨ〃.xlsx')
- // 鍒涘缓涓嬭浇閾炬帴
- // const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
- // const url = window.URL.createObjectURL(blob)
- // const link = document.createElement('a')
- // link.href = url
- // link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx`
- // document.body.appendChild(link)
- // link.click()
- // document.body.removeChild(link)
- // window.URL.revokeObjectURL(url)
-
- // ElMessage.success('瀵煎嚭鎴愬姛')
- } catch (error) {
- ElMessage.error('瀵煎嚭澶辫触锛�' + error.message)
- }
-}
+ // 鍒濆鍖栬秼鍔垮浘
+ const initTrendChart = () => {
+ if (!trendChart.value) return;
-// 鍒濆鍖栧浘琛�
-const initCharts = () => {
- if (!reportData.value.chartData) return
-
- initTrendChart()
- initComparisonChart()
-}
-
-// 鍒濆鍖栬秼鍔垮浘
-const initTrendChart = () => {
- if (!trendChart.value) return
-
- const chart = echarts.init(trendChart.value)
- const option = {
- title: {
- text: '搴撳瓨鍙樺寲瓒嬪娍',
- left: 'center'
- },
- tooltip: {
- trigger: 'axis'
- },
- legend: {
- data: ['搴撳瓨閲�'],
- top: 30
- },
- xAxis: {
- type: 'category',
- data: reportData.value.chartData.trendDates || []
- },
- yAxis: {
- type: 'value'
- },
- series: [{
- name: '搴撳瓨閲�',
- type: 'line',
- data: reportData.value.chartData.trendValues || [],
- smooth: true,
- itemStyle: {
- color: '#409EFF'
- }
- }]
- }
- chart.setOption(option)
-}
-
-// 鍒濆鍖栧姣斿浘
-const initComparisonChart = () => {
- if (!comparisonChart.value) return
-
- const chart = echarts.init(comparisonChart.value)
- const option = {
- title: {
- text: '杩涘嚭搴撳姣�',
- left: 'center'
- },
- tooltip: {
- trigger: 'axis'
- },
- legend: {
- data: ['鍏ュ簱', '鍑哄簱'],
- top: 30
- },
- xAxis: {
- type: 'category',
- data: reportData.value.chartData.comparisonDates || []
- },
- yAxis: {
- type: 'value'
- },
- series: [
- {
- name: '鍏ュ簱',
- type: 'bar',
- data: reportData.value.chartData.inValues || [],
- itemStyle: {
- color: '#67C23A'
- }
+ const chart = echarts.init(trendChart.value);
+ const option = {
+ title: {
+ text: "搴撳瓨鍙樺寲瓒嬪娍",
+ left: "center",
},
- {
- name: '鍑哄簱',
- type: 'bar',
- data: reportData.value.chartData.outValues || [],
- itemStyle: {
- color: '#F56C6C'
- }
- }
- ]
- }
- chart.setOption(option)
-}
+ tooltip: {
+ trigger: "axis",
+ },
+ legend: {
+ data: ["搴撳瓨閲�"],
+ top: 30,
+ },
+ xAxis: {
+ type: "category",
+ data: reportData.value.chartData.trendDates || [],
+ },
+ yAxis: {
+ type: "value",
+ },
+ series: [
+ {
+ name: "搴撳瓨閲�",
+ type: "line",
+ data: reportData.value.chartData.trendValues || [],
+ smooth: true,
+ itemStyle: {
+ color: "#409EFF",
+ },
+ },
+ ],
+ };
+ chart.setOption(option);
+ };
-// 缁勪欢鎸傝浇鏃惰缃粯璁ゆ椂闂�
-onMounted(() => {
- const today = new Date()
- searchForm.singleDate = today.toISOString().split('T')[0]
-
- const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
- searchForm.dateRange = [
- yesterday.toISOString().split('T')[0],
- today.toISOString().split('T')[0]
- ]
+ // 鍒濆鍖栧姣斿浘
+ const initComparisonChart = () => {
+ if (!comparisonChart.value) return;
- fetchStockRecordTypeOptions()
- // 鍒濆鍖栧姞杞戒竴娆℃暟鎹�
- handleQuery()
-})
+ const chart = echarts.init(comparisonChart.value);
+ const option = {
+ title: {
+ text: "杩涘嚭搴撳姣�",
+ left: "center",
+ },
+ tooltip: {
+ trigger: "axis",
+ },
+ legend: {
+ data: ["鍏ュ簱", "鍑哄簱"],
+ top: 30,
+ },
+ xAxis: {
+ type: "category",
+ data: reportData.value.chartData.comparisonDates || [],
+ },
+ yAxis: {
+ type: "value",
+ },
+ series: [
+ {
+ name: "鍏ュ簱",
+ type: "bar",
+ data: reportData.value.chartData.inValues || [],
+ itemStyle: {
+ color: "#67C23A",
+ },
+ },
+ {
+ name: "鍑哄簱",
+ type: "bar",
+ data: reportData.value.chartData.outValues || [],
+ itemStyle: {
+ color: "#F56C6C",
+ },
+ },
+ ],
+ };
+ chart.setOption(option);
+ };
+
+ // 缁勪欢鎸傝浇鏃惰缃粯璁ゆ椂闂�
+ onMounted(() => {
+ const today = new Date();
+ searchForm.singleDate = today.toISOString().split("T")[0];
+
+ const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
+ searchForm.dateRange = [
+ yesterday.toISOString().split("T")[0],
+ today.toISOString().split("T")[0],
+ ];
+
+ fetchStockRecordTypeOptions();
+ // 鍒濆鍖栧姞杞戒竴娆℃暟鎹�
+ handleQuery();
+ });
</script>
<style scoped>
-.app-container {
- padding: 20px;
-}
+ .app-container {
+ padding: 20px;
+ }
-.search_form {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- padding: 20px;
- background: #fff;
- border-radius: 4px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
+ .search_form {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding: 20px;
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ }
-.search_left {
- display: flex;
- align-items: center;
-}
+ .search_left {
+ display: flex;
+ align-items: center;
+ }
-.search_title {
- font-weight: 500;
- color: #333;
- margin-right: 8px;
-}
+ .search_title {
+ font-weight: 500;
+ color: #333;
+ margin-right: 8px;
+ }
-.ml10 {
- margin-left: 10px;
-}
+ .ml10 {
+ margin-left: 10px;
+ }
-.stats_cards {
- margin-bottom: 20px;
-}
+ .stats_cards {
+ margin-bottom: 20px;
+ }
-.stats_card {
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-}
+ .stats_card {
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ }
-.stats_content {
- display: flex;
- align-items: center;
- padding: 10px 0;
-}
+ .stats_content {
+ display: flex;
+ align-items: center;
+ padding: 10px 0;
+ }
-.stats_icon {
- width: 50px;
- height: 50px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 15px;
- font-size: 24px;
- color: #fff;
-}
+ .stats_icon {
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+ font-size: 24px;
+ color: #fff;
+ }
-.stats_icon.in {
- background: linear-gradient(135deg, #67C23A, #85CE61);
-}
+ .stats_icon.in {
+ background: linear-gradient(135deg, #67c23a, #85ce61);
+ }
-.stats_icon.out {
- background: linear-gradient(135deg, #F56C6C, #F78989);
-}
+ .stats_icon.out {
+ background: linear-gradient(135deg, #f56c6c, #f78989);
+ }
-.stats_icon.stock {
- background: linear-gradient(135deg, #409EFF, #66B1FF);
-}
+ .stats_icon.stock {
+ background: linear-gradient(135deg, #409eff, #66b1ff);
+ }
-.stats_icon.turnover {
- background: linear-gradient(135deg, #E6A23C, #EEBE77);
-}
+ .stats_icon.turnover {
+ background: linear-gradient(135deg, #e6a23c, #eebe77);
+ }
-.stats_info {
- flex: 1;
-}
+ .stats_info {
+ flex: 1;
+ }
-.stats_value {
- font-size: 24px;
- font-weight: bold;
- color: #333;
- line-height: 1;
- margin-bottom: 5px;
-}
+ .stats_value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #333;
+ line-height: 1;
+ margin-bottom: 5px;
+ }
-.stats_label {
- font-size: 14px;
- color: #666;
-}
+ .stats_label {
+ font-size: 14px;
+ color: #666;
+ }
-.chart_section {
- margin-bottom: 20px;
-}
+ .chart_section {
+ margin-bottom: 20px;
+ }
-.table_section {
- margin-bottom: 20px;
-}
+ .table_section {
+ margin-bottom: 20px;
+ }
-:deep(.el-card__header) {
- background: #f8f9fa;
- border-bottom: 1px solid #e9ecef;
- font-weight: 500;
-}
+ :deep(.el-card__header) {
+ background: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ font-weight: 500;
+ }
-:deep(.el-table .el-table__header-wrapper th) {
- background-color: #F0F1F5 !important;
- color: #333333;
- font-weight: 600;
-}
+ :deep(.el-table .el-table__header-wrapper th) {
+ background-color: #f0f1f5 !important;
+ color: #333333;
+ font-weight: 600;
+ }
-:deep(.el-table .el-table__body-wrapper td) {
- padding: 8px 0;
-}
+ :deep(.el-table .el-table__body-wrapper td) {
+ padding: 8px 0;
+ }
</style>
diff --git a/src/views/lavorissue/ledger/filesDia.vue b/src/views/lavorissue/ledger/filesDia.vue
index f752496..46da350 100644
--- a/src/views/lavorissue/ledger/filesDia.vue
+++ b/src/views/lavorissue/ledger/filesDia.vue
@@ -163,7 +163,7 @@
}
// 涓嬭浇闄勪欢
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
// 鍒犻櫎
const handleDelete = () => {
diff --git a/src/views/login.vue b/src/views/login.vue
index 872537b..c7db15a 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -1,96 +1,180 @@
<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>
+ <main class="page">
+ <section class="factory">
+ <div class="brand hero-brand">
+ <div class="logo hero-logo">
+ <img
+ :src="brandLogoUrl"
+ :alt="`${companyName} logo`"
+ class="logo-image hero-logo-image"
+ @error="handleLogoError"
+ />
</div>
- <div class="brand-point">
- <span class="point-dot"></span>
- <span>鏇磋交鐨勭晫闈㈠眰娆�</span>
- </div>
- <div class="brand-point">
- <span class="point-dot"></span>
- <span>绋冲畾鐨勪笟鍔″崗鍚屼綋楠�</span>
- </div>
+ </div>
+
+ <div class="hero">
+ <div class="chip">鏁板瓧宸ュ巶 路 鏅鸿兘鎺掍骇 路 璁惧浜掕仈 路 璐ㄩ噺杩芥函</div>
+ <h1>鏁板瓧宸ュ巶<br />MOM 鏅洪�犲钩鍙�</h1>
+ <p>
+ 浠ュ疄鏃舵暟鎹┍鍔ㄧ敓浜х幇鍦猴紝鎶婂伐鍗曘�佽澶囥�佺墿鏂欍�佽川閲忋�佽兘鑰椾笌浠撳偍杩炴帴鎴愪竴寮犻�忔槑鐨勫埗閫犺繍钀ョ綉缁溿��
+ </p>
+ </div>
+
+ <div class="scene" aria-hidden="true">
+ <div class="floor"></div>
+ <svg class="factory-svg" viewBox="0 0 920 360" preserveAspectRatio="xMidYMid meet">
+ <defs>
+ <linearGradient id="g1" x1="0" y1="0" x2="1" y2="1">
+ <stop offset="0" stop-color="#40e4ff" stop-opacity=".9" />
+ <stop offset="1" stop-color="#1f78ff" stop-opacity=".68" />
+ </linearGradient>
+ <linearGradient id="g2" x1="0" y1="0" x2="1" y2="1">
+ <stop offset="0" stop-color="#ffffff" stop-opacity=".28" />
+ <stop offset="1" stop-color="#ffffff" stop-opacity=".06" />
+ </linearGradient>
+ </defs>
+
+ <path d="M45 255H830" stroke="url(#g1)" stroke-width="16" stroke-linecap="round" opacity=".55" />
+ <path class="belt" d="M45 255H830" stroke="#fff" stroke-width="3" stroke-linecap="round" opacity=".75" />
+
+ <g class="box">
+ <rect x="60" y="212" width="54" height="42" rx="8" fill="#ffb15f" />
+ <path d="M60 225h54" stroke="#fff" opacity=".45" />
+ </g>
+ <g class="box two">
+ <rect x="60" y="212" width="54" height="42" rx="8" fill="#28d9cd" />
+ <path d="M60 225h54" stroke="#fff" opacity=".45" />
+ </g>
+ <g class="box three">
+ <rect x="60" y="212" width="54" height="42" rx="8" fill="#8b5cf6" />
+ <path d="M60 225h54" stroke="#fff" opacity=".45" />
+ </g>
+
+ <g>
+ <rect x="120" y="112" width="138" height="128" rx="18" fill="url(#g2)" stroke="rgba(255,255,255,.42)" />
+ <path d="M145 185h88M145 210h58" stroke="#40e4ff" stroke-width="6" stroke-linecap="round" />
+ <path d="M145 140h88" stroke="#fff" stroke-opacity=".5" stroke-width="4" stroke-linecap="round" />
+ </g>
+
+ <g>
+ <rect x="315" y="76" width="190" height="164" rx="22" fill="url(#g2)" stroke="rgba(255,255,255,.42)" />
+ <path d="M350 126h120M350 158h90M350 190h112" stroke="#fff" stroke-opacity=".5" stroke-width="6" stroke-linecap="round" />
+ <circle class="signal" cx="472" cy="104" r="10" fill="#20e0d2" />
+ <circle class="signal two" cx="448" cy="104" r="10" fill="#1f78ff" />
+ <circle class="signal three" cx="424" cy="104" r="10" fill="#ff8a3d" />
+ </g>
+
+ <g class="arm">
+ <path d="M612 124h92" stroke="#40e4ff" stroke-width="14" stroke-linecap="round" />
+ <path d="M704 124l42 56" stroke="#40e4ff" stroke-width="14" stroke-linecap="round" />
+ <circle cx="612" cy="124" r="25" fill="#1f78ff" stroke="#fff" stroke-opacity=".45" />
+ <circle cx="704" cy="124" r="18" fill="#20e0d2" />
+ <path d="M744 180v34M727 214h34" stroke="#fff" stroke-width="7" stroke-linecap="round" />
+ </g>
+
+ <g>
+ <rect x="690" y="82" width="148" height="158" rx="20" fill="url(#g2)" stroke="rgba(255,255,255,.42)" />
+ <path d="M724 206V134M764 206V112M804 206V154" stroke="#20e0d2" stroke-width="12" stroke-linecap="round" />
+ <path d="M720 206h92" stroke="#fff" stroke-opacity=".44" stroke-width="5" stroke-linecap="round" />
+ </g>
+
+ <path
+ d="M190 112C265 42 348 48 410 76C502 118 568 76 612 124C654 170 700 74 764 82"
+ fill="none"
+ stroke="#20e0d2"
+ stroke-width="2"
+ stroke-dasharray="8 10"
+ opacity=".58"
+ >
+ <animate attributeName="stroke-dashoffset" from="80" to="0" dur="2s" repeatCount="indefinite" />
+ </path>
+ </svg>
</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>
+ <section class="login-wrap">
+ <div class="time">
+ <span>{{ todayLabel }}</span>
+ {{ clockLabel }}
+ </div>
+
+ <form class="login-card" @submit.prevent="handleLogin">
+ <div class="brand card-brand">
+ <div class="logo">
+ <img :src="brandIconUrl" :alt="`${companyName} icon`" class="logo-image card-logo-image" />
+ </div>
+ <div class="brand-copy card-brand-copy">
+ <div class="brand-title">{{ companyName }}</div>
+ <small>鏁板瓧宸ュ巶缁熶竴鍏ュ彛</small>
+ </div>
</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>
+ <h2>娆㈣繋鐧诲綍</h2>
+ <p class="sub">杩涘叆 MOM 鏁板瓧宸ュ巶杩愯惀椹鹃┒鑸�</p>
- <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 class="form-row">
+ <label>璐﹀彿</label>
+ <div class="input">
+ <svg viewBox="0 0 24 24" fill="none">
+ <path d="M20 21a8 8 0 0 0-16 0" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
+ <path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke="currentColor" stroke-width="1.8" />
+ </svg>
+ <input v-model.trim="loginForm.username" type="text" placeholder="璇疯緭鍏ョ鐞嗗憳璐﹀彿" />
+ </div>
</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>
+ <div class="form-row">
+ <label>瀵嗙爜</label>
+ <div class="input">
+ <svg viewBox="0 0 24 24" fill="none">
+ <path d="M7 10V8a5 5 0 0 1 10 0v2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
+ <path d="M6.8 10h10.4A1.8 1.8 0 0 1 19 11.8v6.4A1.8 1.8 0 0 1 17.2 20H6.8A1.8 1.8 0 0 1 5 18.2v-6.4A1.8 1.8 0 0 1 6.8 10Z" stroke="currentColor" stroke-width="1.8" />
+ </svg>
+ <input
+ v-model="loginForm.password"
+ type="password"
+ placeholder="璇疯緭鍏ョ櫥褰曞瘑鐮�"
+ autocomplete="current-password"
+ @keyup.enter="handleLogin"
+ />
+ </div>
+ </div>
+
+ <div class="options">
+ <label class="check"><input v-model="loginForm.rememberMe" type="checkbox" />璁颁綇璐﹀彿</label>
+ </div>
+
+ <button class="login-btn" type="submit" :disabled="loading">
+ {{ loading ? "鐧诲綍涓�..." : "鐧诲綍鏁板瓧宸ュ巶" }}
+ </button>
+ </form>
</section>
- </div>
+ </main>
</div>
</template>
<script setup>
-import { getCodeImg } from "@/api/login"
+import { ElMessage } from "element-plus"
import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from "@/store/modules/user"
-import brandLogo from "@/assets/logo/logo.png"
+import defaultBrandLogo 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 appTitle = String(import.meta.env.VITE_APP_TITLE || "鏁板瓧宸ュ巶 MOM 绯荤粺").trim()
+const companySubtitle = String(import.meta.env.VITE_LOGIN_SUBTITLE || "Digital Factory Operation Center").trim()
+const configuredLogo = String(import.meta.env.VITE_APP_LOGO || "").trim()
+const logoModules = import.meta.glob("/src/assets/logo/*.png", { eager: true })
+const brandIconUrl = `${import.meta.env.BASE_URL}favicon.ico`
+
+const redirect = ref("")
+const loading = ref(false)
+const now = ref(new Date())
+const brandLogoUrl = ref(defaultBrandLogo)
const loginForm = ref({
username: "",
@@ -98,304 +182,712 @@
rememberMe: false,
})
-const loginRules = {
- username: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勮处鍙�" }],
- password: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勫瘑鐮�" }],
-}
+const companyName = computed(() => {
+ const currentFactoryName = String(userStore.currentFactoryName || "").trim()
+ return currentFactoryName || appTitle
+})
-const codeUrl = ref("")
-const loading = ref(false)
-const captchaEnabled = ref(true)
-const register = ref(false)
-const redirect = ref(undefined)
+const todayLabel = computed(() => {
+ const date = now.value
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
+})
+
+const clockLabel = computed(() => {
+ const date = now.value
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
+})
watch(
route,
(newRoute) => {
- redirect.value = newRoute.query && newRoute.query.redirect
+ redirect.value = String(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()
- }
- })
- }
- })
+watch(
+ () => userStore.currentFactoryName,
+ () => updateBrandLogo(),
+ { immediate: true }
+)
+
+let timer = 0
+onMounted(() => {
+ timer = window.setInterval(() => {
+ now.value = new Date()
+ }, 1000)
+})
+
+onBeforeUnmount(() => {
+ if (timer) {
+ window.clearInterval(timer)
+ timer = 0
+ }
+})
+
+function pad(value) {
+ return String(value).padStart(2, "0")
}
-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 resolveConfiguredLogo() {
+ if (!configuredLogo) {
+ return ""
+ }
+
+ if (/^(https?:)?\/\//.test(configuredLogo) || configuredLogo.startsWith("data:")) {
+ return configuredLogo
+ }
+
+ const cleanPath = configuredLogo.replace(/^\/+/, "")
+ const fullPath = cleanPath.startsWith("src/") ? `/${cleanPath}` : `/src/${cleanPath}`
+ const localLogo = logoModules[fullPath]
+
+ if (localLogo && localLogo.default) {
+ return localLogo.default
+ }
+
+ if (configuredLogo.startsWith("/")) {
+ return configuredLogo
+ }
+
+ return `${import.meta.env.BASE_URL}${cleanPath}`
+}
+
+function updateBrandLogo() {
+ const logoFromConfig = resolveConfiguredLogo()
+ if (logoFromConfig) {
+ brandLogoUrl.value = logoFromConfig
+ return
+ }
+
+ const currentFactoryName = String(userStore.currentFactoryName || "").trim()
+ if (!currentFactoryName) {
+ brandLogoUrl.value = defaultBrandLogo
+ return
+ }
+
+ const factoryLogoPath = `/src/assets/logo/${currentFactoryName}.png`
+ const matched = logoModules[factoryLogoPath]
+ brandLogoUrl.value = matched && matched.default ? matched.default : defaultBrandLogo
+}
+
+function handleLogoError() {
+ brandLogoUrl.value = defaultBrandLogo
+}
+
+function handleRememberCookie() {
+ if (!loginForm.value.rememberMe) {
+ Cookies.remove("username")
+ Cookies.remove("password")
+ Cookies.remove("rememberMe")
+ return
+ }
+
+ Cookies.set("username", loginForm.value.username, { expires: 30 })
+ Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
+ Cookies.set("rememberMe", "true", { expires: 30 })
}
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),
- }
+
+ loginForm.value.username = username || ""
+ loginForm.value.password = password ? decrypt(password) : ""
+ loginForm.value.rememberMe = rememberMe === "true"
}
-getCode()
+function handleLogin() {
+ if (!loginForm.value.username) {
+ ElMessage.error("璇疯緭鍏ヨ处鍙�")
+ return
+ }
+ if (!loginForm.value.password) {
+ ElMessage.error("璇疯緭鍏ュ瘑鐮�")
+ return
+ }
+
+ loading.value = true
+ handleRememberCookie()
+ 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(() => {})
+ .finally(() => {
+ loading.value = false
+ })
+}
+
getCookie()
</script>
-<style lang="scss" scoped>
+<style scoped lang="scss">
+* {
+ box-sizing: border-box;
+}
+
.login-page {
+ --blue: #1f78ff;
+ --cyan: #20e0d2;
+ --violet: #8b5cf6;
+ --orange: #ff8a3d;
+ --text: #09203f;
+ --muted: #7a8da8;
+ --line: rgba(113, 154, 214, 0.24);
+ --shadow: 0 28px 80px rgba(14, 57, 120, 0.18);
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;
- }
+ radial-gradient(circle at 12% 16%, rgba(31, 120, 255, 0.22), transparent 32%),
+ radial-gradient(circle at 84% 14%, rgba(32, 224, 210, 0.24), transparent 28%),
+ radial-gradient(circle at 80% 84%, rgba(139, 92, 246, 0.14), transparent 28%),
+ linear-gradient(135deg, #eef6ff 0%, #f7fbff 52%, #edf8ff 100%);
}
-.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;
+.login-page::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ background-image:
+ linear-gradient(rgba(22, 78, 160, 0.055) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(22, 78, 160, 0.055) 1px, transparent 1px);
+ background-size: 36px 36px;
+ mask-image: radial-gradient(circle at center, #000 0%, transparent 78%);
+ pointer-events: none;
}
-.brand-logo {
- width: 160px;
- height: auto;
- margin: 30px 0 24px;
+.page {
+ position: relative;
+ display: grid;
+ grid-template-columns: 1.15fr 0.85fr;
+ gap: 28px;
+ min-height: 100vh;
+ padding: 36px;
+}
+
+.factory {
+ position: relative;
+ overflow: hidden;
+ min-height: calc(100vh - 72px);
+ border-radius: 30px;
+ padding: 32px;
+ color: #fff;
+ background:
+ linear-gradient(135deg, rgba(5, 27, 67, 0.98), rgba(9, 71, 143, 0.92)),
+ radial-gradient(circle at 76% 18%, rgba(32, 224, 210, 0.38), transparent 30%);
+ box-shadow: var(--shadow);
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ animation: enter 0.8s ease both;
+}
+
+.factory::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background:
+ linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.11) 46%, transparent 72%),
+ linear-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.055) 1px, transparent 1px);
+ background-size: 100% 100%, 44px 44px, 44px 44px;
+ opacity: 0.86;
+}
+
+.brand {
+ position: relative;
+ z-index: 2;
+ display: inline-flex;
+ align-items: center;
+}
+
+.logo {
+ width: 76px;
+ height: 76px;
+ border-radius: 14px;
+ display: grid;
+ place-items: center;
+ background: linear-gradient(135deg, var(--blue), var(--cyan));
+ box-shadow: 0 16px 40px rgba(32, 224, 210, 0.26);
+ overflow: hidden;
+ position: relative;
+}
+
+.logo-image {
+ width: 82%;
+ height: 82%;
object-fit: contain;
}
-.brand-title {
- margin: 0;
- font-size: 42px;
- line-height: 1.12;
- color: #21313f;
- letter-spacing: -0.03em;
+.hero-brand {
+ display: block;
}
-.brand-copy {
- max-width: 460px;
- margin: 18px 0 0;
- font-size: 16px;
- line-height: 1.75;
- color: #5f6d7e;
+.hero-logo {
+ width: 240px;
+ height: 84px;
+ border-radius: 0;
+ background: transparent;
+ box-shadow: none;
+ overflow: visible;
+ place-items: center start;
}
-.brand-points {
- margin-top: 34px;
- display: flex;
- flex-direction: column;
- gap: 14px;
+.hero-logo-image {
+ width: 100%;
+ height: auto;
+ max-height: 100%;
+ object-fit: contain;
}
-.brand-point {
- display: flex;
+.hero {
+ position: relative;
+ z-index: 2;
+ margin-top: 64px;
+ max-width: 680px;
+}
+
+.chip {
+ display: inline-flex;
align-items: center;
- gap: 12px;
- color: #3d4b59;
- font-size: 15px;
- font-weight: 500;
+ gap: 8px;
+ padding: 8px 14px;
+ border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ background: rgba(255, 255, 255, 0.1);
+ color: rgba(255, 255, 255, 0.78);
+ font-size: 13px;
+ animation: enter 0.75s ease 0.15s both;
}
-.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);
+.hero h1 {
+ margin: 24px 0 14px;
+ font-size: clamp(42px, 5.6vw, 78px);
+ line-height: 1.02;
+ font-weight: 900;
+ letter-spacing: 0;
+ animation: enter 0.8s ease 0.25s both;
}
-.login-panel {
+.hero p {
+ margin: 0;
+ max-width: 630px;
+ font-size: 17px;
+ line-height: 1.8;
+ color: rgba(255, 255, 255, 0.72);
+ animation: enter 0.8s ease 0.35s both;
+}
+
+.scene {
+ position: absolute;
+ left: 42px;
+ right: 42px;
+ bottom: 24px;
+ height: 44%;
+ z-index: 1;
+}
+
+.floor {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 48%;
+ border-radius: 28px;
+ background:
+ linear-gradient(90deg, rgba(32, 224, 210, 0), rgba(32, 224, 210, 0.22), rgba(31, 120, 255, 0)),
+ repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0 1px, transparent 1px 64px),
+ repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.1) 0 1px, transparent 1px 34px);
+ transform: perspective(620px) rotateX(58deg);
+ transform-origin: bottom;
+ opacity: 0.82;
+}
+
+.factory-svg {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 18%;
+ width: 100%;
+ height: 78%;
+ filter: drop-shadow(0 28px 30px rgba(0, 0, 0, 0.18));
+}
+
+.belt {
+ stroke-dasharray: 10 12;
+ animation: flow 1.2s linear infinite;
+}
+
+.arm {
+ transform-origin: 612px 124px;
+ animation: armMove 3.6s ease-in-out infinite;
+}
+
+.box {
+ animation: boxMove 5.4s linear infinite;
+}
+
+.box.two {
+ animation-delay: -1.8s;
+}
+
+.box.three {
+ animation-delay: -3.6s;
+}
+
+.signal {
+ opacity: 0.8;
+ animation: pulse 2.2s ease-in-out infinite;
+}
+
+.signal.two {
+ animation-delay: -0.7s;
+}
+
+.signal.three {
+ animation-delay: -1.4s;
+}
+
+.login-wrap {
+ position: relative;
+ z-index: 2;
display: flex;
align-items: center;
justify-content: center;
- padding: 40px;
- background: rgba(255, 255, 255, 0.7);
+ min-height: calc(100vh - 72px);
+ border-radius: 30px;
+ border: 1px solid rgba(255, 255, 255, 0.8);
+ background: rgba(255, 255, 255, 0.62);
+ backdrop-filter: blur(20px);
+ box-shadow: var(--shadow);
+ animation: enter 0.8s ease 0.12s both;
}
-.login-form {
- width: min(420px, 100%);
- padding: 38px 34px 34px;
- border: 1px solid rgba(216, 225, 219, 0.92);
+.time {
+ position: absolute;
+ top: 26px;
+ right: 28px;
+ display: flex;
+ gap: 9px;
+ align-items: center;
+ font-weight: 900;
+ color: #12325e;
+}
+
+.time span {
+ padding: 8px 12px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.66);
+ border: 1px solid var(--line);
+ color: var(--muted);
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.login-card {
+ width: min(440px, 100%);
+ padding: 34px;
border-radius: 28px;
+ border: 1px solid rgba(255, 255, 255, 0.95);
background: rgba(255, 255, 255, 0.88);
- box-shadow: 0 18px 52px rgba(31, 49, 38, 0.1);
+ box-shadow: 0 22px 60px rgba(21, 73, 143, 0.15);
+ position: relative;
+ overflow: hidden;
}
-.panel-head {
- margin-bottom: 28px;
+.login-card::before {
+ content: "";
+ position: absolute;
+ right: -92px;
+ top: -92px;
+ width: 190px;
+ height: 190px;
+ border-radius: 999px;
+ background: conic-gradient(from 0deg, rgba(31, 120, 255, 0.2), rgba(32, 224, 210, 0.32), rgba(139, 92, 246, 0.18), rgba(31, 120, 255, 0.2));
+ animation: rotate 9s linear infinite;
}
-.panel-kicker {
- margin: 0 0 10px;
- color: #8a98a8;
- font-size: 12px;
- font-weight: 700;
- letter-spacing: 0.16em;
+.login-card > * {
+ position: relative;
+ z-index: 1;
}
-.panel-title {
- margin: 0;
- color: #21313f;
- font-size: 30px;
- font-weight: 700;
+.card-brand {
+ margin-bottom: 18px;
}
-.panel-subtitle {
- margin: 10px 0 0;
- color: #6b7888;
+.card-brand .logo {
+ width: 52px;
+ height: 52px;
+}
+
+.card-brand-copy {
+ margin-left: 12px;
+}
+
+.card-logo-image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.card-brand .logo {
+ width: 68px;
+ height: 68px;
+}
+
+.login-card h2 {
+ margin: 8px 0;
+ font-size: 31px;
+ line-height: 1.15;
+ color: #0d2c5e;
+ font-weight: 900;
+ letter-spacing: 0;
+}
+
+.sub {
+ margin: 0 0 24px;
+ color: var(--muted);
font-size: 14px;
}
-.login-options {
- margin: -4px 0 22px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- color: #5f6d7e;
+.form-row {
+ margin-bottom: 14px;
+ animation: enter 0.6s ease both;
}
-.register-link {
- color: var(--el-color-primary);
- font-weight: 600;
+.form-row:nth-of-type(1) {
+ animation-delay: 0.32s;
}
-.login-submit {
+.form-row:nth-of-type(2) {
+ animation-delay: 0.4s;
+}
+
+.form-row:nth-of-type(3) {
+ animation-delay: 0.48s;
+}
+
+label {
+ display: block;
+ margin-bottom: 8px;
+ color: #24436b;
+ font-size: 13px;
+ font-weight: 800;
+}
+
+.input {
+ position: relative;
+}
+
+.input svg {
+ position: absolute;
+ left: 15px;
+ top: 50%;
+ width: 19px;
+ height: 19px;
+ transform: translateY(-50%);
+ color: #8197b6;
+ pointer-events: none;
+}
+
+input[type="text"],
+input[type="password"] {
width: 100%;
- height: 48px;
+ height: 52px;
+ padding: 0 15px 0 46px;
+ border: 1px solid rgba(108, 143, 190, 0.34);
+ border-radius: 16px;
+ outline: none;
+ background: linear-gradient(180deg, #fff, #f8fbff);
+ color: var(--text);
+ font-size: 15px;
+ transition: 0.2s ease;
}
-.input-icon {
- width: 14px;
+input[type="text"]:hover,
+input[type="password"]:hover {
+ border-color: rgba(31, 120, 255, 0.48);
+ box-shadow: 0 10px 24px rgba(31, 120, 255, 0.08);
}
-:deep(.el-form-item) {
- margin-bottom: 22px;
+input[type="text"]:focus,
+input[type="password"]:focus {
+ border-color: rgba(31, 120, 255, 0.72);
+ box-shadow: 0 0 0 4px rgba(31, 120, 255, 0.1);
}
-:deep(.el-input__wrapper) {
- min-height: 42px;
- height: 42px;
- padding-top: 0;
- padding-bottom: 0;
+.options {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ margin: 12px 0 22px;
+ font-size: 13px;
+ color: var(--muted);
}
-:deep(.el-input__inner) {
- height: 42px;
- line-height: 42px;
+.check {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ cursor: pointer;
}
-:deep(.el-checkbox) {
- color: #5f6d7e;
+.check input {
+ width: 16px;
+ height: 16px;
+ padding: 0;
+ accent-color: var(--blue);
}
-@media (max-width: 960px) {
+.login-btn {
+ width: 100%;
+ height: 54px;
+ border: none;
+ border-radius: 17px;
+ cursor: pointer;
+ color: #fff;
+ font-size: 16px;
+ font-weight: 900;
+ background: linear-gradient(135deg, var(--blue), var(--cyan));
+ box-shadow: 0 18px 38px rgba(31, 120, 255, 0.25);
+ position: relative;
+ overflow: hidden;
+ transition: 0.18s ease;
+}
+
+.login-btn:hover:not(:disabled) {
+ transform: translateY(-2px);
+ box-shadow: 0 22px 48px rgba(31, 120, 255, 0.32);
+}
+
+.login-btn:disabled {
+ opacity: 0.8;
+ cursor: not-allowed;
+}
+
+@keyframes enter {
+ from {
+ opacity: 0;
+ transform: translateY(26px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes rotate {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes flow {
+ to {
+ stroke-dashoffset: -44;
+ }
+}
+
+@keyframes boxMove {
+ 0% {
+ transform: translateX(-150px);
+ opacity: 0;
+ }
+ 12% {
+ opacity: 1;
+ }
+ 78% {
+ opacity: 1;
+ }
+ 100% {
+ transform: translateX(650px);
+ opacity: 0;
+ }
+}
+
+@keyframes armMove {
+ 0%,
+ 100% {
+ transform: rotate(-4deg);
+ }
+ 50% {
+ transform: rotate(9deg);
+ }
+}
+
+@keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 0.45;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.18);
+ }
+}
+
+@keyframes glow {
+ 0%,
+ 100% {
+ box-shadow: 0 0 18px rgba(32, 224, 210, 0.3);
+ }
+ 50% {
+ box-shadow: 0 0 34px rgba(32, 224, 210, 0.68);
+ }
+}
+
+@media (max-width: 1320px) {
+ .hero h1 {
+ font-size: clamp(52px, 5.2vw, 78px);
+ }
+}
+
+@media (max-width: 980px) {
.login-page {
- padding: 18px;
+ overflow: auto;
}
- .login-shell {
- min-height: auto;
+ .page {
grid-template-columns: 1fr;
+ padding: 18px;
+ min-height: auto;
}
- .login-brand {
- padding: 40px 28px 22px;
+ .factory,
+ .login-wrap {
+ min-height: auto;
}
- .login-brand::after {
- inset: 16px;
+ .factory {
+ min-height: 760px;
}
- .brand-title {
- font-size: 32px;
+ .time {
+ display: none;
}
- .brand-copy {
- font-size: 14px;
+ .login-wrap {
+ padding: 22px;
}
- .brand-points {
- margin-top: 24px;
- }
-
- .login-panel {
- padding: 12px 18px 24px;
- }
-
- .login-form {
+ .login-card {
width: 100%;
- padding: 28px 22px 24px;
+ padding: 26px;
}
+
+ .login-btn {
+ font-size: 16px;
+ }
+
}
</style>
diff --git a/src/views/oaSystem/projectManagement/components/milestoneList.vue b/src/views/oaSystem/projectManagement/components/milestoneList.vue
deleted file mode 100644
index ed31772..0000000
--- a/src/views/oaSystem/projectManagement/components/milestoneList.vue
+++ /dev/null
@@ -1,289 +0,0 @@
-// ... existing code ...
-<template>
- <div class="milestone-list-container">
- <el-timeline>
- <el-timeline-item
- v-for="milestone in milestoneList"
- :key="milestone.phaseId"
- :timestamp="milestone.endDate"
- >
- <el-card>
- <template #header>
- <div class="card-header">
- <span>{{ milestone.phaseName }}</span>
- <div class="milestone-actions">
- <el-button type="text" size="small" @click="handleEdit(milestone)">缂栬緫</el-button>
- <el-button type="text" size="small" @click="handleDelete(milestone)" danger>鍒犻櫎</el-button>
- </div>
- </div>
- </template>
- <div class="milestone-content">
- <p>{{ milestone.description }}</p>
- <div class="milestone-status">
- <el-tag :type="getStatusType(milestone.status)">{{ getStatusText(milestone.status) }}</el-tag>
- </div>
- </div>
- </el-card>
- </el-timeline-item>
- </el-timeline>
-
- <!-- 鏃犻噷绋嬬鏃剁殑鎻愮ず -->
- <div v-if="milestoneList.length === 0" class="empty-tip">
- <el-empty description="鏆傛棤閲岀▼纰戞暟鎹�" />
- </div>
-
- <!-- 缂栬緫閲岀▼纰戝璇濇 -->
- <el-dialog
- v-model="dialogVisible"
- :title="'缂栬緫閲岀▼纰�: ' + (form.phaseName || '')"
- width="600px"
- :close-on-click-modal="false"
- >
- <el-form
- ref="formRef"
- :model="form"
- :rules="rules"
- label-width="100px"
- >
- <el-form-item label="閲岀▼纰戝悕绉�" prop="phaseName">
- <el-input v-model="form.phaseName" placeholder="璇疯緭鍏ラ噷绋嬬鍚嶇О" />
- </el-form-item>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
- <el-date-picker
- v-model="form.startDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
- <el-date-picker
- v-model="form.endDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨缁撴潫鏃ユ湡"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="鐘舵��" prop="status">
- <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��">
- <el-option label="鏈紑濮�" value="notStarted" />
- <el-option label="宸插畬鎴�" value="completed" />
- <el-option label="宸插欢杩�" value="delayed" />
- </el-select>
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitEditForm">纭畾</el-button>
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
-</template>
-
-<script setup>
-import { ref, onMounted, watch, reactive } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
-import { getProject, listProjectPhase, updateProjectPhase,delProjectPhase } from '@/api/oaSystem/projectManagement';
-
-const props = defineProps({
- projectId: {
- type: String,
- required: true
- }
-});
-
-const emit = defineEmits(['refresh']);
-
-const milestoneList = ref([]);
-const dialogVisible = ref(false);
-const formRef = ref(null);
-const form = reactive({
- phaseId: '',
- phaseName: '',
- startDate: '',
- endDate: '',
- status: 'notStarted',
- projectId: props.projectId
-});
-
-// 琛ㄥ崟楠岃瘉瑙勫垯
-const rules = {
- phaseName: [
- { required: true, message: '璇疯緭鍏ラ噷绋嬬鍚嶇О', trigger: 'blur' },
- { min: 2, max: 50, message: '闀垮害鍦� 2 鍒� 50 涓瓧绗�', trigger: 'blur' }
- ],
- status: [
- { required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }
- ]
-};
-
-// 鑾峰彇閲岀▼纰戝垪琛�
-const getMilestoneList = async () => {
- try {
- listProjectPhase(props.projectId).then(res => {
- milestoneList.value = res.data.rows || res.data;
- // 鎸夌洰鏍囨棩鏈熸帓搴�
- // milestoneList.value.sort((a, b) => new Date(a.endDate) - new Date(b.endDate));
- })
- } catch (error) {
- ElMessage.error('鑾峰彇閲岀▼纰戝垪琛ㄥけ璐�');
- console.error('鑾峰彇閲岀▼纰戝垪琛ㄥけ璐�:', error);
- }
-};
-
-// 缂栬緫閲岀▼纰�
-const handleEdit = (milestone) => {
- // 澶嶅埗閲岀▼纰戞暟鎹埌琛ㄥ崟
- Object.assign(form, {
- phaseId: milestone.phaseId,
- phaseName: milestone.phaseName,
- description: milestone.description,
- endDate: milestone.endDate,
- status: milestone.status,
- projectId: props.projectId
- });
- dialogVisible.value = true;
-};
-
-// 鎻愪氦缂栬緫琛ㄥ崟
-const submitEditForm = async () => {
- try {
- await formRef.value.validate();
-
- // 鍙戦�佹洿鏂拌姹�
- const res = await updateProjectPhase(form);
-
- if (res.code === 200) {
- ElMessage.success('閲岀▼纰戠紪杈戞垚鍔�');
- dialogVisible.value = false;
- getMilestoneList(); // 鍒锋柊鍒楄〃
- emit('refresh'); // 閫氱煡鐖剁粍浠跺埛鏂�
- } else {
- ElMessage.error(res.msg || '閲岀▼纰戠紪杈戝け璐�');
- }
- } catch (error) {
- if (error.name === 'ValidationError') {
- // 琛ㄥ崟楠岃瘉澶辫触锛孍lement Plus浼氳嚜鍔ㄦ彁绀�
- return;
- }
- ElMessage.error('閲岀▼纰戠紪杈戝け璐�');
- console.error('缂栬緫閲岀▼纰戝け璐�:', error);
- }
-};
-
-// 鍒犻櫎閲岀▼纰�
-const handleDelete = (milestone) => {
- ElMessageBox.confirm(
- `纭畾瑕佸垹闄ら噷绋嬬 "${milestone.phaseName}" 鍚楋紵鍒犻櫎鍚庡皢鏃犳硶鎭㈠銆俙,
- '鍒犻櫎纭',
- {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }
- )
- .then(async () => {
- try {
- // 璋冪敤鍒犻櫎API
- const res = await delProjectPhase(milestone.phaseId);
-
- if (res.code === 200) {
- ElMessage.success('閲岀▼纰戝垹闄ゆ垚鍔�');
- getMilestoneList(); // 鍒锋柊鍒楄〃
- emit('refresh'); // 閫氱煡鐖剁粍浠跺埛鏂�
- } else {
- ElMessage.error(res.msg || '閲岀▼纰戝垹闄ゅけ璐�');
- }
- } catch (error) {
- ElMessage.error('閲岀▼纰戝垹闄ゅけ璐�');
- console.error('鍒犻櫎閲岀▼纰戝け璐�:', error);
- }
- })
- .catch(() => {
- // 鐢ㄦ埛鍙栨秷鍒犻櫎
- ElMessage.info('宸插彇娑堝垹闄�');
- });
-};
-
-// 鑾峰彇鐘舵�佹爣绛剧被鍨�
-const getStatusType = (status) => {
- const statusTypeMap = {
- notStarted: 'info',
- completed: 'success',
- delayed: 'danger'
- };
- return statusTypeMap[status] || 'default';
-};
-
-// 鑾峰彇鐘舵�佹枃鏈�
-const getStatusText = (status) => {
- const statusTextMap = {
- notStarted: '鏈紑濮�',
- completed: '宸插畬鎴�',
- delayed: '宸插欢杩�'
- };
- return statusTextMap[status] || status;
-};
-
-// 鐩戝惉椤圭洰ID鍙樺寲
-watch(() => props.projectId, () => {
- if (props.projectId) {
- getMilestoneList();
- }
-});
-
-// 鍒濆鍖�
-onMounted(() => {
- if (props.projectId) {
- getMilestoneList();
- }
-});
-</script>
-
-<style scoped>
-.milestone-list-container {
- padding: 10px 0;
-}
-
-.card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.milestone-actions {
- display: flex;
- gap: 10px;
-}
-
-.milestone-content {
- padding: 10px 0;
-}
-
-.milestone-status {
- margin-top: 10px;
-}
-
-.empty-tip {
- margin-top: 40px;
- text-align: center;
-}
-
-.dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
-}
-</style>
diff --git a/src/views/oaSystem/projectManagement/components/phaseGoalList.vue b/src/views/oaSystem/projectManagement/components/phaseGoalList.vue
deleted file mode 100644
index 5054299..0000000
--- a/src/views/oaSystem/projectManagement/components/phaseGoalList.vue
+++ /dev/null
@@ -1,165 +0,0 @@
-
-<template>
- <div class="phase-goal-list-container">
- <el-table
- v-loading="loading"
- :data="phaseGoalList"
- style="width: 100%"
- >
- <el-table-column prop="phaseName" label="鎵�灞為樁娈�" width="180" />
- <el-table-column prop="taskName" label="鐩爣鍚嶇О" min-width="200" />
- <el-table-column prop="targetValue" label="鐩爣鍊�" width="120" />
- <el-table-column prop="currentValue" label="褰撳墠鍊�" width="120" />
- <el-table-column prop="unit" label="鍗曚綅" width="80" />
- <el-table-column prop="targetDate" label="鐩爣鏃ユ湡" width="120" />
- <el-table-column prop="status" label="鐘舵��" width="100">
- <template #default="scope">
- <el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="completionRate" label="瀹屾垚搴�" width="120">
- <template #default="scope">
- <el-progress :percentage="scope.row.completionRate" :stroke-width="6" />
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔" width="150" fixed="right">
- <template #default="scope">
- <el-button type="text" size="small" @click="handleEdit(scope.row)">缂栬緫</el-button>
- <el-button type="text" size="small" @click="handleDelete(scope.row)" danger>鍒犻櫎</el-button>
- </template>
- </el-table-column>
- </el-table>
-
- <!-- 鏃犻樁娈电洰鏍囨椂鐨勬彁绀� -->
- <div v-if="!loading && phaseGoalList.length === 0" class="empty-tip">
- <el-empty description="鏆傛棤闃舵鐩爣鏁版嵁" />
- </div>
- </div>
-</template>
-
-<script setup>
-import { ref, onMounted, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
-import { listProjectPhase, updateProjectTask, delProjectTask } from '@/api/oaSystem/projectManagement';
-
-const props = defineProps({
- projectId: {
- type: String,
- required: true
- }
-});
-
-const emit = defineEmits(['refresh']);
-
-const phaseGoalList = ref([]);
-const loading = ref(true);
-
-// 鑾峰彇闃舵鐩爣鍒楄〃
-const getPhaseGoalList = async () => {
- loading.value = true;
- try {
- const { data } = await listProjectPhase(props.projectId);
- // 鍋囪phase鏁版嵁涓寘鍚簡鐩爣淇℃伅
- // 杩欓噷绠�鍖栧鐞嗭紝瀹為檯搴旇璋冪敤涓撻棬鐨勮幏鍙栭樁娈电洰鏍囩殑API
- const phases = data.rows || data;
- phaseGoalList.value = [];
-
- phases.forEach(phase => {
- if (phase.oaProjectPhaseTasks && phase.oaProjectPhaseTasks.length > 0) {
- phase.oaProjectPhaseTasks.forEach(goal => {
- phaseGoalList.value.push({
- ...goal,
- phaseName: phase.phaseName
- });
- });
- }
- });
- } catch (error) {
- ElMessage.error('鑾峰彇闃舵鐩爣鍒楄〃澶辫触');
- console.error('鑾峰彇闃舵鐩爣鍒楄〃澶辫触:', error);
- } finally {
- loading.value = false;
- }
-};
-
-// 缂栬緫闃舵鐩爣
-const handleEdit = (goal) => {
- // 瑙﹀彂鐖剁粍浠剁殑缂栬緫浜嬩欢锛屼紶閫掔洰鏍囨暟鎹�
- emit('editGoal', goal);
-};
-
-// 鍒犻櫎闃舵鐩爣
-const handleDelete = async (goal) => {
- try {
- await ElMessageBox.confirm(
- `纭畾瑕佸垹闄ょ洰鏍囥��${goal.taskName}銆嶅悧锛焋,
- '纭鍒犻櫎',
- {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }
- );
-
- // 璋冪敤鍒犻櫎API
- await delProjectTask(goal.taskId);
- ElMessage.success('鍒犻櫎闃舵鐩爣鎴愬姛');
-
- // 鍒锋柊鍒楄〃
- getPhaseGoalList();
- emit('refresh');
- } catch (error) {
- if (error !== 'cancel') {
- ElMessage.error('鍒犻櫎闃舵鐩爣澶辫触');
- console.error('鍒犻櫎闃舵鐩爣澶辫触:', error);
- }
- }
-};
-
-// 鑾峰彇鐘舵�佹爣绛剧被鍨�
-const getStatusType = (status) => {
- const statusTypeMap = {
- notStarted: 'info',
- inProgress: 'primary',
- completed: 'success',
- delayed: 'danger'
- };
- return statusTypeMap[status] || 'default';
-};
-
-// 鑾峰彇鐘舵�佹枃鏈�
-const getStatusText = (status) => {
- const statusTextMap = {
- notStarted: '鏈紑濮�',
- inProgress: '杩涜涓�',
- completed: '宸插畬鎴�',
- delayed: '宸插欢杩�'
- };
- return statusTextMap[status] || status;
-};
-
-// 鐩戝惉椤圭洰ID鍙樺寲
-watch(() => props.projectId, () => {
- if (props.projectId) {
- getPhaseGoalList();
- }
-});
-
-// 鍒濆鍖�
-onMounted(() => {
- if (props.projectId) {
- getPhaseGoalList();
- }
-});
-</script>
-
-<style scoped>
-.phase-goal-list-container {
- padding: 10px 0;
-}
-
-.empty-tip {
- margin-top: 40px;
- text-align: center;
-}
-</style>
diff --git a/src/views/oaSystem/projectManagement/components/projectForm.vue b/src/views/oaSystem/projectManagement/components/projectForm.vue
deleted file mode 100644
index 50b671c..0000000
--- a/src/views/oaSystem/projectManagement/components/projectForm.vue
+++ /dev/null
@@ -1,221 +0,0 @@
-<template>
- <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
- <el-form-item label="椤圭洰鍚嶇О" prop="projectName">
- <el-input
- v-model="form.projectName"
- placeholder="璇疯緭鍏ラ」鐩悕绉�"
- maxlength="50"
- />
- </el-form-item>
- <el-form-item label="椤圭洰鎻忚堪" prop="description">
- <el-input
- v-model="form.description"
- type="textarea"
- :rows="4"
- placeholder="璇疯緭鍏ラ」鐩弿杩�"
- />
- </el-form-item>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
- <el-date-picker
- v-model="form.startDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
- <el-date-picker
- v-model="form.endDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨缁撴潫鏃ユ湡"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="椤圭洰璐熻矗浜�" prop="managerId">
- <!-- <el-input
- v-model="form.managerName"
- placeholder="璇烽�夋嫨椤圭洰璐熻矗浜�"
- readonly
- @focus="showUserSelect = true"
- /> -->
- <!-- <el-input
- v-model="userSearchKey"
- placeholder="鎼滅储鐢ㄦ埛"
- v-if="showUserSelect"
- @keyup.enter="searchUsers"
- style="margin-top: 10px"
- /> -->
- <el-select
- v-model="form.managerId"
- style="width: 100%; margin-top: 10px"
- @change="selectUser"
- filterable
- remote
- :remote-method="searchUsers"
- :loading="userLoading"
- placeholder="閫夋嫨椤圭洰璐熻矗浜�"
- >
- <el-option
- v-for="user in userList"
- :key="user.userId"
- :label="user.nickName"
- :value="user.userId"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="椤圭洰瀹屾垚搴�" prop="completionRate">
- <el-slider v-model="form.completionRate" :max="100" />
- </el-form-item>
- <el-form-item label="椤圭洰鐘舵��" prop="status">
- <el-radio-group v-model="form.status">
- <el-radio label="planning">瑙勫垝涓�</el-radio>
- <el-radio label="inProgress">杩涜涓�</el-radio>
- <el-radio label="completed">宸插畬鎴�</el-radio>
- <el-radio label="paused">宸叉殏鍋�</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-form>
-</template>
-
-<script setup>
-import { ref, watch, defineExpose } from 'vue';
-import { listUser } from '@/api/system/user';
-import { ElMessage } from 'element-plus';
-const props = defineProps({
- form: {
- type: Object,
- required: true
- },
- rules: {
- type: Object,
- required: true
- },
- visible: {
- type: Boolean,
- required: true
- }
-});
-
-const formRef = ref();
-const showUserSelect = ref(false);
-const userSearchKey = ref('');
-const userList = ref([]);
-const userLoading = ref(false);
-// 妯℃嫙鐢ㄦ埛鏁版嵁
-const mockUserData = [
- { userId: '1', nickName: '寮犱笁', userName: 'zhangsan' },
- { userId: '2', nickName: '鏉庡洓', userName: 'lisi' },
- { userId: '3', nickName: '鐜嬩簲', userName: 'wangwu' },
- { userId: '4', nickName: '璧靛叚', userName: 'zhaoliu' },
- { userId: '5', nickName: '閽变竷', userName: 'qianqi' },
- { userId: '6', nickName: '瀛欏叓', userName: 'sunba' },
- { userId: '7', nickName: '鍛ㄤ節', userName: 'zhoujiu' },
- { userId: '8', nickName: '鍚村崄', userName: 'wushi' }
-];
-// 鎼滅储鐢ㄦ埛
-const searchUsers = async (query) => {
- userLoading.value = true;
- try {
- // 妯℃嫙缃戠粶寤惰繜
- await new Promise(resolve => setTimeout(resolve, 300));
-
- // 姝g‘璁剧疆鐢ㄦ埛鍒楄〃
- if (!query) {
- userList.value = [...mockUserData];
- } else {
- userList.value = mockUserData.filter(user =>
- user.nickName.includes(query) || user.userName.includes(query)
- );
- }
- } catch (error) {
- console.error('鎼滅储鐢ㄦ埛澶辫触:', error);
- } finally {
- userLoading.value = false;
- }
-};
-
-// 閫夋嫨鐢ㄦ埛
-const selectUser = (userId) => {
- // 鍏堜粠userList鏌ユ壘锛屽鏋滄壘涓嶅埌鍐嶄粠mockUserData涓煡鎵�
- let selectedUser = userList.value.find(user => user.userId === userId);
- if (!selectedUser) {
- selectedUser = mockUserData.find(user => user.userId === userId);
- }
-
- // 浣跨敤Vue.set纭繚鍝嶅簲寮忔洿鏂�
- if (selectedUser) {
- Object.assign(props.form, { managerName: selectedUser.nickName });
- } else {
- Object.assign(props.form, { managerName: '' });
- }
- showUserSelect.value = false;
-};
-
-// 閲嶇疆琛ㄥ崟
-const resetFields = () => {
- if (formRef.value) {
- formRef.value.resetFields();
- }
- showUserSelect.value = false;
- userSearchKey.value = '';
- userList.value = [];
-};
-
-// 楠岃瘉琛ㄥ崟
-const validate = () => {
- return new Promise((resolve, reject) => {
- if (formRef.value) {
- formRef.value.validate((valid) => {
- if (valid) {
- // 棰濆楠岃瘉锛氱粨鏉熸棩鏈熶笉鑳芥棭浜庡紑濮嬫棩鏈�
- if (props.form.startDate && props.form.endDate) {
- const startDate = new Date(props.form.startDate);
- const endDate = new Date(props.form.endDate);
- if (endDate < startDate) {
- ElMessage.error('缁撴潫鏃ユ湡涓嶈兘鏃╀簬寮�濮嬫棩鏈�');
- reject(new Error('鏃ユ湡楠岃瘉澶辫触'));
- return;
- }
- }
- resolve();
- } else {
- reject(new Error('琛ㄥ崟楠岃瘉澶辫触'));
- }
- });
- } else {
- resolve();
- }
- });
-};
-
-// 鐩戝惉瀵硅瘽妗嗘樉绀虹姸鎬�
-watch(() => props.visible, (newVisible) => {
- if (!newVisible) {
- resetFields();
- // 寤惰繜閲嶇疆锛岀‘淇濇暟鎹凡缁忔彁浜ゅ埌鍚庣
- // setTimeout(() => {
- // resetFields();
- // }, 300);
- }
-});
-
-// 鏆撮湶鏂规硶
-defineExpose({
- resetFields,
- validate
-});
-</script>
-
-<style scoped>
-
-</style>
\ No newline at end of file
diff --git a/src/views/oaSystem/projectManagement/components/taskTree.vue b/src/views/oaSystem/projectManagement/components/taskTree.vue
deleted file mode 100644
index 03a1a15..0000000
--- a/src/views/oaSystem/projectManagement/components/taskTree.vue
+++ /dev/null
@@ -1,834 +0,0 @@
-<template>
- <div class="task-tree-container">
- <!-- 浠诲姟鏍戞搷浣滄寜閽� -->
- <div class="tree-actions mb10">
- <el-button type="primary" size="small" icon="Plus" @click="handleAddTask">娣诲姞浠诲姟</el-button>
- <el-button type="success" size="small" icon="RefreshRight" @click="refreshTree">鍒锋柊</el-button>
- <el-button type="info" size="small" icon="Filter" @click="toggleFilter">
- {{ showFilter ? '闅愯棌绛涢��' : '鏄剧ず绛涢��' }}
- </el-button>
- </div>
-
- <!-- 绛涢�夋潯浠� -->
- <div v-if="showFilter" class="filter-section mb10">
- <el-form :inline="true" :model="filterParams">
- <el-form-item label="浠诲姟鐘舵��">
- <el-select v-model="filterParams.status" placeholder="鍏ㄩ儴" clearable style="width: 120px">
- <el-option label="鏈紑濮�" value="notStarted" />
- <el-option label="杩涜涓�" value="inProgress" />
- <el-option label="宸插畬鎴�" value="completed" />
- <el-option label="宸查�炬湡" value="overdue" />
- </el-select>
- </el-form-item>
- <el-form-item label="璐熻矗浜�">
- <el-input v-model="filterParams.assignee" placeholder="杈撳叆璐熻矗浜�" clearable style="width: 150px" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" size="small" @click="filterTree">绛涢��</el-button>
- <el-button size="small" @click="resetFilter">閲嶇疆</el-button>
- </el-form-item>
- </el-form>
- </div>
-
- <!-- 浠诲姟鏍� -->
- <div class="tree-content">
- <el-tree
- v-loading="loading"
- :data="taskTreeData"
- :props="defaultProps"
- :expand-on-click-node="false"
- node-key="nodeId"
- ref="treeRef"
- @node-contextmenu="handleContextMenu"
- @node-click="handleNodeClick"
- >
- <template #default="{ node, data }">
- <!-- 鑺傜偣鍐呭 -->
- <div class="tree-node-content" :class="{ 'phase-node': data.type === 'phase', 'task-node': data.type === 'task' }">
- <!-- 鑺傜偣鍥炬爣 -->
- <div class="node-icon">
- <i v-if="data.type === 'phase'" class="el-icon-folder text-primary" />
- <i v-else-if="data.status === 'completed'" class="el-icon-circle-check text-success" />
- <i v-else-if="data.status === 'inProgress'" class="el-icon-circle-check text-primary" />
- <i v-else-if="data.status === 'overdue'" class="el-icon-alarm-clock text-danger" />
- <i v-else class="el-icon-circle-close text-gray-400" />
- </div>
-
- <!-- 鑺傜偣鏍囬鍜屾弿杩� -->
- <div class="node-info">
- <div class="node-title" :class="{ 'overdue-title': data.type === 'task' && data.status === 'overdue' }">
- {{ node.label }}
- <span v-if="data.type === 'task' && data.priority === 'high'" class="priority-tag">楂樹紭</span>
- <span v-else-if="data.type === 'task' && data.priority === 'medium'" class="priority-tag medium">涓紭</span>
- </div>
- <div v-if="data.description" class="node-description">{{ data.description }}</div>
-
- <!-- 浠诲姟鍏冧俊鎭� -->
- <div v-if="data.type === 'task'" class="task-meta">
- <span class="meta-item">
- <i class="el-icon-user"></i>
- {{ data.assigneeName || '鏈垎閰�' }}
- </span>
- <span class="meta-item">
- <i class="el-icon-calendar"></i>
- {{ formatDateRange(data.startDate, data.endDate) }}
- </span>
- </div>
- </div>
-
- <!-- 浠诲姟杩涘害鏉� -->
- <div v-if="data.type === 'task'" class="task-progress">
- <el-progress :percentage="data.progress || 0" :stroke-width="4" :show-text="false" />
- </div>
-
- <!-- 鎿嶄綔鎸夐挳 -->
- <div class="node-actions">
- <el-button
- v-if="data.type === 'task'"
- type="text"
- size="small"
- icon="Edit"
- @click.stop="handleEditTask(data)"
- v-hasPermi="['oaSystem:task:edit']"
- />
- <el-button
- v-if="data.type === 'phase'"
- type="text"
- size="small"
- icon="Plus"
- @click.stop="handleAddTaskUnderPhase(data)"
- v-hasPermi="['oaSystem:task:add']"
- />
- <el-button
- type="text"
- size="small"
- icon="Delete"
- @click.stop="handleDeleteNode(data)"
- v-hasPermi="['oaSystem:task:remove']"
- />
- </div>
- </div>
- </template>
- </el-tree>
- </div>
-
- <!-- 鍙抽敭鑿滃崟 -->
- <div v-if="showContextMenu" :style="contextMenuStyle" class="context-menu">
- <el-menu @select="handleContextMenuSelect">
- <el-menu-item v-if="selectedNode.type === 'task'" index="edit">缂栬緫浠诲姟</el-menu-item>
- <el-menu-item v-if="selectedNode.type === 'phase'" index="addTask">娣诲姞瀛愪换鍔�</el-menu-item>
- <el-menu-item index="delete">鍒犻櫎</el-menu-item>
- <el-menu-item index="expandAll">灞曞紑鍏ㄩ儴</el-menu-item>
- <el-menu-item index="collapseAll">鏀惰捣鍏ㄩ儴</el-menu-item>
- </el-menu>
- </div>
-
- <!-- 浠诲姟琛ㄥ崟瀵硅瘽妗� -->
- <el-dialog :title="dialogTitle" v-model="dialogOpen" width="600px" append-to-body>
- <el-form ref="taskFormRef" :model="taskForm" :rules="taskRules" label-width="80px">
- <el-form-item label="浠诲姟鍚嶇О" prop="taskName">
- <el-input v-model="taskForm.taskName" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" />
- </el-form-item>
- <el-form-item label="璐熻矗浜�" prop="assigneeId">
- <el-input v-model="taskForm.assigneeId" placeholder="璇疯緭鍏ヨ礋璐d汉ID" />
- </el-form-item>
- <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
- <el-date-picker
- v-model="taskForm.startDate"
- type="date"
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
- <el-date-picker
- v-model="taskForm.endDate"
- type="date"
- placeholder="閫夋嫨缁撴潫鏃ユ湡"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="浼樺厛绾�" prop="priority">
- <el-select v-model="taskForm.priority" placeholder="閫夋嫨浼樺厛绾�">
- <el-option label="浣�" value="low" />
- <el-option label="涓�" value="medium" />
- <el-option label="楂�" value="high" />
- </el-select>
- </el-form-item>
- <el-form-item label="杩涘害" prop="progress">
- <el-input-number v-model="taskForm.progress" :min="0" :max="100" style="width: 100%" />
- </el-form-item>
- <el-form-item label="鎻忚堪" prop="description">
- <el-input v-model="taskForm.description" type="textarea" placeholder="璇疯緭鍏ヤ换鍔℃弿杩�" />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitTaskForm">纭畾</el-button>
- <el-button @click="dialogOpen = false">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
-</template>
-
-<script setup>
-import { ref, reactive, computed, watch, onMounted } from 'vue';
-import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from 'element-plus';
-// import { getProject, addTask, updateTask, deleteTask, deletePhase } from '@/api/oaSystem/projectManagement';
-
-const props = defineProps({
- projectId: {
- type: String,
- required: true
- }
-});
-
-const emit = defineEmits(['refresh']);
-
-// 缁勪欢鐘舵��
-const loading = ref(false);
-const treeRef = ref();
-const showContextMenu = ref(false);
-const contextMenuStyle = ref({});
-const selectedNode = ref({});
-const showFilter = ref(false);
-const dialogOpen = ref(false);
-const dialogTitle = ref('');
-const taskFormRef = ref();
-
-// 绛涢�夊弬鏁�
-const filterParams = reactive({
- status: '',
- assignee: ''
-});
-
-// 浠诲姟琛ㄥ崟鏁版嵁
-const taskForm = reactive({
- taskId: undefined,
- taskName: '',
- description: '',
- startDate: '',
- endDate: '',
- assigneeId: '',
- assigneeName: '',
- status: 'notStarted',
- progress: 0,
- priority: 'medium',
- phaseId: '',
- projectId: props.projectId
-});
-
-// 琛ㄥ崟楠岃瘉瑙勫垯
-const taskRules = {
- taskName: [
- { required: true, message: '浠诲姟鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
- { min: 2, max: 50, message: '浠诲姟鍚嶇О闀垮害鍦� 2 鍒� 50 涓瓧绗�', trigger: 'blur' }
- ],
- startDate: [
- { required: true, message: '寮�濮嬫棩鏈熶笉鑳戒负绌�', trigger: 'change' }
- ],
- endDate: [
- { required: true, message: '缁撴潫鏃ユ湡涓嶈兘涓虹┖', trigger: 'change' }
- ],
- assigneeId: [
- { required: true, message: '璐熻矗浜轰笉鑳戒负绌�', trigger: 'blur' }
- ],
- progress: [
- { required: true, message: '杩涘害涓嶈兘涓虹┖', trigger: 'blur' },
- { type: 'number', min: 0, max: 100, message: '杩涘害蹇呴』鍦� 0 鍒� 100 涔嬮棿', trigger: 'blur' }
- ]
-};
-
-// 浠诲姟鏍戞暟鎹�
-const rawTaskTreeData = ref([]);
-
-// 妯℃嫙浠诲姟鏁版嵁
-const mockTaskData = {
- 'PRJ2023001': [
- {
- phaseId: 'PHASE001',
- phaseName: '闇�姹傚垎鏋�',
- startDate: '2023-11-01',
- endDate: '2023-11-15',
- status: 'completed',
- tasks: [
- {
- taskId: 'TASK001',
- taskName: '闇�姹傝皟鐮�',
- description: '璋冪爺鐢ㄦ埛闇�姹傚拰涓氬姟娴佺▼',
- startDate: '2023-11-01',
- endDate: '2023-11-05',
- assigneeId: 'USER001',
- assigneeName: '寮犱笁',
- status: 'completed',
- progress: 100,
- priority: 'medium'
- },
- {
- taskId: 'TASK002',
- taskName: '闇�姹傛枃妗g紪鍐�',
- description: '缂栧啓璇︾粏鐨勯渶姹傝鏍艰鏄庝功',
- startDate: '2023-11-06',
- endDate: '2023-11-15',
- assigneeId: 'USER002',
- assigneeName: '鏉庡洓',
- status: 'completed',
- progress: 100,
- priority: 'high'
- }
- ]
- },
- {
- phaseId: 'PHASE002',
- phaseName: '绯荤粺璁捐',
- startDate: '2023-11-16',
- endDate: '2023-12-10',
- status: 'completed',
- tasks: [
- {
- taskId: 'TASK003',
- taskName: '绯荤粺鏋舵瀯璁捐',
- description: '璁捐绯荤粺鏁翠綋鏋舵瀯',
- startDate: '2023-11-16',
- endDate: '2023-11-25',
- assigneeId: 'USER003',
- assigneeName: '鐜嬩簲',
- status: 'completed',
- progress: 100,
- priority: 'high'
- },
- {
- taskId: 'TASK004',
- taskName: '鏁版嵁搴撹璁�',
- description: '璁捐鏁版嵁搴撹〃缁撴瀯鍜屽叧绯�',
- startDate: '2023-11-26',
- endDate: '2023-12-10',
- assigneeId: 'USER004',
- assigneeName: '璧靛叚',
- status: 'completed',
- progress: 100,
- priority: 'medium'
- }
- ]
- },
- {
- phaseId: 'PHASE003',
- phaseName: '寮�鍙戝疄鐜�',
- startDate: '2023-12-11',
- endDate: '2024-01-31',
- status: 'inProgress',
- tasks: [
- {
- taskId: 'TASK005',
- taskName: '鍓嶇寮�鍙�',
- description: '寮�鍙戠敤鎴风晫闈㈠拰浜や簰閫昏緫',
- startDate: '2023-12-11',
- endDate: '2024-01-15',
- assigneeId: 'USER005',
- assigneeName: '閽变竷',
- status: 'inProgress',
- progress: 70,
- priority: 'high'
- },
- {
- taskId: 'TASK006',
- taskName: '鍚庣寮�鍙�',
- description: '寮�鍙戜笟鍔¢�昏緫鍜孉PI鎺ュ彛',
- startDate: '2023-12-11',
- endDate: '2024-01-20',
- assigneeId: 'USER006',
- assigneeName: '瀛欏叓',
- status: 'inProgress',
- progress: 60,
- priority: 'high'
- }
- ]
- }
- ],
- // 榛樿鏁版嵁
- default: [
- {
- phaseId: 'PHASE_DEFAULT1',
- phaseName: '鍑嗗闃舵',
- startDate: '2023-01-01',
- endDate: '2023-03-31',
- status: 'completed',
- tasks: [
- {
- taskId: 'TASK_DEFAULT1',
- taskName: '椤圭洰鍚姩',
- description: '鍙紑椤圭洰鍚姩浼氳',
- startDate: '2023-01-01',
- endDate: '2023-01-05',
- assigneeId: 'USER_DEFAULT1',
- assigneeName: '璐熻矗浜篈',
- status: 'completed',
- progress: 100,
- priority: 'high'
- }
- ]
- },
- {
- phaseId: 'PHASE_DEFAULT2',
- phaseName: '鎵ц闃舵',
- startDate: '2023-04-01',
- endDate: '2023-09-30',
- status: 'inProgress',
- tasks: [
- {
- taskId: 'TASK_DEFAULT2',
- taskName: '鏍稿績鍔熻兘寮�鍙�',
- description: '寮�鍙戠郴缁熸牳蹇冨姛鑳芥ā鍧�',
- startDate: '2023-04-01',
- endDate: '2023-06-30',
- assigneeId: 'USER_DEFAULT2',
- assigneeName: '璐熻矗浜築',
- status: 'inProgress',
- progress: 50,
- priority: 'high'
- }
- ]
- }
- ]
-};
-
-const taskTreeData = computed(() => {
- // 搴旂敤绛涢�夋潯浠�
- if (!showFilter.value || (!filterParams.status && !filterParams.assignee)) {
- return rawTaskTreeData.value;
- }
-
- // 娣辨嫹璐濆師濮嬫暟鎹互閬垮厤淇敼
- const filteredData = JSON.parse(JSON.stringify(rawTaskTreeData.value));
-
- // 閫掑綊绛涢�夎妭鐐�
- const filterNodes = (nodes) => {
- const result = [];
-
- nodes.forEach(node => {
- // 瀵逛簬闃舵鑺傜偣锛屾鏌ュ叾瀛愪换鍔℃槸鍚︾鍚堢瓫閫夋潯浠�
- if (node.type === 'phase' && node.children) {
- const filteredChildren = filterNodes(node.children);
- if (filteredChildren.length > 0) {
- // 淇濈暀鑷冲皯鏈変竴涓瓙浠诲姟绗﹀悎鏉′欢鐨勯樁娈�
- node.children = filteredChildren;
- result.push(node);
- }
- }
- // 瀵逛簬浠诲姟鑺傜偣锛岀洿鎺ュ簲鐢ㄧ瓫閫夋潯浠�
- else if (node.type === 'task') {
- const statusMatch = !filterParams.status || node.status === filterParams.status;
- const assigneeMatch = !filterParams.assignee ||
- (node.assigneeName && node.assigneeName.includes(filterParams.assignee));
-
- if (statusMatch && assigneeMatch) {
- result.push(node);
- }
- }
- });
-
- return result;
- };
-
- return filterNodes(filteredData);
-});
-
-// 鏍戣妭鐐归厤缃�
-const defaultProps = {
- children: 'children',
- label: (data) => {
- if (data.type === 'phase') {
- return `${data.phaseName}`;
- } else {
- return `${data.taskName}`;
- }
- }
-};
-
-// 鍔犺浇浠诲姟鏍戞暟鎹�
-const loadTaskTree = async () => {
- loading.value = true;
- // try {
- // const { data } = await getProject(props.projectId);
- // rawTaskTreeData.value = buildTaskTree(data.phases || []);
- // } catch (error) {
- // ElMessage.error('鍔犺浇浠诲姟鏍戝け璐�');
- // console.error('鍔犺浇浠诲姟鏍戝け璐�:', error);
- // } finally {
- // loading.value = false;
- // }
- try {
- // 妯℃嫙缃戠粶寤惰繜
- await new Promise(resolve => setTimeout(resolve, 500));
-
- // 浣跨敤妯℃嫙鏁版嵁鏇夸唬API璇锋眰
- const phases = mockTaskData[props.projectId] || mockTaskData.default;
- rawTaskTreeData.value = buildTaskTree(phases);
- } catch (error) {
- ElMessage.error('鍔犺浇浠诲姟鏍戝け璐�');
- console.error('鍔犺浇浠诲姟鏍戝け璐�:', error);
- } finally {
- loading.value = false;
- }
-};
-
-// 鏋勫缓浠诲姟鏍�
-const buildTaskTree = (phases) => {
- return phases.map(phase => ({
- nodeId: phase.phaseId,
- phaseId: phase.phaseId,
- phaseName: phase.phaseName,
- type: 'phase',
- children: (phase.tasks || []).map(task => ({
- nodeId: task.taskId,
- taskId: task.taskId,
- taskName: task.taskName,
- description: task.description,
- startDate: task.startDate,
- endDate: task.endDate,
- assigneeId: task.assigneeId,
- assigneeName: task.assigneeName,
- status: task.status,
- progress: task.progress,
- priority: task.priority,
- phaseId: task.phaseId,
- projectId: props.projectId,
- type: 'task'
- }))
- }));
-};
-
-// 鏍煎紡鍖栨棩鏈熻寖鍥�
-const formatDateRange = (startDate, endDate) => {
- if (!startDate || !endDate) return '';
- return `${startDate} - ${endDate}`;
-};
-
-// 鍒锋柊鏍�
-const refreshTree = () => {
- loadTaskTree();
- // 閫氱煡鐖剁粍浠跺埛鏂版暟鎹�
- emit('refresh');
-};
-
-// 鍒囨崲绛涢�夐潰鏉�
-const toggleFilter = () => {
- showFilter.value = !showFilter.value;
-};
-
-// 搴旂敤绛涢��
-const filterTree = () => {
- // 绛涢�夐�昏緫宸茬粡鍦╟omputed涓疄鐜�
-};
-
-// 閲嶇疆绛涢��
-const resetFilter = () => {
- filterParams.status = '';
- filterParams.assignee = '';
-};
-
-// 澶勭悊鑺傜偣鐐瑰嚮
-const handleNodeClick = (data, node) => {
- // 鍒囨崲灞曞紑/鏀惰捣鐘舵��
- if (data.type === 'phase') {
- node.expanded = !node.expanded;
- }
-};
-
-// 澶勭悊鍙抽敭鑿滃崟
-const handleContextMenu = (event, data) => {
- event.preventDefault();
- selectedNode.value = data;
- contextMenuStyle.value = {
- position: 'fixed',
- left: `${event.clientX}px`,
- top: `${event.clientY}px`,
- zIndex: 1000
- };
- showContextMenu.value = true;
-};
-
-// 澶勭悊鍙抽敭鑿滃崟閫夋嫨
-const handleContextMenuSelect = (index) => {
- showContextMenu.value = false;
- switch (index) {
- case 'edit':
- if (selectedNode.value.type === 'task') {
- handleEditTask(selectedNode.value);
- }
- break;
- case 'addTask':
- if (selectedNode.value.type === 'phase') {
- handleAddTaskUnderPhase(selectedNode.value);
- }
- break;
- case 'delete':
- handleDeleteNode(selectedNode.value);
- break;
- case 'expandAll':
- treeRef.value?.expandAll();
- break;
- case 'collapseAll':
- treeRef.value?.collapseAll();
- break;
- }
-};
-
-// 娣诲姞浠诲姟
-const handleAddTask = () => {
- resetTaskForm();
- dialogTitle.value = '娣诲姞浠诲姟';
- dialogOpen.value = true;
-};
-
-// 鍦ㄦ寚瀹氶樁娈典笅娣诲姞浠诲姟
-const handleAddTaskUnderPhase = (phase) => {
- resetTaskForm();
- taskForm.phaseId = phase.phaseId;
- dialogTitle.value = '娣诲姞瀛愪换鍔�';
- dialogOpen.value = true;
-};
-
-// 缂栬緫浠诲姟
-const handleEditTask = (task) => {
- resetTaskForm();
- Object.assign(taskForm, { ...task });
- dialogTitle.value = '缂栬緫浠诲姟';
- dialogOpen.value = true;
-};
-
-// 鍒犻櫎鑺傜偣
-const handleDeleteNode = async (node) => {
- const confirmMessage = node.type === 'phase'
- ? `纭畾瑕佸垹闄ら樁娈� "${node.phaseName}" 鍙婂叾鎵�鏈夊瓙浠诲姟鍚楋紵`
- : `纭畾瑕佸垹闄や换鍔� "${node.taskName}" 鍚楋紵`;
-
- await ElMessageBox.confirm(confirmMessage, '纭鎿嶄綔', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).catch(() => {
- throw new Error('鍙栨秷鍒犻櫎');
- });
-
- try {
- if (node.type === 'phase') {
- await deletePhase(node.phaseId);
- } else {
- await deleteTask(node.taskId);
- }
- ElMessage.success('鍒犻櫎鎴愬姛');
- refreshTree();
- } catch (error) {
- if (error.message !== '鍙栨秷鍒犻櫎') {
- ElMessage.error('鍒犻櫎澶辫触');
- console.error('鍒犻櫎澶辫触:', error);
- }
- }
-};
-
-// 閲嶇疆浠诲姟琛ㄥ崟
-const resetTaskForm = () => {
- taskForm.taskId = undefined;
- taskForm.taskName = '';
- taskForm.description = '';
- taskForm.startDate = '';
- taskForm.endDate = '';
- taskForm.assigneeId = '';
- taskForm.assigneeName = '';
- taskForm.status = 'notStarted';
- taskForm.progress = 0;
- taskForm.priority = 'medium';
- taskForm.phaseId = '';
- taskForm.projectId = props.projectId;
-
- if (taskFormRef.value) {
- taskFormRef.value.resetFields();
- }
-};
-
-// 鎻愪氦浠诲姟琛ㄥ崟
-const submitTaskForm = async () => {
- try {
- await taskFormRef.value.validate();
-
- if (taskForm.taskId) {
- await updateTask(taskForm);
- ElMessage.success('淇敼浠诲姟鎴愬姛');
- } else {
- await addTask(taskForm);
- ElMessage.success('娣诲姞浠诲姟鎴愬姛');
- }
-
- dialogOpen.value = false;
- refreshTree();
- } catch (error) {
- console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
- }
-};
-
-// 鐐瑰嚮鍏朵粬鍖哄煙鍏抽棴鍙抽敭鑿滃崟
-document.addEventListener('click', () => {
- if (showContextMenu.value) {
- showContextMenu.value = false;
- }
-});
-
-// 鐩戝惉椤圭洰ID鍙樺寲
-watch(() => props.projectId, (newProjectId) => {
- if (newProjectId) {
- loadTaskTree();
- }
-});
-
-// 鍒濆鍖�
-onMounted(() => {
- loadTaskTree();
-});
-</script>
-
-<style scoped>
-.task-tree-container {
- padding: 10px;
-}
-
-.tree-actions {
- display: flex;
- gap: 10px;
- align-items: center;
-}
-
-.filter-section {
- background: #f5f7fa;
- padding: 10px;
- border-radius: 4px;
-}
-
-.tree-content {
- background: #fff;
- border: 1px solid #ebeef5;
- border-radius: 4px;
- padding: 10px;
- max-height: 600px;
- overflow-y: auto;
-}
-
-.tree-node-content {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 5px 0;
- min-height: 40px;
-}
-
-.phase-node {
- font-weight: bold;
- color: #409eff;
-}
-
-.task-node {
- color: #606266;
-}
-
-.node-icon {
- display: flex;
- align-items: center;
- width: 20px;
-}
-
-.node-info {
- flex: 1;
- min-width: 0;
-}
-
-.node-title {
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-bottom: 2px;
-}
-
-.overdue-title {
- color: #f56c6c;
- font-weight: bold;
-}
-
-.priority-tag {
- background: #f56c6c;
- color: white;
- font-size: 10px;
- padding: 1px 4px;
- border-radius: 2px;
- margin-left: 5px;
-}
-
-.priority-tag.medium {
- background: #e6a23c;
-}
-
-.node-description {
- font-size: 12px;
- color: #909399;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.task-meta {
- display: flex;
- gap: 15px;
- font-size: 12px;
- color: #909399;
- margin-top: 2px;
-}
-
-.meta-item {
- display: flex;
- align-items: center;
- gap: 3px;
-}
-
-.task-progress {
- width: 120px;
- margin: 0 10px;
-}
-
-.node-actions {
- display: flex;
- gap: 5px;
- opacity: 0;
- transition: opacity 0.3s;
-}
-
-.tree-node-content:hover .node-actions {
- opacity: 1;
-}
-
-.context-menu {
- background: white;
- border: 1px solid #ebeef5;
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
- border-radius: 4px;
-}
-
-.context-menu .el-menu {
- min-width: 120px;
- border: none;
-}
-
-.context-menu .el-menu-item {
- padding: 0 15px;
- height: 36px;
- line-height: 36px;
-}
-
-.context-menu .el-menu-item:hover {
- background-color: #f5f7fa;
-}
-
-.text-gray-400 {
- color: #c0c4cc;
-}
-</style>
\ No newline at end of file
diff --git a/src/views/oaSystem/projectManagement/index.vue b/src/views/oaSystem/projectManagement/index.vue
deleted file mode 100644
index 79fbe7f..0000000
--- a/src/views/oaSystem/projectManagement/index.vue
+++ /dev/null
@@ -1,481 +0,0 @@
-<template>
- <div class="app-container">
- <!-- 椤堕儴鎼滅储鍜屾搷浣滄爮 -->
- <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
- <el-form-item label="椤圭洰鍚嶇О" prop="projectName">
- <el-input
- v-model="queryParams.projectName"
- placeholder="璇疯緭鍏ラ」鐩悕绉�"
- clearable
- style="width: 240px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="璐熻矗浜�" prop="managerName">
- <el-input
- v-model="queryParams.managerName"
- placeholder="璇疯緭鍏ヨ礋璐d汉濮撳悕"
- clearable
- style="width: 240px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="鐘舵��" prop="status">
- <el-select
- v-model="queryParams.status"
- placeholder="椤圭洰鐘舵��"
- clearable
- style="width: 150px"
- >
- <el-option label="瑙勫垝涓�" value="planning" />
- <el-option label="杩涜涓�" value="inProgress" />
- <el-option label="宸插畬鎴�" value="completed" />
- <el-option label="宸叉殏鍋�" value="paused" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
- <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
- </el-form-item>
- </el-form>
-
- <!-- 宸ュ叿鏍� -->
- <el-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button
- type="primary"
- plain
- icon="Plus"
- @click="handleAdd"
- v-hasPermi="['oaSystem:project:add']"
- >鏂板椤圭洰</el-button>
- </el-col>
- <!-- <el-col :span="1.5">
- <el-button
- type="success"
- plain
- icon="Edit"
- :disabled="single"
- @click="handleUpdate"
- v-hasPermi="['oaSystem:project:edit']"
- >缂栬緫椤圭洰</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button
- type="danger"
- plain
- icon="Delete"
- :disabled="multiple"
- @click="handleDelete"
- v-hasPermi="['oaSystem:project:remove']"
- >鍒犻櫎椤圭洰</el-button>
- </el-col> -->
- <el-col :span="1.5">
- <el-button
- type="warning"
- plain
- icon="Download"
- @click="handleExport"
- v-hasPermi="['oaSystem:project:export']"
- >瀵煎嚭椤圭洰</el-button>
- </el-col>
- </el-row>
-
- <!-- 椤圭洰鍒楄〃琛ㄦ牸 -->
- <el-table
- v-loading="loading"
- :data="projectList"
- @selection-change="handleSelectionChange"
- >
- <el-table-column type="selection" width="50" align="center" />
- <el-table-column
- label="椤圭洰缂栧彿"
- align="center"
- prop="projectId"
- width="100"
- />
- <el-table-column
- label="椤圭洰鍚嶇О"
- align="center"
- prop="projectName"
- :show-overflow-tooltip="true"
- />
- <el-table-column
- label="璐熻矗浜�"
- align="center"
- prop="managerName"
- />
- <el-table-column
- label="寮�濮嬫棩鏈�"
- align="center"
- prop="startDate"
- width="120"
- />
- <el-table-column
- label="缁撴潫鏃ユ湡"
- align="center"
- prop="endDate"
- width="120"
- />
- <el-table-column
- label="鐘舵��"
- align="center"
- prop="status"
- width="90"
- >
- <template #default="scope">
- <el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column
- label="瀹屾垚搴�"
- align="center"
- prop="completionRate"
- width="100"
- >
- <template #default="scope">
- <el-progress :percentage="scope.row.completionRate" :stroke-width="6" />
- </template>
- </el-table-column>
- <el-table-column
- label="鎿嶄綔"
- align="center"
- width="180"
- class-name="small-padding fixed-width"
- >
- <template #default="scope">
- <el-button
- link
- type="primary"
- icon="Search"
- @click="handleView(scope.row)"
- v-hasPermi="['oaSystem:project:query']"
- >璇︽儏</el-button>
- <el-button
- link
- type="primary"
- icon="Edit"
- @click="handleUpdate(scope.row)"
- v-hasPermi="['oaSystem:project:edit']"
- >缂栬緫</el-button>
- <el-button
- link
- type="danger"
- icon="Delete"
- @click="handleDelete(scope.row)"
- v-hasPermi="['oaSystem:project:remove']"
- >鍒犻櫎</el-button>
- </template>
- </el-table-column>
- </el-table>
-
- <!-- 鍒嗛〉缁勪欢 -->
- <pagination
- v-show="total > 0"
- :total="total"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- @pagination="getList"
- />
-
- <!-- 椤圭洰琛ㄥ崟瀵硅瘽妗� -->
- <el-dialog :title="title" v-model="open" width="600px" append-to-body>
- <project-form
- ref="projectFormRef"
- :form="form"
- :rules="rules"
- :visible="open"
- />
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- <el-button @click="cancel">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
-</template>
-
-<script setup>
-import { ref, reactive, computed, onMounted } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
-import Pagination from '@/components/Pagination';
-import ProjectForm from './components/projectForm.vue';
-import { useRouter } from 'vue-router';
-const { proxy } = getCurrentInstance();
-// 瀵煎叆椤圭洰绠$悊API鎺ュ彛
-import { listProject, addProject, updateProject, delProject, exportProject } from '@/api/oaSystem/projectManagement';
-// import { listUser } from '@/api/system/user'; // 瀵煎叆鐢ㄦ埛鍒楄〃API鎺ュ彛
-
-// 鍒涘缓router瀹炰緥
-const router = useRouter();
-
-// 琛ㄦ牸鏁版嵁
-const projectList = ref([]);
-const loading = ref(true);
-const total = ref(0);
-const queryParams = reactive({
- pageNum: 1,
- pageSize: 10,
- projectName: '',
- managerName: '',
- status: ''
-});
-
-// 琛ㄥ崟鏁版嵁
-const form = reactive({
- projectId: undefined,
- projectName: '',
- description: '',
- startDate: '',
- endDate: '',
- managerId: '',
- managerName: '',
- status: 'planning',
- completionRate: 0
-});
-
-// 琛ㄥ崟鏍¢獙瑙勫垯
-const rules = {
- projectName: [
- { required: true, message: '椤圭洰鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
- { min: 2, max: 50, message: '椤圭洰鍚嶇О闀垮害鍦� 2 鍒� 50 涓瓧绗�', trigger: 'blur' }
- ],
- startDate: [
- { required: true, message: '寮�濮嬫棩鏈熶笉鑳戒负绌�', trigger: 'change' }
- ],
- endDate: [
- { required: true, message: '缁撴潫鏃ユ湡涓嶈兘涓虹┖', trigger: 'change' }
- ],
- managerId: [
- { required: true, message: '璐熻矗浜轰笉鑳戒负绌�', trigger: 'blur' }
- ]
-};
-
-// 瀵硅瘽妗嗙姸鎬�
-const open = ref(false);
-const title = ref('');
-const projectFormRef = ref();
-const queryRef = ref();
-
-// 閫変腑鐘舵��
-const multiple = computed(() => {
- return selectedRowKeys.value.length === 0;
-});
-const single = computed(() => {
- return selectedRowKeys.value.length !== 1;
-});
-const selectedRowKeys = ref([]);
-
-// 鑾峰彇椤圭洰鍒楄〃
-const getList = async () => {
- loading.value = true;
- try {
- const { data } = await listProject(queryParams);
- projectList.value = data.records;
- total.value = data.total;
- } catch (error) {
- ElMessage.error('鑾峰彇椤圭洰鍒楄〃澶辫触');
- console.error('鑾峰彇椤圭洰鍒楄〃澶辫触:', error);
- } finally {
- loading.value = false;
- }
-};
-
-// 鎼滅储
-const handleQuery = () => {
- queryParams.pageNum = 1;
- getList();
-};
-
-// 閲嶇疆
-const resetQuery = () => {
- if (queryRef.value) {
- queryRef.value.resetFields();
- }
- handleQuery();
-};
-
-// 閫変腑琛屽彉鍖�
-const handleSelectionChange = (selection) => {
- selectedRowKeys.value = selection.map(item => item.projectId);
-};
-
-// 鏂板椤圭洰
-const handleAdd = () => {
- resetForm();
- open.value = true;
- title.value = '鏂板椤圭洰';
-};
-
-// 缂栬緫椤圭洰
-const handleUpdate = async (row) => {
- resetForm();
- const projectId = row.projectId || selectedRowKeys.value[0];
- try {
- // const { data } = await getProject(projectId);
- Object.assign(form, row);
- open.value = true;
- title.value = '缂栬緫椤圭洰';
- } catch (error) {
- ElMessage.error('鑾峰彇椤圭洰璇︽儏澶辫触');
- console.error('鑾峰彇椤圭洰璇︽儏澶辫触:', error);
- }
-};
-
-// 鍒犻櫎椤圭洰
-const handleDelete = async (row) => {
- // const projectIds = row.projectId ? [row.projectId] : selectedRowKeys.value;
- const projectNames = row.projectName ? [row.projectName] :
- projectList.value.filter(item => projectIds.includes(item.projectId)).map(item => item.projectName);
-
- const confirmMessage = `纭畾瑕佸垹闄ら」鐩� "${projectNames.join('銆�')}" 鍚楋紵`;
- await ElMessageBox.confirm(confirmMessage, '纭鎿嶄綔', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).catch(() => {
- throw new Error('鍙栨秷鍒犻櫎');
- });
-
- try {
- // if (projectIds.length === 1) {
- await delProject(row.projectId);
- // } else {
- // await delProjectBatch(projectIds);
- // }
- ElMessage.success('鍒犻櫎鎴愬姛');
- getList();
- } catch (error) {
- if (error.message !== '鍙栨秷鍒犻櫎') {
- ElMessage.error('鍒犻櫎澶辫触');
- console.error('鍒犻櫎椤圭洰澶辫触:', error);
- }
- }
- // try {
- // await ElMessageBox.confirm(confirmMessage, '纭鎿嶄綔', {
- // confirmButtonText: '纭畾',
- // cancelButtonText: '鍙栨秷',
- // type: 'warning'
- // });
-
- // // 妯℃嫙缃戠粶寤惰繜
- // await new Promise(resolve => setTimeout(resolve, 300));
-
-
- // ElMessage.success('鍒犻櫎鎴愬姛');
- // getList();
- // } catch (error) {
- // if (error !== 'cancel') {
- // console.error('鍒犻櫎椤圭洰澶辫触:', error);
- // }
- // }
-};
-
-// 鏌ョ湅椤圭洰璇︽儏
-const handleView = (row) => {
- const projectId = row.projectId;
- // 璺宠浆鍒伴」鐩鎯呴〉闈�
- router.push({
- path: `/oaSystem/projectManagement/projectDetail/${projectId}`,
- query: { projectName: row.projectName }
- });
-};
-
-// 瀵煎嚭椤圭洰
-const handleExport = async () => {
- let ids = [];
- if (selectedRowKeys.value.length > 0) {
- ids = selectedRowKeys.value; // 瀵煎嚭閫変腑鐨勯」鐩�
- } else {
- ids = projectList.value.map(item => item.projectId); // 瀵煎嚭鎵�鏈夐」鐩�
- }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download(`/oA/project/export/${ids.join(',')}`, {}, "椤圭洰鏁版嵁.xlsx");
- ElMessage.success("瀵煎嚭鎴愬姛");
- ids = [];
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-// 鎻愪氦琛ㄥ崟
-const submitForm = async () => {
- try {
- await projectFormRef.value.validate();
-
- if (form.projectId) {
- await updateProject(form);
- ElMessage.success('淇敼椤圭洰鎴愬姛');
- } else {
- console.log("form",form);
- await addProject(form);
- ElMessage.success('鏂板椤圭洰鎴愬姛');
- }
- open.value = false;
- getList();
- } catch (error) {
- console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
- }
-};
-
-// 鍙栨秷
-const cancel = () => {
- open.value = false;
- resetForm();
-};
-
-// 閲嶇疆琛ㄥ崟
-const resetForm = () => {
- form.projectId = undefined;
- form.projectName = '';
- form.description = '';
- form.startDate = '';
- form.endDate = '';
- form.managerId = '';
- form.managerName = '';
- form.status = 'planning';
- form.completionRate = 0;
- if (projectFormRef.value) {
- projectFormRef.value.resetFields();
- }
-};
-
-// 鑾峰彇鐘舵�佹爣绛剧被鍨�
-const getStatusType = (status) => {
- const statusTypeMap = {
- planning: 'info',
- inProgress: 'primary',
- completed: 'success',
- paused: 'warning'
- };
- return statusTypeMap[status] || 'default';
-};
-
-// 鑾峰彇鐘舵�佹枃鏈�
-const getStatusText = (status) => {
- const statusTextMap = {
- planning: '瑙勫垝涓�',
- inProgress: '杩涜涓�',
- completed: '宸插畬鎴�',
- paused: '宸叉殏鍋�'
- };
- return statusTextMap[status] || status;
-};
-
-// 鍒濆鍖�
-onMounted(() => {
- getList();
-});
-</script>
-
-<style scoped>
-.app-container {
- padding: 20px;
-}
-</style>
\ No newline at end of file
diff --git a/src/views/oaSystem/projectManagement/projectDetail.vue b/src/views/oaSystem/projectManagement/projectDetail.vue
deleted file mode 100644
index 578f76e..0000000
--- a/src/views/oaSystem/projectManagement/projectDetail.vue
+++ /dev/null
@@ -1,565 +0,0 @@
-// ... existing code ...
-<template>
- <div class="app-container">
- <!-- 椤圭洰鍩烘湰淇℃伅 -->
- <el-card class="mb20">
- <template #header>
- <div class="card-header">
- <span>椤圭洰鍩烘湰淇℃伅</span>
- </div>
- </template>
- <el-descriptions :column="2" border>
- <el-descriptions-item label="椤圭洰鍚嶇О">{{ projectInfo.projectName }}</el-descriptions-item>
- <el-descriptions-item label="椤圭洰璐熻矗浜�">{{ projectInfo.managerName }}</el-descriptions-item>
- <el-descriptions-item label="寮�濮嬫棩鏈�">{{ projectInfo.startDate }}</el-descriptions-item>
- <el-descriptions-item label="缁撴潫鏃ユ湡">{{ projectInfo.endDate }}</el-descriptions-item>
- <el-descriptions-item label="椤圭洰鐘舵��">
- <el-tag :type="getStatusType(projectInfo.status)">{{ getStatusText(projectInfo.status) }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="瀹屾垚搴�">
- <el-progress :percentage="projectInfo.completionRate" :stroke-width="6" />
- </el-descriptions-item>
- <el-descriptions-item label="椤圭洰鎻忚堪" :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item>
- </el-descriptions>
- </el-card>
-
- <!-- 椤圭洰杩涘害姒傝 -->
- <el-card class="mb20">
- <template #header>
- <div class="card-header">
- <span>椤圭洰杩涘害姒傝</span>
- </div>
- </template>
- <el-row :gutter="20">
- <el-col :span="6">
- <div class="progress-item">
- <div class="progress-title">鎬讳綋杩涘害</div>
- <div class="progress-number">{{ projectInfo.completionRate }}%</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="progress-item">
- <div class="progress-title">闃舵鎬绘暟</div>
- <div class="progress-number">{{ statistics.totalPhases }}</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="progress-item">
- <div class="progress-title">浠诲姟鎬绘暟</div>
- <div class="progress-number">{{ statistics.totalTasks }}</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="progress-item">
- <div class="progress-title">宸插畬鎴愪换鍔�</div>
- <div class="progress-number">{{ statistics.completedTasks }}</div>
- </div>
- </el-col>
- </el-row>
- </el-card>
-
- <!-- 闃舵鍜屼换鍔$鐞� -->
- <!-- <el-card class="mb20">
- <template #header>
- <div class="card-header">
- <span>椤圭洰浠诲姟缁撴瀯</span>
- <el-button type="primary" size="small" @click="handleAddPhase">娣诲姞闃舵</el-button>
- </div>
- </template>
- <task-tree :project-id="projectId" @refresh="getProjectDetail" />
- </el-card> -->
-
- <!-- 閲岀▼纰戠鐞� -->
- <el-card class="mb20">
- <template #header>
- <div class="card-header">
- <span>椤圭洰闃舵閲岀▼纰�</span>
- <el-button type="primary" size="small" @click="handleAddMilestone">娣诲姞閲岀▼纰�</el-button>
- </div>
- </template>
- <milestone-list :project-id="projectId" @refresh="getProjectDetail" :key="`milestone-${refreshProjectId}`"/>
- </el-card>
-
- <!-- 闃舵鐩爣绠$悊 -->
- <el-card>
- <template #header>
- <div class="card-header">
- <span>闃舵浠诲姟</span>
- <el-button type="primary" size="small" @click="handleAddPhaseGoal">娣诲姞闃舵鐩爣</el-button>
- </div>
- </template>
- <phase-goal-list :project-id="projectId" @refresh="getProjectDetail" @editGoal="handleEditPhaseGoal" :key="`phaseGoal-${refreshProjectId}`"/>
- </el-card>
-
- <!-- 閲岀▼纰戠鐞嗗脊妗� -->
- <el-dialog :title="title" v-model="open" width="600px" append-to-body>
- <el-form :model="form" ref="formRef" label-width="100px">
- <el-form-item label="椤圭洰闃舵鍚嶇О" prop="phaseName">
- <el-input
- v-model="form.phaseName"
- placeholder="璇疯緭鍏ラ」鐩樁娈靛悕绉�"
- maxlength="50"
- />
- </el-form-item>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
- <el-date-picker
- v-model="form.startDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
- <el-date-picker
- v-model="form.endDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨缁撴潫鏃ユ湡"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="鐘舵��" prop="status">
- <el-radio-group v-model="form.status">
- <el-radio label="notStarted">鏈紑濮�</el-radio>
- <el-radio label="completed">宸插畬鎴�</el-radio>
- <el-radio label="delayed">宸插欢杩�</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- <el-button @click="cancel">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
-
- <!-- 闃舵浠诲姟绠$悊寮规 -->
- <el-dialog :title="goalTitle" v-model="goalOpen" width="600px" append-to-body>
- <el-form :model="goalForm" ref="goalFormRef" label-width="100px">
- <el-form-item label="鎵�灞為樁娈�" prop="phaseId">
- <el-select v-model="goalForm.phaseId" placeholder="璇烽�夋嫨鎵�灞為樁娈�">
- <el-option
- v-for="phase in phaseList"
- :key="phase.phaseId"
- :label="phase.phaseName"
- :value="phase.phaseId"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="鐩爣鍚嶇О" prop="taskName">
- <el-input
- v-model="goalForm.taskName"
- placeholder="璇疯緭鍏ョ洰鏍囧悕绉�"
- maxlength="50"
- />
- </el-form-item>
- <el-form-item label="鐩爣鍊�" prop="targetValue">
- <el-input-number
- v-model="goalForm.targetValue"
- :min="0"
- :precision="2"
- placeholder="璇疯緭鍏ョ洰鏍囧��"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="褰撳墠鍊�" prop="currentValue">
- <el-input-number
- v-model="goalForm.currentValue"
- :min="0"
- :precision="2"
- placeholder="璇疯緭鍏ュ綋鍓嶅��"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="鍗曚綅" prop="unit">
- <el-input
- v-model="goalForm.unit"
- placeholder="璇疯緭鍏ュ崟浣�"
- maxlength="10"
- />
- </el-form-item>
- <el-form-item label="浠诲姟瀹屾垚鏃ユ湡" prop="targetDate">
- <el-date-picker
- v-model="goalForm.targetDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨鐩爣鏃ユ湡"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
- <el-date-picker
- v-model="goalForm.startDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨鐩爣鏃ユ湡"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
- <el-date-picker
- v-model="goalForm.endDate"
- type="date"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- placeholder="閫夋嫨鐩爣鏃ユ湡"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="鐘舵��" prop="status">
- <el-select v-model="goalForm.status" placeholder="璇烽�夋嫨鐘舵��">
- <el-option label="鏈紑濮�" value="notStarted" />
- <el-option label="杩涜涓�" value="inProgress" />
- <el-option label="宸插畬鎴�" value="completed" />
- <el-option label="宸插欢杩�" value="delayed" />
- </el-select>
- </el-form-item>
- <!-- <el-form-item label="瀹屾垚搴�" prop="completionRate">
- <el-input-number
- v-model="goalForm.completionRate"
- :min="0"
- :max="100"
- :precision="2"
- placeholder="璇疯緭鍏ュ畬鎴愬害"
- style="width: 100%"
- />
- </el-form-item> -->
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitGoalForm">纭畾</el-button>
- <el-button @click="cancelGoal">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
-</template>
-
-<script setup>
-import { ref, reactive, onMounted, watch } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
-import TaskTree from './components/taskTree.vue';
-import MilestoneList from './components/milestoneList.vue';
-import ProjectForm from './components/projectForm.vue';
-import PhaseGoalList from './components/phaseGoalList.vue';
-import { getProject, addProjectPhase, listProjectPhase, addProjectTask, updateProjectTask } from '@/api/oaSystem/projectManagement';
-
-const route = useRoute();
-const router = useRouter();
-const open = ref(false);
-const title = ref('');
-const projectFormRef = ref();
-const formRef = ref();
-// 椤圭洰ID
-// 鍦ㄥ叾浠杛ef瀹氫箟闄勮繎娣诲姞
-const refreshProjectId = ref(0);
-
-const projectId = ref(route.params.projectId);
-
-// 椤圭洰淇℃伅
-const projectInfo = reactive({
- projectId: '',
- projectName: '',
- description: '',
- startDate: '',
- endDate: '',
- managerId: '',
- managerName: '',
- status: 'planning',
- completionRate: 0
-});
-
-// 缁熻淇℃伅
-const statistics = reactive({
- totalPhases: 0,
- totalTasks: 0,
- completedTasks: 0
-});
-const form = reactive({
- phaseId: '',
- phaseName: '',
- startDate: '',
- endDate: '',
- status: 'planning',
- oaProjectId: projectId.value,
-})
-
-// 闃舵鐩爣鐩稿叧
-const goalOpen = ref(false);
-const goalTitle = ref('');
-const goalFormRef = ref();
-const phaseList = ref([]);
-const goalForm = reactive({
- taskId: '',
- phaseId: '',
- taskName: '',
- targetValue: 100,
- currentValue: 0,
- unit: '%',
- targetDate: '',
- startDate: '',
- endDate: '',
- status: 'notStarted',
- completionRate: 0
-});
-
-// 鑾峰彇椤圭洰璇︽儏
-const getProjectDetail = async () => {
- try {
- getProject().then((res)=>{
- console.log("椤圭洰璇︽儏",res)
- const projectData = res.data[projectId.value];
- // 鏇存柊椤圭洰淇℃伅
- Object.assign(projectInfo, projectData);
-
- // 鏇存柊缁熻淇℃伅
- updateStatistics(projectData);
-
- // 寮哄埗鏇存柊DOM浠ョ‘淇濆瓙缁勪欢鑳芥纭埛鏂�
- // 杩欓噷閫氳繃瑙﹀彂refreshProjectId浜嬩欢鏉ュ己鍒跺埛鏂板瓙缁勪欢
- refreshProjectId.value++;
- })
- } catch (error) {
- ElMessage.error('鑾峰彇椤圭洰璇︽儏澶辫触');
- console.error('鑾峰彇椤圭洰璇︽儏澶辫触:', error);
- }
-};
-
-// 鏇存柊缁熻淇℃伅
-const updateStatistics = (projectData) => {
- // 杩欓噷鍋囪projectData涓寘鍚簡缁熻淇℃伅
- // 濡傛灉娌℃湁锛岄渶瑕佸崟鐙姹傜粺璁℃暟鎹�
- statistics.totalPhases = projectData.phases ? projectData.phases.length : 0;
- statistics.totalTasks = projectData.tasks ? projectData.tasks.length : 0;
- statistics.completedTasks = projectData.tasks ?
- projectData.tasks.filter(task => task.status === 'completed').length : 0;
-};
-
-// 鑾峰彇椤圭洰闃舵鍒楄〃
-const getPhaseList = async () => {
- try {
- const { data } = await listProjectPhase(projectId.value);
- phaseList.value = data.rows || data;
- } catch (error) {
- ElMessage.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触');
- console.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触:', error);
- }
-};
-
-// 璁$畻瀹屾垚搴�
-const calculateCompletionRate = () => {
- if (goalForm.targetValue > 0) {
- goalForm.completionRate = Math.min(Math.round((goalForm.currentValue / goalForm.targetValue) * 100), 100);
- } else {
- goalForm.completionRate = 0;
- }
-};
-
-// 娣诲姞闃舵
-const handleAddPhase = () => {
- // resetForm();
- ElMessage.info('娣诲姞闃舵鍔熻兘寰呭疄鐜�');
-};
-
-// 娣诲姞閲岀▼纰�
-const handleAddMilestone = () => {
- resetForm();
- open.value = true;
- title.value = '鏂板椤圭洰闃舵';
-};
-
-// 娣诲姞闃舵浠诲姟
-const handleAddPhaseGoal = () => {
- goalForm.taskId = '';
- goalForm.phaseId = '';
- goalForm.taskName = '';
- goalForm.targetValue = 0;
- goalForm.currentValue = 0;
- goalForm.unit = '%';
- goalForm.targetDate = '';
- goalForm.startDate = '';
- goalForm.endDate = '';
- goalForm.status = 'notStarted';
- goalForm.completionRate = 0;
- if (goalFormRef.value) {
- goalFormRef.value.resetFields();
- }
- getPhaseList();
- goalTitle.value = '鏂板闃舵鐩爣';
- goalOpen.value = true;
-};
-
-// 鎻愪氦琛ㄥ崟
-const submitForm = async () => {
- try {
- await formRef.value.validate();
-
- if (form.phaseId) {
- // await updateProject(form);
- // ElMessage.success('淇敼椤圭洰闃舵鎴愬姛');
- } else {
- console.log("form",form);
- await addProjectPhase(form);
- ElMessage.success('鏂板椤圭洰闃舵鎴愬姛');
- getProjectDetail();
- }
- open.value = false;
- } catch (error) {
- console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
- }
-};
-
-// 鎻愪氦闃舵浠诲姟琛ㄥ崟
-const submitGoalForm = async () => {
- try {
- await goalFormRef.value.validate();
- calculateCompletionRate();
-
- const goalData = {
- ...goalForm,
- oaProjectId: projectId.value
- };
-
- if (goalForm.taskId) {
- await updateProjectTask(goalData);
- ElMessage.success('淇敼闃舵鐩爣鎴愬姛');
-
- } else {
- await addProjectTask(goalData);
- ElMessage.success('鏂板闃舵鐩爣鎴愬姛');
-
- }
- // 璋冪敤getProjectDetail鍒锋柊鎵�鏈夌浉鍏虫暟鎹�
- getProjectDetail();
- goalOpen.value = false;
-
- } catch (error) {
- console.error('鎻愪氦闃舵鐩爣琛ㄥ崟澶辫触:', error);
- }
-};
-
-// 閲嶇疆閲岀▼纰戣〃鍗�
-const resetForm = () => {
- form.phaseId = '';
- form.phaseName = '';
- form.startDate = '';
- form.endDate = '';
- form.status = 'planning';
- form.oaProjectId = projectId.value;
- if (formRef.value) {
- formRef.value.resetFields();
- }
-};
-
-// 鍙栨秷闃舵浠诲姟鎿嶄綔
-const cancelGoal = () => {
- goalOpen.value = false;
-};
-
-// 鍙栨秷鎿嶄綔
-const cancel = () => {
- open.value = false;
-};
-// 缂栬緫闃舵浠诲姟
-const handleEditPhaseGoal = async (goal) => {
- // 澶嶅埗鐩爣鏁版嵁鍒拌〃鍗�
- Object.assign(goalForm, goal);
-
- // 鑾峰彇椤圭洰闃舵鍒楄〃
- await getPhaseList();
-
- // 鎵撳紑缂栬緫寮圭獥
- goalTitle.value = '缂栬緫闃舵鐩爣';
- goalOpen.value = true;
-};
-// 鑾峰彇鐘舵�佹爣绛剧被鍨�
-const getStatusType = (status) => {
- const statusTypeMap = {
- planning: 'info',
- inProgress: 'primary',
- completed: 'success',
- paused: 'warning'
- };
- return statusTypeMap[status] || 'default';
-};
-
-// 鑾峰彇鐘舵�佹枃鏈�
-const getStatusText = (status) => {
- const statusTextMap = {
- planning: '瑙勫垝涓�',
- inProgress: '杩涜涓�',
- completed: '宸插畬鎴�',
- paused: '宸叉殏鍋�'
- };
- return statusTextMap[status] || status;
-};
-
-// 鐩戝惉璺敱鍙傛暟鍙樺寲
-watch(() => route.params.projectId, (newProjectId) => {
- // console.log('璺敱鍙傛暟鍙樺寲:', projectId);
- if (newProjectId) {
- projectId.value = newProjectId;
- getProjectDetail();
- }
-});
-
-// 鐩戝惉褰撳墠鍊煎拰鐩爣鍊煎彉鍖栵紝閲嶆柊璁$畻瀹屾垚搴�
-watch(() => [goalForm.currentValue, goalForm.targetValue], () => {
- calculateCompletionRate();
-});
-
-// 鍒濆鍖�
-onMounted(() => {
- if (projectId.value) {
- getProjectDetail();
- }
-});
-</script>
-
-<style scoped>
-.app-container {
- padding: 20px;
-}
-
-.card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.progress-item {
- text-align: center;
- padding: 20px;
- background-color: #f5f7fa;
- border-radius: 8px;
-}
-
-.progress-title {
- font-size: 14px;
- color: #606266;
- margin-bottom: 10px;
-}
-
-.progress-number {
- font-size: 24px;
- font-weight: bold;
- color: #409eff;
-}
-
-.mb20 {
- margin-bottom: 20px;
-}
-</style>
diff --git a/src/views/personnelManagement/contractManagement/filesDia.vue b/src/views/personnelManagement/contractManagement/filesDia.vue
index 4bcd812..02f9cef 100644
--- a/src/views/personnelManagement/contractManagement/filesDia.vue
+++ b/src/views/personnelManagement/contractManagement/filesDia.vue
@@ -158,7 +158,7 @@
}
// 涓嬭浇闄勪欢
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
// 鍒犻櫎
const handleDelete = () => {
diff --git a/src/views/personnelManagement/contractManagement/index.vue b/src/views/personnelManagement/contractManagement/index.vue
index a55a502..074b9ac 100644
--- a/src/views/personnelManagement/contractManagement/index.vue
+++ b/src/views/personnelManagement/contractManagement/index.vue
@@ -23,6 +23,12 @@
:total="page.total"></PIMTable>
</div>
<form-dia ref="formDia" @close="handleQuery"></form-dia>
+ <renew-contract
+ v-if="isShowRenewContractModal"
+ v-model:visible="isShowRenewContractModal"
+ :id="id"
+ @completed="handleQuery"
+ />
<!-- 鍚堝悓瀵煎叆瀵硅瘽妗� -->
<el-dialog
@@ -71,8 +77,9 @@
<script setup>
import { Search } from "@element-plus/icons-vue";
-import { onMounted, ref } from "vue";
+import { onMounted, ref, defineAsyncComponent } from "vue";
import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue";
+const RenewContract = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/RenewContract.vue"));
import { ElMessageBox } from "element-plus";
import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
import dayjs from "dayjs";
@@ -183,7 +190,7 @@
label: "鎿嶄綔",
align: "center",
fixed: 'right',
- width: 120,
+ width: 160,
operation: [
{
name: "璇︽儏",
@@ -191,11 +198,22 @@
clickFun: (row) => {
openForm("edit", row);
},
+ },
+ {
+ name: "缁鍚堝悓",
+ type: "text",
+ showHide: row => row.staffState === 1,
+ clickFun: (row) => {
+ isShowRenewContractModal.value = true;
+ id.value = row.id;
+ },
}
],
},
]);
const filesDia = ref()
+const isShowRenewContractModal = ref(false);
+const id = ref(0);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
diff --git a/src/views/personnelManagement/dimission/components/formDia.vue b/src/views/personnelManagement/dimission/components/formDia.vue
index 84922ec..86c59ce 100644
--- a/src/views/personnelManagement/dimission/components/formDia.vue
+++ b/src/views/personnelManagement/dimission/components/formDia.vue
@@ -102,6 +102,7 @@
v-model="form.leaveDate"
type="date"
:disabled="operationType === 'edit'"
+ :disabled-date="disabledFutureDate"
placeholder="璇烽�夋嫨绂昏亴鏃ユ湡"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
@@ -180,6 +181,19 @@
const dialogFormVisible = ref(false);
const operationType = ref('')
+const getTodayDate = () => {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = `${now.getMonth() + 1}`.padStart(2, '0');
+ const day = `${now.getDate()}`.padStart(2, '0');
+ return `${year}-${month}-${day}`;
+};
+
+const disabledFutureDate = (time) => {
+ const todayEnd = new Date();
+ todayEnd.setHours(23, 59, 59, 999);
+ return time.getTime() > todayEnd.getTime();
+};
const data = reactive({
form: {
staffOnJobId: undefined,
@@ -220,6 +234,7 @@
}
]
} else {
+ form.value.leaveDate = getTodayDate()
getList()
}
}
@@ -329,4 +344,4 @@
color: #303133;
font-size: 14px;
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/personnelManagement/employeeRecord/index.vue b/src/views/personnelManagement/employeeRecord/index.vue
index cd4ecf5..a0699b0 100644
--- a/src/views/personnelManagement/employeeRecord/index.vue
+++ b/src/views/personnelManagement/employeeRecord/index.vue
@@ -52,16 +52,14 @@
:tableLoading="tableLoading"
@pagination="pagination"
:total="page.total"
- ></PIMTable>
+ >
+ <template #positiveDate="{ row }">
+ <span :class="getPositiveDateClass(row.positiveDate)">{{ row.positiveDate }}</span>
+ </template>
+ </PIMTable>
</div>
<show-form-dia ref="formDia" @close="handleQuery"></show-form-dia>
<new-or-edit-form-dia ref="formDiaNewOrEditFormDia" @close="handleQuery"></new-or-edit-form-dia>
- <renew-contract
- v-if="isShowRenewContractModal"
- v-model:visible="isShowRenewContractModal"
- :id="id"
- @completed="handleQuery"
- />
<!-- 瀵煎叆瀵硅瘽妗� -->
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
@@ -107,7 +105,6 @@
const NewOrEditFormDia = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue"));
const ShowFormDia = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/Show.vue"));
-const RenewContract = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/RenewContract.vue"));
const data = reactive({
searchForm: {
@@ -119,8 +116,6 @@
deptOptions: [],
});
const { searchForm, deptOptions } = toRefs(data);
-const isShowRenewContractModal = ref(false);
-const id = ref(0);
const tableColumn = ref([
{
label: "鐘舵��",
@@ -177,6 +172,13 @@
width: 120,
},
{
+ label: "杞鏃ユ湡",
+ prop: "positiveDate",
+ width: 120,
+ dataType: "slot",
+ slot: "positiveDate",
+ },
+ {
label: "骞撮緞",
prop: "age",
},
@@ -208,22 +210,6 @@
openFormNewOrEditFormDia("edit", row);
},
},
- {
- name: "缁鍚堝悓",
- type: "text",
- showHide: row => row.staffState === 1,
- clickFun: (row) => {
- isShowRenewContractModal.value = true;
- id.value = row.id;
- },
- },
- // {
- // name: "璇︽儏",
- // type: "text",
- // clickFun: (row) => {
- // openForm("edit", row);
- // },
- // },
],
},
]);
@@ -253,6 +239,22 @@
// 涓婁紶鐨勫湴鍧�
url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import"
})
+
+// 鍒ゆ柇杞鏃ユ湡鏄惁鍦�7澶╁唴
+const getPositiveDateClass = (positiveDate) => {
+ if (!positiveDate) return '';
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const positive = new Date(positiveDate);
+ positive.setHours(0, 0, 0, 0);
+ const diffTime = positive - today;
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+ // 7澶╁唴杞锛堝寘鎷粖澶╋級鏄剧ず璀﹀憡鑹�
+ if (diffDays >= 0 && diffDays <= 7) {
+ return 'positive-date-warning';
+ }
+ return '';
+};
const fetchDeptOptions = () => {
deptTreeSelect().then(response => {
@@ -384,7 +386,11 @@
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 })
+ if (response.code !== 200) {
+ proxy.$modal.msgError(response.msg)
+ } else {
+ proxy.$modal.msgSuccess(response.msg)
+ }
getList()
}
@@ -402,4 +408,9 @@
.search_title2 {
margin-left: 10px;
}
+
+.positive-date-warning {
+ color: #f56c6c;
+ font-weight: bold;
+}
</style>
diff --git a/src/views/procurementManagement/procurementLedger/fileList.vue b/src/views/procurementManagement/procurementLedger/fileList.vue
index fb392c5..945d6a1 100644
--- a/src/views/procurementManagement/procurementLedger/fileList.vue
+++ b/src/views/procurementManagement/procurementLedger/fileList.vue
@@ -32,8 +32,7 @@
tableData.value = list
}
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
-
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
const lookFile = (row) => {
filePreviewRef.value.open(row.url)
diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
index cffdcc6..adf7b6e 100644
--- a/src/views/procurementManagement/procurementLedger/index.vue
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -693,6 +693,7 @@
const salesContractList = ref([]);
const supplierList = ref([]);
const tableLoading = ref(false);
+ const recordId = ref();
const fileListDialogVisible = ref(false);
const page = reactive({
current: 1,
diff --git a/src/views/procurementManagement/purchaseReturnOrder/New.vue b/src/views/procurementManagement/purchaseReturnOrder/New.vue
index dffdeea..2c6801d 100644
--- a/src/views/procurementManagement/purchaseReturnOrder/New.vue
+++ b/src/views/procurementManagement/purchaseReturnOrder/New.vue
@@ -227,10 +227,11 @@
<span class="title-text">浜у搧鍒楄〃</span>
</div>
<el-button type="primary" size="small" style="margin-bottom: 20px" @click="isShowProductsModal = true" :disabled="!formState.purchaseLedgerId">娣诲姞浜у搧</el-button>
- <el-table :data="formState.purchaseReturnOrderProductsDtos"
+ <div class="product-table-scroll">
+ <el-table class="product-table-inner"
+ :data="formState.purchaseReturnOrderProductsDtos"
border
max-height="400"
- :scroll-y="true"
show-summary
:summary-method="summarizeChildrenTable">
<el-table-column align="center"
@@ -240,6 +241,12 @@
label="搴忓彿"
type="index"
width="60" />
+ <el-table-column label="鍏ュ簱鍗曞彿"
+ prop="inboundBatches"
+ width="150" />
+ <el-table-column label="鎵规鍙�"
+ prop="batchNo"
+ width="150" />
<el-table-column label="浜у搧澶х被"
prop="productCategory" />
<el-table-column label="瑙勬牸鍨嬪彿"
@@ -248,11 +255,17 @@
prop="unit"
width="70" />
<el-table-column label="鏁伴噺"
- prop="quantity"
+ prop="stockInNum"
width="100" />
<el-table-column label="鍙��璐ф暟閲�"
- prop="availableQuality"
+ prop="unQuantity"
width="130" />
+ <el-table-column label="宸查��璐ф暟閲�"
+ width="130">
+ <template #default="scope">
+ {{ calcAlreadyReturned(scope.row) }}
+ </template>
+ </el-table-column>
<el-table-column label="閫�璐ф暟閲�"
prop="returnQuantity"
width="180">
@@ -268,27 +281,27 @@
placeholder="璇疯緭鍏ラ��璐ф暟閲�" />
</template>
</el-table-column>
- <el-table-column label="搴撳瓨棰勮鏁伴噺"
+ <!-- <el-table-column label="搴撳瓨棰勮鏁伴噺"
prop="warnNum"
width="120"
show-overflow-tooltip />
<el-table-column label="绋庣巼(%)"
prop="taxRate"
- width="80" />
+ width="80" /> -->
<el-table-column label="鍚◣鍗曚环(鍏�)"
prop="taxInclusiveUnitPrice"
:formatter="formattedNumber"
- width="150" />
+ width="120" />
<el-table-column label="閫�璐ф�讳环(鍏�)"
prop="taxInclusiveTotalPrice"
- width="180">
+ width="120">
<template #default="scope">
{{ formatAmount(getReturnTotal(scope.row)) || '--' }}
</template>
</el-table-column>
<el-table-column label="鏄惁璐ㄦ"
prop="isChecked"
- width="150">
+ width="100">
<template #default="scope">
<el-tag :type="scope.row.isChecked ? 'success' : 'info'">
{{ scope.row.isChecked ? '鏄�' : '鍚�' }}
@@ -311,6 +324,7 @@
</template>
</el-table-column>
</el-table>
+ </div>
</div>
<div class="section-title">
@@ -408,9 +422,6 @@
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,
@@ -518,6 +529,14 @@
return Number.isNaN(num) ? 0 : num
}
+/** 宸查��璐ф暟閲� = 鍏ュ簱琛屾�绘暟閲� 鈭� 褰撳墠鍙��璐ф暟閲忥紙鍓╀綑锛� */
+const calcAlreadyReturned = (row) => {
+ const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0)
+ const un = Number(row?.unQuantity ?? 0)
+ if (!Number.isFinite(total) || !Number.isFinite(un)) return 0
+ return Math.max(total - un, 0)
+}
+
const getReturnTotal = (row) => {
const qty = toNumber(row?.returnQuantity)
const unitPrice = toNumber(row?.taxInclusiveUnitPrice)
@@ -553,7 +572,7 @@
}
const getReturnQtyMax = (row) => {
- const max = Number(row?.availableQuality)
+ const max = Number(row?.unQuantity)
if (Number.isNaN(max) || max < 0) {
return 0
}
@@ -568,17 +587,17 @@
return proxy.summarizeTable(
param,
[
- "quantity",
- "availableQuality",
+ "stockInNum",
+ "unQuantity",
"returnQuantity",
"taxInclusiveUnitPrice",
"taxInclusiveTotalPrice",
"taxExclusiveTotalPrice",
],
{
- quantity: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ stockInNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
returnQuantity: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
- availableQuality: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ unQuantity: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
}
);
};
@@ -660,20 +679,10 @@
}
}
-// 澶勭悊鏀瑰彉閲囪喘鍙拌处鏁版嵁
-const handleChangePurchaseLedgerId = async () => {
+// 澶勭悊鏀瑰彉閲囪喘鍙拌处鏁版嵁锛堜笉璇锋眰鎺ュ彛鍥炴樉浜у搧锛屼骇鍝佷粎鍦ㄣ�屾坊鍔犱骇鍝併�嶅脊绐楀嬀閫夊悗鍐欏叆锛�
+const handleChangePurchaseLedgerId = () => {
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,
- }))
+ formState.value.purchaseReturnOrderProductsDtos = []
syncTotalAmount()
}
@@ -691,7 +700,7 @@
...item,
returnQuantity: undefined,
taxInclusiveTotalPrice: 0,
- salesLedgerProductId: item.id,
+ // salesLedgerProductId: item.salesLedgerProductId,
}));
formState.value.purchaseReturnOrderProductsDtos.push(...newProducts);
syncTotalAmount()
@@ -717,7 +726,7 @@
// 閫愯鏍¢獙閫�璐ф暟閲忥細浠绘剰涓�琛屾湭濉�/闈炴硶/瓒呴檺閮戒笉鍏佽鎻愪氦
const invalidRowIndex = productList.findIndex((item) => {
const qty = Number(item.returnQuantity)
- const maxQty = Number(item.availableQuality)
+ const maxQty = Number(item.unQuantity)
if (item.returnQuantity === null || item.returnQuantity === undefined || item.returnQuantity === "") {
return true
@@ -738,7 +747,15 @@
proxy.$refs["formRef"].validate(valid => {
if (valid) {
- createPurchaseReturnOrder(formState.value).then(res => {
+ console.log(productList)
+ const submitPayload = {
+ ...formState.value,
+ purchaseReturnOrderProductsDtos: productList.map((row) => ({
+ ...row,
+ stockInRecordId: row.id,
+ })),
+ }
+ createPurchaseReturnOrder(submitPayload).then(res => {
// 鍏抽棴妯℃�佹
isShow.value = false;
// 鍛婄煡鐖剁粍浠跺凡瀹屾垚
@@ -785,4 +802,13 @@
border-radius: 50%;
margin-right: 8px;
}
+
+.product-table-scroll {
+ width: 100%;
+ overflow-x: auto;
+}
+
+.product-table-inner {
+ min-width: 1280px;
+}
</style>
\ No newline at end of file
diff --git a/src/views/procurementManagement/purchaseReturnOrder/ProductList.vue b/src/views/procurementManagement/purchaseReturnOrder/ProductList.vue
index 3eeb167..27fae4a 100644
--- a/src/views/procurementManagement/purchaseReturnOrder/ProductList.vue
+++ b/src/views/procurementManagement/purchaseReturnOrder/ProductList.vue
@@ -6,9 +6,10 @@
width="1200"
@close="closeModal"
>
- <div class="table_list">
+ <div class="table_list" v-loading="tableLoading">
<el-table :data="tableData"
border
+ row-key="id"
@selection-change="handleChangeSelection">
<el-table-column align="center"
type="selection"
@@ -17,6 +18,12 @@
label="搴忓彿"
type="index"
width="60" />
+ <el-table-column label="鍏ュ簱鍗曞彿"
+ prop="inboundBatches"
+ width="150" />
+ <el-table-column label="鎵规鍙�"
+ prop="batchNo"
+ width="150" />
<el-table-column label="浜у搧澶х被"
prop="productCategory" />
<el-table-column label="瑙勬牸鍨嬪彿"
@@ -25,27 +32,36 @@
prop="unit"
width="70" />
<el-table-column label="鏁伴噺"
- prop="quantity"
+ prop="stockInNum"
width="70" />
- <el-table-column label="搴撳瓨棰勮鏁伴噺"
+ <el-table-column label="鍙��璐ф暟閲�"
+ prop="unQuantity"
+ width="130" />
+ <el-table-column label="宸查��璐ф暟閲�"
+ width="130">
+ <template #default="scope">
+ {{ calcAlreadyReturned(scope.row) }}
+ </template>
+ </el-table-column>
+ <!-- <el-table-column label="搴撳瓨棰勮鏁伴噺"
prop="warnNum"
width="120"
show-overflow-tooltip />
<el-table-column label="绋庣巼(%)"
prop="taxRate"
- width="80" />
+ width="80" /> -->
<el-table-column label="鍚◣鍗曚环(鍏�)"
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="150" /> -->
<el-table-column label="鏄惁璐ㄦ"
prop="isChecked"
width="150">
@@ -56,8 +72,6 @@
</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>
<template #footer>
@@ -71,8 +85,8 @@
</template>
<script setup>
-import {computed, reactive, ref, onMounted} from "vue";
-import {productList} from "@/api/procurementManagement/procurementLedger.js";
+import {computed, ref, onMounted} from "vue";
+import {getPurchaseReturnOrderByPurchaseLedgerId} from "@/api/procurementManagement/purchase_return_order.js";
import {ElMessage} from "element-plus";
const props = defineProps({
@@ -82,7 +96,7 @@
},
purchaseLedgerId: {
- type: Number,
+ type: [Number, String],
required: true,
}
});
@@ -101,32 +115,59 @@
const tableData = ref([])
const selectedRows = ref([])
const tableLoading = ref(false)
-const page = reactive({
- current: 1,
- size: 100,
-})
-const total = ref(0)
const formattedNumber = (row, column, cellValue) => {
return parseFloat(cellValue).toFixed(2);
};
-const paginationChange = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList()
+/** 宸查��璐ф暟閲� = 鍏ュ簱琛屾�绘暟閲� 鈭� 褰撳墠鍙��璐ф暟閲忥紙鍓╀綑锛� */
+const calcAlreadyReturned = (row) => {
+ const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0)
+ const un = Number(row?.unQuantity ?? 0)
+ if (!Number.isFinite(total) || !Number.isFinite(un)) return 0
+ return Math.max(total - un, 0)
}
const handleChangeSelection = (val) => {
selectedRows.value = val;
}
+/** 涓� New.vue 涓噰璐彴璐﹀彉鏇存椂瑙f瀽 getByPurchaseLedgerId 鐨勮鍒欎竴鑷� */
+const parseProductRowsFromLedgerResponse = (res) => {
+ const payload = res?.data
+ let list = []
+ if (Array.isArray(payload)) {
+ list = payload
+ } else if (payload && typeof payload === 'object') {
+ const nested =
+ payload.purchaseReturnOrderProductsDtos ||
+ payload.purchaseReturnOrderProductsDetailVoList
+ list = Array.isArray(nested) ? nested : []
+ if (list.length && list[0]?.salesLedgerProduct) {
+ list = list.map((item) => ({ ...item, ...item.salesLedgerProduct }))
+ }
+ }
+ return list
+}
+
const fetchData = () => {
- tableLoading.value = true;
- productList({salesLedgerId: props.purchaseLedgerId, type: 2}).then((res) => {
- tableData.value = res.data;
- }).finally(() => {
- tableLoading.value = false;
+ if (props.purchaseLedgerId === undefined || props.purchaseLedgerId === null || props.purchaseLedgerId === '') {
+ tableData.value = []
+ return
+ }
+ tableLoading.value = true
+ getPurchaseReturnOrderByPurchaseLedgerId({
+ purchaseLedgerId: props.purchaseLedgerId,
})
+ .then((res) => {
+ const list = parseProductRowsFromLedgerResponse(res)
+ tableData.value = list
+ })
+ .catch(() => {
+ tableData.value = []
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
}
const handleSubmit = () => {
diff --git a/src/views/procurementManagement/purchaseReturnOrder/index.vue b/src/views/procurementManagement/purchaseReturnOrder/index.vue
index cd2c7f4..f8866e1 100644
--- a/src/views/procurementManagement/purchaseReturnOrder/index.vue
+++ b/src/views/procurementManagement/purchaseReturnOrder/index.vue
@@ -1,24 +1,26 @@
<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.no"
- placeholder="璇疯緭鍏�"
- clearable
- prefix-icon="Search"
- @change="handleQuery" />
+ <el-input
+ v-model="searchForm.no"
+ 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>
- <el-button type="primary" @click="isShowNewModal = true">鏂板</el-button>
+ <el-button type="primary" @click="isShowNewModal = true"
+ >鏂板</el-button
+ >
</div>
</div>
@@ -35,14 +37,25 @@
@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>
+ <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" />
+ <new
+ v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ @completed="handleQuery"
+ />
<el-dialog
v-model="detailVisible"
@@ -52,21 +65,51 @@
>
<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-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>
@@ -77,27 +120,75 @@
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
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="鍏ュ簱鍗曞彿" prop="inboundBatches" width="150" />
+ <el-table-column label="鎵规鍙�" prop="batchNo" width="150" />
+ <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 label="鏁伴噺" prop="stockInNum" width="80" />
+ <el-table-column label="鍙��璐ф暟閲�"
+ prop="unQuantity"
+ width="100" />
+ <el-table-column label="宸查��璐ф暟閲�"
+ width="100">
+ <template #default="scope">
+ {{ calcAlreadyReturned(scope.row) }}
+ </template>
</el-table-column>
- <el-table-column label="鍚◣鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="130">
- <template #default="scope">{{ formatAmount(scope.row.taxInclusiveTotalPrice) }}</template>
+ <!-- <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="taxExclusiveTotalPrice" width="140">
- <template #default="scope">{{ formatAmount(scope.row.taxExclusiveTotalPrice) }}</template>
+ <!-- <el-table-column
+ label="閫�璐ф�讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ width="130"
+ >
+ <template #default="scope">{{
+ formatAmount(scope.row.taxInclusiveTotalPrice)
+ }}</template>
</el-table-column>
- <el-table-column label="鏄惁璐ㄦ" prop="isChecked" width="100" align="center">
+ <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 ? '鏄�' : '鍚�' }}
+ {{ scope.row.isChecked ? "鏄�" : "鍚�" }}
</el-tag>
</template>
</el-table-column>
@@ -111,238 +202,280 @@
</template>
<script setup>
-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([])
-const tableLoading = ref(false)
+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([]);
+const tableLoading = ref(false);
const page = reactive({
current: 1,
size: 100,
total: 0,
-})
-const detailVisible = ref(false)
-const detailLoading = ref(false)
-const detailData = ref({})
-const detailProducts = ref([])
+});
+const detailVisible = ref(false);
+const detailLoading = ref(false);
+const detailData = ref({});
+const detailProducts = ref([]);
// 鏄惁鏄剧ず鏂板寮规
-const isShowNewModal = ref(false)
+const isShowNewModal = ref(false);
const returnTypeOptions = [
- { label: '閫�璐ч��娆�', value: 0 },
- { label: '鎷掓敹', value: 1 },
-]
+ { label: "閫�璐ч��娆�", value: 0 },
+ { label: "鎷掓敹", value: 1 },
+];
const projectPhaseOptions = [
- { label: '绔嬮」', value: 0 },
- { label: '璁捐', value: 1 },
- { label: '閲囪喘', value: 2 },
- { label: '鐢熶骇', value: 3 },
- { label: '鍑鸿揣', value: 4 },
-]
+ { label: "绔嬮」", value: 0 },
+ { label: "璁捐", value: 1 },
+ { label: "閲囪喘", value: 2 },
+ { label: "鐢熶骇", value: 3 },
+ { label: "鍑鸿揣", value: 4 },
+];
const tableColumn = ref([
{
- label: '閫�鏂欏崟鍙�',
- prop: 'no',
+ label: "閫�鏂欏崟鍙�",
+ prop: "no",
},
{
- label: '閫�璐ф柟寮�',
- prop: 'returnType',
- formatData: (val) => returnTypeOptions.find(item => item.value === val)?.label || '--',
+ label: "閫�璐ф柟寮�",
+ prop: "returnType",
+ formatData: (val) =>
+ returnTypeOptions.find((item) => item.value === val)?.label || "--",
},
{
- label: '渚涘簲鍟嗗悕绉�',
- prop: 'supplierName',
+ label: "渚涘簲鍟嗗悕绉�",
+ prop: "supplierName",
width: 180,
},
{
- label: '椤圭洰闃舵',
- prop: 'projectPhase',
+ label: "椤圭洰闃舵",
+ prop: "projectPhase",
width: 100,
- formatData: (val) => projectPhaseOptions.find(item => String(item.value) === String(val))?.label || '--',
+ formatData: (val) =>
+ projectPhaseOptions.find((item) => String(item.value) === String(val))
+ ?.label || "--",
},
{
- label: '鍏宠仈鍗曞彿',
- prop: 'purchaseContractNumber',
+ label: "鍏宠仈鐨勯噰璐鍗曞彿",
+ prop: "purchaseContractNumber",
width: 160,
},
{
- label: '鍒朵綔鏃ユ湡',
- prop: 'preparedAt',
+ label: "鍒朵綔鏃ユ湡",
+ prop: "preparedAt",
width: 130,
},
{
- label: '鍒跺崟浜�',
- prop: 'preparedUserName',
+ label: "鍒跺崟浜�",
+ prop: "preparedUserName",
width: 110,
},
{
- label: '閫�鏂欎汉',
- prop: 'returnUserName',
+ label: "閫�鏂欎汉",
+ prop: "returnUserName",
width: 110,
},
-
+
{
- label: '鏁村崟鎶樻墸棰�',
- prop: 'totalDiscountAmount',
+ label: "鏁村崟鎶樻墸棰�",
+ prop: "totalDiscountAmount",
width: 120,
},
{
- label: '鏁村崟鎶樻墸鐜�',
- prop: 'totalDiscountRate',
+ label: "鏁村崟鎶樻墸鐜�",
+ prop: "totalDiscountRate",
width: 120,
},
{
- label: '鎴愪氦閲戦',
- prop: 'totalAmount',
+ label: "鎴愪氦閲戦",
+ prop: "totalAmount",
width: 120,
},
{
- label: '鍒涘缓浜�',
- prop: 'createUserName',
+ label: "鍒涘缓浜�",
+ prop: "createUserName",
width: 110,
},
{
- label: '鍒涘缓鏃堕棿',
- prop: 'createTime',
+ label: "鍒涘缓鏃堕棿",
+ prop: "createTime",
width: 170,
},
{
- label: '鏈�杩戞洿鏂版椂闂�',
- prop: 'updateTime',
+ label: "鏈�杩戞洿鏂版椂闂�",
+ prop: "updateTime",
width: 170,
},
{
- label: '澶囨敞',
- prop: 'remark',
+ label: "澶囨敞",
+ prop: "remark",
width: 180,
},
{
dataType: "action",
width: 120,
- label: "鎿嶄綔",
- align: "center",
- fixed: "right",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
operation: [
{
- name: "璇︽儏",
- type: "text",
- clickFun: row => {handleDetail(row);},
- },
+ name: "璇︽儏",
+ type: "text",
+ clickFun: (row) => {
+ handleDetail(row);
+ },
+ },
{
name: "鍒犻櫎",
- clickFun: row => {handleDelete(row)},
+ clickFun: (row) => {
+ handleDelete(row);
+ },
},
- ],
+ ],
},
-
-])
+]);
const data = reactive({
searchForm: {
- no: '',
- }
-})
-const { searchForm } = toRefs(data)
+ no: "",
+ },
+});
+const { searchForm } = toRefs(data);
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
const handleQuery = () => {
- page.current = 1
- getList()
-}
+ page.current = 1;
+ 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?.('鍒犻櫎澶辫触')
+ 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?.('宸插彇娑堝垹闄�')
-
- })
-}
+ .catch(() => {
+ // 鍙栨秷鍒犻櫎
+ proxy?.$modal?.msgInfo?.("宸插彇娑堝垹闄�");
+ });
+};
// 鏌ョ湅璇︽儏
const handleDetail = (row) => {
if (!row?.id) {
- proxy?.$modal?.msgWarning?.('鏈幏鍙栧埌鍗曟嵁ID')
- return
+ 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
- })
-}
+ 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;
- getList()
-}
+ getList();
+};
const getList = () => {
- tableLoading.value = true
- findPurchaseReturnOrderListPage({ ...searchForm.value, ...page }).then(res => {
- tableLoading.value = false
- tableData.value = res.data.records
- page.total = res.data.total
- }).catch(() => {
- tableLoading.value = false
- })
-}
+ tableLoading.value = true;
+ findPurchaseReturnOrderListPage({ ...searchForm.value, ...page })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
// 杩囨护鎺夊瓙鏁版嵁
- selectedRows.value = selection.filter(item => item.id);
-}
+ selectedRows.value = selection.filter((item) => item.id);
+};
const getReturnTypeLabel = (value) => {
- return returnTypeOptions.find(item => String(item.value) === String(value))?.label || '--'
-}
+ return (
+ returnTypeOptions.find((item) => String(item.value) === String(value))
+ ?.label || "--"
+ );
+};
const getProjectPhaseLabel = (value) => {
- return projectPhaseOptions.find(item => String(item.value) === String(value))?.label || '--'
-}
+ return (
+ projectPhaseOptions.find((item) => String(item.value) === String(value))
+ ?.label || "--"
+ );
+};
const formatAmount = (value) => {
- if (value === null || value === undefined || value === '') {
- return '--'
+ if (value === null || value === undefined || value === "") {
+ return "--";
}
- const num = Number(value)
+ const num = Number(value);
if (Number.isNaN(num)) {
- return value
+ return value;
}
- return num.toFixed(2)
-}
+ return num.toFixed(2);
+};
+
+/** 宸查��璐ф暟閲� = 鍏ュ簱琛屾�绘暟閲� 鈭� 褰撳墠鍙��璐ф暟閲忥紙鍓╀綑锛� */
+const calcAlreadyReturned = (row) => {
+ const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0);
+ const un = Number(row?.unQuantity ?? 0);
+ if (!Number.isFinite(total) || !Number.isFinite(un)) return 0;
+ return Math.max(total - un, 0);
+};
onMounted(() => {
- getList()
-})
+ getList();
+});
</script>
<style scoped>
.table_list {
- margin-top: unset;
+ margin-top: unset;
}
</style>
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
index 43425c6..1ef5b9d 100644
--- a/src/views/productionManagement/processRoute/index.vue
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -61,7 +61,9 @@
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 FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+ );
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 6d64c30..3c52410 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -47,16 +47,45 @@
<span class="info-value">{{ routeInfo.quantity || '-' }}</span>
</div>
</div>
- <div class="info-item full-width"
- v-if="routeInfo.description">
+ <div class="info-item">
<div class="info-label-wrapper">
- <span class="info-label">鎻忚堪</span>
+ <span class="info-label">澶囨敞</span>
</div>
<div class="info-value-wrapper">
<span class="info-value">{{ routeInfo.description }}</span>
</div>
</div>
</div>
+ </el-card>
+ <!-- 闄勪欢妯″潡 -->
+ <div v-if="pageType === 'order'"
+ class="section-header">
+ <div class="section-title">闄勪欢</div>
+ </div>
+ <el-card v-if="pageType === 'order'"
+ class="attachment-card"
+ shadow="hover"
+ style="margin-top: 10px; margin-bottom: 20px;">
+ <el-table :data="attachmentTableData"
+ border
+ class="attachment-table">
+ <el-table-column label="闄勪欢鍚嶇О"
+ prop="originalFilename"
+ show-overflow-tooltip />
+ <el-table-column fixed="right"
+ label="鎿嶄綔"
+ width="200"
+ align="center">
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ size="small"
+ @click="downloadAttachmentFile(scope.row.downloadURL)">
+ 涓嬭浇
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
</el-card>
<!-- 琛ㄦ牸瑙嗗浘 -->
<div v-if="viewMode === 'table'"
@@ -111,6 +140,13 @@
<el-table-column label="鍗曚綅"
prop="unit"
width="100" />
+ <el-table-column label="璁¤垂绫诲瀷"
+ prop="type"
+ width="100">
+ <template #default="scope">
+ {{scope.row.type==0 ? "璁℃椂" : "璁′欢"}}
+ </template>
+ </el-table-column>
<el-table-column label="鏄惁璐ㄦ"
prop="isQuality"
width="100">
@@ -181,12 +217,16 @@
{{ item.model }}
<!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
</div>
+ <el-tag class="product-tag"
+ :type="item.type == 1 ? 'primary' : 'success'"
+ style="margin-left: 8px;">{{ item.type==0?'璁℃椂':'璁′欢' }}</el-tag>
<el-tag type="primary"
class="product-tag"
+ style="margin-left: 8px;"
v-if="item.isQuality">璐ㄦ</el-tag>
<el-tag type="primary"
class="product-tag"
- :style="item.isQuality?'margin-left:8px':''"
+ style="margin-left: 8px;"
v-if="item.isProduction">鐢熶骇</el-tag>
</div>
<div v-else
@@ -304,7 +344,7 @@
:step="1"
controls-position="right"
style="width: 100%"
- @change="handleUnitQuantityChange(row)"
+ @change="handleUnitQuantityChange"
:disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
</el-form-item>
</template>
@@ -322,7 +362,7 @@
:step="1"
controls-position="right"
style="width: 100%"
- :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
+ :disabled="true" />
</el-form-item>
</template>
</el-table-column>
@@ -371,6 +411,18 @@
v-model="bomDataValue.showProductDialog"
:single="true"
@confirm="handleBomProduct" />
+ <!-- 涓婁紶缁勪欢寮圭獥 -->
+ <el-dialog v-model="uploadDialogVisible"
+ title="涓婁紶闄勪欢"
+ width="50%"
+ @close="closeAttachmentUpload">
+ <AttachmentUpload v-model:file-list="newFileList" />
+ <template #footer>
+ <el-button @click="saveAttachmentUpload"
+ type="primary">淇濆瓨</el-button>
+ <el-button @click="closeAttachmentUpload">鍏抽棴</el-button>
+ </template>
+ </el-dialog>
<!-- 鏂板/缂栬緫寮圭獥 -->
<el-dialog v-model="dialogVisible"
:title="operationType === 'add' ? '鏂板宸ヨ壓璺嚎椤圭洰' : '缂栬緫宸ヨ壓璺嚎椤圭洰'"
@@ -423,6 +475,13 @@
<el-form-item label="鍗曚綅"
v-else>
<span>{{ form.unit }}</span>
+ </el-form-item>
+ <el-form-item label="璁¤垂绫诲瀷"
+ prop="type">
+ <el-radio-group v-model="form.type">
+ <el-radio :label="0">璁℃椂</el-radio>
+ <el-radio :label="1">璁′欢</el-radio>
+ </el-radio-group>
</el-form-item>
<el-form-item label="鏄惁璐ㄦ"
prop="isQuality">
@@ -500,6 +559,12 @@
queryList2,
add2,
} from "@/api/productionManagement/productStructure.js";
+ import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue";
+ import {
+ attachmentList,
+ deleteAttachment,
+ createAttachment,
+ } from "@/api/basicData/storageAttachment.js";
import { useRoute } from "vue-router";
import { ElMessageBox, ElMessage } from "element-plus";
@@ -512,6 +577,7 @@
const orderId = computed(() => route.query.orderId);
const pageType = computed(() => route.query.type);
const editable = computed(() => route.query.editable !== "false");
+ const technologyRoutingId = computed(() => route.query.technologyRoutingId);
const tableLoading = ref(false);
const tableData = ref([]);
@@ -530,7 +596,66 @@
bomNo: "",
description: "",
quantity: 0,
+ technologyRoutingId: "",
});
+
+ // 闄勪欢鐩稿叧
+ const attachmentTableData = ref([]);
+ const uploadDialogVisible = ref(false);
+ const newFileList = ref([]);
+
+ const getAttachmentList = () => {
+ if (!technologyRoutingId.value) return;
+ attachmentList({
+ recordType: "technology_routing",
+ recordId: technologyRoutingId.value,
+ }).then(res => {
+ attachmentTableData.value = (res && res.data) || [];
+ });
+ };
+
+ const handleUploadAttachment = () => {
+ uploadDialogVisible.value = true;
+ };
+
+ const saveAttachmentUpload = async () => {
+ if (newFileList.value.length > 0) {
+ createAttachment({
+ application: "file",
+ recordType: "technology_routing",
+ recordId: technologyRoutingId.value,
+ storageBlobDTOs: [...newFileList.value, ...attachmentTableData.value],
+ })
+ .then(res => {
+ if (res && res.code === 200) {
+ proxy?.$modal?.msgSuccess("涓婁紶鎴愬姛");
+ newFileList.value = [];
+ getAttachmentList();
+ }
+ })
+ .finally(() => {
+ uploadDialogVisible.value = false;
+ });
+ }
+ };
+
+ const closeAttachmentUpload = () => {
+ newFileList.value = [];
+ uploadDialogVisible.value = false;
+ };
+
+ const handleDeleteAttachment = async row => {
+ deleteAttachment([row.storageAttachmentId]).then(res => {
+ if (res && res.code === 200) {
+ proxy?.$modal?.msgSuccess("鍒犻櫎鎴愬姛");
+ getAttachmentList();
+ }
+ });
+ };
+
+ const downloadAttachmentFile = url => {
+ window.open(url, "_blank");
+ };
const processOptions = ref([]);
const showProductSelectDialog = ref(false);
@@ -558,6 +683,7 @@
model: "",
unit: "",
isQuality: false,
+ type: 0,
isProduction: false,
});
@@ -661,6 +787,7 @@
bomId: route.query.bomId || "",
description: route.query.description || "",
quantity: route.query.quantity || 0,
+ technologyRoutingId: route.query.technologyRoutingId || "",
status: !(route.query.status == 1 || route.query.status === "false"),
};
bomTableData.value[0].productName = routeInfo.value.productName;
@@ -687,6 +814,7 @@
model: row.model || "",
unit: row.unit || "",
isQuality: row.isQuality,
+ type: row.type || 0,
isProduction: row.isProduction,
};
dialogVisible.value = true;
@@ -758,6 +886,7 @@
operationName: getProcessName(form.value.technologyOperationId),
productModelId: form.value.productModelId,
isQuality: form.value.isQuality,
+ type: form.value.type,
isProduction: form.value.isProduction,
dragSort,
})
@@ -766,6 +895,7 @@
technologyOperationId: form.value.technologyOperationId,
productModelId: form.value.productModelId,
isQuality: form.value.isQuality,
+ type: form.value.type,
isProduction: form.value.isProduction,
dragSort,
});
@@ -793,6 +923,7 @@
operationName: getProcessName(form.value.technologyOperationId),
productModelId: form.value.productModelId,
isQuality: form.value.isQuality,
+ type: form.value.type,
isProduction: form.value.isProduction,
})
: addOrUpdateProcessRouteItem1({
@@ -801,6 +932,7 @@
productModelId: form.value.productModelId,
id: form.value.id,
isQuality: form.value.isQuality,
+ type: form.value.type,
isProduction: form.value.isProduction,
});
@@ -832,6 +964,7 @@
model: "",
unit: "",
isQuality: false,
+ type: 0,
isProduction: false,
};
formRef.value?.resetFields();
@@ -1064,18 +1197,97 @@
}
});
};
+
+ const toQuantityNumber = value => {
+ const numberValue = Number(value);
+ if (!Number.isFinite(numberValue)) {
+ return 0;
+ }
+ return Number(numberValue.toFixed(2));
+ };
+
+ const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => {
+ items.forEach(item => {
+ if (parentDemandedQuantity !== null) {
+ item.demandedQuantity = toQuantityNumber(
+ parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
+ );
+ }
+
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ syncDemandedQuantityTree(
+ item.children,
+ toQuantityNumber(item.demandedQuantity)
+ );
+ }
+ });
+ };
+
+ const recalculateDemandedQuantities = () => {
+ if (pageType.value !== "order") {
+ return;
+ }
+
+ const rootDemandedQuantity = routeInfo.value.quantity;
+ if (
+ rootDemandedQuantity === undefined ||
+ rootDemandedQuantity === null ||
+ rootDemandedQuantity === ""
+ ) {
+ syncDemandedQuantityTree(bomDataValue.value.dataList);
+ return;
+ }
+
+ syncDemandedQuantityTree(
+ bomDataValue.value.dataList,
+ toQuantityNumber(rootDemandedQuantity)
+ );
+ };
+
const processChange = value => {
processOptions.value.forEach(item => {
if (item.id == value) {
form.value.isQuality = item.isQuality;
+ form.value.type = item.type || 0;
form.value.isProduction = item.isProduction;
}
});
};
+ const findSiblings = (items, tempId) => {
+ if (!items || items.length === 0) return null;
+ // 妫�鏌ュ綋鍓嶅眰绾�
+ if (items.some(item => item.tempId === tempId)) {
+ return items;
+ }
+ // 閫掑綊鏌ユ壘瀛愮骇
+ for (const item of items) {
+ if (item.children && item.children.length > 0) {
+ const result = findSiblings(item.children, tempId);
+ if (result) return result;
+ }
+ }
+ return null;
+ };
+
const handleBomProcessChange = (row, value) => {
row.processId = value || "";
syncProcessOperationFields(row);
+
+ // 妫�鏌ュ悓涓�灞傜骇鏄惁宸茬粡鏈夊叾浠栦笉鍚岀殑宸ュ簭琚�変腑
+ const siblings = findSiblings(bomDataValue.value.dataList, row.tempId);
+ if (siblings && value) {
+ const hasDifferentProcess = siblings.some(sibling => {
+ return (
+ sibling.tempId !== row.tempId &&
+ sibling.processId &&
+ sibling.processId !== value
+ );
+ });
+ if (hasDifferentProcess) {
+ ElMessage.warning("鍚屼竴灞傜骇宸插瓨鍦ㄤ笉鍚岀殑宸ュ簭锛岃鍏堢粺涓�宸ュ簭鍚庡啀杩涜淇敼");
+ }
+ }
};
const openBomDialog = tempId => {
@@ -1091,6 +1303,7 @@
);
bomDataValue.value.dataList = data || [];
normalizeTreeData(bomDataValue.value.dataList);
+ recalculateDemandedQuantities();
} catch (err) {
console.error("鑾峰彇BOM鏁版嵁澶辫触锛�", err);
}
@@ -1186,10 +1399,8 @@
});
};
- const handleUnitQuantityChange = row => {
- if (routeInfo.value.quantity && routeInfo.value.quantity !== 0) {
- row.demandedQuantity = (row.unitQuantity || 0) * routeInfo.value.quantity;
- }
+ const handleUnitQuantityChange = () => {
+ recalculateDemandedQuantities();
};
const addchildItem = (item, tempId) => {
@@ -1210,14 +1421,12 @@
"",
operationName: "",
unitQuantity: 1,
- demandedQuantity:
- routeInfo.value.quantity && routeInfo.value.quantity !== 0
- ? 1 * routeInfo.value.quantity
- : 0,
+ demandedQuantity: 0,
children: [],
unit: "",
tempId: new Date().getTime(),
});
+ recalculateDemandedQuantities();
return true;
}
if (item.children && item.children.length > 0) {
@@ -1249,14 +1458,12 @@
"",
operationName: "",
unitQuantity: 1,
- demandedQuantity:
- routeInfo.value.quantity && routeInfo.value.quantity !== 0
- ? 1 * routeInfo.value.quantity
- : 0,
+ demandedQuantity: 0,
unit: "",
children: [],
tempId: new Date().getTime(),
});
+ recalculateDemandedQuantities();
return;
}
addchildItem(item, tempId);
@@ -1296,9 +1503,36 @@
}
};
+ // 鏍¢獙鍚屼竴灞傜骇鐨勫伐搴忔槸鍚︿竴鑷�
+ const validateProcessConsistency = items => {
+ if (!items || items.length === 0) return;
+
+ // 妫�鏌ュ綋鍓嶅眰绾�
+ const processes = items
+ .filter(item => item.processId)
+ .map(item => item.processId);
+ if (processes.length > 1) {
+ const uniqueProcesses = [...new Set(processes)];
+ if (uniqueProcesses.length > 1) {
+ ElMessage.error("鍚屼竴灞傜骇鐨勫伐搴忓繀椤讳竴鑷�");
+ isValid = false;
+ return;
+ }
+ }
+
+ // 閫掑綊妫�鏌ュ瓙绾�
+ items.forEach(item => {
+ if (item.children && item.children.length > 0) {
+ validateProcessConsistency(item.children);
+ }
+ });
+ };
+
bomDataValue.value.dataList.forEach(item => {
validateItem(item, true);
});
+
+ validateProcessConsistency(bomDataValue.value.dataList);
return isValid;
};
@@ -1324,6 +1558,7 @@
console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList");
normalizeTreeData(bomDataValue.value.dataList);
+ recalculateDemandedQuantities();
const valid = validateAllBom();
if (valid) {
@@ -1335,7 +1570,7 @@
.then(() => {
ElMessage.success("BOM淇濆瓨鎴愬姛");
bomDataValue.value.isEdit = false;
- fetchBomData();
+ refreshCurrentPage();
})
.catch(() => {
ElMessage.error("BOM淇濆瓨澶辫触");
@@ -1348,11 +1583,18 @@
}
};
- onMounted(() => {
+ const refreshCurrentPage = () => {
getRouteInfo();
getList();
getProcessList();
fetchBomData();
+ if (pageType.value === "order") {
+ getAttachmentList();
+ }
+ };
+
+ onMounted(() => {
+ refreshCurrentPage();
});
onUnmounted(() => {
@@ -1619,4 +1861,4 @@
line-height: 1.5;
word-break: break-all;
}
-</style>
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
index 750d584..5cb08e8 100644
--- a/src/views/productionManagement/productStructure/Detail/index.vue
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -86,6 +86,7 @@
:step="1"
controls-position="right"
style="width: 100%"
+ @change="handleUnitQuantityChange"
:disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
</el-form-item>
</template>
@@ -103,7 +104,7 @@
:step="1"
controls-position="right"
style="width: 100%"
- :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
+ :disabled="true" />
</el-form-item>
</template>
</el-table-column>
@@ -268,6 +269,42 @@
});
};
+ const toQuantityNumber = (value: any) => {
+ const numberValue = Number(value);
+ if (!Number.isFinite(numberValue)) {
+ return 0;
+ }
+ return Number(numberValue.toFixed(2));
+ };
+
+ const syncDemandedQuantityTree = (
+ items: any[],
+ parentDemandedQuantity: number | null = null
+ ) => {
+ items.forEach((item: any) => {
+ if (parentDemandedQuantity !== null) {
+ item.demandedQuantity = toQuantityNumber(
+ parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
+ );
+ }
+
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ syncDemandedQuantityTree(
+ item.children,
+ toQuantityNumber(item.demandedQuantity)
+ );
+ }
+ });
+ };
+
+ const recalculateDemandedQuantities = () => {
+ if (!isOrderPage.value) {
+ return;
+ }
+
+ syncDemandedQuantityTree(dataValue.dataList);
+ };
+
const buildSubmitTree = (items: any[]) => {
return items.map((item: any) => {
const current = { ...item };
@@ -279,9 +316,40 @@
});
};
+ const findSiblings = (items: any[], tempId: string): any[] | null => {
+ if (!items || items.length === 0) return null;
+ // 妫�鏌ュ綋鍓嶅眰绾�
+ if (items.some(item => item.tempId === tempId)) {
+ return items;
+ }
+ // 閫掑綊鏌ユ壘瀛愮骇
+ for (const item of items) {
+ if (item.children && item.children.length > 0) {
+ const result = findSiblings(item.children, tempId);
+ if (result) return result;
+ }
+ }
+ return null;
+ };
+
const handleProcessChange = (row: any, value: any) => {
row.processId = value || "";
syncProcessOperationFields(row);
+
+ // 妫�鏌ュ悓涓�灞傜骇鏄惁宸茬粡鏈夊叾浠栦笉鍚岀殑宸ュ簭琚�変腑
+ const siblings = findSiblings(dataValue.dataList, row.tempId);
+ if (siblings && value) {
+ const hasDifferentProcess = siblings.some(sibling => {
+ return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value;
+ });
+ if (hasDifferentProcess) {
+ ElMessage.warning("鍚屼竴灞傜骇宸插瓨鍦ㄤ笉鍚岀殑宸ュ簭锛岃鍏堢粺涓�宸ュ簭鍚庡啀杩涜淇敼");
+ }
+ }
+ };
+
+ const handleUnitQuantityChange = () => {
+ recalculateDemandedQuantities();
};
const tableData = reactive([
@@ -304,6 +372,7 @@
const { data } = await listProcessBom({ orderId: routeOrderId.value });
dataValue.dataList = (data as any) || [];
normalizeTreeData(dataValue.dataList);
+ recalculateDemandedQuantities();
} else {
// 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
const { data } = await queryList(routeId.value);
@@ -389,8 +458,41 @@
const validateAll = () => {
let isValid = true;
+ // 鏍¢獙涓�缁勫厔寮熻妭鐐圭殑宸ュ簭鏄惁閮界浉鍚�
+ const checkProcessUniqueness = (items: any[]) => {
+ if (!items || items.length === 0 || !isValid) return;
+
+ // 鑾峰彇绗竴涓潪绌虹殑宸ュ簭ID浣滀负鍙傝��
+ const firstProcessId = items.find(item => item.processId)?.processId;
+
+ // 濡傛灉鏈夊伐搴廔D锛屾鏌ユ墍鏈夐」鏄惁閮戒娇鐢ㄧ浉鍚岀殑宸ュ簭
+ if (firstProcessId) {
+ for (const item of items) {
+ if (item.processId && item.processId !== firstProcessId) {
+ const option1 = getProcessOptionById(firstProcessId);
+ const option2 = getProcessOptionById(item.processId);
+ const processName1 = option1?.name || "鏈煡宸ュ簭";
+ const processName2 = option2?.name || "鏈煡宸ュ簭";
+ ElMessage.error(
+ `褰撳墠灞傜骇涓嬪伐搴忎笉涓�鑷达紝璇蜂娇鐢ㄧ浉鍚岀殑宸ュ簭銆傚瓨鍦ㄣ��${processName1}銆嶅拰銆�${processName2}銆峘
+ );
+ isValid = false;
+ return;
+ }
+ }
+ }
+
+ // 閫掑綊鏍¢獙瀛愮骇鐨勫厔寮熻妭鐐�
+ for (const item of items) {
+ if (item.children && item.children.length > 0) {
+ checkProcessUniqueness(item.children);
+ }
+ }
+ };
+
// 鏍¢獙鍑芥暟
const validateItem = (item: any, isTopLevel = false) => {
+ if (!isValid) return;
// 鏍¢獙褰撳墠椤圭殑蹇呭~瀛楁
if (!item.model) {
ElMessage.error("璇烽�夋嫨瑙勬牸");
@@ -418,7 +520,7 @@
// return;
// }
- // 閫掑綊鏍¢獙瀛愰」
+ // 閫掑綊鏍¢獙瀛愰」瀛楁
if (item.children && item.children.length > 0) {
item.children.forEach(child => {
validateItem(child, false);
@@ -426,7 +528,11 @@
}
};
- // 閬嶅巻鎵�鏈夐《灞傞」
+ // 1. 棣栧厛鏍¢獙鍚屼竴鐖剁骇涓嬬殑鍚屽眰娑堣�楀伐搴忔槸鍚﹀敮涓�
+ checkProcessUniqueness(dataValue.dataList);
+ if (!isValid) return false;
+
+ // 2. 鐒跺悗閬嶅巻鏍¢獙鎵�鏈夐《灞傞」鐨勫瓧娈靛繀濉儏鍐�
dataValue.dataList.forEach(item => {
validateItem(item, true);
});
@@ -437,6 +543,7 @@
const submit = () => {
dataValue.loading = true;
normalizeTreeData(dataValue.dataList);
+ recalculateDemandedQuantities();
// 鍏堣繘琛岃〃鍗曟牎楠�
const valid = validateAll();
@@ -514,6 +621,7 @@
tempId: new Date().getTime(),
});
+ recalculateDemandedQuantities();
return;
}
addchildItem(item, tempId);
@@ -542,6 +650,7 @@
unit: "",
tempId: new Date().getTime(),
});
+ recalculateDemandedQuantities();
return true;
}
if (item.children && item.children.length > 0) {
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
index f438518..3e79b93 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -125,6 +125,11 @@
minWidth: 100,
},
{
+ label: "宸ユ椂锛坔锛�",
+ prop: "workHour",
+ minWidth: 100,
+ },
+ {
label: "鐢熶骇鏁伴噺",
prop: "quantity",
minWidth: 100,
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
index 210dbba..09e7421 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -111,6 +111,7 @@
<span class="dialog-footer">
<el-button type="primary"
:loading="materialSaving"
+ :disabled="isSaveDisabled"
@click="handleMaterialSave">淇濆瓨</el-button>
<el-button @click="dialogVisible = false">鍙栨秷</el-button>
</span>
@@ -155,6 +156,33 @@
const materialTableLoading = ref(false);
const materialSaving = ref(false);
const materialTableData = ref([]);
+
+ const isSaveDisabled = computed(() => {
+ if (materialTableData.value.length === 0) return true;
+ return !materialTableData.value.some(row => {
+ // 妫�鏌ユ槸鍚︽湁浠讳綍鐢ㄦ埛杈撳叆鍐呭
+ const hasBatch = Array.isArray(row.batchNo) && row.batchNo.length > 0;
+ const hasPickQty =
+ row.pickQty !== null && row.pickQty !== undefined && row.pickQty !== 0;
+
+ if (row.bom) {
+ // 瀵逛簬鏉ヨ嚜BOM鐨勮锛岃緭鍏ユ鍙湁鈥滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
+ return hasBatch || hasPickQty;
+ } else {
+ // 瀵逛簬鏂板琛岋紝杈撳叆妗嗗寘鎷�滃伐搴忊�濄�佲�滃師鏂欌�濄�佲�滈渶姹傛暟閲忊�濄�佲�滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
+ const hasOperation = !!row.operationName;
+ const hasMaterial = !!row.materialName;
+ const hasDemanded =
+ row.demandedQuantity !== null &&
+ row.demandedQuantity !== undefined &&
+ row.demandedQuantity !== 0;
+ return (
+ hasBatch || hasPickQty || hasOperation || hasMaterial || hasDemanded
+ );
+ }
+ });
+ });
+
const processOptions = ref([]);
const currentMaterialSelectRowIndex = ref(-1);
let materialTempId = 0;
@@ -312,12 +340,20 @@
!item.materialName ||
(Number(item.pickQty) > 0 &&
(!item.batchNo || item.batchNo.length === 0)) ||
+ (item.batchNo && item.batchNo.length > 0 && Number(item.pickQty) <= 0) ||
item.demandedQuantity === null ||
item.demandedQuantity === undefined ||
item.pickQty === null ||
item.pickQty === undefined
);
if (invalidRow) {
+ if (
+ invalidRow.batchNo &&
+ invalidRow.batchNo.length > 0 &&
+ Number(invalidRow.pickQty) <= 0
+ ) {
+ return { valid: false, message: "閫夋嫨浜嗘壒鍙锋椂锛岄鐢ㄦ暟閲忓繀椤诲ぇ浜庨浂" };
+ }
return { valid: false, message: "璇峰畬鍠勫伐搴忋�佸師鏂欍�佹壒鍙峰拰鏁伴噺鍚庡啀淇濆瓨" };
}
return { valid: true, message: "" };
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 8d44b85..9c7682b 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -570,7 +570,7 @@
const openBindRouteDialog = async (row, type) => {
bindForm.orderId = row.id;
- bindForm.routeId = type === "add" ? null : row.processRouteCode;
+ bindForm.routeId = type === "add" ? null : row.technologyRoutingId;
bindRouteDialogVisible.value = true;
routeOptions.value = [];
if (!row.productModelId) {
@@ -723,6 +723,7 @@
bomNo: row.bomNo || "",
description: data.description || "",
quantity: row.quantity || 0,
+ technologyRoutingId: data.technologyRoutingId,
orderId,
type: "order",
editable: !row.endOrder,
@@ -810,9 +811,9 @@
})
.then(() => {
proxy.download(
- "/productOrder/export",
+ "/productionOrder/export",
{ ...searchForm.value },
- "鐢熶骇璁㈠崟.xlsx"
+ "鐢熶骇璁㈠崟鏁版嵁.xlsx"
);
})
.catch(() => {
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
index 216e73b..ee49657 100644
--- a/src/views/productionManagement/productionProcess/index.vue
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -44,7 +44,7 @@
<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 class="process-device">鍏宠仈璁惧: {{ (deviceOptions.find(item => item.id === Number(process.deviceLedgerId))?.deviceName) || '鏈叧鑱�' }}</div>
</div>
<div class="card-footer">
<div class="status-tag">
@@ -162,7 +162,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
- <el-button type="primary" @click="handleProcessSubmit">纭畾</el-button>
+ <el-button type="primary"
+ @click="handleProcessSubmit">纭畾</el-button>
<el-button @click="processDialogVisible = false">鍙栨秷</el-button>
</span>
</template>
@@ -242,6 +243,7 @@
</el-form-item>
<el-form-item label="鏍囧噯鍊�">
<el-input v-model="selectedParam.standardValue"
+ @input="val => onStandardValueInput(val, selectedParam)"
placeholder="璇疯緭鍏ラ粯璁ゅ��" />
</el-form-item>
</el-form>
@@ -251,7 +253,9 @@
</div>
<template #footer>
<span class="dialog-footer">
- <el-button type="primary" :disabled="!selectedParam" @click="handleParamSubmit">纭畾</el-button>
+ <el-button type="primary"
+ :disabled="!selectedParam"
+ @click="handleParamSubmit">纭畾</el-button>
<el-button @click="paramDialogVisible = false">鍙栨秷</el-button>
</span>
</template>
@@ -270,14 +274,15 @@
<el-form-item label="鏍囧噯鍊�"
prop="standardValue">
<el-input v-model="editParamForm.standardValue"
+ @input="val => onStandardValueInput(val, editParamForm)"
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>
+ <el-button @click="editParamDialogVisible = false">鍙栨秷</el-button>
</span>
</template>
</el-dialog>
@@ -389,7 +394,18 @@
technologyParamId: null,
paramName: "",
standardValue: null,
+ paramType: null,
});
+
+ const onStandardValueInput = (val, target) => {
+ const data = target.value || target;
+ const type = data.paramType;
+ if (type === 1) {
+ // 鏁板�兼牸寮忥細涓嶈兘杈撳叆涓枃鎴栬嫳鏂囧瓧绗�
+ data.standardValue = val.replace(/[a-zA-Z\u4e00-\u9fa5]/g, "");
+ }
+ };
+
const editParamRules = {
standardValue: [
{
@@ -400,6 +416,12 @@
if (value === null || value === undefined || value === "") {
callback(new Error("璇疯緭鍏ユ爣鍑嗗��"));
} else {
+ const type = editParamForm.paramType;
+ if (type === 1 && value) {
+ if (/[a-zA-Z\u4e00-\u9fa5]/.test(value)) {
+ return callback(new Error("鏁板�兼牸寮忎笉鑳藉寘鍚腑鑻辨枃瀛楃"));
+ }
+ }
callback();
}
},
@@ -548,7 +570,10 @@
processForm.isQuality = !!process.isQuality;
processForm.isProduction = !!process.isProduction;
processForm.remark = process.remark || "";
- processForm.deviceLedgerId = Number(process.deviceLedgerId);
+ // 濡傛灉璁惧 ID 涓� 0 鎴栬�呭湪璁惧鍒楄〃涓壘涓嶅埌锛屽垯鍥炴樉涓虹┖锛坣ull锛�
+ const deviceId = Number(process.deviceLedgerId);
+ const hasDevice = deviceOptions.value.some(item => item.id === deviceId);
+ processForm.deviceLedgerId = deviceId && hasDevice ? deviceId : null;
processForm.type = process.type;
processDialogVisible.value = true;
};
@@ -714,6 +739,7 @@
editParamForm.technologyParamId = row.technologyParamId;
editParamForm.paramName = row.paramName;
editParamForm.standardValue = row.standardValue;
+ editParamForm.paramType = row.paramType;
editParamDialogVisible.value = true;
};
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index aff050f..fa4e163 100644
--- a/src/views/productionManagement/productionReporting/index.vue
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -187,6 +187,11 @@
width: 120,
},
{
+ label: "宸ユ椂锛坔锛�",
+ width: 100,
+ prop: "workHour",
+ },
+ {
label: "宸ュ簭",
prop: "process",
width: 120,
diff --git a/src/views/productionManagement/productionTraceability/index.vue b/src/views/productionManagement/productionTraceability/index.vue
index a850788..b9d37a1 100644
--- a/src/views/productionManagement/productionTraceability/index.vue
+++ b/src/views/productionManagement/productionTraceability/index.vue
@@ -1,5 +1,7 @@
<template>
<div class="app-container">
+ <PageHeader v-if="showHeader"
+ content="鐢熶骇璁㈠崟" />
<el-card style="height:82vh;overflow:auto;">
<template #header>
<div class="card-header">
@@ -78,11 +80,17 @@
{{ row.workOrder.model || '-' }}
</template>
</el-table-column>
+ <el-table-column label="宸ュ簭"
+ prop="workOrder.operationName"
+ align="center" />
<el-table-column prop="workOrder.planQuantity"
label="闇�姹傛暟閲�"
align="center" />
<el-table-column prop="workOrder.completeQuantity"
label="瀹屾垚鏁伴噺"
+ align="center" />
+ <el-table-column prop="workOrder.scrapQty"
+ label="鎶ュ簾鏁伴噺"
align="center" />
<el-table-column prop="workOrder.completionStatus"
label="瀹屾垚杩涘害"
@@ -152,6 +160,15 @@
{{ parseTime(row.createTime) }}
</template>
</el-table-column>
+ <el-table-column label="宸ユ椂锛坔锛�"
+ prop="workHour"
+ align="center" />
+ <el-table-column label="浜у嚭鏁伴噺"
+ prop="quantity"
+ align="center" />
+ <el-table-column label="鎶ュ簾鏁伴噺"
+ prop="scrapQty"
+ align="center" />
<el-table-column label="鎿嶄綔"
align="center"
width="200">
@@ -267,7 +284,7 @@
</template>
<script setup>
- import { ref, reactive, onMounted } from "vue";
+ import { ref, reactive, onMounted, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { parseTime } from "@/utils/ruoyi";
@@ -279,6 +296,13 @@
const route = useRoute();
const router = useRouter();
+
+ // 鍒ゆ柇鏄惁鏄剧ず椤靛ご
+ const showHeader = computed(() => {
+ return (
+ Object.keys(route.query).length > 0 || Object.keys(route.params).length > 0
+ );
+ });
// 鎼滅储鐩稿叧
const searchForm = reactive({
@@ -385,6 +409,7 @@
workOrder: row.workOrder || {},
reports: (row.reportList || []).map(r => ({
...r.reportMain,
+ ...(r.reportOutputList[0] || {}),
productionOperationParamList: r.reportParamList || [],
})),
};
diff --git a/src/views/productionManagement/workOrder/components/filesDia.vue b/src/views/productionManagement/workOrder/components/filesDia.vue
index ca6d7a9..1336861 100644
--- a/src/views/productionManagement/workOrder/components/filesDia.vue
+++ b/src/views/productionManagement/workOrder/components/filesDia.vue
@@ -78,7 +78,7 @@
name: "涓嬭浇",
type: "text",
clickFun: row => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
},
},
{
diff --git a/src/views/productionManagement/workOrderEdit/index.vue b/src/views/productionManagement/workOrderEdit/index.vue
index 1cde5ea..64fd087 100644
--- a/src/views/productionManagement/workOrderEdit/index.vue
+++ b/src/views/productionManagement/workOrderEdit/index.vue
@@ -111,9 +111,9 @@
</div>
<template #footer>
<span class="dialog-footer">
- <el-button @click="assignReporterDialogVisible = false">鍙栨秷</el-button>
<el-button type="primary"
@click="handleSaveReporters">纭畾</el-button>
+ <el-button @click="assignReporterDialogVisible = false">鍙栨秷</el-button>
</span>
</template>
</el-dialog>
diff --git a/src/views/productionManagement/workOrderManagement/components/filesDia.vue b/src/views/productionManagement/workOrderManagement/components/filesDia.vue
index 7f038f7..84d8bd4 100644
--- a/src/views/productionManagement/workOrderManagement/components/filesDia.vue
+++ b/src/views/productionManagement/workOrderManagement/components/filesDia.vue
@@ -78,7 +78,7 @@
name: "涓嬭浇",
type: "text",
clickFun: row => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
},
},
{
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 600b274..60fe511 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -161,6 +161,17 @@
:value="user.userId" />
</el-select>
</el-form-item>
+ <!-- 宸ユ椂 -->
+ <el-form-item label="宸ユ椂"
+ v-if="currentReportRowData?.type == 0"
+ prop="workHour">
+ <el-input v-model.number="reportForm.workHour"
+ type="number"
+ min="0"
+ style="width: 280px"
+ placeholder="璇疯緭鍏ュ伐鏃�" /><span style="margin-left:10px"
+ class="param-unit">h</span>
+ </el-form-item>
<div v-if="params.length > 0"
class="param-grid"
v-loading="paramLoading">
@@ -259,6 +270,7 @@
addProductMain,
downProductWorkOrder,
} from "@/api/productionManagement/workOrder.js";
+ import { listMaterialPickingDetail } from "@/api/productionManagement/productionOrder.js";
import { findProcessParamListOrder } from "@/api/productionManagement/productProcessRoute.js";
import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
import { getDicts } from "@/api/system/dict/data";
@@ -418,6 +430,7 @@
productMainId: null,
productionOrderRoutingOperationId: "",
productionOrderId: "",
+ workHour: 0,
paramGroups: {},
});
@@ -644,9 +657,26 @@
fileDialogVisible.value = true;
};
- const showReportDialog = row => {
+ const showReportDialog = async row => {
+ if (row.productionOrderId) {
+ try {
+ const res = await listMaterialPickingDetail(row.productionOrderId);
+ const records = Array.isArray(res.data)
+ ? res.data
+ : res.data?.records || [];
+ if (res.code === 200 && records.length === 0) {
+ proxy.$modal.msgError("鏈鏂欐棤娉曟姤宸�");
+ return;
+ }
+ } catch (error) {
+ console.error("鏌ヨ棰嗘枡璇︽儏澶辫触:", error);
+ }
+ }
currentReportRowData.value = row;
- reportForm.planQuantity = row.planQuantity;
+ const planQuantity = Number(row.planQuantity || 0);
+ const completeQuantity = Number(row.completeQuantity || 0);
+ const remainingQuantity = Math.max(0, planQuantity - completeQuantity);
+ reportForm.planQuantity = remainingQuantity;
reportForm.quantity =
row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
@@ -658,6 +688,11 @@
reportForm.productionOrderRoutingOperationId =
row.productionOrderRoutingOperationId;
reportForm.productionOrderId = row.productionOrderId;
+ if (row.type == 0) {
+ reportForm.workHour = row.workHour || 0;
+ } else {
+ reportForm.workHour = 0;
+ }
nextTick(() => {
reportFormRef.value?.clearValidate();
if (row.productionOrderRoutingOperationId && row.productionOrderId) {
@@ -757,6 +792,7 @@
productionOrderRoutingOperationId:
reportForm.productionOrderRoutingOperationId,
productionOrderId: reportForm.productionOrderId,
+ workHour: reportForm.workHour,
productionOperationParamList: productionOperationParamList,
};
diff --git a/src/views/productionPlan/productionPlan/index.vue b/src/views/productionPlan/productionPlan/index.vue
index 46d0548..01a513b 100644
--- a/src/views/productionPlan/productionPlan/index.vue
+++ b/src/views/productionPlan/productionPlan/index.vue
@@ -13,6 +13,14 @@
style="width: 160px;"
@keyup.enter="handleQuery" />
</el-form-item>
+ <el-form-item label="閿�鍞悎鍚屽彿:"
+ prop="salesContractNo">
+ <el-input v-model="searchForm.salesContractNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 160px;"
+ @keyup.enter="handleQuery" />
+ </el-form-item>
<el-form-item label="闇�姹傛棩鏈熻寖鍥�:"
prop="dateRange">
<el-date-picker v-model="searchForm.dateRange"
@@ -134,7 +142,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
- <el-button type="primary" @click="handleMergeSubmit">纭畾涓嬪彂</el-button>
+ <el-button type="primary"
+ @click="handleMergeSubmit">纭畾涓嬪彂</el-button>
<el-button @click="isShowNewModal = false">鍙栨秷</el-button>
</span>
</template>
@@ -227,7 +236,8 @@
</el-form>
<template #footer>
<span class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ <el-button type="primary"
+ @click="handleSubmit">纭畾</el-button>
<el-button @click="dialogVisible = false">鍙栨秷</el-button>
</span>
</template>
@@ -256,6 +266,7 @@
productionPlanUpdate,
productionPlanDelete,
productionPlanCombine,
+ exportProductionPlan,
} from "@/api/productionPlan/productionPlan.js";
import { productTreeList, modelListPage } from "@/api/basicData/product.js";
import PIMTable from "./components/PIMTable.vue";
@@ -267,11 +278,6 @@
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 => {
@@ -591,6 +597,7 @@
const data = reactive({
searchForm: {
mpsNo: "",
+ salesContractNo: "",
productName: "",
model: "",
status: "",
@@ -619,6 +626,7 @@
}
Object.assign(searchForm.value, {
mpsNo: "",
+ salesContractNo: "",
productName: "",
model: "",
status: "",
diff --git a/src/views/projectManagement/Management/components/formDia.vue b/src/views/projectManagement/Management/components/formDia.vue
index c2ee9c2..0db3efe 100644
--- a/src/views/projectManagement/Management/components/formDia.vue
+++ b/src/views/projectManagement/Management/components/formDia.vue
@@ -1342,9 +1342,9 @@
}
function downloadAttachment(att) {
- if (att?.name) {
+ if (att) {
try {
- proxy.$download.name(att.url);
+ proxy.$download.byUrl(att.url, att.originalFilename);
return
} catch (e) {}
}
diff --git a/src/views/projectManagement/Management/projectDetail.vue b/src/views/projectManagement/Management/projectDetail.vue
index b526fc6..be670cb 100644
--- a/src/views/projectManagement/Management/projectDetail.vue
+++ b/src/views/projectManagement/Management/projectDetail.vue
@@ -364,16 +364,10 @@
}
}
-function downloadAttachment(att) {
- if (att?.url) {
+function downloadAttachment(row) {
+ if (row?.url) {
try {
- proxy.$download.resource(att.url)
- return
- } catch (e) {}
- }
- if (att?.name) {
- try {
- proxy.$download.name(att.name, false)
+ proxy.$download.byUrl(row.url, row.originalFilename);
return
} catch (e) {}
}
diff --git a/src/views/projectManagement/projectType/index.vue b/src/views/projectManagement/projectType/index.vue
index 16a17e0..65cc916 100644
--- a/src/views/projectManagement/projectType/index.vue
+++ b/src/views/projectManagement/projectType/index.vue
@@ -250,13 +250,8 @@
}
/** 涓嬭浇闄勪欢 */
-function handleDownload(attachment) {
- const url = attachment?.url || attachment?.fileUrl || attachment?.tempPath || attachment?.fileName;
- if (!url) {
- ElMessage.warning("鏈壘鍒板彲涓嬭浇鐨勬枃浠跺湴鍧�");
- return;
- }
- proxy.$download.name(url);
+function handleDownload(row) {
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
onMounted(() => {
diff --git a/src/views/qualityManagement/finalInspection/components/filesDia.vue b/src/views/qualityManagement/finalInspection/components/filesDia.vue
index 8cda761..4517844 100644
--- a/src/views/qualityManagement/finalInspection/components/filesDia.vue
+++ b/src/views/qualityManagement/finalInspection/components/filesDia.vue
@@ -120,7 +120,7 @@
};
// 涓嬭浇闄勪欢
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
// 鍏抽棴寮规
const closeDia = () => {
diff --git a/src/views/qualityManagement/finalInspection/components/formDia.vue b/src/views/qualityManagement/finalInspection/components/formDia.vue
index 5f4c975..10bfad9 100644
--- a/src/views/qualityManagement/finalInspection/components/formDia.vue
+++ b/src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -2,7 +2,7 @@
<div>
<el-dialog
v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板鍑哄巶妫�楠�' : '缂栬緫鍑哄巶妫�楠�'"
+ :title="operationType === 'add' ? '鏂板鍑哄巶妫�楠�' : operationType === 'view' ? '鏌ョ湅鍑哄巶妫�楠�' : '缂栬緫鍑哄巶妫�楠�'"
width="70%"
@close="closeDia"
>
@@ -18,19 +18,21 @@
@change="getModels"
:data="productOptions"
:render-after-expand="false"
- :disabled="operationType === 'edit'"
+ :disabled="isViewMode || operationType === 'edit'"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
- <el-select v-model="form.productModelId" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'edit'"
+ <el-select v-model="form.productModelId" placeholder="璇烽�夋嫨" clearable :disabled="isViewMode || operationType === 'edit'"
filterable readonly @change="handleChangeModel">
<el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
+ </el-row>
+ <el-row :gutter="30">
<el-col :span="12">
<el-form-item label="鎸囨爣閫夋嫨锛�" prop="testStandardId">
<el-select
@@ -39,6 +41,7 @@
clearable
@change="handleTestStandardChange"
style="width: 100%"
+ :disabled="isViewMode"
>
<el-option
v-for="item in testStandardOptions"
@@ -58,21 +61,52 @@
</el-col>
<el-col :span="12">
<el-form-item label="鏁伴噺锛�" prop="quantity">
- <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="璇疯緭鍏�" clearable :precision="2" :disabled="quantityDisabled"/>
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="璇疯緭鍏�" clearable :precision="2" :disabled="isViewMode || processQuantityDisabled"/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚堟牸鏁伴噺锛�"
+ prop="qualifiedQuantity">
+ <el-input-number :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="form.qualifiedQuantity"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :precision="2"
+ @change="handleQualifiedQuantityChange"
+ :disabled="isViewMode" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓嶅悎鏍兼暟閲忥細"
+ prop="unqualifiedQuantity">
+ <el-input-number :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="form.unqualifiedQuantity"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :precision="2"
+ @change="handleUnqualifiedQuantityChange"
+ :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
<el-form-item label="妫�娴嬪崟浣嶏細" prop="checkCompany">
- <el-input v-model="form.checkCompany" placeholder="璇疯緭鍏�" clearable/>
+ <el-input v-model="form.checkCompany" placeholder="璇疯緭鍏�" clearable :disabled="isViewMode"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="妫�娴嬬粨鏋滐細" prop="checkResult">
- <el-select v-model="form.checkResult">
+ <el-select v-model="form.checkResult" :disabled="isViewMode">
<el-option label="鍚堟牸" value="鍚堟牸" />
<el-option label="涓嶅悎鏍�" value="涓嶅悎鏍�" />
+ <el-option label="閮ㄥ垎鍚堟牸" value="閮ㄥ垎鍚堟牸" />
</el-select>
</el-form-item>
</el-col>
@@ -80,10 +114,10 @@
<el-row :gutter="30">
<el-col :span="12">
<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-select v-model="form.checkName" placeholder="璇烽�夋嫨" clearable :disabled="isViewMode">
+ <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
+ :value="item.nickName"/>
+ </el-select>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -96,6 +130,7 @@
format="YYYY-MM-DD"
clearable
style="width: 100%"
+ :disabled="isViewMode"
/>
</el-form-item>
</el-col>
@@ -109,13 +144,16 @@
height="400"
>
<template #slot="{ row }">
- <el-input v-model="row.testValue" clearable/>
+ <el-input v-model="row.testValue" clearable :disabled="isViewMode"/>
</template>
</PIMTable>
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
+ <template v-if="!isViewMode">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </template>
+ <el-button v-else @click="closeDia">鍏抽棴</el-button>
</div>
</template>
</el-dialog>
@@ -134,7 +172,7 @@
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
-const operationType = ref('')
+const operationType = ref("");
const data = reactive({
form: {
checkTime: "",
@@ -147,25 +185,31 @@
testStandardId: "",
unit: "",
quantity: "",
+ qualifiedQuantity: "",
+ unqualifiedQuantity: "",
checkCompany: "",
checkResult: "",
},
rules: {
- checkTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
+ checkTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
process: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
checkName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
productId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- testStandardId: [{required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change"}],
+ testStandardId: [{ required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change" }],
unit: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ qualifiedQuantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ unqualifiedQuantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
checkCompany: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
checkResult: [{ required: true, message: "璇疯緭鍏�", trigger: "change" }],
},
});
const { form, rules } = toRefs(data);
-// 缂栬緫鏃讹細productMainId 鎴� purchaseLedgerId 浠讳竴鏈夊�煎垯鏁伴噺缃伆
-const quantityDisabled = computed(() => {
+// 鏄惁涓烘煡鐪嬫ā寮�
+const isViewMode = computed(() => operationType.value === 'view');
+// 缂栬緫鏃讹細productMainId 鎴� purchaseLedgerId 浠讳竴鏈夊�煎垯宸ュ簭銆佹暟閲忕疆鐏�
+const processQuantityDisabled = computed(() => {
const v = form.value || {};
return !!(v.productMainId != null || v.purchaseLedgerId != null);
});
@@ -209,7 +253,7 @@
// 鍏堟竻绌鸿〃鍗曢獙璇佺姸鎬侊紝閬垮厤闂儊
await nextTick();
proxy.$refs.formRef?.clearValidate();
-
+
// 骞惰鍔犺浇鍩虹鏁版嵁
const [userListsRes] = await Promise.all([
userListNoPage(),
@@ -219,12 +263,12 @@
})
]);
userList.value = userListsRes.data;
-
+
form.value = {}
testStandardOptions.value = [];
tableData.value = [];
-
- if (operationType.value === 'edit') {
+
+ if (operationType.value === 'edit' || operationType.value === 'view') {
// 鍏堜繚瀛� testStandardId锛岄伩鍏嶈娓呯┖
const savedTestStandardId = row.testStandardId;
// 鍏堣缃〃鍗曟暟鎹紝浣嗘殏鏃舵竻绌� testStandardId锛岀瓑閫夐」鍔犺浇瀹屾垚鍚庡啀璁剧疆
@@ -234,18 +278,18 @@
nextTick(() => {
proxy.$refs.formRef?.clearValidate();
});
-
+
// 缂栬緫妯″紡涓嬶紝骞惰鍔犺浇瑙勬牸鍨嬪彿鍜屾寚鏍囬�夐」
if (currentProductId.value) {
// 璁剧疆浜у搧鍚嶇О
form.value.productName = findNodeById(productOptions.value, currentProductId.value);
-
+
// 骞惰鍔犺浇瑙勬牸鍨嬪彿鍜屾寚鏍囬�夐」
const params = {
productId: currentProductId.value,
inspectType: 2
};
-
+
Promise.all([
modelList({ id: currentProductId.value }),
qualityInspectDetailByProductId(params)
@@ -260,15 +304,15 @@
form.value.unit = selectedModel.unit || '';
}
}
-
+
// 璁剧疆鎸囨爣閫夐」
testStandardOptions.value = testStandardRes.data || [];
-
+
// 璁剧疆 testStandardId 骞跺姞杞藉弬鏁板垪琛�
nextTick(() => {
if (savedTestStandardId) {
// 纭繚绫诲瀷鍖归厤锛坕tem.id 鍙兘鏄暟瀛楁垨瀛楃涓诧級
- const matchedOption = testStandardOptions.value.find(item =>
+ const matchedOption = testStandardOptions.value.find(item =>
item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
);
if (matchedOption) {
@@ -313,6 +357,28 @@
form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
}
+const handleQualifiedQuantityChange = (value) => {
+ if (value === null || value === undefined) {
+ form.value.qualifiedQuantity = 0;
+ return;
+ }
+ const quantity = parseFloat(form.value.quantity) || 0;
+ const qualified = parseFloat(value) || 0;
+ form.value.qualifiedQuantity = qualified > quantity?quantity:qualified;
+ form.value.unqualifiedQuantity = Math.max(0, quantity - qualified);
+};
+
+const handleUnqualifiedQuantityChange = (value) => {
+ if (value === null || value === undefined) {
+ form.value.unqualifiedQuantity = 0;
+ return;
+ }
+ const quantity = parseFloat(form.value.quantity) || 0;
+ const unqualified = parseFloat(value) || 0;
+ form.value.unqualifiedQuantity = unqualified > quantity?quantity:unqualified;
+ form.value.qualifiedQuantity = Math.max(0, quantity - unqualified);
+};
+
const findNodeById = (nodes, productId) => {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].value === productId) {
@@ -337,7 +403,7 @@
if (children && children.length > 0) {
newItem.children = convertIdToValue(children);
}
-
+
return newItem;
});
}
@@ -345,26 +411,26 @@
const submitForm = () => {
proxy.$refs.formRef.validate(valid => {
if (valid) {
- form.value.inspectType = 2
- if (operationType.value === "add") {
- tableData.value.forEach((item) => {
- delete item.id
- })
- }
- const data = {...form.value, qualityInspectParams: tableData.value}
+ form.value.inspectType = 2;
+ if (operationType.value === "add") {
+ tableData.value.forEach((item) => {
+ delete item.id;
+ });
+ }
+ const data = { ...form.value, qualityInspectParams: tableData.value };
if (operationType.value === "add") {
qualityInspectAdd(data).then(res => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
- })
+ });
} else {
qualityInspectUpdate(data).then(res => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
- })
+ });
}
}
- })
+ });
}
const getList = () => {
if (!currentProductId.value) {
@@ -375,15 +441,15 @@
let params = {
productId: currentProductId.value,
inspectType: 2
- }
- qualityInspectDetailByProductId(params).then(res => {
- // 淇濆瓨涓嬫媺妗嗛�夐」鏁版嵁
- testStandardOptions.value = res.data || [];
- // 娓呯┖琛ㄦ牸鏁版嵁锛岀瓑寰呯敤鎴烽�夋嫨鎸囨爣
- tableData.value = [];
- // 娓呯┖鎸囨爣閫夋嫨
- form.value.testStandardId = '';
- })
+ };
+ qualityInspectDetailByProductId(params).then(res => {
+ // 淇濆瓨涓嬫媺妗嗛�夐」鏁版嵁
+ testStandardOptions.value = res.data || [];
+ // 娓呯┖琛ㄦ牸鏁版嵁锛岀瓑寰呯敤鎴烽�夋嫨鎸囨爣
+ tableData.value = [];
+ // 娓呯┖鎸囨爣閫夋嫨
+ form.value.testStandardId = '';
+ });
}
// 鎸囨爣閫夋嫨鍙樺寲澶勭悊
@@ -395,17 +461,21 @@
tableLoading.value = true;
getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
tableData.value = res.data || [];
+ tableData.value = tableData.value.map(item => ({
+ ...item,
+ id: null
+ }));
}).catch(error => {
console.error('鑾峰彇鏍囧噯鍙傛暟澶辫触:', error);
tableData.value = [];
}).finally(() => {
tableLoading.value = false;
- })
+ });
}
const getQualityInspectParamList = (id) => {
- qualityInspectParamInfo(id).then(res => {
- tableData.value = res.data;
- })
+ qualityInspectParamInfo(id).then(res => {
+ tableData.value = res.data;
+ });
}
// 鍏抽棴寮规
const closeDia = () => {
@@ -414,8 +484,8 @@
testStandardOptions.value = [];
form.value.testStandardId = '';
dialogFormVisible.value = false;
- emit('close')
-};
+ emit('close');
+}
defineExpose({
openDialog,
});
@@ -423,4 +493,4 @@
<style scoped>
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/qualityManagement/finalInspection/index.vue b/src/views/qualityManagement/finalInspection/index.vue
index a2d1acc..bdb71c4 100644
--- a/src/views/qualityManagement/finalInspection/index.vue
+++ b/src/views/qualityManagement/finalInspection/index.vue
@@ -123,8 +123,18 @@
prop: "unit",
},
{
- label: "鏁伴噺",
+ label: "鎬绘暟閲�",
prop: "quantity",
+ width: 100
+ },
+ {
+ label: "鍚堟牸鏁伴噺",
+ prop: "qualifiedQuantity",
+ width: 100
+ },
+ {
+ label: "涓嶅悎鏍兼暟閲�",
+ prop: "unqualifiedQuantity",
width: 100
},
{
@@ -142,7 +152,7 @@
} else if (params == '鍚堟牸') {
return "success";
} else {
- return null;
+ return 'danger';
}
},
},
@@ -181,6 +191,13 @@
}
},
{
+ name: "鏌ョ湅",
+ type: "text",
+ clickFun: (row) => {
+ openForm("view", row);
+ },
+ },
+ {
name: "闄勪欢",
type: "text",
clickFun: (row) => {
diff --git a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
index 0c6562c..e747d04 100644
--- a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
+++ b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
@@ -35,10 +35,10 @@
</el-col>
<el-col :span="12">
<el-form-item label="瑙勬牸鍨嬪彿锛�" prop="model">
- <el-select v-model="form.model" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'edit'"
- filterable readonly @change="handleChangeModel">
- <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
- </el-select>
+ <el-select v-model="form.productModelId" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'edit'"
+ filterable readonly @change="handleChangeModel">
+ <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
+ </el-select>
</el-form-item>
</el-col>
</el-row>
@@ -149,7 +149,7 @@
productId: "",
model: "",
unit: "",
- quantity: "",
+ quantity: undefined,
checkCompany: "",
checkResult: "",
inspectType: '',
@@ -157,6 +157,7 @@
dealResult: '',
dealName: '',
dealTime: '',
+ productModelId: undefined,
},
rules: {
checkTime: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" },],
@@ -199,8 +200,9 @@
productId: '',
model: '',
unit: '',
- quantity: '',
+ quantity: undefined,
productName: '',
+ productModelId: undefined,
};
} else {
form.value = {};
@@ -223,6 +225,12 @@
modelList({ id: value }).then((res) => {
modelOptions.value = res;
})
+};
+const handleChangeModel = (value) => {
+ const selectedModel = modelOptions.value.find(item => item.id === value);
+ if (selectedModel) {
+ form.value.model = selectedModel.model;
+ }
};
const findNodeById = (nodes, productId) => {
for (let i = 0; i < nodes.length; i++) {
@@ -285,4 +293,4 @@
<style scoped>
-</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 6306397..fc9d5d2 100644
--- a/src/views/qualityManagement/nonconformingManagement/index.vue
+++ b/src/views/qualityManagement/nonconformingManagement/index.vue
@@ -98,7 +98,7 @@
} else if (params == '鍚堟牸') {
return "success";
} else {
- return null;
+ return 'danger';
}
},
},
diff --git a/src/views/qualityManagement/processInspection/components/filesDia.vue b/src/views/qualityManagement/processInspection/components/filesDia.vue
index b0cb258..fe63f4b 100644
--- a/src/views/qualityManagement/processInspection/components/filesDia.vue
+++ b/src/views/qualityManagement/processInspection/components/filesDia.vue
@@ -124,7 +124,7 @@
};
// 涓嬭浇闄勪欢
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
// 鍏抽棴寮规
const closeDia = () => {
diff --git a/src/views/qualityManagement/processInspection/components/formDia.vue b/src/views/qualityManagement/processInspection/components/formDia.vue
index c1185d2..635360f 100644
--- a/src/views/qualityManagement/processInspection/components/formDia.vue
+++ b/src/views/qualityManagement/processInspection/components/formDia.vue
@@ -1,130 +1,206 @@
<template>
<div>
- <el-dialog
- v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板杩囩▼妫�楠�' : '缂栬緫杩囩▼妫�楠�'"
- width="70%"
- @close="closeDia"
- >
- <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-dialog v-model="dialogFormVisible"
+ :title="operationType === 'add' ? '鏂板杩囩▼妫�楠�' : operationType === 'view' ? '鏌ョ湅杩囩▼妫�楠�' : '缂栬緫杩囩▼妫�楠�'"
+ width="70%"
+ @close="closeDia">
+ <el-form :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef">
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="宸ュ簭锛�" prop="process">
- <el-select v-model="form.process" placeholder="璇烽�夋嫨宸ュ簭" clearable :disabled="processQuantityDisabled" style="width: 100%">
- <el-option v-for="item in processList" :key="item.name" :label="item.name" :value="item.name"/>
+ <el-form-item label="宸ュ簭锛�"
+ prop="process">
+ <el-select v-model="form.process"
+ placeholder="璇烽�夋嫨宸ュ簭"
+ clearable
+ :disabled="isViewMode || processQuantityDisabled"
+ style="width: 100%">
+ <el-option v-for="item in processList"
+ :key="item.name"
+ :label="item.name"
+ :value="item.name" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="浜у搧鍚嶇О锛�" prop="productId">
- <el-tree-select
- v-model="form.productId"
- placeholder="璇烽�夋嫨"
- clearable
- check-strictly
- @change="getModels"
- :data="productOptions"
- :render-after-expand="false"
- :disabled="operationType === 'edit'"
- style="width: 100%"
- />
+ <el-form-item label="浜у搧鍚嶇О锛�"
+ prop="productId">
+ <el-tree-select v-model="form.productId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ check-strictly
+ @change="getModels"
+ :data="productOptions"
+ :render-after-expand="false"
+ :disabled="isViewMode || operationType === 'edit'"
+ style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
- <el-select v-model="form.productModelId" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'edit'"
- filterable readonly @change="handleChangeModel">
- <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
+ <el-form-item label="瑙勬牸鍨嬪彿锛�"
+ prop="productModelId">
+ <el-select v-model="form.productModelId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="isViewMode || operationType === 'edit'"
+ filterable
+ readonly
+ @change="handleChangeModel">
+ <el-option v-for="item in modelOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="鎸囨爣閫夋嫨锛�" prop="testStandardId">
- <el-select
- v-model="form.testStandardId"
- placeholder="璇烽�夋嫨鎸囨爣"
- clearable
- @change="handleTestStandardChange"
- style="width: 100%"
- >
- <el-option
- v-for="item in testStandardOptions"
- :key="item.id"
- :label="item.standardName || item.standardNo"
- :value="item.id"
- />
+ <el-form-item label="鎸囨爣閫夋嫨锛�"
+ prop="testStandardId">
+ <el-select v-model="form.testStandardId"
+ placeholder="璇烽�夋嫨鎸囨爣"
+ clearable
+ @change="handleTestStandardChange"
+ style="width: 100%"
+ :disabled="isViewMode">
+ <el-option v-for="item in testStandardOptions"
+ :key="item.id"
+ :label="item.standardName || item.standardNo"
+ :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="鍗曚綅锛�" prop="unit">
- <el-input v-model="form.unit" placeholder="璇疯緭鍏�" disabled/>
+ <el-form-item label="鍗曚綅锛�"
+ prop="unit">
+ <el-input v-model="form.unit"
+ placeholder="璇疯緭鍏�"
+ disabled />
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="鏁伴噺锛�" prop="quantity">
- <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="璇疯緭鍏�" clearable :precision="2" :disabled="processQuantityDisabled"/>
+ <el-form-item label="鏁伴噺锛�"
+ prop="quantity">
+ <el-input-number :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="form.quantity"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :precision="2"
+ :disabled="isViewMode || processQuantityDisabled" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="妫�娴嬪崟浣嶏細" prop="checkCompany">
- <el-input v-model="form.checkCompany" placeholder="璇疯緭鍏�" clearable/>
+ <el-form-item label="鍚堟牸鏁伴噺锛�"
+ prop="qualifiedQuantity">
+ <el-input-number :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="form.qualifiedQuantity"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :precision="2"
+ @change="handleQualifiedQuantityChange"
+ :disabled="isViewMode" />
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="妫�娴嬬粨鏋滐細" prop="checkResult">
- <el-select v-model="form.checkResult">
- <el-option label="鍚堟牸" value="鍚堟牸" />
- <el-option label="涓嶅悎鏍�" value="涓嶅悎鏍�" />
+ <el-form-item label="涓嶅悎鏍兼暟閲忥細"
+ prop="unqualifiedQuantity">
+ <el-input-number :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="form.unqualifiedQuantity"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :precision="2"
+ @change="handleUnqualifiedQuantityChange"
+ :disabled="isViewMode" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="妫�娴嬪崟浣嶏細"
+ prop="checkCompany">
+ <el-input v-model="form.checkCompany"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :disabled="isViewMode" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="妫�娴嬬粨鏋滐細"
+ prop="checkResult">
+ <el-select v-model="form.checkResult" :disabled="isViewMode">
+ <el-option label="鍚堟牸"
+ value="鍚堟牸" />
+ <el-option label="涓嶅悎鏍�"
+ value="涓嶅悎鏍�" />
+ <el-option label="閮ㄥ垎鍚堟牸"
+ value="閮ㄥ垎鍚堟牸" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="妫�楠屽憳锛�" prop="checkName">
- <el-select v-model="form.checkName" placeholder="璇烽�夋嫨" clearable>
- <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
- :value="item.nickName"/>
- </el-select>
+ <el-form-item label="妫�楠屽憳锛�"
+ prop="checkName">
+ <el-select v-model="form.checkName"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="isViewMode">
+ <el-option v-for="item in userList"
+ :key="item.nickName"
+ :label="item.nickName"
+ :value="item.nickName" />
+ </el-select>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="妫�娴嬫棩鏈燂細" prop="checkTime">
- <el-date-picker
- v-model="form.checkTime"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- style="width: 100%"
- />
+ <el-form-item label="妫�娴嬫棩鏈燂細"
+ prop="checkTime">
+ <el-date-picker v-model="form.checkTime"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ style="width: 100%"
+ :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-row>
</el-form>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :tableLoading="tableLoading"
- height="400"
- >
- <template #slot="{ row }">
- <el-input v-model="row.testValue" clearable/>
- </template>
- </PIMTable>
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :tableLoading="tableLoading"
+ height="400">
+ <template #slot="{ row }">
+ <el-input v-model="row.testValue"
+ clearable
+ :disabled="isViewMode" />
+ </template>
+ </PIMTable>
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
+ <template v-if="!isViewMode">
+ <el-button type="primary"
+ @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </template>
+ <el-button v-else @click="closeDia">鍏抽棴</el-button>
</div>
</template>
</el-dialog>
@@ -132,332 +208,398 @@
</template>
<script setup>
-import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue";
-import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
-import {modelList, productTreeList} from "@/api/basicData/product.js";
-import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
-import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
-import {userListNoPage} from "@/api/system/user.js";
-import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
-import { list } from "@/api/productionManagement/productionProcess";
-const { proxy } = getCurrentInstance()
-const emit = defineEmits(['close'])
+ import {
+ ref,
+ reactive,
+ toRefs,
+ computed,
+ getCurrentInstance,
+ nextTick,
+ } from "vue";
+ import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
+ import { modelList, productTreeList } from "@/api/basicData/product.js";
+ import {
+ qualityInspectAdd,
+ qualityInspectUpdate,
+ } from "@/api/qualityManagement/rawMaterialInspection.js";
+ import {
+ qualityInspectDetailByProductId,
+ getQualityTestStandardParamByTestStandardId,
+ } from "@/api/qualityManagement/metricMaintenance.js";
+ import { userListNoPage } from "@/api/system/user.js";
+ import { qualityInspectParamInfo } from "@/api/qualityManagement/qualityInspectParam.js";
+ import { list } from "@/api/productionManagement/productionProcess";
+ import qualified from "@/views/inventoryManagement/stockManagement/Qualified.vue";
+ const { proxy } = getCurrentInstance();
+ const emit = defineEmits(["close"]);
-
-
-const dialogFormVisible = ref(false);
-const operationType = ref('')
-const data = reactive({
- form: {
- checkTime: "",
- process: "",
- checkName: "",
- productName: "",
- productId: "",
- productModelId: "",
- model: "",
- testStandardId: "",
- unit: "",
- quantity: "",
- checkCompany: "",
- checkResult: "",
- },
- rules: {
- checkTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
- process: [{ required: true, message: "璇烽�夋嫨宸ュ簭", trigger: "change" }],
- checkName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
- productId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
- testStandardId: [{required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change"}],
- unit: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
- quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- checkCompany: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
- checkResult: [{ required: true, message: "璇疯緭鍏�", trigger: "change" }],
- },
-});
-const userList = ref([]);
-const { form, rules } = toRefs(data);
-// 缂栬緫鏃讹細productMainId 鎴� purchaseLedgerId 浠讳竴鏈夊�煎垯宸ュ簭銆佹暟閲忕疆鐏�
-const processQuantityDisabled = computed(() => {
- const v = form.value || {};
- return !!(v.productMainId != null || v.purchaseLedgerId != null);
-});
-const processList = ref([]); // 宸ュ簭涓嬫媺鍒楄〃锛堝伐搴忓悕绉� name锛�
-const supplierList = ref([]);
-const productOptions = ref([]);
-const tableColumn = ref([
- {
- label: "鎸囨爣",
- prop: "parameterItem",
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- },
- {
- label: "鏍囧噯鍊�",
- prop: "standardValue",
- },
- {
- label: "鍐呮帶鍊�",
- prop: "controlValue",
- },
- {
- label: "妫�楠屽��",
- prop: "testValue",
- dataType: 'slot',
- slot: 'slot',
- },
-]);
-const tableData = ref([]);
-const tableLoading = ref(false);
-const currentProductId = ref(0);
-const testStandardOptions = ref([]); // 鎸囨爣閫夋嫨涓嬫媺妗嗘暟鎹�
-const modelOptions = ref([]);
-
-// 鎵撳紑寮规
-const openDialog = async (type, row) => {
- operationType.value = type;
- getOptions().then((res) => {
- supplierList.value = res.data;
- });
- // 鍔犺浇宸ュ簭涓嬫媺鍒楄〃
- try {
- const res = await list();
- processList.value = res.data || [];
- } catch (e) {
- console.error("鍔犺浇宸ュ簭鍒楄〃澶辫触", e);
- processList.value = [];
- }
- let userLists = await userListNoPage();
- userList.value = userLists.data;
- // 鍏堥噸缃〃鍗曟暟鎹紙淇濇寔瀛楁瀹屾暣锛岄伩鍏嶅脊绐楅娆℃覆鏌撴椂瑙﹀彂蹇呭~绾㈡鈥滈棯涓�涓嬧�濓級
- form.value = {
- checkTime: "",
- process: "",
- checkName: "",
- productName: "",
- productId: "",
- productModelId: "",
- model: "",
- testStandardId: "",
- unit: "",
- quantity: "",
- checkCompany: "",
- checkResult: "",
- }
- testStandardOptions.value = [];
- tableData.value = [];
- // 鍏堢‘淇濅骇鍝佹爲宸插姞杞斤紝鍚﹀垯缂栬緫鏃朵骇鍝�/瑙勬牸鍨嬪彿鏃犳硶鍙嶆樉
- await getProductOptions();
- if (operationType.value === 'edit') {
- // 鍏堜繚瀛� testStandardId锛岄伩鍏嶈娓呯┖
- const savedTestStandardId = row.testStandardId;
- // 鍏堣缃〃鍗曟暟鎹紝浣嗘殏鏃舵竻绌� testStandardId锛岀瓑閫夐」鍔犺浇瀹屾垚鍚庡啀璁剧疆
- form.value = {...row, testStandardId: ''}
- currentProductId.value = row.productId || 0
- // 鍏抽敭锛氱紪杈戞椂鍔犺浇瑙勬牸鍨嬪彿涓嬫媺閫夐」锛屾墠鑳藉弽鏄� productModelId
- if (currentProductId.value) {
- try {
- const res = await modelList({ id: currentProductId.value });
- modelOptions.value = res || [];
- // 鍚屾鍥炲~ model / unit锛堟湁浜涙帴鍙h繑鍥炵殑 row 閲屽彲鑳芥病甯﹀叏锛�
- if (form.value.productModelId) {
- handleChangeModel(form.value.productModelId);
- }
- } catch (e) {
- console.error("鍔犺浇瑙勬牸鍨嬪彿澶辫触", e);
- modelOptions.value = [];
- }
- }
- // 缂栬緫妯″紡涓嬶紝鍏堝姞杞芥寚鏍囬�夐」锛岀劧鍚庡姞杞藉弬鏁板垪琛�
- if (currentProductId.value) {
- // 鍏堝姞杞芥寚鏍囬�夐」
- let params = {
- productId: currentProductId.value,
- inspectType: 1,
- process: form.value.process || ''
- }
- qualityInspectDetailByProductId(params).then(res => {
- testStandardOptions.value = res.data || [];
- // 浣跨敤 nextTick 鍜� setTimeout 纭繚閫夐」宸茬粡娓叉煋鍒� DOM
- nextTick(() => {
- setTimeout(() => {
- // 濡傛灉缂栬緫鏁版嵁涓湁 testStandardId锛屽垯璁剧疆骞跺姞杞藉搴旂殑鍙傛暟
- if (savedTestStandardId) {
- // 纭繚绫诲瀷鍖归厤锛坕tem.id 鍙兘鏄暟瀛楁垨瀛楃涓诧級
- const matchedOption = testStandardOptions.value.find(item =>
- item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
- );
- if (matchedOption) {
- // 纭繚浣跨敤鍖归厤椤圭殑 id锛堜繚鎸佺被鍨嬩竴鑷达級
- form.value.testStandardId = matchedOption.id;
- // 缂栬緫淇濈暀鍘熸楠屽�硷紝鐩存帴鎷夊彇鍘熷弬鏁版暟鎹�
- getQualityInspectParamList(row.id);
- } else {
- // 濡傛灉鎵句笉鍒板尮閰嶉」锛屽皾璇曠洿鎺ヤ娇鐢ㄥ師鍊�
- console.warn('鏈壘鍒板尮閰嶇殑鎸囨爣閫夐」锛宼estStandardId:', savedTestStandardId, '鍙敤閫夐」:', testStandardOptions.value);
- form.value.testStandardId = savedTestStandardId;
- getQualityInspectParamList(row.id);
- }
- } else {
- // 鍚﹀垯浣跨敤鏃х殑閫昏緫
- getQualityInspectParamList(row.id);
- }
- }, 100);
- });
- });
- } else {
- getQualityInspectParamList(row.id);
- }
- }
- // 鏈�鍚庡啀鎵撳紑寮圭獥锛屽苟娓呯悊鏍¢獙鎬侊紝閬垮厤蹇呭~鎻愮ず闂儊
- dialogFormVisible.value = true;
- nextTick(() => {
- proxy.$refs?.formRef?.clearValidate?.();
- });
-}
-const getProductOptions = () => {
- return productTreeList().then((res) => {
- productOptions.value = convertIdToValue(res);
- return productOptions.value;
+ const dialogFormVisible = ref(false);
+ const operationType = ref("");
+ const data = reactive({
+ form: {
+ checkTime: "",
+ process: "",
+ checkName: "",
+ productName: "",
+ productId: "",
+ productModelId: "",
+ model: "",
+ testStandardId: "",
+ unit: "",
+ quantity: "",
+ qualifiedQuantity: "",
+ unqualifiedQuantity: "",
+ checkCompany: "",
+ checkResult: "",
+ },
+ rules: {
+ checkTime: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ process: [{ required: true, message: "璇烽�夋嫨宸ュ簭", trigger: "change" }],
+ checkName: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ productId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ testStandardId: [{ required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change" }],
+ unit: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ qualifiedQuantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ unqualifiedQuantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ checkCompany: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
+ checkResult: [{ required: true, message: "璇疯緭鍏�", trigger: "change" }],
+ },
});
-};
-const getModels = (value) => {
- form.value.productModelId = undefined;
- form.value.unit = undefined;
- modelOptions.value = [];
- currentProductId.value = value
- form.value.productName = findNodeById(productOptions.value, value);
- modelList({ id: value }).then((res) => {
- modelOptions.value = res;
- })
- if (currentProductId.value) {
- getList();
- }
-};
+ const userList = ref([]);
+ const { form, rules } = toRefs(data);
+ // 鏄惁涓烘煡鐪嬫ā寮�
+ const isViewMode = computed(() => operationType.value === 'view');
+ // 缂栬緫鏃讹細productMainId 鎴� purchaseLedgerId 浠讳竴鏈夊�煎垯宸ュ簭銆佹暟閲忕疆鐏�
+ const processQuantityDisabled = computed(() => {
+ const v = form.value || {};
+ return !!(v.productMainId != null || v.purchaseLedgerId != null);
+ });
+ const processList = ref([]); // 宸ュ簭涓嬫媺鍒楄〃锛堝伐搴忓悕绉� name锛�
+ const supplierList = ref([]);
+ const productOptions = ref([]);
+ const tableColumn = ref([
+ {
+ label: "鎸囨爣",
+ prop: "parameterItem",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "鏍囧噯鍊�",
+ prop: "standardValue",
+ },
+ {
+ label: "鍐呮帶鍊�",
+ prop: "controlValue",
+ },
+ {
+ label: "妫�楠屽��",
+ prop: "testValue",
+ dataType: "slot",
+ slot: "slot",
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const currentProductId = ref(0);
+ const testStandardOptions = ref([]); // 鎸囨爣閫夋嫨涓嬫媺妗嗘暟鎹�
+ const modelOptions = ref([]);
-const handleChangeModel = (value) => {
- form.value.model = modelOptions.value.find(item => item.id == value)?.model || '';
- form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
-}
-
-const findNodeById = (nodes, productId) => {
- for (let i = 0; i < nodes.length; i++) {
- if (nodes[i].value === productId) {
- return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
+ // 鎵撳紑寮规
+ const openDialog = async (type, row) => {
+ operationType.value = type;
+ getOptions().then(res => {
+ supplierList.value = res.data;
+ });
+ // 鍔犺浇宸ュ簭涓嬫媺鍒楄〃
+ try {
+ const res = await list({ size: -1, current: -1 });
+ processList.value = res.data.records || [];
+ } catch (e) {
+ console.error("鍔犺浇宸ュ簭鍒楄〃澶辫触", e);
+ processList.value = [];
}
- 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
+ let userLists = await userListNoPage();
+ userList.value = userLists.data;
+ // 鍏堥噸缃〃鍗曟暟鎹紙淇濇寔瀛楁瀹屾暣锛岄伩鍏嶅脊绐楅娆℃覆鏌撴椂瑙﹀彂蹇呭~绾㈡鈥滈棯涓�涓嬧�濓級
+ form.value = {
+ checkTime: "",
+ process: "",
+ checkName: "",
+ productName: "",
+ productId: "",
+ productModelId: "",
+ model: "",
+ testStandardId: "",
+ unit: "",
+ quantity: "",
+ checkCompany: "",
+ checkResult: "",
};
- if (children && children.length > 0) {
- newItem.children = convertIdToValue(children);
- }
-
- return newItem;
- });
-}
-// 宸ュ簭鍙樺寲澶勭悊
-// 鎻愪氦浜у搧琛ㄥ崟
-const submitForm = () => {
- proxy.$refs.formRef.validate(valid => {
- if (valid) {
- form.value.inspectType = 1
- const processName = form.value.process || '';
- if (operationType.value === "add") {
- tableData.value.forEach((item) => {
- delete item.id
- })
- }
- const data = {
- ...form.value,
- process: processName, // 淇濈暀 process 瀛楁浠ュ吋瀹瑰悗绔�
- qualityInspectParams: tableData.value
- }
- if (operationType.value === "add") {
- qualityInspectAdd(data).then(res => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- })
+ testStandardOptions.value = [];
+ tableData.value = [];
+ // 鍏堢‘淇濅骇鍝佹爲宸插姞杞斤紝鍚﹀垯缂栬緫鏃朵骇鍝�/瑙勬牸鍨嬪彿鏃犳硶鍙嶆樉
+ await getProductOptions();
+ if (operationType.value === "edit" || operationType.value === "view") {
+ // 鍏堜繚瀛� testStandardId锛岄伩鍏嶈娓呯┖
+ const savedTestStandardId = row.testStandardId;
+ // 鍏堣缃〃鍗曟暟鎹紝浣嗘殏鏃舵竻绌� testStandardId锛岀瓑閫夐」鍔犺浇瀹屾垚鍚庡啀璁剧疆
+ form.value = { ...row, testStandardId: "" };
+ currentProductId.value = row.productId || 0;
+ // 鍏抽敭锛氱紪杈戞椂鍔犺浇瑙勬牸鍨嬪彿涓嬫媺閫夐」锛屾墠鑳藉弽鏄� productModelId
+ if (currentProductId.value) {
+ try {
+ const res = await modelList({ id: currentProductId.value });
+ modelOptions.value = res || [];
+ // 鍚屾鍥炲~ model / unit锛堟湁浜涙帴鍙h繑鍥炵殑 row 閲屽彲鑳芥病甯﹀叏锛�
+ if (form.value.productModelId) {
+ handleChangeModel(form.value.productModelId);
+ }
+ } catch (e) {
+ console.error("鍔犺浇瑙勬牸鍨嬪彿澶辫触", e);
+ modelOptions.value = [];
+ }
+ }
+ // 缂栬緫妯″紡涓嬶紝鍏堝姞杞芥寚鏍囬�夐」锛岀劧鍚庡姞杞藉弬鏁板垪琛�
+ if (currentProductId.value) {
+ // 鍏堝姞杞芥寚鏍囬�夐」
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 1,
+ process: form.value.process || "",
+ };
+ qualityInspectDetailByProductId(params).then(res => {
+ testStandardOptions.value = res.data || [];
+ // 浣跨敤 nextTick 鍜� setTimeout 纭繚閫夐」宸茬粡娓叉煋鍒� DOM
+ nextTick(() => {
+ setTimeout(() => {
+ // 濡傛灉缂栬緫鏁版嵁涓湁 testStandardId锛屽垯璁剧疆骞跺姞杞藉搴旂殑鍙傛暟
+ if (savedTestStandardId) {
+ // 纭繚绫诲瀷鍖归厤锛坕tem.id 鍙兘鏄暟瀛楁垨瀛楃涓诧級
+ const matchedOption = testStandardOptions.value.find(
+ item =>
+ item.id == savedTestStandardId ||
+ String(item.id) === String(savedTestStandardId)
+ );
+ if (matchedOption) {
+ // 纭繚浣跨敤鍖归厤椤圭殑 id锛堜繚鎸佺被鍨嬩竴鑷达級
+ form.value.testStandardId = matchedOption.id;
+ // 缂栬緫淇濈暀鍘熸楠屽�硷紝鐩存帴鎷夊彇鍘熷弬鏁版暟鎹�
+ getQualityInspectParamList(row.id);
+ } else {
+ // 濡傛灉鎵句笉鍒板尮閰嶉」锛屽皾璇曠洿鎺ヤ娇鐢ㄥ師鍊�
+ console.warn(
+ "鏈壘鍒板尮閰嶇殑鎸囨爣閫夐」锛宼estStandardId:",
+ savedTestStandardId,
+ "鍙敤閫夐」:",
+ testStandardOptions.value
+ );
+ form.value.testStandardId = savedTestStandardId;
+ getQualityInspectParamList(row.id);
+ }
+ } else {
+ // 鍚﹀垯浣跨敤鏃х殑閫昏緫
+ getQualityInspectParamList(row.id);
+ }
+ }, 100);
+ });
+ });
} else {
- qualityInspectUpdate(data).then(res => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeDia();
- })
+ getQualityInspectParamList(row.id);
}
}
- })
-}
-const getList = () => {
- if (!currentProductId.value) {
- testStandardOptions.value = [];
- tableData.value = [];
- return;
- }
- const processName = form.value.process || '';
- let params = {
- productId: currentProductId.value,
- inspectType: 1,
- process: processName
- }
- qualityInspectDetailByProductId(params).then(res => {
- // 淇濆瓨涓嬫媺妗嗛�夐」鏁版嵁
- testStandardOptions.value = res.data || [];
- // 娓呯┖琛ㄦ牸鏁版嵁锛岀瓑寰呯敤鎴烽�夋嫨鎸囨爣
- tableData.value = [];
- // 娓呯┖鎸囨爣閫夋嫨
- form.value.testStandardId = '';
- })
-}
+ // 鏈�鍚庡啀鎵撳紑寮圭獥锛屽苟娓呯悊鏍¢獙鎬侊紝閬垮厤蹇呭~鎻愮ず闂儊
+ dialogFormVisible.value = true;
+ nextTick(() => {
+ proxy.$refs?.formRef?.clearValidate?.();
+ });
+ };
+ const getProductOptions = () => {
+ return productTreeList().then(res => {
+ productOptions.value = convertIdToValue(res);
+ return productOptions.value;
+ });
+ };
+ const getModels = value => {
+ form.value.productModelId = undefined;
+ form.value.unit = undefined;
+ modelOptions.value = [];
+ currentProductId.value = value;
+ form.value.productName = findNodeById(productOptions.value, value);
+ modelList({ id: value }).then(res => {
+ modelOptions.value = res;
+ });
+ if (currentProductId.value) {
+ getList();
+ }
+ };
-// 鎸囨爣閫夋嫨鍙樺寲澶勭悊
-const handleTestStandardChange = (testStandardId) => {
- if (!testStandardId) {
- tableData.value = [];
- return;
- }
- tableLoading.value = true;
- getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
- tableData.value = res.data || [];
- }).catch(error => {
- console.error('鑾峰彇鏍囧噯鍙傛暟澶辫触:', error);
- tableData.value = [];
- }).finally(() => {
- tableLoading.value = false;
- })
-}
-const getQualityInspectParamList = (id) => {
- qualityInspectParamInfo(id).then(res => {
- tableData.value = res.data;
- })
-}
-// 鍏抽棴寮规
-const closeDia = () => {
- proxy.resetForm("formRef");
- tableData.value = [];
- testStandardOptions.value = [];
- form.value.testStandardId = '';
- dialogFormVisible.value = false;
- emit('close')
-};
-defineExpose({
- openDialog,
-});
+ const handleChangeModel = value => {
+ form.value.model =
+ modelOptions.value.find(item => item.id == value)?.model || "";
+ form.value.unit =
+ modelOptions.value.find(item => item.id == value)?.unit || "";
+ };
+
+ const handleQualifiedQuantityChange = (value) => {
+ if (value === null || value === undefined) {
+ form.value.qualifiedQuantity = 0;
+ return;
+ }
+ const quantity = parseFloat(form.value.quantity) || 0;
+ const qualified = parseFloat(value) || 0;
+ form.value.qualifiedQuantity = qualified > quantity?quantity:qualified;
+ form.value.unqualifiedQuantity = Math.max(0, quantity - qualified);
+ };
+
+ const handleUnqualifiedQuantityChange = (value) => {
+ if (value === null || value === undefined) {
+ form.value.unqualifiedQuantity = 0;
+ return;
+ }
+ const quantity = parseFloat(form.value.quantity) || 0;
+ const unqualified = parseFloat(value) || 0;
+ form.value.unqualifiedQuantity = unqualified > quantity?quantity:unqualified;
+ form.value.qualifiedQuantity = Math.max(0, quantity - unqualified);
+ };
+
+ 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
+ };
+ 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 = () => {
+ proxy.$refs.formRef.validate(valid => {
+ if (valid) {
+ form.value.inspectType = 1;
+ const processName = form.value.process || "";
+ if (operationType.value === "add") {
+ tableData.value.forEach(item => {
+ delete item.id;
+ });
+ }
+ // 纭繚鏁伴噺涓嶄负null
+ const quantity = parseFloat(form.value.quantity) || 0;
+ const qualified = parseFloat(form.value.qualifiedQuantity) || 0;
+ const unqualified = parseFloat(form.value.unqualifiedQuantity) || 0;
+
+ // 楠岃瘉鏁伴噺鍏崇郴
+ if (qualified + unqualified !== quantity) {
+ proxy.$modal.msgError("鍚堟牸鏁伴噺涓庝笉鍚堟牸鏁伴噺涔嬪拰蹇呴』绛変簬鎬绘暟閲�");
+ return;
+ }
+
+ const data = {
+ ...form.value,
+ process: processName, // 淇濈暀 process 瀛楁浠ュ吋瀹瑰悗绔�
+ qualityInspectParams: tableData.value,
+ };
+ if (operationType.value === "add") {
+ qualityInspectAdd(data).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ });
+ } else {
+ qualityInspectUpdate(data).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ });
+ }
+ }
+ });
+ };
+ const getList = () => {
+ if (!currentProductId.value) {
+ testStandardOptions.value = [];
+ tableData.value = [];
+ return;
+ }
+ const processName = form.value.process || "";
+ let params = {
+ productId: currentProductId.value,
+ inspectType: 1,
+ process: processName,
+ };
+ qualityInspectDetailByProductId(params).then(res => {
+ // 淇濆瓨涓嬫媺妗嗛�夐」鏁版嵁
+ testStandardOptions.value = res.data || [];
+ // 娓呯┖琛ㄦ牸鏁版嵁锛岀瓑寰呯敤鎴烽�夋嫨鎸囨爣
+ tableData.value = [];
+ // 娓呯┖鎸囨爣閫夋嫨
+ form.value.testStandardId = "";
+ });
+ };
+
+ // 鎸囨爣閫夋嫨鍙樺寲澶勭悊
+ const handleTestStandardChange = testStandardId => {
+ if (!testStandardId) {
+ tableData.value = [];
+ return;
+ }
+ tableLoading.value = true;
+ getQualityTestStandardParamByTestStandardId(testStandardId)
+ .then(res => {
+ tableData.value = res.data || [];
+ tableData.value = tableData.value.map(item => ({
+ ...item,
+ id: null
+ }));
+ })
+ .catch(error => {
+ console.error("鑾峰彇鏍囧噯鍙傛暟澶辫触:", error);
+ tableData.value = [];
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ };
+ const getQualityInspectParamList = id => {
+ qualityInspectParamInfo(id).then(res => {
+ tableData.value = res.data;
+ });
+ };
+ // 鍏抽棴寮规
+ const closeDia = () => {
+ proxy.resetForm("formRef");
+ tableData.value = [];
+ testStandardOptions.value = [];
+ form.value.testStandardId = "";
+ dialogFormVisible.value = false;
+ emit("close");
+ };
+ defineExpose({
+ openDialog,
+ });
</script>
<style scoped>
-
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/qualityManagement/processInspection/index.vue b/src/views/qualityManagement/processInspection/index.vue
index e5504b6..178e81a 100644
--- a/src/views/qualityManagement/processInspection/index.vue
+++ b/src/views/qualityManagement/processInspection/index.vue
@@ -122,8 +122,18 @@
prop: "unit",
},
{
- label: "鏁伴噺",
+ label: "鎬绘暟閲�",
prop: "quantity",
+ width: 100
+ },
+ {
+ label: "鍚堟牸鏁伴噺",
+ prop: "qualifiedQuantity",
+ width: 100
+ },
+ {
+ label: "涓嶅悎鏍兼暟閲�",
+ prop: "unqualifiedQuantity",
width: 100
},
{
@@ -141,7 +151,7 @@
} else if (params == '鍚堟牸') {
return "success";
} else {
- return null;
+ return 'danger';
}
},
},
@@ -178,6 +188,13 @@
}
return false;
}
+ },
+ {
+ name: "鏌ョ湅",
+ type: "text",
+ clickFun: (row) => {
+ openForm("view", row);
+ },
},
{
name: "闄勪欢",
@@ -363,13 +380,13 @@
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)
})
diff --git a/src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue b/src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue
index 9b89a3b..e4c9700 100644
--- a/src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue
@@ -153,7 +153,7 @@
}
// 涓嬭浇闄勪欢
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
// 棰勮闄勪欢
const lookFile = (row) => {
diff --git a/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue b/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
index 7e373bf..8bcc72b 100644
--- a/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -2,7 +2,7 @@
<div>
<el-dialog
v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板鍘熸潗鏂欐楠�' : '缂栬緫鍘熸潗鏂欐楠�'"
+ :title="operationType === 'add' ? '鏂板鍘熸潗鏂欐楠�' : operationType === 'view' ? '鏌ョ湅鍘熸潗鏂欐楠�' : '缂栬緫鍘熸潗鏂欐楠�'"
width="70%"
@close="closeDia"
>
@@ -14,7 +14,7 @@
v-model="form.supplier"
placeholder="璇烽�夋嫨"
clearable
- :disabled="supplierQuantityDisabled"
+ :disabled="isViewMode || supplierQuantityDisabled"
>
<el-option
v-for="item in supplierList"
@@ -35,7 +35,7 @@
@change="getModels"
:data="productOptions"
:render-after-expand="false"
- :disabled="operationType === 'edit'"
+ :disabled="isViewMode || operationType === 'edit'"
style="width: 100%"
/>
</el-form-item>
@@ -44,7 +44,7 @@
<el-row :gutter="30">
<el-col :span="12">
<el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
- <el-select v-model="form.productModelId" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'edit'"
+ <el-select v-model="form.productModelId" placeholder="璇烽�夋嫨" clearable :disabled="isViewMode || operationType === 'edit'"
filterable readonly @change="handleChangeModel">
<el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
</el-select>
@@ -58,6 +58,7 @@
clearable
@change="handleTestStandardChange"
style="width: 100%"
+ :disabled="isViewMode"
>
<el-option
v-for="item in testStandardOptions"
@@ -78,21 +79,39 @@
<el-col :span="12">
<el-form-item label="鏁伴噺锛�" prop="quantity">
<el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="璇疯緭鍏�"
- clearable :precision="2" :disabled="supplierQuantityDisabled"/>
+ clearable :precision="2" :disabled="isViewMode || supplierQuantityDisabled"/>
</el-form-item>
</el-col>
</el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍚堟牸鏁伴噺锛�" prop="qualifiedQuantity">
+ <el-input-number :step="0.01" :min="0" :max="form.quantity || 0" style="width: 100%"
+ v-model="form.qualifiedQuantity" placeholder="璇疯緭鍏�" :precision="2"
+ @change="onQualifiedChange" :disabled="isViewMode"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓嶅悎鏍兼暟閲忥細" prop="unqualifiedQuantity">
+ <el-input-number :step="0.01" :min="0" :max="form.quantity || 0" style="width: 100%"
+ v-model="form.unqualifiedQuantity" placeholder="璇疯緭鍏�" :precision="2"
+ @change="onUnqualifiedChange" :disabled="isViewMode"/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
<el-row :gutter="30">
<el-col :span="12">
<el-form-item label="妫�娴嬪崟浣嶏細" prop="checkCompany">
- <el-input v-model="form.checkCompany" placeholder="璇疯緭鍏�" clearable/>
+ <el-input v-model="form.checkCompany" placeholder="璇疯緭鍏�" clearable :disabled="isViewMode"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="妫�娴嬬粨鏋滐細" prop="checkResult">
- <el-select v-model="form.checkResult">
+ <el-select v-model="form.checkResult" :disabled="isViewMode">
<el-option label="鍚堟牸" value="鍚堟牸"/>
<el-option label="涓嶅悎鏍�" value="涓嶅悎鏍�"/>
+ <el-option label="閮ㄥ垎鍚堟牸" value="閮ㄥ垎鍚堟牸"/>
</el-select>
</el-form-item>
</el-col>
@@ -100,7 +119,7 @@
<el-row :gutter="30">
<el-col :span="12">
<el-form-item label="妫�楠屽憳锛�" prop="checkName">
- <el-select v-model="form.checkName" placeholder="璇烽�夋嫨" clearable style="width: 100%">
+ <el-select v-model="form.checkName" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="isViewMode">
<el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
</el-select>
</el-form-item>
@@ -115,6 +134,7 @@
format="YYYY-MM-DD"
clearable
style="width: 100%"
+ :disabled="isViewMode"
/>
</el-form-item>
</el-col>
@@ -131,13 +151,16 @@
height="400"
>
<template #slot="{ row }">
- <el-input v-model="row.testValue" clearable/>
+ <el-input v-model="row.testValue" clearable :disabled="isViewMode"/>
</template>
</PIMTable>
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="submitForm">纭</el-button>
- <el-button @click="closeDia">鍙栨秷</el-button>
+ <template v-if="!isViewMode">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </template>
+ <el-button v-else @click="closeDia">鍏抽棴</el-button>
</div>
</template>
</el-dialog>
@@ -182,6 +205,8 @@
testStandardId: [{required: false, message: "璇烽�夋嫨鎸囨爣", trigger: "change"}],
unit: [{required: false, message: "璇疯緭鍏�", trigger: "blur"}],
quantity: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
+ qualifiedQuantity: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
+ unqualifiedQuantity: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
checkCompany: [{required: false, message: "璇疯緭鍏�", trigger: "blur"}],
checkResult: [{required: true, message: "璇烽�夋嫨妫�娴嬬粨鏋�", trigger: "change"}],
},
@@ -220,6 +245,9 @@
const testStandardOptions = ref([]); // 鎸囨爣閫夋嫨涓嬫媺妗嗘暟鎹�
const modelOptions = ref([]);
const userList = ref([]); // 妫�楠屽憳涓嬫媺鍒楄〃
+
+// 鏄惁涓烘煡鐪嬫ā寮�
+const isViewMode = computed(() => operationType.value === 'view');
// 缂栬緫鏃讹細productMainId 鎴� purchaseLedgerId 浠讳竴鏈夊�煎垯渚涘簲鍟嗐�佹暟閲忕疆鐏�
const supplierQuantityDisabled = computed(() => {
@@ -260,7 +288,7 @@
tableData.value = [];
// 鍏堢‘淇濅骇鍝佹爲宸插姞杞斤紝鍚﹀垯缂栬緫鏃朵骇鍝�/瑙勬牸鍨嬪彿鏃犳硶鍙嶆樉
await getProductOptions();
- if (operationType.value === 'edit') {
+ if (operationType.value === 'edit' || operationType.value === 'view') {
// 鍏堜繚瀛� testStandardId锛岄伩鍏嶈娓呯┖
const savedTestStandardId = row.testStandardId;
form.value = {...row}
@@ -294,7 +322,7 @@
// 濡傛灉缂栬緫鏁版嵁涓湁 testStandardId锛屽垯璁剧疆骞跺姞杞藉搴旂殑鍙傛暟
if (savedTestStandardId) {
// 纭繚绫诲瀷鍖归厤锛坕tem.id 鍙兘鏄暟瀛楁垨瀛楃涓诧級
- const matchedOption = testStandardOptions.value.find(item =>
+ const matchedOption = testStandardOptions.value.find(item =>
item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
);
if (matchedOption) {
@@ -435,6 +463,10 @@
tableLoading.value = true;
getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
tableData.value = res.data || [];
+ tableData.value = tableData.value.map(item => ({
+ ...item,
+ id: null
+ }));
}).catch(error => {
console.error('鑾峰彇鏍囧噯鍙傛暟澶辫触:', error);
tableData.value = [];
@@ -448,6 +480,32 @@
tableData.value = res.data;
})
}
+// 鑷姩璁$畻鍚堟牸鏁伴噺鍙樺寲鏃剁殑涓嶅悎鏍兼暟閲�
+const onQualifiedChange = (value) => {
+ if (form.value.quantity !== undefined && form.value.quantity !== null) {
+ const maxUnqualified = form.value.quantity - value;
+ if (maxUnqualified >= 0) {
+ form.value.unqualifiedQuantity = maxUnqualified;
+ } else {
+ form.value.qualifiedQuantity = form.value.quantity;
+ form.value.unqualifiedQuantity = 0;
+ }
+ }
+};
+
+// 鑷姩璁$畻涓嶅悎鏍兼暟閲忓彉鍖栨椂鐨勫悎鏍兼暟閲�
+const onUnqualifiedChange = (value) => {
+ if (form.value.quantity !== undefined && form.value.quantity !== null) {
+ const maxQualified = form.value.quantity - value;
+ if (maxQualified >= 0) {
+ form.value.qualifiedQuantity = maxQualified;
+ } else {
+ form.value.unqualifiedQuantity = form.value.quantity;
+ form.value.qualifiedQuantity = 0;
+ }
+ }
+};
+
// 鍏抽棴寮规
const closeDia = () => {
proxy.resetForm("formRef");
@@ -464,4 +522,4 @@
<style scoped>
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/qualityManagement/rawMaterialInspection/index.vue b/src/views/qualityManagement/rawMaterialInspection/index.vue
index cc2c151..b6adb40 100644
--- a/src/views/qualityManagement/rawMaterialInspection/index.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -124,8 +124,23 @@
prop: "unit",
},
{
- label: "鏁伴噺",
+ label: "鎬绘暟閲�",
prop: "quantity",
+ width: 100
+ },
+ {
+ label: "鍚堟牸鏁伴噺",
+ prop: "qualifiedQuantity",
+ width: 100
+ },
+ {
+ label: "涓嶅悎鏍兼暟閲�",
+ prop: "unqualifiedQuantity",
+ width: 100
+ },
+ {
+ label: "妫�娴嬪崟浣�",
+ prop: "checkCompany",
width: 120
},
{
@@ -143,7 +158,7 @@
} else if (params === '鍚堟牸') {
return "success";
} else {
- return null;
+ return 'danger';
}
},
},
@@ -182,6 +197,13 @@
}
},
{
+ name: "鏌ョ湅",
+ type: "text",
+ clickFun: (row) => {
+ openForm("view", row);
+ },
+ },
+ {
name: "闄勪欢",
type: "text",
clickFun: (row) => {
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
index ccd7504..f4e49b6 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
@@ -23,7 +23,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from './PanelHeader.vue'
@@ -151,6 +151,13 @@
fetchData()
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
})
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
index 0f3ec84..6e39eb1 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
@@ -27,7 +27,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import { productTurnoverDays } from '@/api/viewIndex.js'
@@ -82,6 +82,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
})
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
index 0937b32..055fb66 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
@@ -24,7 +24,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js'
const statItems = ref([])
@@ -52,6 +52,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
})
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
index 669c826..65a72fe 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
@@ -22,7 +22,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
+import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from './PanelHeader.vue'
import CarouselCards from './CarouselCards.vue'
@@ -205,6 +205,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
initBackground()
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
index 8fcaa42..f5dac31 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
@@ -21,7 +21,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
+import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue'
import { productSalesAnalysis } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
import CarouselCards from './CarouselCards.vue'
@@ -175,6 +175,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
initBackground()
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/index.vue b/src/views/reportAnalysis/PSIDataAnalysis/index.vue
index 065b59d..f81e482 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/index.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/index.vue
@@ -43,7 +43,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue'
import autofit from 'autofit.js'
import LeftBottom from './components/left-bottom.vue'
import CenterCenter from './components/center-center.vue'
@@ -65,6 +65,12 @@
// 鐢ㄦ埛store
const userStore = useUserStore()
+
+/** 涓� dataDashboard 鍏辩敤娉ㄥ叆鍚嶏紝瀛愮粍浠讹紙鍚鐢ㄧ殑 right-top/right-bottom锛夋瘡鍒嗛挓鍒锋柊 */
+const DASHBOARD_REFRESH_MS = 60 * 1000
+const dataDashboardRefreshTick = ref(0)
+provide('dataDashboardRefreshTick', dataDashboardRefreshTick)
+let dashboardPollTimer = null
// 璁$畻缂╂斁姣斾緥
const calculateScale = () => {
@@ -140,9 +146,17 @@
window.addEventListener('fullscreenchange', handleFullscreenChange)
window.addEventListener('webkitfullscreenchange', handleFullscreenChange)
window.addEventListener('MSFullscreenChange', handleFullscreenChange)
+
+ dashboardPollTimer = setInterval(() => {
+ dataDashboardRefreshTick.value++
+ }, DASHBOARD_REFRESH_MS)
})
onBeforeUnmount(() => {
+ if (dashboardPollTimer) {
+ clearInterval(dashboardPollTimer)
+ dashboardPollTimer = null
+ }
window.removeEventListener('resize', handleResize)
window.removeEventListener('fullscreenchange', handleFullscreenChange)
window.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue b/src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue
index a4824a2..c28c2fa 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue
@@ -20,7 +20,7 @@
</template>
<script setup>
-import { ref, onMounted, computed } from 'vue'
+import { ref, onMounted, computed, inject, watch } from 'vue'
import { deptStaffDistribution } from '@/api/viewIndex.js'
import PanelHeader from '../PanelHeader.vue'
import Echarts from '@/components/Echarts/echarts.vue'
@@ -148,6 +148,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ getDeptStaffDistribution()
+ })
+}
+
onMounted(() => {
getDeptStaffDistribution()
})
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue b/src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
index 950038e..a7d0174 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
@@ -110,7 +110,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import { ref, onMounted, onBeforeUnmount, nextTick, inject, watch } from 'vue'
import { homeTodos, summaryStatistics } from '@/api/viewIndex.js'
import { getLedgerPage } from '@/api/equipmentManagement/ledger.js'
import { getRepairPage } from '@/api/equipmentManagement/repair.js'
@@ -175,8 +175,23 @@
})
}
+const destroyTodoListScroll = () => {
+ const todoListEl = refTodoList.value
+ if (todoListEl) {
+ if (todoListEl._animationFrame) {
+ cancelAnimationFrame(todoListEl._animationFrame)
+ todoListEl._animationFrame = null
+ }
+ if (todoListEl._pauseTimer) {
+ clearInterval(todoListEl._pauseTimer)
+ todoListEl._pauseTimer = null
+ }
+ }
+}
+
// 鍒濆鍖栧緟鍔炰簨椤瑰垪琛ㄦ粴鍔ㄥ姛鑳�
const initTodoListScroll = () => {
+ destroyTodoListScroll()
const todoListEl = refTodoList.value
// 寮哄埗鍚敤婊氬姩锛屼笉妫�鏌ヤ换浣曟潯浠�
if (todoListEl) {
@@ -259,6 +274,7 @@
// 寰呭姙浜嬮」
const todoInfoS = () => {
+ destroyTodoListScroll()
homeTodos().then((res) => {
todoList.value = res.data
// 鍦ㄨ幏鍙栧埌寰呭姙浜嬮」鏁版嵁鍚庯紝鍒濆鍖栨粴鍔ㄥ姛鑳�
@@ -268,25 +284,25 @@
})
}
-onMounted(() => {
+const refreshCenterTopData = () => {
getNum()
getLedgerNum()
todoInfoS()
+}
+
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ refreshCenterTopData()
+ })
+}
+
+onMounted(() => {
+ refreshCenterTopData()
})
onBeforeUnmount(() => {
- // 娓呯悊寰呭姙浜嬮」鍒楄〃鐨勫姩鐢诲拰瀹氭椂鍣�
- const todoListEl = refTodoList.value
- if (todoListEl) {
- if (todoListEl._animationFrame) {
- cancelAnimationFrame(todoListEl._animationFrame)
- todoListEl._animationFrame = null
- }
- if (todoListEl._pauseTimer) {
- clearInterval(todoListEl._pauseTimer)
- todoListEl._pauseTimer = null
- }
- }
+ destroyTodoListScroll()
})
</script>
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue b/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
index 58c83d8..bab6024 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
@@ -40,7 +40,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import DateTypeSwitch from '../DateTypeSwitch.vue'
@@ -192,6 +192,13 @@
getCustomerRevenueAnalysis()
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ getCustomerRevenueAnalysis()
+ })
+}
+
onMounted(() => {
fetchCustomerOptions()
})
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue b/src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue
index 5b7e29e..16791de 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue
@@ -21,7 +21,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount } from 'vue'
+import { ref, onMounted, onBeforeUnmount, inject, watch } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import { productCategoryDistribution } from '@/api/viewIndex.js'
@@ -207,6 +207,13 @@
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ loadData()
+ })
+}
+
onMounted(() => {
loadData()
initBackground()
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue b/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
index bbcd5f0..6fcd80f 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
@@ -22,7 +22,7 @@
</template>
<script setup>
-import { ref, onMounted, computed } from 'vue'
+import { ref, onMounted, computed, inject, watch } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import DateTypeSwitch from '../DateTypeSwitch.vue'
@@ -308,6 +308,13 @@
fetchCustomerRanking()
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchCustomerRanking()
+ })
+}
+
onMounted(() => {
fetchCustomerRanking()
})
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue b/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
index 21696fa..96e4548 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
@@ -21,7 +21,7 @@
</template>
<script setup>
-import { ref, onMounted, computed } from 'vue'
+import { ref, onMounted, computed, inject, watch } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from '../PanelHeader.vue'
import DateTypeSwitch from '../DateTypeSwitch.vue'
@@ -331,6 +331,13 @@
fetchSupplierRanking()
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchSupplierRanking()
+ })
+}
+
onMounted(() => {
fetchSupplierRanking()
})
diff --git a/src/views/reportAnalysis/dataDashboard/index.vue b/src/views/reportAnalysis/dataDashboard/index.vue
index 67a700f..ff53a7b 100644
--- a/src/views/reportAnalysis/dataDashboard/index.vue
+++ b/src/views/reportAnalysis/dataDashboard/index.vue
@@ -44,7 +44,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue'
import autofit from 'autofit.js'
import LeftTop from './components/basic/left-top.vue'
import LeftBottom from './components/basic/left-bottom.vue'
@@ -65,6 +65,12 @@
// 鐢ㄦ埛store
const userStore = useUserStore()
+
+// 澶у睆鎺ュ彛杞闂撮殧
+const DASHBOARD_REFRESH_MS = 60 * 1000
+const dataDashboardRefreshTick = ref(0)
+provide('dataDashboardRefreshTick', dataDashboardRefreshTick)
+let dashboardPollTimer = null
// 璁$畻缂╂斁姣斾緥
const calculateScale = () => {
@@ -140,9 +146,17 @@
window.addEventListener('fullscreenchange', handleFullscreenChange)
window.addEventListener('webkitfullscreenchange', handleFullscreenChange)
window.addEventListener('MSFullscreenChange', handleFullscreenChange)
+
+ dashboardPollTimer = setInterval(() => {
+ dataDashboardRefreshTick.value++
+ }, DASHBOARD_REFRESH_MS)
})
onBeforeUnmount(() => {
+ if (dashboardPollTimer) {
+ clearInterval(dashboardPollTimer)
+ dashboardPollTimer = null
+ }
window.removeEventListener('resize', handleResize)
window.removeEventListener('fullscreenchange', handleFullscreenChange)
window.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
diff --git a/src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue b/src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
index 46a870a..f4f4024 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
@@ -51,7 +51,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import { ref, onMounted, onBeforeUnmount, nextTick, inject, watch } from 'vue'
import { getProgressStatistics } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
import CarouselCards from './CarouselCards.vue'
@@ -132,17 +132,22 @@
}, 150)
}
-const initProgressTableScroll = () => {
- const tableContainer = progressTableRef.value
- if (!tableContainer) return
+const stopProgressTableScroll = () => {
if (progressTableScrollTimer.value) {
cancelAnimationFrame(progressTableScrollTimer.value)
progressTableScrollTimer.value = null
}
- if (tableContainer._pauseTimer) {
+ const tableContainer = progressTableRef.value
+ if (tableContainer?._pauseTimer) {
clearInterval(tableContainer._pauseTimer)
tableContainer._pauseTimer = null
}
+}
+
+const initProgressTableScroll = () => {
+ const tableContainer = progressTableRef.value
+ if (!tableContainer) return
+ stopProgressTableScroll()
const tbody = tableContainer.querySelector('tbody')
if (!tbody) return
const originalCount = progressTableData.value.length
@@ -198,6 +203,7 @@
const progressStatisticsInfo = () => {
getProgressStatistics()
.then((res) => {
+ stopProgressTableScroll()
if (!res || !res.data) return
const obj = {
totalOrderCount: res.data.totalOrderCount || 0,
@@ -224,14 +230,19 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ progressStatisticsInfo()
+ })
+}
+
onMounted(() => {
progressStatisticsInfo()
})
onBeforeUnmount(() => {
- if (progressTableScrollTimer.value) {
- cancelAnimationFrame(progressTableScrollTimer.value)
- }
+ stopProgressTableScroll()
if (tableScrollTimeout.value) clearTimeout(tableScrollTimeout.value)
const tableContainer = progressTableRef.value
if (tableContainer?._pauseTimer) {
diff --git a/src/views/reportAnalysis/productionAnalysis/components/center-center.vue b/src/views/reportAnalysis/productionAnalysis/components/center-center.vue
index 96bcada..a65f4f8 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/center-center.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/center-center.vue
@@ -27,7 +27,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/Echarts/echarts.vue'
import { inputOutputAnalysis } from '@/api/viewIndex.js'
@@ -146,6 +146,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
})
diff --git a/src/views/reportAnalysis/productionAnalysis/components/center-top.vue b/src/views/reportAnalysis/productionAnalysis/components/center-top.vue
index 7201828..a806150 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/center-top.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/center-top.vue
@@ -24,7 +24,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import { orderCount } from '@/api/viewIndex.js'
const statItems = ref([])
@@ -52,6 +52,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
})
diff --git a/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue b/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
index 9f6a8c1..b7c7358 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
@@ -22,7 +22,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from './PanelHeader.vue'
import CarouselCards from './CarouselCards.vue'
@@ -143,6 +143,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ workInProcessTurnoverInfo()
+ })
+}
+
onMounted(() => {
workInProcessTurnoverInfo()
})
diff --git a/src/views/reportAnalysis/productionAnalysis/components/left-top.vue b/src/views/reportAnalysis/productionAnalysis/components/left-top.vue
index fd52b1b..37c82f0 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/left-top.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/left-top.vue
@@ -23,7 +23,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
+import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue'
import { processOutputAnalysis } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
import Echarts from '@/components/Echarts/echarts.vue'
@@ -170,6 +170,13 @@
fetchData()
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
initBackground()
diff --git a/src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue b/src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue
index 28de03b..62356e7 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue
@@ -22,7 +22,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import { productionAccountingAnalysis } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
import DateTypeSwitch from './DateTypeSwitch.vue'
@@ -157,6 +157,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
})
diff --git a/src/views/reportAnalysis/productionAnalysis/components/right-top.vue b/src/views/reportAnalysis/productionAnalysis/components/right-top.vue
index d3a9eb9..5ec836f 100644
--- a/src/views/reportAnalysis/productionAnalysis/components/right-top.vue
+++ b/src/views/reportAnalysis/productionAnalysis/components/right-top.vue
@@ -22,7 +22,7 @@
</template>
<script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, watch } from 'vue'
import { workOrderEfficiencyAnalysis } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
import Echarts from '@/components/Echarts/echarts.vue'
@@ -152,6 +152,13 @@
})
}
+const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
+if (dataDashboardRefreshTick) {
+ watch(dataDashboardRefreshTick, () => {
+ fetchData()
+ })
+}
+
onMounted(() => {
fetchData()
})
diff --git a/src/views/reportAnalysis/productionAnalysis/index.vue b/src/views/reportAnalysis/productionAnalysis/index.vue
index e179150..7e03bbd 100644
--- a/src/views/reportAnalysis/productionAnalysis/index.vue
+++ b/src/views/reportAnalysis/productionAnalysis/index.vue
@@ -44,7 +44,7 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue'
import autofit from 'autofit.js'
import LeftBottom from './components/left-bottom.vue'
import CenterCenter from './components/center-center.vue'
@@ -66,6 +66,12 @@
// 鐢ㄦ埛store
const userStore = useUserStore()
+
+/** 涓庡叾瀹冮┚椹惰埍鍏辩敤娉ㄥ叆鍚嶏紝瀛愮粍浠舵瘡鍒嗛挓鍒锋柊鎺ュ彛鏁版嵁 */
+const DASHBOARD_REFRESH_MS = 60 * 1000
+const dataDashboardRefreshTick = ref(0)
+provide('dataDashboardRefreshTick', dataDashboardRefreshTick)
+let dashboardPollTimer = null
// 璁$畻缂╂斁姣斾緥
const calculateScale = () => {
@@ -141,9 +147,17 @@
window.addEventListener('fullscreenchange', handleFullscreenChange)
window.addEventListener('webkitfullscreenchange', handleFullscreenChange)
window.addEventListener('MSFullscreenChange', handleFullscreenChange)
+
+ dashboardPollTimer = setInterval(() => {
+ dataDashboardRefreshTick.value++
+ }, DASHBOARD_REFRESH_MS)
})
onBeforeUnmount(() => {
+ if (dashboardPollTimer) {
+ clearInterval(dashboardPollTimer)
+ dashboardPollTimer = null
+ }
window.removeEventListener('resize', handleResize)
window.removeEventListener('fullscreenchange', handleFullscreenChange)
window.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
diff --git a/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue b/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue
index a0146ba..86acdf8 100644
--- a/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue
+++ b/src/views/safeProduction/safeWorkApproval/components/infoFormDia.vue
@@ -218,7 +218,7 @@
approveDeptName: "",
approveReason: "",
checkResult: "",
- storageBlobDTOs: [],
+ storageBlobDTOS: [],
approverList: [], // 鏂板瀛楁锛屽瓨鍌ㄦ墍鏈夎妭鐐圭殑瀹℃壒浜篿d
startDate: "", // 璇峰亣寮�濮嬫椂闂�
endDate: "", // 璇峰亣缁撴潫鏃堕棿
@@ -298,7 +298,7 @@
approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then(
res => {
form.value = { ...res.data };
- form.value.storageBlobDTOs = res.data.storageBlobVOS;
+ form.value.storageBlobDTOS = res.data.storageBlobVOS;
// 鍙嶆樉瀹℃壒浜�
if (res.data && res.data.approveUserIds) {
const userIds = res.data.approveUserIds.split(",");
@@ -388,7 +388,7 @@
return;
}
}
- form.value.storageBlobDTOs = fileList.value;
+ form.value.storageBlobDTOS = fileList.value;
proxy.$refs.formRef.validate(valid => {
if (valid) {
if (operationType.value === "add" || currentApproveStatus.value == 3) {
diff --git a/src/views/safeProduction/safeWorkApproval/fileList.vue b/src/views/safeProduction/safeWorkApproval/fileList.vue
index 5cc65f1..c3b7597 100644
--- a/src/views/safeProduction/safeWorkApproval/fileList.vue
+++ b/src/views/safeProduction/safeWorkApproval/fileList.vue
@@ -32,7 +32,7 @@
tableData.value = list
}
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
const lookFile = (row) => {
diff --git a/src/views/salesManagement/deliveryLedger/index.vue b/src/views/salesManagement/deliveryLedger/index.vue
index d7f47dd..c8890cc 100644
--- a/src/views/salesManagement/deliveryLedger/index.vue
+++ b/src/views/salesManagement/deliveryLedger/index.vue
@@ -3,19 +3,34 @@
<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>
@@ -30,68 +45,143 @@
<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
- <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
- :row-key="(row) => row.id" style="width: 100%" height="calc(100vh - 21.5em)">
- <el-table-column align="center" type="selection" width="55"/>
- <el-table-column align="center" label="搴忓彿" type="index" width="60"/>
- <el-table-column label="閿�鍞鍗�" prop="salesContractNo" show-overflow-tooltip/>
- <el-table-column label="鍙戣揣璁㈠崟鍙�" prop="shippingNo" show-overflow-tooltip/>
- <el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" show-overflow-tooltip/>
- <el-table-column label="浜у搧鍚嶇О" prop="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">
+ <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="totalQuantity"
+ 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="100"
+ >
<template #default="scope">
<el-tag :type="getApprovalStatusType(scope.row.status)">
{{ getApprovalStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
+ <el-table-column
+ label="鍑哄簱鍗曞彿"
+ prop="outboundBatches"
+ show-overflow-tooltip
+ width="130"
+ />
<el-table-column fixed="right" label="鎿嶄綔" width="220" align="center">
<template #default="scope">
+ <!-- <el-button-->
+ <!-- link-->
+ <!-- type="primary"-->
+ <!-- :disabled="!isApproved(scope.row.status)"-->
+ <!-- @click="openForm('edit', scope.row)">鍙戣揣-->
+ <!-- </el-button>-->
<el-button
- link
- type="primary"
- :disabled="!isApproved(scope.row.status)"
- @click="openForm('edit', scope.row)">琛ュ厖鍙戣揣淇℃伅
+ link
+ type="primary"
+ style="color: #67c23a"
+ @click="openDetail(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)">鍒犻櫎
+ 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"/>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
</div>
- <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板鍙戣揣鍙拌处' : '缂栬緫鍙戣揣鍙拌处'"
- width="40%"
- @close="closeDia">
- <el-form :model="form" label-width="120px" label-position="top" :rules="rules" ref="formRef">
+ <el-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>
@@ -100,13 +190,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>
@@ -115,18 +205,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>
@@ -135,9 +225,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>
@@ -145,7 +235,7 @@
<el-row :gutter="30">
<el-col :span="24">
<el-form-item label="鍙戣揣鍥剧墖锛�">
- <ImageUpload v-model:file-list="deliveryFileList" :limit="9"/>
+ <ImageUpload v-model:file-list="deliveryFileList" :limit="9" />
</el-form-item>
</el-col>
</el-row>
@@ -159,42 +249,82 @@
</el-dialog>
<!-- 璇︽儏寮规 -->
- <el-dialog v-model="detailDialogVisible" title="鍙戣揣鍙拌处璇︽儏" width="55%" @close="closeDetail">
+ <el-dialog
+ v-model="detailDialogVisible"
+ title="鍙戣揣鍙拌处璇︽儏"
+ width="55%"
+ @close="closeDetail"
+ >
<div v-if="detailRow" class="detail-wrapper">
<el-descriptions :column="2" border>
- <el-descriptions-item label="閿�鍞鍗�">{{ detailRow.salesContractNo || '--' }}</el-descriptions-item>
- <el-descriptions-item label="鍙戣揣璁㈠崟鍙�">{{ detailRow.shippingNo || '--' }}</el-descriptions-item>
- <el-descriptions-item label="瀹㈡埛鍚嶇О">{{ detailRow.customerName || '--' }}</el-descriptions-item>
- <el-descriptions-item label="浜у搧鍚嶇О">{{ detailRow.productName || '--' }}</el-descriptions-item>
- <el-descriptions-item label="瑙勬牸鍨嬪彿">{{ detailRow.specificationModel || '--' }}</el-descriptions-item>
- <el-descriptions-item label="鍙戣揣绫诲瀷">{{ detailRow.type || '--' }}</el-descriptions-item>
- <el-descriptions-item label="鍙戣揣鏃ユ湡">{{ detailRow.shippingDate || '--' }}</el-descriptions-item>
- <el-descriptions-item label="瀹℃牳鐘舵��">{{ getApprovalStatusText(detailRow.status) }}</el-descriptions-item>
- <el-descriptions-item label="鍙戣揣杞︾墝鍙�">{{ detailRow.shippingCarNumber || '--' }}</el-descriptions-item>
- <el-descriptions-item label="蹇�掑叕鍙�">{{ detailRow.expressCompany || '--' }}</el-descriptions-item>
- <el-descriptions-item label="蹇�掑崟鍙�" :span="2">{{ detailRow.expressNumber || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="閿�鍞鍗�">{{
+ detailRow.salesContractNo || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣璁㈠崟鍙�">{{
+ detailRow.shippingNo || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="瀹㈡埛鍚嶇О">{{
+ detailRow.customerName || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="浜у搧鍚嶇О">{{
+ detailRow.productName || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="瑙勬牸鍨嬪彿">{{
+ detailRow.specificationModel || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣绫诲瀷">{{
+ detailRow.type || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣鏃ユ湡">{{
+ detailRow.shippingDate || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="瀹℃牳鐘舵��">{{
+ getApprovalStatusText(detailRow.status)
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣杞︾墝鍙�">{{
+ detailRow.shippingCarNumber || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="蹇�掑叕鍙�">{{
+ detailRow.expressCompany || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="蹇�掑崟鍙�" :span="2">{{
+ detailRow.expressNumber || "--"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鍑哄簱鍗曞彿" :span="2">{{
+ detailRow.outboundBatches || "--"
+ }}</el-descriptions-item>
</el-descriptions>
- <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
+ :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>
@@ -209,21 +339,20 @@
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
-import {onMounted, ref, reactive, toRefs, getCurrentInstance} from "vue";
-import {ElMessageBox} from "element-plus";
-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,
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);
@@ -261,23 +390,34 @@
expressNumber: "", // 蹇�掑崟鍙�
},
rules: {
- salesContractNo: [{required: true, message: "璇烽�夋嫨閿�鍞鍗�", trigger: "change"}],
- customerName: [{required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur"}],
- type: [
- {required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change"}
+ salesContractNo: [
+ { required: true, message: "璇烽�夋嫨閿�鍞鍗�", trigger: "change" },
],
- shippingDate: [{required: true, message: "璇烽�夋嫨鍙戣揣鏃堕棿", trigger: "change"}],
+ customerName: [
+ { required: true, message: "璇疯緭鍏ュ鎴峰悕绉�", trigger: "blur" },
+ ],
+ type: [{ required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change" }],
+ shippingDate: [
+ { required: true, message: "璇烽�夋嫨鍙戣揣鏃堕棿", trigger: "change" },
+ ],
shippingCarNumber: [
- {validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur"}
+ {
+ 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 = () => {
@@ -293,20 +433,22 @@
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;
+ });
};
// 閿�鍞鍗曞彉鍖栨椂鑷姩濉厖瀹㈡埛鍚嶇О
const handleSalesOrderChange = (value) => {
- const selectedOrder = salesOrderOptions.value.find(item => item.salesContractNo === value);
+ const selectedOrder = salesOrderOptions.value.find(
+ (item) => item.salesContractNo === value
+ );
if (selectedOrder) {
form.value.customerName = selectedOrder.customerName;
}
@@ -319,15 +461,15 @@
// 鎵撳紑寮规
const openForm = async (type, row) => {
- // 琛ュ厖鍙戣揣淇℃伅锛氫粎鈥滃鏍搁�氳繃鈥濆厑璁哥紪杈�
- if (type === 'edit' && row && !isApproved(row.status)) {
- proxy.$modal.msgWarning("鍙湁瀹℃牳閫氳繃鐨勬暟鎹墠鍙互琛ュ厖鍙戣揣淇℃伅");
+ // 鍙戣揣锛氫粎鈥滃鏍搁�氳繃鈥濆厑璁哥紪杈�
+ if (type === "edit" && row && !isApproved(row.status)) {
+ proxy.$modal.msgWarning("鍙湁瀹℃牳閫氳繃鐨勬暟鎹墠鍙互鍙戣揣");
return;
}
operationType.value = type;
- if (type === 'edit' && row) {
+ if (type === "edit" && row) {
form.value = {
id: row.id ?? null,
salesContractNo: row.salesContractNo ?? "",
@@ -362,62 +504,69 @@
proxy.$modal.msgError("鍔犺浇鍙戣揣鍙拌处璇︽儏澶辫触");
}
};
-const resolveDeliveryDetailList = data => {
+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) || [];
+ 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 => {
+ 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
+ .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 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 ?? "--",
+ 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 ?? "--",
+ item.specificationModel ?? item.model ?? row.specificationModel ?? "--",
deliveryQuantity:
- item.deliveryQuantity ??
- item.quantity ??
- item.shippingQuantity ??
- row.deliveryQuantity ??
- row.quantity ??
- "--",
+ 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 batchNoList.map((batchNo) => toTableRow({ batchNo }));
}
return [toTableRow()];
};
@@ -435,9 +584,12 @@
id: form.value.id,
type: form.value.type,
shippingDate: form.value.shippingDate,
- shippingCarNumber: form.value.type === "璐ц溅" ? form.value.shippingCarNumber : "",
- expressCompany: form.value.type === "蹇��" ? form.value.expressCompany : "",
- expressNumber: form.value.type === "蹇��" ? form.value.expressNumber : "",
+ shippingCarNumber:
+ form.value.type === "璐ц溅" ? form.value.shippingCarNumber : "",
+ expressCompany:
+ form.value.type === "蹇��" ? form.value.expressCompany : "",
+ expressNumber:
+ form.value.type === "蹇��" ? form.value.expressNumber : "",
storageBlobDTOs: deliveryFileList.value || [],
};
deductStock(payload).then((res) => {
@@ -463,12 +615,12 @@
cancelButtonText: "鍙栨秷",
type: "warning",
})
- .then(() => {
- proxy.download("/shippingInfo/export", {}, "鍙戣揣鍙拌处.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ .then(() => {
+ proxy.download("/shippingInfo/export", {}, "鍙戣揣鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鎵归噺鍒犻櫎
@@ -479,7 +631,9 @@
}
// 妫�鏌ラ�変腑鐨勮鏄惁鏈�"瀹℃牳涓�"鐘舵��
- const approvingRows = selectedRows.value.filter(row => isApproving(row.status));
+ const approvingRows = selectedRows.value.filter((row) =>
+ isApproving(row.status)
+ );
if (approvingRows.length > 0) {
proxy.$modal.msgWarning("瀹℃牳涓殑鏁版嵁涓嶈兘鍒犻櫎");
return;
@@ -491,15 +645,15 @@
cancelButtonText: "鍙栨秷",
type: "warning",
})
- .then(() => {
- delDeliveryLedger(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
+ .then(() => {
+ delDeliveryLedger(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
});
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鍗曚釜鍒犻櫎
@@ -515,15 +669,15 @@
cancelButtonText: "鍙栨秷",
type: "warning",
})
- .then(() => {
- delDeliveryLedger([row.id]).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
+ .then(() => {
+ delDeliveryLedger([row.id]).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
});
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
};
// 鍙戣揣绫诲瀷鏍¢獙锛氳揣杞︽椂瑕佹眰杞︾墝锛屽揩閫掓椂瑕佹眰蹇�掑叕鍙�
@@ -543,7 +697,10 @@
// 鍙戣揣鍥剧墖涓婁紶鍓嶆牎妫�
function handleDeliveryBeforeUpload(file) {
// 鏍℃鏂囦欢绫诲瀷
- const isImage = file.type === 'image/png' || file.type === 'image/jpeg' || file.type === 'image/jpg';
+ const isImage =
+ file.type === "image/png" ||
+ file.type === "image/jpeg" ||
+ file.type === "image/jpg";
if (!isImage) {
proxy.$modal.msgError("鍙兘涓婁紶 jpg銆乯peg銆乸ng 鏍煎紡鐨勫浘鐗�!");
return false;
@@ -578,24 +735,30 @@
// 绉婚櫎鍙戣揣鍥剧墖
function handleDeliveryRemove(file) {
- console.log('file--', file)
+ console.log("file--", file);
// 濡傛灉鏄紪杈戞ā寮忎笖鏂囦欢鏈� id锛岄渶瑕佽皟鐢ㄦ帴鍙e垹闄�
if (operationType.value === "edit") {
let ids = [];
ids.push(file.uid);
- delLedgerFile(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- // 浠庢枃浠跺垪琛ㄤ腑绉婚櫎
- const index = deliveryFileList.value.findIndex(item => item.uid === file.uid);
- if (index > -1) {
- deliveryFileList.value.splice(index, 1);
- }
- }).catch(() => {
- proxy.$modal.msgError("鍒犻櫎澶辫触");
- });
+ delLedgerFile(ids)
+ .then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ // 浠庢枃浠跺垪琛ㄤ腑绉婚櫎
+ const index = deliveryFileList.value.findIndex(
+ (item) => item.uid === file.uid
+ );
+ if (index > -1) {
+ deliveryFileList.value.splice(index, 1);
+ }
+ })
+ .catch(() => {
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ });
} else {
// 鏂板妯″紡鎴栨病鏈� id 鐨勬枃浠讹紝鐩存帴浠庡垪琛ㄤ腑绉婚櫎
- const index = deliveryFileList.value.findIndex(item => item.uid === file.uid);
+ const index = deliveryFileList.value.findIndex(
+ (item) => item.uid === file.uid
+ );
if (index > -1) {
deliveryFileList.value.splice(index, 1);
}
@@ -614,92 +777,92 @@
// 鑾峰彇瀹℃牳鐘舵�佹枃鏈�
const getApprovalStatusText = (status) => {
- if (status === null || status === undefined || status === '') {
- return '寰呭鏍�';
+ if (status === null || status === undefined || status === "") {
+ return "寰呭鏍�";
}
// 濡傛灉鏄暟瀛�
- if (typeof status === 'number') {
+ if (typeof status === "number") {
const statusMap = {
- 0: '寰呭鏍�',
- 1: '瀹℃牳涓�',
- 2: '瀹℃牳鎷掔粷',
- 3: '瀹℃牳閫氳繃'
+ 0: "寰呭鏍�",
+ 1: "瀹℃牳涓�",
+ 2: "瀹℃牳鎷掔粷",
+ 3: "瀹℃牳閫氳繃",
};
- return statusMap[status] || '寰呭鏍�';
+ return statusMap[status] || "寰呭鏍�";
}
// 濡傛灉鏄瓧绗︿覆锛岀洿鎺ヨ繑鍥炴垨鏄犲皠
const statusStr = String(status).trim();
const statusTextMap = {
- '寰呭鏍�': '寰呭鏍�',
- '瀹℃牳涓�': '瀹℃牳涓�',
- '瀹℃牳鎷掔粷': '瀹℃牳鎷掔粷',
- '瀹℃牳閫氳繃': '瀹℃牳閫氳繃',
- '宸插彂璐�': '宸插彂璐�',
- '0': '寰呭鏍�',
- '1': '瀹℃牳涓�',
- '2': '瀹℃牳鎷掔粷',
- '3': '瀹℃牳閫氳繃'
+ 寰呭鏍�: "寰呭鏍�",
+ 瀹℃牳涓�: "瀹℃牳涓�",
+ 瀹℃牳鎷掔粷: "瀹℃牳鎷掔粷",
+ 瀹℃牳閫氳繃: "瀹℃牳閫氳繃",
+ 宸插彂璐�: "宸插彂璐�",
+ 0: "寰呭鏍�",
+ 1: "瀹℃牳涓�",
+ 2: "瀹℃牳鎷掔粷",
+ 3: "瀹℃牳閫氳繃",
};
- return statusTextMap[statusStr] || statusStr || '寰呭鏍�';
+ return statusTextMap[statusStr] || statusStr || "寰呭鏍�";
};
// 鑾峰彇瀹℃牳鐘舵�佹爣绛剧被鍨嬶紙棰滆壊锛�
const getApprovalStatusType = (status) => {
- if (status === null || status === undefined || status === '') {
- return 'info';
+ if (status === null || status === undefined || status === "") {
+ return "info";
}
// 濡傛灉鏄暟瀛�
- if (typeof status === 'number') {
+ if (typeof status === "number") {
const typeMap = {
- 0: 'info', // 寰呭鏍� - 鐏拌壊
- 1: 'warning', // 瀹℃牳涓� - 榛勮壊
- 2: 'danger', // 瀹℃牳鎷掔粷 - 绾㈣壊
- 3: 'success' // 瀹℃牳閫氳繃 - 缁胯壊
+ 0: "info", // 寰呭鏍� - 鐏拌壊
+ 1: "warning", // 瀹℃牳涓� - 榛勮壊
+ 2: "danger", // 瀹℃牳鎷掔粷 - 绾㈣壊
+ 3: "success", // 瀹℃牳閫氳繃 - 缁胯壊
};
- return typeMap[status] || 'info';
+ return typeMap[status] || "info";
}
// 濡傛灉鏄瓧绗︿覆
const statusStr = String(status).trim();
const typeTextMap = {
- '寰呭鏍�': 'info',
- '瀹℃牳涓�': 'warning',
- '瀹℃牳鎷掔粷': 'danger',
- '瀹℃牳閫氳繃': 'success',
- '宸插彂璐�': 'success',
- '0': 'info',
- '1': 'warning',
- '2': 'danger',
- '3': 'success'
+ 寰呭鏍�: "info",
+ 瀹℃牳涓�: "warning",
+ 瀹℃牳鎷掔粷: "danger",
+ 瀹℃牳閫氳繃: "success",
+ 宸插彂璐�: "success",
+ 0: "info",
+ 1: "warning",
+ 2: "danger",
+ 3: "success",
};
- return typeTextMap[statusStr] || 'info';
+ return typeTextMap[statusStr] || "info";
};
// 妫�鏌ュ鏍哥姸鎬佹槸鍚︿负"瀹℃牳閫氳繃"
const isApproved = (status) => {
- if (status === null || status === undefined || status === '') {
+ if (status === null || status === undefined || status === "") {
return false;
}
// 濡傛灉鏄暟瀛楋紝3 琛ㄧず瀹℃牳閫氳繃
- if (typeof status === 'number') {
+ if (typeof status === "number") {
return status === 3;
}
// 濡傛灉鏄瓧绗︿覆
const statusStr = String(status).trim();
- return statusStr === '瀹℃牳閫氳繃' || statusStr === '3';
+ return statusStr === "瀹℃牳閫氳繃" || statusStr === "3";
};
// 妫�鏌ュ鏍哥姸鎬佹槸鍚︿负"瀹℃牳涓�"
const isApproving = (status) => {
- if (status === null || status === undefined || status === '') {
+ if (status === null || status === undefined || status === "") {
return false;
}
// 濡傛灉鏄暟瀛楋紝1 琛ㄧず瀹℃牳涓�
- if (typeof status === 'number') {
+ if (typeof status === "number") {
return status === 1;
}
// 濡傛灉鏄瓧绗︿覆
const statusStr = String(status).trim();
- return statusStr === '瀹℃牳涓�' || statusStr === '1';
+ return statusStr === "瀹℃牳涓�" || statusStr === "1";
};
onMounted(() => {
@@ -752,4 +915,3 @@
color: #909399;
}
</style>
-
diff --git a/src/views/salesManagement/invoiceLedger/index.vue b/src/views/salesManagement/invoiceLedger/index.vue
index 444560d..e52ea47 100644
--- a/src/views/salesManagement/invoiceLedger/index.vue
+++ b/src/views/salesManagement/invoiceLedger/index.vue
@@ -409,12 +409,6 @@
}
};
-const handleFile = (commonFiles) => {
- commonFiles.forEach((e) => {
- proxy.$download.name(e.url);
- });
-};
-
const clearRange = () => {
searchForm.invoiceDate = [];
searchForm.invoiceDateStart = undefined;
diff --git a/src/views/salesManagement/returnOrder/components/detailDia.vue b/src/views/salesManagement/returnOrder/components/detailDia.vue
index 9ff4e89..ecc663f 100644
--- a/src/views/salesManagement/returnOrder/components/detailDia.vue
+++ b/src/views/salesManagement/returnOrder/components/detailDia.vue
@@ -10,8 +10,8 @@
<el-descriptions-item label="瀹㈡埛鍚嶇О">{{ detail.customerName }}</el-descriptions-item>
<el-descriptions-item label="閿�鍞崟鍙�">{{ detail.salesContractNo }}</el-descriptions-item>
<el-descriptions-item label="涓氬姟鍛�">{{ detail.salesman }}</el-descriptions-item>
- <el-descriptions-item label="鍏宠仈鍑哄簱鍗曞彿">{{ detail.shippingNo }}</el-descriptions-item>
- <el-descriptions-item label="椤圭洰鍚嶇О">{{ detail.projectName }}</el-descriptions-item>
+ <el-descriptions-item label="鍏宠仈鍙戣揣鍗曞彿">{{ detail.shippingNo }}</el-descriptions-item>
+ <!-- <el-descriptions-item label="椤圭洰鍚嶇О">{{ detail.projectName }}</el-descriptions-item> -->
<el-descriptions-item label="鍒跺崟浜�">{{ detail.maker }}</el-descriptions-item>
<el-descriptions-item label="鍒跺崟鏃堕棿">{{ detail.makeTime }}</el-descriptions-item>
<el-descriptions-item label="閫�璐у師鍥�">{{ detail.returnReason }}</el-descriptions-item>
@@ -20,7 +20,11 @@
<div style="padding-top: 20px">
<span class="descriptions">浜у搧鍒楄〃</span>
- <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData" />
+ <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData">
+ <template #totalReturnNum="{ row }">
+ {{ calcAlreadyReturned(row) }}
+ </template>
+ </PIMTable>
</div>
</div>
<template #footer>
@@ -41,12 +45,214 @@
const tableData = ref([]);
const availableProducts = ref([]);
+const sameKey = (a, b) => a != null && b != null && String(a) === String(b);
+
+/** 涓� formDia 涓�鑷达細涓や唤鍒楄〃鎸� id 鍚堝苟锛岄伩鍏嶅彧鍙� productDtoData 鏃剁己鍑哄簱鍗曞彿/鎵规/鏁伴噺 */
+const mergeShippingProductLists = (data) => {
+ const lists = [data?.shippingProductVoList, data?.productDtoData].filter(Array.isArray);
+ if (!lists.length) return [];
+ const map = new Map();
+ for (const list of lists) {
+ for (const p of list) {
+ if (p == null) continue;
+ const key = p.id != null ? String(p.id) : null;
+ if (!key) continue;
+ const prev = map.get(key);
+ map.set(key, prev ? { ...prev, ...p } : { ...p });
+ }
+ }
+ return Array.from(map.values());
+};
+
+const pickShippingLine = (normalized) => {
+ const pid = normalized?.returnSaleLedgerProductId ?? normalized?.id;
+ const sid = normalized?.stockOutRecordId ?? normalized?.shippingProductId;
+ const direct = availableProducts.value.find(
+ (p) =>
+ sameKey(p?.id, pid) ||
+ sameKey(p?.stockOutRecordId, pid) ||
+ sameKey(p?.id, sid) ||
+ sameKey(p?.stockOutRecordId, sid)
+ );
+ if (direct) return direct;
+ const pmid = normalized?.productModelId;
+ if (pmid == null || pmid === "") return undefined;
+ const candidates = availableProducts.value.filter((p) => sameKey(p?.productModelId, pmid));
+ if (!candidates.length) return undefined;
+ if (candidates.length === 1) return candidates[0];
+ const spec = String(normalized?.specificationModel ?? normalized?.model ?? "");
+ if (spec) {
+ const hit = candidates.find((p) => {
+ const ps = String(p?.specificationModel ?? p?.model ?? "");
+ return ps && ps === spec;
+ });
+ if (hit) return hit;
+ }
+ return candidates[0];
+};
+
+const isEmptyText = (v) => v === "" || v == null || v === undefined;
+
+const firstFiniteNumber = (...vals) => {
+ for (const v of vals) {
+ if (v === "" || v == null || v === undefined) continue;
+ const n = Number(v);
+ if (Number.isFinite(n)) return n;
+ }
+ return undefined;
+};
+
+const firstNonEmptyText = (...vals) => {
+ const hit = vals.find((v) => !isEmptyText(v));
+ return hit === undefined ? "" : hit;
+};
+
+const calcAlreadyReturned = (row) => {
+ const total = Number(row?.stockOutNum ?? row?.totalQuantity ?? row?.totalReturnNum ?? 0);
+ const un = Number(row?.unQuantity ?? 0);
+ if (!Number.isFinite(total) || !Number.isFinite(un)) return 0;
+ return Math.max(total - un, 0);
+};
+
+/** 璇︽儏琛ㄧ敤 productName / model锛涘悎骞舵椂鍕胯绌轰覆鐩栨帀鍑哄簱琛屽瓧娈� */
+const mergeDetailProductRow = (product, normalized) => {
+ const row = { ...product, ...normalized };
+ row.outboundBatches = firstNonEmptyText(
+ row.outboundBatches,
+ product?.outboundBatches,
+ product?.shippingNo,
+ product?.outboundNo,
+ normalized?.outboundBatches,
+ normalized?.outboundNo,
+ normalized?.shippingNo
+ );
+ row.batchNo = firstNonEmptyText(
+ row.batchNo,
+ product?.batchNo,
+ product?.batchNumber,
+ product?.lotNo,
+ product?.batchCode,
+ product?.shippingBatchNo,
+ normalized?.batchNo,
+ normalized?.batchNumber,
+ normalized?.lotNo,
+ normalized?.shippingBatchNo
+ );
+ const stock = firstFiniteNumber(
+ row.stockOutNum,
+ product?.stockOutNum,
+ product?.totalQuantity,
+ product?.shippingQuantity,
+ product?.deliveryQuantity,
+ product?.quantity,
+ product?.outQuantity,
+ normalized?.stockOutNum,
+ normalized?.totalQuantity,
+ normalized?.shippingQuantity,
+ normalized?.deliveryQuantity
+ );
+ if (stock !== undefined) row.stockOutNum = stock;
+ const un = firstFiniteNumber(
+ row.unQuantity,
+ product?.unQuantity,
+ product?.remainingQuantity,
+ product?.noReturnQuantity,
+ product?.canReturnQuantity,
+ product?.availableReturnNum,
+ normalized?.unQuantity,
+ normalized?.remainingQuantity,
+ normalized?.noReturnQuantity,
+ normalized?.canReturnQuantity
+ );
+ if (un !== undefined) row.unQuantity = un;
+ else {
+ const s = Number(row.stockOutNum);
+ const ret = Number(row.totalReturnNum ?? 0);
+ if (Number.isFinite(s) && s >= 0 && Number.isFinite(ret) && ret >= 0) {
+ row.unQuantity = Math.max(0, s - ret);
+ }
+ }
+ const returned = firstFiniteNumber(
+ row.totalReturnNum,
+ product?.totalReturnNum,
+ product?.totalReturnedNum,
+ normalized?.totalReturnNum,
+ normalized?.totalReturnedNum
+ );
+ if (returned !== undefined) row.totalReturnNum = returned;
+ else if (isEmptyText(row.totalReturnNum)) row.totalReturnNum = 0;
+ if (isEmptyText(row.unit)) {
+ row.unit = firstNonEmptyText(product?.unit, normalized?.unit);
+ }
+ row.productName = firstNonEmptyText(
+ row.productName,
+ normalized?.productName,
+ normalized?.productCategory,
+ product?.productName,
+ product?.productCategory
+ );
+ row.model = firstNonEmptyText(
+ row.model,
+ normalized?.model,
+ normalized?.specificationModel,
+ product?.model,
+ product?.specificationModel
+ );
+ return row;
+};
+
+const normalizeDetailRow = (raw) => {
+ const ledgerId =
+ raw?.returnSaleLedgerProductId ??
+ raw?.saleLedgerProductId ??
+ raw?.stockOutRecordId ??
+ raw?.shippingProductId;
+ const productId = ledgerId ?? raw?.id;
+ const num = Number(raw?.num ?? raw?.returnQuantity ?? 0);
+ return {
+ ...raw,
+ id: productId,
+ returnSaleLedgerProductId: productId,
+ productModelId: raw?.productModelId,
+ stockOutRecordId: raw?.stockOutRecordId,
+ shippingProductId: raw?.shippingProductId,
+ productName: raw?.productName ?? raw?.productCategory ?? raw?.productTypeName ?? "",
+ model: raw?.model ?? raw?.specificationModel ?? raw?.specModel ?? "",
+ outboundBatches: raw?.outboundBatches ?? raw?.outboundNo ?? raw?.shippingNo,
+ batchNo:
+ raw?.batchNo ??
+ raw?.batchNumber ??
+ raw?.lotNo ??
+ raw?.batchCode ??
+ raw?.shippingBatchNo,
+ stockOutNum:
+ raw?.stockOutNum ??
+ raw?.totalQuantity ??
+ raw?.shippingQuantity ??
+ raw?.deliveryQuantity ??
+ raw?.quantity,
+ totalReturnNum: raw?.totalReturnNum ?? raw?.totalReturnedNum,
+ unQuantity:
+ raw?.unQuantity ??
+ raw?.remainingQuantity ??
+ raw?.noReturnQuantity ??
+ raw?.canReturnQuantity,
+ returnQuantity: Number.isFinite(num) ? num : 0,
+ price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0),
+ amount: Number(raw?.amount ?? 0).toFixed(2),
+ isQuality: raw?.isQuality ?? 2,
+ remark: raw?.remark ?? "",
+ };
+};
+
const tableColumn = [
- {align: "center", label: "浜у搧澶х被", prop: "productCategory"},
- {align: "center", label: "瑙勬牸鍨嬪彿", prop: "specificationModel"},
+ {align: "center", label: "鍑哄簱鍗曞彿", prop: "outboundBatches"},
+ {align: "center", label: "鎵规鍙�", prop: "batchNo"},
+ {align: "center", label: "浜у搧澶х被", prop: "productName"},
+ {align: "center", label: "瑙勬牸鍨嬪彿", prop: "model"},
{align: "center", label: "鍗曚綅", prop: "unit", width: 80},
- {align: "center", label: "鎬绘暟閲�", prop: "quantity", width: 120},
- {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120},
+ {align: "center", label: "鎬绘暟閲�", prop: "stockOutNum", width: 120},
+ {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120, dataType: "slot", slot: "totalReturnNum"},
{align: "center", label: "鏈��璐ф暟閲�", prop: "unQuantity", width: 120},
{align: "center", label: "閫�璐ф暟閲�", prop: "returnQuantity", width: 120},
{align: "center", label: "閫�璐т骇鍝佸崟浠�", prop: "price", width: 120},
@@ -82,30 +288,30 @@
if (detail.value.shippingId) {
const productRes = await returnManagementGetByShippingId({ shippingId: detail.value.shippingId });
if (productRes.code === 200) {
- availableProducts.value = productRes.data.productDtoData || [];
+ availableProducts.value = mergeShippingProductLists(productRes.data);
}
}
-
+
const list =
detail.value?.returnSaleProducts ||
- detail.value?.returnSaleProductList ||
- detail.value?.returnSaleProductDtoData ||
- [];
-
- tableData.value = Array.isArray(list) ? list.map(raw => {
- const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id;
- const product = availableProducts.value.find((p) => p.id === productId);
- const normalized = {
- ...raw,
- id: productId,
- returnQuantity: Number(raw?.num ?? raw?.returnQuantity ?? 0),
- price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0),
- amount: Number(raw?.amount ?? 0).toFixed(2),
- isQuality: raw?.isQuality ?? 2,
- remark: raw?.remark ?? "",
- };
- return product ? { ...product, ...normalized } : normalized;
- }) : [];
+ detail.value?.returnSaleProductList ||
+ detail.value?.returnSaleProductDtoData ||
+ [];
+
+ tableData.value = Array.isArray(list)
+ ? list.map((raw) => {
+ const normalized = normalizeDetailRow(raw);
+ const product = pickShippingLine(normalized);
+ return product ? mergeDetailProductRow(product, normalized) : normalized;
+ })
+ : [];
+
+ const headerShipNo = detail.value?.shippingNo;
+ if (headerShipNo && Array.isArray(tableData.value) && tableData.value.length) {
+ tableData.value = tableData.value.map((r) =>
+ isEmptyText(r.outboundBatches) ? { ...r, outboundBatches: headerShipNo } : r
+ );
+ }
} catch (e) {
console.error("Failed to load detail", e);
} finally {
diff --git a/src/views/salesManagement/returnOrder/components/formDia.vue b/src/views/salesManagement/returnOrder/components/formDia.vue
index 7a35ea9..6a6d756 100644
--- a/src/views/salesManagement/returnOrder/components/formDia.vue
+++ b/src/views/salesManagement/returnOrder/components/formDia.vue
@@ -32,7 +32,7 @@
</el-form-item>
</el-col>
<el-col :span="4">
- <el-form-item label="鍏宠仈鍑哄簱鍗曞彿锛�" prop="shippingId">
+ <el-form-item label="鍏宠仈鍙戣揣鍗曞彿锛�" prop="shippingId">
<el-select v-model="form.shippingId" filterable placeholder="璇烽�夋嫨鍑哄簱鍗曞彿" @change="outboundNoChange">
<el-option
v-for="item in outboundOptions"
@@ -55,14 +55,14 @@
<el-date-picker v-model="form.makeTime" type="datetime" style="width:100%" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" />
</el-form-item>
</el-col>
- <el-col :span="4">
+ <!-- <el-col :span="4">
<el-form-item label="鐘舵�侊細" prop="status">
<el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��">
<el-option label="寰呭鐞�" :value="0" />
<el-option label="宸插鐞�" :value="1" />
</el-select>
</el-form-item>
- </el-col>
+ </el-col> -->
<el-col :span="4">
<el-form-item label="閫�璐у師鍥狅細" prop="returnReason">
<el-input v-model="form.returnReason" placeholder="璇疯緭鍏ラ��璐у師鍥�" />
@@ -82,6 +82,9 @@
<el-button type="primary" @click="openProductSelection" :disabled="!form.shippingId">娣诲姞浜у搧</el-button>
</div>
<PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData">
+ <template #totalReturnNum="{ row }">
+ {{ calcAlreadyReturned(row) }}
+ </template>
<template #returnQuantity="{ row }">
<el-input
v-model="row.returnQuantity"
@@ -122,7 +125,7 @@
placeholder="璇疯緭鍏�"
/>
</template>
- <template #action="{ row, index }">
+ <template #action="{ index }">
<el-button type="danger" link @click="deleteRow(index)">鍒犻櫎</el-button>
</template>
</PIMTable>
@@ -145,10 +148,12 @@
row-key="id"
>
<el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" prop="outboundBatches" label="鍑哄簱鍗曞彿" />
+ <el-table-column align="center" prop="batchNo" label="鎵规鍙�" />
<el-table-column align="center" prop="productCategory" label="浜у搧澶х被" />
<el-table-column align="center" prop="specificationModel" label="瑙勬牸鍨嬪彿" />
<el-table-column align="center" prop="unit" label="鍗曚綅" />
- <el-table-column align="center" prop="quantity" label="鎬绘暟閲�" />
+ <el-table-column align="center" prop="stockOutNum" label="鎬绘暟閲�" />
<el-table-column align="center" prop="unQuantity" label="鏈��璐ф暟閲�" />
<el-table-column align="center" label="宸查��璐ф暟閲�">
<template #default="{ row }">{{ calcAlreadyReturned(row) }}</template>
@@ -170,7 +175,7 @@
import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById } from "@/api/salesManagement/returnOrder.js";
import useUserStore from "@/store/modules/user.js";
import { userListNoPageByTenantId } from "@/api/system/user.js";
-import { listProject } from "@/api/oaSystem/projectManagement.js";
+import { listProject } from "@/api/projectManagement/project.js";
import {listCustomer} from "@/api/basicData/customer.js";
const { proxy } = getCurrentInstance();
@@ -208,18 +213,20 @@
const { form, rules } = toRefs(data);
const calcAlreadyReturned = (row) => {
- const total = Number(row?.quantity ?? row?.totalQuantity ?? row?.totalReturnNum ?? 0);
+ const total = Number(row?.stockOutNum ?? row?.totalQuantity ?? row?.totalReturnNum ?? 0);
const un = Number(row?.unQuantity ?? 0);
if (!Number.isFinite(total) || !Number.isFinite(un)) return 0;
return Math.max(total - un, 0);
};
const tableColumn = ref([
+ {align: "center", label: "鍑哄簱鍗曞彿", prop: "outboundBatches" },
+ {align: "center", label: "鎵规鍙�", prop: "batchNo" },
{align: "center", label: "浜у搧澶х被", prop: "productCategory" },
{align: "center", label: "瑙勬牸鍨嬪彿", prop: "specificationModel" },
{align: "center", label: "鍗曚綅", prop: "unit", width: 80 },
- {align: "center", label: "鎬绘暟閲�", prop: "quantity", width: 120 },
- {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120 },
+ {align: "center", label: "鎬绘暟閲�", prop: "stockOutNum", width: 120 },
+ {align: "center", label: "宸查��璐ф暟閲�", prop: "totalReturnNum", width: 120, dataType: "slot", slot: "totalReturnNum" },
{align: "center", label: "鏈��璐ф暟閲�", prop: "unQuantity", width: 120 },
{align: "center", label: "閫�璐ф暟閲�", prop: "returnQuantity", dataType: "slot", slot: "returnQuantity", width: 120 },
{align: "center", label: "閫�璐т骇鍝佸崟浠�", prop: "price", dataType: "slot", slot: "price", width: 120 },
@@ -238,8 +245,164 @@
tableData.value.splice(index, 1);
};
+const sameKey = (a, b) => a != null && b != null && String(a) === String(b);
+
+/** 鎺ュ彛鍙兘鎷嗘垚 shippingProductVoList / productDtoData 涓や唤锛屽彧鍙栧叾涓�浼氱己鎵规銆佹暟閲忕瓑瀛楁 */
+const mergeShippingProductLists = (data) => {
+ const lists = [data?.shippingProductVoList, data?.productDtoData].filter(Array.isArray);
+ if (!lists.length) return [];
+ const map = new Map();
+ for (const list of lists) {
+ for (const p of list) {
+ if (p == null) continue;
+ const key = p.id != null ? String(p.id) : null;
+ if (!key) continue;
+ const prev = map.get(key);
+ map.set(key, prev ? { ...prev, ...p } : { ...p });
+ }
+ }
+ return Array.from(map.values());
+};
+
+const pickShippingLine = (normalized) => {
+ const pid = normalized?.returnSaleLedgerProductId ?? normalized?.id;
+ const sid = normalized?.stockOutRecordId ?? normalized?.shippingProductId;
+ const direct = availableProducts.value.find(
+ (p) =>
+ sameKey(p?.id, pid) ||
+ sameKey(p?.stockOutRecordId, pid) ||
+ sameKey(p?.id, sid) ||
+ sameKey(p?.stockOutRecordId, sid)
+ );
+ if (direct) return direct;
+ const pmid = normalized?.productModelId;
+ if (pmid == null || pmid === "") return undefined;
+ const candidates = availableProducts.value.filter((p) => sameKey(p?.productModelId, pmid));
+ if (!candidates.length) return undefined;
+ if (candidates.length === 1) return candidates[0];
+ const spec = String(normalized?.specificationModel ?? normalized?.model ?? "");
+ if (spec) {
+ const hit = candidates.find((p) => {
+ const ps = String(p?.specificationModel ?? p?.model ?? "");
+ return ps && ps === spec;
+ });
+ if (hit) return hit;
+ }
+ return candidates[0];
+};
+
+const isEmptyText = (v) => v === "" || v == null || v === undefined;
+
+const firstFiniteNumber = (...vals) => {
+ for (const v of vals) {
+ if (v === "" || v == null || v === undefined) continue;
+ const n = Number(v);
+ if (Number.isFinite(n)) return n;
+ }
+ return undefined;
+};
+
+const firstNonEmptyText = (...vals) => {
+ const hit = vals.find((v) => !isEmptyText(v));
+ return hit === undefined ? "" : hit;
+};
+
+/** 璇︽儏鎺ュ彛瀛楁甯镐笉鍏紱{...product,...normalized} 浼氳 normalized 閲岀殑绌轰覆鐩栨帀鍑哄簱琛屼笂鐨勫睍绀哄瓧娈� */
+const mergeShippingLineWithDetail = (product, normalized) => {
+ const row = { ...product, ...normalized };
+ row.outboundBatches = firstNonEmptyText(
+ row.outboundBatches,
+ product?.outboundBatches,
+ product?.shippingNo,
+ product?.outboundNo,
+ normalized?.outboundBatches,
+ normalized?.outboundNo,
+ normalized?.shippingNo
+ );
+ row.batchNo = firstNonEmptyText(
+ row.batchNo,
+ product?.batchNo,
+ product?.batchNumber,
+ product?.lotNo,
+ product?.batchCode,
+ product?.shippingBatchNo,
+ normalized?.batchNo,
+ normalized?.batchNumber,
+ normalized?.lotNo,
+ normalized?.shippingBatchNo
+ );
+ const stock = firstFiniteNumber(
+ row.stockOutNum,
+ product?.stockOutNum,
+ product?.totalQuantity,
+ product?.shippingQuantity,
+ product?.deliveryQuantity,
+ product?.quantity,
+ product?.outQuantity,
+ normalized?.stockOutNum,
+ normalized?.totalQuantity,
+ normalized?.shippingQuantity,
+ normalized?.deliveryQuantity
+ );
+ if (stock !== undefined) row.stockOutNum = stock;
+ const un = firstFiniteNumber(
+ row.unQuantity,
+ product?.unQuantity,
+ product?.remainingQuantity,
+ product?.noReturnQuantity,
+ product?.canReturnQuantity,
+ product?.availableReturnNum,
+ normalized?.unQuantity,
+ normalized?.remainingQuantity,
+ normalized?.noReturnQuantity,
+ normalized?.canReturnQuantity
+ );
+ if (un !== undefined) row.unQuantity = un;
+ else {
+ const s = Number(row.stockOutNum);
+ const ret = Number(row.totalReturnNum ?? 0);
+ if (Number.isFinite(s) && s >= 0 && Number.isFinite(ret) && ret >= 0) {
+ row.unQuantity = Math.max(0, s - ret);
+ }
+ }
+ const returned = firstFiniteNumber(
+ row.totalReturnNum,
+ product?.totalReturnNum,
+ product?.totalReturnedNum,
+ normalized?.totalReturnNum,
+ normalized?.totalReturnedNum
+ );
+ if (returned !== undefined) row.totalReturnNum = returned;
+ else if (isEmptyText(row.totalReturnNum)) row.totalReturnNum = 0;
+ if (isEmptyText(row.unit)) {
+ row.unit = firstNonEmptyText(product?.unit, normalized?.unit);
+ }
+ if (isEmptyText(row.productCategory)) {
+ row.productCategory = firstNonEmptyText(
+ normalized?.productCategory,
+ normalized?.productName,
+ product?.productCategory,
+ product?.productName
+ );
+ }
+ if (isEmptyText(row.specificationModel)) {
+ row.specificationModel = firstNonEmptyText(
+ normalized?.specificationModel,
+ normalized?.model,
+ product?.specificationModel,
+ product?.model
+ );
+ }
+ return row;
+};
+
const normalizeDetailRow = (raw) => {
- const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id;
+ const ledgerId =
+ raw?.returnSaleLedgerProductId ??
+ raw?.saleLedgerProductId ??
+ raw?.stockOutRecordId ??
+ raw?.shippingProductId;
+ const productId = ledgerId ?? raw?.id;
const returnSaleProductId = raw?.returnSaleProductId ?? raw?.id;
const num = Number(raw?.num ?? raw?.returnQuantity ?? 0);
return {
@@ -248,6 +411,29 @@
returnSaleProductId,
returnSaleLedgerProductId: productId,
productModelId: raw?.productModelId,
+ stockOutRecordId: raw?.stockOutRecordId,
+ shippingProductId: raw?.shippingProductId,
+ productCategory: raw?.productCategory ?? raw?.productName ?? raw?.productTypeName ?? "",
+ specificationModel: raw?.specificationModel ?? raw?.model ?? raw?.specModel ?? "",
+ outboundBatches: raw?.outboundBatches ?? raw?.outboundNo ?? raw?.shippingNo,
+ batchNo:
+ raw?.batchNo ??
+ raw?.batchNumber ??
+ raw?.lotNo ??
+ raw?.batchCode ??
+ raw?.shippingBatchNo,
+ stockOutNum:
+ raw?.stockOutNum ??
+ raw?.totalQuantity ??
+ raw?.shippingQuantity ??
+ raw?.deliveryQuantity ??
+ raw?.quantity,
+ totalReturnNum: raw?.totalReturnNum ?? raw?.totalReturnedNum,
+ unQuantity:
+ raw?.unQuantity ??
+ raw?.remainingQuantity ??
+ raw?.noReturnQuantity ??
+ raw?.canReturnQuantity,
num,
returnQuantity: Number.isFinite(num) ? num : 0,
price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0),
@@ -259,7 +445,6 @@
const setFormForEdit = async (row) => {
const res = await returnManagementGetById({ returnManagementId: row?.id });
- console.log("res", res);
const detail = res?.data ?? res ?? {};
Object.assign(form.value, detail);
@@ -281,11 +466,18 @@
tableData.value = Array.isArray(list)
? list.map((raw) => {
const normalized = normalizeDetailRow(raw);
- const product = availableProducts.value.find((p) => p.id === normalized.id);
- return product ? { ...product, ...normalized } : normalized;
+ const product = pickShippingLine(normalized);
+ return product ? mergeShippingLineWithDetail(product, normalized) : normalized;
})
: [];
-
+
+ const headerShipNo = detail?.shippingNo ?? form.value?.shippingNo;
+ if (headerShipNo && Array.isArray(tableData.value) && tableData.value.length) {
+ tableData.value = tableData.value.map((r) =>
+ isEmptyText(r.outboundBatches) ? { ...r, outboundBatches: headerShipNo } : r
+ );
+ }
+
calculateTotalRefund();
};
@@ -320,7 +512,7 @@
proxy.$refs["formRef"].validate(valid => {
if (!valid) return;
const returnSaleProducts = (tableData.value || []).map(el => ({
- returnSaleLedgerProductId: el.returnSaleLedgerProductId ?? el.id,
+ stockOutRecordId: el.returnSaleLedgerProductId ?? el.id,
productModelId: el.productModelId,
unit: el.unit,
num: Number(el.num ?? el.returnQuantity ?? 0),
@@ -419,8 +611,7 @@
// If backend returns project info, set it
if (res.data.projectId) form.value.projectId = res.data.projectId;
- // Store available products for selection
- availableProducts.value = res.data.productDtoData || [];
+ availableProducts.value = mergeShippingProductLists(res.data);
if (clearTable) tableData.value = [];
}
};
@@ -457,9 +648,9 @@
};
const calculateRowAmount = (row) => {
- const quantity = Number(row.returnQuantity || 0);
+ const stockOutNum = Number(row.returnQuantity || 0);
const price = Number(row.price || 0);
- row.amount = (quantity * price).toFixed(2);
+ row.amount = (stockOutNum * price).toFixed(2);
};
const calculateTotalRefund = () => {
@@ -511,10 +702,11 @@
amount: "0.00",
isQuality: 2,
remark: "",
+ productCategory: product.productCategory ?? product.productName ?? "",
productName: product.productName,
- specificationModel: product.specificationModel,
+ specificationModel: product.specificationModel ?? product.model ?? "",
unit: product.unit,
- quantity: product.quantity,
+ stockOutNum: product.stockOutNum,
totalReturnNum: product.totalReturnNum,
unQuantity: product.unQuantity
});
diff --git a/src/views/salesManagement/returnOrder/index.vue b/src/views/salesManagement/returnOrder/index.vue
index d41d4bf..0a8257b 100644
--- a/src/views/salesManagement/returnOrder/index.vue
+++ b/src/views/salesManagement/returnOrder/index.vue
@@ -11,8 +11,8 @@
<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 label="鍏宠仈鍙戣揣鍗曞彿">
+ <el-input v-model="searchForm.shippingNo" placeholder="鍏宠仈鍙戣揣鍗曞彿" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">鎼滅储</el-button>
@@ -112,22 +112,22 @@
]);
const defaultColumns = [
- { label: "閫�璐у崟鍙�", prop: "returnNo", width: 160 },
- { label: "鍗曟嵁鐘舵��", prop: "status", width: 90, dataType: "slot", slot: "status" },
- { label: "鍒跺崟鏃堕棿", prop: "makeTime", width: 170 },
- { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: 220 },
- { label: "閿�鍞崟鍙�", prop: "salesContractNo", width: 160 },
- { label: "涓氬姟鍛�", prop: "salesman", width: 120 },
- { label: "鍏宠仈鍑哄簱鍗曞彿", prop: "shippingNo", width: 170 },
- { label: "椤圭洰鍚嶇О", prop: "projectName", width: 180 },
- { label: "鍒跺崟浜�", prop: "maker", width: 120 },
+ { label: "閫�璐у崟鍙�", prop: "returnNo", minWidth: 160 },
+ { label: "鍗曟嵁鐘舵��", prop: "status", minWidth: 90, dataType: "slot", slot: "status" },
+ { label: "鍒跺崟鏃堕棿", prop: "makeTime", minWidth: 170 },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", minWidth: 220 },
+ { label: "閿�鍞崟鍙�", prop: "salesContractNo", minWidth: 160 },
+ { label: "涓氬姟鍛�", prop: "salesman", minWidth: 120 },
+ { label: "鍏宠仈鍙戣揣鍗曞彿", prop: "shippingNo", minWidth: 170 },
+ { label: "椤圭洰鍚嶇О", prop: "projectName", minWidth: 180 },
+ { label: "鍒跺崟浜�", prop: "maker", minWidth: 120 },
{
label: "鎿嶄綔",
prop: "operation",
dataType: "action",
align: "center",
fixed: "right",
- width: 240,
+ minWidth: 240,
operation: [
{ name: "缂栬緫", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => openForm("edit", row) },
{ name: "閫�娆惧鐞�", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => handleRowHandle(row) },
diff --git a/src/views/salesManagement/salesLedger/fileList.vue b/src/views/salesManagement/salesLedger/fileList.vue
index 57c4332..eb4d401 100644
--- a/src/views/salesManagement/salesLedger/fileList.vue
+++ b/src/views/salesManagement/salesLedger/fileList.vue
@@ -29,7 +29,7 @@
tableData.value = list
}
const downLoadFile = (row) => {
- proxy.$download.name(row.url);
+ proxy.$download.byUrl(row.url, row.originalFilename);
}
const lookFile = (row) => {
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 5340d99..904979c 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -8,21 +8,21 @@
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
- @change="handleQuery"/>
+ @change="handleQuery" />
</el-form-item>
<el-form-item label="閿�鍞悎鍚屽彿锛�">
<el-input v-model="searchForm.salesContractNo"
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
- @change="handleQuery"/>
+ @change="handleQuery" />
</el-form-item>
<el-form-item label="椤圭洰鍚嶇О锛�">
<el-input v-model="searchForm.projectName"
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
- @change="handleQuery"/>
+ @change="handleQuery" />
</el-form-item>
<el-form-item label="褰曞叆鏃ユ湡锛�">
<el-date-picker v-model="searchForm.entryDate"
@@ -31,7 +31,7 @@
type="daterange"
placeholder="璇烽�夋嫨"
clearable
- @change="changeDaterange"/>
+ @change="changeDaterange" />
</el-form-item>
<el-form-item>
<el-button type="primary"
@@ -78,7 +78,7 @@
<el-table-column align="center"
type="selection"
width="55"
- fixed="left"/>
+ fixed="left" />
<el-table-column type="expand"
width="60"
fixed="left">
@@ -89,24 +89,22 @@
:summary-method="(param) => summarizeChildrenTable(param, props.row)">
<el-table-column align="center"
label="搴忓彿"
- type="index"/>
+ type="index" />
<el-table-column label="浜у搧澶х被"
- prop="productCategory"/>
+ prop="productCategory" />
<el-table-column label="瑙勬牸鍨嬪彿"
- prop="specificationModel"/>
+ prop="specificationModel" />
<el-table-column label="鍗曚綅"
- prop="unit"/>
+ prop="unit" />
<el-table-column label="浜у搧鐘舵��"
width="100px"
align="center">
<template #default="scope">
- <el-tag
- v-if="scope.row.approveStatus === 1 && scope.row.noQuantity !== 0"
- type="success">鍏呰冻
+ <el-tag v-if="scope.row.approveStatus === 1 "
+ type="success">鍏呰冻
</el-tag>
- <el-tag
- v-else-if="scope.row.approveStatus === 0 && scope.row.noQuantity === 0"
- type="success">宸插嚭搴�
+ <el-tag v-else-if="scope.row.approveStatus === 0 && scope.row.noQuantity === 0"
+ type="success">宸插嚭搴�
</el-tag>
<el-tag v-else
type="danger">涓嶈冻
@@ -125,10 +123,10 @@
</el-table-column>
<el-table-column label="蹇�掑叕鍙�"
prop="expressCompany"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="蹇�掑崟鍙�"
prop="expressNumber"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="鍙戣揣杞︾墝"
minWidth="100px"
align="center">
@@ -156,20 +154,20 @@
</template>
</el-table-column>
<el-table-column label="鏁伴噺"
- prop="quantity"/>
+ prop="quantity" />
<el-table-column label="寰呭彂璐ф暟閲�"
- prop="noQuantity"/>
+ prop="noQuantity" />
<el-table-column label="绋庣巼(%)"
- prop="taxRate"/>
+ prop="taxRate" />
<el-table-column label="鍚◣鍗曚环(鍏�)"
prop="taxInclusiveUnitPrice"
- :formatter="sensitiveAmountFormatter"/>
+ :formatter="sensitiveAmountFormatter" />
<el-table-column label="鍚◣鎬讳环(鍏�)"
prop="taxInclusiveTotalPrice"
- :formatter="sensitiveAmountFormatter"/>
+ :formatter="sensitiveAmountFormatter" />
<el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
prop="taxExclusiveTotalPrice"
- :formatter="sensitiveAmountFormatter"/>
+ :formatter="sensitiveAmountFormatter" />
<!--鎿嶄綔-->
<el-table-column Width="60px"
label="鎿嶄綔"
@@ -189,51 +187,51 @@
<el-table-column align="center"
label="搴忓彿"
type="index"
- width="60"/>
+ width="60" />
<el-table-column label="閿�鍞悎鍚屽彿"
prop="salesContractNo"
width="180"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="瀹㈡埛鍚嶇О"
prop="customerName"
width="300"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="涓氬姟鍛�"
prop="salesman"
width="100"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="椤圭洰鍚嶇О"
prop="projectName"
width="180"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="浠樻鏂瑰紡"
prop="paymentMethod"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="鍚堝悓閲戦(鍏�)"
prop="contractAmount"
width="220"
show-overflow-tooltip
- :formatter="formattedNumber"/>
+ :formatter="formattedNumber" />
<el-table-column label="褰曞叆浜�"
prop="entryPersonName"
width="100"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="褰曞叆鏃ユ湡"
prop="entryDate"
width="120"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="绛捐鏃ユ湡"
prop="executionDate"
width="120"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="浜や粯鏃ユ湡"
prop="deliveryDate"
width="120"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column label="澶囨敞"
prop="remarks"
width="200"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column fixed="right"
label="鎿嶄綔"
width="130"
@@ -256,7 +254,7 @@
layout="total, sizes, prev, pager, next, jumper"
:page="page.current"
:limit="page.size"
- @pagination="paginationChange"/>
+ @pagination="paginationChange" />
</div>
<FormDialog v-model="dialogFormVisible"
:title="operationType === 'add' ? '鏂板閿�鍞彴璐﹂〉闈�' : '缂栬緫閿�鍞彴璐﹂〉闈�'"
@@ -286,10 +284,15 @@
<el-col :span="12">
<el-form-item label="閿�鍞悎鍚屽彿锛�"
prop="salesContractNo">
- <el-input v-model="form.salesContractNo"
- placeholder="鑷姩鐢熸垚"
- clearable
- disabled/>
+ <div style="display: flex; align-items: center; gap: 12px;width: 100%;">
+ <el-checkbox v-model="form.autoGenerateContractNo" v-if="operationType === 'add'">鑷姩鐢熸垚
+ </el-checkbox>
+ <el-input v-model="form.salesContractNo"
+ :placeholder="form.autoGenerateContractNo ? '鑷姩鐢熸垚' : '璇疯緭鍏�'"
+ clearable
+ :disabled="form.autoGenerateContractNo" />
+
+ </div>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -303,7 +306,7 @@
<el-option v-for="item in userList"
:key="item.nickName"
:label="item.nickName"
- :value="item.nickName"/>
+ :value="item.nickName" />
</el-select>
</el-form-item>
</el-col>
@@ -334,7 +337,7 @@
<el-input v-model="form.projectName"
placeholder="璇疯緭鍏�"
clearable
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
</el-row>
@@ -349,7 +352,7 @@
type="date"
placeholder="璇烽�夋嫨"
clearable
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -357,7 +360,7 @@
<el-input v-model="form.paymentMethod"
placeholder="璇疯緭鍏�"
clearable
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
</el-row>
@@ -375,7 +378,7 @@
<el-option v-for="item in userList"
:key="item.userId"
:label="item.nickName"
- :value="item.userId"/>
+ :value="item.userId" />
</el-select>
</el-form-item>
</el-col>
@@ -388,7 +391,7 @@
format="YYYY-MM-DD"
type="date"
placeholder="璇烽�夋嫨"
- clearable/>
+ clearable />
</el-form-item>
</el-col>
</el-row>
@@ -402,7 +405,7 @@
format="YYYY-MM-DD"
type="date"
placeholder="璇烽�夋嫨"
- clearable/>
+ clearable />
</el-form-item>
</el-col>
</el-row>
@@ -429,30 +432,30 @@
type="selection"
width="55"
v-if="operationType !== 'view'"
- :selectable="(row) => !isProductShipped(row)"/>
+ :selectable="(row) => !isProductShipped(row)" />
<el-table-column align="center"
label="搴忓彿"
type="index"
- width="60"/>
+ width="60" />
<el-table-column label="浜у搧澶х被"
- prop="productCategory"/>
+ prop="productCategory" />
<el-table-column label="瑙勬牸鍨嬪彿"
- prop="specificationModel"/>
+ prop="specificationModel" />
<el-table-column label="鍗曚綅"
- prop="unit"/>
+ prop="unit" />
<el-table-column label="鏁伴噺"
- prop="quantity"/>
+ prop="quantity" />
<el-table-column label="绋庣巼(%)"
- prop="taxRate"/>
+ prop="taxRate" />
<el-table-column label="鍚◣鍗曚环(鍏�)"
prop="taxInclusiveUnitPrice"
- :formatter="formattedNumber"/>
+ :formatter="formattedNumber" />
<el-table-column label="鍚◣鎬讳环(鍏�)"
prop="taxInclusiveTotalPrice"
- :formatter="formattedNumber"/>
+ :formatter="formattedNumber" />
<el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
prop="taxExclusiveTotalPrice"
- :formatter="formattedNumber"/>
+ :formatter="formattedNumber" />
<el-table-column label="鏄惁鐢熶骇"
prop="isProduction"
width="150">
@@ -486,7 +489,7 @@
clearable
type="textarea"
:rows="2"
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
</el-row>
@@ -494,7 +497,7 @@
<el-col :span="24">
<el-form-item label="闄勪欢鏉愭枡锛�"
prop="salesLedgerFiles">
- <FileUpload v-model:file-list="fileList"/>
+ <FileUpload v-model:file-list="fileList" />
</el-form-item>
</el-col>
</el-row>
@@ -510,12 +513,12 @@
placeholder="璇疯緭鍏ユ姤浠峰崟鍙�"
clearable
style="max-width: 260px;"
- @change="fetchQuotationList"/>
+ @change="fetchQuotationList" />
<el-input v-model="quotationSearchForm.customer"
placeholder="璇疯緭鍏ュ鎴峰悕绉�"
clearable
style="max-width: 260px;"
- @change="fetchQuotationList"/>
+ @change="fetchQuotationList" />
<el-button type="primary"
@click="fetchQuotationList">鎼滅储
</el-button>
@@ -529,26 +532,26 @@
<el-table-column align="center"
label="搴忓彿"
type="index"
- width="60"/>
+ width="60" />
<el-table-column prop="quotationNo"
label="鎶ヤ环鍗曞彿"
width="180"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column prop="customer"
label="瀹㈡埛鍚嶇О"
min-width="220"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column prop="salesperson"
label="涓氬姟鍛�"
width="120"
- show-overflow-tooltip/>
+ show-overflow-tooltip />
<el-table-column prop="quotationDate"
label="鎶ヤ环鏃ユ湡"
- width="140"/>
+ width="140" />
<el-table-column prop="status"
label="瀹℃壒鐘舵��"
width="120"
- align="center"/>
+ align="center" />
<el-table-column prop="totalAmount"
label="鎶ヤ环閲戦(鍏�)"
width="160"
@@ -574,7 +577,7 @@
layout="total, sizes, prev, pager, next, jumper"
:page="quotationPage.current"
:limit="quotationPage.size"
- @pagination="quotationPaginationChange"/>
+ @pagination="quotationPaginationChange" />
<template #footer>
<el-button @click="quotationDialogVisible = false">鍏抽棴</el-button>
</template>
@@ -603,7 +606,7 @@
@change="getModels"
:data="productOptions"
:render-after-expand="false"
- style="width: 100%"/>
+ style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
@@ -619,7 +622,7 @@
<el-option v-for="item in modelOptions"
:key="item.id"
:label="item.model"
- :value="item.id"/>
+ :value="item.id" />
</el-select>
</el-form-item>
</el-col>
@@ -630,7 +633,7 @@
prop="unit">
<el-input v-model="productForm.unit"
placeholder="璇疯緭鍏�"
- clearable/>
+ clearable />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -640,12 +643,10 @@
placeholder="璇烽�夋嫨"
clearable
@change="calculateFromTaxRate">
- <el-option
- v-for="dict in tax_rate"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
+ <el-option v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
@@ -661,7 +662,7 @@
:precision="2"
placeholder="璇疯緭鍏�"
clearable
- @change="calculateFromUnitPrice"/>
+ @change="calculateFromUnitPrice" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -674,7 +675,7 @@
clearable
:precision="2"
@change="calculateFromQuantity"
- style="width: 100%"/>
+ style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
@@ -685,7 +686,7 @@
<el-input v-model="productForm.taxInclusiveTotalPrice"
placeholder="璇疯緭鍏�"
clearable
- @change="calculateFromTotalPrice"/>
+ @change="calculateFromTotalPrice" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -694,7 +695,7 @@
<el-input v-model="productForm.taxExclusiveTotalPrice"
placeholder="璇疯緭鍏�"
clearable
- @change="calculateFromExclusiveTotalPrice"/>
+ @change="calculateFromExclusiveTotalPrice" />
</el-form-item>
</el-col>
</el-row>
@@ -706,9 +707,9 @@
placeholder="璇烽�夋嫨"
clearable>
<el-option label="澧炴櫘绁�"
- value="澧炴櫘绁�"/>
+ value="澧炴櫘绁�" />
<el-option label="澧炰笓绁�"
- value="澧炰笓绁�"/>
+ value="澧炰笓绁�" />
</el-select>
</el-form-item>
</el-col>
@@ -717,9 +718,9 @@
prop="isProduction">
<el-radio-group v-model="productForm.isProduction">
<el-radio label="鏄�"
- :value="true"/>
+ :value="true" />
<el-radio label="鍚�"
- :value="false"/>
+ :value="false" />
</el-radio-group>
</el-form-item>
</el-col>
@@ -762,8 +763,10 @@
</FormDialog>
<!-- // todo 闄勪欢棰勮鐩稿叧 -->
<!-- 闄勪欢鍒楄〃寮圭獥 -->
- <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="sales_ledger"
- :record-id="recordId"/>
+ <FileList v-if="fileDialogVisible"
+ v-model:visible="fileDialogVisible"
+ record-type="sales_ledger"
+ :record-id="recordId" />
<!-- 鎵撳嵃棰勮寮圭獥 -->
<el-dialog v-model="printPreviewVisible"
title="鎵撳嵃棰勮"
@@ -816,40 +819,40 @@
<div class="table-section">
<table class="product-table">
<thead>
- <tr>
- <th>浜у搧鍚嶇О</th>
- <th>瑙勬牸鍨嬪彿</th>
- <th>鍗曚綅</th>
- <th>鍗曚环</th>
- <th>闆跺敭鏁伴噺</th>
- <th>闆跺敭閲戦</th>
- </tr>
+ <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>
+ <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>
+ <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>
@@ -900,12 +903,54 @@
prop="type">
<el-select v-model="deliveryForm.type"
placeholder="璇烽�夋嫨鍙戣揣绫诲瀷"
- style="width: 100%">
+ style="width: 100%"
+ @change="handleDeliveryTypeChange">
<el-option label="璐ц溅"
- value="璐ц溅"/>
+ value="璐ц溅" />
<el-option label="蹇��"
- value="蹇��"/>
+ value="蹇��" />
</el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="寰呭彂璐ф暟閲忥細">
+ <el-input :model-value="currentDeliveryRow?.noQuantity"
+ disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24" v-if="deliveryForm.type === '璐ц溅'">
+ <el-form-item label="鍙戣揣杞︾墝鍙凤細"
+ prop="shippingCarNumber">
+ <el-input v-model="deliveryForm.shippingCarNumber"
+ placeholder="璇疯緭鍏ュ彂璐ц溅鐗屽彿"
+ clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24" v-else>
+ <el-form-item label="蹇�掑叕鍙革細"
+ prop="expressCompany">
+ <el-input v-model="deliveryForm.expressCompany"
+ placeholder="璇疯緭鍏ュ揩閫掑叕鍙�"
+ clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30" v-if="deliveryForm.type === '蹇��'">
+ <el-col :span="24">
+ <el-form-item label="蹇�掑崟鍙凤細"
+ prop="expressNumber">
+ <el-input v-model="deliveryForm.expressNumber"
+ placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
+ clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="鍙戣揣鍥剧墖锛�">
+ <ImageUpload v-model:file-list="deliveryFileList" :limit="9" />
</el-form-item>
</el-col>
</el-row>
@@ -920,8 +965,8 @@
style="width: 100%;">
<el-table-column label="鎵瑰彿"
prop="batchNo"
- min-width="180"/>
- <el-table-column label="鏁伴噺"
+ min-width="180" />
+ <el-table-column label="搴撳瓨鏁伴噺"
min-width="120"
align="center">
<template #default="scope">
@@ -939,7 +984,7 @@
:step="0.01"
controls-position="right"
@change="handleDeliveryBatchQuantityChange(scope.row)"
- style="width: 100%;"/>
+ style="width: 100%;" />
</template>
</el-table-column>
</el-table>
@@ -960,309 +1005,352 @@
</template>
<script setup>
-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 useUserStore from "@/store/modules/user";
-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,
-} from "@/api/salesManagement/salesLedger.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";
+ 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 useUserStore from "@/store/modules/user";
+ 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,
+ } from "@/api/salesManagement/salesLedger.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 ImageUpload from "@/components/AttachmentUpload/image/index.vue";
+ import { getCurrentDate } from "@/utils/index.js";
+ import { listCustomer } from "@/api/basicData/customer.js";
-const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
+ const FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+ );
-const router = useRouter();
-const route = useRoute();
-const userStore = useUserStore();
-const {proxy} = getCurrentInstance();
-const { tax_rate } = proxy.useDict("tax_rate");
-const tableData = ref([]);
-const productData = ref([]);
-const selectedRows = ref([]);
-const productSelectedRows = ref([]);
-const userList = ref([]);
-const customerOption = ref([]);
-const productOptions = ref([]);
-const modelOptions = ref([]);
-const tableLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
-});
-const total = ref(0);
-const fileList = ref([]);
+ const router = useRouter();
+ const route = useRoute();
+ const userStore = useUserStore();
+ const { proxy } = getCurrentInstance();
+ const { tax_rate } = proxy.useDict("tax_rate");
+ const tableData = ref([]);
+ const productData = ref([]);
+ const selectedRows = ref([]);
+ const productSelectedRows = ref([]);
+ const userList = ref([]);
+ const customerOption = ref([]);
+ const productOptions = ref([]);
+ const modelOptions = ref([]);
+ const tableLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ });
+ const total = ref(0);
+ const fileList = ref([]);
+ const deliveryFileList = ref([]);
-// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
-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: "",
- deliveryDate: "",
- 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 productFormVisible = ref(false);
-const productOperationType = ref("");
-const currentId = ref("");
-const productFormData = reactive({
- 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 isCalculating = ref(false);
-// 鎵撳嵃鐩稿叧
-const printPreviewVisible = ref(false);
-const printData = ref([]);
+ // 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+ const operationType = ref("");
+ const dialogFormVisible = ref(false);
+ const data = reactive({
+ searchForm: {
+ customerName: "", // 瀹㈡埛鍚嶇О
+ salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
+ entryDate: null, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
+ },
+ form: {
+ salesContractNo: "",
+ autoGenerateContractNo: true,
+ salesman: "",
+ customerId: "",
+ entryPerson: "",
+ entryDate: "",
+ deliveryDate: "",
+ 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 productFormVisible = ref(false);
+ const productOperationType = ref("");
+ const currentId = ref("");
+ const productFormData = reactive({
+ 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 isCalculating = ref(false);
+ // 鎵撳嵃鐩稿叧
+ const printPreviewVisible = ref(false);
+ const printData = ref([]);
-// 鎶ヤ环鍗曞鍏ョ浉鍏�
-const quotationDialogVisible = ref(false);
-const quotationLoading = ref(false);
-const quotationList = ref([]);
-const quotationSearchForm = reactive({
- quotationNo: "",
- customer: "",
-});
-// 鎶ヤ环鍗曞脊妗嗗垎椤�
-const quotationPage = reactive({
- current: 1,
- size: 10,
- total: 0,
-});
-const selectedQuotation = ref(null);
+ // 鎶ヤ环鍗曞鍏ョ浉鍏�
+ const quotationDialogVisible = ref(false);
+ const quotationLoading = ref(false);
+ const quotationList = ref([]);
+ const quotationSearchForm = reactive({
+ quotationNo: "",
+ customer: "",
+ });
+ // 鎶ヤ环鍗曞脊妗嗗垎椤�
+ const quotationPage = reactive({
+ 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(
+ // 鍙戣揣鐩稿叧
+ 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(
+ );
+ 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(
+ );
+ return Math.max(0, Math.min(batchQuantity, remainingProductQuantity));
+ };
+ const handleDeliveryBatchQuantityChange = 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
+ );
+ const currentValue = Number(row?.deliveryQuantity || 0);
+
+ if (currentValue > batchQuantity) {
+ row.deliveryQuantity = batchQuantity;
+ proxy.$modal.msgWarning("鍙戣揣鏁伴噺涓嶈兘澶т簬搴撳瓨鏁伴噺");
+ } else if (currentValue > remainingProductQuantity) {
+ row.deliveryQuantity = remainingProductQuantity;
+ 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)
+ );
+ };
+ 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;
+ 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 validateDeliveryShippingCarNumber = (_rule, value, callback) => {
+ if (deliveryForm.value.type === "璐ц溅" && !value) {
+ return callback(new Error("璇疯緭鍏ュ彂璐ц溅鐗屽彿"));
}
- seenIds.add(item.id);
- return true;
- }).map(item => ({
- ...item,
- deliveryQuantity: 0,
- }));
-};
-const deliveryFormData = reactive({
- deliveryForm: {
- type: "璐ц溅", // 璐ц溅, 蹇��
- },
- deliveryRules: {
- type: [{required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change"}],
- },
-});
-const {deliveryForm, deliveryRules} = toRefs(deliveryFormData);
+ callback();
+ };
+ const validateDeliveryExpressCompany = (_rule, value, callback) => {
+ if (deliveryForm.value.type === "蹇��" && !value) {
+ return callback(new Error("璇疯緭鍏ュ揩閫掑叕鍙�"));
+ }
+ callback();
+ };
+ const deliveryFormData = reactive({
+ deliveryForm: {
+ shippingCarNumber: "",
+ expressCompany: "",
+ expressNumber: "",
+ type: "璐ц溅", // 璐ц溅, 蹇��
+ },
+ deliveryRules: {
+ shippingCarNumber: [
+ { validator: validateDeliveryShippingCarNumber, trigger: "blur" },
+ ],
+ expressCompany: [{ validator: validateDeliveryExpressCompany, trigger: "blur" }],
+ type: [{ required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change" }],
+ },
+ });
+ 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();
+ // 瀵煎叆鐩稿叧
+ 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;
}
- getList();
+ 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 {
- proxy.$modal.msgError(response.msg || "瀵煎叆澶辫触");
+ searchForm.entryDateStart = undefined;
+ searchForm.entryDateEnd = undefined;
}
- },
- onError: (error, file, fileList) => {
- console.error("涓婁紶澶辫触", error, file, fileList);
- importUpload.isUploading = false;
- proxy.$modal.msgError("瀵煎叆澶辫触锛岃閲嶈瘯");
- },
-});
+ 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();
-};
-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)
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ // 鍙湁鍦ㄧ偣鍑绘悳绱㈡寜閽椂鎵嶉噸缃〉鐮佸埌绗竴椤�
+ // 閬垮厤琛ㄥ崟瀛楁change浜嬩欢骞叉壈鍒嗛〉
+ if (arguments.length === 0) {
+ page.current = 1;
+ }
+ expandedRowKeys.value = [];
+ 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;
@@ -1275,624 +1363,633 @@
.catch(() => {
tableLoading.value = false;
});
-};
-// 鑾峰彇浜у搧澶х被tree鏁版嵁
-const getProductOptions = () => {
- // 杩斿洖 Promise锛屼究浜庡湪缂栬緫浜у搧鏃剁瓑寰呭姞杞藉畬鎴�
- return productTreeList().then(res => {
- productOptions.value = convertIdToValue(res);
- return productOptions.value;
- });
-};
-const formattedNumber = (row, column, cellValue) => {
- if (cellValue === undefined || cellValue === null || cellValue === "") {
- return "0.00";
- }
- return parseFloat(cellValue).toFixed(2);
-};
-const findLedgerRecordByRow = row => {
- if (!row) return null;
- if (
+ };
+ // 鑾峰彇浜у搧澶х被tree鏁版嵁
+ const getProductOptions = () => {
+ // 杩斿洖 Promise锛屼究浜庡湪缂栬緫浜у搧鏃剁瓑寰呭姞杞藉畬鎴�
+ return productTreeList().then(res => {
+ productOptions.value = convertIdToValue(res);
+ return productOptions.value;
+ });
+ };
+ const formattedNumber = (row, column, cellValue) => {
+ 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 (
+ ) {
+ return row;
+ }
+ if (row.salesLedgerId !== undefined && row.salesLedgerId !== null) {
+ return (
tableData.value.find(
- item => String(item.id) === String(row.salesLedgerId)
+ 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 =
+ );
+ }
+ 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 (
+ if (
maintainerId !== undefined &&
maintainerId !== null &&
String(maintainerId) !== ""
- ) {
- return String(maintainerId) === currentUserId;
- }
- if (
+ ) {
+ 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 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; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
+ ) {
+ return String(maintainerName).trim() === currentNickName;
}
- if (nodes[i].children && nodes[i].children.length > 0) {
- const foundNode = findNodeById(nodes[i].children, productId);
- if (foundNode) {
- return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖璇ヨ妭鐐�
+ 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 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; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
}
- }
- }
- 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;
- });
-}
-
-// 鏍规嵁鍚嶇О鍙嶆煡浜у搧澶х被 id锛屼究浜庝粎瀛樺悕绉版椂鐨勫弽鏄�
-function findNodeIdByLabel(nodes, label) {
- if (!label) return null;
- for (let i = 0; i < nodes.length; i++) {
- const node = nodes[i];
- if (node.label === label) return node.value;
- if (node.children && node.children.length > 0) {
- const found = findNodeIdByLabel(node.children, label);
- if (found !== null && found !== undefined) return found;
- }
- }
- return null;
-}
-
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = selection => {
- // 杩囨护鎺夊瓙鏁版嵁
- selectedRows.value = selection.filter(item => item.children !== undefined);
- console.log("selection", selectedRows.value);
-};
-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;
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖璇ヨ妭鐐�
}
- 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 diff = row.deliveryDaysDiff;
- if (diff === 15) {
- return "yellow";
- } else if (diff === 10) {
- return "pink";
- } else if (diff === 2) {
- return "purple";
- } else if (diff < 2) {
- return "red";
- }
-};
-// 涓昏〃鍚堣鏂规硶
-const summarizeMainTable = param => {
- return proxy.summarizeTable(param, [
- "contractAmount",
- "taxInclusiveTotalPrice",
- "taxExclusiveTotalPrice",
- ]);
-};
-// 瀛愯〃鍚堣鏂规硶
-const summarizeChildrenTable = (param, parentRow) => {
- if (!isCurrentUserMaintainer(parentRow)) {
- const {columns} = param;
- return columns.map((column, index) => {
- if (index === 0) {
- return "鍚堣";
}
- if (
+ }
+ 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;
+ });
+ }
+
+ // 鏍规嵁鍚嶇О鍙嶆煡浜у搧澶х被 id锛屼究浜庝粎瀛樺悕绉版椂鐨勫弽鏄�
+ function findNodeIdByLabel(nodes, label) {
+ if (!label) return null;
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+ if (node.label === label) return node.value;
+ if (node.children && node.children.length > 0) {
+ const found = findNodeIdByLabel(node.children, label);
+ if (found !== null && found !== undefined) return found;
+ }
+ }
+ return null;
+ }
+
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter(item => item.children !== undefined);
+ console.log("selection", selectedRows.value);
+ };
+ 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 = [];
+ }
+ };
+
+ // 娣诲姞琛ㄨ绫诲悕鏂规硶
+ const tableRowClassName = ({ row }) => {
+ if (!row.deliveryDate) return "";
+ if (row.isFh) return "";
+
+ const diff = row.deliveryDaysDiff;
+ if (diff === 15) {
+ return "yellow";
+ } else if (diff === 10) {
+ return "pink";
+ } else if (diff === 2) {
+ return "purple";
+ } else if (diff < 2) {
+ return "red";
+ }
+ };
+ // 涓昏〃鍚堣鏂规硶
+ const summarizeMainTable = param => {
+ return proxy.summarizeTable(param, [
+ "contractAmount",
+ "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) => {
- 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 {
- listCustomer({current: -1, size: -1}).then(res => {
- customerOption.value = res.data.records;
+ ) {
+ return "*****";
+ }
+ return "";
});
- } catch (e) {
- // ignore锛屽厑璁哥敤鎴峰悗缁墜鍔ㄩ�夋嫨瀹㈡埛
}
- }
- await fetchQuotationList();
-};
+ return proxy.summarizeTable(param, [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
+ };
+ // 鎵撳紑寮规
+ const openForm = async (type, row) => {
+ 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, type: 0 }).then(res => {
+ customerOption.value = res.data.records;
+ });
+ form.value.entryPerson = userStore.id;
+ if (type === "add") {
+ // 鏂板鏃惰缃綍鍏ユ棩鏈熶负褰撳ぉ
+ form.value.entryDate = getCurrentDate();
+ // 绛捐鏃ユ湡榛樿涓哄綋澶�
+ form.value.executionDate = getCurrentDate();
+ // 榛樿鑷姩鐢熸垚閿�鍞悎鍚屽彿
+ form.value.autoGenerateContractNo = true;
+ } 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;
+ // 缂栬緫鏃惰缃嚜鍔ㄧ敓鎴愪负false锛屽厑璁告墜鍔ㄤ慨鏀�
+ form.value.autoGenerateContractNo = false;
+ });
+ }
+ // 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 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;
- }
-};
+ // 鎵撳紑鎶ヤ环鍗曢�夋嫨寮圭獥锛堜粎瀹℃壒閫氳繃锛�
+ const openQuotationDialog = async () => {
+ 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 resetQuotationSearch = async () => {
- quotationSearchForm.quotationNo = "";
- quotationSearchForm.customer = "";
- quotationPage.current = 1;
- 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;
+ }
+ };
-// 鎶ヤ环鍗曞脊妗嗗垎椤靛垏鎹�
-const quotationPaginationChange = obj => {
- quotationPage.current = obj.page;
- quotationPage.size = obj.limit;
- fetchQuotationList();
-};
+ const resetQuotationSearch = async () => {
+ quotationSearchForm.quotationNo = "";
+ quotationSearchForm.customer = "";
+ quotationPage.current = 1;
+ await fetchQuotationList();
+ };
-// 閫変腑鎶ヤ环鍗曞悗鍥炲~鍒板彴璐﹁〃鍗�
-const applyQuotation = row => {
- if (!row) return;
- selectedQuotation.value = row;
+ // 鎶ヤ环鍗曞脊妗嗗垎椤靛垏鎹�
+ const quotationPaginationChange = obj => {
+ quotationPage.current = obj.page;
+ quotationPage.size = obj.limit;
+ fetchQuotationList();
+ };
- // 涓氬姟鍛�
- form.value.salesman = (row.salesperson || "").trim();
+ // 閫変腑鎶ヤ环鍗曞悗鍥炲~鍒板彴璐﹁〃鍗�
+ const applyQuotation = row => {
+ if (!row) return;
+ selectedQuotation.value = row;
- // 瀹㈡埛鍚嶇О -> customerId
- const qCustomerName = String(row.customer || "").trim();
- const customer = (customerOption.value || []).find(c => {
- const name = String(c.customerName || "").trim();
- return (
+ // 涓氬姟鍛�
+ 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 || "";
- }
+ );
+ });
+ 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(
+ // 浜у搧淇℃伅鏄犲皠锛氭姤浠� 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,
- };
- });
+ );
+ 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,
+ productId: p.productId,
+ productModelId: p.productModelId
+ };
+ });
- quotationDialogVisible.value = false;
-};
+ quotationDialogVisible.value = false;
+ };
-function changs(val) {
- console.log(val);
-}
+ function changs(val) {
+ console.log(val);
+ }
-// 鎻愪氦琛ㄥ崟
-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;
+ // 鎻愪氦琛ㄥ崟
+ 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;
+ }
+ form.value.storageBlobDTOs = fileList;
+ form.value.type = 1;
+ if (form.value.autoGenerateContractNo) {
+ form.value.salesContractNo = '';
+ }
+ addOrUpdateSalesLedger(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ expandedRowKeys.value = [];
+ getList();
+ });
}
- 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;
+ };
+
+ const productIndex = ref(0);
+ // 鎵撳紑浜у搧寮规
+ const openProductForm = async (type, row, index) => {
+ // 缂栬緫鏃舵鏌ヤ骇鍝佹槸鍚﹀凡鍙戣揣鎴栧鏍搁�氳繃
+ if (type === "edit" && isProductShipped(row)) {
+ proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳界紪杈�");
+ return;
}
- });
-};
-// 鍏抽棴寮规
-const closeDia = () => {
- 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 = {};
- if (type === "add") {
- productForm.value.isProduction = true;
- }
- proxy.resetForm("productFormRef");
- if (type === "edit") {
- productForm.value = {...row};
- productIndex.value = index;
- // 缂栬緫鏃舵牴鎹骇鍝佸ぇ绫诲悕绉板弽鏌� tree 鑺傜偣 id锛屽苟鍔犺浇瑙勬牸鍨嬪彿鍒楄〃
- try {
- const options =
+ 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(
+ ? 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;
+ 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);
}
- } catch (e) {
- // 鍔犺浇澶辫触鏃朵繚鎸佸彲缂栬緫锛屼笉涓柇寮圭獥
- console.error("鍔犺浇浜у搧瑙勬牸鍨嬪彿澶辫触", e);
+ } else {
+ getProductOptions();
}
- } 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});
+ productFormVisible.value = true;
+ };
+ // 鎻愪氦浜у搧琛ㄥ崟
+ const submitProduct = () => {
+ proxy.$refs["productFormRef"].validate(valid => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitProductEdit();
} else {
- productData.value[productIndex.value] = {...productForm.value};
+ if (productOperationType.value === "add") {
+ productData.value.push({ ...productForm.value });
+ } else {
+ productData.value[productIndex.value] = { ...productForm.value };
+ }
+ closeProductDia();
}
- 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;
});
- });
-};
-// 鍒犻櫎浜у搧
-const deleteProduct = () => {
- if (productSelectedRows.value.length === 0) {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
+ };
+ 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;
+ });
+ });
+ };
+ // 鍒犻櫎浜у搧
+ const deleteProduct = () => {
+ if (productSelectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
- // 妫�鏌ユ槸鍚︽湁宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝�
- const shippedProducts = productSelectedRows.value.filter(row =>
+ // 妫�鏌ユ槸鍚︽湁宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝�
+ 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);
+ );
+ if (shippedProducts.length > 0) {
+ proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳藉垹闄�");
+ return;
}
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
+
+ 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;
- }
+ getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then(
+ res => {
+ productData.value = res.productData;
+ }
);
});
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
});
- }
-};
-// 鍏抽棴浜у搧寮规
-const closeProductDia = () => {
- proxy.resetForm("productFormRef");
- productFormVisible.value = false;
-};
-// 瀵煎叆
-const handleImport = () => {
- importUpload.title = "瀵煎叆閿�鍞彴璐�";
- importUpload.open = true;
- if (importUploadRef.value) {
- importUploadRef.value.clearFiles();
- }
-};
+ }
+ };
+ // 鍏抽棴浜у搧寮规
+ const closeProductDia = () => {
+ proxy.resetForm("productFormRef");
+ productFormVisible.value = false;
+ };
+ // 瀵煎叆
+ const handleImport = () => {
+ importUpload.title = "瀵煎叆閿�鍞彴璐�";
+ importUpload.open = true;
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ }
+ };
-// 涓嬭浇瀵煎叆妯℃澘
-const downloadTemplate = () => {
- proxy.download("/sales/ledger/exportTemplate", {}, "閿�鍞彴璐﹀鍏ユā鏉�.xlsx");
-};
+ // 涓嬭浇瀵煎叆妯℃澘
+ const downloadTemplate = () => {
+ proxy.download("/sales/ledger/exportTemplate", {}, "閿�鍞彴璐﹀鍏ユā鏉�.xlsx");
+ };
-// 鎻愪氦瀵煎叆鏂囦欢
-const submitImportFile = () => {
- importUpload.isUploading = true;
- proxy.$refs["importUploadRef"].submit();
-};
+ // 鎻愪氦瀵煎叆鏂囦欢
+ const submitImportFile = () => {
+ importUpload.isUploading = true;
+ proxy.$refs["importUploadRef"].submit();
+ };
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
+ // 瀵煎嚭
+ const handleOut = () => {
+ 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 === "瀹℃牳閫氳繃";
-};
+ };
+ /** 鍒ゆ柇鍗曚釜浜у搧鏄惁宸插彂璐э紙鏍规嵁shippingStatus鍒ゆ柇锛屽凡鍙戣揣鎴栧鏍搁�氳繃涓嶅彲缂栬緫鍜屽垹闄わ級 */
+ 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 (
+ /** 鍒ゆ柇閿�鍞鍗曚笅鏄惁瀛樺湪宸插彂璐�/鍙戣揣瀹屾垚鐨勪骇鍝侊紙涓嶅彲鍒犻櫎锛� */
+ 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 unauthorizedRows = selectedRows.value.filter(
+ // 鍒犻櫎
+ const handleDelete = async () => {
+ 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);
+ );
+ 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 =
+ // 妫�鏌ユ槸鍚︽湁宸茶繘琛屽彂璐ф垨鍙戣揣瀹屾垚鐨勯攢鍞鍗曪紝鑻ユ湁鍒欎笉鍏佽鍒犻櫎
+ 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 (!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 (hasShippedProducts(products)) {
- cannotDeleteNames.push(row.salesContractNo || `ID:${row.id}`);
- }
- }
- if (cannotDeleteNames.length > 0) {
- proxy.$modal.msgWarning(
+ if (cannotDeleteNames.length > 0) {
+ proxy.$modal.msgWarning(
"宸茶繘琛屽彂璐ф垨鍙戣揣瀹屾垚鐨勯攢鍞鍗曚笉鑳藉垹闄わ細" + cannotDeleteNames.join("銆�")
- );
- return;
- }
+ );
+ return;
+ }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
.then(() => {
delLedger(ids).then(res => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
@@ -1902,985 +1999,1025 @@
.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();
- }
-};
-// 鎵ц鎵撳嵃
-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="document-title">闆跺敭鍙戣揣鍗�</div>
- </div>
-
- <div class="info-section">
- <div class="info-row">
- <div>
- <span class="label">鍙戣揣鏃ユ湡锛�</span>
- <span class="value">${formatDate(
- item.createTime
- )}</span>
- </div>
- <div>
- <span class="label">瀹㈡埛鍚嶇О锛�</span>
- <span class="value">${
- item.customerName
- }</span>
- </div>
- </div>
- <div class="info-row">
- <span class="label">鍗曞彿锛�</span>
- <span class="value">${
- item.salesContractNo ||
- ""
- }</span>
- </div>
- </div>
-
- <div class="table-section">
- <table class="product-table">
- <thead>
- <tr>
- <th>浜у搧鍚嶇О</th>
- <th>瑙勬牸鍨嬪彿</th>
- <th>鍗曚綅</th>
- <th>鍗曚环</th>
- <th>闆跺敭鏁伴噺</th>
- <th>闆跺敭閲戦</th>
- </tr>
- </thead>
- <tbody>
- ${
- item.products &&
- item.products
- .length > 0
- ? item.products
- .map(
- product => `
- <tr>
- <td>${
- product.productCategory ||
- ""
- }</td>
- <td>${
- product.specificationModel ||
- ""
- }</td>
- <td>${
- product.unit ||
- ""
- }</td>
- <td>${
- product.taxInclusiveUnitPrice ||
- "0"
- }</td>
- <td>${
- product.quantity ||
- "0"
- }</td>
- <td>${
- product.taxInclusiveTotalPrice ||
- "0"
- }</td>
- </tr>
- `
- )
- .join("")
- : '<tr><td colspan="6" style="text-align: center; color: #999;">鏆傛棤浜у搧鏁版嵁</td></tr>'
- }
- </tbody>
- <tfoot>
- <tr>
- <td class="label">鍚堣</td>
- <td class="total-value"></td>
- <td class="total-value"></td>
- <td class="total-value"></td>
- <td class="total-value">${getTotalQuantityForPrint(
- item.products
- )}</td>
- <td class="total-value">${getTotalAmountForPrint(
- item.products
- )}</td>
- </tr>
- </tfoot>
- </table>
- </div>
-
- <div class="footer-section">
- <div class="footer-row">
- <div class="footer-item">
- <span class="label">鏀惰揣鐢佃瘽锛�</span>
- <span class="value"></span>
- </div>
- <div class="footer-item">
- <span class="label">鏀惰揣浜猴細</span>
- <span class="value"></span>
- </div>
- <div class="footer-item address-item">
- <span class="label">鏀惰揣鍦板潃锛�</span>
- <span class="value address-value"></span>
- </div>
- </div>
- <div class="footer-row">
- <div class="footer-item">
- <span class="label">鎿嶄綔鍛橈細</span>
- <span class="value">${
- userStore.nickName ||
- "鎾曞紑鍓�"
- }</span>
- </div>
- <div class="footer-item">
- <span class="label">鎵撳嵃鏃ユ湡锛�</span>
- <span class="value">${formatDateTime(
- new Date()
- )}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- `;
- });
-
- printContent += `
- </body>
- </html>
- `;
-
- // 鍐欏叆鍐呭鍒版柊绐楀彛
- printWindow.document.write(printContent);
- printWindow.document.close();
-
- // 绛夊緟鍐呭鍔犺浇瀹屾垚鍚庢墦鍗�
- printWindow.onload = () => {
- setTimeout(() => {
- printWindow.print();
- printWindow.close();
- printPreviewVisible.value = false;
- }, 500);
};
-};
-// 鏍煎紡鍖栨棩鏈�
-const formatDate = dateString => {
- if (!dateString) return getCurrentDate();
- const date = new Date(dateString);
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, "0");
- const day = String(date.getDate()).padStart(2, "0");
- return `${year}/${month}/${day}`;
-};
-// 鏍煎紡鍖栨棩鏈熸椂闂�
-const formatDateTime = date => {
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, "0");
- const day = String(date.getDate()).padStart(2, "0");
- const hours = String(date.getHours()).padStart(2, "0");
- const minutes = String(date.getMinutes()).padStart(2, "0");
- const seconds = String(date.getSeconds()).padStart(2, "0");
- return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
-};
-// 璁$畻浜у搧鎬绘暟閲�
-const getTotalQuantity = products => {
- if (!products || products.length === 0) return "0";
- const total = products.reduce((sum, product) => {
- return sum + (parseFloat(product.quantity) || 0);
- }, 0);
- return total.toFixed(2);
-};
-// 璁$畻浜у搧鎬婚噾棰�
-const getTotalAmount = products => {
- if (!products || products.length === 0) return "0";
- const total = products.reduce((sum, product) => {
- return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
- }, 0);
- return total.toFixed(2);
-};
+ // 鎵撳嵃鍔熻兘
+ const handlePrint = async () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
+ return;
+ }
-// 鐢ㄤ簬鎵撳嵃鐨勮绠楀嚱鏁�
-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);
-};
+ // 鏄剧ず鍔犺浇鐘舵��
+ proxy.$modal.loading("姝e湪鑾峰彇浜у搧鏁版嵁锛岃绋嶅��...");
-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);
-};
+ try {
+ // 涓烘瘡涓�変腑鐨勯攢鍞彴璐﹁褰曟煡璇㈠搴旂殑浜у搧鏁版嵁
+ const printDataWithProducts = [];
-const mathNum = () => {
- console.log("productForm.value", productForm.value);
- if (!productForm.value.taxInclusiveUnitPrice) {
- return;
- }
- if (!productForm.value.quantity) {
- return;
- }
- // 鍚◣鎬讳环璁$畻
- productForm.value.taxInclusiveTotalPrice =
+ 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="document-title">闆跺敭鍙戣揣鍗�</div>
+ </div>
+
+ <div class="info-section">
+ <div class="info-row">
+ <div>
+ <span class="label">鍙戣揣鏃ユ湡锛�</span>
+ <span class="value">${formatDate(
+ item.createTime
+ )}</span>
+ </div>
+ <div>
+ <span class="label">瀹㈡埛鍚嶇О锛�</span>
+ <span class="value">${
+ item.customerName
+ }</span>
+ </div>
+ </div>
+ <div class="info-row">
+ <span class="label">鍗曞彿锛�</span>
+ <span class="value">${
+ item.salesContractNo ||
+ ""
+ }</span>
+ </div>
+ </div>
+
+ <div class="table-section">
+ <table class="product-table">
+ <thead>
+ <tr>
+ <th>浜у搧鍚嶇О</th>
+ <th>瑙勬牸鍨嬪彿</th>
+ <th>鍗曚綅</th>
+ <th>鍗曚环</th>
+ <th>闆跺敭鏁伴噺</th>
+ <th>闆跺敭閲戦</th>
+ </tr>
+ </thead>
+ <tbody>
+ ${
+ item.products &&
+ item
+ .products
+ .length >
+ 0
+ ? item.products
+ .map(
+ product => `
+ <tr>
+ <td>${
+ product.productCategory ||
+ ""
+ }</td>
+ <td>${
+ product.specificationModel ||
+ ""
+ }</td>
+ <td>${
+ product.unit ||
+ ""
+ }</td>
+ <td>${
+ product.taxInclusiveUnitPrice ||
+ "0"
+ }</td>
+ <td>${
+ product.quantity ||
+ "0"
+ }</td>
+ <td>${
+ product.taxInclusiveTotalPrice ||
+ "0"
+ }</td>
+ </tr>
+ `
+ )
+ .join(
+ ""
+ )
+ : '<tr><td colspan="6" style="text-align: center; color: #999;">鏆傛棤浜у搧鏁版嵁</td></tr>'
+ }
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="label">鍚堣</td>
+ <td class="total-value"></td>
+ <td class="total-value"></td>
+ <td class="total-value"></td>
+ <td class="total-value">${getTotalQuantityForPrint(
+ item.products
+ )}</td>
+ <td class="total-value">${getTotalAmountForPrint(
+ item.products
+ )}</td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+
+ <div class="footer-section">
+ <div class="footer-row">
+ <div class="footer-item">
+ <span class="label">鏀惰揣鐢佃瘽锛�</span>
+ <span class="value"></span>
+ </div>
+ <div class="footer-item">
+ <span class="label">鏀惰揣浜猴細</span>
+ <span class="value"></span>
+ </div>
+ <div class="footer-item address-item">
+ <span class="label">鏀惰揣鍦板潃锛�</span>
+ <span class="value address-value"></span>
+ </div>
+ </div>
+ <div class="footer-row">
+ <div class="footer-item">
+ <span class="label">鎿嶄綔鍛橈細</span>
+ <span class="value">${
+ userStore.nickName ||
+ "鎾曞紑鍓�"
+ }</span>
+ </div>
+ <div class="footer-item">
+ <span class="label">鎵撳嵃鏃ユ湡锛�</span>
+ <span class="value">${formatDateTime(
+ new Date()
+ )}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ `;
+ });
+
+ printContent += `
+ </body>
+ </html>
+ `;
+
+ // 鍐欏叆鍐呭鍒版柊绐楀彛
+ printWindow.document.write(printContent);
+ printWindow.document.close();
+
+ // 绛夊緟鍐呭鍔犺浇瀹屾垚鍚庢墦鍗�
+ printWindow.onload = () => {
+ setTimeout(() => {
+ printWindow.print();
+ printWindow.close();
+ printPreviewVisible.value = false;
+ }, 500);
+ };
+ };
+ // 鏍煎紡鍖栨棩鏈�
+ const formatDate = dateString => {
+ if (!dateString) return getCurrentDate();
+ const date = new Date(dateString);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}/${month}/${day}`;
+ };
+ // 鏍煎紡鍖栨棩鏈熸椂闂�
+ const formatDateTime = date => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ const hours = String(date.getHours()).padStart(2, "0");
+ const minutes = String(date.getMinutes()).padStart(2, "0");
+ const seconds = String(date.getSeconds()).padStart(2, "0");
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
+ };
+ // 璁$畻浜у搧鎬绘暟閲�
+ const getTotalQuantity = products => {
+ if (!products || products.length === 0) return "0";
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.quantity) || 0);
+ }, 0);
+ return total.toFixed(2);
+ };
+
+ // 璁$畻浜у搧鎬婚噾棰�
+ const getTotalAmount = products => {
+ if (!products || products.length === 0) return "0";
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
+ }, 0);
+ return total.toFixed(2);
+ };
+
+ // 鐢ㄤ簬鎵撳嵃鐨勮绠楀嚱鏁�
+ const getTotalQuantityForPrint = products => {
+ if (!products || products.length === 0) return "0";
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.quantity) || 0);
+ }, 0);
+ return total.toFixed(2);
+ };
+
+ const getTotalAmountForPrint = products => {
+ if (!products || products.length === 0) return "0";
+ const total = products.reduce((sum, product) => {
+ return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
+ }, 0);
+ return total.toFixed(2);
+ };
+
+ const mathNum = () => {
+ console.log("productForm.value", productForm.value);
+ if (!productForm.value.taxInclusiveUnitPrice) {
+ return;
+ }
+ if (!productForm.value.quantity) {
+ return;
+ }
+ // 鍚◣鎬讳环璁$畻
+ productForm.value.taxInclusiveTotalPrice =
proxy.calculateTaxIncludeTotalPrice(
- productForm.value.taxInclusiveUnitPrice,
- productForm.value.quantity
+ productForm.value.taxInclusiveUnitPrice,
+ productForm.value.quantity
);
- if (productForm.value.taxRate) {
- // 涓嶅惈绋庢�讳环璁$畻
- productForm.value.taxExclusiveTotalPrice =
+ if (productForm.value.taxRate) {
+ // 涓嶅惈绋庢�讳环璁$畻
+ productForm.value.taxExclusiveTotalPrice =
proxy.calculateTaxExclusiveTotalPrice(
- productForm.value.taxInclusiveTotalPrice,
- productForm.value.taxRate
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
);
- }
-};
+ }
+ };
-// 鏍规嵁鍚◣鎬讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
-const calculateFromTotalPrice = () => {
- if (isCalculating.value) return;
+ // 鏍规嵁鍚◣鎬讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
+ const calculateFromTotalPrice = () => {
+ if (isCalculating.value) return;
- const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
- const quantity = parseFloat(productForm.value.quantity);
+ const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
+ const quantity = parseFloat(productForm.value.quantity);
- if (!totalPrice || !quantity || quantity <= 0) {
- return;
- }
+ if (!totalPrice || !quantity || quantity <= 0) {
+ return;
+ }
- isCalculating.value = true;
+ isCalculating.value = true;
- // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
- productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
+ // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
+ productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
- // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
- if (productForm.value.taxRate) {
- productForm.value.taxExclusiveTotalPrice =
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
proxy.calculateTaxExclusiveTotalPrice(
- totalPrice,
- productForm.value.taxRate
+ totalPrice,
+ productForm.value.taxRate
);
- }
+ }
- isCalculating.value = false;
-};
+ isCalculating.value = false;
+ };
-// 鏍规嵁涓嶅惈绋庢�讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
-const calculateFromExclusiveTotalPrice = () => {
- if (!productForm.value.taxRate) {
- proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
- return;
- }
- if (isCalculating.value) return;
+ // 鏍规嵁涓嶅惈绋庢�讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
+ const calculateFromExclusiveTotalPrice = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (isCalculating.value) return;
- const exclusiveTotalPrice = parseFloat(
+ const exclusiveTotalPrice = parseFloat(
productForm.value.taxExclusiveTotalPrice
- );
- const quantity = parseFloat(productForm.value.quantity);
- const taxRate = parseFloat(productForm.value.taxRate);
+ );
+ const quantity = parseFloat(productForm.value.quantity);
+ const taxRate = parseFloat(productForm.value.taxRate);
- if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
- return;
- }
+ if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
+ return;
+ }
- isCalculating.value = true;
+ isCalculating.value = true;
- // 鍏堣绠楀惈绋庢�讳环 = 涓嶅惈绋庢�讳环 / (1 - 绋庣巼/100)
- const taxRateDecimal = taxRate / 100;
- const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
- productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
+ // 鍏堣绠楀惈绋庢�讳环 = 涓嶅惈绋庢�讳环 / (1 - 绋庣巼/100)
+ const taxRateDecimal = taxRate / 100;
+ const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
+ productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
- // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
- productForm.value.taxInclusiveUnitPrice = (
+ // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
+ productForm.value.taxInclusiveUnitPrice = (
inclusiveTotalPrice / quantity
- ).toFixed(2);
+ ).toFixed(2);
- isCalculating.value = false;
-};
+ isCalculating.value = false;
+ };
-// 鏍规嵁鏁伴噺鍙樺寲璁$畻鎬讳环
-const calculateFromQuantity = () => {
- if (!productForm.value.taxRate) {
- proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
- return;
- }
- if (isCalculating.value) return;
+ // 鏍规嵁鏁伴噺鍙樺寲璁$畻鎬讳环
+ 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);
+ const quantity = parseFloat(productForm.value.quantity);
+ const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
- if (!quantity || quantity <= 0 || !unitPrice) {
- return;
- }
+ if (!quantity || quantity <= 0 || !unitPrice) {
+ return;
+ }
- isCalculating.value = true;
+ isCalculating.value = true;
- // 璁$畻鍚◣鎬讳环
- productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
+ // 璁$畻鍚◣鎬讳环
+ productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
- // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
- if (productForm.value.taxRate) {
- productForm.value.taxExclusiveTotalPrice =
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
proxy.calculateTaxExclusiveTotalPrice(
- productForm.value.taxInclusiveTotalPrice,
- productForm.value.taxRate
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
);
- }
+ }
- isCalculating.value = false;
-};
+ isCalculating.value = false;
+ };
-// 鏍规嵁鍚◣鍗曚环鍙樺寲璁$畻鎬讳环
-const calculateFromUnitPrice = () => {
- if (!productForm.value.taxRate) {
- proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
- return;
- }
- if (isCalculating.value) return;
+ // 鏍规嵁鍚◣鍗曚环鍙樺寲璁$畻鎬讳环
+ 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);
+ const quantity = parseFloat(productForm.value.quantity);
+ const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
- if (!quantity || quantity <= 0 || !unitPrice) {
- return;
- }
+ if (!quantity || quantity <= 0 || !unitPrice) {
+ return;
+ }
- isCalculating.value = true;
+ isCalculating.value = true;
- // 璁$畻鍚◣鎬讳环
- productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
+ // 璁$畻鍚◣鎬讳环
+ productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
- // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
- if (productForm.value.taxRate) {
- productForm.value.taxExclusiveTotalPrice =
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
proxy.calculateTaxExclusiveTotalPrice(
- productForm.value.taxInclusiveTotalPrice,
- productForm.value.taxRate
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
);
- }
+ }
- isCalculating.value = false;
-};
+ isCalculating.value = false;
+ };
-// 鏍规嵁绋庣巼鍙樺寲璁$畻涓嶅惈绋庢�讳环
-const calculateFromTaxRate = () => {
- if (!productForm.value.taxRate) {
- proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
- return;
- }
- if (isCalculating.value) return;
+ // 鏍规嵁绋庣巼鍙樺寲璁$畻涓嶅惈绋庢�讳环
+ const calculateFromTaxRate = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (isCalculating.value) return;
- const inclusiveTotalPrice = parseFloat(
+ const inclusiveTotalPrice = parseFloat(
productForm.value.taxInclusiveTotalPrice
- );
- const taxRate = parseFloat(productForm.value.taxRate);
+ );
+ const taxRate = parseFloat(productForm.value.taxRate);
- if (!inclusiveTotalPrice || !taxRate) {
- return;
- }
+ if (!inclusiveTotalPrice || !taxRate) {
+ return;
+ }
- isCalculating.value = true;
+ isCalculating.value = true;
- // 璁$畻涓嶅惈绋庢�讳环
- productForm.value.taxExclusiveTotalPrice =
+ // 璁$畻涓嶅惈绋庢�讳环
+ 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 = {
- 寰呭彂璐�: "寰呭彂璐�",
- 寰呭鏍�: "寰呭鏍�",
- 瀹℃牳涓�: "瀹℃牳涓�",
- 瀹℃牳鎷掔粷: "瀹℃牳鎷掔粷",
- 瀹℃牳閫氳繃: "瀹℃牳閫氳繃",
- 宸插彂璐�: "宸插彂璐�",
+ isCalculating.value = false;
};
- return statusTextMap[statusStr] || "寰呭彂璐�";
-};
+ /**
+ * 鑾峰彇鍙戣揣鐘舵�佹枃鏈�
+ * @param row 琛屾暟鎹�
+ */
+ const getShippingStatusText = row => {
+ // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屾樉绀�"宸插彂璐�"
+ // if (row.shippingDate || row.shippingCarNumber) {
+ // return "宸插彂璐�";
+ // }
-/**
- * 鑾峰彇鍙戣揣鐘舵�佹爣绛剧被鍨嬶紙棰滆壊锛�
- * @param row 琛屾暟鎹�
- */
-const getShippingStatusType = row => {
- // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屾樉绀虹豢鑹�
- if (row.shippingStatus === "宸插彂璐�") {
- return "success";
- }
+ // 鑾峰彇鍙戣揣鐘舵�佸瓧娈�
+ const status = row.shippingStatus;
- // 鑾峰彇鍙戣揣鐘舵�佸瓧娈�
- const status = row.shippingStatus;
+ // 濡傛灉鐘舵�佷负绌烘垨鏈畾涔夛紝榛樿涓�"寰呭彂璐�"
+ if (status === null || status === undefined || status === "") {
+ return "寰呭彂璐�";
+ }
- // 濡傛灉鐘舵�佷负绌烘垨鏈畾涔夛紝榛樿涓虹伆鑹诧紙寰呭彂璐э級
- if (status === null || status === undefined || status === "") {
- return "info";
- }
-
- // 鐘舵�佹槸瀛楃涓�
- const statusStr = String(status).trim();
- const typeTextMap = {
- 寰呭彂璐�: "info",
- 寰呭鏍�: "info",
- 瀹℃牳涓�: "warning",
- 瀹℃牳鎷掔粷: "danger",
- 瀹℃牳閫氳繃: "success",
- 宸插彂璐�: "success",
+ // 鐘舵�佹槸瀛楃涓�
+ const statusStr = String(status).trim();
+ const statusTextMap = {
+ 寰呭彂璐�: "寰呭彂璐�",
+ 寰呭鏍�: "寰呭鏍�",
+ 瀹℃牳涓�: "瀹℃牳涓�",
+ 瀹℃牳鎷掔粷: "瀹℃牳鎷掔粷",
+ 瀹℃牳閫氳繃: "瀹℃牳閫氳繃",
+ 宸插彂璐�: "宸插彂璐�",
+ 閮ㄥ垎鍙戣揣: "閮ㄥ垎鍙戣揣",
+ };
+ return statusTextMap[statusStr] || "寰呭彂璐�";
};
- return typeTextMap[statusStr] || "info";
-};
-/**
- * 鍒ゆ柇鏄惁鍙互鍙戣揣
- * 鍙湁鍦ㄤ骇鍝佺姸鎬佹槸鍏呰冻锛屽彂璐х姸鎬佹槸寰呭彂璐у拰瀹℃牳鎷掔粷鐨勬椂鍊欐墠鍙互鍙戣揣
- * @param row 琛屾暟鎹�
- */
-const canShip = row => {
- // 浜у搧鐘舵�佸繀椤绘槸鍏呰冻锛坅pproveStatus === 1锛�
- if (row.approveStatus !== 1) {
- return false;
- }
+ /**
+ * 鑾峰彇鍙戣揣鐘舵�佹爣绛剧被鍨嬶紙棰滆壊锛�
+ * @param row 琛屾暟鎹�
+ */
+ const getShippingStatusType = row => {
+ // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屾樉绀虹豢鑹�
+ if (row.shippingStatus === "宸插彂璐�") {
+ return "success";
+ }
- // 鑾峰彇鍙戣揣鐘舵��
- const shippingStatus = row.shippingStatus;
+ // 鑾峰彇鍙戣揣鐘舵�佸瓧娈�
+ const status = row.shippingStatus;
- // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屼笉鑳藉啀娆″彂璐�
- if (shippingStatus === "宸插彂璐�") {
- return false;
- }
+ // 濡傛灉鐘舵�佷负绌烘垨鏈畾涔夛紝榛樿涓虹伆鑹诧紙寰呭彂璐э級
+ if (status === null || status === undefined || status === "") {
+ return "info";
+ }
- // 鍙戣揣鐘舵�佸繀椤绘槸"寰呭彂璐�"鎴�"瀹℃牳鎷掔粷"
- const statusStr = shippingStatus ? String(shippingStatus).trim() : "";
- return statusStr === "寰呭彂璐�" || statusStr === "瀹℃牳鎷掔粷";
-};
+ // 鐘舵�佹槸瀛楃涓�
+ const statusStr = String(status).trim();
+ const typeTextMap = {
+ 寰呭彂璐�: "info",
+ 寰呭鏍�: "info",
+ 瀹℃牳涓�: "warning",
+ 瀹℃牳鎷掔粷: "danger",
+ 瀹℃牳閫氳繃: "success",
+ 宸插彂璐�: "success",
+ 閮ㄥ垎鍙戣揣: "warning",
+ };
+ return typeTextMap[statusStr] || "info";
+ };
-// 鎵撳紑闄勪欢寮圭獥
-const recordId = ref(0)
-const fileDialogVisible = ref(false)
+ /**
+ * 鍒ゆ柇鏄惁鍙互鍙戣揣
+ * 鍙湁鍦ㄤ骇鍝佺姸鎬佹槸鍏呰冻锛屽彂璐х姸鎬佹槸寰呭彂璐у拰瀹℃牳鎷掔粷鐨勬椂鍊欐墠鍙互鍙戣揣
+ * @param row 琛屾暟鎹�
+ */
+ const canShip = row => {
+ // 浜у搧鐘舵�佸繀椤绘槸鍏呰冻锛坅pproveStatus === 1锛�
+ if (row.approveStatus !== 1) {
+ return false;
+ }
-// 鎵撳紑闄勪欢寮规
-const openFileDialog = async (row) => {
- recordId.value = row.id
- fileDialogVisible.value = true
-}
+ // 鑾峰彇鍙戣揣鐘舵��
+ const shippingStatus = row.shippingStatus;
-// 鎵撳紑鍙戣揣寮规
-const openDeliveryForm = async row => {
- // 妫�鏌ユ槸鍚﹀彲浠ュ彂璐�
- if (!canShip(row)) {
- proxy.$modal.msgWarning(
+ // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屼笉鑳藉啀娆″彂璐�
+ if (shippingStatus === "宸插彂璐�") {
+ return false;
+ }
+
+ // 鍙戣揣鐘舵�佸繀椤绘槸"寰呭彂璐�"鎴�"瀹℃牳鎷掔粷"
+ const statusStr = shippingStatus ? String(shippingStatus).trim() : "";
+ return statusStr === "寰呭彂璐�" || statusStr === "瀹℃牳鎷掔粷" || statusStr === "閮ㄥ垎鍙戣揣";
+ };
+
+ // 鎵撳紑闄勪欢寮圭獥
+ const recordId = ref(0);
+ const fileDialogVisible = ref(false);
+
+ // 鎵撳紑闄勪欢寮规
+ const openFileDialog = async row => {
+ recordId.value = row.id;
+ fileDialogVisible.value = true;
+ };
+
+ // 鎵撳紑鍙戣揣寮规
+ const openDeliveryForm = async row => {
+ // 妫�鏌ユ槸鍚﹀彲浠ュ彂璐�
+ if (!canShip(row)) {
+ proxy.$modal.msgWarning(
"鍙湁鍦ㄤ骇鍝佺姸鎬佹槸鍏呰冻锛屽彂璐х姸鎬佹槸寰呭彂璐ф垨瀹℃牳鎷掔粷鐨勬椂鍊欐墠鍙互鍙戣揣"
- );
- return;
- }
+ );
+ return;
+ }
- currentDeliveryRow.value = row;
- const batchNoList = await getDeliveryBatchNoList(
+ currentDeliveryRow.value = row;
+ const batchNoList = await getDeliveryBatchNoList(
row.productModelId || row.modelId
- );
- deliveryForm.value = {
- type: "璐ц溅",
- batchNo: [],
- batchNoList,
+ );
+ deliveryForm.value = {
+ shippingCarNumber: "",
+ expressCompany: "",
+ expressNumber: "",
+ type: "璐ц溅",
+ batchNo: [],
+ batchNoList,
+ };
+ deliveryFileList.value = [];
+ deliveryFormVisible.value = true;
};
- deliveryFormVisible.value = true;
-};
-// 鎻愪氦鍙戣揣琛ㄥ崟
-const submitDelivery = () => {
- proxy.$refs["deliveryFormRef"].validate(valid => {
- if (valid) {
- const selectedBatchRows = getSelectedDeliveryBatchRows();
- if (selectedBatchRows.length === 0) {
- proxy.$modal.msgWarning("璇疯嚦灏戝~鍐欎竴涓壒鍙风殑鍙戣揣鏁伴噺");
- return;
- }
- const totalDeliveryQuantity = selectedBatchRows.reduce(
+ // 鎻愪氦鍙戣揣琛ㄥ崟
+ const submitDelivery = () => {
+ proxy.$refs["deliveryFormRef"].validate(valid => {
+ if (valid) {
+ const selectedBatchRows = getSelectedDeliveryBatchRows();
+ if (selectedBatchRows.length === 0) {
+ proxy.$modal.msgWarning("璇疯嚦灏戝~鍐欎竴涓壒鍙风殑鍙戣揣鏁伴噺");
+ return;
+ }
+ 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,
- 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(
+ );
+ const currentRowNoQuantity = Number(
+ currentDeliveryRow.value?.noQuantity || 0
+ );
+ if (
+ currentRowNoQuantity > 0 &&
+ totalDeliveryQuantity > currentRowNoQuantity
+ ) {
+ 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,
+ shippingCarNumber:
+ deliveryForm.value.type === "璐ц溅"
+ ? deliveryForm.value.shippingCarNumber
+ : "",
+ expressCompany:
+ deliveryForm.value.type === "蹇��"
+ ? deliveryForm.value.expressCompany
+ : "",
+ expressNumber:
+ deliveryForm.value.type === "蹇��"
+ ? deliveryForm.value.expressNumber
+ : "",
+ storageBlobDTOs: deliveryFileList.value || [],
+ 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
+ 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;
+ });
+ }
+ });
});
- });
- }
- });
-};
+ }
+ });
+ };
-// 鍏抽棴鍙戣揣寮规
-const closeDeliveryDia = () => {
- proxy.resetForm("deliveryFormRef");
- deliveryFormVisible.value = false;
- currentDeliveryRow.value = null;
-};
-const currentFactoryName = ref("");
-const getCurrentFactoryName = async () => {
- let res = await userStore.getInfo();
- currentFactoryName.value = res.user.currentFactoryName;
-};
-onMounted(() => {
- searchForm.salesContractNo = route.query.salesContractNo;
- getList();
- userListNoPage().then(res => {
- userList.value = res.data;
+ // 鍏抽棴鍙戣揣寮规
+ const handleDeliveryTypeChange = val => {
+ if (val === "璐ц溅") {
+ deliveryForm.value.expressCompany = "";
+ deliveryForm.value.expressNumber = "";
+ } else {
+ deliveryForm.value.shippingCarNumber = "";
+ }
+ };
+
+ const closeDeliveryDia = () => {
+ proxy.resetForm("deliveryFormRef");
+ deliveryFileList.value = [];
+ deliveryFormVisible.value = false;
+ currentDeliveryRow.value = null;
+ };
+ const currentFactoryName = ref("");
+ const getCurrentFactoryName = async () => {
+ let res = await userStore.getInfo();
+ currentFactoryName.value = res.user.currentFactoryName;
+ };
+ onMounted(() => {
+ searchForm.salesContractNo = route.query.salesContractNo;
+ getList();
+ userListNoPage().then(res => {
+ userList.value = res.data;
+ });
+ getCurrentFactoryName();
});
- getCurrentFactoryName();
-});
</script>
<style scoped lang="scss">
-.ml-10 {
- margin-left: 10px;
-}
-
-:deep(.yellow) {
- background-color: #faf0de;
-}
-
-:deep(.pink) {
- background-color: #fae1de;
-}
-
-:deep(.red) {
- background-color: #fae1de;
-}
-
-:deep(.purple) {
- background-color: #f4defa;
-}
-
-.table_list {
- margin-top: unset;
-}
-
-.actions {
- display: flex;
- justify-content: space-between;
- margin-bottom: 10px;
-}
-
-.print-preview-dialog {
- .el-dialog__body {
- padding: 0;
- max-height: 80vh;
- overflow-y: auto;
- }
-}
-
-.print-preview-container {
- .print-preview-header {
- padding: 15px;
- border-bottom: 1px solid #e4e7ed;
- text-align: center;
-
- .el-button {
- margin: 0 10px;
- }
+ .ml-10 {
+ margin-left: 10px;
}
- .print-preview-content {
- padding: 20px;
- background-color: #f5f5f5;
- min-height: 400px;
- }
-}
-
-.print-page {
- width: 220mm;
- height: 90mm;
- padding: 10mm;
- margin: 0 auto;
- background: white;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
- margin-bottom: 10px;
- box-sizing: border-box;
-}
-
-.delivery-note {
- width: 100%;
- height: 100%;
- font-family: "SimSun", serif;
- font-size: 10px;
- line-height: 1.2;
- display: flex;
- flex-direction: column;
-}
-
-.header {
- text-align: center;
- margin-bottom: 8px;
-
- .company-name {
- font-size: 18px;
- font-weight: bold;
- margin-bottom: 4px;
+ :deep(.yellow) {
+ background-color: #faf0de;
}
- .document-title {
- font-size: 16px;
- font-weight: bold;
+ :deep(.pink) {
+ background-color: #fae1de;
}
-}
-.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;
- }
+ :deep(.red) {
+ background-color: #fae1de;
}
-}
-.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;
- }
+ :deep(.purple) {
+ background-color: #f4defa;
}
-}
-.footer-section {
- .footer-row {
+ .table_list {
+ margin-top: unset;
+ }
+
+ .actions {
display: flex;
- margin-bottom: 3px;
- line-height: 20px;
justify-content: space-between;
+ margin-bottom: 10px;
+ }
- .footer-item {
- display: flex;
- margin-right: 20px;
+ .print-preview-dialog {
+ .el-dialog__body {
+ padding: 0;
+ max-height: 80vh;
+ overflow-y: auto;
+ }
+ }
+
+ .print-preview-container {
+ .print-preview-header {
+ padding: 15px;
+ border-bottom: 1px solid #e4e7ed;
+ text-align: center;
+
+ .el-button {
+ margin: 0 10px;
+ }
+ }
+
+ .print-preview-content {
+ padding: 20px;
+ background-color: #f5f5f5;
+ min-height: 400px;
+ }
+ }
+
+ .print-page {
+ width: 220mm;
+ height: 90mm;
+ padding: 10mm;
+ margin: 0 auto;
+ background: white;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ margin-bottom: 10px;
+ box-sizing: border-box;
+ }
+
+ .delivery-note {
+ width: 100%;
+ height: 100%;
+ font-family: "SimSun", serif;
+ font-size: 10px;
+ line-height: 1.2;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .header {
+ text-align: center;
+ margin-bottom: 8px;
+
+ .company-name {
+ font-size: 18px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+
+ .document-title {
+ font-size: 16px;
+ font-weight: bold;
+ }
+ }
+
+ .info-section {
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .info-row {
+ line-height: 20px;
.label {
font-weight: bold;
- width: 80px;
+ width: 60px;
font-size: 14px;
}
.value {
+ margin-right: 20px;
min-width: 80px;
font-size: 14px;
}
+ }
+ }
- &.address-item {
- .address-value {
- min-width: 200px;
+ .table-section {
+ margin-bottom: 4px;
+ flex: 1;
+
+ .product-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #000;
+
+ th,
+ td {
+ border: 1px solid #000;
+ padding: 6px;
+ text-align: center;
+ font-size: 14px;
+ line-height: 1.4;
+ }
+
+ th {
+ font-weight: bold;
+ }
+
+ .total-label {
+ text-align: right;
+ font-weight: bold;
+ }
+
+ .total-value {
+ font-weight: bold;
+ }
+ }
+ }
+
+ .footer-section {
+ .footer-row {
+ display: flex;
+ margin-bottom: 3px;
+ line-height: 20px;
+ justify-content: space-between;
+
+ .footer-item {
+ display: flex;
+ margin-right: 20px;
+
+ .label {
+ font-weight: bold;
+ width: 80px;
+ font-size: 14px;
+ }
+
+ .value {
+ min-width: 80px;
+ font-size: 14px;
+ }
+
+ &.address-item {
+ .address-value {
+ min-width: 200px;
+ }
}
}
}
}
-}
-@media print {
- .app-container {
- display: none;
- }
+ @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 {
+ 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;
+ }
}
- .print-page:last-child {
- page-break-after: avoid;
- }
-}
-</style>
+</style>
\ No newline at end of file
diff --git a/src/views/salesManagement/salesQuotation/index.vue b/src/views/salesManagement/salesQuotation/index.vue
index 2237b72..fce764f 100644
--- a/src/views/salesManagement/salesQuotation/index.vue
+++ b/src/views/salesManagement/salesQuotation/index.vue
@@ -187,9 +187,9 @@
</el-table-column>
<el-table-column prop="specification" label="瑙勬牸鍨嬪彿" width="200">
<template #default="scope">
- <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item">
+ <el-form-item :prop="`products.${scope.$index}.productModelId`" class="product-table-form-item">
<el-select
- v-model="scope.row.specificationId"
+ v-model="scope.row.productModelId"
placeholder="璇烽�夋嫨"
clearable
@change="getProductModel($event, scope.row)"
@@ -239,10 +239,10 @@
</template>
<div class="form-content">
<el-form-item label="澶囨敞" prop="remark">
- <el-input
- type="textarea"
- v-model="form.remark"
- placeholder="璇疯緭鍏ュ娉ㄤ俊鎭紙閫夊~锛�"
+ <el-input
+ type="textarea"
+ v-model="form.remark"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭紙閫夊~锛�"
:rows="4"
maxlength="500"
show-word-limit
@@ -270,7 +270,7 @@
<span style="font-size: 18px; color: #e6a23c; font-weight: bold;">楼{{ currentQuotation.totalAmount?.toFixed(2) }}</span>
</el-descriptions-item>
</el-descriptions>
-
+
<div style="margin: 20px 0;">
<h4>浜у搧鏄庣粏</h4>
<el-table :data="currentQuotation.products" border style="width: 100%">
@@ -354,7 +354,7 @@
const productRowRules = {
productId: [{ required: true, message: '璇烽�夋嫨浜у搧鍚嶇О', trigger: 'change' }],
- specificationId: [{ required: true, message: '璇烽�夋嫨瑙勬牸鍨嬪彿', trigger: 'change' }],
+ productModelId: [{ required: true, message: '璇烽�夋嫨瑙勬牸鍨嬪彿', trigger: 'change' }],
unit: [{ required: true, message: '璇峰~鍐欏崟浣�', trigger: 'blur' }],
unitPrice: [{ required: true, message: '璇峰~鍐欏崟浠�', trigger: 'change' }]
}
@@ -362,7 +362,7 @@
const r = { ...baseRules }
;(form.products || []).forEach((_, i) => {
r[`products.${i}.productId`] = productRowRules.productId
- r[`products.${i}.specificationId`] = productRowRules.specificationId
+ r[`products.${i}.productModelId`] = productRowRules.productModelId
r[`products.${i}.unit`] = productRowRules.unit
r[`products.${i}.unitPrice`] = productRowRules.unitPrice
})
@@ -433,7 +433,7 @@
if (children && children.length > 0) {
newItem.children = convertIdToValue(children);
}
-
+
return newItem;
});
}
@@ -457,7 +457,7 @@
row.productId = '';
row.product = '';
row.modelOptions = [];
- row.specificationId = '';
+ row.productModelId = '';
row.specification = '';
row.unit = '';
return;
@@ -478,13 +478,13 @@
if (!row) return;
// 濡傛灉娓呯┖閫夋嫨锛屽垯娓呯┖鐩稿叧瀛楁
if (!value) {
- row.specificationId = '';
+ row.productModelId = '';
row.specification = '';
row.unit = '';
return;
}
- // 鏇存柊 specificationId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
- row.specificationId = value;
+ // 鏇存柊 productModelId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
+ row.productModelId = value;
const modelOptions = row.modelOptions || [];
const index = modelOptions.findIndex((item) => item.id === value);
if (index !== -1) {
@@ -523,7 +523,7 @@
products: row.products ? row.products.map(product => ({
productId: product.productId || '',
product: product.product || product.productName || '',
- specificationId: product.specificationId || '',
+ productModelId: product.productModelId || '',
specification: product.specification || '',
quantity: product.quantity || 0,
unit: product.unit || '',
@@ -560,32 +560,32 @@
const resolvedProductId = product.productId
? Number(product.productId)
: findNodeIdByLabel(productOptions.value, productName) || ''
-
+
// 濡傛灉鏈変骇鍝両D锛屽姞杞藉搴旂殑瑙勬牸鍨嬪彿鍒楄〃
let modelOptions = [];
- let resolvedSpecificationId = product.specificationId || '';
-
+ let resolvedProductModelId = product.productModelId || '';
+
if (resolvedProductId) {
try {
const res = await modelList({ id: resolvedProductId });
modelOptions = res || [];
-
- // 濡傛灉杩斿洖鐨勬暟鎹病鏈� specificationId锛屼絾鏈� specification 鍚嶇О锛屾牴鎹悕绉版煡鎵� ID
- if (!resolvedSpecificationId && product.specification) {
+
+ // 濡傛灉杩斿洖鐨勬暟鎹病鏈� productModelId锛屼絾鏈� specification 鍚嶇О锛屾牴鎹悕绉版煡鎵� ID
+ if (!resolvedProductModelId && product.specification) {
const foundModel = modelOptions.find(item => item.model === product.specification);
if (foundModel) {
- resolvedSpecificationId = foundModel.id;
+ resolvedProductModelId = foundModel.id;
}
}
} catch (error) {
console.error('鍔犺浇瑙勬牸鍨嬪彿澶辫触:', error);
}
}
-
+
return {
productId: resolvedProductId,
product: productName,
- specificationId: resolvedSpecificationId,
+ productModelId: resolvedProductModelId,
specification: product.specification || '',
quantity: product.quantity || 0,
unit: product.unit || '',
@@ -649,8 +649,7 @@
productId: '',
product: '',
productName: '',
- specificationId: '',
- specification: '',
+ productModelId: '',
quantity: 1,
unit: '',
unitPrice: 0,
@@ -755,7 +754,7 @@
products: item.products ? item.products.map(product => ({
productId: product.productId || '',
product: product.product || product.productName || '',
- specificationId: product.specificationId || '',
+ productModelId: product.productModelId || '',
specification: product.specification || '',
quantity: product.quantity || 0,
unit: product.unit || '',
@@ -803,16 +802,16 @@
padding: 10px 0;
max-height: calc(100vh - 200px);
overflow-y: auto;
-
+
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
-
+
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
-
+
&:hover {
background: #a8a8a8;
}
@@ -829,17 +828,17 @@
margin-bottom: 24px;
border-radius: 8px;
transition: all 0.3s ease;
-
+
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
}
-
+
:deep(.el-card__header) {
padding: 16px 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
border-bottom: 1px solid #ebeef5;
}
-
+
:deep(.el-card__body) {
padding: 20px;
}
@@ -849,19 +848,19 @@
display: flex;
align-items: center;
gap: 8px;
-
+
.card-icon {
font-size: 18px;
color: #409eff;
}
-
+
.card-title {
font-weight: 600;
font-size: 16px;
color: #303133;
flex: 1;
}
-
+
.header-btn {
margin-left: auto;
}
@@ -885,20 +884,20 @@
.product-table {
:deep(.el-table__header) {
background-color: #f5f7fa;
-
+
th {
background-color: #f5f7fa !important;
color: #606266;
font-weight: 600;
}
}
-
+
:deep(.el-table__row) {
&:hover {
background-color: #f5f7fa;
}
}
-
+
:deep(.el-table__cell) {
padding: 12px 0;
}
diff --git a/src/views/systemArchitecture/index.vue b/src/views/systemArchitecture/index.vue
index 1eb37bb..c4cb56b 100644
--- a/src/views/systemArchitecture/index.vue
+++ b/src/views/systemArchitecture/index.vue
@@ -1,436 +1,699 @@
<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>
+ <div class="architecture-shell">
+ <header class="page-title">
+ <span class="page-title__bar"></span>
+ <h1>绯荤粺鏋舵瀯鍥�</h1>
+ </header>
- <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>
+ <section class="section-card section-card--top">
+ <article class="module-title module-title--basic">
+ <div class="module-title__icon">
+ <span class="module-title__icon-shape">
+ <svg-icon icon-class="system" />
+ </span>
+ </div>
+ <div class="module-title__text">
+ <h2>鍩虹閰嶇疆</h2>
+ <p>绯荤粺鍩虹淇℃伅绠$悊</p>
+ </div>
+ </article>
- <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" />
+ <div class="flow-row flow-row--top">
+ <template v-for="item in basicFlow" :key="item.key">
+ <div class="flow-item">
+ <article class="flow-node">
+ <div class="flow-node__icon">
+ <svg-icon :icon-class="item.icon" />
+ </div>
+ <span>{{ item.name }}</span>
+ </article>
+ </div>
+ </template>
+ </div>
+ </section>
+
+ <section class="middle-layout">
+ <div class="panel-card panel-card--sale">
+ <article class="module-title module-title--sale">
+ <div class="module-title__icon">
+ <span class="module-title__icon-shape">
+ <svg-icon icon-class="chart" />
</span>
- <h3>{{ item.name }}</h3>
- </article>
- <span v-if="index < row.items.length - 1" class="flow-arrow"></span>
+ </div>
+ <div class="module-title__text">
+ <h2>閿�鍞厤缃�</h2>
+ <p>瀹㈡埛涓庨攢鍞鐞�</p>
+ </div>
+ </article>
+
+ <div class="flow-grid flow-grid--sale">
+ <template v-for="(item, index) in saleFlow" :key="item.key">
+ <div class="grid-cell">
+ <article class="flow-node">
+ <div class="flow-node__icon" :class="{ 'flow-node__icon--accent': item.accent }">
+ <svg-icon :icon-class="item.icon" />
+ </div>
+ <span>{{ item.name }}</span>
+ </article>
+ <span
+ v-if="item.arrow === 'right'"
+ class="flow-arrow flow-arrow--right flow-arrow--grid"
+ ></span>
+ <span
+ v-if="item.arrow === 'down'"
+ class="flow-arrow flow-arrow--down flow-arrow--grid"
+ ></span>
+ </div>
+ </template>
+ </div>
+ </div>
+
+ <div class="ai-stage">
+ <div class="ai-stage__connector ai-stage__connector--top"></div>
+ <div class="ai-stage__connector ai-stage__connector--left"></div>
+ <div class="ai-stage__connector ai-stage__connector--right"></div>
+ <div class="ai-stage__connector ai-stage__connector--bottom"></div>
+
+ <div class="ai-stage__ring">
+ <div class="ai-stage__core">
+ <img :src="aiHead" alt="AI 鏍稿績寮曟搸" />
+ </div>
+ </div>
+ </div>
+
+ <div class="panel-card panel-card--purchase">
+ <article class="module-title module-title--purchase">
+ <div class="module-title__icon">
+ <span class="module-title__icon-shape">
+ <svg-icon icon-class="shopping" />
+ </span>
+ </div>
+ <div class="module-title__text">
+ <h2>閲囪喘閰嶇疆</h2>
+ <p>閲囪喘涓庝緵搴斿晢绠$悊</p>
+ </div>
+ </article>
+
+ <div class="flow-grid flow-grid--purchase">
+ <template v-for="(item, index) in purchaseFlow" :key="item.key">
+ <div class="grid-cell">
+ <article class="flow-node">
+ <div class="flow-node__icon" :class="{ 'flow-node__icon--accent': item.accent }">
+ <svg-icon :icon-class="item.icon" />
+ </div>
+ <span>{{ item.name }}</span>
+ </article>
+ <span
+ v-if="item.arrow === 'right'"
+ class="flow-arrow flow-arrow--right flow-arrow--grid"
+ ></span>
+ <span
+ v-if="item.arrow === 'down'"
+ class="flow-arrow flow-arrow--down flow-arrow--grid"
+ ></span>
+ </div>
+ </template>
</div>
</div>
</section>
- </section>
+
+ <section class="section-card section-card--bottom">
+ <article class="module-title module-title--produce">
+ <div class="module-title__icon">
+ <span class="module-title__icon-shape">
+ <svg-icon icon-class="build" />
+ </span>
+ </div>
+ <div class="module-title__text">
+ <h2>鐢熶骇閰嶇疆</h2>
+ <p>鐢熶骇杩囩▼绠$悊</p>
+ </div>
+ </article>
+
+ <div class="flow-row flow-row--wide">
+ <template v-for="(item, index) in produceFlow" :key="item.key">
+ <div class="flow-item">
+ <article class="flow-node">
+ <div class="flow-node__icon" :class="{ 'flow-node__icon--green': item.green, 'flow-node__icon--accent': item.accent }">
+ <svg-icon :icon-class="item.icon" />
+ </div>
+ <span>{{ item.name }}</span>
+ </article>
+ <span v-if="index < produceFlow.length - 1" class="flow-arrow flow-arrow--right"></span>
+ </div>
+ </template>
+ </div>
+ </section>
+
+ <section class="section-card section-card--bottom">
+ <article class="module-title module-title--store">
+ <div class="module-title__icon">
+ <span class="module-title__icon-shape">
+ <svg-icon icon-class="redis" />
+ </span>
+ </div>
+ <div class="module-title__text">
+ <h2>浠撳偍鐗╂祦</h2>
+ <p>搴撳瓨涓庡嚭鍏ュ簱绠$悊</p>
+ </div>
+ </article>
+
+ <div class="flow-row flow-row--store">
+ <template v-for="(item, index) in storeFlow" :key="item.key">
+ <div class="flow-item">
+ <article class="flow-node">
+ <div class="flow-node__icon">
+ <svg-icon :icon-class="item.icon" />
+ </div>
+ <span>{{ item.name }}</span>
+ </article>
+ <span v-if="index < storeFlow.length - 1" class="flow-arrow flow-arrow--right"></span>
+ </div>
+ </template>
+ </div>
+ </section>
+ </div>
</div>
</template>
<script setup>
-import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'
+import aiHead from '@/assets/images/head.svg'
-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 basicFlow = [
+ { key: 'basic-role', name: '瑙掕壊绠$悊', icon: 'user' },
+ { key: 'basic-user', name: '鐢ㄦ埛绠$悊', icon: 'peoples' },
+ { key: 'basic-post', name: '宀椾綅绠$悊', icon: 'tree' },
+ { key: 'basic-dept', name: '閮ㄩ棬绠$悊', icon: 'tree' },
+ { key: 'basic-product', name: '浜у搧缁存姢', icon: 'table' },
+ { key: 'basic-approval', name: '瀹℃壒绠$悊', icon: 'form' }
]
-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
+const saleFlow = [
+ { key: 'sale-customer', name: '瀹㈡埛妗f', icon: 'peoples', arrow: 'right' },
+ { key: 'sale-quote', name: '閿�鍞姤浠�', icon: 'form', arrow: 'right' },
+ { key: 'sale-ledger', name: '閿�鍞彴璐�', icon: 'monitor', arrow: 'right' },
+ { key: 'sale-delivery', name: '鍙戣揣鍙拌处', icon: 'clipboard', arrow: 'right' },
+ { key: 'sale-return', name: '閿�鍞��璐�', icon: 'nested', accent: true }
+]
-function setNodeRef(item) {
- return (el) => {
- if (item.linkSource) sourceNodeRef.value = el
- if (item.linkTarget) targetNodeRef.value = el
- }
-}
+const purchaseFlow = [
+ { key: 'purchase-supplier', name: '渚涘簲鍟嗘。妗�', icon: 'people', arrow: 'right' },
+ { key: 'purchase-ledger', name: '閲囪喘鍙拌处', icon: 'monitor', arrow: 'right' },
+ { key: 'purchase-return', name: '閲囪喘閫�璐�', icon: 'nested', accent: true },
+ { key: 'purchase-contact', name: '渚涘簲鍟嗗線鏉�', icon: 'peoples', arrow: 'right' },
+ { key: 'purchase-report', name: '閲囪喘鎶ヨ〃', icon: 'chart' }
+]
-function resetLine() {
- linkPath.value = ''
-}
+const produceFlow = [
+ { key: 'produce-process', name: '宸ュ簭', icon: 'list' },
+ { key: 'produce-bom', name: 'BOM', icon: 'monitor' },
+ { key: 'produce-route', name: '宸ヨ壓璺嚎', icon: 'tree', green: true },
+ { key: 'produce-order', name: '鐢熶骇璁㈠崟', icon: 'peoples', green: true },
+ { key: 'produce-schedule', name: '鐢熶骇鎺掍骇', icon: 'time' },
+ { key: 'produce-work', name: '鐢熶骇鎶ュ伐', icon: 'edit' },
+ { key: 'produce-ledger', name: '鎶ュ伐鍙拌处', icon: 'form' },
+ { key: 'produce-cost', name: '鐢熶骇鏍哥畻', icon: 'money', accent: true }
+]
-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()
-})
+const storeFlow = [
+ { key: 'store-in', name: '鍏ュ簱绠$悊', icon: 'download' },
+ { key: 'store-out', name: '鍑哄簱绠$悊', icon: 'guide' },
+ { key: 'store-stock', name: '搴撳瓨绠$悊', icon: 'redis-list' }
+]
</script>
<style scoped>
.architecture-page {
- min-height: calc(100vh - 84px);
- padding: 18px;
- background: #f6f7f9;
+ padding: 20px 0;
}
-.canvas {
- position: relative;
- display: grid;
- gap: 10px;
+.architecture-shell {
+ max-width: 1680px;
+ margin: 0 auto;
+ padding: 12px 12px 18px;
+ border-radius: 34px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(246, 250, 255, 0.98) 100%);
+ box-shadow:
+ 0 18px 48px rgba(117, 146, 198, 0.12),
+ inset 0 0 0 1px rgba(228, 237, 250, 0.9);
}
-.lane {
- display: grid;
- grid-template-columns: 94px 1fr;
- gap: 10px;
- align-items: stretch;
-}
-
-.lane__aside {
+.page-title {
display: flex;
align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 8px 6px;
+ gap: 12px;
+ padding: 8px 10px 18px;
}
-.lane__icon {
- font-size: 18px;
- color: #2563eb;
- flex-shrink: 0;
+.page-title__bar {
+ width: 5px;
+ height: 24px;
+ border-radius: 999px;
+ background: linear-gradient(180deg, #1f71ff 0%, #5daaff 100%);
}
-.lane__aside h2 {
+.page-title h1 {
margin: 0;
- width: 34px;
+ font-size: 24px;
+ font-weight: 700;
+ color: #1f2d3d;
+}
+
+.section-card,
+.panel-card {
+ background: rgba(255, 255, 255, 0.96);
+ border-radius: 26px;
+ box-shadow:
+ 0 12px 28px rgba(118, 147, 204, 0.08),
+ 0 4px 12px rgba(118, 147, 204, 0.05);
+}
+
+.section-card {
+ display: grid;
+ grid-template-columns: 290px minmax(0, 1fr);
+ gap: 8px;
+ align-items: center;
+ padding: 18px 24px;
+}
+
+.section-card + .section-card {
+ margin-top: 18px;
+}
+
+.middle-layout {
+ display: grid;
+ grid-template-columns: minmax(300px, 1fr) 420px minmax(300px, 1fr);
+ gap: 18px;
+ align-items: center;
+}
+
+.panel-card {
+ min-height: 284px;
+ padding: 16px 18px 18px;
+}
+
+.module-title {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.module-title__icon {
+ width: 44px;
+ height: 50px;
+ position: relative;
+ display: grid;
+ place-items: center;
+ transform: translateY(-1px);
+ filter: drop-shadow(0 10px 14px rgba(82, 125, 211, 0.28))
+ drop-shadow(0 4px 8px rgba(82, 125, 211, 0.18));
+}
+
+.module-title__icon-shape {
+ width: 100%;
+ height: 100%;
+ clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
+ display: grid;
+ place-items: center;
+ color: #fff;
+ font-size: 18px;
+}
+
+.module-title__text h2 {
+ margin: 0;
+ font-size: 17px;
+ font-weight: 800;
+ color: #1f2d3d;
+}
+
+.module-title__text p {
+ margin: 6px 0 0;
font-size: 12px;
- line-height: 1.15;
- font-weight: 600;
- color: #1f2937;
+ color: #7f8ba3;
+}
+
+.module-title--basic .module-title__icon {
+ filter: drop-shadow(0 10px 14px rgba(47, 110, 236, 0.28))
+ drop-shadow(0 4px 8px rgba(47, 110, 236, 0.18));
+}
+
+.module-title--basic .module-title__icon-shape {
+ background: linear-gradient(135deg, #2069ff 0%, #5bb0ff 100%);
+}
+
+.module-title--sale .module-title__icon {
+ filter: drop-shadow(0 10px 14px rgba(123, 66, 255, 0.24))
+ drop-shadow(0 4px 8px rgba(123, 66, 255, 0.16));
+}
+
+.module-title--sale .module-title__icon-shape {
+ background: linear-gradient(135deg, #7b42ff 0%, #a172ff 100%);
+}
+
+.module-title--purchase .module-title__icon {
+ filter: drop-shadow(0 10px 14px rgba(52, 161, 255, 0.24))
+ drop-shadow(0 4px 8px rgba(52, 161, 255, 0.16));
+}
+
+.module-title--purchase .module-title__icon-shape {
+ background: linear-gradient(135deg, #34a1ff 0%, #66c6ff 100%);
+}
+
+.module-title--produce .module-title__icon {
+ filter: drop-shadow(0 10px 14px rgba(36, 183, 118, 0.24))
+ drop-shadow(0 4px 8px rgba(36, 183, 118, 0.16));
+}
+
+.module-title--produce .module-title__icon-shape {
+ background: linear-gradient(135deg, #24b776 0%, #62d49d 100%);
+}
+
+.module-title--store .module-title__icon {
+ filter: drop-shadow(0 10px 14px rgba(119, 81, 255, 0.24))
+ drop-shadow(0 4px 8px rgba(119, 81, 255, 0.16));
+}
+
+.module-title--store .module-title__icon-shape {
+ background: linear-gradient(135deg, #7751ff 0%, #9b76ff 100%);
+}
+
+.flow-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ min-width: 0;
+}
+
+.flow-row--top {
+ padding-right: 16px;
+}
+
+.flow-row--wide {
+ gap: 2px;
+}
+
+.flow-row--store {
+ justify-content: flex-start;
+ gap: 36px;
+}
+
+.flow-item {
+ display: flex;
+ align-items: center;
+ min-width: 0;
+}
+
+.flow-node {
+ width: 84px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
text-align: center;
}
-.lane__arrow {
- position: relative;
- width: 16px;
- height: 1px;
- background: #cbd5e1;
- flex-shrink: 0;
+.flow-node__icon {
+ width: 38px;
+ height: 38px;
+ display: grid;
+ place-items: center;
+ color: #3f76f3;
+ font-size: 24px;
}
-.lane__arrow::after {
+.flow-node__icon--accent {
+ color: #ff6c4f;
+}
+
+.flow-node__icon--green {
+ color: #2bb875;
+}
+
+.flow-node span {
+ font-size: 13px;
+ line-height: 1.2;
+ font-weight: 600;
+ color: #1f2d3d;
+}
+
+.flow-arrow {
+ position: relative;
+ flex: 0 0 auto;
+ opacity: 0.68;
+}
+
+.flow-arrow--right {
+ width: 24px;
+ height: 1px;
+ margin: 0 4px;
+ background: linear-gradient(90deg, rgba(182, 205, 255, 0.9) 0%, rgba(127, 168, 255, 0.9) 100%);
+}
+
+.flow-arrow--right::after {
content: '';
position: absolute;
+ right: 0;
top: 50%;
- right: -1px;
width: 6px;
height: 6px;
- border-top: 1px solid #cbd5e1;
- border-right: 1px solid #cbd5e1;
+ border-top: 1px solid #8aacff;
+ border-right: 1px solid #8aacff;
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;
+.flow-arrow--down {
+ width: 1px;
+ height: 22px;
+ margin: 2px auto 0;
+ background: linear-gradient(180deg, rgba(182, 205, 255, 0.9) 0%, rgba(127, 168, 255, 0.9) 100%);
}
-.node-wrap {
- display: flex;
- align-items: center;
- gap: 8px;
- position: relative;
+.flow-arrow--down::after {
+ content: '';
+ position: absolute;
+ left: 50%;
+ bottom: 0;
+ width: 6px;
+ height: 6px;
+ border-right: 1px solid #8aacff;
+ border-bottom: 1px solid #8aacff;
+ transform: translateX(-50%) rotate(45deg);
}
-.node {
- width: 100px;
- min-height: 72px;
- padding: 4px 2px;
+.flow-grid {
+ display: grid;
+ margin-top: 26px;
+}
+
+.flow-grid--sale {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ row-gap: 18px;
+}
+
+.flow-grid--purchase {
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(2, auto);
+ row-gap: 34px;
+}
+
+.grid-cell {
+ position: relative;
+ min-height: 80px;
+}
+
+.flow-arrow--grid.flow-arrow--right {
+ position: absolute;
+ top: 18px;
+ right: 2px;
+ margin: 0;
+}
+
+.flow-arrow--grid.flow-arrow--down {
+ position: absolute;
+ left: 50%;
+ bottom: -2px;
+ transform: translateX(-50%);
+ margin: 0;
+}
+
+.ai-stage {
+ position: relative;
+ height: 388px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
- gap: 6px;
+ transform: translateX(-30px);
}
-.node__mark {
+.ai-stage__ring {
+ width: 300px;
+ height: 300px;
+ display: grid;
+ place-items: center;
position: relative;
- width: 34px;
- height: 28px;
- display: flex;
- align-items: flex-start;
- justify-content: center;
}
-.node__cap {
+.ai-stage__core {
+ width: 340px;
+ height: 340px;
+ display: grid;
+ place-items: center;
+}
+
+.ai-stage__core img {
+ width: 360px;
+ height: 360px;
+ object-fit: contain;
+ transform: translateY(-24px);
+ filter: drop-shadow(0 10px 24px rgba(76, 135, 255, 0.22));
+}
+
+.ai-stage__base {
+ width: 210px;
+ height: 42px;
+ margin-top: -14px;
+ border-radius: 999px;
+ background:
+ radial-gradient(circle at center, rgba(111, 223, 255, 0.8) 0%, rgba(111, 223, 255, 0.22) 34%, rgba(111, 223, 255, 0) 66%),
+ linear-gradient(180deg, #ffffff 0%, #dce9ff 100%);
+ box-shadow:
+ 0 10px 22px rgba(102, 145, 222, 0.14),
+ inset 0 -4px 10px rgba(94, 170, 255, 0.2);
+}
+
+.ai-stage__label {
+ margin-top: 18px;
+ padding: 8px 18px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.9);
+ color: #537ae5;
+ font-size: 14px;
+ font-weight: 700;
+ box-shadow: 0 10px 20px rgba(118, 147, 204, 0.08);
+}
+
+.ai-stage__connector {
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;
+ border-color: rgba(145, 181, 247, 0.5);
+ border-style: dashed;
+ border-width: 1px;
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;
+.ai-stage__connector--top {
+ top: 24px;
+ left: 50%;
+ width: 0;
+ height: 64px;
+ border-left-width: 1px;
+ transform: translateX(-50%);
+}
+
+.ai-stage__connector--left,
+.ai-stage__connector--right {
+ top: 148px;
+ width: 74px;
+ border-top-width: 1px;
+}
+
+.ai-stage__connector--left {
+ left: 18px;
+}
+
+.ai-stage__connector--right {
+ right: 18px;
+}
+
+.ai-stage__connector--bottom {
+ bottom: 32px;
+ left: 50%;
+ width: 0;
+ height: 64px;
+ border-left-width: 1px;
+ transform: translateX(-50%);
+}
+
+@media (max-width: 1440px) {
+ .middle-layout {
+ grid-template-columns: 1fr 360px 1fr;
+ }
+
+ .ai-stage__ring {
+ width: 260px;
+ height: 260px;
+ }
+
+ .ai-stage__core img {
+ width: 272px;
+ height: 272px;
+ }
}
@media (max-width: 1180px) {
- .lane {
+ .section-card {
grid-template-columns: 1fr;
- gap: 6px;
+ gap: 18px;
}
- .lane__aside {
- justify-content: flex-start;
+ .middle-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .ai-stage {
+ order: 2;
+ height: 360px;
+ }
+
+ .panel-card--sale {
+ order: 1;
+ }
+
+ .panel-card--purchase {
+ order: 3;
}
}
-@media (max-width: 768px) {
+@media (max-width: 860px) {
.architecture-page {
padding: 12px;
}
- .lane__flow {
- display: grid;
- gap: 8px;
- padding: 12px;
+ .architecture-shell {
+ padding: 10px;
+ border-radius: 24px;
}
- .node-wrap {
- display: grid;
- gap: 8px;
+ .page-title h1 {
+ font-size: 22px;
}
- .node {
- width: 100%;
- min-height: 52px;
- flex-direction: row;
+ .section-card,
+ .panel-card {
+ padding: 16px;
+ border-radius: 20px;
+ }
+
+ .module-title__icon {
+ width: 40px;
+ height: 46px;
+ font-size: 16px;
+ }
+
+ .module-title__text h2 {
+ font-size: 16px;
+ }
+
+ .flow-row {
justify-content: flex-start;
- gap: 10px;
+ flex-wrap: wrap;
}
- .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);
+ .flow-grid--sale,
+ .flow-grid--purchase {
+ grid-template-columns: repeat(2, 1fr);
}
}
</style>
--
Gitblit v1.9.3