src/components/AIChatSidebar/assistants/index.js
@@ -1,6 +1,13 @@ import { generalAssistant } from './generalAssistant' import { purchaseAssistant } from './purchaseAssistant' import { productionAssistant } from './productionAssistant' export { generalAssistant, purchaseAssistant } export { generalAssistant, purchaseAssistant, productionAssistant } export const builtInAssistants = [generalAssistant, purchaseAssistant] export const assistantRegistry = { general: generalAssistant, purchase: purchaseAssistant, production: productionAssistant } export const builtInAssistants = [generalAssistant, purchaseAssistant, productionAssistant] src/components/AIChatSidebar/assistants/productionAssistant.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,27 @@ import { Operation } from '@element-plus/icons-vue' export const productionAssistant = { key: 'production', label: 'ç产å©ç', title: 'ç产æºè½å©ç', tooltip: 'ç产æºè½å©æ', icon: Operation, apiBase: '/manufacturing-ai', storageKey: 'production_ai_chat_uuid', placeholder: '请è¾å ¥ç产ç¸å ³é®é¢... (Enter åé, Shift+Enter æ¢è¡)', welcomeMessage: 'ä½ å¥½', description: 'æå¯ä»¥å´ç»ç产ç°åºã计åãå·¥åã设å¤ãè´¨éãç©æãå¼å¸¸å¤çæä¾æ¥è¯¢ãé¢è¦ãåæååç建议ã', allowFileUpload: false, emptySessionText: 'ææ ç产ä¼è¯', quickPrompts: [ 'æ¥è¯¢æ¬æç产计å', 'æ¥çæè¿10æ¡å·¥å', 'æ¥è®¾å¤A-01çç»´ä¿®æ åµ', 'æ¥è´¨éä¸åæ ¼è®°å½', 'æ¥ä½åºåç©æ', 'æ¥è¿7天å¼å¸¸å¤ç', 'çæå¶é é¢è¦çæ¿', 'åææ¬æçäº§å®æçåå¼å¸¸ç', 'ç»åºå·¥å龿å设å¤å¾ ä¿®çåç建议' ] } src/components/AIChatSidebar/index.vue
@@ -242,7 +242,123 @@ </el-table> </div> <!-- æåä¸å¨ç» --> <div v-if="message.manufacturingData" class="manufacturing-card"> <div class="manufacturing-card__title">{{ getManufacturingTypeLabel(message.type) }}</div> <div v-if="message.manufacturingData.summaryEntries?.length || message.manufacturingData.coreMetrics?.length" class="manufacturing-summary-grid" > <div v-for="(entry, entryIndex) in message.manufacturingData.summaryEntries" :key="`summary-${entry.key}-${entryIndex}`" class="manufacturing-summary-item" > <span class="manufacturing-summary-label">{{ entry.label }}</span> <strong class="manufacturing-summary-value">{{ entry.value }}</strong> </div> <div v-for="(metric, metricIndex) in message.manufacturingData.coreMetrics" :key="`core-${metric.key}-${metricIndex}`" class="manufacturing-summary-item manufacturing-summary-item--core" > <span class="manufacturing-summary-label">{{ metric.label }}</span> <strong class="manufacturing-summary-value">{{ metric.value }}</strong> </div> </div> <div v-if="message.manufacturingData.warningItems?.length" class="manufacturing-warning-list"> <div v-for="(warning, warningIndex) in message.manufacturingData.warningItems" :key="`warning-${warning.title || warningIndex}`" class="manufacturing-warning-item" > <div class="manufacturing-warning-item__head"> <el-tag size="small" :type="getManufacturingWarningLevelType(warning.level)"> {{ getManufacturingWarningLevelLabel(warning.level) }} </el-tag> <strong>{{ warning.title || `é¢è¦ ${warningIndex + 1}` }}</strong> <span v-if="warning.count !== '' && warning.count !== null && warning.count !== undefined" class="manufacturing-warning-count"> {{ warning.count }} </span> </div> <p v-if="warning.detail" class="manufacturing-warning-detail">{{ warning.detail }}</p> </div> </div> <div v-if="message.manufacturingData.listItems?.length && message.manufacturingData.columns?.length" class="table-wrapper manufacturing-table-wrapper" > <el-table :data="message.manufacturingData.listItems" border stripe size="small" style="width: 100%"> <el-table-column v-for="col in message.manufacturingData.columns" :key="col" :label="getStructuredFieldLabel(col)" min-width="140" show-overflow-tooltip > <template #default="{ row }"> {{ formatStructuredValue(row[col]) }} </template> </el-table-column> </el-table> </div> <div v-if="message.manufacturingData.actionCards?.length" class="manufacturing-action-list"> <div v-for="(card, cardIndex) in message.manufacturingData.actionCards" :key="card.runtimeKey || `${card.code}-${card.targetApi}-${cardIndex}`" class="manufacturing-action-card" > <div class="manufacturing-action-card__head"> <strong>{{ card.name || `å¨ä½ ${cardIndex + 1}` }}</strong> <el-tag size="small" type="info">{{ getNormalizedRequestMethod(card.method) }}</el-tag> </div> <div class="manufacturing-action-card__meta"> <span>{{ card.code || '--' }}</span> <span>{{ card.targetApi || '--' }}</span> </div> <p v-if="card.description" class="manufacturing-action-card__desc">{{ card.description }}</p> <div v-if="card.requiredFields?.length" class="manufacturing-required-fields"> <span>å¿ å¡«åæ®µ</span> <el-tag v-for="field in card.requiredFields" :key="field" size="small" type="warning" > {{ getStructuredPathLabel(field) }} </el-tag> </div> <el-input v-model="card.payloadText" type="textarea" :rows="6" resize="vertical" :disabled="card.executing" placeholder="请è¾å ¥ JSON 请æ±åæ°" /> <div class="manufacturing-action-footer"> <span v-if="card.executeResult" :class="['manufacturing-action-result', card.executeError ? 'error' : 'success']" > {{ card.executeResult }} </span> <el-button type="primary" size="small" :loading="card.executing" @click="executeManufacturingAction(message, card, cardIndex)" > 确认并æ§è¡ </el-button> </div> </div> </div> </div> <div v-if="message.purchaseAnalysisData" class="purchase-confirm-card"> <div class="purchase-confirm-header"> <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || 'éè´ä¸å¡' }}</span> @@ -523,7 +639,7 @@ import request from '@/utils/request' import * as echarts from 'echarts' import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, Promotion, RefreshRight } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus' import { builtInAssistants, generalAssistant } from './assistants' import todoAssistantAvatar from '@/assets/AI/å¾ å婿.png' import salesAssistantAvatar from '@/assets/AI/éå®å©æ.png' @@ -633,6 +749,75 @@ payment_registration: '仿¬¾ç»è®°', purchase_return_order: 'éè´éè´§å', unknown: 'æªç¥éè´ä¸å¡' } const manufacturingStructuredTypeSet = new Set([ 'manufacturing_site_snapshot', 'manufacturing_plan_list', 'manufacturing_workorder_list', 'manufacturing_device_list', 'manufacturing_device_repair_list', 'manufacturing_quality_list', 'manufacturing_material_list', 'manufacturing_exception_list', 'manufacturing_warning', 'manufacturing_analysis', 'manufacturing_action_plan' ]) const manufacturingListTypeSet = new Set([ 'manufacturing_plan_list', 'manufacturing_workorder_list', 'manufacturing_device_list', 'manufacturing_device_repair_list', 'manufacturing_quality_list', 'manufacturing_material_list', 'manufacturing_exception_list' ]) const manufacturingTypeLabelMap = { manufacturing_site_snapshot: 'ç产ç°åºæ¦è§', manufacturing_plan_list: 'è®¡åæ¥è¯¢', manufacturing_workorder_list: 'å·¥åæ¥è¯¢', manufacturing_device_list: 'è®¾å¤æ¥è¯¢', manufacturing_device_repair_list: '设å¤ç»´ä¿®è®°å½æ¥è¯¢', manufacturing_quality_list: 'è´¨éæ¥è¯¢', manufacturing_material_list: 'ç©ææ¥è¯¢', manufacturing_exception_list: 'å¼å¸¸æ¥è¯¢', manufacturing_warning: 'é¢è¦çæ¿', manufacturing_analysis: 'ç»è¥åæ', manufacturing_action_plan: 'åç建议' } const structuredFieldLabelMap = { workOrderNo: 'å·¥åå·', planEndTime: '计åç»ææ¶é´', planStartTime: '计åå¼å§æ¶é´', timeRange: 'æ¶é´èå´', startDate: 'å¼å§æ¥æ', endDate: 'ç»ææ¥æ', warningCount: 'é¢è¦æ°é', overduePlanCount: 'é¾æè®¡åæ°', overdueWorkOrderCount: '龿工忰', actionCount: '建议å¨ä½æ°', qualityOpenCount: 'è´¨éå¾ å¤çæ°', lowStockCount: 'ä½åºåæ°', exceptionCount: 'å¼å¸¸æ°', userId: 'ç¨æ·ID', tenantId: 'ç§æ·ID', status: 'ç¶æ', deviceName: '设å¤åç§°', deviceModel: '设å¤åå·', pendingRepairCount: 'å¾ ç»´ä¿®æ°', repairTime: 'ç»´ä¿®æ¶é´', repairName: 'æ¥ä¿®äºº', maintenanceName: '维修人å', level: 'é¢è¦ç级', title: 'æ é¢', count: 'æ°é', detail: '详æ ', remark: '夿³¨', createTime: 'å建æ¶é´', updateTime: 'æ´æ°æ¶é´', exceptionType: 'å¼å¸¸ç±»å', materialName: 'ç©æåç§°', stockQty: 'åºåé' } const purchasePayloadFieldLabelMap = { purchaseLedgers: 'éè´å°è´¦', @@ -785,6 +970,366 @@ inventoryWarningQuantity: 'inventoryWarningQuantity', isInspected: 'isInspected', isChecked: 'isInspected' } const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) const stringifyStructuredPayload = (value, spaces = 2) => { if (typeof value === 'string') return value try { return JSON.stringify(value ?? {}, null, spaces) } catch (err) { return '{}' } } const structuredFieldTokenLabelMap = { time: 'æ¶é´', range: 'èå´', start: 'å¼å§', end: 'ç»æ', date: 'æ¥æ', warning: 'é¢è¦', overdue: '龿', plan: '计å', work: 'å·¥', order: 'å', workorder: 'å·¥å', count: 'æ°é', quality: 'è´¨é', low: 'ä½', stock: 'åºå', exception: 'å¼å¸¸', action: 'å¨ä½', user: 'ç¨æ·', tenant: 'ç§æ·', id: 'ID', no: 'ç¼å·', number: 'ç¼å·', code: 'ç¼ç ', name: 'åç§°', status: 'ç¶æ', level: 'ç级', title: 'æ é¢', detail: '详æ ', total: 'æ»æ°', rate: 'æ¯ç', type: 'ç±»å', pending: 'å¾ ', repair: 'ç»´ä¿®', device: '设å¤', material: 'ç©æ' } const convertStructuredFieldKeyToChinese = (fieldKey = '') => { const key = String(fieldKey || '').trim() if (!key) return '-' if (structuredFieldLabelMap[key]) return structuredFieldLabelMap[key] if (/[\u4e00-\u9fa5]/.test(key)) return key const rawTokens = key .replace(/([a-z0-9])([A-Z])/g, '$1 $2') .replace(/[_-]+/g, ' ') .trim() .split(/\s+/) .filter(Boolean) .map(token => token.toLowerCase()) if (!rawTokens.length) return 'åæ®µ' const mappedTokens = rawTokens .map(token => structuredFieldTokenLabelMap[token] || '') .filter(Boolean) if (mappedTokens.length) { return mappedTokens.join('') } return 'åæ®µ' } const getStructuredFieldLabel = (fieldKey = '') => { return convertStructuredFieldKeyToChinese(fieldKey) } const getStructuredPathLabel = (fieldPath = '') => { const path = String(fieldPath || '').trim() if (!path) return '-' const segments = path .replace(/\[(\d+)]/g, '.$1') .split('.') .filter(Boolean) if (!segments.length) return getStructuredFieldLabel(path) return segments.map((segment) => { if (/^\d+$/.test(segment)) { return `第${Number(segment) + 1}项` } return getStructuredFieldLabel(segment) }).join(' / ') } const formatStructuredValue = (value) => { if (value === null || value === undefined || value === '') return '-' if (Array.isArray(value)) { const preview = value.slice(0, 3).map(item => formatStructuredValue(item)).join('ã') return value.length > 3 ? `${preview} ç${value.length}项` : preview } if (isPlainObject(value)) return stringifyStructuredPayload(value, 0) return String(value) } const normalizeManufacturingSummaryEntries = (summary) => { if (!isPlainObject(summary)) return [] return Object.entries(summary) .filter(([, value]) => value !== undefined && value !== null && `${value}`.trim() !== '') .map(([key, value]) => ({ key, label: getStructuredFieldLabel(key), value: formatStructuredValue(value) })) } const normalizeManufacturingCoreMetrics = (coreMetrics) => { if (Array.isArray(coreMetrics)) { return coreMetrics.map((item, index) => { if (isPlainObject(item)) { const label = item.label || item.name || item.key || `ææ ${index + 1}` const metricValue = item.value ?? item.metricValue ?? item.data ?? '-' const unit = item.unit ? ` ${item.unit}` : '' return { key: String(item.key || item.name || index), label, value: `${formatStructuredValue(metricValue)}${unit}`.trim() } } return { key: String(index), label: `ææ ${index + 1}`, value: formatStructuredValue(item) } }) } if (isPlainObject(coreMetrics)) { return Object.entries(coreMetrics).map(([key, value]) => ({ key, label: getStructuredFieldLabel(key), value: formatStructuredValue(value) })) } return [] } const normalizeManufacturingWarningItems = (items = []) => { if (!Array.isArray(items)) return [] return items .filter(item => isPlainObject(item)) .map(item => ({ level: String(item.level || '').toLowerCase(), title: item.title || '', count: item.count ?? '', detail: item.detail ?? '' })) } const inferManufacturingColumns = (items = []) => { if (!Array.isArray(items) || !items.length) return [] const fieldSet = new Set() items.forEach((item) => { if (!isPlainObject(item)) return Object.keys(item).forEach((key) => fieldSet.add(key)) }) return Array.from(fieldSet) } const getManufacturingActionCardRuntimeKey = (card = {}, index = 0) => { const code = String(card?.code || '').trim() const api = String(card?.targetApi || '').trim() const name = String(card?.name || '').trim() return `${code}::${api}::${name}::${index}` } const normalizeManufacturingActionCards = (actionCards = [], previousCards = []) => { const previousMap = new Map() if (Array.isArray(previousCards)) { previousCards.forEach((card, index) => { previousMap.set(getManufacturingActionCardRuntimeKey(card, index), card) }) } return (Array.isArray(actionCards) ? actionCards : []) .filter(card => isPlainObject(card)) .map((card, index) => { const runtimeKey = getManufacturingActionCardRuntimeKey(card, index) const previousCard = previousMap.get(runtimeKey) const fallbackPayloadText = stringifyStructuredPayload(card.examplePayload, 2) return { ...card, runtimeKey, payloadText: previousCard?.payloadText ?? fallbackPayloadText, executing: Boolean(previousCard?.executing), executed: Boolean(previousCard?.executed), executeResult: previousCard?.executeResult || '', executeError: Boolean(previousCard?.executeError) } }) } const buildManufacturingStructuredData = (parsedData, previousData = null) => { const type = String(parsedData?.type || '') if (!manufacturingStructuredTypeSet.has(type)) return null const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {} const items = Array.isArray(rawData.items) ? rawData.items.filter(item => isPlainObject(item)) : [] const warningItems = type === 'manufacturing_warning' ? normalizeManufacturingWarningItems(items) : [] const listItems = manufacturingListTypeSet.has(type) ? items : [] const actionCards = type === 'manufacturing_action_plan' ? normalizeManufacturingActionCards(rawData.actionCards, previousData?.actionCards) : [] return { type, summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary), coreMetrics: normalizeManufacturingCoreMetrics(rawData.coreMetrics), listItems, columns: inferManufacturingColumns(listItems), warningItems, actionCards } } const getManufacturingTypeLabel = (type = '') => manufacturingTypeLabelMap[String(type || '')] || 'å¶é ç»æ' const getManufacturingWarningLevelType = (level = '') => { const normalizedLevel = String(level || '').toLowerCase() if (normalizedLevel === 'high') return 'danger' if (normalizedLevel === 'medium') return 'warning' return 'info' } const getManufacturingWarningLevelLabel = (level = '') => { const normalizedLevel = String(level || '').toLowerCase() if (normalizedLevel === 'high') return 'é«' if (normalizedLevel === 'medium') return 'ä¸' return normalizedLevel ? normalizedLevel.toUpperCase() : 'ä¸è¬' } const normalizeRequestMethod = (method = 'POST') => { const normalized = String(method || 'POST').trim().toUpperCase() if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(normalized)) return normalized return 'POST' } const getNormalizedRequestMethod = (method) => normalizeRequestMethod(method) const getPayloadValueByPath = (payload, fieldPath = '') => { const normalizedPath = String(fieldPath || '').trim() if (!normalizedPath || !isPlainObject(payload)) return undefined const pathSegments = normalizedPath .replace(/\[(\d+)]/g, '.$1') .split('.') .filter(Boolean) return pathSegments.reduce((current, segment) => { if (current === null || current === undefined) return undefined if (!['object', 'function'].includes(typeof current)) return undefined return current[segment] }, payload) } const getMissingRequiredFields = (requiredFields = [], payload = {}) => { if (!Array.isArray(requiredFields) || !requiredFields.length) return [] return requiredFields.filter((fieldPath) => { const value = getPayloadValueByPath(payload, fieldPath) return !hasMeaningfulPayloadValue(value) }) } const parseManufacturingActionPayload = (payloadText = '') => { const text = String(payloadText ?? '').trim() if (!text) return {} return JSON.parse(text) } const executeManufacturingAction = async (message, actionCard, cardIndex = 0) => { if (!message?.manufacturingData || !actionCard || actionCard.executing) return const actionName = actionCard.name || `å¨ä½ ${cardIndex + 1}` const targetApi = String(actionCard.targetApi || '').trim() if (!targetApi) { actionCard.executeError = true actionCard.executeResult = 'ç¼ºå° targetApiï¼æ æ³æ§è¡å¨ä½' return } let payload = {} try { payload = parseManufacturingActionPayload(actionCard.payloadText) } catch (err) { actionCard.executeError = true actionCard.executeResult = '请æ±åæ°ä¸æ¯åæ³ JSONï¼è¯·æ£æ¥åéè¯' return } const requiredFields = Array.isArray(actionCard.requiredFields) ? actionCard.requiredFields : [] if (requiredFields.length && !isPlainObject(payload)) { actionCard.executeError = true actionCard.executeResult = 'å¿ å¡«åæ®µæ ¡éªå¤±è´¥ï¼è¯·æ±åæ°å¿ é¡»æ¯ JSON 对象' return } const missingFields = getMissingRequiredFields(requiredFields, payload) if (missingFields.length) { actionCard.executeError = true const missingFieldLabels = missingFields.map(field => getStructuredPathLabel(field)) actionCard.executeResult = `缺å°å¿ å¡«åæ®µï¼${missingFieldLabels.join('ã')}` return } try { await ElMessageBox.confirm(`确认æ§è¡ã${actionName}ãåï¼`, 'æ§è¡ç¡®è®¤', { confirmButtonText: '确认æ§è¡', cancelButtonText: 'åæ¶', type: 'warning' }) } catch (err) { return } actionCard.executing = true actionCard.executeError = false actionCard.executeResult = '' const method = normalizeRequestMethod(actionCard.method) const requestConfig = { url: targetApi, method: method.toLowerCase() } if (method === 'GET') { requestConfig.params = payload } else { requestConfig.data = payload } try { const res = await request(requestConfig) const successMsg = res?.msg || `${actionName}æ§è¡æå` actionCard.executed = true actionCard.executeError = false actionCard.executeResult = successMsg ElMessage.success(successMsg) } catch (err) { actionCard.executed = false actionCard.executeError = true actionCard.executeResult = err?.message || `${actionName}æ§è¡å¤±è´¥ï¼è¯·ç¨åéè¯` } finally { actionCard.executing = false } } // åå²ä¼è¯ç¸å ³ @@ -1034,6 +1579,8 @@ tableData: null, payloadTreeData: null, payloadHiddenData: null, purchaseAnalysisData: null, manufacturingData: null, localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : [] } @@ -1273,15 +1820,25 @@ const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => { if (!messageObj || !parsedData?.success) return const previousManufacturingData = messageObj.manufacturingData messageObj.type = parsedData.type || '' messageObj.tableData = null messageObj.purchaseAnalysisData = null messageObj.manufacturingData = null if (messageObj.type === 'todo_list' && parsedData.data) { messageObj.tableData = parsedData.data } const manufacturingData = buildManufacturingStructuredData(parsedData, previousManufacturingData) if (manufacturingData) { messageObj.manufacturingData = manufacturingData } if (parsedData.action === 'confirm_required' && parsedData.businessType) { messageObj.type = 'purchase_analysis_confirm' messageObj.purchaseAnalysisData = parsedData messageObj.manufacturingData = null if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) { initializePurchasePayloadTree(messageObj, parsedData.payload || {}) } @@ -1321,6 +1878,19 @@ } return null } const getStructuredFallbackText = (parsedData) => { if (!parsedData) return 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' if (parsedData.type === 'todo_list') return 'å·²ä¸ºæ¨æ´ç好ç¸å ³æ°æ®ã' if (manufacturingStructuredTypeSet.has(parsedData.type)) { if (parsedData.type === 'manufacturing_action_plan') return '已为æ¨çæåç建议ï¼è¯·ç¡®è®¤å¨ä½åæ§è¡ã' if (parsedData.type === 'manufacturing_warning') return '已为æ¨çæå¶é é¢è¦çæ¿ã' if (parsedData.type === 'manufacturing_analysis') return '已为æ¨çæå¶é åæç»æã' return 'å·²è¿åå¶é æ¥è¯¢ç»æã' } if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '已为æ¨çæåæå¾è¡¨ã' return 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' } const buildPurchaseMaterialRankCharts = (parsedData) => { @@ -2446,7 +3016,9 @@ type: '', tableData: null, payloadTreeData: null, payloadHiddenData: null payloadHiddenData: null, purchaseAnalysisData: null, manufacturingData: null }) outputState.value[botMsgIndex] = { @@ -2569,7 +3141,9 @@ type: '', tableData: null, payloadTreeData: null, payloadHiddenData: null payloadHiddenData: null, purchaseAnalysisData: null, manufacturingData: null } messages.value.push(botMsg) @@ -2735,13 +3309,7 @@ } if (!display) { if (parsed.type === 'todo_list') { display = 'å·²ä¸ºæ¨æ´ç好ç¸å ³æ°æ®ã' } else if (parsed.charts && Object.keys(parsed.charts).length > 0) { display = '已为æ¨çæåæå¾è¡¨ã' } else { display = 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' } display = getStructuredFallbackText(parsed) } } else if (startIdx !== -1) { const lastBraceIdx = output.lastIndexOf('}') @@ -2763,13 +3331,7 @@ } if (!display) { if (parsed.type === 'todo_list') { display = 'å·²ä¸ºæ¨æ´ç好ç¸å ³æ°æ®ï¼' } else if (parsed.charts && Object.keys(parsed.charts).length > 0) { display = '已为æ¨çæåæå¾è¡¨ï¼' } else { display = 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' } display = getStructuredFallbackText(parsed) } } catch (e) { // è§£æå¤±è´¥ï¼è¯´æ JSON è¿å¨ä¼ è¾ä¸ææ ¼å¼ä¸æ£ç¡® @@ -3792,6 +4354,171 @@ } } .manufacturing-card { margin-top: 12px; width: 100%; background: #fff; border: 1px solid rgba(0, 85, 212, 0.12); border-radius: 12px; box-shadow: $shadow-card; padding: 14px; } .manufacturing-card__title { font-size: 14px; font-weight: 700; color: $deep-blue; margin-bottom: 10px; } .manufacturing-summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 8px; margin-bottom: 12px; } .manufacturing-summary-item { border-radius: 10px; padding: 10px 12px; border: 1px solid rgba(0, 85, 212, 0.08); background: linear-gradient(180deg, #f8fbff, #f1f7ff); min-height: 66px; display: flex; flex-direction: column; justify-content: space-between; gap: 6px; } .manufacturing-summary-item--core { border-color: rgba(30, 91, 255, 0.24); } .manufacturing-summary-label { font-size: 12px; color: #4b5563; } .manufacturing-summary-value { font-size: 15px; color: #1f2937; line-height: 1.4; word-break: break-all; } .manufacturing-warning-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; } .manufacturing-warning-item { border-radius: 10px; border: 1px solid rgba(245, 158, 11, 0.22); background: linear-gradient(135deg, rgba(255, 247, 237, 0.9), rgba(255, 255, 255, 0.98)); padding: 10px 12px; } .manufacturing-warning-item__head { display: flex; align-items: center; gap: 8px; color: #92400e; font-size: 13px; } .manufacturing-warning-count { margin-left: auto; font-weight: 700; color: #c2410c; } .manufacturing-warning-detail { margin: 8px 0 0; font-size: 12px; line-height: 1.6; color: #7c2d12; word-break: break-all; } .manufacturing-table-wrapper { margin-top: 10px; } .manufacturing-action-list { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; } .manufacturing-action-card { border: 1px solid rgba(0, 85, 212, 0.1); border-radius: 10px; padding: 10px 12px; background: #f8fbff; } .manufacturing-action-card__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 13px; color: #1f2937; margin-bottom: 8px; } .manufacturing-action-card__meta { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; font-size: 12px; color: #64748b; word-break: break-all; } .manufacturing-action-card__desc { margin: 0 0 8px; font-size: 12px; line-height: 1.6; color: #475467; } .manufacturing-required-fields { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 12px; color: #7c2d12; } .manufacturing-action-footer { margin-top: 8px; display: flex; align-items: center; justify-content: flex-end; gap: 12px; } .manufacturing-action-result { flex: 1; font-size: 12px; line-height: 1.5; &.success { color: #1f9d55; } &.error { color: #d93025; } } .purchase-confirm-card { margin-top: 12px; width: 100%; src/views/aiIndustrialBrain/MAINTAIN_RULES.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,7 @@ # AIå·¥ä¸å¤§èç»´æ¤è§å 1. å½ `src/views/aiIndustrialBrain/index.vue` æ°å¢æºè½ä½ï¼`agents`ï¼é»è¾æ¶ï¼å¿ é¡»åæ¥ç¡®è®¤å¼¹çªå©æå¯ç¨æ§ã 2. å¼¹çªå©æç± `src/components/AIChatSidebar/assistants/index.js` ç `assistantRegistry` ç»ä¸æ³¨åã 3. æ°å¢æºè½ä½ç `key` è¥è¦å¨å¼¹çªä¸å¯ç¨ï¼å¿ é¡»å¨ `assistantRegistry` 䏿ä¾ååé ç½®ã 4. æªå¨ `assistantRegistry` 注åçæºè½ä½ä¼å¨å¼¹çªä¸æ¾ç¤ºä¸º `pending`ï¼å¼åä¸ï¼æã src/views/aiIndustrialBrain/components/AiAssistantWorkspace.vue
@@ -17,7 +17,7 @@ v-if="assistantMode !== 'pending'" :key="assistantMode" class="workspace-chat" :assistants="assistantMode === 'purchase' ? [purchaseAssistant] : [generalAssistant]" :assistants="resolvedAssistants" :default-assistant="assistantMode" :hide-trigger="true" :auto-open="true" @@ -43,7 +43,7 @@ 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"; import { assistantRegistry } from "@/components/AIChatSidebar/assistants"; const props = defineProps({ visible: { @@ -60,11 +60,17 @@ const agentKey = computed(() => String(props.agent?.key || "")); const agentTitle = computed(() => String(props.agent?.name || "AI婿")); /** * ç»´æ¤è§åï¼ * AIå·¥ä¸å¤§èæ°å¢æºè½ä½æ¶ï¼è¥å¸æå³ä¾§å¼¹çªå¯ç¨ï¼éä¿è¯æºè½ä½ key å¨ assistantRegistry 䏿ååé ç½®ã * æªé ç½®æ¶ä¼è¿å ¥ pendingï¼å¼åä¸ï¼æï¼ä½ä¸ºæ¾å¼æéã */ const resolvedAssistant = computed(() => assistantRegistry[agentKey.value] || null); const assistantMode = computed(() => { if (agentKey.value === "purchase") return "purchase"; if (agentKey.value === "general") return "general"; return "pending"; return resolvedAssistant.value ? agentKey.value : "pending"; }); const resolvedAssistants = computed(() => (resolvedAssistant.value ? [resolvedAssistant.value] : [])); </script> <style scoped> src/views/aiIndustrialBrain/index.vue
@@ -154,6 +154,7 @@ const router = useRouter(); // ç»´æ¤çº¦å®è§ï¼src/views/aiIndustrialBrain/MAINTAIN_RULES.md const agents = [ { key: "general", src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -315,7 +315,7 @@ :step="1" controls-position="right" style="width: 100%" @change="handleUnitQuantityChange(row)" @change="handleUnitQuantityChange" :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" /> </el-form-item> </template> @@ -333,7 +333,7 @@ :step="1" controls-position="right" style="width: 100%" :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" /> :disabled="true" /> </el-form-item> </template> </el-table-column> @@ -1089,6 +1089,53 @@ } }); }; const toQuantityNumber = value => { const numberValue = Number(value); if (!Number.isFinite(numberValue)) { return 0; } return Number(numberValue.toFixed(2)); }; const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => { items.forEach(item => { if (parentDemandedQuantity !== null) { item.demandedQuantity = toQuantityNumber( parentDemandedQuantity * toQuantityNumber(item.unitQuantity) ); } if (Array.isArray(item.children) && item.children.length > 0) { syncDemandedQuantityTree( item.children, toQuantityNumber(item.demandedQuantity) ); } }); }; const recalculateDemandedQuantities = () => { if (pageType.value !== "order") { return; } const rootDemandedQuantity = routeInfo.value.quantity; if ( rootDemandedQuantity === undefined || rootDemandedQuantity === null || rootDemandedQuantity === "" ) { syncDemandedQuantityTree(bomDataValue.value.dataList); return; } syncDemandedQuantityTree( bomDataValue.value.dataList, toQuantityNumber(rootDemandedQuantity) ); }; const processChange = value => { processOptions.value.forEach(item => { if (item.id == value) { @@ -1117,6 +1164,7 @@ ); bomDataValue.value.dataList = data || []; normalizeTreeData(bomDataValue.value.dataList); recalculateDemandedQuantities(); } catch (err) { console.error("è·åBOMæ°æ®å¤±è´¥ï¼", err); } @@ -1212,10 +1260,8 @@ }); }; const handleUnitQuantityChange = row => { if (routeInfo.value.quantity && routeInfo.value.quantity !== 0) { row.demandedQuantity = (row.unitQuantity || 0) * routeInfo.value.quantity; } const handleUnitQuantityChange = () => { recalculateDemandedQuantities(); }; const addchildItem = (item, tempId) => { @@ -1236,14 +1282,12 @@ "", operationName: "", unitQuantity: 1, demandedQuantity: routeInfo.value.quantity && routeInfo.value.quantity !== 0 ? 1 * routeInfo.value.quantity : 0, demandedQuantity: 0, children: [], unit: "", tempId: new Date().getTime(), }); recalculateDemandedQuantities(); return true; } if (item.children && item.children.length > 0) { @@ -1275,14 +1319,12 @@ "", operationName: "", unitQuantity: 1, demandedQuantity: routeInfo.value.quantity && routeInfo.value.quantity !== 0 ? 1 * routeInfo.value.quantity : 0, demandedQuantity: 0, unit: "", children: [], tempId: new Date().getTime(), }); recalculateDemandedQuantities(); return; } addchildItem(item, tempId); @@ -1350,6 +1392,7 @@ console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList"); normalizeTreeData(bomDataValue.value.dataList); recalculateDemandedQuantities(); const valid = validateAllBom(); if (valid) { @@ -1361,7 +1404,7 @@ .then(() => { ElMessage.success("BOMä¿åæå"); bomDataValue.value.isEdit = false; fetchBomData(); refreshCurrentPage(); }) .catch(() => { ElMessage.error("BOMä¿å失败"); @@ -1374,11 +1417,15 @@ } }; onMounted(() => { const refreshCurrentPage = () => { getRouteInfo(); getList(); getProcessList(); fetchBomData(); }; onMounted(() => { refreshCurrentPage(); }); onUnmounted(() => { src/views/productionManagement/productStructure/Detail/index.vue
@@ -86,6 +86,7 @@ :step="1" controls-position="right" style="width: 100%" @change="handleUnitQuantityChange" :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> </el-form-item> </template> @@ -103,7 +104,7 @@ :step="1" controls-position="right" style="width: 100%" :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> :disabled="true" /> </el-form-item> </template> </el-table-column> @@ -268,6 +269,42 @@ }); }; const toQuantityNumber = (value: any) => { const numberValue = Number(value); if (!Number.isFinite(numberValue)) { return 0; } return Number(numberValue.toFixed(2)); }; const syncDemandedQuantityTree = ( items: any[], parentDemandedQuantity: number | null = null ) => { items.forEach((item: any) => { if (parentDemandedQuantity !== null) { item.demandedQuantity = toQuantityNumber( parentDemandedQuantity * toQuantityNumber(item.unitQuantity) ); } if (Array.isArray(item.children) && item.children.length > 0) { syncDemandedQuantityTree( item.children, toQuantityNumber(item.demandedQuantity) ); } }); }; const recalculateDemandedQuantities = () => { if (!isOrderPage.value) { return; } syncDemandedQuantityTree(dataValue.dataList); }; const buildSubmitTree = (items: any[]) => { return items.map((item: any) => { const current = { ...item }; @@ -282,6 +319,10 @@ const handleProcessChange = (row: any, value: any) => { row.processId = value || ""; syncProcessOperationFields(row); }; const handleUnitQuantityChange = () => { recalculateDemandedQuantities(); }; const tableData = reactive([ @@ -304,6 +345,7 @@ const { data } = await listProcessBom({ orderId: routeOrderId.value }); dataValue.dataList = (data as any) || []; normalizeTreeData(dataValue.dataList); recalculateDemandedQuantities(); } else { // éè®¢åæ åµï¼ä½¿ç¨åæ¥çæ¥å£ const { data } = await queryList(routeId.value); @@ -437,6 +479,7 @@ const submit = () => { dataValue.loading = true; normalizeTreeData(dataValue.dataList); recalculateDemandedQuantities(); // å è¿è¡è¡¨åæ ¡éª const valid = validateAll(); @@ -514,6 +557,7 @@ tempId: new Date().getTime(), }); recalculateDemandedQuantities(); return; } addchildItem(item, tempId); @@ -542,6 +586,7 @@ unit: "", tempId: new Date().getTime(), }); recalculateDemandedQuantities(); return true; } if (item.children && item.children.length > 0) { @@ -587,4 +632,4 @@ await fetchProcessOptions(); await fetchData(); }); </script> </script> src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -31,6 +31,8 @@ </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="ææ éæ©ï¼" prop="testStandardId"> <el-select @@ -58,7 +60,35 @@ </el-col> <el-col :span="12"> <el-form-item label="æ°éï¼" prop="quantity"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请è¾å ¥" clearable :precision="2" :disabled="quantityDisabled"/> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请è¾å ¥" clearable :precision="2" :disabled="processQuantityDisabled"/> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="åæ ¼æ°éï¼" prop="qualifiedQuantity"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请è¾å ¥" clearable :precision="2" @change="handleQualifiedQuantityChange" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸åæ ¼æ°éï¼" prop="unqualifiedQuantity"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请è¾å ¥" clearable :precision="2" @change="handleUnqualifiedQuantityChange" /> </el-form-item> </el-col> </el-row> @@ -73,6 +103,7 @@ <el-select v-model="form.checkResult"> <el-option label="åæ ¼" value="åæ ¼" /> <el-option label="ä¸åæ ¼" value="ä¸åæ ¼" /> <el-option label="é¨ååæ ¼" value="é¨ååæ ¼" /> </el-select> </el-form-item> </el-col> @@ -80,10 +111,10 @@ <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="æ£éªåï¼" prop="checkName"> <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> </el-select> <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> </el-select> </el-form-item> </el-col> <el-col :span="12"> @@ -134,7 +165,7 @@ const emit = defineEmits(['close']) const dialogFormVisible = ref(false); const operationType = ref('') const operationType = ref(""); const data = reactive({ form: { checkTime: "", @@ -147,25 +178,29 @@ testStandardId: "", unit: "", quantity: "", qualifiedQuantity: "", unqualifiedQuantity: "", checkCompany: "", checkResult: "", }, rules: { checkTime: [{ required: true, message: "请è¾å ¥", trigger: "blur" },], checkTime: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], process: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], checkName: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], productId: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], testStandardId: [{required: false, message: "è¯·éæ©ææ ", trigger: "change"}], testStandardId: [{ required: false, message: "è¯·éæ©ææ ", trigger: "change" }], unit: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], quantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], qualifiedQuantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], unqualifiedQuantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], checkCompany: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], checkResult: [{ required: true, message: "请è¾å ¥", trigger: "change" }], }, }); const { form, rules } = toRefs(data); // ç¼è¾æ¶ï¼productMainId æ purchaseLedgerId 任䏿å¼åæ°éç½®ç° const quantityDisabled = computed(() => { // ç¼è¾æ¶ï¼productMainId æ purchaseLedgerId 任䏿å¼åå·¥åºãæ°éç½®ç° const processQuantityDisabled = computed(() => { const v = form.value || {}; return !!(v.productMainId != null || v.purchaseLedgerId != null); }); @@ -209,7 +244,7 @@ // å æ¸ 空表åéªè¯ç¶æï¼é¿å éªç await nextTick(); proxy.$refs.formRef?.clearValidate(); // å¹¶è¡å è½½åºç¡æ°æ® const [userListsRes] = await Promise.all([ userListNoPage(), @@ -219,11 +254,11 @@ }) ]); userList.value = userListsRes.data; form.value = {} testStandardOptions.value = []; tableData.value = []; if (operationType.value === 'edit') { // å ä¿å testStandardIdï¼é¿å è¢«æ¸ ç©º const savedTestStandardId = row.testStandardId; @@ -234,18 +269,18 @@ nextTick(() => { proxy.$refs.formRef?.clearValidate(); }); // ç¼è¾æ¨¡å¼ä¸ï¼å¹¶è¡å è½½è§æ ¼åå·åææ é项 if (currentProductId.value) { // 设置产ååç§° form.value.productName = findNodeById(productOptions.value, currentProductId.value); // å¹¶è¡å è½½è§æ ¼åå·åææ é项 const params = { productId: currentProductId.value, inspectType: 2 }; Promise.all([ modelList({ id: currentProductId.value }), qualityInspectDetailByProductId(params) @@ -260,15 +295,15 @@ form.value.unit = selectedModel.unit || ''; } } // è®¾ç½®ææ é项 testStandardOptions.value = testStandardRes.data || []; // 设置 testStandardId å¹¶å è½½åæ°å表 nextTick(() => { if (savedTestStandardId) { // ç¡®ä¿ç±»åå¹é ï¼item.id å¯è½æ¯æ°åæåç¬¦ä¸²ï¼ const matchedOption = testStandardOptions.value.find(item => const matchedOption = testStandardOptions.value.find(item => item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId) ); if (matchedOption) { @@ -313,6 +348,28 @@ form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || ''; } const handleQualifiedQuantityChange = (value) => { if (value === null || value === undefined) { form.value.qualifiedQuantity = 0; return; } const quantity = parseFloat(form.value.quantity) || 0; const qualified = parseFloat(value) || 0; form.value.qualifiedQuantity = qualified > quantity?quantity:qualified; form.value.unqualifiedQuantity = Math.max(0, quantity - qualified); }; const handleUnqualifiedQuantityChange = (value) => { if (value === null || value === undefined) { form.value.unqualifiedQuantity = 0; return; } const quantity = parseFloat(form.value.quantity) || 0; const unqualified = parseFloat(value) || 0; form.value.unqualifiedQuantity = unqualified > quantity?quantity:unqualified; form.value.qualifiedQuantity = Math.max(0, quantity - unqualified); }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { @@ -337,7 +394,7 @@ if (children && children.length > 0) { newItem.children = convertIdToValue(children); } return newItem; }); } @@ -345,26 +402,26 @@ const submitForm = () => { proxy.$refs.formRef.validate(valid => { if (valid) { form.value.inspectType = 2 if (operationType.value === "add") { tableData.value.forEach((item) => { delete item.id }) } const data = {...form.value, qualityInspectParams: tableData.value} form.value.inspectType = 2; if (operationType.value === "add") { tableData.value.forEach((item) => { delete item.id; }); } const data = { ...form.value, qualityInspectParams: tableData.value }; if (operationType.value === "add") { qualityInspectAdd(data).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); }) }); } else { qualityInspectUpdate(data).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); }) }); } } }) }); } const getList = () => { if (!currentProductId.value) { @@ -375,15 +432,15 @@ let params = { productId: currentProductId.value, inspectType: 2 } qualityInspectDetailByProductId(params).then(res => { // ä¿å䏿æ¡éé¡¹æ°æ® testStandardOptions.value = res.data || []; // æ¸ ç©ºè¡¨æ ¼æ°æ®ï¼çå¾ ç¨æ·éæ©ææ tableData.value = []; // æ¸ ç©ºææ éæ© form.value.testStandardId = ''; }) }; qualityInspectDetailByProductId(params).then(res => { // ä¿å䏿æ¡éé¡¹æ°æ® testStandardOptions.value = res.data || []; // æ¸ ç©ºè¡¨æ ¼æ°æ®ï¼çå¾ ç¨æ·éæ©ææ tableData.value = []; // æ¸ ç©ºææ éæ© form.value.testStandardId = ''; }); } // ææ éæ©ååå¤ç @@ -400,12 +457,12 @@ tableData.value = []; }).finally(() => { tableLoading.value = false; }) }); } const getQualityInspectParamList = (id) => { qualityInspectParamInfo(id).then(res => { tableData.value = res.data; }) qualityInspectParamInfo(id).then(res => { tableData.value = res.data; }); } // å ³éå¼¹æ¡ const closeDia = () => { @@ -414,8 +471,8 @@ testStandardOptions.value = []; form.value.testStandardId = ''; dialogFormVisible.value = false; emit('close') }; emit('close'); } defineExpose({ openDialog, }); @@ -423,4 +480,4 @@ <style scoped> </style> </style> src/views/qualityManagement/finalInspection/index.vue
@@ -123,8 +123,18 @@ prop: "unit", }, { label: "æ°é", label: "æ»æ°é", prop: "quantity", width: 100 }, { label: "åæ ¼æ°é", prop: "qualifiedQuantity", width: 100 }, { label: "ä¸åæ ¼æ°é", prop: "unqualifiedQuantity", width: 100 }, { @@ -142,7 +152,7 @@ } else if (params == 'åæ ¼') { return "success"; } else { return null; return 'danger'; } }, }, src/views/qualityManagement/nonconformingManagement/index.vue
@@ -98,7 +98,7 @@ } else if (params == 'åæ ¼') { return "success"; } else { return null; return 'danger'; } }, }, src/views/qualityManagement/processInspection/components/formDia.vue
@@ -99,6 +99,34 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="åæ ¼æ°éï¼" prop="qualifiedQuantity"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请è¾å ¥" clearable :precision="2" @change="handleQualifiedQuantityChange" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸åæ ¼æ°éï¼" prop="unqualifiedQuantity"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请è¾å ¥" clearable :precision="2" @change="handleUnqualifiedQuantityChange" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="æ£æµåä½ï¼" prop="checkCompany"> <el-input v-model="form.checkCompany" @@ -114,6 +142,8 @@ value="åæ ¼" /> <el-option label="ä¸åæ ¼" value="ä¸åæ ¼" /> <el-option label="é¨ååæ ¼" value="é¨ååæ ¼" /> </el-select> </el-form-item> </el-col> @@ -189,6 +219,7 @@ import { userListNoPage } from "@/api/system/user.js"; import { qualityInspectParamInfo } from "@/api/qualityManagement/qualityInspectParam.js"; import { list } from "@/api/productionManagement/productionProcess"; import qualified from "@/views/inventoryManagement/stockManagement/Qualified.vue"; const { proxy } = getCurrentInstance(); const emit = defineEmits(["close"]); @@ -206,6 +237,8 @@ testStandardId: "", unit: "", quantity: "", qualifiedQuantity: "", unqualifiedQuantity: "", checkCompany: "", checkResult: "", }, @@ -215,11 +248,11 @@ checkName: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], productId: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], testStandardId: [ { required: false, message: "è¯·éæ©ææ ", trigger: "change" }, ], testStandardId: [{ required: false, message: "è¯·éæ©ææ ", trigger: "change" }], unit: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], quantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], qualifiedQuantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], unqualifiedQuantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], checkCompany: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], checkResult: [{ required: true, message: "请è¾å ¥", trigger: "change" }], }, @@ -400,6 +433,28 @@ modelOptions.value.find(item => item.id == value)?.unit || ""; }; const handleQualifiedQuantityChange = (value) => { if (value === null || value === undefined) { form.value.qualifiedQuantity = 0; return; } const quantity = parseFloat(form.value.quantity) || 0; const qualified = parseFloat(value) || 0; form.value.qualifiedQuantity = qualified > quantity?quantity:qualified; form.value.unqualifiedQuantity = Math.max(0, quantity - qualified); }; const handleUnqualifiedQuantityChange = (value) => { if (value === null || value === undefined) { form.value.unqualifiedQuantity = 0; return; } const quantity = parseFloat(form.value.quantity) || 0; const unqualified = parseFloat(value) || 0; form.value.unqualifiedQuantity = unqualified > quantity?quantity:unqualified; form.value.qualifiedQuantity = Math.max(0, quantity - unqualified); }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { @@ -440,6 +495,17 @@ delete item.id; }); } // ç¡®ä¿æ°éä¸ä¸ºnull const quantity = parseFloat(form.value.quantity) || 0; const qualified = parseFloat(form.value.qualifiedQuantity) || 0; const unqualified = parseFloat(form.value.unqualifiedQuantity) || 0; // éªè¯æ°éå ³ç³» if (qualified + unqualified !== quantity) { proxy.$modal.msgError("åæ ¼æ°éä¸ä¸åæ ¼æ°éä¹åå¿ é¡»çäºæ»æ°é"); return; } const data = { ...form.value, process: processName, // ä¿ç process åæ®µä»¥å ¼å®¹å端 @@ -520,4 +586,4 @@ </script> <style scoped> </style> </style> src/views/qualityManagement/processInspection/index.vue
@@ -122,8 +122,18 @@ prop: "unit", }, { label: "æ°é", label: "æ»æ°é", prop: "quantity", width: 100 }, { label: "åæ ¼æ°é", prop: "qualifiedQuantity", width: 100 }, { label: "ä¸åæ ¼æ°é", prop: "unqualifiedQuantity", width: 100 }, { @@ -141,7 +151,7 @@ } else if (params == 'åæ ¼') { return "success"; } else { return null; return 'danger'; } }, }, @@ -363,13 +373,13 @@ type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', }) const downloadUrl = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = downloadUrl link.download = 'è¿ç¨æ£éªæ¥å.docx' document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(downloadUrl) }) src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -82,6 +82,23 @@ </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="åæ ¼æ°éï¼" prop="qualifiedQuantity"> <el-input-number :step="0.01" :min="0" :max="form.quantity || 0" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请è¾å ¥" :precision="2" @change="onQualifiedChange"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸åæ ¼æ°éï¼" prop="unqualifiedQuantity"> <el-input-number :step="0.01" :min="0" :max="form.quantity || 0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请è¾å ¥" :precision="2" @change="onUnqualifiedChange"/> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="æ£æµåä½ï¼" prop="checkCompany"> @@ -93,6 +110,7 @@ <el-select v-model="form.checkResult"> <el-option label="åæ ¼" value="åæ ¼"/> <el-option label="ä¸åæ ¼" value="ä¸åæ ¼"/> <el-option label="é¨ååæ ¼" value="é¨ååæ ¼"/> </el-select> </el-form-item> </el-col> @@ -182,6 +200,8 @@ testStandardId: [{required: false, message: "è¯·éæ©ææ ", trigger: "change"}], unit: [{required: false, message: "请è¾å ¥", trigger: "blur"}], quantity: [{required: true, message: "请è¾å ¥", trigger: "blur"}], qualifiedQuantity: [{required: true, message: "请è¾å ¥", trigger: "blur"}], unqualifiedQuantity: [{required: true, message: "请è¾å ¥", trigger: "blur"}], checkCompany: [{required: false, message: "请è¾å ¥", trigger: "blur"}], checkResult: [{required: true, message: "è¯·éæ©æ£æµç»æ", trigger: "change"}], }, @@ -294,7 +314,7 @@ // 妿ç¼è¾æ°æ®ä¸æ testStandardIdï¼å设置并å 载对åºçåæ° if (savedTestStandardId) { // ç¡®ä¿ç±»åå¹é ï¼item.id å¯è½æ¯æ°åæåç¬¦ä¸²ï¼ const matchedOption = testStandardOptions.value.find(item => const matchedOption = testStandardOptions.value.find(item => item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId) ); if (matchedOption) { @@ -448,6 +468,32 @@ tableData.value = res.data; }) } // èªå¨è®¡ç®åæ ¼æ°éååæ¶çä¸åæ ¼æ°é const onQualifiedChange = (value) => { if (form.value.quantity !== undefined && form.value.quantity !== null) { const maxUnqualified = form.value.quantity - value; if (maxUnqualified >= 0) { form.value.unqualifiedQuantity = maxUnqualified; } else { form.value.qualifiedQuantity = form.value.quantity; form.value.unqualifiedQuantity = 0; } } }; // èªå¨è®¡ç®ä¸åæ ¼æ°éååæ¶çåæ ¼æ°é const onUnqualifiedChange = (value) => { if (form.value.quantity !== undefined && form.value.quantity !== null) { const maxQualified = form.value.quantity - value; if (maxQualified >= 0) { form.value.qualifiedQuantity = maxQualified; } else { form.value.unqualifiedQuantity = form.value.quantity; form.value.qualifiedQuantity = 0; } } }; // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); @@ -464,4 +510,4 @@ <style scoped> </style> </style> src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -124,8 +124,23 @@ prop: "unit", }, { label: "æ°é", label: "æ»æ°é", prop: "quantity", width: 100 }, { label: "åæ ¼æ°é", prop: "qualifiedQuantity", width: 100 }, { label: "ä¸åæ ¼æ°é", prop: "unqualifiedQuantity", width: 100 }, { label: "æ£æµåä½", prop: "checkCompany", width: 120 }, { @@ -143,7 +158,7 @@ } else if (params === 'åæ ¼') { return "success"; } else { return null; return 'danger'; } }, }, src/views/salesManagement/salesQuotation/index.vue
@@ -185,7 +185,7 @@ </el-form-item> </template> </el-table-column> <el-table-column prop="ProductModel" label="è§æ ¼åå·" width="200"> <el-table-column prop="specification" label="è§æ ¼åå·" width="200"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.productModelId`" class="product-table-form-item"> <el-select @@ -275,7 +275,7 @@ <h4>产åæç»</h4> <el-table :data="currentQuotation.products" border style="width: 100%"> <el-table-column prop="product" label="产ååç§°" /> <el-table-column prop="ProductModel" label="è§æ ¼åå·" /> <el-table-column prop="specification" label="è§æ ¼åå·" /> <el-table-column prop="unit" label="åä½" /> <el-table-column prop="unitPrice" label="åä»·"> <template #default="scope"> @@ -458,7 +458,7 @@ row.product = ''; row.modelOptions = []; row.productModelId = ''; row.ProductModel = ''; row.specification = ''; row.unit = ''; return; } @@ -479,7 +479,7 @@ // å¦ææ¸ ç©ºéæ©ï¼åæ¸ ç©ºç¸å ³å段 if (!value) { row.productModelId = ''; row.ProductModel = ''; row.specification = ''; row.unit = ''; return; } @@ -488,10 +488,10 @@ const modelOptions = row.modelOptions || []; const index = modelOptions.findIndex((item) => item.id === value); if (index !== -1) { row.ProductModel = modelOptions[index].model; row.specification = modelOptions[index].model; row.unit = modelOptions[index].unit; } else { row.ProductModel = ''; row.specification = ''; row.unit = ''; } }; @@ -524,7 +524,7 @@ productId: product.productId || '', product: product.product || product.productName || '', productModelId: product.productModelId || '', ProductModel: product.ProductModel || '', specification: product.specification || '', quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0, @@ -570,9 +570,9 @@ const res = await modelList({ id: resolvedProductId }); modelOptions = res || []; // 妿è¿åçæ°æ®æ²¡æ productModelIdï¼ä½æ ProductModel åç§°ï¼æ ¹æ®åç§°æ¥æ¾ ID if (!resolvedProductModelId && product.ProductModel) { const foundModel = modelOptions.find(item => item.model === product.ProductModel); // 妿è¿åçæ°æ®æ²¡æ productModelIdï¼ä½æ specification åç§°ï¼æ ¹æ®åç§°æ¥æ¾ ID if (!resolvedProductModelId && product.specification) { const foundModel = modelOptions.find(item => item.model === product.specification); if (foundModel) { resolvedProductModelId = foundModel.id; } @@ -586,7 +586,7 @@ productId: resolvedProductId, product: productName, productModelId: resolvedProductModelId, ProductModel: product.ProductModel || '', specification: product.specification || '', quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0, @@ -755,7 +755,7 @@ productId: product.productId || '', product: product.product || product.productName || '', productModelId: product.productModelId || '', ProductModel: product.ProductModel || '', specification: product.specification || '', quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0,