From 9cecea613e29bb152bb6ea69a3cca94aa537c621 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期三, 20 五月 2026 14:56:34 +0800
Subject: [PATCH] bom同一层级只能选一样的工序,增加提示

---
 src/components/AIChatSidebar/index.vue | 1173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 1,135 insertions(+), 38 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index a2a365a..9836854 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>
@@ -242,7 +242,253 @@
                   </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.salesData" class="sales-structured-card">
+                  <div class="sales-structured-card__title">{{ getSalesTypeLabel(message.type) }}</div>
+
+                  <div v-if="message.salesData.summaryEntries?.length" class="sales-summary-grid">
+                    <div
+                        v-for="(entry, entryIndex) in message.salesData.summaryEntries"
+                        :key="`sales-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.type === 'sales_customer_churn_risk' && message.salesData.listItems?.length" class="sales-focus-list">
+                    <div
+                        v-for="(item, itemIndex) in message.salesData.listItems"
+                        :key="`risk-${item.customerName || itemIndex}`"
+                        class="sales-focus-item"
+                    >
+                      <div class="sales-focus-item__head">
+                        <strong>{{ formatStructuredValue(item.customerName) }}</strong>
+                        <div class="sales-focus-tags">
+                          <el-tag size="small" :type="getSalesLevelTagType(item.riskLevel)">
+                            {{ getSalesLevelLabel(item.riskLevel, 'risk') }}
+                          </el-tag>
+                          <el-tag size="small" type="warning">椋庨櫓鍒� {{ formatStructuredValue(item.riskScore) }}</el-tag>
+                        </div>
+                      </div>
+                      <div class="sales-focus-metrics">
+                        <span>寰呭洖娆撅細{{ formatStructuredValue(item.pendingAmount) }}</span>
+                        <span>寰呭洖娆惧崰姣旓細{{ formatStructuredValue(item.pendingRate) }}</span>
+                        <span>璺濅笂娆′笅鍗曪細{{ formatStructuredValue(item.daysSinceLastOrder) }}</span>
+                      </div>
+                      <div v-if="toStructuredStringArray(item.riskReasons).length" class="sales-focus-reasons">
+                        <el-tag
+                            v-for="(reason, reasonIndex) in toStructuredStringArray(item.riskReasons)"
+                            :key="`${item.customerName || itemIndex}-reason-${reasonIndex}`"
+                            size="small"
+                            type="danger"
+                            effect="plain"
+                        >
+                          {{ reason }}
+                        </el-tag>
+                      </div>
+                    </div>
+                  </div>
+
+                  <div v-if="message.type === 'sales_collection_quote_strategy' && message.salesData.listItems?.length" class="sales-focus-list">
+                    <div
+                        v-for="(item, itemIndex) in message.salesData.listItems"
+                        :key="`strategy-${item.customerName || itemIndex}`"
+                        class="sales-focus-item sales-focus-item--strategy"
+                    >
+                      <div class="sales-focus-item__head">
+                        <strong>{{ formatStructuredValue(item.customerName) }}</strong>
+                        <div class="sales-focus-tags">
+                          <el-tag size="small" :type="getSalesLevelTagType(item.priority)">
+                            {{ getSalesLevelLabel(item.priority, 'priority') }}
+                          </el-tag>
+                          <el-tag size="small" type="success">杞寲鐜� {{ formatStructuredValue(item.quoteConversionRate) }}</el-tag>
+                        </div>
+                      </div>
+                      <div class="sales-focus-metrics">
+                        <span>寰呭洖娆撅細{{ formatStructuredValue(item.pendingAmount) }}</span>
+                        <span v-if="item.nextAction">涓嬩竴姝ワ細{{ formatStructuredValue(item.nextAction) }}</span>
+                      </div>
+                      <p v-if="item.collectionStrategy" class="sales-strategy-line">
+                        <strong>鍥炴绛栫暐锛�</strong>{{ formatStructuredValue(item.collectionStrategy) }}
+                      </p>
+                      <p v-if="item.quotationStrategy" class="sales-strategy-line">
+                        <strong>鎶ヤ环绛栫暐锛�</strong>{{ formatStructuredValue(item.quotationStrategy) }}
+                      </p>
+                    </div>
+                  </div>
+
+                  <div
+                      v-if="message.salesData.listItems?.length && message.salesData.columns?.length && !isSalesFocusType(message.type)"
+                      class="table-wrapper manufacturing-table-wrapper"
+                  >
+                    <el-table :data="message.salesData.listItems" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.salesData.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.salesData.topCustomers?.length && message.salesData.topCustomerColumns?.length" class="table-wrapper manufacturing-table-wrapper">
+                    <div class="sales-section-title">閲嶇偣瀹㈡埛</div>
+                    <el-table :data="message.salesData.topCustomers" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.salesData.topCustomerColumns"
+                          :key="`top-customer-${col}`"
+                          :label="getStructuredFieldLabel(col)"
+                          min-width="120"
+                          show-overflow-tooltip
+                      >
+                        <template #default="{ row }">
+                          {{ formatStructuredValue(row[col]) }}
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                  </div>
+
+                  <div v-if="message.salesData.contractTrend?.length && message.salesData.contractTrendColumns?.length" class="table-wrapper manufacturing-table-wrapper">
+                    <div class="sales-section-title">鍚堝悓瓒嬪娍</div>
+                    <el-table :data="message.salesData.contractTrend" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.salesData.contractTrendColumns"
+                          :key="`contract-trend-${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.purchaseAnalysisData" class="purchase-confirm-card">
                   <div class="purchase-confirm-header">
                     <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '閲囪喘涓氬姟' }}</span>
