yyb
15 小时以前 04d6024553ac73e67148ce578cb01b541eebd02a
src/components/AIChatSidebar/index.vue
@@ -242,7 +242,123 @@
                  </el-table>
                </div>
                <!-- 打字中动画 -->
                <div v-if="message.manufacturingData" class="manufacturing-card">
                  <div class="manufacturing-card__title">{{ getManufacturingTypeLabel(message.type) }}</div>
                  <div
                      v-if="message.manufacturingData.summaryEntries?.length || message.manufacturingData.coreMetrics?.length"
                      class="manufacturing-summary-grid"
                  >
                    <div
                        v-for="(entry, entryIndex) in message.manufacturingData.summaryEntries"
                        :key="`summary-${entry.key}-${entryIndex}`"
                        class="manufacturing-summary-item"
                    >
                      <span class="manufacturing-summary-label">{{ entry.label }}</span>
                      <strong class="manufacturing-summary-value">{{ entry.value }}</strong>
                    </div>
                    <div
                        v-for="(metric, metricIndex) in message.manufacturingData.coreMetrics"
                        :key="`core-${metric.key}-${metricIndex}`"
                        class="manufacturing-summary-item manufacturing-summary-item--core"
                    >
                      <span class="manufacturing-summary-label">{{ metric.label }}</span>
                      <strong class="manufacturing-summary-value">{{ metric.value }}</strong>
                    </div>
                  </div>
                  <div v-if="message.manufacturingData.warningItems?.length" class="manufacturing-warning-list">
                    <div
                        v-for="(warning, warningIndex) in message.manufacturingData.warningItems"
                        :key="`warning-${warning.title || warningIndex}`"
                        class="manufacturing-warning-item"
                    >
                      <div class="manufacturing-warning-item__head">
                        <el-tag size="small" :type="getManufacturingWarningLevelType(warning.level)">
                          {{ getManufacturingWarningLevelLabel(warning.level) }}
                        </el-tag>
                        <strong>{{ warning.title || `预警 ${warningIndex + 1}` }}</strong>
                        <span v-if="warning.count !== '' && warning.count !== null && warning.count !== undefined" class="manufacturing-warning-count">
                          {{ warning.count }}
                        </span>
                      </div>
                      <p v-if="warning.detail" class="manufacturing-warning-detail">{{ warning.detail }}</p>
                    </div>
                  </div>
                  <div
                      v-if="message.manufacturingData.listItems?.length && message.manufacturingData.columns?.length"
                      class="table-wrapper manufacturing-table-wrapper"
                  >
                    <el-table :data="message.manufacturingData.listItems" border stripe size="small" style="width: 100%">
                      <el-table-column
                          v-for="col in message.manufacturingData.columns"
                          :key="col"
                          :label="getStructuredFieldLabel(col)"
                          min-width="140"
                          show-overflow-tooltip
                      >
                        <template #default="{ row }">
                          {{ formatStructuredValue(row[col]) }}
                        </template>
                      </el-table-column>
                    </el-table>
                  </div>
                  <div v-if="message.manufacturingData.actionCards?.length" class="manufacturing-action-list">
                    <div
                        v-for="(card, cardIndex) in message.manufacturingData.actionCards"
                        :key="card.runtimeKey || `${card.code}-${card.targetApi}-${cardIndex}`"
                        class="manufacturing-action-card"
                    >
                      <div class="manufacturing-action-card__head">
                        <strong>{{ card.name || `动作 ${cardIndex + 1}` }}</strong>
                        <el-tag size="small" type="info">{{ getNormalizedRequestMethod(card.method) }}</el-tag>
                      </div>
                      <div class="manufacturing-action-card__meta">
                        <span>{{ card.code || '--' }}</span>
                        <span>{{ card.targetApi || '--' }}</span>
                      </div>
                      <p v-if="card.description" class="manufacturing-action-card__desc">{{ card.description }}</p>
                      <div v-if="card.requiredFields?.length" class="manufacturing-required-fields">
                        <span>必填字段</span>
                        <el-tag
                            v-for="field in card.requiredFields"
                            :key="field"
                            size="small"
                            type="warning"
                        >
                          {{ getStructuredPathLabel(field) }}
                        </el-tag>
                      </div>
                      <el-input
                          v-model="card.payloadText"
                          type="textarea"
                          :rows="6"
                          resize="vertical"
                          :disabled="card.executing"
                          placeholder="请输入 JSON 请求参数"
                      />
                      <div class="manufacturing-action-footer">
                        <span
                            v-if="card.executeResult"
                            :class="['manufacturing-action-result', card.executeError ? 'error' : 'success']"
                        >
                          {{ card.executeResult }}
                        </span>
                        <el-button
                            type="primary"
                            size="small"
                            :loading="card.executing"
                            @click="executeManufacturingAction(message, card, cardIndex)"
                        >
                          确认并执行
                        </el-button>
                      </div>
                    </div>
                  </div>
                </div>
                <div v-if="message.purchaseAnalysisData" class="purchase-confirm-card">
                  <div class="purchase-confirm-header">
                    <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '采购业务' }}</span>
