From 4a407279f0c9757f0714eaf385fdd5cd68c038c2 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期四, 21 五月 2026 16:55:43 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro
---
src/views/index.vue | 54 +
src/views/productionManagement/processRoute/index.vue | 4
src/store/modules/user.js | 133 ++--
src/components/AIChatSidebar/assistants/purchaseAssistant.js | 2
src/api/viewIndex.js | 21
src/views/productionManagement/processRoute/processRouteItem/index.vue | 131 ++++
src/views/productionManagement/productionOrder/index.vue | 1
src/components/AIChatSidebar/index.vue | 171 ++++-
src/views/equipmentManagement/inspectionManagement/index.vue | 27
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue | 680 ++++++++++++++++++++++++++
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue | 335 +++++++-----
11 files changed, 1,289 insertions(+), 270 deletions(-)
diff --git a/src/api/viewIndex.js b/src/api/viewIndex.js
index 9cae219..dafe6e8 100644
--- a/src/api/viewIndex.js
+++ b/src/api/viewIndex.js
@@ -347,18 +347,30 @@
});
};
+const HOME_PROGRESS_STATUS_LIST = ["all", "waiting", "inProgress", "completed", "paused", "1", "2", "3", "4"];
+const HOME_PROGRESS_TAB_LIST = ["all", "inProgress", "completed", "paused"];
+const HOME_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
+
+const normalizeDateParam = (value) => {
+ const dateText = typeof value === "string" ? value.trim() : "";
+ return HOME_DATE_PATTERN.test(dateText) ? dateText : undefined;
+};
+
export const productionOrderProgress = (params = {}) => {
const safePageNum = Math.max(1, Number(params.pageNum || 1));
const safePageSize = Math.min(50, Math.max(1, Number(params.pageSize || 10)));
- const safeTab = ["all", "inProgress", "completed", "paused"].includes(params.tab)
- ? params.tab
- : "all";
+ const rawStatus = String(params.status ?? "").trim();
+ const safeStatus = HOME_PROGRESS_STATUS_LIST.includes(rawStatus) ? rawStatus : undefined;
+ const safeTab = HOME_PROGRESS_TAB_LIST.includes(params.tab) ? params.tab : "all";
+ const normalizedTab = safeStatus && HOME_PROGRESS_TAB_LIST.includes(safeStatus) ? safeStatus : safeTab;
return request({
url: "/home/productionOrderProgress",
method: "get",
params: {
...params,
- tab: safeTab,
+ status: safeStatus,
+ tab: normalizedTab,
+ bizDate: normalizeDateParam(params.bizDate),
pageNum: safePageNum,
pageSize: safePageSize,
},
@@ -376,6 +388,7 @@
params: {
...params,
limit: safeLimit,
+ planDate: normalizeDateParam(params.planDate),
},
headers: {
handleAuthError: false,
diff --git a/src/components/AIChatSidebar/assistants/purchaseAssistant.js b/src/components/AIChatSidebar/assistants/purchaseAssistant.js
index 8f0ae0a..4f2e72c 100644
--- a/src/components/AIChatSidebar/assistants/purchaseAssistant.js
+++ b/src/components/AIChatSidebar/assistants/purchaseAssistant.js
@@ -19,7 +19,7 @@
'鏈湀閲囪喘閲戦鎺掑悕鍓嶅崄鐨勭墿鏂欐湁鍝簺锛�',
'鍝簺閲囪喘璁㈠崟杩樻湭鍏ュ簱锛�',
'鏈�杩�7澶╀緵搴斿晢鍒拌揣寮傚父鏈夊摢浜涳紵',
- '甯垜缁熻寰呬粯娆鹃噰璐崟',
+ '甯垜缁熻寰呬粯娆鹃噰璐崟锛�',
'鍒楀嚭鏈湀閲囪喘閫�璐ф儏鍐�'
]
}
diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index 9836854..c9db563 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -489,6 +489,56 @@
</div>
</div>
+ <div v-if="message.purchaseData" class="sales-structured-card">
+ <div class="sales-structured-card__title">{{ getPurchaseTypeLabel(message.type) }}</div>
+
+ <div v-if="message.purchaseData.summaryEntries?.length" class="sales-summary-grid">
+ <div
+ v-for="(entry, entryIndex) in message.purchaseData.summaryEntries"
+ :key="`purchase-summary-${entry.key}-${entryIndex}`"
+ class="sales-summary-item"
+ >
+ <span class="sales-summary-label">{{ entry.label }}</span>
+ <strong class="sales-summary-value">{{ entry.value }}</strong>
+ </div>
+ </div>
+
+ <div
+ v-if="message.purchaseData.listItems?.length && message.purchaseData.columns?.length"
+ class="table-wrapper manufacturing-table-wrapper"
+ >
+ <el-table :data="message.purchaseData.listItems" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.purchaseData.columns"
+ :key="col"
+ :label="getStructuredFieldLabel(col)"
+ min-width="140"
+ show-overflow-tooltip
+ >
+ <template #default="{ row }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+
+ <div v-if="message.purchaseIntentData?.quickPrompts?.length" class="purchase-intent-quick-prompt-wrap">
+ <div class="purchase-intent-quick-prompt-title">璇曡瘯浠ヤ笅鎻愰棶</div>
+ <div class="quick-prompt-list purchase-intent-quick-prompt-list">
+ <button
+ v-for="prompt in message.purchaseIntentData.quickPrompts"
+ :key="`purchase-intent-${prompt}`"
+ type="button"
+ class="quick-prompt-btn"
+ :disabled="isSending"
+ @click="sendQuickPrompt(prompt)"
+ >
+ {{ prompt }}
+ </button>
+ </div>
+ </div>
+
<div v-if="message.purchaseAnalysisData" class="purchase-confirm-card">
<div class="purchase-confirm-header">
<span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '閲囪喘涓氬姟' }}</span>
@@ -906,6 +956,10 @@
sales_customer_churn_risk: '瀹㈡埛娴佸け椋庨櫓鍒嗘瀽',
sales_collection_quote_strategy: '鍥炴涓庢姤浠风瓥鐣ュ缓璁�'
}
+const purchaseTypeLabelMap = {
+ purchase_material_rank: '閲囪喘鐗╂枡閲戦鎺掕',
+ purchase_pending_payment_list: '寰呬粯娆鹃噰璐崟'
+}
const manufacturingStructuredTypeSet = new Set([
'manufacturing_site_snapshot',
'manufacturing_plan_list',
@@ -991,7 +1045,11 @@
contractAmountTotal: '鍚堝悓鎬婚',
receivedAmountTotal: '宸插洖娆鹃噾棰�',
pendingAmountTotal: '寰呭洖娆炬�婚',
- shipRate: '鍙戣揣鐜�'
+ shipRate: '鍙戣揣鐜�',
+ pendingOrderCount: '寰呬粯娆捐鍗曟暟',
+ totalContractAmount: '寰呬粯娆惧悎鍚屾�婚',
+ totalPaidAmount: '宸蹭粯娆炬�婚',
+ totalPendingAmount: '寰呬粯娆炬�婚'
})
const purchasePayloadFieldLabelMap = {
purchaseLedgers: '閲囪喘鍙拌处',
@@ -1415,6 +1473,25 @@
const getSalesTypeLabel = (type = '') => salesTypeLabelMap[String(type || '')] || '閿�鍞煡璇㈢粨鏋�'
+const buildPurchaseStructuredData = (parsedData) => {
+ if (parsedData?.success !== true) return null
+
+ const type = String(parsedData?.type || '')
+ if (!type.startsWith('purchase_')) return null
+
+ const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+ const listItems = normalizeSalesListItems(rawData.items)
+
+ return {
+ type,
+ summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+ listItems,
+ columns: inferSalesColumns(listItems)
+ }
+}
+
+const getPurchaseTypeLabel = (type = '') => purchaseTypeLabelMap[String(type || '')] || '閲囪喘鏌ヨ缁撴灉'
+
const isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || ''))
const getSalesLevelTagType = (level = '') => {
@@ -1446,6 +1523,13 @@
.filter(Boolean)
}
return []
+}
+
+const normalizePurchaseIntentNotRecognizedData = (parsedData) => {
+ if (String(parsedData?.type || '') !== 'purchase_intent_not_recognized') return null
+ const quickPrompts = toStructuredStringArray(parsedData?.data?.quickPrompts)
+ if (!quickPrompts.length) return null
+ return { quickPrompts }
}
const getManufacturingWarningLevelType = (level = '') => {
@@ -1827,6 +1911,8 @@
purchaseAnalysisData: null,
manufacturingData: null,
salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null,
localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
}
@@ -2045,7 +2131,7 @@
const candidate = text.slice(i, j + 1)
try {
const parsed = JSON.parse(candidate)
- if (parsed?.success === true) {
+ if (typeof parsed?.success === 'boolean') {
return {
data: parsed,
startIdx: i,
@@ -2064,7 +2150,8 @@
}
const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
- if (!messageObj || !parsedData?.success) return
+ const isPurchaseIntentNotRecognized = String(parsedData?.type || '') === 'purchase_intent_not_recognized'
+ if (!messageObj || (parsedData?.success !== true && !isPurchaseIntentNotRecognized)) return
const previousManufacturingData = messageObj.manufacturingData
messageObj.type = parsedData.type || ''
@@ -2072,6 +2159,15 @@
messageObj.purchaseAnalysisData = null
messageObj.manufacturingData = null
messageObj.salesData = null
+ messageObj.purchaseData = null
+ messageObj.purchaseIntentData = null
+
+ if (isPurchaseIntentNotRecognized) {
+ messageObj.purchaseIntentData = normalizePurchaseIntentNotRecognizedData(parsedData)
+ messageObj.chartOptions = null
+ messageObj.chartRenderReady = false
+ return
+ }
if (messageObj.type === 'todo_list' && parsedData.data) {
messageObj.tableData = parsedData.data
@@ -2087,11 +2183,17 @@
messageObj.manufacturingData = manufacturingData
}
+ const purchaseData = buildPurchaseStructuredData(parsedData)
+ if (purchaseData) {
+ messageObj.purchaseData = purchaseData
+ }
+
if (parsedData.action === 'confirm_required' && parsedData.businessType) {
messageObj.type = 'purchase_analysis_confirm'
messageObj.purchaseAnalysisData = parsedData
messageObj.manufacturingData = null
messageObj.salesData = null
+ messageObj.purchaseData = null
if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
initializePurchasePayloadTree(messageObj, parsedData.payload || {})
}
@@ -2136,6 +2238,7 @@
const getStructuredFallbackText = (parsedData) => {
if (!parsedData) return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
if (parsedData.type === 'todo_list') return '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+ if (parsedData.type === 'purchase_intent_not_recognized') return '鏈瘑鍒埌鍙墽琛岀殑閲囪喘鏌ヨ鏉′欢锛岃琛ュ厖鏌ヨ鏉′欢鍚庡啀璇曘��'
if (salesStructuredTypeSet.has(parsedData.type)) {
if (parsedData.type === 'sales_customer_churn_risk') return '宸蹭负鎮ㄧ敓鎴愬鎴锋祦澶遍闄╁垎鏋愩��'
if (parsedData.type === 'sales_collection_quote_strategy') return '宸蹭负鎮ㄧ敓鎴愬洖娆句笌鎶ヤ环绛栫暐寤鸿銆�'
@@ -2148,6 +2251,7 @@
if (parsedData.type === 'manufacturing_analysis') return '宸蹭负鎮ㄧ敓鎴愬埗閫犲垎鏋愮粨鏋溿��'
return '宸茶繑鍥炲埗閫犳煡璇㈢粨鏋溿��'
}
+ if (String(parsedData.type || '').startsWith('purchase_')) return '宸茶繑鍥為噰璐煡璇㈢粨鏋溿��'
if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
}
@@ -3278,7 +3382,9 @@
payloadHiddenData: null,
purchaseAnalysisData: null,
manufacturingData: null,
- salesData: null
+ salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null
})
outputState.value[botMsgIndex] = {
@@ -3404,7 +3510,9 @@
payloadHiddenData: null,
purchaseAnalysisData: null,
manufacturingData: null,
- salesData: null
+ salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null
}
messages.value.push(botMsg)
@@ -3436,28 +3544,6 @@
const extracted = extractEmbeddedSuccessJson(fullText)
if (extracted) {
applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
- } else {
- const extractJson = (text) => {
- const startIdx = text.indexOf('{"success": true')
- if (startIdx === -1) return null
-
- // 浠庡悗寰�鍓嶆壘鏈�鍚庝竴涓� '}'
- const lastBraceIdx = text.lastIndexOf('}')
- if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
-
- const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
- try {
- return JSON.parse(potentialJson)
- } catch (err) {
- return null
- }
- }
-
- const parsedData = extractJson(fullText)
- if (parsedData) {
- applyStructuredMessageData(currentMsg, parsedData, botMsgIndex, true)
- }
-
}
updateOutputState(fullText, botMsgIndex)
@@ -3475,24 +3561,6 @@
const extracted = extractEmbeddedSuccessJson(currentMsg.content)
if (extracted) {
applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
- } else {
- const extractJson = (text) => {
- const startIdx = text.indexOf('{"success": true')
- if (startIdx === -1) return null
- const lastBraceIdx = text.lastIndexOf('}')
- if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
- const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
- try {
- return JSON.parse(potentialJson)
- } catch (err) {
- return null
- }
- }
-
- const finalParsed = extractJson(currentMsg.content)
- if (finalParsed) {
- applyStructuredMessageData(currentMsg, finalParsed, botMsgIndex)
- }
}
}).catch(err => {
if (err.name === 'CanceledError' || err.name === 'AbortError') {
@@ -4889,6 +4957,19 @@
color: $deep-blue;
}
+.purchase-intent-quick-prompt-wrap {
+ margin-top: 12px;
+}
+
+.purchase-intent-quick-prompt-title {
+ font-size: 12px;
+ color: #4b5563;
+}
+
+.purchase-intent-quick-prompt-list {
+ margin-top: 8px;
+}
+
.purchase-confirm-card {
margin-top: 12px;
width: 100%;
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index dd31b26..b7714f6 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -1,12 +1,12 @@
-import {login, logout, getInfo, loginCheck, loginCheckFactory,tideLogin} from '@/api/login'
-import { getToken, setToken, removeToken } from '@/utils/auth'
-import { isHttp, isEmpty } from "@/utils/validate"
-import defAva from '@/assets/images/profile.jpg'
-import { defineStore } from 'pinia'
-
-const useUserStore = defineStore(
- 'user',
- {
+import {login, logout, getInfo, loginCheck, loginCheckFactory,tideLogin} from '@/api/login'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { isHttp, isEmpty } from "@/utils/validate"
+import defAva from '@/assets/images/profile.jpg'
+import { defineStore } from 'pinia'
+
+const useUserStore = defineStore(
+ 'user',
+ {
state: () => ({
token: getToken(),
id: '',
@@ -15,9 +15,9 @@
roles: [],
permissions: [],
aiEnabled: 0
- }),
- actions: {
- // 鐧诲綍
+ }),
+ actions: {
+ // 鐧诲綍
login(userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
@@ -37,33 +37,34 @@
reject(error)
})
})
- },
- getCurrentTime() {
- const now = new Date();
- const year = now.getFullYear(); // 鑾峰彇骞翠唤
- const month = String(now.getMonth() + 1).padStart(2, '0'); // 鏈堜唤浠�0寮�濮嬶紝瑕�+1锛屽苟琛ラ浂
- const day = String(now.getDate()).padStart(2, '0'); // 鏃ユ湡琛ラ浂
- const hours = String(now.getHours()).padStart(2, '0'); // 灏忔椂琛ラ浂
- const minutes = String(now.getMinutes()).padStart(2, '0'); // 鍒嗛挓琛ラ浂
- const seconds = String(now.getSeconds()).padStart(2, '0'); // 绉掓暟琛ラ浂
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- },
- // 鑾峰彇鐢ㄦ埛淇℃伅
- getInfo() {
- return new Promise((resolve, reject) => {
- getInfo().then(res => {
+ },
+ getCurrentTime() {
+ const now = new Date();
+ const year = now.getFullYear(); // 鑾峰彇骞翠唤
+ const month = String(now.getMonth() + 1).padStart(2, '0'); // 鏈堜唤浠�0寮�濮嬶紝瑕�+1锛屽苟琛ラ浂
+ const day = String(now.getDate()).padStart(2, '0'); // 鏃ユ湡琛ラ浂
+ const hours = String(now.getHours()).padStart(2, '0'); // 灏忔椂琛ラ浂
+ const minutes = String(now.getMinutes()).padStart(2, '0'); // 鍒嗛挓琛ラ浂
+ const seconds = String(now.getSeconds()).padStart(2, '0'); // 绉掓暟琛ラ浂
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+ },
+ // 鑾峰彇鐢ㄦ埛淇℃伅
+ getInfo() {
+ return new Promise((resolve, reject) => {
+ getInfo().then(res => {
+ res = res.data
const user = res.user || {}
- let avatar = user.avatar || ""
- avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
- if (res.roles && res.roles.length > 0) { // 楠岃瘉杩斿洖鐨剅oles鏄惁鏄竴涓潪绌烘暟缁�
- this.roles = res.roles
- this.permissions = res.permissions
- } else {
- this.roles = ['ROLE_DEFAULT']
- }
+ let avatar = user.avatar || ""
+ avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
+ if (res.roles && res.roles.length > 0) { // 楠岃瘉杩斿洖鐨剅oles鏄惁鏄竴涓潪绌烘暟缁�
+ this.roles = res.roles
+ this.permissions = res.permissions
+ } else {
+ this.roles = ['ROLE_DEFAULT']
+ }
this.id = user.userId || ''
this.name = user.userName || ''
- this.avatar = avatar
+ this.avatar = avatar
this.currentFactoryName = user.currentFactoryName || ''
this.nickName = user.nickName || ''
this.roleName = Array.isArray(user.roles) && user.roles.length > 0 ? (user.roles[0].roleName || '') : ''
@@ -75,11 +76,11 @@
reject(error)
})
})
- },
- // 閫�鍑虹郴缁�
- logOut() {
- return new Promise((resolve, reject) => {
- logout(this.token).then(() => {
+ },
+ // 閫�鍑虹郴缁�
+ logOut() {
+ return new Promise((resolve, reject) => {
+ logout(this.token).then(() => {
this.token = ''
this.roles = []
this.permissions = []
@@ -89,21 +90,21 @@
}).catch(error => {
reject(error)
})
- })
- },
- // 鐧诲綍鏍¢獙
- loginCheck(userInfo) {
- const username = userInfo.username.trim()
- const password = userInfo.password
- return new Promise((resolve, reject) => {
- loginCheck(username, password).then(res => {
- resolve(res)
- }).catch(error => {
- reject(error)
- })
- })
- },
- // 閮ㄩ棬鐧诲綍
+ })
+ },
+ // 鐧诲綍鏍¢獙
+ loginCheck(userInfo) {
+ const username = userInfo.username.trim()
+ const password = userInfo.password
+ return new Promise((resolve, reject) => {
+ loginCheck(username, password).then(res => {
+ resolve(res)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+ // 閮ㄩ棬鐧诲綍
loginCheckFactory(userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
@@ -121,7 +122,7 @@
reject(error)
})
})
- },
+ },
TideLogin(code) {
return new Promise((resolve, reject) => {
tideLogin(code)
@@ -138,12 +139,12 @@
};
resolve();
})
- .catch((error) => {
- reject(error);
- });
- });
- },
- }
- })
-
-export default useUserStore
+ .catch((error) => {
+ reject(error);
+ });
+ });
+ },
+ }
+ })
+
+export default useUserStore
diff --git a/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
new file mode 100644
index 0000000..36256c8
--- /dev/null
+++ b/src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
@@ -0,0 +1,680 @@
+<template>
+ <FormDialog
+ v-model="dialogVisible"
+ title="涓婁紶宸℃璁板綍"
+ width="980px"
+ @close="handleClose"
+ @cancel="handleClose"
+ >
+ <main class="upload-content">
+ <el-card v-if="taskInfo" class="section-card">
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="宸℃浠诲姟鍚嶇О">
+ {{ taskInfo.taskName || "-" }}
+ </el-descriptions-item>
+ <el-descriptions-item label="宸℃椤圭洰">
+ {{ taskInfo.inspectionProject || "-" }}
+ </el-descriptions-item>
+ <el-descriptions-item label="澶囨敞">
+ {{ taskInfo.remarks || "-" }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <el-card class="section-card">
+ <h3>宸℃鐘舵��</h3>
+ <el-radio-group v-model="hasException">
+ <el-radio-button :value="false">姝e父</el-radio-button>
+ <el-radio-button :value="true">瀛樺湪寮傚父</el-radio-button>
+ </el-radio-group>
+ </el-card>
+
+ <el-card v-if="hasException === true" class="section-card">
+ <h3>寮傚父鎻忚堪</h3>
+ <el-input
+ v-model="abnormalDescription"
+ type="textarea"
+ maxlength="500"
+ show-word-limit
+ :rows="4"
+ placeholder="璇锋弿杩板紓甯告儏鍐�..."
+ />
+ </el-card>
+
+ <el-card v-if="hasException === true" class="section-card">
+ <el-tabs v-model="currentUploadType">
+ <el-tab-pane label="鐢熶骇鍓�" name="before" />
+ <el-tab-pane label="鐢熶骇涓�" name="after" />
+ <el-tab-pane label="鐢熶骇鍚�" name="issue" />
+ </el-tabs>
+
+ <div class="upload-buttons">
+ <el-upload
+ :show-file-list="false"
+ :http-request="uploadFile"
+ :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
+ accept="image/*"
+ >
+ <el-button type="primary" :loading="uploading">
+ <el-icon><Camera /></el-icon>
+ 閫夋嫨鍥剧墖
+ </el-button>
+ </el-upload>
+
+ <el-upload
+ :show-file-list="false"
+ :http-request="uploadFile"
+ :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
+ accept="video/*"
+ >
+ <el-button type="success" :loading="uploading">
+ <el-icon><VideoCamera /></el-icon>
+ 閫夋嫨瑙嗛
+ </el-button>
+ </el-upload>
+ </div>
+
+ <el-progress
+ v-if="uploading"
+ :percentage="uploadProgress"
+ class="upload-progress"
+ />
+
+ <div v-if="getCurrentFiles().length" class="file-list">
+ <div
+ v-for="(file, index) in getCurrentFiles()"
+ :key="file.uid || file.id || index"
+ class="file-item"
+ >
+ <div class="file-preview-container">
+ <el-image
+ v-if="file.type === 'image' || !file.type"
+ :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
+ fit="cover"
+ class="file-preview"
+ :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]"
+ preview-teleported
+ />
+
+ <div v-else class="video-preview" @click="previewVideo(file)">
+ <el-icon><VideoCamera /></el-icon>
+ <span>瑙嗛</span>
+ </div>
+
+ <el-button
+ class="delete-btn"
+ type="danger"
+ circle
+ size="small"
+ @click="removeFile(index)"
+ >
+ <el-icon><Close /></el-icon>
+ </el-button>
+ </div>
+
+ <div class="file-info">
+ <div class="file-name">
+ {{ file.bucketFilename || file.name || (file.type === "image" ? "鍥剧墖" : "瑙嗛") }}
+ </div>
+ <div class="file-size">{{ formatFileSize(file.size) }}</div>
+ </div>
+ </div>
+ </div>
+
+ <el-empty
+ v-else
+ :description="`璇烽�夋嫨瑕佷笂浼犵殑${getUploadTypeText()}鍥剧墖鎴栬棰慲"
+ />
+
+ <el-alert
+ class="upload-summary"
+ type="info"
+ :closable="false"
+ :title="`鐢熶骇鍓嶏細${beforeModelValue.length}涓枃浠� | 鐢熶骇涓細${afterModelValue.length}涓枃浠� | 鐢熶骇鍚庯細${issueModelValue.length}涓枃浠禶"
+ />
+ </el-card>
+
+ <el-result
+ v-if="hasException === false"
+ icon="success"
+ title="璁惧杩愯姝e父"
+ sub-title="鏃犻渶涓婁紶鐓х墖"
+ />
+ </main>
+
+ <template #footer>
+ <footer class="footer-buttons">
+ <el-button type="primary" @click="submitUpload">鎻愪氦</el-button>
+ <el-button v-if="hasException === true" type="warning" @click="goToRepair">
+ 鏂板鎶ヤ慨
+ </el-button>
+ <el-button @click="handleClose">鍙栨秷</el-button>
+ </footer>
+ </template>
+ </FormDialog>
+
+ <el-dialog
+ v-model="showVideoDialog"
+ :title="currentVideoFile?.originalFilename || currentVideoFile?.name || '瑙嗛棰勮'"
+ width="720px"
+ >
+ <video
+ v-if="currentVideoFile"
+ :src="currentVideoFile.url || currentVideoFile.downloadUrl"
+ class="video-player"
+ controls
+ autoplay
+ />
+ </el-dialog>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { useRouter } from "vue-router";
+import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
+import { Camera, Close, VideoCamera } from "@element-plus/icons-vue";
+import axios from "axios";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { uploadInspectionTask } from "@/api/inspectionManagement/index.js";
+import { getToken } from "@/utils/auth";
+
+const emit = defineEmits(["closeDia", "success"]);
+const router = useRouter();
+
+const dialogVisible = ref(false);
+const taskInfo = ref(null);
+const uploading = ref(false);
+const uploadProgress = ref(0);
+
+const beforeModelValue = ref([]);
+const afterModelValue = ref([]);
+const issueModelValue = ref([]);
+
+const currentUploadType = ref("before");
+const hasException = ref(null);
+const abnormalDescription = ref("");
+
+const showVideoDialog = ref(false);
+const currentVideoFile = ref(null);
+
+const uploadConfig = {
+ action: "/common/upload",
+ limit: 10,
+ fileSize: 50,
+ fileType: ["jpg", "jpeg", "png", "mp4", "mov"],
+};
+
+const uploadFileUrl = computed(
+ () => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}`
+);
+
+const processFileUrl = fileUrl => {
+ if (!fileUrl) return "";
+
+ let currentUrl = String(fileUrl);
+ if (currentUrl.includes("\\")) {
+ const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads");
+ if (uploadsIndex > -1) {
+ currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`;
+ } else {
+ const fileName = currentUrl.split("\\").pop();
+ currentUrl = `/uploads/${fileName}`;
+ }
+ }
+
+ if (currentUrl && !currentUrl.startsWith("http")) {
+ if (!currentUrl.startsWith("/")) {
+ currentUrl = `/${currentUrl}`;
+ }
+ currentUrl = __BASE_API__ + currentUrl;
+ }
+
+ return currentUrl;
+};
+
+const normalizeList = (list, fileType) => {
+ if (!Array.isArray(list)) return [];
+
+ return list.filter(Boolean).map(item => {
+ let currentType = item.type;
+ if (!currentType && item.contentType) {
+ currentType = item.contentType.startsWith("video") ? "video" : "image";
+ } else if (!currentType) {
+ currentType = fileType || "image";
+ }
+
+ return {
+ ...item,
+ url: processFileUrl(item.url || item.previewURL || item.downloadUrl || item.path || ""),
+ downloadUrl: processFileUrl(
+ item.downloadUrl || item.url || item.previewURL || item.path || ""
+ ),
+ name: item.name || item.originalFilename || item.bucketFilename,
+ tempId: item.tempId || item.id || item.tempFileId,
+ tempFileId: item.tempFileId || item.tempId || item.id,
+ size: item.size || item.byteSize || 0,
+ type: currentType,
+ status: "success",
+ uid: item.uid || `${Date.now()}-${Math.random()}`,
+ };
+ });
+};
+
+const resetState = () => {
+ taskInfo.value = null;
+ beforeModelValue.value = [];
+ afterModelValue.value = [];
+ issueModelValue.value = [];
+ currentUploadType.value = "before";
+ hasException.value = null;
+ abnormalDescription.value = "";
+ uploading.value = false;
+ uploadProgress.value = 0;
+ showVideoDialog.value = false;
+ currentVideoFile.value = null;
+};
+
+const openDialog = row => {
+ const raw = JSON.parse(JSON.stringify(row?.__raw || row || {}));
+ taskInfo.value = raw;
+
+ beforeModelValue.value = normalizeList(
+ raw.commonFileListBeforeVO || raw.commonFileListBefore || [],
+ "image"
+ );
+ afterModelValue.value = normalizeList(
+ raw.commonFileListVO || raw.commonFileList || [],
+ "image"
+ );
+ issueModelValue.value = normalizeList(
+ raw.commonFileListAfterVO || raw.commonFileListAfter || [],
+ "image"
+ );
+
+ abnormalDescription.value = raw.abnormalDescription || "";
+
+ if (raw.hasException !== undefined && raw.hasException !== null) {
+ hasException.value = raw.hasException;
+ } else if (raw.inspectionResult !== undefined && raw.inspectionResult !== null) {
+ hasException.value = String(raw.inspectionResult) === "0";
+ } else {
+ hasException.value = null;
+ }
+
+ if (
+ hasException.value !== true &&
+ (beforeModelValue.value.length || afterModelValue.value.length || issueModelValue.value.length)
+ ) {
+ hasException.value = true;
+ }
+
+ dialogVisible.value = true;
+};
+
+const handleClose = () => {
+ dialogVisible.value = false;
+ resetState();
+ emit("closeDia");
+};
+
+const getCurrentFiles = () => {
+ if (currentUploadType.value === "before") return beforeModelValue.value;
+ if (currentUploadType.value === "after") return afterModelValue.value;
+ if (currentUploadType.value === "issue") return issueModelValue.value;
+ return [];
+};
+
+const getUploadTypeText = () => {
+ if (currentUploadType.value === "before") return "鐢熶骇鍓�";
+ if (currentUploadType.value === "after") return "鐢熶骇涓�";
+ if (currentUploadType.value === "issue") return "鐢熶骇鍚�";
+ return "";
+};
+
+const getTabType = () => {
+ if (currentUploadType.value === "before") return 10;
+ if (currentUploadType.value === "after") return 11;
+ if (currentUploadType.value === "issue") return 12;
+ return 10;
+};
+
+const previewVideo = file => {
+ currentVideoFile.value = file;
+ showVideoDialog.value = true;
+};
+
+const uploadFile = async uploadRequest => {
+ const rawFile = uploadRequest.file;
+
+ if (getCurrentFiles().length >= uploadConfig.limit) {
+ ElMessage.warning(`鏈�澶氬彧鑳介�夋嫨${uploadConfig.limit}涓枃浠禶);
+ return;
+ }
+
+ const ext = rawFile.name.split(".").pop()?.toLowerCase();
+ if (!uploadConfig.fileType.includes(ext)) {
+ ElMessage.warning(`鏂囦欢鏍煎紡涓嶆敮鎸侊紝璇蜂笂浼� ${uploadConfig.fileType.join("/")} 鏍煎紡`);
+ return;
+ }
+
+ if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) {
+ ElMessage.warning(`鏂囦欢澶у皬涓嶈兘瓒呰繃 ${uploadConfig.fileSize}MB`);
+ return;
+ }
+
+ const token = getToken();
+ if (!token) {
+ ElMessage.warning("鐢ㄦ埛鏈櫥褰�");
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append("files", rawFile);
+ formData.append("type", getTabType());
+
+ uploading.value = true;
+ uploadProgress.value = 0;
+
+ try {
+ const { data } = await axios.post(uploadFileUrl.value, formData, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "multipart/form-data",
+ },
+ onUploadProgress: event => {
+ if (event.total) {
+ uploadProgress.value = Math.round((event.loaded / event.total) * 100);
+ }
+ },
+ });
+
+ if (data.code !== 200) {
+ ElMessage.error(data.msg || "涓婁紶澶辫触");
+ return;
+ }
+
+ const resultData = Array.isArray(data.data) ? data.data[0] : data.data;
+ const finalUrl = processFileUrl(
+ resultData.url || resultData.previewURL || resultData.downloadUrl || ""
+ );
+ const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename;
+ const finalId = resultData.tempId || resultData.id || resultData.tempFileId;
+
+ const uploadedFile = {
+ ...resultData,
+ url: finalUrl,
+ downloadUrl: finalUrl,
+ name: finalName,
+ tempId: finalId,
+ tempFileId: resultData.tempFileId || finalId,
+ size: rawFile.size || resultData.size || resultData.byteSize || 0,
+ type: rawFile.type?.startsWith("video") ? "video" : "image",
+ status: "success",
+ uid: `${Date.now()}-${Math.random()}`,
+ };
+
+ getCurrentFiles().push(uploadedFile);
+ ElMessage.success("涓婁紶鎴愬姛");
+ } catch (error) {
+ ElMessage.error(error?.message || "涓婁紶澶辫触");
+ } finally {
+ uploading.value = false;
+ }
+};
+
+const buildFileItem = item => ({
+ id: item?.id,
+ tempId: item?.tempId,
+ tempFileId: item?.tempFileId,
+ url: item?.downloadUrl || item?.url || "",
+ downloadUrl: item?.downloadUrl || item?.url || "",
+ name: item?.name,
+ bucketFilename: item?.bucketFilename || item?.name,
+ originalFilename: item?.originalFilename || item?.name,
+ size: item?.size || 0,
+ byteSize: item?.byteSize || item?.size || 0,
+ contentType: item?.contentType || "",
+ type: item?.type,
+});
+
+const submitUpload = async () => {
+ if (hasException.value === null) {
+ ElMessage.warning("璇烽�夋嫨宸℃鐘舵��");
+ return;
+ }
+
+ if (hasException.value === true) {
+ const totalFiles =
+ beforeModelValue.value.length +
+ afterModelValue.value.length +
+ issueModelValue.value.length;
+
+ if (!totalFiles) {
+ ElMessage.warning("璇蜂笂浼犲紓甯哥収鐗囨垨瑙嗛");
+ return;
+ }
+
+ if (!abnormalDescription.value.trim()) {
+ ElMessage.warning("璇峰~鍐欏紓甯告弿杩�");
+ return;
+ }
+ }
+
+ const loading = ElLoading.service({
+ text: "鎻愪氦涓�...",
+ background: "rgba(0, 0, 0, 0.3)",
+ });
+
+ try {
+ const allFiles = [
+ ...beforeModelValue.value,
+ ...afterModelValue.value,
+ ...issueModelValue.value,
+ ];
+
+ const tempFileIds = allFiles
+ .map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
+ .filter(Boolean);
+
+ const {
+ createTime,
+ updateTime,
+ storageBlobDTO,
+ commonFileListAfterVO,
+ commonFileListVO,
+ commonFileListBeforeVO,
+ commonFileListAfter,
+ commonFileList,
+ commonFileListBefore,
+ __raw,
+ ...baseTaskInfo
+ } = taskInfo.value || {};
+
+ const submitData = {
+ ...baseTaskInfo,
+ commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
+ commonFileListDTO: afterModelValue.value.map(buildFileItem),
+ commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
+ hasException: hasException.value,
+ inspectionResult: hasException.value ? 0 : 1,
+ abnormalDescription: abnormalDescription.value,
+ tempFileIds,
+ };
+
+ const result = await uploadInspectionTask(submitData);
+
+ if (result && (result.code === 200 || result.success)) {
+ ElMessage.success("鎻愪氦鎴愬姛");
+ dialogVisible.value = false;
+ resetState();
+ emit("success");
+ emit("closeDia");
+ } else {
+ ElMessage.error(result?.msg || result?.message || "鎻愪氦澶辫触");
+ }
+ } catch (error) {
+ ElMessage.error(error?.message || "鎻愪氦澶辫触");
+ } finally {
+ loading.close();
+ }
+};
+
+const removeFile = async index => {
+ try {
+ await ElMessageBox.confirm("纭畾瑕佸垹闄よ繖涓枃浠跺悧锛�", "纭鍒犻櫎", {
+ type: "warning",
+ });
+ getCurrentFiles().splice(index, 1);
+ } catch {}
+};
+
+const goToRepair = () => {
+ const taskData = {
+ taskId: taskInfo.value?.taskId || taskInfo.value?.id,
+ taskName: taskInfo.value?.taskName,
+ inspectionLocation: taskInfo.value?.inspectionLocation,
+ inspector: taskInfo.value?.inspector,
+ hasException: hasException.value,
+ inspectionResult: hasException.value ? 0 : 1,
+ commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
+ commonFileListDTO: afterModelValue.value.map(buildFileItem),
+ commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
+ uploadedFiles: {
+ before: beforeModelValue.value,
+ after: afterModelValue.value,
+ issue: issueModelValue.value,
+ },
+ };
+
+ sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData));
+ router.push("/equipmentManagement/repair/add");
+};
+
+const formatFileSize = size => {
+ if (!size) return "0 B";
+
+ const units = ["B", "KB", "MB", "GB"];
+ let index = 0;
+ let fileSize = size;
+
+ while (fileSize >= 1024 && index < units.length - 1) {
+ fileSize /= 1024;
+ index += 1;
+ }
+
+ return `${fileSize.toFixed(2)} ${units[index]}`;
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+.inspection-upload-page {
+ min-height: 70vh;
+ background: #f5f7fa;
+ padding: 20px 20px 90px;
+ box-sizing: border-box;
+}
+
+.upload-content {
+ max-width: 960px;
+ margin: 20px auto 0;
+}
+
+.section-card {
+ margin-bottom: 16px;
+}
+
+.section-card h3 {
+ margin: 0 0 16px;
+ font-size: 16px;
+}
+
+.upload-buttons {
+ display: flex;
+ gap: 12px;
+ margin: 16px 0;
+}
+
+.upload-progress {
+ margin-bottom: 16px;
+}
+
+.file-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 12px;
+}
+
+.file-preview-container {
+ position: relative;
+ aspect-ratio: 1;
+ border-radius: 8px;
+ overflow: hidden;
+ background: #f2f3f5;
+}
+
+.file-preview {
+ width: 100%;
+ height: 100%;
+}
+
+.video-preview {
+ width: 100%;
+ height: 100%;
+ background: #303133;
+ color: #fff;
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.delete-btn {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+}
+
+.file-info {
+ margin-top: 6px;
+ font-size: 12px;
+}
+
+.file-name {
+ color: #606266;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.file-size {
+ color: #909399;
+ margin-top: 2px;
+}
+
+.upload-summary {
+ margin-top: 16px;
+}
+
+.footer-buttons {
+ position: sticky;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 14px 20px 0;
+ background: #f5f7fa;
+ display: flex;
+ justify-content: center;
+ gap: 12px;
+}
+
+.video-player {
+ width: 100%;
+ max-height: 70vh;
+ background: #000;
+}
+</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
index 66867e3..b5604fe 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,210 +1,226 @@
<template>
<div>
- <el-dialog title="鏌ョ湅闄勪欢"
- v-model="dialogVisitable" width="800px" @close="cancel">
+ <el-dialog title="鏌ョ湅闄勪欢" v-model="dialogVisitable" width="800px" @close="cancel">
<div class="upload-container">
- <!-- 鐢熶骇鍓� -->
<div class="form-container">
<div class="title">鐢熶骇鍓�</div>
-
- <!-- 鍥剧墖鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <img v-for="(item, index) in beforeProductionImgs" :key="index"
- @click="showMedia(beforeProductionImgs, index, 'image')"
- :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+
+ <div class="media-list">
+ <img
+ v-for="(item, index) in beforeProductionImgs"
+ :key="`before-img-${index}`"
+ :src="item"
+ alt=""
+ class="media-image"
+ @click="showMedia(beforeProductionImgs, index, 'image')"
+ />
</div>
-
- <!-- 瑙嗛鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
+
+ <div class="media-list">
<div
- v-for="(videoUrl, index) in beforeProductionVideos"
- :key="index"
- @click="showMedia(beforeProductionVideos, index, 'video')"
- style="position: relative; margin: 10px; cursor: pointer;"
+ v-for="(videoUrl, index) in beforeProductionVideos"
+ :key="`before-video-${index}`"
+ class="video-item"
+ @click="showMedia(beforeProductionVideos, index, 'video')"
>
- <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
- <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ <div class="video-thumb">
+ <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
</div>
- <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ <div class="video-text">鐐瑰嚮鎾斁</div>
</div>
</div>
</div>
-
- <!-- 鐢熶骇鍚� -->
+
+ <div class="form-container">
+ <div class="title">鐢熶骇涓�</div>
+
+ <div class="media-list">
+ <img
+ v-for="(item, index) in afterProductionImgs"
+ :key="`during-img-${index}`"
+ :src="item"
+ alt=""
+ class="media-image"
+ @click="showMedia(afterProductionImgs, index, 'image')"
+ />
+ </div>
+
+ <div class="media-list">
+ <div
+ v-for="(videoUrl, index) in afterProductionVideos"
+ :key="`during-video-${index}`"
+ class="video-item"
+ @click="showMedia(afterProductionVideos, index, 'video')"
+ >
+ <div class="video-thumb">
+ <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
+ </div>
+ <div class="video-text">鐐瑰嚮鎾斁</div>
+ </div>
+ </div>
+ </div>
+
<div class="form-container">
<div class="title">鐢熶骇鍚�</div>
-
- <!-- 鍥剧墖鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <img v-for="(item, index) in afterProductionImgs" :key="index"
- @click="showMedia(afterProductionImgs, index, 'image')"
- :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+
+ <div class="media-list">
+ <img
+ v-for="(item, index) in productionIssuesImgs"
+ :key="`after-img-${index}`"
+ :src="item"
+ alt=""
+ class="media-image"
+ @click="showMedia(productionIssuesImgs, index, 'image')"
+ />
</div>
-
- <!-- 瑙嗛鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
+
+ <div class="media-list">
<div
- v-for="(videoUrl, index) in afterProductionVideos"
- :key="index"
- @click="showMedia(afterProductionVideos, index, 'video')"
- style="position: relative; margin: 10px; cursor: pointer;"
+ v-for="(videoUrl, index) in productionIssuesVideos"
+ :key="`after-video-${index}`"
+ class="video-item"
+ @click="showMedia(productionIssuesVideos, index, 'video')"
>
- <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
- <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+ <div class="video-thumb">
+ <img src="@/assets/images/video.png" alt="鎾斁" class="video-icon" />
</div>
- <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
- </div>
- </div>
- </div>
-
- <!-- 鐢熶骇闂 -->
- <div class="form-container">
- <div class="title">鐢熶骇闂</div>
-
- <!-- 鍥剧墖鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <img v-for="(item, index) in productionIssuesImgs" :key="index"
- @click="showMedia(productionIssuesImgs, index, 'image')"
- :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
- </div>
-
- <!-- 瑙嗛鍒楄〃 -->
- <div style="display: flex; flex-wrap: wrap;">
- <div
- v-for="(videoUrl, index) in productionIssuesVideos"
- :key="index"
- @click="showMedia(productionIssuesVideos, index, 'video')"
- style="position: relative; margin: 10px; cursor: pointer;"
- >
- <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
- <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
- </div>
- <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+ <div class="video-text">鐐瑰嚮鎾斁</div>
</div>
</div>
</div>
</div>
</el-dialog>
-
- <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+
<div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
<div class="media-viewer-content" @click.stop>
- <!-- 鍥剧墖 -->
<vue-easy-lightbox
- v-if="mediaType === 'image'"
- :visible="isMediaViewerVisible"
- :imgs="mediaList"
- :index="currentMediaIndex"
- @hide="closeMediaViewer"
- ></vue-easy-lightbox>
-
- <!-- 瑙嗛 -->
- <div v-else-if="mediaType === 'video'" style="position: relative;">
- <video
- :src="mediaList[currentMediaIndex]"
- autoplay
- controls
- style="max-width: 90vw; max-height: 80vh;"
- />
+ v-if="mediaType === 'image'"
+ :visible="isMediaViewerVisible"
+ :imgs="mediaList"
+ :index="currentMediaIndex"
+ @hide="closeMediaViewer"
+ />
+
+ <div v-else-if="mediaType === 'video'" class="video-player-wrap">
+ <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" />
</div>
</div>
</div>
</div>
</template>
-<script setup>
-import { ref } from 'vue';
-import VueEasyLightbox from 'vue-easy-lightbox';
-const { proxy } = getCurrentInstance();
-// 鎺у埗寮圭獥鏄剧ず
+<script setup>
+import { ref } from "vue";
+import VueEasyLightbox from "vue-easy-lightbox";
+
const dialogVisitable = ref(false);
-// 鍥剧墖鏁扮粍
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
-// 瑙嗛鏁扮粍
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = ref([]);
-// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
-const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
-const mediaType = ref('image'); // image | video
+const mediaList = ref([]);
+const mediaType = ref("image");
-// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
-function processItems(items) {
+const processFileUrl = fileUrl => {
+ if (!fileUrl) return "";
+
+ let currentUrl = String(fileUrl);
+ if (currentUrl.includes("\\")) {
+ const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads");
+ if (uploadsIndex > -1) {
+ currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`;
+ } else {
+ const fileName = currentUrl.split("\\").pop();
+ currentUrl = `/uploads/${fileName}`;
+ }
+ }
+
+ if (currentUrl && !currentUrl.startsWith("http")) {
+ if (!currentUrl.startsWith("/")) {
+ currentUrl = `/${currentUrl}`;
+ }
+ currentUrl = __BASE_API__ + currentUrl;
+ }
+
+ return currentUrl;
+};
+
+const processItems = items => {
const images = [];
const videos = [];
-
- // 妫�鏌� items 鏄惁瀛樺湪涓斾负鏁扮粍
- if (!items || !Array.isArray(items)) {
+
+ if (!Array.isArray(items)) {
return { images, videos };
}
-
+
items.forEach(item => {
- if (!item || !item.previewURL || !item.contentType) return;
+ if (!item) return;
-
- // 澶勭悊鏂囦欢 URL
- const fileUrl = item.previewURL;
- const contentType = String(item.contentType).toLowerCase();
+ const fileUrl = processFileUrl(
+ item.previewURL || item.url || item.downloadUrl || item.path || ""
+ );
+ const contentType = String(item.contentType || "").toLowerCase();
- // 鏍规嵁 contentType 鍒ゆ柇鏄浘鐗囪繕鏄棰�
- if (contentType.startsWith('image/')) {
- images.push(fileUrl);
- } else if (contentType.startsWith('video/')) {
+ if (!fileUrl) return;
+
+ if (contentType.startsWith("video/")) {
videos.push(fileUrl);
+ return;
}
- });
-
- return { images, videos };
-}
-// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
-const openDialog = async (row) => {
- // 浣跨敤姝g‘鐨勫瓧娈靛悕锛歝ommonFileListBefore, commonFileListAfter
- const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBeforeVO || []);
- const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfterVO || []);
- const { images: issueImgs, videos: issueVids } = processItems(row.commonFileListVO || []);
-
+ images.push(fileUrl);
+ });
+
+ return { images, videos };
+};
+
+const openDialog = row => {
+ const { images: beforeImgs, videos: beforeVids } = processItems(
+ row.commonFileListBeforeVO || []
+ );
+ const { images: afterImgs, videos: afterVids } = processItems(
+ row.commonFileListVO || []
+ );
+ const { images: issueImgs, videos: issueVids } = processItems(
+ row.commonFileListAfterVO || []
+ );
+
beforeProductionImgs.value = beforeImgs;
beforeProductionVideos.value = beforeVids;
-
afterProductionImgs.value = afterImgs;
afterProductionVideos.value = afterVids;
-
productionIssuesImgs.value = issueImgs;
productionIssuesVideos.value = issueVids;
-
dialogVisitable.value = true;
};
-// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
-function showMedia(mediaArray, index, type) {
- mediaList.value = mediaArray;
+const showMedia = (items, index, type) => {
+ mediaList.value = items;
currentMediaIndex.value = index;
mediaType.value = type;
isMediaViewerVisible.value = true;
-}
+};
-// 鍏抽棴濯掍綋鏌ョ湅鍣�
-function closeMediaViewer() {
+const closeMediaViewer = () => {
isMediaViewerVisible.value = false;
mediaList.value = [];
- mediaType.value = 'image';
-}
+ mediaType.value = "image";
+};
-// 琛ㄥ崟鍏抽棴鏂规硶
const cancel = () => {
dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
+
<style scoped lang="scss">
.upload-container {
display: flex;
@@ -213,7 +229,7 @@
padding: 20px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
-
+
.form-container {
flex: 1;
width: 100%;
@@ -229,7 +245,7 @@
padding-left: 10px;
position: relative;
margin: 6px 0;
-
+
&::before {
content: "";
position: absolute;
@@ -241,12 +257,48 @@
}
}
+.media-list {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.media-image {
+ max-width: 100px;
+ height: 100px;
+ margin: 5px;
+ cursor: pointer;
+}
+
+.video-item {
+ position: relative;
+ margin: 10px;
+ cursor: pointer;
+}
+
+.video-thumb {
+ width: 160px;
+ height: 90px;
+ background-color: #333;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.video-icon {
+ width: 30px;
+ height: 30px;
+ opacity: 0.8;
+}
+
+.video-text {
+ text-align: center;
+ font-size: 12px;
+ color: #666;
+}
+
.media-viewer-overlay {
position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
+ inset: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 9999;
display: flex;
@@ -260,4 +312,13 @@
max-height: 90vh;
overflow: hidden;
}
-</style>
\ No newline at end of file
+
+.video-player-wrap {
+ position: relative;
+}
+
+.video-player {
+ max-width: 90vw;
+ max-height: 80vh;
+}
+</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index 809fd4f..fa531f1 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -82,6 +82,9 @@
<form-dia ref="formDia"
@closeDia="handleQuery"></form-dia>
<view-files ref="viewFiles"></view-files>
+ <upload-files ref="uploadFiles"
+ @success="handleQuery"
+ @closeDia="handleQuery"></upload-files>
</div>
</template>
@@ -93,6 +96,7 @@
// 缁勪欢寮曞叆
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
+ import UploadFiles from "@/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue";
import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
// 鎺ュ彛寮曞叆
@@ -106,6 +110,7 @@
const { proxy } = getCurrentInstance();
const formDia = ref();
const viewFiles = ref();
+ const uploadFiles = ref();
// 鏌ヨ鍙傛暟
const queryParams = reactive({
@@ -211,8 +216,9 @@
const operationConfig = {
label: "鎿嶄綔",
- width: 130,
+ width: operations.length > 1 ? 180 : 130,
fixed: "right",
+ align: 'center',
dataType: "action",
operation: operations
.map(op => {
@@ -221,6 +227,12 @@
return {
name: "缂栬緫",
clickFun: handleAdd,
+ color: "#409EFF",
+ };
+ case "upload":
+ return {
+ name: "涓婁紶",
+ clickFun: openUploadDialog,
color: "#409EFF",
};
case "viewFile":
@@ -253,14 +265,14 @@
];
operationsArr.value = ["edit"];
} else if (value === "task") {
- const operationColumn = getOperationColumn(["viewFile"]);
+ const operationColumn = getOperationColumn(["upload", "viewFile"]);
// 瀹氭椂浠诲姟璁板綍涓嶅睍绀�"鏄惁鍚敤"鍒�
const taskColumns = columns.value.filter(col => col.prop !== "isEnabled");
tableColumns.value = [
...taskColumns,
...(operationColumn ? [operationColumn] : []),
];
- operationsArr.value = ["viewFile"];
+ operationsArr.value = ["upload", "viewFile"];
}
pageNum.value = 1;
pageSize.value = 10;
@@ -302,6 +314,7 @@
// 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
tableData.value = rawData.map(item => {
const processedItem = { ...item };
+ processedItem.__raw = { ...item };
// 澶勭悊 inspector 瀛楁
if (processedItem.inspector) {
@@ -351,6 +364,12 @@
const viewFile = row => {
nextTick(() => {
viewFiles.value?.openDialog(row);
+ });
+ };
+
+ const openUploadDialog = row => {
+ nextTick(() => {
+ uploadFiles.value?.openDialog(row);
});
};
@@ -419,4 +438,4 @@
color: #909399;
font-size: 14px;
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/index.vue b/src/views/index.vue
index 3176042..0d71970 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -114,6 +114,7 @@
<div class="panel-title">鐢熶骇璁㈠崟杩涘害</div>
<el-radio-group v-model="orderFilter" size="small">
<el-radio-button label="all">鍏ㄩ儴({{ orderProgressMeta.total }})</el-radio-button>
+ <el-radio-button label="waiting">寰呭紑濮�({{ orderProgressMeta.waitingCount }})</el-radio-button>
<el-radio-button label="inProgress">杩涜涓�({{ orderProgressMeta.inProgressCount }})</el-radio-button>
<el-radio-button label="completed">宸插畬鎴�({{ orderProgressMeta.completedCount }})</el-radio-button>
<el-radio-button label="paused">宸叉殏鍋�({{ orderProgressMeta.pausedCount }})</el-radio-button>
@@ -454,10 +455,13 @@
});
const orderProgressMeta = ref({
+ status: "all",
tab: "all",
+ bizDate: null,
total: 0,
pageNum: 1,
pageSize: 10,
+ waitingCount: 0,
inProgressCount: 0,
completedCount: 0,
pausedCount: 0,
@@ -832,8 +836,36 @@
const productionOrders = ref([]);
+const orderFilterOptions = ["all", "waiting", "inProgress", "completed", "paused"];
+const orderFilterAliasMap = {
+ 1: "waiting",
+ 2: "inProgress",
+ 3: "completed",
+ 4: "paused",
+};
const orderFilter = ref("all");
const filteredOrders = computed(() => productionOrders.value);
+
+const normalizeOrderFilter = (value, fallback = "all") => {
+ const safeFallback = orderFilterOptions.includes(fallback) ? fallback : "all";
+ const text = String(value ?? "").trim();
+ if (orderFilterAliasMap[text]) {
+ return orderFilterAliasMap[text];
+ }
+ return orderFilterOptions.includes(text) ? text : safeFallback;
+};
+
+const parseCount = (value) => {
+ if (value === null || value === undefined || value === "") return null;
+ const num = Number(value);
+ return Number.isFinite(num) ? num : null;
+};
+
+const resolveProgressCount = (rawValue, currentStatus, targetStatus, total) => {
+ const count = parseCount(rawValue);
+ if (count !== null) return count;
+ return currentStatus === targetStatus ? total : 0;
+};
const getCompareTrend = (value) => {
const num = Number(value || 0);
@@ -1198,27 +1230,36 @@
const refreshProductionOrderProgress = async () => {
try {
const res = await productionOrderProgress({
+ status: orderFilter.value,
tab: orderFilter.value,
pageNum: 1,
pageSize: 10,
});
const data = res?.data || {};
+ const statusValue = normalizeOrderFilter(data.status, orderFilter.value);
+ const total = Number(data.total || 0);
orderProgressMeta.value = {
+ status: statusValue,
tab: data.tab || orderFilter.value,
- total: Number(data.total || 0),
+ bizDate: data.bizDate || null,
+ total,
pageNum: Number(data.pageNum || 1),
pageSize: Number(data.pageSize || 10),
- inProgressCount: Number(data.inProgressCount || 0),
- completedCount: Number(data.completedCount || 0),
- pausedCount: Number(data.pausedCount || 0),
+ waitingCount: resolveProgressCount(data.waitingCount, statusValue, "waiting", total),
+ inProgressCount: resolveProgressCount(data.inProgressCount, statusValue, "inProgress", total),
+ completedCount: resolveProgressCount(data.completedCount, statusValue, "completed", total),
+ pausedCount: resolveProgressCount(data.pausedCount, statusValue, "paused", total),
};
productionOrders.value = (data.records || []).map(mapOrderProgressRecord);
} catch {
orderProgressMeta.value = {
+ status: orderFilter.value,
tab: orderFilter.value,
+ bizDate: null,
total: 0,
pageNum: 1,
pageSize: 10,
+ waitingCount: 0,
inProgressCount: 0,
completedCount: 0,
pausedCount: 0,
@@ -1229,7 +1270,10 @@
const refreshTodayProductionPlan = async () => {
try {
- const res = await todayProductionPlan({ limit: 4 });
+ const res = await todayProductionPlan({
+ limit: 4,
+ planDate: nowDate.value,
+ });
const data = res?.data || {};
todayPlanTotal.value = Number(data.total || 0);
todayPlanList.value = (data.records || []).map(mapTodayPlanRecord);
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
index 43425c6..1ef5b9d 100644
--- a/src/views/productionManagement/processRoute/index.vue
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -61,7 +61,9 @@
import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
import { listPage, del } from "@/api/productionManagement/processRoute.js";
- const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
+ const FileList = defineAsyncComponent(() =>
+ import("@/components/Dialog/FileList.vue")
+ );
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index eaf6397..3c52410 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -47,16 +47,45 @@
<span class="info-value">{{ routeInfo.quantity || '-' }}</span>
</div>
</div>
- <div class="info-item full-width"
- v-if="routeInfo.description">
+ <div class="info-item">
<div class="info-label-wrapper">
- <span class="info-label">鎻忚堪</span>
+ <span class="info-label">澶囨敞</span>
</div>
<div class="info-value-wrapper">
<span class="info-value">{{ routeInfo.description }}</span>
</div>
</div>
</div>
+ </el-card>
+ <!-- 闄勪欢妯″潡 -->
+ <div v-if="pageType === 'order'"
+ class="section-header">
+ <div class="section-title">闄勪欢</div>
+ </div>
+ <el-card v-if="pageType === 'order'"
+ class="attachment-card"
+ shadow="hover"
+ style="margin-top: 10px; margin-bottom: 20px;">
+ <el-table :data="attachmentTableData"
+ border
+ class="attachment-table">
+ <el-table-column label="闄勪欢鍚嶇О"
+ prop="originalFilename"
+ show-overflow-tooltip />
+ <el-table-column fixed="right"
+ label="鎿嶄綔"
+ width="200"
+ align="center">
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ size="small"
+ @click="downloadAttachmentFile(scope.row.downloadURL)">
+ 涓嬭浇
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
</el-card>
<!-- 琛ㄦ牸瑙嗗浘 -->
<div v-if="viewMode === 'table'"
@@ -382,6 +411,18 @@
v-model="bomDataValue.showProductDialog"
:single="true"
@confirm="handleBomProduct" />
+ <!-- 涓婁紶缁勪欢寮圭獥 -->
+ <el-dialog v-model="uploadDialogVisible"
+ title="涓婁紶闄勪欢"
+ width="50%"
+ @close="closeAttachmentUpload">
+ <AttachmentUpload v-model:file-list="newFileList" />
+ <template #footer>
+ <el-button @click="saveAttachmentUpload"
+ type="primary">淇濆瓨</el-button>
+ <el-button @click="closeAttachmentUpload">鍏抽棴</el-button>
+ </template>
+ </el-dialog>
<!-- 鏂板/缂栬緫寮圭獥 -->
<el-dialog v-model="dialogVisible"
:title="operationType === 'add' ? '鏂板宸ヨ壓璺嚎椤圭洰' : '缂栬緫宸ヨ壓璺嚎椤圭洰'"
@@ -518,6 +559,12 @@
queryList2,
add2,
} from "@/api/productionManagement/productStructure.js";
+ import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue";
+ import {
+ attachmentList,
+ deleteAttachment,
+ createAttachment,
+ } from "@/api/basicData/storageAttachment.js";
import { useRoute } from "vue-router";
import { ElMessageBox, ElMessage } from "element-plus";
@@ -530,6 +577,7 @@
const orderId = computed(() => route.query.orderId);
const pageType = computed(() => route.query.type);
const editable = computed(() => route.query.editable !== "false");
+ const technologyRoutingId = computed(() => route.query.technologyRoutingId);
const tableLoading = ref(false);
const tableData = ref([]);
@@ -548,7 +596,66 @@
bomNo: "",
description: "",
quantity: 0,
+ technologyRoutingId: "",
});
+
+ // 闄勪欢鐩稿叧
+ const attachmentTableData = ref([]);
+ const uploadDialogVisible = ref(false);
+ const newFileList = ref([]);
+
+ const getAttachmentList = () => {
+ if (!technologyRoutingId.value) return;
+ attachmentList({
+ recordType: "technology_routing",
+ recordId: technologyRoutingId.value,
+ }).then(res => {
+ attachmentTableData.value = (res && res.data) || [];
+ });
+ };
+
+ const handleUploadAttachment = () => {
+ uploadDialogVisible.value = true;
+ };
+
+ const saveAttachmentUpload = async () => {
+ if (newFileList.value.length > 0) {
+ createAttachment({
+ application: "file",
+ recordType: "technology_routing",
+ recordId: technologyRoutingId.value,
+ storageBlobDTOs: [...newFileList.value, ...attachmentTableData.value],
+ })
+ .then(res => {
+ if (res && res.code === 200) {
+ proxy?.$modal?.msgSuccess("涓婁紶鎴愬姛");
+ newFileList.value = [];
+ getAttachmentList();
+ }
+ })
+ .finally(() => {
+ uploadDialogVisible.value = false;
+ });
+ }
+ };
+
+ const closeAttachmentUpload = () => {
+ newFileList.value = [];
+ uploadDialogVisible.value = false;
+ };
+
+ const handleDeleteAttachment = async row => {
+ deleteAttachment([row.storageAttachmentId]).then(res => {
+ if (res && res.code === 200) {
+ proxy?.$modal?.msgSuccess("鍒犻櫎鎴愬姛");
+ getAttachmentList();
+ }
+ });
+ };
+
+ const downloadAttachmentFile = url => {
+ window.open(url, "_blank");
+ };
const processOptions = ref([]);
const showProductSelectDialog = ref(false);
@@ -680,6 +787,7 @@
bomId: route.query.bomId || "",
description: route.query.description || "",
quantity: route.query.quantity || 0,
+ technologyRoutingId: route.query.technologyRoutingId || "",
status: !(route.query.status == 1 || route.query.status === "false"),
};
bomTableData.value[0].productName = routeInfo.value.productName;
@@ -1165,12 +1273,16 @@
const handleBomProcessChange = (row, value) => {
row.processId = value || "";
syncProcessOperationFields(row);
-
+
// 妫�鏌ュ悓涓�灞傜骇鏄惁宸茬粡鏈夊叾浠栦笉鍚岀殑宸ュ簭琚�変腑
const siblings = findSiblings(bomDataValue.value.dataList, row.tempId);
if (siblings && value) {
const hasDifferentProcess = siblings.some(sibling => {
- return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value;
+ return (
+ sibling.tempId !== row.tempId &&
+ sibling.processId &&
+ sibling.processId !== value
+ );
});
if (hasDifferentProcess) {
ElMessage.warning("鍚屼竴灞傜骇宸插瓨鍦ㄤ笉鍚岀殑宸ュ簭锛岃鍏堢粺涓�宸ュ簭鍚庡啀杩涜淇敼");
@@ -1392,11 +1504,13 @@
};
// 鏍¢獙鍚屼竴灞傜骇鐨勫伐搴忔槸鍚︿竴鑷�
- const validateProcessConsistency = (items) => {
+ const validateProcessConsistency = items => {
if (!items || items.length === 0) return;
// 妫�鏌ュ綋鍓嶅眰绾�
- const processes = items.filter(item => item.processId).map(item => item.processId);
+ const processes = items
+ .filter(item => item.processId)
+ .map(item => item.processId);
if (processes.length > 1) {
const uniqueProcesses = [...new Set(processes)];
if (uniqueProcesses.length > 1) {
@@ -1474,6 +1588,9 @@
getList();
getProcessList();
fetchBomData();
+ if (pageType.value === "order") {
+ getAttachmentList();
+ }
};
onMounted(() => {
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index a9f61aa..100b94c 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -724,6 +724,7 @@
bomNo: row.bomNo || "",
description: data.description || "",
quantity: row.quantity || 0,
+ technologyRoutingId: data.technologyRoutingId,
orderId,
type: "order",
editable: !row.endOrder,
--
Gitblit v1.9.3