@@ -523,7 +769,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'
@@ -634,6 +880,119 @@
   purchase_return_order: '閲囪喘閫�璐у崟',
   unknown: '鏈煡閲囪喘涓氬姟'
 }
+const salesStructuredTypeSet = new Set([
+  'sales_customer_profile_list',
+  'sales_quotation_list',
+  'sales_ledger_list',
+  'sales_return_list',
+  'sales_customer_interaction_list',
+  'sales_shipping_list',
+  'sales_dashboard',
+  'sales_customer_churn_risk',
+  'sales_collection_quote_strategy'
+])
+const salesFocusTypeSet = new Set([
+  'sales_customer_churn_risk',
+  'sales_collection_quote_strategy'
+])
+const salesTypeLabelMap = {
+  sales_customer_profile_list: '瀹㈡埛妗f',
+  sales_quotation_list: '閿�鍞姤浠�',
+  sales_ledger_list: '閿�鍞彴璐�',
+  sales_return_list: '閿�鍞��璐�',
+  sales_customer_interaction_list: '瀹㈡埛寰�鏉�',
+  sales_shipping_list: '鍙戣揣鍙拌处',
+  sales_dashboard: '閿�鍞寚鏍囩粺璁�',
+  sales_customer_churn_risk: '瀹㈡埛娴佸け椋庨櫓鍒嗘瀽',
+  sales_collection_quote_strategy: '鍥炴涓庢姤浠风瓥鐣ュ缓璁�'
+}
+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: '搴撳瓨閲�'
+}
+Object.assign(structuredFieldLabelMap, {
+  customerName: '瀹㈡埛鍚嶇О',
+  riskLevel: '椋庨櫓绛夌骇',
+  riskScore: '椋庨櫓璇勫垎',
+  riskReasons: '椋庨櫓鍘熷洜',
+  pendingAmount: '寰呭洖娆鹃噾棰�',
+  pendingRate: '寰呭洖娆惧崰姣�',
+  daysSinceLastOrder: '璺濅笂娆′笅鍗曞ぉ鏁�',
+  priority: '浼樺厛绾�',
+  quoteConversionRate: '鎶ヤ环杞寲鐜�',
+  collectionStrategy: '鍥炴绛栫暐',
+  quotationStrategy: '鎶ヤ环绛栫暐',
+  nextAction: '涓嬩竴姝ュ姩浣�',
+  contractAmountTotal: '鍚堝悓鎬婚',
+  receivedAmountTotal: '宸插洖娆鹃噾棰�',
+  pendingAmountTotal: '寰呭洖娆炬�婚',
+  shipRate: '鍙戣揣鐜�'
+})
 const purchasePayloadFieldLabelMap = {
   purchaseLedgers: '閲囪喘鍙拌处',
   productData: '浜у搧鏄庣粏',
@@ -785,6 +1144,437 @@
   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 inferSalesColumns = (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 normalizeSalesListItems = (items) => {
+  if (!Array.isArray(items)) return []
+  return items.filter(item => isPlainObject(item))
+}
+
+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 topCustomers = normalizeSalesListItems(rawData.topCustomers)
+  const contractTrend = normalizeSalesListItems(rawData.contractTrend)
+
+  return {
+    type,
+    summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+    listItems,
+    columns: inferSalesColumns(listItems),
+    topCustomers,
+    topCustomerColumns: inferSalesColumns(topCustomers),
+    contractTrend,
+    contractTrendColumns: inferSalesColumns(contractTrend)
+  }
+}
+
+const getSalesTypeLabel = (type = '') => salesTypeLabelMap[String(type || '')] || '閿�鍞煡璇㈢粨鏋�'
+
+const isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || ''))
+
+const getSalesLevelTagType = (level = '') => {
+  const normalizedLevel = String(level || '').toLowerCase()
+  if (normalizedLevel === 'high') return 'danger'
+  if (normalizedLevel === 'medium') return 'warning'
+  if (normalizedLevel === 'low') return 'success'
+  return 'info'
+}
+
+const getSalesLevelLabel = (level = '', mode = 'risk') => {
+  const normalizedLevel = String(level || '').toLowerCase()
+  const suffix = mode === 'priority' ? '浼樺厛绾�' : '椋庨櫓'
+  if (normalizedLevel === 'high') return `楂�${suffix}`
+  if (normalizedLevel === 'medium') return `涓�${suffix}`
+  if (normalizedLevel === 'low') return `浣�${suffix}`
+  if (!normalizedLevel) return mode === 'priority' ? '鏈垎绾�' : '鏈瘎浼�'
+  return normalizedLevel.toUpperCase()
+}
+
+const toStructuredStringArray = (value) => {
+  if (Array.isArray(value)) {
+    return value.map(item => String(item || '').trim()).filter(Boolean)
+  }
+  if (typeof value === 'string') {
+    return value
+      .split(/[,\uFF0C\u3001;\uFF1B\n]/)
+      .map(item => item.trim())
+      .filter(Boolean)
+  }
+  return []
+}
+
+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 +1824,9 @@
           tableData: null,
           payloadTreeData: null,
           payloadHiddenData: null,
