From ab264123941cd3d345687af92aab2a9e04968960 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 27 五月 2026 14:21:35 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' into dev_宁夏_英泽防锈
---
src/components/AIChatSidebar/index.vue | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 407 insertions(+), 13 deletions(-)
diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index c9db563..7b22d7b 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -227,6 +227,12 @@
:id="`ai-chart-${index}-${key}`"
></div>
</div>
+ <div
+ v-else-if="message.chartMarkdownParseFailed"
+ class="chart-empty-state"
+ >
+ 鍥捐〃瑙f瀽澶辫触锛岃绋嶅悗閲嶈瘯銆�
+ </div>
<!-- 琛ㄦ牸鍐呭 -->
<div v-if="message.type === 'todo_list' && message.tableData" class="table-wrapper">
@@ -513,6 +519,64 @@
: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.financeData" class="sales-structured-card">
+ <div class="sales-structured-card__title">{{ getFinancialTypeLabel(message.type) }}</div>
+
+ <div v-if="message.financeData.summaryEntries?.length" class="sales-summary-grid">
+ <div
+ v-for="(entry, entryIndex) in message.financeData.summaryEntries"
+ :key="`finance-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.financeData.headline" class="finance-headline">
+ {{ message.financeData.headline }}
+ </div>
+
+ <div v-if="message.financeData.conclusions?.length" class="finance-text-section">
+ <div class="sales-section-title">鏍稿績缁撹</div>
+ <ul>
+ <li v-for="(item, idx) in message.financeData.conclusions" :key="`finance-conclusion-${idx}`">
+ {{ item }}
+ </li>
+ </ul>
+ </div>
+
+ <div v-if="message.financeData.riskSuggestions?.length" class="finance-text-section">
+ <div class="sales-section-title">椋庨櫓寤鸿</div>
+ <ul>
+ <li v-for="(item, idx) in message.financeData.riskSuggestions" :key="`finance-risk-${idx}`">
+ {{ item }}
+ </li>
+ </ul>
+ </div>
+
+ <div
+ v-for="section in message.financeData.sections"
+ :key="`finance-section-${section.key}`"
+ class="table-wrapper manufacturing-table-wrapper"
+ >
+ <div class="sales-section-title">{{ section.title }}</div>
+ <el-table :data="section.items" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in section.columns"
+ :key="`finance-${section.key}-${col}`"
+ :label="getStructuredFieldLabel(col)"
+ min-width="120"
show-overflow-tooltip
>
<template #default="{ row }">
@@ -949,7 +1013,7 @@
sales_customer_profile_list: '瀹㈡埛妗f',
sales_quotation_list: '閿�鍞姤浠�',
sales_ledger_list: '閿�鍞彴璐�',
- sales_return_list: '閿�鍞��璐�',
+ sales_return_list: '閿�鍞��娆�/鍥炴璁板綍',
sales_customer_interaction_list: '瀹㈡埛寰�鏉�',
sales_shipping_list: '鍙戣揣鍙拌处',
sales_dashboard: '閿�鍞寚鏍囩粺璁�',
@@ -958,7 +1022,28 @@
}
const purchaseTypeLabelMap = {
purchase_material_rank: '閲囪喘鐗╂枡閲戦鎺掕',
+ purchase_stats: '閲囪喘缁熻',
purchase_pending_payment_list: '寰呬粯娆鹃噰璐崟'
+}
+const financialStructuredTypeSet = new Set([
+ 'financial_cost_accounting',
+ 'financial_order_profit_analysis',
+ 'financial_inventory_capital_analysis',
+ 'financial_cashflow_forecast',
+ 'financial_business_anomaly_warning',
+ 'financial_business_cockpit',
+ 'financial_operation_report',
+ 'financial_rag_knowledge'
+])
+const financialTypeLabelMap = {
+ financial_cost_accounting: '鎴愭湰鏍哥畻',
+ financial_order_profit_analysis: '璁㈠崟鍒╂鼎鍒嗘瀽',
+ financial_inventory_capital_analysis: '搴撳瓨璧勯噾鍒嗘瀽',
+ financial_cashflow_forecast: '鐜伴噾娴侀娴�',
+ financial_business_anomaly_warning: '缁忚惀寮傚父棰勮',
+ financial_business_cockpit: '缁忚惀椹鹃┒鑸�',
+ financial_operation_report: '缁忚惀鎶ュ憡',
+ financial_rag_knowledge: '璐㈠姟鐭ヨ瘑妫�绱�'
}
const manufacturingStructuredTypeSet = new Set([
'manufacturing_site_snapshot',
@@ -1030,6 +1115,26 @@
stockQty: '搴撳瓨閲�'
}
Object.assign(structuredFieldLabelMap, {
+ refundId: '閫�娆�/鍥炴鍗曞彿',
+ collectionNumber: '鏀舵鍗曞彿',
+ collectionAmount: '鏀舵閲戦',
+ actualAmount: '鏀舵閲戦(鍏煎)',
+ customerId: '瀹㈡埛ID',
+ salesLedgerId: '閿�鍞彴璐D',
+ projectName: '椤圭洰鍚嶇О',
+ receiptPaymentDate: '鏀朵粯娆炬棩鏈�',
+ receiptPaymentAmount: '鏀朵粯娆鹃噾棰�',
+ receiptPaymentType: '鏀朵粯娆剧被鍨�',
+ registrant: '鐧昏浜�',
+ returnAmount: '鍥炴閲戦姹囨��',
+ totalReceiptAmount: '鏀舵鎬婚',
+ customerCount: '瀹㈡埛鏁�',
+ paidAmount: '宸蹭粯娆鹃噾棰�',
+ contractAmount: '鍚堝悓閲戦',
+ paymentCount: '浠樻绗旀暟',
+ invoiceCount: '鍙戠エ绗旀暟',
+ paymentAmount: '浠樻閲戦',
+ invoiceAmount: '鍙戠エ閲戦',
customerName: '瀹㈡埛鍚嶇О',
riskLevel: '椋庨櫓绛夌骇',
riskScore: '椋庨櫓璇勫垎',
@@ -1042,6 +1147,32 @@
collectionStrategy: '鍥炴绛栫暐',
quotationStrategy: '鎶ヤ环绛栫暐',
nextAction: '涓嬩竴姝ュ姩浣�',
+ salesContractNo: '閿�鍞悎鍚屽彿',
+ revenue: '鏀跺叆',
+ materialCost: '鏉愭枡鎴愭湰',
+ laborCost: '浜哄伐鎴愭湰',
+ depreciationCost: '鎶樻棫鎴愭湰',
+ scrapCost: '鎶ュ簾鎴愭湰',
+ totalCost: '鎬绘垚鏈�',
+ profit: '鍒╂鼎',
+ profitRate: '鍒╂鼎鐜�',
+ reasons: '鍘熷洜',
+ suggestion: '寤鸿',
+ productName: '浜у搧鍚嶇О',
+ model: '鍨嬪彿',
+ quantity: '鏁伴噺',
+ inventoryValue: '搴撳瓨璧勯噾',
+ stagnantDays: '鍛嗘粸澶╂暟',
+ overstock: '鏄惁瓒呭偍',
+ month: '鏈堜唤',
+ income: '鏀跺叆',
+ expense: '鏀嚭',
+ netFlow: '鍑�鐜伴噾娴�',
+ message: '棰勮淇℃伅',
+ headline: '鎶ュ憡鏍囬',
+ conclusions: '鏍稿績缁撹',
+ riskSuggestions: '椋庨櫓寤鸿',
+ orderProfitTop: '鍒╂鼎Top',
contractAmountTotal: '鍚堝悓鎬婚',
receivedAmountTotal: '宸插洖娆鹃噾棰�',
pendingAmountTotal: '寰呭洖娆炬�婚',
@@ -1450,12 +1581,41 @@
return items.filter(item => isPlainObject(item))
}
+const normalizeSalesReturnListItems = (items = []) => {
+ return normalizeSalesListItems(items).map((item) => {
+ const collectionAmount = item.collectionAmount ?? item.actualAmount
+ const collectionNumber = item.collectionNumber || item.refundId || ''
+ return {
+ ...item,
+ refundId: item.refundId || collectionNumber,
+ collectionNumber,
+ collectionAmount: collectionAmount ?? 0,
+ actualAmount: item.actualAmount ?? collectionAmount ?? 0
+ }
+ })
+}
+
+const normalizeSalesTypeListItems = (type = '', items = []) => {
+ if (type === 'sales_return_list') {
+ return normalizeSalesReturnListItems(items)
+ }
+ return normalizeSalesListItems(items)
+}
+
+const inferSalesColumnsByType = (type = '', listItems = []) => {
+ const columns = inferSalesColumns(listItems)
+ if (type === 'sales_return_list') {
+ return columns.filter(column => column !== 'actualAmount')
+ }
+ return columns
+}
+
const buildSalesStructuredData = (parsedData) => {
const type = String(parsedData?.type || '')
if (!salesStructuredTypeSet.has(type)) return null
const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
- const listItems = normalizeSalesListItems(rawData.items)
+ const listItems = normalizeSalesTypeListItems(type, rawData.items)
const topCustomers = normalizeSalesListItems(rawData.topCustomers)
const contractTrend = normalizeSalesListItems(rawData.contractTrend)
@@ -1463,7 +1623,7 @@
type,
summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
listItems,
- columns: inferSalesColumns(listItems),
+ columns: inferSalesColumnsByType(type, listItems),
topCustomers,
topCustomerColumns: inferSalesColumns(topCustomers),
contractTrend,
@@ -1491,6 +1651,50 @@
}
const getPurchaseTypeLabel = (type = '') => purchaseTypeLabelMap[String(type || '')] || '閲囪喘鏌ヨ缁撴灉'
+
+const financialDataSectionConfig = [
+ { key: 'orders', title: '璁㈠崟鏄庣粏' },
+ { key: 'items', title: '鏄庣粏鍒楄〃' },
+ { key: 'actualMonthly', title: '鍘嗗彶鐜伴噾娴�' },
+ { key: 'forecastMonthly', title: '棰勬祴鐜伴噾娴�' },
+ { key: 'receivableRiskTop', title: '搴旀敹椋庨櫓Top' },
+ { key: 'payablePressureTop', title: '搴斾粯鍘嬪姏Top' },
+ { key: 'orderProfitTop', title: '鍒╂鼎Top' }
+]
+
+const buildFinancialStructuredSections = (rawData = {}) => {
+ const sections = []
+ financialDataSectionConfig.forEach((section) => {
+ const items = normalizeSalesListItems(rawData?.[section.key])
+ if (!items.length) return
+ sections.push({
+ key: section.key,
+ title: section.title,
+ items,
+ columns: inferSalesColumns(items)
+ })
+ })
+ return sections
+}
+
+const buildFinancialStructuredData = (parsedData) => {
+ const type = String(parsedData?.type || '')
+ if (!financialStructuredTypeSet.has(type) || parsedData?.success !== true) return null
+
+ const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+ const headline = String(rawData?.headline || '').trim()
+
+ return {
+ type,
+ summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+ headline,
+ conclusions: toStructuredStringArray(rawData?.conclusions),
+ riskSuggestions: toStructuredStringArray(rawData?.riskSuggestions),
+ sections: buildFinancialStructuredSections(rawData)
+ }
+}
+
+const getFinancialTypeLabel = (type = '') => financialTypeLabelMap[String(type || '')] || '璐㈠姟鏌ヨ缁撴灉'
const isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || ''))
@@ -1913,6 +2117,8 @@
salesData: null,
purchaseData: null,
purchaseIntentData: null,
+ financeData: null,
+ chartMarkdownParseFailed: false,
localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
}
@@ -2161,6 +2367,8 @@
messageObj.salesData = null
messageObj.purchaseData = null
messageObj.purchaseIntentData = null
+ messageObj.financeData = null
+ messageObj.chartMarkdownParseFailed = false
if (isPurchaseIntentNotRecognized) {
messageObj.purchaseIntentData = normalizePurchaseIntentNotRecognizedData(parsedData)
@@ -2188,12 +2396,18 @@
messageObj.purchaseData = purchaseData
}
+ const financeData = buildFinancialStructuredData(parsedData)
+ if (financeData) {
+ messageObj.financeData = financeData
+ }
+
if (parsedData.action === 'confirm_required' && parsedData.businessType) {
messageObj.type = 'purchase_analysis_confirm'
messageObj.purchaseAnalysisData = parsedData
messageObj.manufacturingData = null
messageObj.salesData = null
messageObj.purchaseData = null
+ messageObj.financeData = null
if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
initializePurchasePayloadTree(messageObj, parsedData.payload || {})
}
@@ -2243,6 +2457,8 @@
if (parsedData.type === 'sales_customer_churn_risk') return '宸蹭负鎮ㄧ敓鎴愬鎴锋祦澶遍闄╁垎鏋愩��'
if (parsedData.type === 'sales_collection_quote_strategy') return '宸蹭负鎮ㄧ敓鎴愬洖娆句笌鎶ヤ环绛栫暐寤鸿銆�'
if (parsedData.type === 'sales_dashboard') return '宸蹭负鎮ㄧ敓鎴愰攢鍞寚鏍囩粺璁°��'
+ if (parsedData.type === 'sales_return_list') return '宸茶繑鍥為攢鍞��娆�/鍥炴璁板綍銆�'
+ if (parsedData.type === 'sales_customer_interaction_list') return '宸茶繑鍥炲鎴峰線鏉ユ暟鎹��'
return '宸茶繑鍥為攢鍞煡璇㈢粨鏋溿��'
}
if (manufacturingStructuredTypeSet.has(parsedData.type)) {
@@ -2251,9 +2467,31 @@
if (parsedData.type === 'manufacturing_analysis') return '宸蹭负鎮ㄧ敓鎴愬埗閫犲垎鏋愮粨鏋溿��'
return '宸茶繑鍥炲埗閫犳煡璇㈢粨鏋溿��'
}
+ if (financialStructuredTypeSet.has(parsedData.type)) return '宸茶繑鍥炶储鍔″垎鏋愮粨鏋溿��'
if (String(parsedData.type || '').startsWith('purchase_')) return '宸茶繑鍥為噰璐煡璇㈢粨鏋溿��'
if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+}
+
+const resolveStructuredDescription = (parsedData) => {
+ if (!parsedData) return ''
+ if (parsedData.action === 'confirm_required') {
+ return getPurchaseConfirmDescription(parsedData)
+ }
+
+ const description = String(parsedData.description || '').trim()
+ if (!description) return ''
+
+ if (parsedData.type === 'sales_customer_interaction_list') {
+ if (description === 'no_customer_interactions') return '璇ユ椂闂磋寖鍥存殏鏃犲鎴峰線鏉ヨ褰曘��'
+ if (description === 'ok') return '宸茶繑鍥炲鎴峰線鏉ユ暟鎹��'
+ }
+
+ if (description === 'ok' || description === 'success') {
+ return getStructuredFallbackText(parsedData)
+ }
+
+ return description
}
const buildPurchaseMaterialRankCharts = (parsedData) => {
@@ -3384,7 +3622,9 @@
manufacturingData: null,
salesData: null,
purchaseData: null,
- purchaseIntentData: null
+ purchaseIntentData: null,
+ financeData: null,
+ chartMarkdownParseFailed: false
})
outputState.value[botMsgIndex] = {
@@ -3440,6 +3680,7 @@
if (extracted) {
applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex, !outputState.value[botMsgIndex].hasRenderedChart)
}
+ currentMsg.htmlContent = convertStreamOutput(currentMsg.content || '', botMsgIndex)
// 鏈�缁堣В鏋愮‘淇濆浘琛ㄦ覆鏌�
if (currentMsg.chartOptions && !outputState.value[botMsgIndex].hasRenderedChart) {
@@ -3512,7 +3753,9 @@
manufacturingData: null,
salesData: null,
purchaseData: null,
- purchaseIntentData: null
+ purchaseIntentData: null,
+ financeData: null,
+ chartMarkdownParseFailed: false
}
messages.value.push(botMsg)
@@ -3562,6 +3805,7 @@
if (extracted) {
applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
}
+ currentMsg.htmlContent = convertStreamOutput(currentMsg.content || '', botMsgIndex)
}).catch(err => {
if (err.name === 'CanceledError' || err.name === 'AbortError') {
console.log('Request aborted by user')
@@ -3605,6 +3849,126 @@
.replace(/\n/g, '<br>')
}
+const localChartMarkdownImagePattern = /!\[[^\]]*]\((https?:\/\/local\/generate_chart\?[^)\s]+)\)/gi
+
+const parseLocalChartOptionText = (optionText = '') => {
+ const text = String(optionText || '').trim()
+ if (!text) return null
+
+ const parseCandidates = [text]
+ try {
+ const decoded = decodeURIComponent(text)
+ if (decoded && decoded !== text) {
+ parseCandidates.push(decoded)
+ }
+ } catch (err) {
+ // Keep original text candidate.
+ }
+
+ for (const candidate of parseCandidates) {
+ try {
+ const parsed = JSON.parse(candidate)
+ if (isPlainObject(parsed)) {
+ return parsed
+ }
+ } catch (err) {
+ continue
+ }
+ }
+
+ return null
+}
+
+const parseLocalChartOptionFromUrl = (urlText = '') => {
+ try {
+ const url = new URL(String(urlText || '').trim())
+ if (String(url.hostname || '').toLowerCase() !== 'local' || !String(url.pathname || '').includes('/generate_chart')) {
+ return null
+ }
+ const optionText = url.searchParams.get('options')
+ return parseLocalChartOptionText(optionText)
+ } catch (err) {
+ return null
+ }
+}
+
+const extractLocalChartMarkdown = (text = '') => {
+ const sourceText = String(text || '')
+ if (!sourceText) {
+ return {
+ cleanedText: '',
+ hasLocalChartMarkdown: false,
+ chartOptions: null,
+ parseFailed: false
+ }
+ }
+
+ let hasLocalChartMarkdown = false
+ let chartIndex = 0
+ const chartOptions = {}
+
+ const cleanedText = sourceText.replace(localChartMarkdownImagePattern, (fullMatch, chartUrl) => {
+ hasLocalChartMarkdown = true
+ const option = parseLocalChartOptionFromUrl(chartUrl)
+ if (option) {
+ chartOptions[`markdownChart_${chartIndex++}`] = option
+ }
+ return ''
+ })
+
+ const normalizedText = cleanedText
+ .replace(/\n[ \t]*\n[ \t]*\n+/g, '\n\n')
+ .trim()
+ const hasParsedCharts = Object.keys(chartOptions).length > 0
+
+ return {
+ cleanedText: normalizedText,
+ hasLocalChartMarkdown,
+ chartOptions: hasParsedCharts ? chartOptions : null,
+ parseFailed: hasLocalChartMarkdown && !hasParsedCharts
+ }
+}
+
+const applyLocalChartMarkdownFallback = (displayText, msgIndex) => {
+ const messageObj = messages.value[msgIndex]
+ if (!messageObj || messageObj.isUser) return displayText
+
+ const {
+ cleanedText,
+ hasLocalChartMarkdown,
+ chartOptions,
+ parseFailed
+ } = extractLocalChartMarkdown(displayText)
+
+ if (!hasLocalChartMarkdown) {
+ return displayText
+ }
+
+ if (chartOptions) {
+ messageObj.chartOptions = chartOptions
+ messageObj.chartRenderReady = true
+ messageObj.chartMarkdownParseFailed = false
+
+ const streamState = outputState.value[msgIndex]
+ if (!streamState || !streamState.hasRenderedChart) {
+ renderCharts(msgIndex, chartOptions)
+ if (streamState) {
+ streamState.hasRenderedChart = true
+ }
+ }
+
+ return cleanedText || '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
+ }
+
+ if (!messageObj.chartOptions || !messageObj.chartRenderReady) {
+ messageObj.chartOptions = null
+ messageObj.chartRenderReady = false
+ messageObj.chartMarkdownParseFailed = parseFailed
+ }
+
+ return cleanedText || '鍥捐〃瑙f瀽澶辫触锛岃绋嶅悗閲嶈瘯銆�'
+}
+
const convertStreamOutput = (output, msgIndex) => {
if (!output) return ''
const state = outputState.value[msgIndex]
@@ -3631,10 +3995,9 @@
display = ''
}
- if (parsed.description) {
- display = parsed.action === 'confirm_required'
- ? getPurchaseConfirmDescription(parsed)
- : parsed.description
+ const resolvedDescription = resolveStructuredDescription(parsed)
+ if (resolvedDescription) {
+ display = resolvedDescription
}
if (!display) {
@@ -3653,10 +4016,9 @@
display = ''
}
- if (parsed.description) {
- display = parsed.action === 'confirm_required'
- ? getPurchaseConfirmDescription(parsed)
- : parsed.description
+ const resolvedDescription = resolveStructuredDescription(parsed)
+ if (resolvedDescription) {
+ display = resolvedDescription
}
if (!display) {
@@ -3672,6 +4034,7 @@
}
}
+ display = applyLocalChartMarkdownFallback(display, msgIndex)
let html = convertTextToHtml(display)
// 杩樺師浠g爜鍧�
@@ -4654,6 +5017,18 @@
margin-bottom: 12px;
}
+.chart-empty-state {
+ margin-top: 12px;
+ width: 100%;
+ border-radius: 10px;
+ border: 1px dashed rgba(148, 163, 184, 0.6);
+ background: #f8fafc;
+ color: #64748b;
+ font-size: 13px;
+ line-height: 1.6;
+ padding: 12px;
+}
+
.table-wrapper {
margin-top: 12px;
background: #fff;
@@ -4957,6 +5332,25 @@
color: $deep-blue;
}
+.finance-headline {
+ margin-top: 4px;
+ font-size: 13px;
+ line-height: 1.7;
+ color: #344054;
+}
+
+.finance-text-section {
+ margin-top: 10px;
+
+ ul {
+ margin: 6px 0 0;
+ padding-left: 18px;
+ font-size: 13px;
+ line-height: 1.7;
+ color: #344054;
+ }
+}
+
.purchase-intent-quick-prompt-wrap {
margin-top: 12px;
}
--
Gitblit v1.9.3