From 93b8ceac34e2fbd5c57fe5ab4f5bac32c85408aa Mon Sep 17 00:00:00 2001
From: liyong <18434998025@163.com>
Date: 星期五, 22 五月 2026 15:37:09 +0800
Subject: [PATCH] fix(hr): 修正岗位字段映射
---
src/components/AIChatSidebar/index.vue | 385 +++++++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 320 insertions(+), 65 deletions(-)
diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index 3d234d6..3006ce7 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -4,7 +4,7 @@
<div v-if="!hideTrigger" class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
<el-tooltip :content="currentAssistant.tooltip" placement="left">
<div class="trigger-icon">
- <el-icon :size="30" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
+ <el-icon :size="22" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
</div>
</el-tooltip>
</div>
@@ -489,6 +489,114 @@
</div>
</div>
+ <div v-if="message.purchaseData" class="sales-structured-card">
+ <div class="sales-structured-card__title">{{ getPurchaseTypeLabel(message.type) }}</div>
+
+ <div v-if="message.purchaseData.summaryEntries?.length" class="sales-summary-grid">
+ <div
+ v-for="(entry, entryIndex) in message.purchaseData.summaryEntries"
+ :key="`purchase-summary-${entry.key}-${entryIndex}`"
+ class="sales-summary-item"
+ >
+ <span class="sales-summary-label">{{ entry.label }}</span>
+ <strong class="sales-summary-value">{{ entry.value }}</strong>
+ </div>
+ </div>
+
+ <div
+ v-if="message.purchaseData.listItems?.length && message.purchaseData.columns?.length"
+ class="table-wrapper manufacturing-table-wrapper"
+ >
+ <el-table :data="message.purchaseData.listItems" border stripe size="small" style="width: 100%">
+ <el-table-column
+ v-for="col in message.purchaseData.columns"
+ :key="col"
+ :label="getStructuredFieldLabel(col)"
+ min-width="140"
+ show-overflow-tooltip
+ >
+ <template #default="{ row }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+
+ <div v-if="message.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 }">
+ {{ formatStructuredValue(row[col]) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+
+ <div v-if="message.purchaseIntentData?.quickPrompts?.length" class="purchase-intent-quick-prompt-wrap">
+ <div class="purchase-intent-quick-prompt-title">璇曡瘯浠ヤ笅鎻愰棶</div>
+ <div class="quick-prompt-list purchase-intent-quick-prompt-list">
+ <button
+ v-for="prompt in message.purchaseIntentData.quickPrompts"
+ :key="`purchase-intent-${prompt}`"
+ type="button"
+ class="quick-prompt-btn"
+ :disabled="isSending"
+ @click="sendQuickPrompt(prompt)"
+ >
+ {{ prompt }}
+ </button>
+ </div>
+ </div>
+
<div v-if="message.purchaseAnalysisData" class="purchase-confirm-card">
<div class="purchase-confirm-header">
<span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '閲囪喘涓氬姟' }}</span>
@@ -906,6 +1014,30 @@
sales_customer_churn_risk: '瀹㈡埛娴佸け椋庨櫓鍒嗘瀽',
sales_collection_quote_strategy: '鍥炴涓庢姤浠风瓥鐣ュ缓璁�'
}
+const purchaseTypeLabelMap = {
+ purchase_material_rank: '閲囪喘鐗╂枡閲戦鎺掕',
+ 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',
'manufacturing_plan_list',
@@ -988,10 +1120,40 @@
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: '寰呭洖娆炬�婚',
- shipRate: '鍙戣揣鐜�'
+ shipRate: '鍙戣揣鐜�',
+ pendingOrderCount: '寰呬粯娆捐鍗曟暟',
+ totalContractAmount: '寰呬粯娆惧悎鍚屾�婚',
+ totalPaidAmount: '宸蹭粯娆炬�婚',
+ totalPendingAmount: '寰呬粯娆炬�婚'
})
const purchasePayloadFieldLabelMap = {
purchaseLedgers: '閲囪喘鍙拌处',
@@ -1415,6 +1577,69 @@
const getSalesTypeLabel = (type = '') => salesTypeLabelMap[String(type || '')] || '閿�鍞煡璇㈢粨鏋�'
+const buildPurchaseStructuredData = (parsedData) => {
+ if (parsedData?.success !== true) return null
+
+ const type = String(parsedData?.type || '')
+ if (!type.startsWith('purchase_')) return null
+
+ const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+ const listItems = normalizeSalesListItems(rawData.items)
+
+ return {
+ type,
+ summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+ listItems,
+ columns: inferSalesColumns(listItems)
+ }
+}
+
+const getPurchaseTypeLabel = (type = '') => purchaseTypeLabelMap[String(type || '')] || '閲囪喘鏌ヨ缁撴灉'
+
+const 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 || ''))
const getSalesLevelTagType = (level = '') => {
@@ -1446,6 +1671,13 @@
.filter(Boolean)
}
return []
+}
+
+const normalizePurchaseIntentNotRecognizedData = (parsedData) => {
+ if (String(parsedData?.type || '') !== 'purchase_intent_not_recognized') return null
+ const quickPrompts = toStructuredStringArray(parsedData?.data?.quickPrompts)
+ if (!quickPrompts.length) return null
+ return { quickPrompts }
}
const getManufacturingWarningLevelType = (level = '') => {
@@ -1827,6 +2059,9 @@
purchaseAnalysisData: null,
manufacturingData: null,
salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null,
+ financeData: null,
localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
}
@@ -2045,7 +2280,7 @@
const candidate = text.slice(i, j + 1)
try {
const parsed = JSON.parse(candidate)
- if (parsed?.success === true) {
+ if (typeof parsed?.success === 'boolean') {
return {
data: parsed,
startIdx: i,
@@ -2064,7 +2299,8 @@
}
const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
- if (!messageObj || !parsedData?.success) return
+ const isPurchaseIntentNotRecognized = String(parsedData?.type || '') === 'purchase_intent_not_recognized'
+ if (!messageObj || (parsedData?.success !== true && !isPurchaseIntentNotRecognized)) return
const previousManufacturingData = messageObj.manufacturingData
messageObj.type = parsedData.type || ''
@@ -2072,6 +2308,16 @@
messageObj.purchaseAnalysisData = null
messageObj.manufacturingData = null
messageObj.salesData = null
+ messageObj.purchaseData = null
+ messageObj.purchaseIntentData = null
+ messageObj.financeData = null
+
+ if (isPurchaseIntentNotRecognized) {
+ messageObj.purchaseIntentData = normalizePurchaseIntentNotRecognizedData(parsedData)
+ messageObj.chartOptions = null
+ messageObj.chartRenderReady = false
+ return
+ }
if (messageObj.type === 'todo_list' && parsedData.data) {
messageObj.tableData = parsedData.data
@@ -2087,11 +2333,23 @@
messageObj.manufacturingData = manufacturingData
}
+ const purchaseData = buildPurchaseStructuredData(parsedData)
+ if (purchaseData) {
+ 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 || {})
}
@@ -2136,6 +2394,7 @@
const getStructuredFallbackText = (parsedData) => {
if (!parsedData) return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
if (parsedData.type === 'todo_list') return '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+ if (parsedData.type === 'purchase_intent_not_recognized') return '鏈瘑鍒埌鍙墽琛岀殑閲囪喘鏌ヨ鏉′欢锛岃琛ュ厖鏌ヨ鏉′欢鍚庡啀璇曘��'
if (salesStructuredTypeSet.has(parsedData.type)) {
if (parsedData.type === 'sales_customer_churn_risk') return '宸蹭负鎮ㄧ敓鎴愬鎴锋祦澶遍闄╁垎鏋愩��'
if (parsedData.type === 'sales_collection_quote_strategy') return '宸蹭负鎮ㄧ敓鎴愬洖娆句笌鎶ヤ环绛栫暐寤鸿銆�'
@@ -2148,6 +2407,8 @@
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湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
}
@@ -3278,7 +3539,10 @@
payloadHiddenData: null,
purchaseAnalysisData: null,
manufacturingData: null,
- salesData: null
+ salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null,
+ financeData: null
})
outputState.value[botMsgIndex] = {
@@ -3404,7 +3668,10 @@
payloadHiddenData: null,
purchaseAnalysisData: null,
manufacturingData: null,
- salesData: null
+ salesData: null,
+ purchaseData: null,
+ purchaseIntentData: null,
+ financeData: null
}
messages.value.push(botMsg)
@@ -3436,28 +3703,6 @@
const extracted = extractEmbeddedSuccessJson(fullText)
if (extracted) {
applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
- } else {
- const extractJson = (text) => {
- const startIdx = text.indexOf('{"success": true')
- if (startIdx === -1) return null
-
- // 浠庡悗寰�鍓嶆壘鏈�鍚庝竴涓� '}'
- const lastBraceIdx = text.lastIndexOf('}')
- if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
-
- const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
- try {
- return JSON.parse(potentialJson)
- } catch (err) {
- return null
- }
- }
-
- const parsedData = extractJson(fullText)
- if (parsedData) {
- applyStructuredMessageData(currentMsg, parsedData, botMsgIndex, true)
- }
-
}
updateOutputState(fullText, botMsgIndex)
@@ -3475,24 +3720,6 @@
const extracted = extractEmbeddedSuccessJson(currentMsg.content)
if (extracted) {
applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
- } else {
- const extractJson = (text) => {
- const startIdx = text.indexOf('{"success": true')
- if (startIdx === -1) return null
- const lastBraceIdx = text.lastIndexOf('}')
- if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
- const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
- try {
- return JSON.parse(potentialJson)
- } catch (err) {
- return null
- }
- }
-
- const finalParsed = extractJson(currentMsg.content)
- if (finalParsed) {
- applyStructuredMessageData(currentMsg, finalParsed, botMsgIndex)
- }
}
}).catch(err => {
if (err.name === 'CanceledError' || err.name === 'AbortError') {
@@ -3850,30 +4077,30 @@
.ai-chat-trigger {
pointer-events: auto;
position: fixed;
- right: 24px;
- bottom: 100px;
- width: 56px;
- height: 56px;
+ right: 10px;
+ bottom: 12px;
+ width: 40px;
+ height: 40px;
background: $gradient-dark;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
- box-shadow: $shadow-deep, 0 0 0 2px rgba(0, 85, 212, 0.3) inset, 0 0 30px rgba(0, 119, 232, 0.2);
- transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
- z-index: 2001;
- animation: triggerPulse 3s ease-in-out infinite;
+ box-shadow: 0 8px 18px rgba(0, 68, 170, 0.32), 0 0 0 1px rgba(0, 136, 232, 0.32) inset;
+ transition: transform 0.22s ease, box-shadow 0.22s ease, opacity 0.22s ease;
+ z-index: 1020;
+ opacity: 0.9;
&::before {
content: '';
position: absolute;
- inset: -6px;
+ inset: -4px;
background: linear-gradient(135deg, rgba(0, 85, 212, 0.4), rgba(0, 136, 232, 0.3), rgba(90, 159, 224, 0.2));
border-radius: 50%;
z-index: -1;
- filter: blur(16px);
- animation: glowPulse 2s ease-in-out infinite alternate;
+ filter: blur(10px);
+ opacity: 0.35;
}
&::after {
@@ -3886,16 +4113,12 @@
}
&:hover {
- transform: scale(1.12) translateY(-4px);
- box-shadow: $shadow-deep, 0 0 0 3px rgba(0, 136, 232, 0.4) inset, 0 0 50px rgba(0, 136, 232, 0.3);
-
- &::before {
- animation: glowPulse 1s ease-in-out infinite alternate;
- }
+ transform: scale(1.05) translateY(-1px);
+ box-shadow: 0 10px 22px rgba(0, 68, 170, 0.38), 0 0 0 1px rgba(0, 136, 232, 0.42) inset;
.trigger-icon {
- transform: rotate(-8deg) scale(1.05);
- filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5));
+ transform: scale(1.03);
+ filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.42));
}
}
@@ -4893,6 +5116,38 @@
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;
+}
+
+.purchase-intent-quick-prompt-title {
+ font-size: 12px;
+ color: #4b5563;
+}
+
+.purchase-intent-quick-prompt-list {
+ margin-top: 8px;
+}
+
.purchase-confirm-card {
margin-top: 12px;
width: 100%;
--
Gitblit v1.9.3