+          purchaseAnalysisData: null,
+          manufacturingData: null,
+          salesData: null,
           localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
         }
 
@@ -1273,15 +2066,32 @@
 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
+  messageObj.salesData = null
 
   if (messageObj.type === 'todo_list' && parsedData.data) {
     messageObj.tableData = parsedData.data
   }
 
+  const salesData = buildSalesStructuredData(parsedData)
+  if (salesData) {
+    messageObj.salesData = salesData
+  }
+
+  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
+    messageObj.salesData = null
     if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
       initializePurchasePayloadTree(messageObj, parsedData.payload || {})
     }
@@ -1321,6 +2131,25 @@
   }
 
   return null
+}
+
+const getStructuredFallbackText = (parsedData) => {
+  if (!parsedData) return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+  if (parsedData.type === 'todo_list') return '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+  if (salesStructuredTypeSet.has(parsedData.type)) {
+    if (parsedData.type === 'sales_customer_churn_risk') return '宸蹭负鎮ㄧ敓鎴愬鎴锋祦澶遍闄╁垎鏋愩��'
+    if (parsedData.type === 'sales_collection_quote_strategy') return '宸蹭负鎮ㄧ敓鎴愬洖娆句笌鎶ヤ环绛栫暐寤鸿銆�'
+    if (parsedData.type === 'sales_dashboard') return '宸蹭负鎮ㄧ敓鎴愰攢鍞寚鏍囩粺璁°��'
+    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 '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
 }
 
 const buildPurchaseMaterialRankCharts = (parsedData) => {
@@ -2446,7 +3275,10 @@
     type: '',
     tableData: null,
     payloadTreeData: null,
-    payloadHiddenData: null
+    payloadHiddenData: null,
+    purchaseAnalysisData: null,
+    manufacturingData: null,
+    salesData: null
   })
 
   outputState.value[botMsgIndex] = {
@@ -2569,7 +3401,10 @@
     type: '',
     tableData: null,
     payloadTreeData: null,
-    payloadHiddenData: null
+    payloadHiddenData: null,
+    purchaseAnalysisData: null,
+    manufacturingData: null,
+    salesData: null
   }
   messages.value.push(botMsg)
 
