From db2e215ffe92af3a6558344b2150cadbab7cfe2c Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 09 五月 2026 17:54:46 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' into dev_浪潮
---
src/views/productionManagement/productionProcess/index.vue | 9
src/assets/AI/老板助手.png | 0
src/assets/AI/待办助手.png | 0
src/layout/index.vue | 8
src/views/financialManagement/generalLedger/index.vue | 527 +-
multiple/assets/logo/KSLogo.png | 0
src/api/salesManagement/deliveryLedger.js | 9
src/views/customerService/expiryAfterSales/components/formDia.vue | 92
src/views/productionManagement/workOrderManagement/index.vue | 35
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 8
src/views/productionManagement/productionTraceability/index.vue | 16
src/assets/AI/采购助手.png | 0
src/views/inventoryManagement/receiptManagement/Record.vue | 13
src/api/financialManagement/accountSubject.js | 46
src/views/salesManagement/deliveryLedger/index.vue | 6
src/views/equipmentManagement/operationManagement/index.vue | 132
src/assets/AI/生产助手.png | 0
src/assets/aiIndustrialBrain/reference-chat.png | 0
src/views/aiIndustrialBrain/index.vue | 1499 ++++++++
src/assets/AI/财务助手.png | 0
src/router/index.js | 239
src/views/productionManagement/workOrderEdit/index.vue | 2
multiple/assets/favicon/DZZBfavicon.ico | 0
src/views/productionManagement/productionReporting/index.vue | 5
src/components/AIChatSidebar/assistants/generalAssistant.js | 33
multiple/assets/favicon/KSfavicon.ico | 0
src/views/productionManagement/productionOrder/index.vue | 2
src/components/PurchaseAIChatSidebar/index.vue | 20
src/views/inventoryManagement/stockReport/index.vue | 1251 +++---
src/views/customerService/feedbackRegistration/components/formDia.vue | 895 ++--
multiple/config.json | 20
src/components/AIChatSidebar/assistants/purchaseAssistant.js | 25
src/views/productionManagement/processRoute/processRouteItem/index.vue | 28
src/components/AIChatSidebar/assistants/index.js | 6
src/views/salesManagement/salesLedger/index.vue | 3632 +++++++++---------
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue | 852 ++-
src/views/inventoryManagement/dispatchLog/Record.vue | 13
multiple/assets/logo/DZZBLogo.png | 0
src/views/procurementManagement/purchaseReturnOrder/index.vue | 12
src/assets/AI/销售助手.png | 0
src/assets/aiIndustrialBrain/reference-cards.png | 0
src/views/productionManagement/productionCosting/index.vue | 5
src/store/modules/permission.js | 35
src/components/AIChatSidebar/index.vue | 1362 +++++--
src/views/personnelManagement/dimission/components/formDia.vue | 17
src/views/customerService/expiryAfterSales/index.vue | 6
src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue | 192 +
package.json | 3
src/views/productionPlan/productionPlan/index.vue | 22
49 files changed, 7,007 insertions(+), 4,070 deletions(-)
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/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/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/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/config.json b/multiple/config.json
index 565ad80..a2586e5 100644
--- a/multiple/config.json
+++ b/multiple/config.json
@@ -24,6 +24,24 @@
"logo": "logo/SDJCLogo.png",
"favicon": "favicon/SDJCfavicon.ico"
},
+ "KS": {
+ "env": {
+ "VITE_APP_TITLE": "榛庡煄搴锋.鍟嗙牸鏈夐檺鍏徃",
+ "VITE_BASE_API": "http://36.138.236.176:9000",
+ "VITE_JAVA_API": "http://36.138.236.176:9001"
+ },
+ "logo": "logo/KSLogo.png",
+ "favicon": "favicon/KSfavicon.ico"
+ },
+ "DZZB": {
+ "env": {
+ "VITE_APP_TITLE": "灞辫タ涓规湵瑁呭鍒堕�犺偂浠芥湁闄愬叕鍙�",
+ "VITE_BASE_API": "http://36.138.236.176:9000",
+ "VITE_JAVA_API": "http://36.138.236.176:9001"
+ },
+ "logo": "logo/DZZBLogo.png",
+ "favicon": "favicon/DZZBfavicon.ico"
+ },
"logo": "/src/assets/logo/logo.png",
"favicon": "/public/favicon.ico"
-}
\ No newline at end of file
+}
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/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/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/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..d8e5e80
--- /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..2d5d3b5
--- /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\200\201\346\235\277\345\212\251\346\211\213.png" "b/src/assets/AI/\350\200\201\346\235\277\345\212\251\346\211\213.png"
new file mode 100644
index 0000000..1b5030d
--- /dev/null
+++ "b/src/assets/AI/\350\200\201\346\235\277\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..fd6c90c
--- /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/\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..3eba34a
--- /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..8382efa
--- /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/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..61d4752
--- /dev/null
+++ b/src/components/AIChatSidebar/assistants/index.js
@@ -0,0 +1,6 @@
+import { generalAssistant } from './generalAssistant'
+import { purchaseAssistant } from './purchaseAssistant'
+
+export { generalAssistant, purchaseAssistant }
+
+export const builtInAssistants = [generalAssistant, purchaseAssistant]
diff --git a/src/components/AIChatSidebar/assistants/purchaseAssistant.js b/src/components/AIChatSidebar/assistants/purchaseAssistant.js
new file mode 100644
index 0000000..8f0ae0a
--- /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/index.vue b/src/components/AIChatSidebar/index.vue
index 369d75d..5d81355 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -1,7 +1,7 @@
<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>
@@ -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">
@@ -443,9 +474,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 +522,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 { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, Promotion, RefreshRight } from '@element-plus/icons-vue'
import { ElMessage } 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 +542,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 +594,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 +610,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({})
@@ -792,6 +826,142 @@
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 +1005,7 @@
const selectSession = async (session) => {
showHistory.value = false
+ clearSelectedFiles()
uuid.value = session.memoryId
localStorage.setItem(currentAssistant.value.storageKey, uuid.value)
@@ -842,6 +1013,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 +1025,7 @@
const messageObj = {
isUser,
- content: msg.content,
+ content: msg.content || '',
htmlContent: '',
isTyping: false,
chartOptions: null,
@@ -861,7 +1033,8 @@
type: '',
tableData: null,
payloadTreeData: null,
- payloadHiddenData: null
+ payloadHiddenData: null,
+ localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
}
messages.value.push(messageObj)
@@ -876,15 +1049,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 +1092,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 +1110,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 +1150,12 @@
}
const handleClose = () => {
+ if (hideTrigger.value) return
visible.value = false
}
const handleManualClose = () => {
+ if (hideTrigger.value) return
if (isSending.value) {
abortCurrentRequest()
}
@@ -980,22 +1171,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 = () => {
@@ -1667,6 +1853,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)
@@ -1746,10 +2002,191 @@
}, {})
}
+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)
}
@@ -1954,16 +2391,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 +2417,8 @@
isUser: true,
content: userMsg,
htmlContent: convertTextToHtml(userMsg),
- isTyping: false
+ isTyping: false,
+ localUploadFiles: Array.isArray(localFileSnapshots) ? localFileSnapshots : []
})
const botMsgIndex = messages.value.length
@@ -2075,9 +2517,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)
}
@@ -2659,8 +3101,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 +3114,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 +3246,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 +3337,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;
}
@@ -3124,6 +3576,87 @@
background: rgba(0, 85, 212, 0.25);
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;
}
}
@@ -3537,6 +4070,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 +4099,12 @@
overflow: hidden;
text-overflow: ellipsis;
font-weight: 600;
+ }
+
+ .file-size {
+ font-size: 11px;
+ color: #5f86b4;
+ line-height: 1.1;
}
.remove-file {
@@ -3703,75 +4264,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 +4330,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 +4517,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 +4576,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 +4663,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 +4673,8 @@
}
.welcome-title {
- font-size: 17px;
- line-height: 1.3;
+ font-size: 16px;
+ line-height: 1.25;
br {
display: none;
@@ -4161,65 +4682,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 +4792,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 +4814,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 +4909,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/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/layout/index.vue b/src/layout/index.vue
index 03c13ba..fc47afa 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -16,12 +16,13 @@
<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";
@@ -33,6 +34,7 @@
const settingsStore = useSettingsStore();
const userStore = useUserStore();
+ const route = useRoute();
const theme = computed(() => settingsStore.theme);
const sideTheme = computed(() => settingsStore.sideTheme);
const sidebar = computed(() => useAppStore().sidebar);
@@ -40,6 +42,10 @@
const needTagsView = computed(() => settingsStore.tagsView);
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,
diff --git a/src/router/index.js b/src/router/index.js
index 2bb5586..2c98627 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -86,6 +86,18 @@
],
},
{
+ 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,
hidden: true,
@@ -134,118 +146,121 @@
],
},
// 璐㈠姟绠$悊妯″潡璺敱
- // {
- // path: "/financial",
- // component: Layout,
- // hidden: false,
- // redirect: "/financial/general-ledger",
- // alwaysShow: true,
- // meta: { title: "璐㈠姟绠$悊", icon: "money" },
- // children: [
- // {
- // path: "general-ledger",
- // component: () => import("@/views/financialManagement/generalLedger/index.vue"),
- // name: "GeneralLedger",
- // meta: { title: "鎬诲笎绉戠洰" },
- // },
- // {
- // path: "sales-out",
- // component: () => import("@/views/financialManagement/receivable/salesOut.vue"),
- // name: "SalesOut",
- // meta: { title: "閿�鍞嚭搴�" },
- // },
- // {
- // path: "sales-return",
- // component: () => import("@/views/financialManagement/receivable/salesReturn.vue"),
- // name: "SalesReturn",
- // meta: { title: "閿�鍞��璐�" },
- // },
- // {
- // path: "receivable-reconciliation",
- // component: () => import("@/views/financialManagement/receivable/reconciliation.vue"),
- // name: "ReceivableReconciliation",
- // meta: { title: "搴旀敹瀵硅处" },
- // },
- // {
- // path: "invoice-apply",
- // component: () => import("@/views/financialManagement/receivable/invoiceApply.vue"),
- // name: "InvoiceApply",
- // meta: { title: "寮�绁ㄧ敵璇�" },
- // },
- // {
- // path: "output-invoice",
- // component: () => import("@/views/financialManagement/receivable/outputInvoice.vue"),
- // name: "OutputInvoice",
- // meta: { title: "閿�椤瑰彂绁�" },
- // },
- // {
- // path: "receipt",
- // component: () => import("@/views/financialManagement/receivable/receipt.vue"),
- // name: "Receipt",
- // meta: { title: "鏀舵鍗�" },
- // },
- // {
- // path: "purchase-in",
- // component: () => import("@/views/financialManagement/payable/purchaseIn.vue"),
- // name: "PurchaseIn",
- // meta: { title: "閲囪喘鍏ュ簱" },
- // },
- // {
- // path: "payable-reconciliation",
- // component: () => import("@/views/financialManagement/payable/reconciliation.vue"),
- // name: "PayableReconciliation",
- // meta: { title: "搴斾粯瀵硅处" },
- // },
- // {
- // path: "input-invoice",
- // component: () => import("@/views/financialManagement/payable/input-invoice.vue"),
- // name: "InputInvoice",
- // meta: { title: "杩涢」鍙戠エ" },
- // },
- // {
- // path: "payment-apply",
- // component: () => import("@/views/financialManagement/payable/paymentApply.vue"),
- // name: "PaymentApply",
- // meta: { title: "浠樻鐢宠" },
- // },
- // {
- // path: "payment",
- // component: () => import("@/views/financialManagement/payable/payment.vue"),
- // name: "Payment",
- // meta: { title: "浠樻鍗�" },
- // },
- // {
- // path: "fixed-assets",
- // component: () => import("@/views/financialManagement/assets/fixedAssets.vue"),
- // name: "FixedAssets",
- // meta: { title: "鍥哄畾璧勪骇" },
- // },
- // {
- // path: "intangible-assets",
- // component: () => import("@/views/financialManagement/assets/intangibleAssets.vue"),
- // name: "IntangibleAssets",
- // meta: { title: "鏃犲舰璧勪骇" },
- // },
- // {
- // path: "voucher",
- // component: () => import("@/views/financialManagement/voucher/index.vue"),
- // name: "Voucher",
- // meta: { title: "鍑瘉" },
- // },
- // {
- // path: "voucher-general-ledger",
- // component: () => import("@/views/financialManagement/voucher/generalLedger.vue"),
- // name: "VoucherGeneralLedger",
- // meta: { title: "绉戠洰鎬诲笎" },
- // },
- // {
- // path: "voucher-detail-ledger",
- // component: () => import("@/views/financialManagement/voucher/detailLedger.vue"),
- // name: "VoucherDetailLedger",
- // meta: { title: "绉戠洰鏄庣粏甯�" },
- // },
- // ],
- // },
+ {
+ path: "/financial",
+ component: Layout,
+ hidden: false,
+ redirect: "/financial/general-ledger",
+ alwaysShow: true,
+ meta: { title: "璐㈠姟绠$悊", icon: "money" },
+ children: [
+ {
+ path: "sales-out",
+ component: () => import("@/views/financialManagement/receivable/salesOut.vue"),
+ name: "SalesOut",
+ meta: { title: "閿�鍞嚭搴�" },
+ },
+ {
+ path: "sales-return",
+ component: () => import("@/views/financialManagement/receivable/salesReturn.vue"),
+ name: "SalesReturn",
+ meta: { title: "閿�鍞��璐�" },
+ },
+
+ {
+ path: "invoice-apply",
+ component: () => import("@/views/financialManagement/receivable/invoiceApply.vue"),
+ name: "InvoiceApply",
+ meta: { title: "寮�绁ㄧ敵璇�" },
+ },
+ {
+ path: "output-invoice",
+ component: () => import("@/views/financialManagement/receivable/outputInvoice.vue"),
+ name: "OutputInvoice",
+ meta: { title: "閿�椤瑰彂绁�" },
+ },
+ {
+ path: "receipt",
+ component: () => import("@/views/financialManagement/receivable/receipt.vue"),
+ name: "Receipt",
+ meta: { title: "鏀舵鍗�" },
+ },
+ {
+ path: "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: "input-invoice",
+ component: () => import("@/views/financialManagement/payable/input-invoice.vue"),
+ name: "InputInvoice",
+ meta: { title: "杩涢」鍙戠エ" },
+ },
+ {
+ path: "payment-apply",
+ component: () => import("@/views/financialManagement/payable/paymentApply.vue"),
+ name: "PaymentApply",
+ meta: { title: "浠樻鐢宠" },
+ },
+
+ {
+ path: "payment",
+ component: () => import("@/views/financialManagement/payable/payment.vue"),
+ name: "Payment",
+ meta: { title: "浠樻鍗�" },
+ },
+ {
+ path: "payable-reconciliation",
+ component: () => import("@/views/financialManagement/payable/reconciliation.vue"),
+ name: "PayableReconciliation",
+ meta: { title: "搴斾粯瀵硅处" },
+ },
+ {
+ path: "fixed-assets",
+ component: () => import("@/views/financialManagement/assets/fixedAssets.vue"),
+ name: "FixedAssets",
+ meta: { title: "鍥哄畾璧勪骇" },
+ },
+ {
+ path: "intangible-assets",
+ component: () => import("@/views/financialManagement/assets/intangibleAssets.vue"),
+ name: "IntangibleAssets",
+ meta: { title: "鏃犲舰璧勪骇" },
+ },
+ {
+ path: "general-ledger",
+ component: () => import("@/views/financialManagement/generalLedger/index.vue"),
+ name: "GeneralLedger",
+ meta: { title: "鎬诲笎绉戠洰" },
+ },
+ {
+ path: "voucher",
+ component: () => import("@/views/financialManagement/voucher/index.vue"),
+ name: "Voucher",
+ meta: { title: "鍑瘉" },
+ },
+ {
+ path: "voucher-general-ledger",
+ component: () => import("@/views/financialManagement/voucher/generalLedger.vue"),
+ name: "VoucherGeneralLedger",
+ meta: { title: "绉戠洰鎬诲笎" },
+ },
+ {
+ path: "voucher-detail-ledger",
+ component: () => import("@/views/financialManagement/voucher/detailLedger.vue"),
+ name: "VoucherDetailLedger",
+ meta: { title: "绉戠洰鏄庣粏甯�" },
+ },
+ ],
+ },
];
// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇
diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js
index 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/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue b/src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue
new file mode 100644
index 0000000..2f55511
--- /dev/null
+++ b/src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue
@@ -0,0 +1,192 @@
+<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="assistantMode === 'purchase' ? [purchaseAssistant] : [generalAssistant]"
+ :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 { generalAssistant, purchaseAssistant } 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鍔╂墜"));
+const assistantMode = computed(() => {
+ if (agentKey.value === "purchase") return "purchase";
+ if (agentKey.value === "general") return "general";
+ return "pending";
+});
+</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..33b967d
--- /dev/null
+++ b/src/views/aiIndustrialBrain/index.vue
@@ -0,0 +1,1499 @@
+<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();
+
+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/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/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 e5905ee..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.data.records){
- customerNameOptions.value = res.data.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/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/financialManagement/generalLedger/index.vue b/src/views/financialManagement/generalLedger/index.vue
index 0570627..fbe2210 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,85 @@
<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="{
+ <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>
+ @pagination="changePage">
</PIMTable>
</div>
-
- <FormDialog :title="dialogTitle" v-model="dialogVisible" width="600px" @confirm="submitForm" @cancel="dialogVisible = false">
- <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
- <el-form-item label="绉戠洰缂栫爜" prop="subjectCode">
- <el-input v-model="form.subjectCode" placeholder="璇疯緭鍏ョ鐩紪鐮�" />
+ <FormDialog :title="dialogTitle"
+ v-model="dialogVisible"
+ width="600px"
+ @confirm="submitForm"
+ @cancel="dialogVisible = false">
+ <el-form :model="form"
+ :rules="rules"
+ ref="formRef"
+ label-width="100px">
+ <el-form-item label="绉戠洰缂栫爜"
+ prop="subjectCode">
+ <el-input v-model="form.subjectCode"
+ placeholder="璇疯緭鍏ョ鐩紪鐮�" />
</el-form-item>
- <el-form-item label="绉戠洰鍚嶇О" prop="subjectName">
- <el-input v-model="form.subjectName" placeholder="璇疯緭鍏ョ鐩悕绉�" />
+ <el-form-item label="绉戠洰鍚嶇О"
+ prop="subjectName">
+ <el-input v-model="form.subjectName"
+ placeholder="璇疯緭鍏ョ鐩悕绉�" />
</el-form-item>
- <el-form-item label="绉戠洰绫诲瀷" prop="subjectType">
- <el-select v-model="form.subjectType" placeholder="璇烽�夋嫨绉戠洰绫诲瀷" style="width: 100%;">
- <el-option label="璧勪骇绫�" value="asset" />
- <el-option label="璐熷�虹被" value="liability" />
- <el-option label="鏉冪泭绫�" value="equity" />
- <el-option label="鎴愭湰绫�" value="cost" />
- <el-option label="鎹熺泭绫�" value="profit_loss" />
+ <el-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 +127,238 @@
</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, 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: "150",
+ operation: [
+ {
+ 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 formRef = ref(null);
+ const isEdit = ref(false);
+
+ const form = reactive({
+ id: undefined,
+ 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 };
+ const getSubjectTypeType = type => {
+ const map = {
+ 璧勪骇绫�: "success",
+ 璐熷�虹被: "danger",
+ 鏉冪泭绫�: "warning",
+ 鎴愭湰绫�: "info",
+ 鎹熺泭绫�: "primary",
+ };
+ return map[type] || "";
+ };
+
+ const getTableData = () => {
+ const query = {
+ pageNum: pagination.currentPage,
+ pageSize: pagination.pageSize,
+ ...filters,
+ };
+ listAccountSubject(query).then(response => {
+ dataList.value = response.data.records;
+ pagination.total = response.data.total;
+ });
+ };
+
+ 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 add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板绉戠洰";
+ Object.assign(form, {
+ id: undefined,
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+ balanceDirection: "鍊熸柟",
+ status: 0,
+ remark: "",
+ });
+ dialogVisible.value = true;
+ };
+
+ const edit = row => {
+ isEdit.value = true;
+ dialogTitle.value = "缂栬緫绉戠洰";
+ Object.assign(form, row);
+ 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();
+ });
}
- ElMessage.success("缂栬緫鎴愬姛");
- } else {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- mockData.push({ id: newId, ...form });
- ElMessage.success("鏂板鎴愬姛");
}
- dialogVisible.value = false;
- getTableData();
- }
- });
-};
+ });
+ };
-const handleDelete = (row) => {
- ElMessageBox.confirm("纭鍒犻櫎璇ョ鐩悧锛�", "鎻愮ず", {
- confirmButtonText: "纭畾",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData.splice(index, 1);
- }
- ElMessage.success("鍒犻櫎鎴愬姛");
+ 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;
+ }
</style>
diff --git a/src/views/inventoryManagement/dispatchLog/Record.vue b/src/views/inventoryManagement/dispatchLog/Record.vue
index 6060cf5..f41eaaa 100644
--- a/src/views/inventoryManagement/dispatchLog/Record.vue
+++ b/src/views/inventoryManagement/dispatchLog/Record.vue
@@ -96,7 +96,9 @@
</el-table-column>
<el-table-column label="瀹℃壒鐘舵��" 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>
@@ -179,7 +181,7 @@
};
const getList = () => {
tableLoading.value = true;
- getStockOutPage({ ...searchForm.value, ...page, type: props.type, topParentProductId: props.topParentProductId })
+ getStockOutPage({ ...searchForm.value, ...page, topParentProductId: props.topParentProductId })
.then((res) => {
tableLoading.value = false;
tableData.value = res.data.records;
@@ -216,6 +218,13 @@
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') {
diff --git a/src/views/inventoryManagement/receiptManagement/Record.vue b/src/views/inventoryManagement/receiptManagement/Record.vue
index 7ca8fea..c9cb6ee 100644
--- a/src/views/inventoryManagement/receiptManagement/Record.vue
+++ b/src/views/inventoryManagement/receiptManagement/Record.vue
@@ -91,7 +91,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>
@@ -187,6 +189,13 @@
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 pageProductChange = obj => {
page.current = obj.page;
page.size = obj.limit;
@@ -195,7 +204,7 @@
const getList = () => {
tableLoading.value = true;
- const params = {...page, type: props.type, topParentProductId: props.topParentProductId};
+ const params = {...page, topParentProductId: props.topParentProductId};
params.timeStr = searchForm.value.timeStr;
params.productName = searchForm.value.productName;
params.recordType = searchForm.value.recordType;
diff --git a/src/views/inventoryManagement/stockReport/index.vue b/src/views/inventoryManagement/stockReport/index.vue
index 89773cf..8139508 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/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/procurementManagement/purchaseReturnOrder/index.vue b/src/views/procurementManagement/purchaseReturnOrder/index.vue
index cd2c7f4..942b4dc 100644
--- a/src/views/procurementManagement/purchaseReturnOrder/index.vue
+++ b/src/views/procurementManagement/purchaseReturnOrder/index.vue
@@ -88,10 +88,10 @@
<el-table-column label="鍚◣鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="130">
<template #default="scope">{{ formatAmount(scope.row.taxInclusiveUnitPrice) }}</template>
</el-table-column>
- <el-table-column label="鍚◣鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="130">
+ <el-table-column label="閫�璐ф�讳环(鍏�)" prop="taxInclusiveTotalPrice" width="130">
<template #default="scope">{{ formatAmount(scope.row.taxInclusiveTotalPrice) }}</template>
</el-table-column>
- <el-table-column label="涓嶅惈绋庢�讳环(鍏�)" prop="taxExclusiveTotalPrice" width="140">
+ <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">
@@ -182,7 +182,7 @@
prop: 'returnUserName',
width: 110,
},
-
+
{
label: '鏁村崟鎶樻墸棰�',
prop: 'totalDiscountAmount',
@@ -236,7 +236,7 @@
},
],
},
-
+
])
const data = reactive({
searchForm: {
@@ -281,8 +281,8 @@
const payload = res?.data || {}
detailData.value = payload
// 鎷兼帴杩炰釜瀵硅薄鎴愪竴涓璞★紝鏂逛究灞曠ず item 鍜� item.salesLedgerProduct 閲岀殑瀛楁
-
-
+
+
detailProducts.value =
payload.purchaseReturnOrderProductsDetailVoList.map(item => ({ ...item, ...item.salesLedgerProduct })) ||
[]
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 6d64c30..99d4ab1 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -111,6 +111,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 +188,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
@@ -424,6 +435,13 @@
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">
<el-switch v-model="form.isQuality"
@@ -558,6 +576,7 @@
model: "",
unit: "",
isQuality: false,
+ type: 0,
isProduction: false,
});
@@ -687,6 +706,7 @@
model: row.model || "",
unit: row.unit || "",
isQuality: row.isQuality,
+ type: row.type || 0,
isProduction: row.isProduction,
};
dialogVisible.value = true;
@@ -758,6 +778,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 +787,7 @@
technologyOperationId: form.value.technologyOperationId,
productModelId: form.value.productModelId,
isQuality: form.value.isQuality,
+ type: form.value.type,
isProduction: form.value.isProduction,
dragSort,
});
@@ -793,6 +815,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 +824,7 @@
productModelId: form.value.productModelId,
id: form.value.id,
isQuality: form.value.isQuality,
+ type: form.value.type,
isProduction: form.value.isProduction,
});
@@ -832,6 +856,7 @@
model: "",
unit: "",
isQuality: false,
+ type: 0,
isProduction: false,
};
formRef.value?.resetFields();
@@ -1068,6 +1093,7 @@
processOptions.value.forEach(item => {
if (item.id == value) {
form.value.isQuality = item.isQuality;
+ form.value.type = item.type || 0;
form.value.isProduction = item.isProduction;
}
});
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
index 2dc63bd..5d42a7d 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -130,6 +130,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..d058896 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -312,12 +312,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 e761523..96185b2 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -617,7 +617,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) {
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
index 216e73b..6d007a4 100644
--- a/src/views/productionManagement/productionProcess/index.vue
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -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>
@@ -251,7 +252,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>
@@ -275,9 +278,9 @@
</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>
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index cf42a0a..ee1f0df 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 a494cf5..671a204 100644
--- a/src/views/productionManagement/productionTraceability/index.vue
+++ b/src/views/productionManagement/productionTraceability/index.vue
@@ -80,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="瀹屾垚杩涘害"
@@ -154,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">
@@ -387,6 +402,7 @@
workOrder: row.workOrder || {},
reports: (row.reportList || []).map(r => ({
...r.reportMain,
+ ...(r.reportOutputList[0] || {}),
productionOperationParamList: r.reportParamList || [],
})),
};
diff --git a/src/views/productionManagement/workOrderEdit/index.vue b/src/views/productionManagement/workOrderEdit/index.vue
index d0b77bd..60d6f9c 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/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 6a1b24f..3c99447 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";
@@ -423,6 +435,7 @@
productMainId: null,
productionOrderRoutingOperationId: "",
productionOrderId: "",
+ workHour: 0,
paramGroups: {},
});
@@ -649,7 +662,21 @@
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;
reportForm.quantity =
@@ -663,6 +690,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) {
@@ -762,6 +794,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 78dca1f..6bcc7b2 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"
@@ -146,7 +154,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>
@@ -239,7 +248,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>
@@ -269,6 +279,7 @@
productionPlanUpdate,
productionPlanDelete,
productionPlanCombine,
+ exportProductionPlan,
} from "@/api/productionPlan/productionPlan.js";
import { productTreeList, modelListPage } from "@/api/basicData/product.js";
import { workshopPage } from "@/api/basicData/workshop.js";
@@ -281,11 +292,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 => {
@@ -650,6 +656,7 @@
const data = reactive({
searchForm: {
mpsNo: "",
+ salesContractNo: "",
productName: "",
model: "",
status: "",
@@ -678,6 +685,7 @@
}
Object.assign(searchForm.value, {
mpsNo: "",
+ salesContractNo: "",
productName: "",
model: "",
status: "",
diff --git a/src/views/salesManagement/deliveryLedger/index.vue b/src/views/salesManagement/deliveryLedger/index.vue
index d7f47dd..f93731b 100644
--- a/src/views/salesManagement/deliveryLedger/index.vue
+++ b/src/views/salesManagement/deliveryLedger/index.vue
@@ -56,7 +56,7 @@
link
type="primary"
:disabled="!isApproved(scope.row.status)"
- @click="openForm('edit', scope.row)">琛ュ厖鍙戣揣淇℃伅
+ @click="openForm('edit', scope.row)">鍙戣揣
</el-button>
<el-button
link
@@ -319,9 +319,9 @@
// 鎵撳紑寮规
const openForm = async (type, row) => {
- // 琛ュ厖鍙戣揣淇℃伅锛氫粎鈥滃鏍搁�氳繃鈥濆厑璁哥紪杈�
+ // 鍙戣揣锛氫粎鈥滃鏍搁�氳繃鈥濆厑璁哥紪杈�
if (type === 'edit' && row && !isApproved(row.status)) {
- proxy.$modal.msgWarning("鍙湁瀹℃牳閫氳繃鐨勬暟鎹墠鍙互琛ュ厖鍙戣揣淇℃伅");
+ proxy.$modal.msgWarning("鍙湁瀹℃牳閫氳繃鐨勬暟鎹墠鍙互鍙戣揣");
return;
}
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 5340d99..8d25f53 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' ? '鏂板閿�鍞彴璐﹂〉闈�' : '缂栬緫閿�鍞彴璐﹂〉闈�'"
@@ -289,7 +287,7 @@
<el-input v-model="form.salesContractNo"
placeholder="鑷姩鐢熸垚"
clearable
- disabled/>
+ disabled />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -303,7 +301,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 +332,7 @@
<el-input v-model="form.projectName"
placeholder="璇疯緭鍏�"
clearable
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
</el-row>
@@ -349,7 +347,7 @@
type="date"
placeholder="璇烽�夋嫨"
clearable
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -357,7 +355,7 @@
<el-input v-model="form.paymentMethod"
placeholder="璇疯緭鍏�"
clearable
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
</el-row>
@@ -375,7 +373,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 +386,7 @@
format="YYYY-MM-DD"
type="date"
placeholder="璇烽�夋嫨"
- clearable/>
+ clearable />
</el-form-item>
</el-col>
</el-row>
@@ -402,7 +400,7 @@
format="YYYY-MM-DD"
type="date"
placeholder="璇烽�夋嫨"
- clearable/>
+ clearable />
</el-form-item>
</el-col>
</el-row>
@@ -429,30 +427,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 +484,7 @@
clearable
type="textarea"
:rows="2"
- :disabled="operationType === 'view'"/>
+ :disabled="operationType === 'view'" />
</el-form-item>
</el-col>
</el-row>
@@ -494,7 +492,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 +508,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 +527,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 +572,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 +601,7 @@
@change="getModels"
:data="productOptions"
:render-after-expand="false"
- style="width: 100%"/>
+ style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
@@ -619,7 +617,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 +628,7 @@
prop="unit">
<el-input v-model="productForm.unit"
placeholder="璇疯緭鍏�"
- clearable/>
+ clearable />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -640,12 +638,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 +657,7 @@
:precision="2"
placeholder="璇疯緭鍏�"
clearable
- @change="calculateFromUnitPrice"/>
+ @change="calculateFromUnitPrice" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -674,7 +670,7 @@
clearable
:precision="2"
@change="calculateFromQuantity"
- style="width: 100%"/>
+ style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
@@ -685,7 +681,7 @@
<el-input v-model="productForm.taxInclusiveTotalPrice"
placeholder="璇疯緭鍏�"
clearable
- @change="calculateFromTotalPrice"/>
+ @change="calculateFromTotalPrice" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -694,7 +690,7 @@
<el-input v-model="productForm.taxExclusiveTotalPrice"
placeholder="璇疯緭鍏�"
clearable
- @change="calculateFromExclusiveTotalPrice"/>
+ @change="calculateFromExclusiveTotalPrice" />
</el-form-item>
</el-col>
</el-row>
@@ -706,9 +702,9 @@
placeholder="璇烽�夋嫨"
clearable>
<el-option label="澧炴櫘绁�"
- value="澧炴櫘绁�"/>
+ value="澧炴櫘绁�" />
<el-option label="澧炰笓绁�"
- value="澧炰笓绁�"/>
+ value="澧炰笓绁�" />
</el-select>
</el-form-item>
</el-col>
@@ -717,9 +713,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 +758,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 +814,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>
@@ -902,10 +900,16 @@
placeholder="璇烽�夋嫨鍙戣揣绫诲瀷"
style="width: 100%">
<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>
@@ -920,8 +924,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 +943,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 +964,330 @@
</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 { 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 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: "",
+ 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;
- }
- 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);
+ const seenIds = new Set();
+ return rawList
+ .filter(item => {
+ if (!item?.id || !item?.batchNo || seenIds.has(item.id)) {
+ return false;
+ }
+ seenIds.add(item.id);
+ return true;
+ })
+ .map(item => ({
+ ...item,
+ deliveryQuantity: 0,
+ }));
+ };
+ const deliveryFormData = reactive({
+ deliveryForm: {
+ type: "璐ц溅", // 璐ц溅, 蹇��
+ },
+ deliveryRules: {
+ type: [{ required: true, message: "璇烽�夋嫨鍙戣揣绫诲瀷", trigger: "change" }],
+ },
+ });
+ 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 +1300,624 @@
.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 }).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 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,
+ };
+ });
- 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;
+ 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 +1927,996 @@
.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",
+ };
+ 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 === "瀹℃牳鎷掔粷";
+ };
+
+ // 鎵撳紑闄勪欢寮圭獥
+ 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 = {
+ type: "璐ц溅",
+ batchNo: [],
+ batchNoList,
+ };
+ 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,
+ 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 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;
+ });
+ 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>
--
Gitblit v1.9.3