@@ -523,7 +639,7 @@
import request from '@/utils/request'
import * as echarts from 'echarts'
import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, Promotion, RefreshRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { builtInAssistants, generalAssistant } from './assistants'
import todoAssistantAvatar from '@/assets/AI/待办助手.png'
import salesAssistantAvatar from '@/assets/AI/销售助手.png'
@@ -633,6 +749,75 @@
  payment_registration: '付款登记',
  purchase_return_order: '采购退货单',
  unknown: '未知采购业务'
}
const manufacturingStructuredTypeSet = new Set([
  'manufacturing_site_snapshot',
  'manufacturing_plan_list',
  'manufacturing_workorder_list',
  'manufacturing_device_list',
  'manufacturing_device_repair_list',
  'manufacturing_quality_list',
  'manufacturing_material_list',
  'manufacturing_exception_list',
  'manufacturing_warning',
  'manufacturing_analysis',
  'manufacturing_action_plan'
])
const manufacturingListTypeSet = new Set([
  'manufacturing_plan_list',
  'manufacturing_workorder_list',
  'manufacturing_device_list',
  'manufacturing_device_repair_list',
  'manufacturing_quality_list',
  'manufacturing_material_list',
  'manufacturing_exception_list'
])
const manufacturingTypeLabelMap = {
  manufacturing_site_snapshot: '生产现场概览',
  manufacturing_plan_list: '计划查询',
  manufacturing_workorder_list: '工单查询',
  manufacturing_device_list: '设备查询',
  manufacturing_device_repair_list: '设备维修记录查询',
  manufacturing_quality_list: '质量查询',
  manufacturing_material_list: '物料查询',
  manufacturing_exception_list: '异常查询',
  manufacturing_warning: '预警看板',
  manufacturing_analysis: '经营分析',
  manufacturing_action_plan: '办理建议'
}
const structuredFieldLabelMap = {
  workOrderNo: '工单号',
  planEndTime: '计划结束时间',
  planStartTime: '计划开始时间',
  timeRange: '时间范围',
  startDate: '开始日期',
  endDate: '结束日期',
  warningCount: '预警数量',
  overduePlanCount: '逾期计划数',
  overdueWorkOrderCount: '逾期工单数',
  actionCount: '建议动作数',
  qualityOpenCount: '质量待处理数',
  lowStockCount: '低库存数',
  exceptionCount: '异常数',
  userId: '用户ID',
  tenantId: '租户ID',
  status: '状态',
  deviceName: '设备名称',
  deviceModel: '设备型号',
  pendingRepairCount: '待维修数',
  repairTime: '维修时间',
  repairName: '报修人',
  maintenanceName: '维修人员',
  level: '预警等级',
  title: '标题',
  count: '数量',
  detail: '详情',
  remark: '备注',
  createTime: '创建时间',
  updateTime: '更新时间',
  exceptionType: '异常类型',
  materialName: '物料名称',
  stockQty: '库存量'
}
const purchasePayloadFieldLabelMap = {
  purchaseLedgers: '采购台账',
@@ -785,6 +970,366 @@
  inventoryWarningQuantity: 'inventoryWarningQuantity',
  isInspected: 'isInspected',
  isChecked: 'isInspected'
}
const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
const stringifyStructuredPayload = (value, spaces = 2) => {
  if (typeof value === 'string') return value
  try {
    return JSON.stringify(value ?? {}, null, spaces)
  } catch (err) {
    return '{}'
  }
}
const structuredFieldTokenLabelMap = {
  time: '时间',
  range: '范围',
  start: '开始',
  end: '结束',
  date: '日期',
  warning: '预警',
  overdue: '逾期',
  plan: '计划',
  work: '工',
  order: '单',
  workorder: '工单',
  count: '数量',
  quality: '质量',
  low: '低',
  stock: '库存',
  exception: '异常',
  action: '动作',
  user: '用户',
  tenant: '租户',
  id: 'ID',
  no: '编号',
  number: '编号',
  code: '编码',
  name: '名称',
  status: '状态',
  level: '等级',
  title: '标题',
  detail: '详情',
  total: '总数',
  rate: '比率',
  type: '类型',
  pending: '待',
  repair: '维修',
  device: '设备',
  material: '物料'
}
const convertStructuredFieldKeyToChinese = (fieldKey = '') => {
  const key = String(fieldKey || '').trim()
  if (!key) return '-'
  if (structuredFieldLabelMap[key]) return structuredFieldLabelMap[key]
  if (/[\u4e00-\u9fa5]/.test(key)) return key
  const rawTokens = key
    .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
    .replace(/[_-]+/g, ' ')
    .trim()
    .split(/\s+/)
    .filter(Boolean)
    .map(token => token.toLowerCase())
  if (!rawTokens.length) return '字段'
  const mappedTokens = rawTokens
    .map(token => structuredFieldTokenLabelMap[token] || '')
    .filter(Boolean)
  if (mappedTokens.length) {
    return mappedTokens.join('')
  }
  return '字段'
}
const getStructuredFieldLabel = (fieldKey = '') => {
  return convertStructuredFieldKeyToChinese(fieldKey)
}
const getStructuredPathLabel = (fieldPath = '') => {
  const path = String(fieldPath || '').trim()
  if (!path) return '-'
  const segments = path
    .replace(/\[(\d+)]/g, '.$1')
    .split('.')
    .filter(Boolean)
  if (!segments.length) return getStructuredFieldLabel(path)
  return segments.map((segment) => {
    if (/^\d+$/.test(segment)) {
      return `第${Number(segment) + 1}项`
    }
    return getStructuredFieldLabel(segment)
  }).join(' / ')
}
const formatStructuredValue = (value) => {
  if (value === null || value === undefined || value === '') return '-'
  if (Array.isArray(value)) {
    const preview = value.slice(0, 3).map(item => formatStructuredValue(item)).join('、')
    return value.length > 3 ? `${preview} 等${value.length}项` : preview
  }
  if (isPlainObject(value)) return stringifyStructuredPayload(value, 0)
  return String(value)
}
const normalizeManufacturingSummaryEntries = (summary) => {
  if (!isPlainObject(summary)) return []
  return Object.entries(summary)
    .filter(([, value]) => value !== undefined && value !== null && `${value}`.trim() !== '')
    .map(([key, value]) => ({
      key,
      label: getStructuredFieldLabel(key),
      value: formatStructuredValue(value)
    }))
}
const normalizeManufacturingCoreMetrics = (coreMetrics) => {
  if (Array.isArray(coreMetrics)) {
    return coreMetrics.map((item, index) => {
      if (isPlainObject(item)) {
        const label = item.label || item.name || item.key || `指标${index + 1}`
        const metricValue = item.value ?? item.metricValue ?? item.data ?? '-'
        const unit = item.unit ? ` ${item.unit}` : ''
        return {
          key: String(item.key || item.name || index),
          label,
          value: `${formatStructuredValue(metricValue)}${unit}`.trim()
        }
      }
      return {
        key: String(index),
        label: `指标${index + 1}`,
        value: formatStructuredValue(item)
      }
    })
  }
  if (isPlainObject(coreMetrics)) {
    return Object.entries(coreMetrics).map(([key, value]) => ({
      key,
      label: getStructuredFieldLabel(key),
      value: formatStructuredValue(value)
    }))
  }
  return []
}
const normalizeManufacturingWarningItems = (items = []) => {
  if (!Array.isArray(items)) return []
  return items
    .filter(item => isPlainObject(item))
    .map(item => ({
      level: String(item.level || '').toLowerCase(),
      title: item.title || '',
      count: item.count ?? '',
      detail: item.detail ?? ''
    }))
}
const inferManufacturingColumns = (items = []) => {
  if (!Array.isArray(items) || !items.length) return []
  const fieldSet = new Set()
  items.forEach((item) => {
    if (!isPlainObject(item)) return
    Object.keys(item).forEach((key) => fieldSet.add(key))
  })
  return Array.from(fieldSet)
}
const getManufacturingActionCardRuntimeKey = (card = {}, index = 0) => {
  const code = String(card?.code || '').trim()
  const api = String(card?.targetApi || '').trim()
  const name = String(card?.name || '').trim()
  return `${code}::${api}::${name}::${index}`
}
const normalizeManufacturingActionCards = (actionCards = [], previousCards = []) => {
  const previousMap = new Map()
  if (Array.isArray(previousCards)) {
    previousCards.forEach((card, index) => {
      previousMap.set(getManufacturingActionCardRuntimeKey(card, index), card)
    })
  }
  return (Array.isArray(actionCards) ? actionCards : [])
    .filter(card => isPlainObject(card))
    .map((card, index) => {
      const runtimeKey = getManufacturingActionCardRuntimeKey(card, index)
      const previousCard = previousMap.get(runtimeKey)
      const fallbackPayloadText = stringifyStructuredPayload(card.examplePayload, 2)
      return {
        ...card,
        runtimeKey,
        payloadText: previousCard?.payloadText ?? fallbackPayloadText,
        executing: Boolean(previousCard?.executing),
        executed: Boolean(previousCard?.executed),
        executeResult: previousCard?.executeResult || '',
        executeError: Boolean(previousCard?.executeError)
      }
    })
}
const buildManufacturingStructuredData = (parsedData, previousData = null) => {
  const type = String(parsedData?.type || '')
  if (!manufacturingStructuredTypeSet.has(type)) return null
  const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
  const items = Array.isArray(rawData.items) ? rawData.items.filter(item => isPlainObject(item)) : []
  const warningItems = type === 'manufacturing_warning' ? normalizeManufacturingWarningItems(items) : []
  const listItems = manufacturingListTypeSet.has(type) ? items : []
  const actionCards = type === 'manufacturing_action_plan'
    ? normalizeManufacturingActionCards(rawData.actionCards, previousData?.actionCards)
    : []
  return {
    type,
    summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
    coreMetrics: normalizeManufacturingCoreMetrics(rawData.coreMetrics),
    listItems,
    columns: inferManufacturingColumns(listItems),
    warningItems,
    actionCards
  }
}
const getManufacturingTypeLabel = (type = '') => manufacturingTypeLabelMap[String(type || '')] || '制造结果'
const getManufacturingWarningLevelType = (level = '') => {
  const normalizedLevel = String(level || '').toLowerCase()
  if (normalizedLevel === 'high') return 'danger'
  if (normalizedLevel === 'medium') return 'warning'
  return 'info'
}
const getManufacturingWarningLevelLabel = (level = '') => {
  const normalizedLevel = String(level || '').toLowerCase()
  if (normalizedLevel === 'high') return '高'
  if (normalizedLevel === 'medium') return '中'
  return normalizedLevel ? normalizedLevel.toUpperCase() : '一般'
}
const normalizeRequestMethod = (method = 'POST') => {
  const normalized = String(method || 'POST').trim().toUpperCase()
  if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(normalized)) return normalized
  return 'POST'
}
const getNormalizedRequestMethod = (method) => normalizeRequestMethod(method)
const getPayloadValueByPath = (payload, fieldPath = '') => {
  const normalizedPath = String(fieldPath || '').trim()
  if (!normalizedPath || !isPlainObject(payload)) return undefined
  const pathSegments = normalizedPath
    .replace(/\[(\d+)]/g, '.$1')
    .split('.')
    .filter(Boolean)
  return pathSegments.reduce((current, segment) => {
    if (current === null || current === undefined) return undefined
    if (!['object', 'function'].includes(typeof current)) return undefined
    return current[segment]
  }, payload)
}
const getMissingRequiredFields = (requiredFields = [], payload = {}) => {
  if (!Array.isArray(requiredFields) || !requiredFields.length) return []
  return requiredFields.filter((fieldPath) => {
    const value = getPayloadValueByPath(payload, fieldPath)
    return !hasMeaningfulPayloadValue(value)
  })
}
const parseManufacturingActionPayload = (payloadText = '') => {
  const text = String(payloadText ?? '').trim()
  if (!text) return {}
  return JSON.parse(text)
}
const executeManufacturingAction = async (message, actionCard, cardIndex = 0) => {
  if (!message?.manufacturingData || !actionCard || actionCard.executing) return
  const actionName = actionCard.name || `动作 ${cardIndex + 1}`
  const targetApi = String(actionCard.targetApi || '').trim()
  if (!targetApi) {
    actionCard.executeError = true
    actionCard.executeResult = '缺少 targetApi,无法执行动作'
    return
  }
  let payload = {}
  try {
    payload = parseManufacturingActionPayload(actionCard.payloadText)
  } catch (err) {
    actionCard.executeError = true
    actionCard.executeResult = '请求参数不是合法 JSON,请检查后重试'
    return
  }
  const requiredFields = Array.isArray(actionCard.requiredFields) ? actionCard.requiredFields : []
  if (requiredFields.length && !isPlainObject(payload)) {
    actionCard.executeError = true
    actionCard.executeResult = '必填字段校验失败:请求参数必须是 JSON 对象'
    return
  }
  const missingFields = getMissingRequiredFields(requiredFields, payload)
  if (missingFields.length) {
    actionCard.executeError = true
    const missingFieldLabels = missingFields.map(field => getStructuredPathLabel(field))
    actionCard.executeResult = `缺少必填字段:${missingFieldLabels.join('、')}`
    return
  }
  try {
    await ElMessageBox.confirm(`确认执行「${actionName}」吗?`, '执行确认', {
      confirmButtonText: '确认执行',
      cancelButtonText: '取消',
      type: 'warning'
    })
  } catch (err) {
    return
  }
  actionCard.executing = true
  actionCard.executeError = false
  actionCard.executeResult = ''
  const method = normalizeRequestMethod(actionCard.method)
  const requestConfig = {
    url: targetApi,
    method: method.toLowerCase()
  }
  if (method === 'GET') {
    requestConfig.params = payload
  } else {
    requestConfig.data = payload
  }
  try {
    const res = await request(requestConfig)
    const successMsg = res?.msg || `${actionName}执行成功`
    actionCard.executed = true
    actionCard.executeError = false
    actionCard.executeResult = successMsg
    ElMessage.success(successMsg)
  } catch (err) {
    actionCard.executed = false
    actionCard.executeError = true
    actionCard.executeResult = err?.message || `${actionName}执行失败,请稍后重试`
  } finally {
    actionCard.executing = false
  }
}
// 历史会话相关
@@ -1034,6 +1579,8 @@
          tableData: null,
          payloadTreeData: null,
          payloadHiddenData: null,
          purchaseAnalysisData: null,
          manufacturingData: null,
          localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
        }