@@ -2735,13 +3570,7 @@
     }
 
     if (!display) {
-      if (parsed.type === 'todo_list') {
-        display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
-      } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
-        display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
-      } else {
-        display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
-      }
+      display = getStructuredFallbackText(parsed)
     }
   } else if (startIdx !== -1) {
     const lastBraceIdx = output.lastIndexOf('}')
@@ -2763,13 +3592,7 @@
         }
 
         if (!display) {
-          if (parsed.type === 'todo_list') {
-            display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁锛�'
-          } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
-            display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛細'
-          } else {
-            display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
-          }
+          display = getStructuredFallbackText(parsed)
         }
       } catch (e) {
         // 瑙f瀽澶辫触锛岃鏄� JSON 杩樺湪浼犺緭涓垨鏍煎紡涓嶆纭�
@@ -3027,30 +3850,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 {
@@ -3063,16 +3886,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));
     }
   }
 
@@ -3792,6 +4611,284 @@
   }
 }
 
+.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;
+  }
+}
+
+.sales-structured-card {
+  margin-top: 12px;
+  width: 100%;
+  background: #fff;
+  border: 1px solid rgba(31, 122, 114, 0.2);
+  border-radius: 12px;
+  box-shadow: $shadow-card;
+  padding: 14px;
+}
+
+.sales-structured-card__title {
+  font-size: 14px;
+  font-weight: 700;
+  color: #1f5ddf;
+  margin-bottom: 10px;
+}
+
+.sales-summary-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.sales-summary-item {
+  border-radius: 10px;
+  padding: 10px 12px;
+  border: 1px solid rgba(30, 91, 255, 0.12);
+  background: linear-gradient(180deg, #f7fbff, #edf6ff);
+  min-height: 66px;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  gap: 6px;
+}
+
+.sales-summary-label {
+  font-size: 12px;
+  color: #4b5563;
+}
+
+.sales-summary-value {
+  font-size: 15px;
+  color: #1f2937;
+  line-height: 1.4;
+  word-break: break-all;
+}
+
+.sales-focus-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.sales-focus-item {
+  border-radius: 10px;
+  border: 1px solid rgba(30, 91, 255, 0.14);
+  background: #f8fbff;
+  padding: 10px 12px;
+}
+
+.sales-focus-item--strategy {
+  border-color: rgba(31, 122, 114, 0.22);
+  background: linear-gradient(180deg, #f7fcfb, #edf9f6);
+}
+
+.sales-focus-item__head {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 10px;
+  font-size: 13px;
+  color: #1f2937;
+}
+
+.sales-focus-tags {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  flex-wrap: wrap;
+  justify-content: flex-end;
+}
+
+.sales-focus-metrics {
+  margin-top: 8px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  font-size: 12px;
+  color: #475467;
+}
+
+.sales-focus-reasons {
+  margin-top: 8px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+}
+
+.sales-strategy-line {
+  margin: 8px 0 0;
+  font-size: 12px;
+  line-height: 1.6;
+  color: #334155;
+}
+
+.sales-section-title {
+  margin: 4px 0 8px;
+  font-size: 13px;
+  font-weight: 700;
+  color: $deep-blue;
+}
+
 .purchase-confirm-card {
   margin-top: 12px;
   width: 100%;

--
Gitblit v1.9.3