| | |
| | | </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> |
| | |
| | | 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' |
| | | import purchaseAssistantAvatar from '@/assets/AI/采购助手.png' |
| | | import productionAssistantAvatar from '@/assets/AI/生产助手.png' |
| | | import financeAssistantAvatar from '@/assets/AI/财务助手.png' |
| | | import bossAssistantAvatar from '@/assets/AI/老板助手.png' |
| | | import bossAssistantAvatar from '@/assets/AI/待办助手.png' |
| | | |
| | | const emit = defineEmits(['header-extra-action']) |
| | | |
| | |
| | | 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: '采购台账', |
| | |
| | | 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 |
| | | } |
| | | } |
| | | |
| | | // 历史会话相关 |
| | |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : [] |
| | | } |
| | | |
| | |
| | | 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 || {}) |
| | | } |
| | |
| | | } |
| | | |
| | | 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) => { |
| | |
| | | if (visibleValue === undefined || visibleValue === null) return clonePurchasePayloadValue(hiddenValue) |
| | | |
| | | if (Array.isArray(visibleValue) && Array.isArray(hiddenValue)) { |
| | | const maxLength = Math.max(visibleValue.length, hiddenValue.length) |
| | | const merged = [] |
| | | for (let i = 0; i < maxLength; i++) { |
| | | merged[i] = mergePurchasePayloadWithHidden(visibleValue[i], hiddenValue[i]) |
| | | } |
| | | return merged |
| | | return visibleValue.map((item, index) => mergePurchasePayloadWithHidden(item, hiddenValue[index])) |
| | | } |
| | | |
| | | if ( |
| | |
| | | '' |
| | | } |
| | | |
| | | const prunePurchaseProductRecord = (record) => { |
| | | if (!record || typeof record !== 'object' || Array.isArray(record)) return null |
| | | const normalizedRecord = normalizePurchaseProductRecord(record) |
| | | const hasVisibleFieldValue = Object.entries(normalizedRecord).some(([key, value]) => { |
| | | if (shouldHidePurchaseField(key)) return false |
| | | return hasMeaningfulPayloadValue(value) |
| | | }) |
| | | return hasVisibleFieldValue ? normalizedRecord : null |
| | | } |
| | | |
| | | const normalizeAndFilterPurchaseProductData = (value) => { |
| | | if (!Array.isArray(value)) return value |
| | | return value |
| | | .map(item => prunePurchaseProductRecord(item)) |
| | | .filter(Boolean) |
| | | } |
| | | |
| | | const mergeLegacyProductDataIntoLedgers = (payload) => { |
| | | if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return payload |
| | | if (!Array.isArray(payload.purchaseLedgers) || !Array.isArray(payload.productData) || !payload.productData.length) { |
| | |
| | | |
| | | const ledgers = payload.purchaseLedgers.map(ledger => ({ |
| | | ...ledger, |
| | | productData: Array.isArray(ledger.productData) |
| | | ? ledger.productData.map(normalizePurchaseProductRecord) |
| | | : [] |
| | | productData: normalizeAndFilterPurchaseProductData(ledger.productData) || [] |
| | | })) |
| | | const unmatchedProducts = [] |
| | | |
| | | payload.productData.map(normalizePurchaseProductRecord).forEach(product => { |
| | | normalizeAndFilterPurchaseProductData(payload.productData).forEach(product => { |
| | | const productMatchKey = getPurchaseProductMatchKey(product) |
| | | const matchedLedger = ledgers.find(ledger => { |
| | | const ledgerKeys = [ |
| | |
| | | if (!record || typeof record !== 'object' || Array.isArray(record)) return record |
| | | const normalizedRecord = { |
| | | ...record, |
| | | productData: Array.isArray(record.productData) |
| | | ? record.productData.map(normalizePurchaseProductRecord) |
| | | : record.productData |
| | | productData: normalizeAndFilterPurchaseProductData(record.productData) |
| | | } |
| | | return Object.entries(normalizedRecord).reduce((result, [key, value]) => { |
| | | if (purchaseLedgerAllowedFieldKeys.has(key)) { |
| | |
| | | sanitized = normalizePurchasePayloadFieldTypes(sanitized) |
| | | if (Array.isArray(sanitized.purchaseLedgers)) { |
| | | sanitized.purchaseLedgers = sanitized.purchaseLedgers.map(filterPurchaseLedgerRecord) |
| | | } |
| | | if (Array.isArray(sanitized.productData)) { |
| | | sanitized.productData = normalizeAndFilterPurchaseProductData(sanitized.productData) |
| | | } |
| | | if (Array.isArray(sanitized.productData) && !sanitized.productData.length) { |
| | | delete sanitized.productData |
| | | } |
| | | |
| | | purchaseApprovalFieldKeys.forEach(key => { |
| | |
| | | type: '', |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null |
| | | }) |
| | | |
| | | outputState.value[botMsgIndex] = { |
| | |
| | | type: '', |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null |
| | | } |
| | | messages.value.push(botMsg) |
| | | |
| | |
| | | } |
| | | |
| | | 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('}') |
| | |
| | | } |
| | | |
| | | 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 还在传输中或格式不正确 |
| | |
| | | } |
| | | } |
| | | |
| | | .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%; |