@@ -1273,15 +1820,25 @@
const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
  if (!messageObj || !parsedData?.success) return
  const previousManufacturingData = messageObj.manufacturingData
  messageObj.type = parsedData.type || ''
  messageObj.tableData = null
  messageObj.purchaseAnalysisData = null
  messageObj.manufacturingData = null
  if (messageObj.type === 'todo_list' && parsedData.data) {
    messageObj.tableData = parsedData.data
  }
  const manufacturingData = buildManufacturingStructuredData(parsedData, previousManufacturingData)
  if (manufacturingData) {
    messageObj.manufacturingData = manufacturingData
  }
  if (parsedData.action === 'confirm_required' && parsedData.businessType) {
    messageObj.type = 'purchase_analysis_confirm'
    messageObj.purchaseAnalysisData = parsedData
    messageObj.manufacturingData = null
    if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
      initializePurchasePayloadTree(messageObj, parsedData.payload || {})
    }
@@ -1321,6 +1878,19 @@
  }
  return null
}
const getStructuredFallbackText = (parsedData) => {
  if (!parsedData) return '正在为您展示分析结果...'
  if (parsedData.type === 'todo_list') return '已为您整理好相关数据。'
  if (manufacturingStructuredTypeSet.has(parsedData.type)) {
    if (parsedData.type === 'manufacturing_action_plan') return '已为您生成办理建议,请确认动作后执行。'
    if (parsedData.type === 'manufacturing_warning') return '已为您生成制造预警看板。'
    if (parsedData.type === 'manufacturing_analysis') return '已为您生成制造分析结果。'
    return '已返回制造查询结果。'
  }
  if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '已为您生成分析图表。'
  return '正在为您展示分析结果...'
}
const buildPurchaseMaterialRankCharts = (parsedData) => {
@@ -2446,7 +3016,9 @@
    type: '',
    tableData: null,
    payloadTreeData: null,
    payloadHiddenData: null
    payloadHiddenData: null,
    purchaseAnalysisData: null,
    manufacturingData: null
  })
  outputState.value[botMsgIndex] = {
@@ -2569,7 +3141,9 @@
    type: '',
    tableData: null,
    payloadTreeData: null,
    payloadHiddenData: null
    payloadHiddenData: null,
    purchaseAnalysisData: null,
    manufacturingData: null
  }
  messages.value.push(botMsg)
@@ -2735,13 +3309,7 @@
    }
    if (!display) {
      if (parsed.type === 'todo_list') {
        display = '已为您整理好相关数据。'
      } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
        display = '已为您生成分析图表。'
      } else {
        display = '正在为您展示分析结果...'
      }
      display = getStructuredFallbackText(parsed)
    }
  } else if (startIdx !== -1) {
    const lastBraceIdx = output.lastIndexOf('}')
@@ -2763,13 +3331,7 @@
        }
        if (!display) {
          if (parsed.type === 'todo_list') {
            display = '已为您整理好相关数据:'
          } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
            display = '已为您生成分析图表:'
          } else {
            display = '正在为您展示分析结果...'
          }
          display = getStructuredFallbackText(parsed)
        }
      } catch (e) {
        // 解析失败,说明 JSON 还在传输中或格式不正确
@@ -3792,6 +4354,171 @@
  }
}
.manufacturing-card {
  margin-top: 12px;
  width: 100%;
  background: #fff;
  border: 1px solid rgba(0, 85, 212, 0.12);
  border-radius: 12px;
  box-shadow: $shadow-card;
  padding: 14px;
}
.manufacturing-card__title {
  font-size: 14px;
  font-weight: 700;
  color: $deep-blue;
  margin-bottom: 10px;
}
.manufacturing-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px;
  margin-bottom: 12px;
}
.manufacturing-summary-item {
  border-radius: 10px;
  padding: 10px 12px;
  border: 1px solid rgba(0, 85, 212, 0.08);
  background: linear-gradient(180deg, #f8fbff, #f1f7ff);
  min-height: 66px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 6px;
}
.manufacturing-summary-item--core {
  border-color: rgba(30, 91, 255, 0.24);
}
.manufacturing-summary-label {
  font-size: 12px;
  color: #4b5563;
}
.manufacturing-summary-value {
  font-size: 15px;
  color: #1f2937;
  line-height: 1.4;
  word-break: break-all;
}
.manufacturing-warning-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}
.manufacturing-warning-item {
  border-radius: 10px;
  border: 1px solid rgba(245, 158, 11, 0.22);
  background: linear-gradient(135deg, rgba(255, 247, 237, 0.9), rgba(255, 255, 255, 0.98));
  padding: 10px 12px;
}
.manufacturing-warning-item__head {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #92400e;
  font-size: 13px;
}
.manufacturing-warning-count {
  margin-left: auto;
  font-weight: 700;
  color: #c2410c;
}
.manufacturing-warning-detail {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.6;
  color: #7c2d12;
  word-break: break-all;
}
.manufacturing-table-wrapper {
  margin-top: 10px;
}
.manufacturing-action-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
}
.manufacturing-action-card {
  border: 1px solid rgba(0, 85, 212, 0.1);
  border-radius: 10px;
  padding: 10px 12px;
  background: #f8fbff;
}
.manufacturing-action-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  color: #1f2937;
  margin-bottom: 8px;
}
.manufacturing-action-card__meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
  font-size: 12px;
  color: #64748b;
  word-break: break-all;
}
.manufacturing-action-card__desc {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.6;
  color: #475467;
}
.manufacturing-required-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
  font-size: 12px;
  color: #7c2d12;
}
.manufacturing-action-footer {
  margin-top: 8px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}
.manufacturing-action-result {
  flex: 1;
  font-size: 12px;
  line-height: 1.5;
  &.success {
    color: #1f9d55;
  }
  &.error {
    color: #d93025;
  }
}
.purchase-confirm-card {
  margin-top: 12px;
  width: 100%;