From 3b577bb9493c389c5a3f0dca4538ddbf41fb1387 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 29 四月 2026 11:17:51 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_NEW_pro' into dev_NEW_pro

---
 src/views/equipmentManagement/upkeep/index.vue                                         |   88 
 src/views/equipmentManagement/upkeep/Form/PlanModal.vue                                |   22 
 src/views/equipmentManagement/repair/Modal/RepairModal.vue                             |   16 
 src/layout/index.vue                                                                   |  264 +-
 src/views/equipmentManagement/repair/index.vue                                         |   22 
 src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue |  159 +
 src/api/productionManagement/productionOrder.js                                        |    2 
 src/components/AIChatSidebar/index.vue                                                 | 3001 ++++++++++++++++++++++++++++++++++
 src/views/productionManagement/productionProcess/Edit.vue                              |  168 +
 src/views/productionManagement/workOrderEdit/index.vue                                 |  447 ++--
 src/views/productionManagement/workOrderManagement/index.vue                           |  206 ++
 src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue     |   72 
 src/views/productionManagement/productionReporting/index.vue                           |   61 
 src/views/productionManagement/productionOrder/index.vue                               |   78 
 src/api/basicData/storageAttachment.js                                                 |   29 
 src/components/Dialog/FileList.vue                                                     |  253 ++
 vite.config.js                                                                         |    2 
 src/components/PurchaseAIChatSidebar/index.vue                                         |   24 
 src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue |  225 ++
 src/views/productionManagement/productionProcess/New.vue                               |  129 +
 20 files changed, 4,767 insertions(+), 501 deletions(-)

diff --git a/src/api/basicData/storageAttachment.js b/src/api/basicData/storageAttachment.js
new file mode 100644
index 0000000..56364a4
--- /dev/null
+++ b/src/api/basicData/storageAttachment.js
@@ -0,0 +1,29 @@
+// 闄勪欢椤甸潰鎺ュ彛
+import request from '@/utils/request'
+
+// 闄勪欢鏌ヨ
+export function attachmentList(query) {
+    return request({
+        url: '/basic/storage_attachment/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 闄勪欢鏂板
+export function createAttachment(data) {
+    return request({
+        url: '/basic/storage_attachment/add',
+        method: 'post',
+        data
+    })
+}
+
+// 闄勪欢鍒犻櫎
+export function deleteAttachment(data) {
+    return request({
+        url: '/basic/storage_attachment/delete',
+        method: 'delete',
+        data
+    })
+}
diff --git a/src/api/productionManagement/productionOrder.js b/src/api/productionManagement/productionOrder.js
index b87adbb..688abfc 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -116,7 +116,7 @@
 // 鐢熶骇璁㈠崟-琛ユ枡璁板綍鍒楄〃
 export function listMaterialSupplementRecord(query) {
   return request({
-    url: "/productOrderMaterial/supplementRecord",
+    url: "/productionOrderPickRecord/feeding",
     method: "get",
     params: query,
   });
diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
new file mode 100644
index 0000000..548ec31
--- /dev/null
+++ b/src/components/AIChatSidebar/index.vue
@@ -0,0 +1,3001 @@
+<template>
+  <div class="ai-chat-sidebar-wrapper">
+    <!-- 鎮诞鍥炬爣 -->
+    <div 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>
+        </div>
+      </el-tooltip>
+    </div>
+
+    <!-- 渚ц竟鏍忓璇濇 -->
+    <el-drawer
+        v-model="visible"
+        :size="drawerSize"
+        direction="rtl"
+        :with-header="true"
+        class="ai-chat-drawer"
+        :modal="false"
+        modal-class="ai-chat-overlay"
+        :show-close="false"
+        :append-to-body="false"
+        @close="handleClose"
+    >
+      <template #header>
+        <div class="drawer-header">
+          <div class="header-left">
+            <el-icon :size="20" class="header-icon"><component :is="currentAssistant.icon" /></el-icon>
+            <span class="title">{{ currentAssistant.title }}</span>
+          </div>
+          <div v-if="showAssistantSwitch" class="assistant-switcher">
+            <el-radio-group v-model="selectedAssistantKey" size="small">
+              <el-radio-button
+                  v-for="assistant in assistants"
+                  :key="assistant.key"
+                  :label="assistant.key"
+              >
+                {{ assistant.label }}
+              </el-radio-button>
+            </el-radio-group>
+          </div>
+          <div class="header-actions">
+            <el-tooltip content="浼氳瘽鍘嗗彶" placement="bottom">
+              <el-button link class="header-action-btn" @click="handleToggleHistory">
+                <el-icon :size="18"><Timer /></el-icon>
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="寮�鍚柊浼氳瘽" placement="bottom">
+              <el-button link class="header-action-btn" @click="handleNewChat">
+                <el-icon :size="18"><Plus /></el-icon>
+              </el-button>
+            </el-tooltip>
+            <div class="action-divider"></div>
+            <el-tooltip content="鍏抽棴" placement="bottom">
+              <el-button link class="header-action-btn close-btn" @click="handleManualClose">
+                <el-icon :size="18"><Close /></el-icon>
+              </el-button>
+            </el-tooltip>
+          </div>
+        </div>
+      </template>
+
+      <div class="chat-container">
+        <!-- 鍘嗗彶浼氳瘽鍒楄〃 -->
+        <div v-if="showHistory" class="history-panel">
+          <div class="history-header">
+            <span>鏈�杩戜細璇�</span>
+            <el-button link type="primary" @click="showHistory = false">杩斿洖瀵硅瘽</el-button>
+          </div>
+          <el-skeleton :loading="loadingSessions" animated>
+            <template #template>
+              <div v-for="i in 5" :key="i" style="padding: 10px">
+                <el-skeleton-item variant="p" style="width: 80%" />
+              </div>
+            </template>
+            <div class="session-list">
+              <div
+                  v-for="session in sessions"
+                  :key="session.memoryId"
+                  :class="['session-item', { active: uuid === session.memoryId }]"
+                  @click="selectSession(session)"
+              >
+                <el-icon><ChatDotSquare /></el-icon>
+                <span class="session-name" :title="session.lastMessage || '鏂颁細璇�'">
+                  {{ session.lastMessage || '鏂颁細璇�' }}
+                </span>
+                <el-button
+                    link
+                    type="danger"
+                    class="delete-btn"
+                    @click.stop="handleDeleteSession(session.memoryId)"
+                >
+                  <el-icon><Delete /></el-icon>
+                </el-button>
+              </div>
+              <el-empty v-if="sessions.length === 0" :description="currentAssistant.emptySessionText" />
+            </div>
+          </el-skeleton>
+        </div>
+
+        <div v-else class="chat-main">
+          <div :class="['chat-hero', { compact: hasMessages }]">
+            <div :class="['assistant-stand', { thinking: isSending, compact: hasMessages }]">
+              <div class="assistant-halo"></div>
+              <div class="assistant-scan-ring"></div>
+              <div class="assistant-orbit assistant-orbit-a"></div>
+              <div class="assistant-orbit assistant-orbit-b"></div>
+              <div class="assistant-bot">
+                <div class="assistant-bot-antenna assistant-bot-antenna-left"></div>
+                <div class="assistant-bot-antenna assistant-bot-antenna-right"></div>
+                <div class="assistant-bot-head">
+                  <div class="assistant-bot-head-glow"></div>
+                  <div class="assistant-bot-eye assistant-bot-eye-left"></div>
+                  <div class="assistant-bot-eye assistant-bot-eye-right"></div>
+                  <div class="assistant-bot-mouth"></div>
+                </div>
+                <div class="assistant-bot-neck"></div>
+                <div class="assistant-bot-body">
+                  <div class="assistant-bot-core">
+                    <div class="assistant-bot-core-ring"></div>
+                    <el-icon :size="22"><component :is="currentAssistant.icon" /></el-icon>
+                  </div>
+                  <div class="assistant-bot-arm assistant-bot-arm-left"></div>
+                  <div class="assistant-bot-arm assistant-bot-arm-right"></div>
+                </div>
+              </div>
+              <div class="assistant-status">
+                <span class="assistant-status-dot"></span>
+                {{ isSending ? '鎬濊�冧腑...' : currentAssistant.label }}
+              </div>
+              <div class="assistant-base assistant-base-lg"></div>
+              <div class="assistant-base assistant-base-md"></div>
+              <div class="assistant-base assistant-base-sm"></div>
+            </div>
+
+            <div :class="['welcome-card', { compact: hasMessages }]">
+              <div class="welcome-eyebrow">鏅鸿兘鍔╂墜</div>
+              <h3 class="welcome-title">
+                鎮ㄥソ
+                <br />
+                鎴戞槸{{ currentAssistant.label }}鍒嗘瀽瑙h鍔╂墜
+              </h3>
+              <p class="welcome-desc">
+                {{ currentAssistant.description || '鎴戝彲浠ュ洿缁曚笟鍔¢棶棰樻彁渚涜В璇汇�佹煡璇㈠缓璁拰鍒嗘瀽鏀寔锛屽府鍔╀綘鏇村揩瀹屾垚鍒ゆ柇涓庡鐞嗐��' }}
+              </p>
+
+              <div class="quick-prompt-list">
+                <button
+                    v-for="prompt in displayedQuickPrompts"
+                    :key="prompt"
+                    type="button"
+                    class="quick-prompt-btn"
+                    :disabled="isSending"
+                    @click="sendQuickPrompt(prompt)"
+                >
+                  {{ prompt }}
+                </button>
+              </div>
+
+              <button
+                  v-if="quickPrompts.length > quickPromptLimit"
+                  type="button"
+                  class="more-prompts-btn"
+                  @click="refreshQuickPrompts"
+              >
+                <el-icon><RefreshRight /></el-icon>
+                <span>鎹竴鎹�</span>
+              </button>
+            </div>
+          </div>
+
+          <div v-show="!hasMessages" class="hero-dot-grid" aria-hidden="true">
+            <span v-for="dot in 28" :key="dot"></span>
+          </div>
+
+          <div class="message-list" ref="messageListRef">
+            <div
+                v-for="(message, index) in messages"
+                :key="index"
+                :class="['message-item', message.isUser ? 'user-message' : 'bot-message']"
+            >
+              <div class="avatar">
+                <el-icon v-if="message.isUser"><User /></el-icon>
+                <el-icon v-else><Cpu /></el-icon>
+              </div>
+              <div class="message-content">
+                <!-- 鏂囨湰鍐呭 -->
+                <div class="text-box" v-html="message.htmlContent"></div>
+
+                <!-- 鍥捐〃鍐呭 -->
+                <div v-if="message.chartOptions && message.chartRenderReady" class="charts-wrapper">
+                  <div
+                      v-for="(option, key) in message.chartOptions"
+                      :key="key"
+                      class="chart-item"
+                      :id="`ai-chart-${index}-${key}`"
+                  ></div>
+                </div>
+
+                <!-- 琛ㄦ牸鍐呭 -->
+                <div v-if="message.type === 'todo_list' && message.tableData" class="table-wrapper">
+                  <el-table :data="message.tableData.items" border stripe size="small" style="width: 100%">
+                    <el-table-column
+                        v-for="col in message.tableData.columns"
+                        :key="col"
+                        :prop="col"
+                        :label="columnLabelMap[col] || col"
+                        min-width="100"
+                        show-overflow-tooltip
+                    />
+                  </el-table>
+                </div>
+
+                <!-- 鎵撳瓧涓姩鐢� -->
+                <div v-if="message.isTyping" class="typing-indicator">
+                  <span class="dot"></span>
+                  <span class="dot"></span>
+                  <span class="dot"></span>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <div class="input-area">
+            <div class="input-actions">
+              <el-button link class="utility-action-btn" type="primary" size="small" @click="handleNewChat">
+                <el-icon><Plus /></el-icon>鏂颁細璇�
+              </el-button>
+              <el-button v-if="isSending" link class="utility-action-btn stop-action-btn" type="danger" size="small" @click="stopGeneration">
+                <el-icon><VideoPause /></el-icon>鍋滄鐢熸垚
+              </el-button>
+              <el-upload
+                  v-if="currentAssistant.allowFileUpload"
+                  class="file-upload-trigger"
+                  action="#"
+                  :auto-upload="false"
+                  :show-file-list="false"
+                  :on-change="handleFileChange"
+                  :disabled="isSending"
+              >
+                <el-button link class="utility-action-btn upload-action-btn" type="primary" size="small" :disabled="isSending">
+                  <el-icon><Upload /></el-icon>鍒嗘瀽鏂囦欢
+                </el-button>
+              </el-upload>
+            </div>
+            <div class="input-box">
+              <div v-if="selectedFile" class="selected-file-tag">
+                <el-icon><Document /></el-icon>
+                <span class="file-name">{{ selectedFile.name }}</span>
+                <el-icon class="remove-file" @click="removeSelectedFile"><Close /></el-icon>
+              </div>
+              <el-input
+                  v-model="inputMessage"
+                  type="textarea"
+                  :rows="selectedFile ? 2 : 3"
+                  :placeholder="currentAssistant.placeholder"
+                  resize="none"
+                  @keydown.enter.exact.prevent="sendMessage"
+              />
+              <el-button
+                  type="primary"
+                  class="send-btn"
+                  :disabled="isSending || (!inputMessage.trim() && !selectedFile)"
+                  @click="sendMessage"
+                  aria-label="鍙戦��"
+              >
+                <el-icon><Promotion /></el-icon>
+              </el-button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
+import request from '@/utils/request'
+import * as echarts from 'echarts'
+import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, ShoppingCart, Promotion, RefreshRight } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  assistants: {
+    type: Array,
+    default: () => []
+  },
+  defaultAssistant: {
+    type: String,
+    default: ''
+  }
+})
+
+const builtInAssistants = [
+  {
+    key: 'general',
+    label: '寰呭姙鍔╃悊',
+    title: '寰呭姙鏅鸿兘鍔╃悊',
+    tooltip: '寰呭姙鍔╂墜',
+    icon: Cpu,
+    apiBase: '/xiaozhi',
+    storageKey: 'ai_chat_uuid',
+    placeholder: '璇疯緭鍏ユ偍鐨勯棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+    welcomeMessage: '浣犲ソ',
+    description: '鎴戝彲浠ュ洖绛斾綘鐨勯棶棰橈紝涓轰綘鎻愪緵涓氬姟鏁版嵁瑙h淇℃伅銆佸鐞嗗缓璁拰杈呭姪鍐崇瓥鏀寔銆�',
+    allowFileUpload: true,
+    emptySessionText: '鏆傛棤鍘嗗彶浼氳瘽'
+  },
+  {
+    key: 'purchase',
+    label: '閲囪喘鍔╃悊',
+    title: '閲囪喘鏅鸿兘鍔╃悊',
+    tooltip: '閲囪喘鏅鸿兘鍔╃悊',
+    icon: ShoppingCart,
+    apiBase: '/purchase-ai',
+    storageKey: 'purchase_ai_chat_uuid',
+    placeholder: '璇疯緭鍏ラ噰璐棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+    welcomeMessage: '浣犲ソ',
+    description: '鎴戝彲浠ュ崗鍔╀綘鍒嗘瀽閲囪喘璁㈠崟銆佸埌璐ц繘搴︺�佷緵搴斿晢琛ㄧ幇鍜屼粯娆炬儏鍐碉紝甯姪浣犲揩閫熷畾浣嶉噰璐紓甯搞��',
+    allowFileUpload: false,
+    emptySessionText: '鏆傛棤閲囪喘浼氳瘽'
+  }
+]
+
+const assistants = computed(() => props.assistants?.length ? props.assistants : builtInAssistants)
+const selectedAssistantKey = ref(props.defaultAssistant || assistants.value[0]?.key || 'general')
+const currentAssistant = computed(() => assistants.value.find(item => item.key === selectedAssistantKey.value) || assistants.value[0] || builtInAssistants[0])
+const showAssistantSwitch = computed(() => assistants.value.length > 1)
+const assistantQuickPromptMap = {
+  general: [
+    '鎴戝綋鍓嶆湁鍝簺瀹℃壒寰呭姙闇�瑕佸鐞嗭紵',
+    '甯垜鍒楀嚭浠婂ぉ鏂板鐨勫鎵瑰緟鍔炪��',
+    '褰撳墠寰呮垜瀹℃壒鐨勫崟鎹紝鎸夋椂闂村�掑簭鍒楀嚭鏉ャ��',
+    '鎴戝彂璧风殑瀹℃壒閲岋紝鍝簺杩樺湪澶勭悊涓紵',
+    '鏌ヨ娴佺▼缂栧彿 XXX 鐨勫鎵硅鎯呫��',
+    '娴佺▼缂栧彿 XXX 鐜板湪鍗″湪鍝釜瀹℃壒鑺傜偣锛熷綋鍓嶅鎵逛汉鏄皝锛�',
+    '甯垜鏌ョ湅娴佺▼缂栧彿 XXX 鐨勫鎵规祦杞褰曘��',
+    '杩�7澶╂垜鐨勫鎵瑰緟鍔炵粺璁℃儏鍐垫�庝箞鏍凤紵',
+    '鏈湀鎴戠殑瀹℃壒涓紝閫氳繃銆侀┏鍥炪�佸鐞嗕腑鍚勬湁澶氬皯锛�',
+    '杩�30澶╁悇绫诲瀷瀹℃壒鏁伴噺鍒嗗竷鏄粈涔堬紵',
+    '甯垜瀹℃壒閫氳繃娴佺▼缂栧彿 XXX锛屽娉ㄢ�滃悓鎰忊�濄��',
+    '甯垜椹冲洖娴佺▼缂栧彿 XXX锛屽娉ㄢ�滆琛ュ厖璇存槑鈥濄��',
+    '鎾ら攢鎴戝垰鍒氬娴佺▼缂栧彿 XXX 鐨勫鎵规搷浣溿��',
+    '甯垜淇敼娴佺▼缂栧彿 XXX 鐨勫娉ㄤ负鈥滃凡琛ュ厖闄勪欢鈥濄��',
+    '鍒犻櫎鎴戝彂璧风殑娴佺▼缂栧彿 XXX銆�'
+  ],
+  purchase: [
+    '鏈湀閲囪喘閲戦鎺掑悕鍓嶅崄鐨勭墿鏂欐湁鍝簺锛�',
+    '鍝簺閲囪喘璁㈠崟杩樻湭鍏ュ簱锛�',
+    '鏈�杩�7澶╀緵搴斿晢鍒拌揣寮傚父鏈夊摢浜涳紵',
+    '甯垜缁熻寰呬粯娆鹃噰璐崟',
+    '鍒楀嚭鏈湀閲囪喘閫�璐ф儏鍐�'
+  ]
+}
+const quickPromptLimit = 3
+const quickPromptStart = ref(0)
+const quickPrompts = computed(() => {
+  const assistant = currentAssistant.value || {}
+  if (Array.isArray(assistant.quickPrompts) && assistant.quickPrompts.length) {
+    return assistant.quickPrompts
+  }
+  return assistantQuickPromptMap[assistant.key] || assistantQuickPromptMap.general
+})
+const displayedQuickPrompts = computed(() => {
+  const prompts = quickPrompts.value || []
+  if (prompts.length <= quickPromptLimit) return prompts
+
+  const result = []
+  for (let i = 0; i < quickPromptLimit; i++) {
+    result.push(prompts[(quickPromptStart.value + i) % prompts.length])
+  }
+  return result
+})
+const hasMessages = computed(() => messages.value.length > 0)
+
+const visible = ref(false)
+const windowWidth = ref(window.innerWidth)
+const drawerSize = computed(() => {
+  if (windowWidth.value < 768) return '100%'
+  if (windowWidth.value < 1200) return '500px'
+  return '600px'
+})
+const messageListRef = ref(null)
+const isSending = ref(false)
+const currentAbortController = ref(null)
+const inputMessage = ref('')
+const selectedFile = ref(null)
+const messages = ref([])
+const uuid = ref('')
+const chartInstances = ref({})
+const resizeHandlers = ref([])
+const outputState = ref({})
+
+// 鍘嗗彶浼氳瘽鐩稿叧
+const showHistory = ref(false)
+const sessions = ref([])
+const loadingSessions = ref(false)
+
+const abortCurrentRequest = () => {
+  if (!currentAbortController.value) return
+
+  currentAbortController.value.abort()
+  currentAbortController.value = null
+  isSending.value = false
+
+  const lastMsg = messages.value[messages.value.length - 1]
+  if (lastMsg && !lastMsg.isUser) {
+    lastMsg.isTyping = false
+  }
+}
+
+const toggleHistory = () => {
+  showHistory.value = !showHistory.value
+  if (showHistory.value) {
+    loadSessions()
+  }
+}
+
+const handleToggleHistory = () => {
+  if (isSending.value) {
+    abortCurrentRequest()
+  }
+  toggleHistory()
+}
+
+const loadSessions = async () => {
+  loadingSessions.value = true
+  try {
+    const res = await request.get(`${currentAssistant.value.apiBase}/history/sessions`)
+    if (res.code === 200) {
+      sessions.value = res.data || []
+    }
+  } catch (err) {
+    console.error('Failed to load sessions', err)
+  } finally {
+    loadingSessions.value = false
+  }
+}
+
+const selectSession = async (session) => {
+  showHistory.value = false
+  uuid.value = session.memoryId
+  localStorage.setItem(currentAssistant.value.storageKey, uuid.value)
+
+  // 鍔犺浇浼氳瘽娑堟伅
+  try {
+    const res = await request.get(`${currentAssistant.value.apiBase}/history/messages/${uuid.value}`)
+    if (res.code === 200) {
+      disposeCharts()
+      messages.value = []
+      const historyMsgs = res.data || []
+
+      // 閲嶆柊鏋勯�犳秷鎭垪琛ㄥ苟瑙f瀽
+      historyMsgs.forEach((msg, idx) => {
+        const isUser = msg.role === 'user'
+        const botMsgIndex = messages.value.length
+
+        const messageObj = {
+          isUser,
+          content: msg.content,
+          htmlContent: '',
+          isTyping: false,
+          chartOptions: null,
+          chartRenderReady: false,
+          type: '',
+          tableData: null
+        }
+
+        messages.value.push(messageObj)
+
+        if (!isUser) {
+          outputState.value[botMsgIndex] = {
+            isPaused: false,
+            jsonBlockStartPos: -1,
+            jsBlockStartPos: -1,
+            blockEndPos: -1,
+            hasRenderedChart: false
+          }
+
+          // 瑙f瀽鍘嗗彶娑堟伅涓殑 JSON
+          const extracted = extractEmbeddedSuccessJson(msg.content)
+          if (extracted) {
+            applyStructuredMessageData(messageObj, extracted.data, botMsgIndex)
+          }
+
+          updateOutputState(msg.content, botMsgIndex)
+          messageObj.htmlContent = convertStreamOutput(msg.content, botMsgIndex)
+        } else {
+          messageObj.htmlContent = convertTextToHtml(msg.content)
+        }
+      })
+      scrollToBottom()
+    }
+  } catch (err) {
+    console.error('Failed to load messages', err)
+  }
+}
+
+const handleDeleteSession = async (memoryId) => {
+  try {
+    const res = await request.delete(`${currentAssistant.value.apiBase}/history/${memoryId}`)
+    if (res.code === 200) {
+      loadSessions()
+      if (uuid.value === memoryId) {
+        newChat()
+      }
+    }
+  } catch (err) {
+    console.error('Failed to delete session', err)
+  }
+}
+
+const columnLabelMap = {
+  approveId: '瀹℃壒缂栧彿',
+  approveType: '瀹℃壒绫诲瀷',
+  approveUserName: '瀹℃壒浜�',
+  approveUserCurrentName: '褰撳墠澶勭悊浜�',
+  approveReason: '瀹℃壒鍘熷洜',
+  approveStatus: '瀹℃壒鐘舵��',
+  createTime: '鍒涘缓鏃堕棿'
+}
+
+onMounted(() => {
+  initUUID()
+  // 鍒濆娆㈣繋
+  if (messages.value.length === 0) {
+    hello()
+  }
+  window.addEventListener('resize', handleWindowResize)
+})
+
+onUnmounted(() => {
+  disposeCharts()
+  window.removeEventListener('resize', handleWindowResize)
+})
+
+watch(selectedAssistantKey, (nextKey, prevKey) => {
+  if (!prevKey || nextKey === prevKey) return
+
+  abortCurrentRequest()
+  disposeCharts()
+  messages.value = []
+  outputState.value = {}
+  sessions.value = []
+  showHistory.value = false
+  selectedFile.value = null
+  inputMessage.value = ''
+  quickPromptStart.value = 0
+  initUUID()
+  hello()
+})
+
+const handleWindowResize = () => {
+  windowWidth.value = window.innerWidth
+}
+
+const toggleSidebar = () => {
+  visible.value = !visible.value
+  if (visible.value) {
+    scrollToBottom()
+  }
+}
+
+const handleClose = () => {
+  visible.value = false
+}
+
+const handleManualClose = () => {
+  if (isSending.value) {
+    abortCurrentRequest()
+  }
+  handleClose()
+}
+
+const initUUID = () => {
+  let storedUUID = localStorage.getItem(currentAssistant.value.storageKey)
+  if (!storedUUID) {
+    storedUUID = Math.random().toString(36).substring(2, 10) + Date.now().toString(36).substring(4)
+    localStorage.setItem(currentAssistant.value.storageKey, storedUUID)
+  }
+  uuid.value = storedUUID
+}
+
+const hello = () => {
+  sendRequest(currentAssistant.value.welcomeMessage || '浣犲ソ')
+}
+
+const newChat = () => {
+  disposeCharts()
+  messages.value = []
+  outputState.value = {}
+  sessions.value = []
+  showHistory.value = false
+  selectedFile.value = null
+  quickPromptStart.value = 0
+  localStorage.removeItem(currentAssistant.value.storageKey)
+  initUUID()
+  hello()
+}
+
+const handleNewChat = () => {
+  if (isSending.value) {
+    abortCurrentRequest()
+  }
+  newChat()
+}
+
+const sendQuickPrompt = (prompt) => {
+  if (!prompt || isSending.value) return
+  inputMessage.value = prompt
+  sendMessage()
+}
+
+const refreshQuickPrompts = () => {
+  const prompts = quickPrompts.value || []
+  if (prompts.length <= quickPromptLimit) return
+  quickPromptStart.value = (quickPromptStart.value + quickPromptLimit) % prompts.length
+}
+
+const disposeCharts = () => {
+  Object.values(chartInstances.value).forEach(chart => chart.dispose())
+  resizeHandlers.value.forEach(handler => window.removeEventListener('resize', handler))
+  chartInstances.value = {}
+  resizeHandlers.value = []
+}
+
+const extractEmbeddedSuccessJson = (text) => {
+  if (!text || typeof text !== 'string') return null
+
+  const startIdx = text.indexOf('{"success"')
+  if (startIdx === -1) return null
+
+  for (let i = startIdx; i < text.length; i++) {
+    if (text[i] !== '{') continue
+
+    let depth = 0
+    let inString = false
+    let escaped = false
+
+    for (let j = i; j < text.length; j++) {
+      const char = text[j]
+
+      if (inString) {
+        if (escaped) {
+          escaped = false
+        } else if (char === '\\') {
+          escaped = true
+        } else if (char === '"') {
+          inString = false
+        }
+        continue
+      }
+
+      if (char === '"') {
+        inString = true
+        continue
+      }
+
+      if (char === '{') {
+        depth++
+      } else if (char === '}') {
+        depth--
+        if (depth === 0) {
+          const candidate = text.slice(i, j + 1)
+          try {
+            const parsed = JSON.parse(candidate)
+            if (parsed?.success === true) {
+              return {
+                data: parsed,
+                startIdx: i,
+                endIdx: j + 1
+              }
+            }
+          } catch (err) {
+            continue
+          }
+        }
+      }
+    }
+  }
+
+  return null
+}
+
+const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
+  if (!messageObj || !parsedData?.success) return
+
+  messageObj.type = parsedData.type || ''
+
+  if (messageObj.type === 'todo_list' && parsedData.data) {
+    messageObj.tableData = parsedData.data
+  }
+
+  if (parsedData.charts && Object.keys(parsedData.charts).length > 0) {
+    messageObj.chartOptions = parsedData.charts
+    messageObj.chartRenderReady = true
+
+    if (shouldRenderCharts) {
+      renderCharts(msgIndex, messageObj.chartOptions)
+      if (outputState.value[msgIndex]) {
+        outputState.value[msgIndex].hasRenderedChart = true
+      }
+    }
+  }
+}
+
+const scrollToBottom = () => {
+  nextTick(() => {
+    if (messageListRef.value) {
+      messageListRef.value.scrollTop = messageListRef.value.scrollHeight
+    }
+  })
+}
+
+const handleFileChange = (file) => {
+  if (!file) return
+  const rawFile = file.raw
+  if (rawFile) {
+    // 闄愬埗鏂囦欢澶у皬锛屼緥濡� 10MB
+    const isLt10M = rawFile.size / 1024 / 1024 < 10
+    if (!isLt10M) {
+      ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!')
+      return
+    }
+    selectedFile.value = rawFile
+  }
+}
+
+const removeSelectedFile = () => {
+  selectedFile.value = null
+}
+
+const analyzeFile = async (file, message = '') => {
+  if (isSending.value) return
+  isSending.value = true
+  currentAbortController.value = new AbortController()
+
+  const userMsg = message ? `${message}\n[涓婁紶鏂囦欢鍒嗘瀽] ${file.name}` : `[涓婁紶鏂囦欢鍒嗘瀽] ${file.name}`
+  messages.value.push({
+    isUser: true,
+    content: userMsg,
+    htmlContent: convertTextToHtml(userMsg),
+    isTyping: false
+  })
+
+  const botMsgIndex = messages.value.length
+  messages.value.push({
+    isUser: false,
+    content: '',
+    htmlContent: '',
+    isTyping: true,
+    chartOptions: null,
+    chartRenderReady: false,
+    type: '',
+    tableData: null
+  })
+
+  outputState.value[botMsgIndex] = {
+    isPaused: false,
+    jsonBlockStartPos: -1,
+    jsBlockStartPos: -1,
+    blockEndPos: -1,
+    hasRenderedChart: false
+  }
+
+  scrollToBottom()
+
+  const formData = new FormData()
+  formData.append('file', file)
+  formData.append('memoryId', uuid.value)
+  if (message.trim()) {
+    formData.append('message', message.trim())
+  }
+
+  request.post(`${currentAssistant.value.apiBase}/analyze-file`, formData, {
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    },
+    signal: currentAbortController.value.signal,
+    onDownloadProgress: (e) => {
+      const fullText = e.target ? e.target.responseText : (e.event ? e.event.target.responseText : '')
+      if (!fullText) return
+
+      const currentMsg = messages.value[botMsgIndex]
+      if (!currentMsg) return
+
+      currentMsg.content = fullText
+
+      // 瑙f瀽 JSON 鏁版嵁锛堥拡瀵瑰祵鍏ュ紡 JSON锛�
+      const extracted = extractEmbeddedSuccessJson(fullText)
+      if (extracted) {
+        applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex, !outputState.value[botMsgIndex].hasRenderedChart)
+      }
+
+      updateOutputState(fullText, botMsgIndex)
+      currentMsg.htmlContent = convertStreamOutput(fullText, botMsgIndex)
+      scrollToBottom()
+    }
+  }).then(() => {
+    const currentMsg = messages.value[botMsgIndex]
+    currentMsg.isTyping = false
+    isSending.value = false
+    currentAbortController.value = null
+
+    // 鏈�缁堣В鏋愮‘淇濆浘琛ㄦ覆鏌�
+    if (currentMsg.chartOptions && !outputState.value[botMsgIndex].hasRenderedChart) {
+      renderCharts(botMsgIndex, currentMsg.chartOptions)
+      outputState.value[botMsgIndex].hasRenderedChart = true
+    }
+  }).catch(err => {
+    if (err.name === 'CanceledError' || err.name === 'AbortError') {
+      console.log('Analysis aborted by user')
+      return
+    }
+    console.error('File analysis error:', err)
+    const errorMsg = '鎶辨瓑锛屾枃浠跺垎鏋愯繃绋嬩腑閬囧埌浜嗕竴鐐归棶棰橈紝璇风◢鍚庡啀璇曘��'
+    if (messages.value[botMsgIndex]) {
+      messages.value[botMsgIndex].content = errorMsg
+      messages.value[botMsgIndex].htmlContent = convertTextToHtml(errorMsg)
+      messages.value[botMsgIndex].isTyping = false
+    }
+    isSending.value = false
+    currentAbortController.value = null
+  })
+}
+
+const sendMessage = () => {
+  const msg = inputMessage.value?.trim() || ''
+  if ((msg || selectedFile.value) && !isSending.value) {
+    if (selectedFile.value) {
+      analyzeFile(selectedFile.value, msg)
+      selectedFile.value = null
+    } else {
+      sendRequest(msg)
+    }
+    inputMessage.value = ''
+  }
+}
+
+const stopGeneration = () => {
+  abortCurrentRequest()
+}
+
+const sendRequest = (message) => {
+  isSending.value = true
+  currentAbortController.value = new AbortController()
+
+  // 鐢ㄦ埛娑堟伅
+  messages.value.push({
+    isUser: true,
+    content: message,
+    htmlContent: convertTextToHtml(message),
+    isTyping: false
+  })
+
+  // 鏈哄櫒浜哄崰浣�
+  const botMsgIndex = messages.value.length
+  const botMsg = {
+    isUser: false,
+    content: '',
+    htmlContent: '',
+    isTyping: true,
+    chartOptions: null,
+    chartRenderReady: false,
+    type: '',
+    tableData: null
+  }
+  messages.value.push(botMsg)
+
+  outputState.value[botMsgIndex] = {
+    isPaused: false,
+    jsonBlockStartPos: -1,
+    jsBlockStartPos: -1,
+    blockEndPos: -1,
+    hasRenderedChart: false
+  }
+
+  scrollToBottom()
+
+  request.post(`${currentAssistant.value.apiBase}/chat`,
+      { memoryId: uuid.value, message },
+      {
+        signal: currentAbortController.value.signal,
+        onDownloadProgress: (e) => {
+          // 鍏煎涓嶅悓鐗堟湰鐨� axios 鑾峰彇鍝嶅簲鏂囨湰鐨勬柟寮�
+          const fullText = e.target ? e.target.responseText : (e.event ? e.event.target.responseText : '')
+          if (!fullText) return
+
+          const currentMsg = messages.value[botMsgIndex]
+          if (!currentMsg) return
+
+          currentMsg.content = fullText
+
+          // 灏濊瘯鎻愬彇骞惰В鏋� JSON
+          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) {
+              currentMsg.type = parsedData.type || ''
+              if (currentMsg.type === 'todo_list' && parsedData.data) {
+                currentMsg.tableData = parsedData.data
+              }
+              if (parsedData.charts && Object.keys(parsedData.charts).length > 0) {
+                currentMsg.chartOptions = parsedData.charts
+                currentMsg.chartRenderReady = true
+                // 姣忔瑙f瀽鎴愬姛閮藉皾璇曟覆鏌�/鏇存柊鍥捐〃锛屼互鏀寔娴佸紡鏇存柊
+                renderCharts(botMsgIndex, currentMsg.chartOptions)
+              }
+            }
+
+          }
+
+          updateOutputState(fullText, botMsgIndex)
+          currentMsg.htmlContent = convertStreamOutput(fullText, botMsgIndex)
+          scrollToBottom()
+        }
+      }
+  ).then(() => {
+    const currentMsg = messages.value[botMsgIndex]
+    currentMsg.isTyping = false
+    isSending.value = false
+    currentAbortController.value = null
+
+    // 鏈�缁堣В鏋�
+    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) {
+        currentMsg.type = finalParsed.type || ''
+        if (currentMsg.type === 'todo_list' && finalParsed.data) {
+          currentMsg.tableData = finalParsed.data
+        }
+        if (finalParsed.charts && Object.keys(finalParsed.charts).length > 0) {
+          currentMsg.chartOptions = finalParsed.charts
+          currentMsg.chartRenderReady = true
+          renderCharts(botMsgIndex, currentMsg.chartOptions)
+        }
+      }
+    }
+  }).catch(err => {
+    if (err.name === 'CanceledError' || err.name === 'AbortError') {
+      console.log('Request aborted by user')
+      return
+    }
+    console.error('AI Chat Error:', err)
+    const errorMsg = '鎶辨瓑锛屾垜鐜板湪閬囧埌浜嗕竴鐐归棶棰橈紝璇风◢鍚庡啀璇曘��'
+    if (messages.value[botMsgIndex]) {
+      messages.value[botMsgIndex].content = errorMsg
+      messages.value[botMsgIndex].htmlContent = convertTextToHtml(errorMsg)
+      messages.value[botMsgIndex].isTyping = false
+    }
+    isSending.value = false
+    currentAbortController.value = null
+  })
+}
+
+const updateOutputState = (text, msgIndex) => {
+  const state = outputState.value[msgIndex]
+  if (state.jsonBlockStartPos === -1) {
+    const pos = text.indexOf('```json')
+    if (pos !== -1) { state.jsonBlockStartPos = pos; state.isPaused = true }
+  }
+  if (state.jsBlockStartPos === -1) {
+    const pos = text.indexOf('```javascript') !== -1 ? text.indexOf('```javascript') : text.indexOf('```js')
+    if (pos !== -1) { state.jsBlockStartPos = pos; state.isPaused = true }
+  }
+  if ((state.jsonBlockStartPos !== -1 || state.jsBlockStartPos !== -1) && state.blockEndPos === -1) {
+    const startCheck = state.jsonBlockStartPos !== -1 ? state.jsonBlockStartPos + 7 : state.jsBlockStartPos + (text.includes('javascript') ? 13 : 5)
+    const endPos = text.indexOf('```', startCheck)
+    if (endPos !== -1) { state.blockEndPos = endPos + 3; state.isPaused = false }
+  }
+}
+
+const convertTextToHtml = (text) => {
+  if (!text) return ''
+  return text
+      .replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(/\n/g, '<br>')
+}
+
+const convertStreamOutput = (output, msgIndex) => {
+  if (!output) return ''
+  const state = outputState.value[msgIndex]
+  let display = output
+
+  // 灏濊瘯鎻愬彇 JSON 閮ㄥ垎
+  const extracted = extractEmbeddedSuccessJson(output)
+  const startIdx = extracted ? extracted.startIdx : output.indexOf('{"success"')
+
+  // 濡傛灉杩樺湪浠g爜鍧椾腑涓旀湭缁撴潫锛屾樉绀烘彁绀烘枃瀛�
+  if (state && ((state.jsonBlockStartPos !== -1) || (state.jsBlockStartPos !== -1)) && state.blockEndPos === -1) {
+    const startPos = state.jsonBlockStartPos !== -1 ? state.jsonBlockStartPos : state.jsBlockStartPos
+    const textBeforeBlock = display.substring(0, startPos).trim()
+    display = textBeforeBlock || '姝e湪鍒嗘瀽鏁版嵁骞剁敓鎴愬浘琛�...'
+    return convertTextToHtml(display)
+  }
+
+  if (extracted) {
+    const parsed = extracted.data
+    display = (output.substring(0, extracted.startIdx) + output.substring(extracted.endIdx)).trim()
+
+    if (/^[\s}\],锛屻��.:锛�;锛沒+$/.test(display)) {
+      display = ''
+    }
+
+    if (parsed.description) {
+      display = parsed.description
+    }
+
+    if (!display) {
+      if (parsed.type === 'todo_list') {
+        display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+      } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
+        display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
+      } else {
+        display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+      }
+    }
+  } else if (startIdx !== -1) {
+    const lastBraceIdx = output.lastIndexOf('}')
+    if (lastBraceIdx !== -1 && lastBraceIdx > startIdx) {
+      const potentialJson = output.substring(startIdx, lastBraceIdx + 1)
+      try {
+        const parsed = JSON.parse(potentialJson)
+        // 鎴愬姛瑙f瀽锛岀Щ闄� JSON 閮ㄥ垎鏄剧ず鏂囧瓧
+        display = (output.substring(0, startIdx) + output.substring(lastBraceIdx + 1)).trim()
+
+        if (/^[\s}\],锛屻��.:锛�;锛沒+$/.test(display)) {
+          display = ''
+        }
+
+        if (parsed.description) {
+          display = parsed.description
+        }
+
+        if (!display) {
+          if (parsed.type === 'todo_list') {
+            display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁锛�'
+          } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
+            display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛細'
+          } else {
+            display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+          }
+        }
+      } catch (e) {
+        // 瑙f瀽澶辫触锛岃鏄� JSON 杩樺湪浼犺緭涓垨鏍煎紡涓嶆纭�
+        display = output.substring(0, startIdx).trim() || '姝e湪鍒嗘瀽鏁版嵁骞剁敓鎴愬浘琛�...'
+      }
+    } else {
+      // 鎵惧埌浜嗗紑濮嬩絾杩樻病鎵惧埌缁撴潫
+      display = output.substring(0, startIdx).trim() || '姝e湪鍒嗘瀽鏁版嵁骞剁敓鎴愬浘琛�...'
+    }
+  }
+
+  let html = convertTextToHtml(display)
+
+  // 杩樺師浠g爜鍧�
+  html = html.replace(/```(javascript|js)([\s\S]*?)```/g, '<pre class="code-block js-code">$2</pre>')
+  html = html.replace(/```([\s\S]*?)```/g, '<pre class="code-block">$1</pre>')
+
+  return html || '...'
+}
+
+const renderCharts = (msgIndex, chartOptions) => {
+  nextTick(() => {
+    Object.keys(chartOptions).forEach(key => {
+      const id = `ai-chart-${msgIndex}-${key}`
+      const tryInit = (count = 0) => {
+        const dom = document.getElementById(id)
+        if (dom) {
+          if (chartInstances.value[id]) {
+            // 濡傛灉宸茬粡鍒濆鍖栬繃锛岀洿鎺ユ洿鏂版暟鎹�
+            const chart = chartInstances.value[id]
+            const option = normalizeAiChartOption(chartOptions[key])
+            if (option) chart.setOption(option)
+            return
+          }
+
+          const chart = echarts.init(dom)
+          chartInstances.value[id] = chart
+          const option = normalizeAiChartOption(chartOptions[key])
+          if (option) {
+            chart.setOption(option)
+          } else {
+            console.warn('Invalid chart option for:', id, chartOptions[key])
+          }
+
+          const handler = () => chart.resize()
+          resizeHandlers.value.push(handler)
+          window.addEventListener('resize', handler)
+        } else if (count < 15) { // 绋嶅井澧炲姞閲嶈瘯娆℃暟
+          setTimeout(() => tryInit(count + 1), 200)
+        }
+      }
+      tryInit()
+    })
+  })
+}
+
+// 鏍煎紡鍖� AI 杩斿洖鐨勫浘琛ㄩ厤缃紝灏嗗叾杞崲涓烘爣鍑嗙殑 ECharts 閰嶇疆
+const formatChartOption = (rawOption) => {
+  if (!rawOption) return null
+
+  // 濡傛灉宸茬粡鏄爣鍑� ECharts 閰嶇疆锛堝寘鍚� series锛夛紝鍒欑洿鎺ヨ繑鍥�
+  const hasSeries = rawOption.series && Array.isArray(rawOption.series)
+
+  // 灏濊瘯杞崲绠�鏄撴牸寮�
+  try {
+    const isPie = rawOption.type === 'pie' || (rawOption.title && rawOption.title.includes('鍗犳瘮'))
+
+    const option = {
+      title: {
+        text: rawOption.title || '',
+        left: 'center',
+        textStyle: { fontSize: 14 }
+      },
+      tooltip: {
+        trigger: isPie ? 'item' : 'axis'
+      },
+      legend: {
+        bottom: '0'
+      },
+      grid: {
+        left: '3%',
+        right: '4%',
+        bottom: '15%',
+        containLabel: true
+      },
+      xAxis: isPie ? undefined : {
+        type: 'category',
+        data: rawOption.xAxisData || (Array.isArray(rawOption.xAxis) ? rawOption.xAxis : []),
+        name: typeof rawOption.xAxis === 'string' ? rawOption.xAxis : ''
+      },
+      yAxis: isPie ? undefined : {
+        type: 'value',
+        name: typeof rawOption.yAxis === 'string' ? rawOption.yAxis : ''
+      },
+      series: rawOption.series || [{
+        name: rawOption.title || '鏁板��',
+        type: rawOption.type || 'line',
+        data: rawOption.seriesData || (Array.isArray(rawOption.data) ? rawOption.data : []),
+        smooth: true,
+        radius: isPie ? '50%' : undefined
+      }]
+    }
+
+    // 閽堝楗煎浘鐨勭壒娈婂鐞�
+    if (isPie && !option.series[0].data && Array.isArray(rawOption.data)) {
+      option.series[0].data = rawOption.data
+    }
+
+    return option
+  } catch (err) {
+    console.error('Chart option conversion failed:', err)
+    return null
+  }
+}
+
+const normalizeAiChartOption = (rawOption) => {
+  if (!rawOption) return null
+
+  try {
+    const hasSeries = Array.isArray(rawOption.series) && rawOption.series.length > 0
+    const firstSeriesType = hasSeries ? rawOption.series[0]?.type : rawOption.type
+    const titleConfig = rawOption.title && typeof rawOption.title === 'object'
+      ? rawOption.title
+      : null
+    const tooltipConfig = rawOption.tooltip && typeof rawOption.tooltip === 'object'
+      ? rawOption.tooltip
+      : null
+    const legendConfig = rawOption.legend && typeof rawOption.legend === 'object'
+      ? rawOption.legend
+      : null
+    const rawXAxisConfig = rawOption.xAxis && typeof rawOption.xAxis === 'object' && !Array.isArray(rawOption.xAxis)
+      ? rawOption.xAxis
+      : null
+    const rawYAxisConfig = rawOption.yAxis && typeof rawOption.yAxis === 'object' && !Array.isArray(rawOption.yAxis)
+      ? rawOption.yAxis
+      : null
+    const titleText = typeof rawOption.title === 'string' ? rawOption.title : rawOption.title?.text || ''
+    const isPie = firstSeriesType === 'pie' || titleText.includes('鍗犳瘮')
+    const baseXAxisData = Array.isArray(rawOption.xAxisData)
+        ? rawOption.xAxisData
+        : (Array.isArray(rawOption.xAxis) ? rawOption.xAxis : (Array.isArray(rawXAxisConfig?.data) ? rawXAxisConfig.data : []))
+    const fallbackSeries = [{
+      name: titleText || '鏁版嵁',
+      type: rawOption.type || 'line',
+      data: Array.isArray(rawOption.seriesData) ? rawOption.seriesData : (Array.isArray(rawOption.data) ? rawOption.data : [])
+    }]
+    const normalizedSeries = (hasSeries ? rawOption.series : fallbackSeries).map((seriesItem, index) => {
+      const seriesType = seriesItem?.type || rawOption.type || 'line'
+      const nextSeries = {
+        ...seriesItem,
+        name: seriesItem?.name || titleText || `绯诲垪${index + 1}`,
+        type: seriesType
+      }
+
+      if (isPie) {
+        nextSeries.radius = nextSeries.radius || '55%'
+        nextSeries.data = Array.isArray(nextSeries.data) ? nextSeries.data : (Array.isArray(rawOption.data) ? rawOption.data : [])
+      } else {
+        nextSeries.smooth = typeof nextSeries.smooth === 'boolean' ? nextSeries.smooth : seriesType === 'line'
+        nextSeries.data = Array.isArray(nextSeries.data) ? nextSeries.data : []
+      }
+
+      return nextSeries
+    })
+    const categorySource = !isPie
+      ? normalizedSeries.find(seriesItem => Array.isArray(seriesItem.data) && seriesItem.data.every(item => item && typeof item === 'object' && 'name' in item && 'value' in item))
+      : null
+    const xAxisData = categorySource
+      ? categorySource.data.map(item => item.name)
+      : baseXAxisData
+    const finalSeries = !isPie && categorySource
+      ? normalizedSeries.map(seriesItem => ({
+          ...seriesItem,
+          data: Array.isArray(seriesItem.data)
+            ? seriesItem.data.map(item => (item && typeof item === 'object' && 'value' in item ? item.value : item))
+            : []
+        }))
+      : normalizedSeries
+
+    return {
+      title: titleConfig || {
+        text: titleText,
+        left: 'center',
+        textStyle: { fontSize: 14 }
+      },
+      tooltip: tooltipConfig || {
+        trigger: isPie ? 'item' : 'axis'
+      },
+      legend: legendConfig || {
+        bottom: '0'
+      },
+      grid: isPie ? undefined : {
+        left: '3%',
+        right: '4%',
+        bottom: '15%',
+        containLabel: true
+      },
+      xAxis: isPie ? undefined : {
+        ...(rawXAxisConfig || {}),
+        type: 'category',
+        data: xAxisData,
+        name: typeof rawOption.xAxis === 'string' ? rawOption.xAxis : (rawXAxisConfig?.name || '')
+      },
+      yAxis: isPie ? undefined : (rawYAxisConfig || {
+        type: 'value',
+        name: typeof rawOption.yAxis === 'string' ? rawOption.yAxis : ''
+      }),
+      series: finalSeries
+    }
+  } catch (err) {
+    console.error('AI chart normalization failed:', err, rawOption)
+    return formatChartOption(rawOption)
+  }
+}
+
+watch(messages, () => scrollToBottom(), { deep: true })
+</script>
+
+<style lang="scss">
+.ai-chat-overlay {
+  pointer-events: none !important;
+  background: transparent !important;
+}
+
+.ai-chat-overlay .el-drawer {
+  pointer-events: auto;
+}
+</style>
+
+<style scoped lang="scss">
+$primary-blue: #0055d4;
+$secondary-blue: #2e8ce0;
+$light-blue: #7ab8ff;
+$pale-blue: #c5dcff;
+$ice-white: #e8f2ff;
+$deep-blue: #003b8e;
+$deepest-blue: #002b66;
+$gradient-blue: linear-gradient(145deg, #004fc7 0%, #0066e0 40%, #2580e8 70%, #5a9fe0 100%);
+$gradient-dark: linear-gradient(145deg, #003b8e 0%, #0055d4 50%, #0077e8 100%);
+$gradient-ice: linear-gradient(180deg, #e0ecff 0%, #d4e5ff 50%, #e8f0ff 100%);
+$shadow-blue: 0 8px 40px rgba(0, 85, 212, 0.35);
+$shadow-deep: 0 12px 48px rgba(0, 40, 120, 0.4);
+$shadow-card: 0 6px 24px rgba(0, 51, 136, 0.12);
+
+.ai-chat-sidebar-wrapper {
+  position: static;
+  z-index: 2000;
+  pointer-events: auto;
+
+  :deep(.el-drawer) {
+    pointer-events: auto;
+  }
+}
+
+.ai-chat-trigger {
+  pointer-events: auto;
+  position: fixed;
+  right: 24px;
+  bottom: 100px;
+  width: 56px;
+  height: 56px;
+  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;
+
+  &::before {
+    content: '';
+    position: absolute;
+    inset: -6px;
+    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;
+  }
+
+  &::after {
+    content: '';
+    position: absolute;
+    inset: 0;
+    border-radius: 50%;
+    background: linear-gradient(135deg, rgba(255, 255, 255, 0.3) 0%, transparent 50%);
+    pointer-events: none;
+  }
+
+  &: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;
+    }
+
+    .trigger-icon {
+      transform: rotate(-8deg) scale(1.05);
+      filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5));
+    }
+  }
+
+  .trigger-icon {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+    color: #fff;
+    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
+  }
+}
+
+@keyframes triggerPulse {
+  0%, 100% {
+    box-shadow: $shadow-blue, 0 0 0 2px rgba(0, 85, 212, 0.25) inset, 0 0 20px rgba(0, 119, 232, 0.15);
+  }
+  50% {
+    box-shadow: $shadow-deep, 0 0 0 3px rgba(0, 136, 232, 0.35) inset, 0 0 40px rgba(0, 136, 232, 0.25);
+  }
+}
+
+@keyframes glowPulse {
+  0% {
+    opacity: 0.6;
+    transform: scale(1);
+  }
+  100% {
+    opacity: 1;
+    transform: scale(1.1);
+  }
+}
+
+.ai-chat-drawer {
+  :deep(.el-drawer__body) {
+    padding: 0;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+  :deep(.el-drawer__header) {
+    margin-bottom: 0;
+    padding: 0;
+    background: $gradient-dark;
+    color: #fff;
+  }
+}
+
+.drawer-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding: 18px 20px;
+  background: $gradient-dark;
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: -60%;
+    right: -25%;
+    width: 250px;
+    height: 250px;
+    background: radial-gradient(circle, rgba(0, 136, 232, 0.4) 0%, transparent 70%);
+    pointer-events: none;
+    animation: headerGlow 4s ease-in-out infinite alternate;
+  }
+
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: -40%;
+    left: -15%;
+    width: 200px;
+    height: 200px;
+    background: radial-gradient(circle, rgba(0, 85, 212, 0.3) 0%, transparent 70%);
+    pointer-events: none;
+    animation: headerGlow 5s ease-in-out infinite alternate-reverse;
+  }
+
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    position: relative;
+    z-index: 1;
+
+    .header-icon {
+      color: rgba(255, 255, 255, 0.95);
+      filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.2));
+      animation: iconFloat 3s ease-in-out infinite;
+    }
+
+    .title {
+      font-size: 17px;
+      font-weight: 600;
+      color: #fff;
+      text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
+      letter-spacing: 0.5px;
+    }
+  }
+
+  .header-actions {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    position: relative;
+    z-index: 1;
+
+    .action-divider {
+      width: 1px;
+      height: 16px;
+      background: rgba(255, 255, 255, 0.2);
+      margin: 0 2px;
+    }
+
+    :deep(.el-button) {
+      color: rgba(255, 255, 255, 0.85);
+      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+      background: rgba(255, 255, 255, 0.12);
+      border: 1px solid rgba(255, 255, 255, 0.1);
+      border-radius: 8px;
+      padding: 8px;
+      height: 32px;
+      width: 32px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      &:hover {
+        color: #fff;
+        background: rgba(255, 255, 255, 0.25);
+        border-color: rgba(255, 255, 255, 0.3);
+        transform: translateY(-1px);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+      }
+
+      &:active {
+        transform: translateY(0);
+      }
+
+      &.close-btn {
+        background: rgba(255, 255, 255, 0.1);
+        &:hover {
+          background: rgba(245, 108, 108, 0.8);
+          border-color: rgba(245, 108, 108, 0.5);
+        }
+      }
+    }
+
+    :deep(.header-action-btn) {
+      position: relative;
+      overflow: hidden;
+      background: linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.08));
+      border: 1px solid rgba(255, 255, 255, 0.16);
+      box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 18px rgba(0, 0, 0, 0.12);
+
+      &::before {
+        content: '';
+        position: absolute;
+        inset: 0;
+        background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), transparent 55%);
+        pointer-events: none;
+      }
+
+      &::after {
+        content: '';
+        position: absolute;
+        top: -120%;
+        left: -40%;
+        width: 60%;
+        height: 260%;
+        background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.28), transparent);
+        transform: rotate(24deg);
+        opacity: 0;
+        transition: all 0.35s ease;
+      }
+
+      &:hover::after {
+        left: 100%;
+        opacity: 1;
+      }
+    }
+  }
+
+  .assistant-switcher {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex: 1;
+    padding: 0 12px;
+    position: relative;
+    z-index: 1;
+
+    :deep(.el-radio-group) {
+      display: flex;
+      gap: 6px;
+      flex-wrap: wrap;
+      justify-content: center;
+      padding: 4px;
+      border-radius: 999px;
+      background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.08));
+      border: 1px solid rgba(255, 255, 255, 0.12);
+      box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 18px rgba(0, 0, 0, 0.1);
+    }
+
+    :deep(.el-radio-button__inner) {
+      border-radius: 999px;
+      border: 1px solid rgba(255, 255, 255, 0.18);
+      background: rgba(255, 255, 255, 0.12);
+      color: rgba(255, 255, 255, 0.86);
+      box-shadow: none;
+      padding: 7px 14px;
+      font-weight: 500;
+    }
+
+    :deep(.el-radio-button:first-child .el-radio-button__inner),
+    :deep(.el-radio-button:last-child .el-radio-button__inner) {
+      border-radius: 999px;
+    }
+
+    :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
+      background: #fff;
+      color: $primary-blue;
+      border-color: #fff;
+      box-shadow: 0 6px 14px rgba(0, 40, 120, 0.16);
+    }
+  }
+}
+
+@keyframes headerGlow {
+  0% {
+    opacity: 0.6;
+    transform: scale(1);
+  }
+  100% {
+    opacity: 1;
+    transform: scale(1.15);
+  }
+}
+
+@keyframes iconFloat {
+  0%, 100% {
+    transform: translateY(0) rotate(0);
+  }
+  50% {
+    transform: translateY(-2px) rotate(3deg);
+  }
+}
+
+.chat-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  width: 100%;
+  background: $ice-white;
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 240px;
+    background: linear-gradient(180deg, rgba(0, 85, 212, 0.06) 0%, transparent 100%);
+    pointer-events: none;
+  }
+}
+
+.history-panel {
+  position: absolute;
+  inset: 0;
+  background: linear-gradient(180deg, #fff 0%, $ice-white 100%);
+  z-index: 10;
+  display: flex;
+  flex-direction: column;
+  box-shadow: -8px 0 32px rgba(0, 85, 212, 0.15);
+
+  .history-header {
+    padding: 18px 20px;
+    border-bottom: 1px solid rgba(0, 85, 212, 0.12);
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-weight: 600;
+    font-size: 14px;
+    color: $deep-blue;
+    background: linear-gradient(135deg, rgba(0, 85, 212, 0.08) 0%, rgba(0, 136, 232, 0.05) 100%);
+    position: relative;
+
+    &::after {
+      content: '';
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      height: 1px;
+      background: linear-gradient(90deg, transparent, rgba(0, 85, 212, 0.2), transparent);
+    }
+  }
+
+  .session-list {
+    flex: 1;
+    overflow-y: auto;
+    padding: 12px 16px;
+
+    &::-webkit-scrollbar {
+      width: 8px;
+    }
+    &::-webkit-scrollbar-thumb {
+      background: linear-gradient(180deg, $secondary-blue, $primary-blue);
+      border-radius: 4px;
+      box-shadow: 0 0 6px rgba(0, 85, 212, 0.25);
+    }
+
+    .session-item {
+      display: flex;
+      align-items: center;
+      padding: 14px 16px;
+      margin-bottom: 6px;
+      border-radius: 12px;
+      cursor: pointer;
+      transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+      gap: 12px;
+      position: relative;
+      border: 1px solid transparent;
+      background: #fff;
+      animation: sessionSlideIn 0.35s ease;
+
+      @keyframes sessionSlideIn {
+        from {
+          opacity: 0;
+          transform: translateX(-15px);
+        }
+        to {
+          opacity: 1;
+          transform: translateX(0);
+        }
+      }
+
+      &:hover {
+        background: linear-gradient(135deg, rgba(0, 85, 212, 0.06) 0%, rgba(0, 136, 232, 0.08) 100%);
+        border-color: rgba(0, 85, 212, 0.12);
+        box-shadow: 0 4px 16px rgba(0, 85, 212, 0.1);
+        transform: translateX(4px);
+
+        .delete-btn {
+          opacity: 1;
+          transform: scale(1);
+        }
+      }
+
+      &.active {
+        background: linear-gradient(135deg, rgba(0, 85, 212, 0.12) 0%, rgba(0, 136, 232, 0.15) 100%);
+        border-color: rgba(0, 85, 212, 0.25);
+        color: $primary-blue;
+        box-shadow: 0 4px 16px rgba(0, 85, 212, 0.15);
+
+        .el-icon {
+          color: $primary-blue;
+        }
+      }
+
+      .el-icon {
+        font-size: 18px;
+        flex-shrink: 0;
+        color: $secondary-blue;
+        transition: color 0.2s;
+      }
+
+      .session-name {
+        flex: 1;
+        font-size: 13px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        color: #1a1a2e;
+        font-weight: 500;
+      }
+
+      .delete-btn {
+        opacity: 0;
+        transform: scale(0.8);
+        transition: all 0.25s ease;
+        padding: 6px;
+        border-radius: 6px;
+        color: #c0c4cc;
+
+        &:hover {
+          color: #fff;
+          background: rgba(245, 108, 108, 0.85);
+          transform: scale(1.1) rotate(8deg);
+        }
+      }
+    }
+  }
+}
+
+.chat-main {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  flex: 1;
+  overflow: hidden;
+}
+
+.message-list {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px 20px;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  background: linear-gradient(180deg, transparent 0%, rgba(0, 85, 212, 0.02) 100%);
+
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: linear-gradient(180deg, $secondary-blue, $primary-blue);
+    border-radius: 4px;
+    box-shadow: 0 0 8px rgba(0, 85, 212, 0.3);
+  }
+}
+
+.message-item {
+  display: flex;
+  gap: 14px;
+  width: 100%;
+  animation: messageSlideIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+
+  @keyframes messageSlideIn {
+    from {
+      opacity: 0;
+      transform: translateY(20px) scale(0.95);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0) scale(1);
+    }
+  }
+
+  .avatar {
+    width: 42px;
+    height: 42px;
+    border-radius: 14px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+    font-size: 24px;
+    position: relative;
+    overflow: hidden;
+
+    &::before {
+      content: '';
+      position: absolute;
+      inset: 0;
+      background: inherit;
+      filter: blur(10px);
+      opacity: 0.5;
+      z-index: -1;
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      top: -50%;
+      left: -50%;
+      width: 200%;
+      height: 200%;
+      background: linear-gradient(45deg, transparent 40%, rgba(255, 255, 255, 0.2) 50%, transparent 60%);
+      animation: shimmer 3s infinite;
+    }
+  }
+
+  .message-content {
+    flex: 1;
+    overflow-x: hidden;
+    display: flex;
+    flex-direction: column;
+    max-width: calc(100% - 56px);
+
+    .text-box {
+      padding: 14px 20px;
+      border-radius: 18px;
+      font-size: 14px;
+      line-height: 1.7;
+      word-break: break-word;
+      max-width: 100%;
+      width: fit-content;
+      overflow-x: auto;
+      transition: all 0.3s ease;
+      position: relative;
+
+      &::-webkit-scrollbar {
+        height: 4px;
+      }
+      &::-webkit-scrollbar-thumb {
+        background: rgba(0, 85, 212, 0.25);
+        border-radius: 2px;
+      }
+    }
+  }
+
+  &.bot-message {
+    .message-content {
+      align-items: flex-start;
+    }
+    .avatar {
+      background: $gradient-dark;
+      color: #fff;
+      box-shadow: 0 6px 20px rgba(0, 85, 212, 0.35);
+    }
+    .text-box {
+      background: #fff;
+      color: #1a1a2e;
+      box-shadow: $shadow-card;
+      border: 1px solid rgba(0, 85, 212, 0.08);
+      border-top-left-radius: 6px;
+
+      &::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        height: 1px;
+        background: linear-gradient(90deg, rgba(0, 85, 212, 0.15), transparent);
+      }
+    }
+  }
+
+  &.user-message {
+    flex-direction: row-reverse;
+    .message-content {
+      align-items: flex-end;
+    }
+    .avatar {
+      background: linear-gradient(145deg, #5a9fe0, #3d8bd4);
+      color: #fff;
+      box-shadow: 0 6px 20px rgba(0, 85, 212, 0.4);
+    }
+    .text-box {
+      background: $gradient-dark;
+      color: #fff;
+      border-top-right-radius: 6px;
+      box-shadow: 0 6px 24px rgba(0, 85, 212, 0.3);
+
+      &::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        height: 1px;
+        background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3));
+      }
+    }
+  }
+}
+
+@keyframes shimmer {
+  0% {
+    transform: translateX(-100%) rotate(45deg);
+  }
+  100% {
+    transform: translateX(100%) rotate(45deg);
+  }
+}
+
+.charts-wrapper {
+  margin-top: 12px;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  overflow-x: auto;
+  width: 100%;
+  padding-bottom: 8px;
+
+  &::-webkit-scrollbar {
+    height: 6px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: linear-gradient(90deg, $light-blue, $secondary-blue);
+    border-radius: 3px;
+  }
+}
+
+.chart-item {
+  width: 100%;
+  min-width: 300px;
+  height: 300px;
+  border-radius: 12px;
+  padding: 12px;
+  margin-bottom: 12px;
+}
+
+.table-wrapper {
+  margin-top: 12px;
+  background: #fff;
+  border-radius: 12px;
+  overflow: hidden;
+  overflow-x: auto;
+  width: 100%;
+  box-shadow: $shadow-card;
+  border: 1px solid rgba(0, 122, 255, 0.06);
+
+  &::-webkit-scrollbar {
+    height: 6px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: linear-gradient(90deg, $light-blue, $secondary-blue);
+    border-radius: 3px;
+  }
+
+  .el-table {
+    min-width: 300px;
+    --el-table-border-color: rgba(0, 122, 255, 0.08);
+    --el-table-header-bg-color: $ice-white;
+  }
+}
+
+.input-area {
+  padding: 18px 20px;
+  background: linear-gradient(180deg, rgba(232, 242, 255, 0.95) 0%, #fff 100%);
+  border-top: 1px solid rgba(0, 85, 212, 0.1);
+  position: relative;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 20px;
+    right: 20px;
+    height: 1px;
+    background: linear-gradient(90deg, transparent, rgba(0, 85, 212, 0.15), transparent);
+  }
+
+  .input-actions {
+    display: flex;
+    gap: 14px;
+    margin-bottom: 12px;
+    align-items: center;
+
+    .file-upload-trigger {
+      display: inline-flex;
+      align-items: center;
+    }
+
+    :deep(.utility-action-btn) {
+      position: relative;
+      height: 34px;
+      padding: 0 14px;
+      border-radius: 999px;
+      border: 1px solid rgba(92, 119, 255, 0.18);
+      background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(236, 243, 255, 0.98));
+      color: $primary-blue;
+      font-weight: 600;
+      box-shadow: 0 10px 20px rgba(0, 85, 212, 0.08);
+      transition: all 0.25s ease;
+
+      .el-icon {
+        margin-right: 5px;
+      }
+
+      &:hover:not(.is-disabled) {
+        color: #fff;
+        border-color: transparent;
+        background: linear-gradient(135deg, #1f6dff 0%, #6b38ef 100%);
+        box-shadow: 0 14px 24px rgba(64, 90, 255, 0.2);
+        transform: translateY(-1px);
+      }
+    }
+
+    :deep(.stop-action-btn) {
+      border-color: rgba(255, 99, 123, 0.18);
+      color: #d33e5e;
+
+      &:hover:not(.is-disabled) {
+        background: linear-gradient(135deg, #f5536e 0%, #a33cff 100%);
+      }
+    }
+  }
+
+  .input-box {
+    padding: 16px;
+    position: relative;
+    background: #fff;
+    border: 2px solid rgba(0, 85, 212, 0.12);
+    border-radius: 16px;
+    margin: 0 4px;
+    transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+
+    &:focus-within {
+      border-color: $primary-blue;
+      box-shadow: 0 0 0 4px rgba(0, 85, 212, 0.12), $shadow-deep;
+      transform: translateY(-2px);
+      background: #fff;
+    }
+
+    .selected-file-tag {
+      display: flex;
+      align-items: center;
+      background: linear-gradient(135deg, rgba(0, 85, 212, 0.1) 0%, rgba(0, 136, 232, 0.15) 100%);
+      border: 1px solid rgba(0, 85, 212, 0.2);
+      border-radius: 10px;
+      padding: 8px 12px;
+      margin-bottom: 12px;
+      gap: 10px;
+      width: fit-content;
+      max-width: 100%;
+      animation: tagSlideIn 0.3s ease;
+
+      @keyframes tagSlideIn {
+        from {
+          opacity: 0;
+          transform: translateX(-10px);
+        }
+        to {
+          opacity: 1;
+          transform: translateX(0);
+        }
+      }
+
+      .el-icon {
+        color: $primary-blue;
+        font-size: 18px;
+      }
+
+      .file-name {
+        font-size: 13px;
+        color: $deep-blue;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        font-weight: 600;
+      }
+
+      .remove-file {
+        cursor: pointer;
+        color: $secondary-blue;
+        transition: all 0.2s;
+        padding: 4px;
+        border-radius: 50%;
+
+        &:hover {
+          color: #fff;
+          background: rgba(245, 108, 108, 0.8);
+          transform: scale(1.1) rotate(90deg);
+        }
+      }
+    }
+
+    :deep(.el-textarea__inner) {
+      padding: 0;
+      padding-bottom: 35px;
+      border: none;
+      box-shadow: none;
+      background: transparent;
+      font-family: inherit;
+      font-size: 14px;
+      line-height: 1.6;
+      color: #1a1a2e;
+
+      &::placeholder {
+        color: #7ab8ff;
+      }
+
+      &:focus {
+        box-shadow: none;
+      }
+    }
+
+    .send-btn {
+      position: absolute;
+      right: 16px;
+      bottom: 16px;
+      padding: 10px 22px;
+      background: $gradient-dark;
+      border: none;
+      border-radius: 10px;
+      font-weight: 600;
+      font-size: 14px;
+      color: #fff;
+      box-shadow: 0 6px 20px rgba(0, 85, 212, 0.4);
+      transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+      overflow: hidden;
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      letter-spacing: 0.3px;
+
+      &::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: -100%;
+        width: 100%;
+        height: 100%;
+        background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+        transition: left 0.5s;
+      }
+
+      &:hover:not(:disabled) {
+        transform: translateY(-3px) scale(1.02);
+        box-shadow: 0 10px 30px rgba(0, 85, 212, 0.5);
+
+        &::before {
+          left: 100%;
+        }
+      }
+
+      &:active:not(:disabled) {
+        transform: translateY(-1px) scale(0.98);
+      }
+
+      &:disabled {
+        background: linear-gradient(145deg, #b0b0b0, #c5c5c5);
+        box-shadow: none;
+        cursor: not-allowed;
+      }
+
+      .el-icon {
+        font-size: 15px;
+        transform: translateY(-1px);
+      }
+    }
+  }
+}
+
+.typing-indicator {
+  display: flex;
+  gap: 5px;
+  padding: 10px 14px;
+  background: #fff;
+  border-radius: 14px;
+  width: fit-content;
+  box-shadow: $shadow-card;
+  margin-top: 6px;
+  border: 1px solid rgba(0, 122, 255, 0.06);
+  border-top-left-radius: 4px;
+
+  .dot {
+    width: 7px;
+    height: 7px;
+    background: $secondary-blue;
+    border-radius: 50%;
+    animation: typing 1.4s infinite ease-in-out;
+
+    &:nth-child(2) {
+      animation-delay: 0.2s;
+      background: $primary-blue;
+    }
+    &:nth-child(3) {
+      animation-delay: 0.4s;
+      background: $deep-blue;
+    }
+  }
+}
+
+@keyframes typing {
+  0%, 80%, 100% {
+    transform: scale(0.6);
+    opacity: 0.4;
+  }
+  40% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+.code-block {
+  background: linear-gradient(145deg, #1a1a2e, #16213e);
+  color: #a8d8ff;
+  padding: 14px;
+  border-radius: 10px;
+  font-family: 'Fira Code', 'Consolas', monospace;
+  margin: 10px 0;
+  overflow-x: auto;
+  border: 1px solid rgba(90, 200, 250, 0.15);
+  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2);
+
+  &.js-code {
+    color: #5ac8fa;
+  }
+}
+
+.chat-main {
+  background:
+    radial-gradient(circle at top left, rgba(46, 140, 224, 0.12) 0%, transparent 34%),
+    linear-gradient(180deg, #fff 0%, #f7fbff 46%, #fff 100%);
+}
+
+.chat-hero {
+  display: grid;
+  grid-template-columns: 164px minmax(0, 1fr);
+  gap: 18px;
+  align-items: start;
+  padding: 14px 18px 6px;
+
+  &.compact {
+    grid-template-columns: 122px minmax(0, 1fr);
+    gap: 12px;
+    padding: 8px 18px 2px;
+  }
+}
+
+.assistant-stand {
+  position: relative;
+  min-height: 252px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  padding-top: 18px;
+  overflow: hidden;
+
+  &.compact {
+    min-height: 176px;
+    padding-top: 8px;
+  }
+
+  &.thinking {
+    .assistant-halo {
+      opacity: 1;
+      transform: scale(1.08);
+      filter: blur(8px);
+    }
+
+    .assistant-scan-ring {
+      opacity: 1;
+      animation-duration: 1.6s;
+    }
+
+    .assistant-orbit {
+      opacity: 1;
+    }
+
+    .assistant-bot {
+      transform: translateY(-4px) scale(1.02);
+    }
+
+    .assistant-bot-head {
+      box-shadow: 0 0 30px rgba(80, 157, 255, 0.36);
+    }
+
+    .assistant-bot-eye {
+      animation: robotBlinkFast 1.1s infinite;
+      box-shadow: 0 0 16px rgba(72, 186, 255, 0.95);
+    }
+
+    .assistant-bot-mouth {
+      width: 28px;
+      opacity: 1;
+      animation: robotTalk 1.2s ease-in-out infinite;
+    }
+
+    .assistant-bot-core {
+      animation: corePulse 1.4s ease-in-out infinite;
+      box-shadow: 0 0 24px rgba(78, 120, 255, 0.26);
+    }
+
+    .assistant-bot-core-ring {
+      animation: coreRotate 3s linear infinite;
+    }
+
+    .assistant-status {
+      color: #6a3bee;
+      box-shadow: 0 10px 22px rgba(106, 59, 238, 0.14);
+    }
+
+    .assistant-status-dot {
+      background: #6a3bee;
+      box-shadow: 0 0 12px rgba(106, 59, 238, 0.9);
+      animation: thinkingDot 1s ease-in-out infinite;
+    }
+
+    .assistant-base-sm {
+      box-shadow: 0 0 24px rgba(255, 93, 122, 0.48);
+    }
+  }
+}
+
+.assistant-halo {
+  position: absolute;
+  top: 22px;
+  width: 130px;
+  height: 130px;
+  border-radius: 50%;
+  background: radial-gradient(circle, rgba(46, 140, 224, 0.3) 0%, rgba(0, 85, 212, 0.18) 42%, rgba(113, 54, 244, 0.12) 60%, transparent 78%);
+  filter: blur(6px);
+  opacity: 0.82;
+  transition: all 0.35s ease;
+}
+
+.assistant-scan-ring {
+  position: absolute;
+  top: 40px;
+  width: 132px;
+  height: 132px;
+  border-radius: 50%;
+  border: 1px solid rgba(90, 159, 224, 0.22);
+  box-shadow: inset 0 0 16px rgba(255, 255, 255, 0.25);
+  opacity: 0.55;
+  animation: scanRing 4s linear infinite;
+}
+
+.assistant-orbit {
+  position: absolute;
+  top: 52px;
+  width: 150px;
+  height: 150px;
+  border-radius: 50%;
+  border: 1px dashed rgba(92, 135, 255, 0.22);
+  opacity: 0.45;
+}
+
+.assistant-orbit-a {
+  animation: orbitRotate 8s linear infinite;
+}
+
+.assistant-orbit-b {
+  width: 118px;
+  height: 118px;
+  top: 68px;
+  border-color: rgba(255, 108, 150, 0.22);
+  animation: orbitRotateReverse 5.6s linear infinite;
+}
+
+.assistant-bot {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  margin-top: 12px;
+  transition: transform 0.35s ease;
+}
+
+.assistant-bot-antenna {
+  position: absolute;
+  top: -4px;
+  width: 4px;
+  height: 20px;
+  border-radius: 999px;
+  background: linear-gradient(180deg, #fefefe, #aac9ff);
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: -6px;
+    left: 50%;
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+    transform: translateX(-50%);
+    background: linear-gradient(135deg, #54bfff, #7a41ff);
+    box-shadow: 0 0 14px rgba(84, 191, 255, 0.65);
+  }
+}
+
+.assistant-bot-antenna-left {
+  left: 36px;
+  transform: rotate(-14deg);
+}
+
+.assistant-bot-antenna-right {
+  right: 36px;
+  transform: rotate(14deg);
+}
+
+.assistant-bot-head {
+  position: relative;
+  width: 92px;
+  height: 78px;
+  border-radius: 28px;
+  background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #e8f1ff 100%);
+  border: 1px solid rgba(0, 85, 212, 0.14);
+  box-shadow: 0 16px 32px rgba(0, 85, 212, 0.14);
+}
+
+.assistant-bot-head-glow {
+  position: absolute;
+  inset: 10px 16px auto;
+  height: 20px;
+  border-radius: 999px;
+  background: linear-gradient(180deg, rgba(0, 85, 212, 0.16), transparent);
+}
+
+.assistant-bot-eye {
+  position: absolute;
+  top: 30px;
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  background: radial-gradient(circle, #8ef0ff 0%, #56c0ff 42%, #2869ff 100%);
+  box-shadow: 0 0 12px rgba(72, 186, 255, 0.72);
+  animation: robotBlink 3.2s infinite;
+}
+
+.assistant-bot-eye-left {
+  left: 22px;
+}
+
+.assistant-bot-eye-right {
+  right: 22px;
+}
+
+.assistant-bot-mouth {
+  position: absolute;
+  left: 50%;
+  bottom: 16px;
+  width: 22px;
+  height: 4px;
+  transform: translateX(-50%);
+  border-radius: 999px;
+  background: linear-gradient(90deg, rgba(72, 186, 255, 0.2), rgba(72, 186, 255, 0.9), rgba(72, 186, 255, 0.2));
+}
+
+.assistant-bot-neck {
+  width: 16px;
+  height: 10px;
+  border-radius: 0 0 10px 10px;
+  background: linear-gradient(180deg, #dceaff, #bdd5ff);
+  margin-top: -2px;
+}
+
+.assistant-bot-body {
+  position: relative;
+  width: 104px;
+  height: 92px;
+  margin-top: 2px;
+  border-radius: 28px 28px 34px 34px;
+  background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #e3eeff 100%);
+  border: 1px solid rgba(0, 85, 212, 0.14);
+  box-shadow: 0 18px 36px rgba(0, 85, 212, 0.16);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.assistant-bot-arm {
+  position: absolute;
+  top: 18px;
+  width: 16px;
+  height: 44px;
+  border-radius: 999px;
+  background: linear-gradient(180deg, #eff5ff, #c7dbff);
+  border: 1px solid rgba(0, 85, 212, 0.12);
+}
+
+.assistant-bot-arm-left {
+  left: -10px;
+  transform: rotate(16deg);
+}
+
+.assistant-bot-arm-right {
+  right: -10px;
+  transform: rotate(-16deg);
+}
+
+.assistant-bot-core {
+  position: relative;
+  width: 46px;
+  height: 46px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: $primary-blue;
+  background: radial-gradient(circle, rgba(255, 255, 255, 1) 0%, #dae8ff 55%, #adc7ff 100%);
+}
+
+.assistant-bot-core-ring {
+  position: absolute;
+  inset: -6px;
+  border-radius: 50%;
+  border: 1px solid rgba(88, 135, 255, 0.3);
+  border-top-color: rgba(255, 96, 139, 0.85);
+  border-right-color: rgba(79, 145, 255, 0.9);
+}
+
+.assistant-status {
+  position: relative;
+  z-index: 1;
+  margin-top: 14px;
+  padding: 6px 12px;
+  border-radius: 999px;
+  font-size: 12px;
+  font-weight: 600;
+  color: $deep-blue;
+  background: rgba(255, 255, 255, 0.95);
+  border: 1px solid rgba(0, 85, 212, 0.12);
+  box-shadow: 0 8px 20px rgba(0, 85, 212, 0.08);
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.assistant-status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #2e8ce0;
+  box-shadow: 0 0 10px rgba(46, 140, 224, 0.72);
+}
+
+.assistant-base {
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  border-radius: 50%;
+  border: 2px solid rgba(255, 93, 122, 0.22);
+  background: radial-gradient(circle, rgba(255, 255, 255, 0.9) 0%, rgba(255, 111, 145, 0.1) 70%, transparent 100%);
+}
+
+.assistant-base-lg {
+  width: 118px;
+  height: 30px;
+}
+
+.assistant-base-md {
+  bottom: 6px;
+  width: 88px;
+  height: 20px;
+  border-color: rgba(255, 93, 122, 0.34);
+}
+
+.assistant-base-sm {
+  bottom: 11px;
+  width: 54px;
+  height: 10px;
+  background: linear-gradient(90deg, rgba(255, 93, 122, 0.95), rgba(255, 173, 188, 0.9));
+  border: none;
+  box-shadow: 0 0 18px rgba(255, 93, 122, 0.38);
+}
+
+@keyframes robotBlink {
+  0%, 44%, 48%, 100% {
+    transform: scaleY(1);
+  }
+  46% {
+    transform: scaleY(0.14);
+  }
+}
+
+@keyframes robotBlinkFast {
+  0%, 100% {
+    transform: scaleY(1);
+  }
+  50% {
+    transform: scaleY(0.3);
+  }
+}
+
+@keyframes robotTalk {
+  0%, 100% {
+    transform: translateX(-50%) scaleX(1);
+  }
+  50% {
+    transform: translateX(-50%) scaleX(1.35);
+  }
+}
+
+@keyframes corePulse {
+  0%, 100% {
+    transform: scale(1);
+    filter: brightness(1);
+  }
+  50% {
+    transform: scale(1.08);
+    filter: brightness(1.08);
+  }
+}
+
+@keyframes coreRotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes orbitRotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes orbitRotateReverse {
+  from {
+    transform: rotate(360deg);
+  }
+  to {
+    transform: rotate(0deg);
+  }
+}
+
+@keyframes scanRing {
+  0%, 100% {
+    transform: scale(0.96);
+    opacity: 0.42;
+  }
+  50% {
+    transform: scale(1.04);
+    opacity: 0.86;
+  }
+}
+
+@keyframes thinkingDot {
+  0%, 100% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.35);
+  }
+}
+
+.welcome-card {
+  position: relative;
+  padding: 14px 14px 12px;
+  border-radius: 16px;
+  background:
+    linear-gradient(#fff, #fff) padding-box,
+    linear-gradient(135deg, rgba(255, 64, 96, 0.85), rgba(117, 65, 255, 0.9)) border-box;
+  border: 1px solid transparent;
+  box-shadow: 0 16px 36px rgba(0, 85, 212, 0.12);
+
+  &.compact {
+    padding: 10px 12px;
+    border-radius: 12px;
+    box-shadow: 0 8px 16px rgba(0, 85, 212, 0.07);
+
+    .welcome-eyebrow {
+      margin-bottom: 4px;
+    }
+
+    .welcome-title {
+      font-size: 17px;
+      line-height: 1.3;
+
+      br {
+        display: none;
+      }
+    }
+
+    .welcome-desc {
+      margin-top: 6px;
+      font-size: 12px;
+      line-height: 1.55;
+    }
+
+    .quick-prompt-list {
+      margin-top: 10px;
+      gap: 6px;
+    }
+
+    .quick-prompt-btn {
+      padding: 8px 10px;
+      font-size: 12px;
+      border-radius: 7px;
+    }
+
+    .more-prompts-btn {
+      margin-top: 8px;
+      font-size: 12px;
+    }
+  }
+}
+
+.welcome-eyebrow {
+  font-size: 11px;
+  font-weight: 700;
+  letter-spacing: 2px;
+  color: rgba(0, 85, 212, 0.58);
+  margin-bottom: 8px;
+}
+
+.welcome-title {
+  margin: 0;
+  font-size: 26px;
+  line-height: 1.2;
+  font-weight: 800;
+  color: #172033;
+}
+
+.welcome-desc {
+  margin: 10px 0 0;
+  font-size: 13px;
+  line-height: 1.7;
+  color: #5f6980;
+}
+
+.quick-prompt-list {
+  display: grid;
+  gap: 8px;
+  margin-top: 14px;
+}
+
+.quick-prompt-btn {
+  width: 100%;
+  border: none;
+  border-radius: 10px;
+  padding: 11px 14px;
+  text-align: left;
+  font-size: 13px;
+  font-weight: 600;
+  color: #fff;
+  cursor: pointer;
+  background: linear-gradient(90deg, #ff4c55 0%, #7c38ef 100%);
+  box-shadow: 0 12px 22px rgba(124, 56, 239, 0.18);
+  transition: transform 0.25s ease, box-shadow 0.25s ease, opacity 0.2s ease;
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    inset: 0;
+    background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), transparent 56%);
+    pointer-events: none;
+  }
+
+  &::after {
+    content: '';
+    position: absolute;
+    top: -120%;
+    left: -30%;
+    width: 45%;
+    height: 260%;
+    background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+    transform: rotate(22deg);
+    opacity: 0;
+    transition: all 0.35s ease;
+  }
+
+  &:hover:not(:disabled) {
+    transform: translateY(-2px) scale(1.01);
+    box-shadow: 0 16px 28px rgba(124, 56, 239, 0.24);
+
+    &::after {
+      left: 100%;
+      opacity: 1;
+    }
+  }
+
+  &:disabled {
+    cursor: not-allowed;
+    opacity: 0.65;
+  }
+}
+
+.more-prompts-btn {
+  margin-top: 10px;
+  padding: 0 12px;
+  height: 32px;
+  border: 1px solid rgba(208, 65, 81, 0.12);
+  border-radius: 999px;
+  background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(255, 241, 245, 0.96));
+  color: #d04151;
+  font-size: 13px;
+  font-weight: 600;
+  cursor: pointer;
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  box-shadow: 0 10px 18px rgba(208, 65, 81, 0.08);
+  transition: all 0.25s ease;
+
+  &:hover {
+    transform: translateY(-1px);
+    background: linear-gradient(135deg, #ff5570 0%, #8a3df6 100%);
+    border-color: transparent;
+    color: #fff;
+    box-shadow: 0 14px 24px rgba(138, 61, 246, 0.18);
+  }
+}
+
+.hero-dot-grid {
+  display: grid;
+  grid-template-columns: repeat(14, 1fr);
+  gap: 7px;
+  padding: 0 18px 14px;
+
+  span {
+    display: block;
+    width: 100%;
+    aspect-ratio: 1;
+    border-radius: 2px;
+    background: linear-gradient(135deg, rgba(255, 110, 138, 0.95), rgba(255, 190, 201, 0.55));
+  }
+}
+
+.message-list {
+  padding: 8px 18px 18px;
+  gap: 16px;
+  background: transparent;
+}
+
+.input-area {
+  padding: 12px 18px 16px;
+  background: #fff;
+  border-top: none;
+
+  &::before {
+    display: none;
+  }
+
+  .input-box {
+    padding: 14px 16px 16px;
+    border: 1px solid rgba(123, 56, 239, 0.9);
+    border-radius: 22px;
+    margin: 0;
+    transition: all 0.25s ease;
+    box-shadow: 0 14px 34px rgba(0, 85, 212, 0.08);
+
+    &:focus-within {
+      border-color: #7c38ef;
+      box-shadow: 0 0 0 3px rgba(124, 56, 239, 0.1), 0 18px 40px rgba(0, 85, 212, 0.12);
+      transform: none;
+    }
+
+    :deep(.el-textarea__inner) {
+      padding-right: 58px;
+      padding-bottom: 0;
+      min-height: 104px;
+
+      &::placeholder {
+        color: #a0a9bc;
+      }
+    }
+
+    .send-btn {
+      right: 25px;
+      top: 50%;
+      transform: translateY(-50%);
+      width: 36px;
+      min-width: 36px;
+      height: 36px;
+      padding: 0;
+      background: linear-gradient(135deg, #ff5570 0%, #7a36f2 58%, #2d79ff 100%);
+      border-radius: 50%;
+      box-shadow: 0 12px 24px rgba(109, 50, 236, 0.24);
+      transition: all 0.25s ease;
+      gap: 0;
+
+      &:hover:not(:disabled) {
+        transform: translateY(calc(-50% - 1px)) scale(1.04);
+        box-shadow: 0 16px 28px rgba(109, 50, 236, 0.3);
+      }
+
+      &:active:not(:disabled) {
+        transform: translateY(-50%) scale(0.96);
+      }
+
+      .el-icon {
+        margin: 0;
+        font-size: 16px;
+        transform: translate(0, -1px);
+      }
+    }
+  }
+}
+
+@media (max-width: 767px) {
+  .chat-hero {
+    grid-template-columns: 1fr;
+    gap: 10px;
+    padding: 14px 14px 6px;
+
+    &.compact {
+      padding: 8px 14px 4px;
+    }
+  }
+
+  .assistant-stand {
+    min-height: 184px;
+  }
+
+  .welcome-card {
+    padding: 12px 12px 10px;
+  }
+
+  .welcome-title {
+    font-size: 21px;
+  }
+
+  .hero-dot-grid {
+    grid-template-columns: repeat(12, 1fr);
+    gap: 6px;
+    padding: 0 14px 12px;
+  }
+
+  .message-list {
+    padding: 8px 14px 14px;
+  }
+
+  .input-area {
+    padding: 10px 14px 14px;
+  }
+
+  .input-area .input-actions {
+    gap: 10px;
+    flex-wrap: wrap;
+  }
+}
+</style>
diff --git a/src/components/Dialog/FileList.vue b/src/components/Dialog/FileList.vue
new file mode 100644
index 0000000..e373c27
--- /dev/null
+++ b/src/components/Dialog/FileList.vue
@@ -0,0 +1,253 @@
+<template>
+  <el-dialog
+      v-model="isShow"
+      :title="title"
+      :width="width"
+      @close="handleClose"
+      class="attachment-dialog"
+  >
+    <!-- 宸ュ叿鏍� -->
+    <div class="toolbar">
+      <el-button
+          type="primary"
+          size="small"
+          @click="handleUpload"
+      >
+        涓婁紶闄勪欢
+      </el-button>
+    </div>
+
+    <!-- 涓婁紶缁勪欢寮圭獥 -->
+    <el-dialog
+        v-model="uploadDialogVisible"
+        title="涓婁紶闄勪欢"
+        width="50%"
+        @close="handleUploadClose"
+    >
+      <AttachmentUpload
+          v-model:file-list="newFileList"
+      />
+      <template #footer>
+        <el-button @click="handleUploadClose">鍏抽棴</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 鏂囦欢鍒楄〃琛ㄦ牸 -->
+    <div class="table-container">
+      <el-table
+          :data="tableData"
+          border
+          class="attachment-table"
+          :height="tableData.length > 0 ? 'auto' : '120px'"
+      >
+        <el-table-column
+            label="闄勪欢鍚嶇О"
+            prop="originalFilename"
+            show-overflow-tooltip
+        />
+        <el-table-column
+            v-if="showActions"
+            fixed="right"
+            label="鎿嶄綔"
+            :width="120"
+            align="center"
+        >
+          <template #default="scope">
+            <el-button
+                link
+                type="primary"
+                size="small"
+                :href="scope.row.downloadURL"
+                class="download-link"
+            >
+              涓嬭浇
+            </el-button>
+            <el-button
+                link
+                type="danger"
+                size="small"
+                @click="handleDelete(scope.row)"
+            >
+              鍒犻櫎
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance, onMounted, watch } from 'vue'
+import AttachmentUpload from '@/components/AttachmentUpload/file/index.vue'
+import {attachmentList, deleteAttachment, createAttachment} from "@/api/basicData/storageAttachment.js";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+  recordType: {
+    type: String,
+    default: '',
+    required: true
+  },
+  recordId: {
+    type: Number,
+    default: 0,
+    required: true
+  },
+  title: {
+    type: String,
+    default: '闄勪欢'
+  },
+  width: {
+    type: String,
+    default: '50%'
+  },
+  showActions: {
+    type: Boolean,
+    default: true
+  }
+})
+
+const emit = defineEmits([
+  'close',
+  'download',
+  'upload',
+  'delete'
+])
+
+const { proxy } = getCurrentInstance()
+const tableData = ref([])
+const uploadDialogVisible = ref(false)
+const newFileList = ref([])
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit("update:visible", val);
+  },
+});
+
+const handleClose = () => {
+  isShow.value = false
+}
+
+const handleUpload = () => {
+  uploadDialogVisible.value = true
+}
+
+const handleUploadClose = async () => {
+  // 妫�鏌ユ槸鍚︽湁鏂颁笂浼犵殑鏂囦欢
+  if (newFileList.value.length > 0) {
+    try {
+      await createAttachment({
+        application: 'file',
+        recordType: props.recordType,
+        recordId: props.recordId,
+        storageBlobDTOs: [...newFileList.value, ...tableData.value]
+      })
+      newFileList.value = []
+      // 鍒锋柊鍒楄〃
+      setList()
+    } catch (error) {
+      proxy?.$modal?.msgError('涓婁紶澶辫触')
+    }
+  }
+  uploadDialogVisible.value = false
+}
+
+
+
+const handleDelete = async (row, index) => {
+  try {
+    await deleteAttachment([row.storageAttachmentId])
+    proxy?.$modal?.msgSuccess('鍒犻櫎鎴愬姛')
+    setList()
+  } catch (error) {
+    proxy?.$modal?.msgError('鍒犻櫎澶辫触')
+  }
+}
+
+const setList = () => {
+  attachmentList({
+    recordType: props.recordType,
+    recordId: props.recordId,
+  }).then(res => {
+    if (res && res.data) {
+      tableData.value = res.data || []
+    }
+  })
+}
+
+onMounted(() => {
+  setList()
+})
+</script>
+
+<style scoped>
+.attachment-dialog {
+  border-radius: 12px;
+}
+
+.toolbar {
+  margin-bottom: 16px;
+  text-align: right;
+}
+
+.table-container {
+  max-height: 40vh;
+  overflow-y: auto;
+  min-height: 120px;
+  padding-bottom: 16px;
+  box-sizing: border-box;
+  will-change: scroll-position;
+  transform: translateZ(0);
+  -webkit-overflow-scrolling: touch;
+}
+
+:deep(.el-table) {
+  margin-bottom: 0;
+}
+
+:deep(.el-table__body-wrapper) {
+  overflow-y: auto;
+  will-change: transform;
+  transform: translateZ(0);
+}
+
+:deep(.el-table__body tr) {
+  transition: none;
+}
+
+:deep(.el-dialog__footer) {
+  padding-top: 12px;
+  border-top: 1px solid #e9ecef;
+}
+
+.attachment-table {
+  border-radius: 8px;
+}
+
+:deep(.el-dialog__header) {
+  background-color: #f8f9fa;
+  border-bottom: 1px solid #e9ecef;
+  padding: 16px 20px;
+}
+
+:deep(.el-dialog__title) {
+  font-size: 16px;
+  font-weight: 600;
+}
+
+:deep(.el-dialog__body) {
+  padding: 16px 20px;
+}
+
+:deep(.el-table__empty-text) {
+  color: #999;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/PurchaseAIChatSidebar/index.vue b/src/components/PurchaseAIChatSidebar/index.vue
new file mode 100644
index 0000000..f97d521
--- /dev/null
+++ b/src/components/PurchaseAIChatSidebar/index.vue
@@ -0,0 +1,24 @@
+<template>
+  <AIChatSidebar :assistants="assistants" default-assistant="purchase" />
+</template>
+
+<script setup>
+import { ShoppingCart } from '@element-plus/icons-vue'
+import AIChatSidebar from '@/components/AIChatSidebar/index.vue'
+
+const assistants = [
+  {
+    key: 'purchase',
+    label: '閲囪喘鍔╃悊',
+    title: '閲囪喘鏅鸿兘鍔╃悊',
+    tooltip: '閲囪喘鏅鸿兘鍔╃悊',
+    icon: ShoppingCart,
+    apiBase: '/purchase-ai',
+    storageKey: 'purchase_ai_chat_uuid',
+    placeholder: '璇疯緭鍏ラ噰璐棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
+    welcomeMessage: '浣犲ソ',
+    allowFileUpload: false,
+    emptySessionText: '鏆傛棤閲囪喘浼氳瘽'
+  }
+]
+</script>
diff --git a/src/layout/index.vue b/src/layout/index.vue
index d3580d0..a1bb724 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -1,131 +1,133 @@
-<template>
-  <div :class="classObj"
-       class="app-wrapper"
-       :style="{ '--current-color': theme }">
-    <div v-if="device === 'mobile' && sidebar.opened"
-         class="drawer-bg"
-         @click="handleClickOutside" />
-    <sidebar v-if="!sidebar.hide"
-             class="sidebar-container" />
-    <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }"
-         class="main-container">
-      <div :class="{ 'fixed-header': fixedHeader }">
-        <navbar @setLayout="setLayout" />
-        <tags-view v-if="needTagsView" />
-      </div>
-      <app-main />
-      <settings ref="settingRef" />
-    </div>
-  </div>
-</template>
-
-<script setup>
-  import { useWindowSize } from "@vueuse/core";
-  import Sidebar from "./components/Sidebar/index.vue";
-  import { AppMain, Navbar, Settings, TagsView } from "./components";
-  import defaultSettings from "@/settings";
-
-  import useAppStore from "@/store/modules/app";
-  import useSettingsStore from "@/store/modules/settings";
-
-  const settingsStore = useSettingsStore();
-  const theme = computed(() => settingsStore.theme);
-  const sideTheme = computed(() => settingsStore.sideTheme);
-  const sidebar = computed(() => useAppStore().sidebar);
-  const device = computed(() => useAppStore().device);
-  const needTagsView = computed(() => settingsStore.tagsView);
-  const fixedHeader = computed(() => settingsStore.fixedHeader);
-
-  const classObj = computed(() => ({
-    hideSidebar: !sidebar.value.opened,
-    openSidebar: sidebar.value.opened,
-    withoutAnimation: sidebar.value.withoutAnimation,
-    mobile: device.value === "mobile",
-  }));
-
-  const { width, height } = useWindowSize();
-  const WIDTH = 992; // refer to Bootstrap's responsive design
-
-  watch(
-    () => device.value,
-    () => {
-      if (device.value === "mobile" && sidebar.value.opened) {
-        useAppStore().closeSideBar({ withoutAnimation: false });
-      }
-    }
-  );
-
-  watchEffect(() => {
-    if (width.value - 1 < WIDTH) {
-      useAppStore().toggleDevice("mobile");
-      useAppStore().closeSideBar({ withoutAnimation: true });
-    } else {
-      useAppStore().toggleDevice("desktop");
-    }
-  });
-
-  function handleClickOutside() {
-    useAppStore().closeSideBar({ withoutAnimation: false });
-  }
-
-  const settingRef = ref(null);
-  function setLayout() {
-    settingRef.value.openSetting();
-  }
-</script>
-
-<style lang="scss" scoped>
-  @import "@/assets/styles/mixin.scss";
-  @import "@/assets/styles/variables.module.scss";
-
-  .app-wrapper {
-    @include clearfix;
-    position: relative;
-    height: 100%;
-    width: 100%;
-    background: radial-gradient(
-        circle at top,
-        rgba(223, 232, 226, 0.95),
-        transparent 32%
-      ),
-      linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
-
-    &.mobile.openSidebar {
-      position: fixed;
-      top: 0;
-    }
-  }
-
-  .drawer-bg {
-    background: #000;
-    opacity: 0.3;
-    width: 100%;
-    top: 0;
-    height: 100%;
-    position: absolute;
-    z-index: 999;
-  }
-
-  .fixed-header {
-    position: fixed;
-    top: 0px;
-    padding-top: 12px;
-    right: 16px;
-    z-index: 9;
-    width: calc(100% - #{$base-sidebar-width} - 32px);
-    transition: width 0.28s, right 0.28s;
-    padding-bottom: 8px;
-    background-color: #f3f6f4;
-  }
-  .hideSidebar .fixed-header {
-    width: calc(100% - 100px);
-  }
-
-  .sidebarHide .fixed-header {
-    width: calc(100% - 32px);
-  }
-
-  .mobile .fixed-header {
-    width: 100%;
-  }
-</style>
+<template>
+  <div :class="classObj"
+       class="app-wrapper"
+       :style="{ '--current-color': theme }">
+    <div v-if="device === 'mobile' && sidebar.opened"
+         class="drawer-bg"
+         @click="handleClickOutside" />
+    <sidebar v-if="!sidebar.hide"
+             class="sidebar-container" />
+    <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }"
+         class="main-container">
+      <div :class="{ 'fixed-header': fixedHeader }">
+        <navbar @setLayout="setLayout" />
+        <tags-view v-if="needTagsView" />
+      </div>
+      <app-main />
+      <settings ref="settingRef" />
+    </div>
+    <AIChatSidebar />
+  </div>
+</template>
+
+<script setup>
+  import { useWindowSize } from "@vueuse/core";
+  import Sidebar from "./components/Sidebar/index.vue";
+  import { AppMain, Navbar, Settings, TagsView } from "./components";
+  import AIChatSidebar from "@/components/AIChatSidebar/index.vue";
+  import defaultSettings from "@/settings";
+
+  import useAppStore from "@/store/modules/app";
+  import useSettingsStore from "@/store/modules/settings";
+
+  const settingsStore = useSettingsStore();
+  const theme = computed(() => settingsStore.theme);
+  const sideTheme = computed(() => settingsStore.sideTheme);
+  const sidebar = computed(() => useAppStore().sidebar);
+  const device = computed(() => useAppStore().device);
+  const needTagsView = computed(() => settingsStore.tagsView);
+  const fixedHeader = computed(() => settingsStore.fixedHeader);
+
+  const classObj = computed(() => ({
+    hideSidebar: !sidebar.value.opened,
+    openSidebar: sidebar.value.opened,
+    withoutAnimation: sidebar.value.withoutAnimation,
+    mobile: device.value === "mobile",
+  }));
+
+  const { width, height } = useWindowSize();
+  const WIDTH = 992; // refer to Bootstrap's responsive design
+
+  watch(
+    () => device.value,
+    () => {
+      if (device.value === "mobile" && sidebar.value.opened) {
+        useAppStore().closeSideBar({ withoutAnimation: false });
+      }
+    }
+  );
+
+  watchEffect(() => {
+    if (width.value - 1 < WIDTH) {
+      useAppStore().toggleDevice("mobile");
+      useAppStore().closeSideBar({ withoutAnimation: true });
+    } else {
+      useAppStore().toggleDevice("desktop");
+    }
+  });
+
+  function handleClickOutside() {
+    useAppStore().closeSideBar({ withoutAnimation: false });
+  }
+
+  const settingRef = ref(null);
+  function setLayout() {
+    settingRef.value.openSetting();
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/styles/mixin.scss";
+  @import "@/assets/styles/variables.module.scss";
+
+  .app-wrapper {
+    @include clearfix;
+    position: relative;
+    height: 100%;
+    width: 100%;
+    background: radial-gradient(
+        circle at top,
+        rgba(223, 232, 226, 0.95),
+        transparent 32%
+      ),
+      linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
+
+    &.mobile.openSidebar {
+      position: fixed;
+      top: 0;
+    }
+  }
+
+  .drawer-bg {
+    background: #000;
+    opacity: 0.3;
+    width: 100%;
+    top: 0;
+    height: 100%;
+    position: absolute;
+    z-index: 999;
+  }
+
+  .fixed-header {
+    position: fixed;
+    top: 0px;
+    padding-top: 12px;
+    right: 16px;
+    z-index: 9;
+    width: calc(100% - #{$base-sidebar-width} - 32px);
+    transition: width 0.28s, right 0.28s;
+    padding-bottom: 8px;
+    background-color: #f3f6f4;
+  }
+  .hideSidebar .fixed-header {
+    width: calc(100% - 100px);
+  }
+
+  .sidebarHide .fixed-header {
+    width: calc(100% - 32px);
+  }
+
+  .mobile .fixed-header {
+    width: 100%;
+  }
+</style>
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 1728b37..5e31943 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -49,8 +49,8 @@
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="绫荤洰">
-            <el-input v-model="form.machineryCategory" placeholder="璇疯緭鍏ョ被鐩�" />
+          <el-form-item label="椤圭洰">
+            <el-input v-model="form.machineryCategory" placeholder="璇疯緭鍏ラ」鐩�" />
           </el-form-item>
         </el-col>
       </el-row>
@@ -77,12 +77,20 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <el-row :gutter="30">
+        <el-col :span="24">
+          <el-form-item label="闄勪欢" prop="attachmentIds">
+            <FileUpload v-model:file-list="form.storageBlobDTOs" />
+          </el-form-item>
+        </el-col>
+      </el-row>
     </el-form>
   </FormDialog>
 </template>
 
 <script setup>
 import FormDialog from "@/components/Dialog/FormDialog.vue";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
 import {
   addRepair,
   editRepair,
@@ -106,6 +114,7 @@
 
 const userStore = useUserStore();
 const deviceOptions = ref([]);
+const fileList = ref([]);
 
 const loadDeviceName = async () => {
   const { data } = await getDeviceLedger();
@@ -121,6 +130,7 @@
   remark: undefined, // 鏁呴殰鐜拌薄
   status: 0, // 鎶ヤ慨鐘舵��
   machineryCategory: undefined,
+  storageBlobDTOs: [],
 });
 
 const setDeviceModel = (deviceId) => {
@@ -137,6 +147,7 @@
   form.remark = data.remark;
   form.status = data.status;
   form.machineryCategory = data.machineryCategory;
+  form.storageBlobDTOs = data.storageBlobVOs || [];
 };
 
 const sendForm = async () => {
@@ -168,6 +179,7 @@
 const openAdd = async () => {
   id.value = undefined;
   visible.value = true;
+  fileList.value = [];
   await nextTick();
   await loadDeviceName();
 };
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index 27d0acb..f3a4330 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -127,22 +127,31 @@
           >
             鍒犻櫎
           </el-button>
+          <el-button
+              type="primary"
+              link
+              @click="openFileDialog(row)"
+          >
+            闄勪欢
+          </el-button>
         </template>
       </PIMTable>
     </div>
     <RepairModal ref="repairModalRef" @ok="getTableData"/>
     <MaintainModal ref="maintainModalRef" @ok="getTableData"/>
+    <FileList v-if="fileDialogVisible"  v-model:visible="fileDialogVisible" :record-type="'device_repair'" :record-id="recordId"  />
   </div>
 </template>
 
 <script setup>
-import { onMounted, getCurrentInstance, computed } from "vue";
+import {onMounted, getCurrentInstance, computed, ref, defineAsyncComponent} from "vue";
 import {usePaginationApi} from "@/hooks/usePaginationApi";
 import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
 import RepairModal from "./Modal/RepairModal.vue";
 import {ElMessageBox, ElMessage} from "element-plus";
 import dayjs from "dayjs";
 import MaintainModal from "./Modal/MaintainModal.vue";
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
 
 defineOptions({
   name: "璁惧鎶ヤ慨",
@@ -188,7 +197,7 @@
         prop: "deviceModel",
       },
       {
-        label: "绫荤洰",
+        label: "椤圭洰",
         align: "center",
         prop: "machineryCategory",
       },
@@ -258,6 +267,15 @@
   getTableData();
 };
 
+// 鎵撳紑闄勪欢寮圭獥
+const recordId =ref(0)
+const fileDialogVisible = ref(false)
+
+const openFileDialog = async (row) => {
+  recordId.value = row.id
+  fileDialogVisible.value = true
+}
+
 // 澶氶�夊悗鍋氫粈涔�
 const handleSelectionChange = (selectionList) => {
   multipleList.value = selectionList;
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
index 6fa6595..ee59ce2 100644
--- a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
+++ b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
@@ -32,10 +32,10 @@
           disabled
         />
       </el-form-item>
-      <el-form-item label="绫荤洰">
+      <el-form-item label="椤圭洰">
         <el-input
             v-model="form.machineryCategory"
-            placeholder="璇疯緭鍏ョ被鐩�"
+            placeholder="璇疯緭鍏ラ」鐩�"
         />
       </el-form-item>
       <el-form-item label="褰曞叆浜�">
@@ -73,6 +73,13 @@
           clearable
         />
       </el-form-item>
+      <el-row :gutter="30">
+        <el-col :span="24">
+          <el-form-item label="闄勪欢" prop="attachmentIds">
+            <FileUpload v-model:file-list="form.storageBlobDTOs" />
+          </el-form-item>
+        </el-col>
+      </el-row>
     </el-form>
   </FormDialog>
 </template>
@@ -90,6 +97,7 @@
 import { onMounted } from "vue";
 import dayjs from "dayjs";
 import { userListNoPage } from "@/api/system/user.js";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
 
 defineOptions({
   name: "璁惧淇濆吇鏂板璁″垝",
@@ -115,6 +123,7 @@
   createUser: undefined, // 褰曞叆浜�
   status: 0, //淇濅慨鐘舵��
   machineryCategory: undefined,
+  storageBlobDTOs: [],
 });
 
 const setDeviceModel = (deviceId) => {
@@ -133,9 +142,12 @@
   form.createUser = Number(data.createUser);
   form.status = data.status;
   form.machineryCategory = data.machineryCategory;
-  form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
-    "YYYY-MM-DD HH:mm:ss"
-  );
+  if (data.maintenancePlanTime) {
+    form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
+      "YYYY-MM-DD HH:mm:ss"
+    );
+  }
+  form.storageBlobDTOs = data.storageBlobVOs || [];
 };
 
 // 鐢ㄦ埛鍒楄〃
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 1e65663..3b771bf 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -218,40 +218,27 @@
     <PlanModal ref="planModalRef" @ok="getTableData" />
         <MaintenanceModal ref="maintainModalRef" @ok="getTableData" />
         <FormDia ref="formDiaRef" @closeDia="getScheduledTableData" />
-    <FileListDialog 
-      ref="fileListDialogRef"
-      v-model="fileDialogVisible"
-      :show-upload-button="true"
-      :show-delete-button="true"
-      :delete-method="handleAttachmentDelete"
-      :name-column-label="'闄勪欢鍚嶇О'"
-      :rulesRegulationsManagementId="currentMaintenanceTaskId"
-      @upload="handleAttachmentUpload" />
+    <FileList v-if="fileDialogVisible"  v-model:visible="fileDialogVisible" :record-type="'device_maintenance'" :record-id="currentMaintenanceTaskId"  />
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted, reactive, getCurrentInstance, nextTick, computed } from 'vue'
+import {ref, onMounted, reactive, getCurrentInstance, nextTick, computed, defineAsyncComponent} from 'vue'
 import { Search } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import PlanModal from './Form/PlanModal.vue'
 import MaintenanceModal from './Form/MaintenanceModal.vue'
 import FormDia from './Form/formDia.vue'
-import FileListDialog from '@/components/Dialog/FileListDialog.vue'
 import {
   getUpkeepPage,
   delUpkeep,
   deviceMaintenanceTaskList,
   deviceMaintenanceTaskDel,
 } from '@/api/equipmentManagement/upkeep'
-import {
-  listMaintenanceTaskFiles,
-  addMaintenanceTaskFile,
-  delMaintenanceTaskFile,
-} from '@/api/equipmentManagement/maintenanceTaskFile'
 import dayjs from 'dayjs'
 
 const { proxy } = getCurrentInstance()
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
 
 // Tab鐩稿叧
 const activeTab = ref('scheduled')
@@ -373,7 +360,7 @@
 		prop: "createUserName",
 	},
   {
-    label: "绫荤洰",
+    label: "椤圭洰",
     align: "center",
     prop: "machineryCategory",
   },
@@ -602,77 +589,10 @@
   getTableData()
 }
 
-// 闄勪欢鐩稿叧鏂规硶
-// 鏌ヨ闄勪欢鍒楄〃
-const fetchMaintenanceTaskFiles = async (deviceMaintenanceId) => {
-  try {
-    const params = {
-      current: 1,
-      size: 100,
-      deviceMaintenanceId,
-      rulesRegulationsManagementId:deviceMaintenanceId
-    }
-    const res = await listMaintenanceTaskFiles(params)
-    const records = res?.data?.records || []
-    const mapped = records.map(item => ({
-      id: item.id,
-      name: item.fileName || item.name,
-      url: item.fileUrl || item.url,
-      raw: item,
-    }))
-    fileListDialogRef.value?.setList(mapped)
-  } catch (error) {
-    ElMessage.error('鑾峰彇闄勪欢鍒楄〃澶辫触')
-  }
-}
-
 // 鎵撳紑闄勪欢寮圭獥
 const openFileDialog = async (row) => {
   currentMaintenanceTaskId.value = row.id
   fileDialogVisible.value = true
-  await fetchMaintenanceTaskFiles(row.id)
-}
-
-// 鍒锋柊闄勪欢鍒楄〃
-const refreshFileList = async () => {
-  if (!currentMaintenanceTaskId.value) return
-  await fetchMaintenanceTaskFiles(currentMaintenanceTaskId.value)
-}
-
-// 涓婁紶闄勪欢
-const handleAttachmentUpload = async (filePayload) => {
-  if (!currentMaintenanceTaskId.value) return
-  try {
-    const payload = {
-      name: filePayload?.fileName || filePayload?.name,
-      url: filePayload?.fileUrl || filePayload?.url,
-      deviceMaintenanceId: currentMaintenanceTaskId.value,
-    }
-    await addMaintenanceTaskFile(payload)
-    ElMessage.success('鏂囦欢涓婁紶鎴愬姛')
-    await refreshFileList()
-  } catch (error) {
-    ElMessage.error('鏂囦欢涓婁紶澶辫触')
-  }
-}
-
-// 鍒犻櫎闄勪欢
-const handleAttachmentDelete = async (row) => {
-  if (!row?.id) return false
-  try {
-    await ElMessageBox.confirm('纭鍒犻櫎璇ラ檮浠讹紵', '鎻愮ず', { type: 'warning' })
-  } catch {
-    return false
-  }
-  try {
-    await delMaintenanceTaskFile(row.id)
-    ElMessage.success('鍒犻櫎鎴愬姛')
-    await refreshFileList()
-    return true
-  } catch (error) {
-    ElMessage.error('鍒犻櫎澶辫触')
-    return false
-  }
 }
 
 onMounted(() => {
diff --git a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
index a83ff6a..e9b2646 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -35,16 +35,30 @@
             <el-button type="primary"
                        link
                        @click="handleViewSupplementRecord(row)">
-              {{ row.supplementQty ?? 0 }}
+              {{ row.feedingQty ?? 0 }}
             </el-button>
           </template>
         </el-table-column>
         <el-table-column label="閫�鏂欐暟閲�"
-                         prop="returnQty"
-                         min-width="110" />
+                         min-width="110">
+          <template #default="{ row }">
+            {{ row.returnQty ?? 0 }}
+          </template>
+        </el-table-column>
         <el-table-column label="瀹為檯鏁伴噺"
-                         prop="actualQty"
-                         min-width="110" />
+                         min-width="140">
+          <template #default="{ row }">
+            <el-input-number v-model="row.actualQty"
+                             :min="0"
+                             :precision="3"
+                             :step="1"
+                             controls-position="right"
+                             placeholder="杈撳叆瀹為檯鏁伴噺"
+                             style="width: 100%;"
+                             :disabled="row.returned"
+                             @change="val => handleActualQtyChange(row, val)" />
+          </template>
+        </el-table-column>
       </el-table>
       <template #footer>
         <span class="dialog-footer">
@@ -66,7 +80,7 @@
                 border
                 row-key="id">
         <el-table-column label="琛ユ枡鏁伴噺"
-                         prop="supplementQty"
+                         prop="pickQuantity"
                          min-width="120" />
         <el-table-column label="琛ユ枡浜�"
                          prop="supplementUserName"
@@ -75,7 +89,7 @@
                          prop="supplementTime"
                          min-width="160" />
         <el-table-column label="琛ユ枡鍘熷洜"
-                         prop="supplementReason"
+                         prop="feedingReason"
                          min-width="200" />
       </el-table>
       <template #footer>
@@ -121,7 +135,7 @@
   import {
     listMaterialPickingDetail,
     listMaterialSupplementRecord,
-    confirmMaterialReturn,
+    updateMaterialPickingLedger,
   } from "@/api/productionManagement/productionOrder.js";
 
   const props = defineProps({
@@ -145,10 +159,12 @@
   const returnSummaryList = ref([]);
   const calcReturnQty = item =>
     Number(item.pickQuantity || 0) +
-    Number(item.supplementQty || 0) -
+    Number(item.feedingQty || 0) -
     Number(item.actualQty || 0);
   const canOpenReturnSummary = computed(() =>
-    materialDetailTableData.value.some(item => calcReturnQty(item) > 0)
+    materialDetailTableData.value.some(
+      item => item.returned !== true && calcReturnQty(item) > 0
+    )
   );
 
   const loadDetailList = async () => {
@@ -157,7 +173,13 @@
     materialDetailTableData.value = [];
     try {
       const res = await listMaterialPickingDetail(props.orderRow.id);
-      materialDetailTableData.value = res.data || [];
+      materialDetailTableData.value = (res.data || []).map(item => ({
+        ...item,
+        actualQty:
+          item.actualQty ??
+          Number(item.pickQuantity || 0) + Number(item.feedingQty || 0),
+        returnQty: item.returnQty ?? 0,
+      }));
     } finally {
       materialDetailLoading.value = false;
     }
@@ -176,6 +198,10 @@
     materialDetailTableData.value = [];
   };
 
+  const handleActualQtyChange = (row, val) => {
+    row.returnQty = calcReturnQty(row);
+  };
+
   const handleViewSupplementRecord = async row => {
     if (!row?.id) return;
     supplementRecordDialogVisible.value = true;
@@ -183,7 +209,8 @@
     supplementRecordTableData.value = [];
     try {
       const res = await listMaterialSupplementRecord({
-        materialDetailId: row.id,
+        pickId: row.id,
+        productionOrderId: props.orderRow.id,
       });
       supplementRecordTableData.value = res.data || [];
     } finally {
@@ -225,9 +252,24 @@
     if (!props.orderRow?.id) return;
     materialReturnConfirming.value = true;
     try {
-      await confirmMaterialReturn({
-        orderId: props.orderRow.id,
-        returnSummaryList: returnSummaryList.value,
+      await updateMaterialPickingLedger({
+        productionOrderId: props.orderRow.id,
+        productionOrderPickDto: materialDetailTableData.value.map(item => ({
+          id: item.id,
+          technologyOperationId: item.technologyOperationId,
+          operationName: item.operationName,
+          bom: item.bom === true,
+          productModelId: item.productModelId,
+          demandedQuantity: item.demandedQuantity,
+          unit: item.unit,
+          pickQuantity: item.pickQuantity,
+          batchNo: item.batchNo,
+          feedingQty: item.feedingQty,
+          returnQty: item.returnQty,
+          actualQty: item.actualQty,
+          feedingReason: item.feedingReason,
+          returned: true,
+        })),
       });
       returnSummaryDialogVisible.value = false;
       dialogVisible.value = false;
diff --git a/src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
new file mode 100644
index 0000000..67e44f1
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
@@ -0,0 +1,159 @@
+<template>
+  <el-dialog v-model="dialogVisible"
+             title="琛ユ枡"
+             width="1200px"
+             @close="handleClose">
+    <el-table v-loading="loading"
+              :data="tableData"
+              border
+              row-key="id">
+      <el-table-column label="宸ュ簭鍚嶇О"
+                       prop="operationName"
+                       min-width="140" />
+      <el-table-column label="鍘熸枡鍚嶇О"
+                       prop="productName"
+                       min-width="140" />
+      <el-table-column label="鍘熸枡鍨嬪彿"
+                       prop="model"
+                       min-width="140" />
+      <el-table-column label="璁¢噺鍗曚綅"
+                       prop="unit"
+                       width="100" />
+      <el-table-column label="棰嗙敤鏁伴噺"
+                       prop="pickQuantity"
+                       width="100" />
+      <el-table-column label="琛ユ枡鏁伴噺"
+                       min-width="150">
+        <template #default="{ row }">
+          <el-input-number v-model="row.newSupplementQty"
+                           :min="0"
+                           :precision="3"
+                           :step="1"
+                           controls-position="right"
+                           placeholder="杈撳叆琛ユ枡鏁伴噺"
+                           style="width: 100%;" />
+        </template>
+      </el-table-column>
+      <el-table-column label="琛ユ枡鍘熷洜"
+                       min-width="200">
+        <template #default="{ row }">
+          <el-input v-model="row.newSupplementReason"
+                    placeholder="杈撳叆琛ユ枡鍘熷洜"
+                    maxlength="200"
+                    show-word-limit />
+        </template>
+      </el-table-column>
+    </el-table>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button type="primary"
+                   :loading="submitting"
+                   @click="handleSubmit">纭� 瀹�</el-button>
+        <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+  import { computed, ref, watch } from "vue";
+  import { ElMessage } from "element-plus";
+  import {
+    listMaterialPickingDetail,
+    updateMaterialPickingLedger,
+  } from "@/api/productionManagement/productionOrder.js";
+
+  const props = defineProps({
+    modelValue: { type: Boolean, default: false },
+    orderRow: { type: Object, default: null },
+  });
+  const emit = defineEmits(["update:modelValue", "saved"]);
+
+  const dialogVisible = computed({
+    get: () => props.modelValue,
+    set: val => emit("update:modelValue", val),
+  });
+
+  const loading = ref(false);
+  const submitting = ref(false);
+  const tableData = ref([]);
+
+  const loadData = async () => {
+    if (!props.orderRow?.id) return;
+    loading.value = true;
+    try {
+      const res = await listMaterialPickingDetail(props.orderRow.id);
+      tableData.value = (res.data || []).map(item => ({
+        ...item,
+        newSupplementQty: 0,
+        newSupplementReason: "",
+      }));
+    } catch (e) {
+      console.error("鑾峰彇鐗╂枡鏄庣粏澶辫触锛�", e);
+      ElMessage.error("鑾峰彇鐗╂枡鏄庣粏澶辫触");
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  watch(
+    () => dialogVisible.value,
+    visible => {
+      if (visible) {
+        loadData();
+      }
+    }
+  );
+
+  const handleClose = () => {
+    tableData.value = [];
+  };
+
+  const handleSubmit = async () => {
+    const supplementList = tableData.value.filter(
+      item => item.newSupplementQty > 0
+    );
+    if (supplementList.length === 0) {
+      ElMessage.warning("璇疯嚦灏戣緭鍏ヤ竴鏉¤ˉ鏂欐暟閲�");
+      return;
+    }
+
+    const invalidRow = supplementList.find(item => !item.newSupplementReason);
+    if (invalidRow) {
+      ElMessage.warning("璇疯緭鍏ヨˉ鏂欏師鍥�");
+      return;
+    }
+
+    submitting.value = true;
+    try {
+      await updateMaterialPickingLedger({
+        productionOrderId: props.orderRow.id,
+        productionOrderPickDto: tableData.value.map(item => ({
+          id: item.id,
+          technologyOperationId: item.technologyOperationId,
+          operationName: item.operationName,
+          bom: item.bom === true,
+          productModelId: item.productModelId,
+          demandedQuantity: item.demandedQuantity,
+          unit: item.unit,
+          pickQuantity: item.pickQuantity,
+          batchNo: item.batchNo,
+          feedingQuantity: item.newSupplementQty || 0,
+          feedingReason: item.newSupplementReason || "",
+          pickType: 2,
+        })),
+      });
+      ElMessage.success("琛ユ枡鎴愬姛");
+      dialogVisible.value = false;
+      emit("saved");
+    } catch (e) {
+      console.error("琛ユ枡澶辫触锛�", e);
+      ElMessage.error("琛ユ枡澶辫触");
+    } finally {
+      submitting.value = false;
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+</style>
diff --git a/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue b/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
new file mode 100644
index 0000000..d1aa267
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
@@ -0,0 +1,225 @@
+<template>
+  <div class="print-container"
+       id="print-requisition">
+    <div class="print-content">
+      <div class="bill-title">鐢熶骇棰嗘枡鍗�</div>
+      <div class="info-grid">
+        <div class="info-row">
+          <div class="info-item">
+            <span class="label">鍒涘缓鏃ユ湡锛�</span>
+            <span class="value">{{ formatDate(orderRow?.createTime) }}</span>
+          </div>
+          <div class="info-item">
+            <span class="label">棰嗘枡鍗曞彿锛�</span>
+            <span class="value">{{ orderRow?.npsNo }}</span>
+          </div>
+          <div class="info-item">
+            <span class="label">鐢宠浜猴細</span>
+            <span class="value">{{ userName }}</span>
+          </div>
+        </div>
+        <div class="info-row">
+          <div class="info-item"
+               style="width: 50%;">
+            <span class="label">浜у搧鍚嶇О/鍨嬪彿锛�</span>
+            <span class="value">{{ orderRow?.productName }} / {{ orderRow?.model }}</span>
+          </div>
+          <div class="info-item"
+               style="width: 25%;">
+            <span class="label">鐢熶骇鏁伴噺锛�</span>
+            <span class="value">{{ orderRow?.quantity }}</span>
+          </div>
+          <div class="info-item"
+               style="width: 25%;">
+            <span class="label">闇�姹傛棩鏈燂細</span>
+            <span class="value">{{ formatDate(orderRow?.planCompleteTime) }}</span>
+          </div>
+        </div>
+      </div>
+      <table class="material-table">
+        <thead>
+          <tr>
+            <th width="50">搴忓彿</th>
+            <th>宸ュ簭鍚嶇О</th>
+            <th>瑙勬牸/鍚嶇О</th>
+            <th>鎵瑰彿</th>
+            <th width="80">闇�姹傛暟閲�</th>
+            <th width="80">棰嗘枡鏁伴噺</th>
+            <th width="60">鍗曚綅</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(item, index) in materialList"
+              :key="index">
+            <td align="center">{{ index + 1 }}</td>
+            <td>{{ item.operationName || '-' }}</td>
+            <td>{{ item.materialName || item.productName }} {{ item.materialModel || item.model }}</td>
+            <td>{{ item.batchNo || '-' }}</td>
+            <td align="right">{{ item.demandedQuantity }}</td>
+            <td align="right">{{ item.pickQuantity || item.pickQty || 0 }}</td>
+            <td align="center">{{ item.unit }}</td>
+          </tr>
+        </tbody>
+      </table>
+      <div class="print-footer">
+        <div class="footer-item">棰嗘枡锛歘_______________</div>
+        <div class="footer-item">鍙戞枡锛歘_______________</div>
+        <div class="footer-item">瀹℃牳锛歘_______________</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import dayjs from "dayjs";
+  import useUserStore from "@/store/modules/user";
+  import { computed } from "vue";
+
+  const props = defineProps({
+    orderRow: {
+      type: Object,
+      default: () => ({}),
+    },
+    materialList: {
+      type: Array,
+      default: () => [],
+    },
+  });
+
+  const userStore = useUserStore();
+  const userName = computed(() => userStore.nickName || userStore.name || "-");
+
+  const formatDate = date => {
+    return date ? dayjs(date).format("YYYY骞碝M鏈圖D鏃�") : "-";
+  };
+</script>
+
+<style lang="scss">
+  /* 灞忓箷鏄剧ず鏍峰紡 */
+  .print-requisition-wrapper {
+    display: none;
+  }
+
+  /* 鎵撳嵃涓撶敤鏍峰紡 */
+  @media print {
+    @page {
+      size: landscape;
+      margin: 10mm;
+    }
+
+    /* 鍩虹鎵撳嵃璁剧疆 */
+    html,
+    body {
+      visibility: hidden;
+      height: auto !important;
+      overflow: visible !important;
+      margin: 0 !important;
+      padding: 0 !important;
+      width: 100%;
+    }
+
+    /* 鏄惧紡鏄剧ず鎵撳嵃瀹瑰櫒鍙婂叾鎵�鏈夊瓙鍏冪礌 */
+    .print-requisition-wrapper,
+    .print-requisition-wrapper * {
+      visibility: visible !important;
+    }
+
+    /* 纭繚鎵撳嵃瀹瑰櫒鍗犳嵁鏁翠釜椤甸潰骞剁Щ闄ょ粷瀵瑰畾浣嶅共鎵� */
+    .print-requisition-wrapper {
+      display: block !important;
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: auto;
+      background: white;
+      margin: 0 !important;
+      padding: 0 !important;
+      z-index: 99999;
+    }
+
+    .print-container {
+      width: 100% !important;
+      padding: 0 10mm; /* 浣跨敤瀵圭О鐨勫乏鍙冲唴杈硅窛纭繚灞呬腑 */
+      box-sizing: border-box;
+      height: auto;
+      overflow: visible;
+      color: #000;
+      font-family: "SimSun", "STSong", serif;
+      page-break-inside: avoid;
+      display: block;
+      .print-content {
+        width: 100%;
+        text-align: center;
+      }
+      .bill-title {
+        font-size: 20px;
+        font-weight: bold;
+        text-align: center;
+        margin-bottom: 20px;
+        letter-spacing: 5px;
+        text-decoration: underline;
+      }
+
+      .info-grid {
+        margin-bottom: 10px;
+        font-size: 14px;
+
+        .info-row {
+          display: flex;
+          flex-wrap: wrap;
+          margin-bottom: 8px;
+
+          .info-item {
+            width: 33.33%;
+            display: flex;
+            align-items: flex-end;
+
+            .label {
+              font-weight: bold;
+              white-space: nowrap;
+            }
+            .value {
+              border-bottom: 1px solid #000;
+              padding: 0 5px;
+              flex: 1;
+              min-height: 20px;
+            }
+          }
+        }
+      }
+
+      .material-table {
+        width: 100%;
+        border-collapse: collapse;
+        border: 2px solid #000;
+        font-size: 13px;
+
+        th,
+        td {
+          border: 1px solid #000 !important;
+          padding: 6px 4px;
+          height: 25px;
+          -webkit-print-color-adjust: exact;
+          print-color-adjust: exact;
+        }
+
+        th {
+          background-color: #f2f2f2 !important;
+          font-weight: bold;
+        }
+      }
+
+      .print-footer {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 30px;
+        padding: 0 10px;
+
+        .footer-item {
+          font-size: 14px;
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index f348aae..28356dd 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -175,9 +175,18 @@
     <MaterialDetailDialog v-model="materialDetailDialogVisible"
                           :order-row="currentMaterialDetailOrder"
                           @confirmed="getList" />
+    <MaterialSupplementDialog v-model="materialSupplementDialogVisible"
+                              :order-row="currentMaterialSupplementOrder"
+                              @saved="getList" />
     <new-product-order v-if="isShowNewModal"
                        v-model:visible="isShowNewModal"
                        @completed="handleQuery" />
+    <!-- 鎵撳嵃棰嗘枡鍗曠粍浠� -->
+    <div class="print-requisition-wrapper">
+      <PrintMaterialRequisition ref="printRef"
+                                :order-row="printOrderRow"
+                                :material-list="printMaterialList" />
+    </div>
   </div>
 </template>
 
@@ -205,8 +214,14 @@
   import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
   import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue";
   import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue";
+  import MaterialSupplementDialog from "@/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue";
+  import PrintMaterialRequisition from "@/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue";
   import PIMTable from "@/components/PIMTable/PIMTable.vue";
   import { listPage } from "@/api/productionManagement/processRoute.js";
+  import {
+    listMaterialPickingDetail,
+    listMaterialPickingBom,
+  } from "@/api/productionManagement/productionOrder.js";
   const NewProductOrder = defineAsyncComponent(() =>
     import("@/views/productionManagement/productionOrder/New.vue")
   );
@@ -304,7 +319,7 @@
       label: "鎿嶄綔",
       align: "center",
       fixed: "right",
-      width: 360,
+      width: 260,
       operation: [
         {
           name: "宸ヨ壓璺嚎",
@@ -340,15 +355,33 @@
         {
           name: "棰嗘枡",
           type: "text",
+          color: "#5EC7AB",
           clickFun: row => {
             openMaterialDialog(row);
           },
         },
         {
+          name: "琛ユ枡",
+          type: "text",
+          color: "#5EC7AB",
+          clickFun: row => {
+            openMaterialSupplementDialog(row);
+          },
+        },
+        {
           name: "棰嗘枡璇︽儏",
           type: "text",
+          color: "#5EC7AB",
           clickFun: row => {
             openMaterialDetailDialog(row);
+          },
+        },
+        {
+          name: "鎵撳嵃棰嗘枡鍗�",
+          type: "text",
+          color: "#409eff",
+          clickFun: row => {
+            handlePrint(row);
           },
         },
       ],
@@ -423,6 +456,44 @@
   const currentMaterialOrder = ref(null);
   const materialDetailDialogVisible = ref(false);
   const currentMaterialDetailOrder = ref(null);
+  const materialSupplementDialogVisible = ref(false);
+  const currentMaterialSupplementOrder = ref(null);
+
+  // 鎵撳嵃鐩稿叧
+  const printOrderRow = ref(null);
+  const printMaterialList = ref([]);
+  const handlePrint = async row => {
+    printOrderRow.value = row;
+    proxy.$modal.loading("姝e湪鑾峰彇棰嗘枡鏁版嵁...");
+    try {
+      printMaterialList.value = [];
+      const detailRes = await listMaterialPickingDetail(row.id);
+      const detailList = Array.isArray(detailRes?.data)
+        ? detailRes.data
+        : detailRes?.data?.records || [];
+
+      if (detailList.length > 0) {
+        printMaterialList.value = detailList;
+      }
+
+      if (printMaterialList.value.length === 0) {
+        proxy.$modal.msgWarning("鏆傛棤棰嗘枡鏁版嵁");
+        return;
+      }
+
+      // 绛夊緟 DOM 鏇存柊鍚庢墽琛屾墦鍗�
+      proxy.$nextTick(() => {
+        setTimeout(() => {
+          window.print();
+        }, 800);
+      });
+    } catch (e) {
+      console.error("鑾峰彇棰嗘枡鏁版嵁澶辫触锛�", e);
+      proxy.$modal.msgError("鑾峰彇棰嗘枡鏁版嵁澶辫触");
+    } finally {
+      proxy.$modal.closeLoading();
+    }
+  };
 
   const openBindRouteDialog = async (row, type) => {
     bindForm.orderId = row.id;
@@ -478,6 +549,11 @@
     materialDetailDialogVisible.value = true;
   };
 
+  const openMaterialSupplementDialog = row => {
+    currentMaterialSupplementOrder.value = row;
+    materialSupplementDialogVisible.value = true;
+  };
+
   const handleReset = () => {
     searchForm.value = {
       ...searchForm.value,
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
index e69de29..28077b6 100644
--- a/src/views/productionManagement/productionProcess/Edit.vue
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -0,0 +1,168 @@
+<template>
+  <div>
+    <el-dialog
+        v-model="isShow"
+        title="缂栬緫宸ュ簭"
+        width="400"
+        @close="closeModal"
+    >
+      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+        <el-form-item
+            label="宸ュ簭鍚嶇О锛�"
+            prop="name"
+            :rules="[
+                {
+                required: true,
+                message: '璇疯緭鍏ュ伐搴忓悕绉�',
+              },
+              {
+                max: 100,
+                message: '鏈�澶�100涓瓧绗�',
+              }
+            ]">
+          <el-input v-model="formState.name" />
+        </el-form-item>
+        <el-form-item label="宸ュ簭缂栧彿" prop="no">
+          <el-input v-model="formState.no"  />
+        </el-form-item>
+        <el-form-item
+            label="宸ュ簭绫诲瀷"
+            prop="type"
+            :rules="[
+                {
+                required: true,
+                message: '璇烽�夋嫨宸ュ簭绫诲瀷',
+              }
+            ]"
+        >
+          <el-select v-model="formState.type" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷">
+            <el-option label="璁℃椂" :value="0" />
+            <el-option label="璁′欢" :value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
+          <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
+        </el-form-item>
+        <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
+          <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
+          <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
+          <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="formState.remark" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">纭</el-button>
+          <el-button @click="closeModal">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance, watch } from "vue";
+import {update} from "@/api/productionManagement/productionProcess.js";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+
+  record: {
+    type: Object,
+    required: true,
+  }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+  id: props.record.id,
+  name: props.record.name,
+  type: props.record.type,
+  no: props.record.no,
+  remark: props.record.remark,
+  salaryQuota: props.record.salaryQuota,
+  isQuality: props.record.isQuality,
+  inbound: props.record.inbound,
+  reportWork: props.record.reportWork,
+});
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit('update:visible', val);
+  },
+});
+
+// 鐩戝惉 record 鍙樺寲锛屾洿鏂拌〃鍗曟暟鎹�
+watch(() => props.record, (newRecord) => {
+  if (newRecord && isShow.value) {
+    formState.value = {
+      id: newRecord.id,
+      name: newRecord.name || '',
+      no: newRecord.no || '',
+      type: newRecord.type,
+      remark: newRecord.remark || '',
+      salaryQuota: newRecord.salaryQuota || '',
+      isQuality: props.record.isQuality,
+      inbound: newRecord.inbound,
+      reportWork: newRecord.reportWork,
+    };
+  }
+}, { immediate: true, deep: true });
+
+// 鐩戝惉寮圭獥鎵撳紑锛岄噸鏂板垵濮嬪寲琛ㄥ崟鏁版嵁
+watch(() => props.visible, (visible) => {
+  if (visible && props.record) {
+    formState.value = {
+      id: props.record.id,
+      name: props.record.name || '',
+      no: props.record.no || '',
+      type: props.record.type,
+      remark: props.record.remark || '',
+      salaryQuota: props.record.salaryQuota || '',
+      isQuality: props.record.isQuality,
+      inbound: props.record.inbound,
+      reportWork: props.record.reportWork,
+    };
+  }
+});
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+  isShow.value = false;
+};
+
+const handleSubmit = () => {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      update(formState.value).then(res => {
+        // 鍏抽棴妯℃�佹
+        isShow.value = false;
+        // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+        emit('completed');
+        proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+      })
+    }
+  })
+};
+
+defineExpose({
+  closeModal,
+  handleSubmit,
+  isShow,
+});
+</script>
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
index e69de29..0b3fd47 100644
--- a/src/views/productionManagement/productionProcess/New.vue
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -0,0 +1,129 @@
+<template>
+  <div>
+    <el-dialog
+        v-model="isShow"
+        title="鏂板宸ュ簭"
+        width="400"
+        @close="closeModal"
+    >
+      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+        <el-form-item
+            label="宸ュ簭鍚嶇О锛�"
+            prop="name"
+            :rules="[
+                {
+                required: true,
+                message: '璇疯緭鍏ュ伐搴忓悕绉�',
+              },
+              {
+                max: 100,
+                message: '鏈�澶�100涓瓧绗�',
+              }
+            ]">
+          <el-input v-model="formState.name" />
+        </el-form-item>
+        <el-form-item label="宸ュ簭缂栧彿" prop="no">
+          <el-input v-model="formState.no"  />
+        </el-form-item>
+        <el-form-item
+            label="宸ュ簭绫诲瀷"
+            prop="type"
+            :rules="[
+                {
+                required: true,
+                message: '璇烽�夋嫨宸ュ簭绫诲瀷',
+              }
+            ]"
+        >
+          <el-select v-model="formState.type" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷">
+            <el-option label="璁℃椂" :value="0" />
+            <el-option label="璁′欢" :value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
+          <el-input v-model="formState.salaryQuota" type="number" :step="0.001">
+            <template #append>鍏�</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
+          <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
+          <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
+          <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="formState.remark" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">纭</el-button>
+          <el-button @click="closeModal">鍙栨秷</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance } from "vue";
+import {add} from "@/api/productionManagement/productionProcess.js";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+  name: '',
+  type: undefined,
+  remark: '',
+  salaryQuota:  '',
+  isQuality: false,
+  inbound: false,
+  reportWork: false,
+});
+
+const isShow = computed({
+  get() {
+    return props.visible;
+  },
+  set(val) {
+    emit('update:visible', val);
+  },
+});
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+  isShow.value = false;
+};
+
+const handleSubmit = () => {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      add(formState.value).then(res => {
+        // 鍏抽棴妯℃�佹
+        isShow.value = false;
+        // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+        emit('completed');
+        proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+      })
+    }
+  })
+};
+
+defineExpose({
+  closeModal,
+  handleSubmit,
+  isShow,
+});
+</script>
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index 8490b85..aff050f 100644
--- a/src/views/productionManagement/productionReporting/index.vue
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -99,8 +99,7 @@
                                 style="width: 100%" />
               </template>
             </el-table-column>
-            <el-table-column label="鎿嶄綔"
-                             >
+            <el-table-column label="鎿嶄綔">
               <template #default="scope">
                 <el-button link
                            type="primary"
@@ -124,11 +123,36 @@
     <input-modal v-if="isShowInput"
                  v-model:visible="isShowInput"
                  :production-product-main-id="isShowingId" />
+    <!-- 鍙傛暟璇︽儏寮圭獥 -->
+    <el-dialog v-model="paramDetailVisible"
+               title="鍙傛暟璇︽儏"
+               width="600px">
+      <div v-if="currentParams && currentParams.length > 0"
+           class="param-detail-list">
+        <el-descriptions :column="1"
+                         border>
+          <el-descriptions-item v-for="param in currentParams"
+                                :key="param.id"
+                                :label="param.paramName">
+            {{ param.inputValue }}
+            <span v-if="param.unit && param.unit !== '/'"
+                  class="unit-text">({{ param.unit }})</span>
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+      <el-empty v-else
+                description="鏆傛棤鍙傛暟鏁版嵁" />
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="paramDetailVisible = false">鍏抽棴</el-button>
+        </span>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-  import { onMounted, ref } from "vue";
+  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
   import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
   import { ElMessageBox } from "element-plus";
   import {
@@ -202,7 +226,7 @@
       prop: "unit",
       width: 120,
     },
-    
+
     {
       label: "鍒涘缓鏃堕棿",
       prop: "createTime",
@@ -213,12 +237,20 @@
       label: "鎿嶄綔",
       align: "center",
       fixed: "right",
+      width: 250,
       operation: [
         {
           name: "鏌ョ湅鎶曞叆",
           type: "text",
           clickFun: row => {
             showInput(row);
+          },
+        },
+        {
+          name: "鍙傛暟璇︽儏",
+          type: "text",
+          clickFun: row => {
+            showParamDetail(row);
           },
         },
         {
@@ -232,6 +264,13 @@
     },
   ]);
   const tableData = ref([]);
+  const paramDetailVisible = ref(false);
+  const currentParams = ref([]);
+
+  const showParamDetail = row => {
+    currentParams.value = row.productionOperationParamList || [];
+    paramDetailVisible.value = true;
+  };
   const selectedRows = ref([]);
   const tableLoading = ref(false);
   const childrenLoading = ref(false);
@@ -418,7 +457,15 @@
 </script>
 
 <style scoped>
-.table_list {
-	margin-top: unset;
-}
+  .unit-text {
+    margin-left: 5px;
+    color: #909399;
+    font-size: 12px;
+  }
+  .param-detail-list {
+    padding: 10px;
+  }
+  .table_list {
+    margin-top: unset;
+  }
 </style>
diff --git a/src/views/productionManagement/workOrderEdit/index.vue b/src/views/productionManagement/workOrderEdit/index.vue
index 52a36cd..b919973 100644
--- a/src/views/productionManagement/workOrderEdit/index.vue
+++ b/src/views/productionManagement/workOrderEdit/index.vue
@@ -4,74 +4,66 @@
       <div class="search-row">
         <div class="search-item">
           <span class="search_title">宸ュ崟缂栧彿锛�</span>
-          <el-input
-            v-model="searchForm.workOrderNo"
-            style="width: 240px"
-            placeholder="璇疯緭鍏�"
-            @change="handleQuery"
-            clearable
-            prefix-icon="Search"
-          />
+          <el-input v-model="searchForm.workOrderNo"
+                    style="width: 240px"
+                    placeholder="璇疯緭鍏�"
+                    @change="handleQuery"
+                    clearable
+                    prefix-icon="Search" />
         </div>
         <div class="search-item">
           <span class="search_title">鐢熶骇璁㈠崟鍙凤細</span>
-          <el-input
-            v-model="searchForm.productOrderNpsNo"
-            style="width: 240px"
-            placeholder="璇疯緭鍏�"
-            @change="handleQuery"
-            clearable
-            prefix-icon="Search"
-          />
+          <el-input v-model="searchForm.productOrderNpsNo"
+                    style="width: 240px"
+                    placeholder="璇疯緭鍏�"
+                    @change="handleQuery"
+                    clearable
+                    prefix-icon="Search" />
         </div>
         <div class="search-item">
-          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button type="primary"
+                     @click="handleQuery">鎼滅储</el-button>
         </div>
       </div>
     </div>
     <div class="table_list">
-      <PIMTable
-        rowKey="id"
-        :column="tableColumn"
-        :tableData="tableData"
-        :page="page"
-        :tableLoading="tableLoading"
-        @pagination="pagination"
-      >
+      <PIMTable rowKey="id"
+                :column="tableColumn"
+                :tableData="tableData"
+                :page="page"
+                :tableLoading="tableLoading"
+                @pagination="pagination">
         <template #completionStatus="{ row }">
-          <el-progress
-            :percentage="toProgressPercentage(row?.completionStatus)"
-            :color="progressColor(toProgressPercentage(row?.completionStatus))"
-            :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
-          />
+          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
+                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
+                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
         </template>
       </PIMTable>
     </div>
-
-    <el-dialog v-model="editDialogVisible" title="缂栬緫璁″垝鏃堕棿" width="500px">
-      <el-form :model="editrow" label-width="120px">
+    <el-dialog v-model="editDialogVisible"
+               title="缂栬緫璁″垝鏃堕棿"
+               width="500px">
+      <el-form :model="editrow"
+               label-width="120px">
         <el-form-item label="璁″垝寮�濮嬫椂闂�">
-          <el-date-picker
-            v-model="editrow.planStartTime"
-            type="date"
-            placeholder="璇烽�夋嫨"
-            value-format="YYYY-MM-DD"
-            style="width: 300px"
-          />
+          <el-date-picker v-model="editrow.planStartTime"
+                          type="date"
+                          placeholder="璇烽�夋嫨"
+                          value-format="YYYY-MM-DD"
+                          style="width: 300px" />
         </el-form-item>
         <el-form-item label="璁″垝缁撴潫鏃堕棿">
-          <el-date-picker
-            v-model="editrow.planEndTime"
-            type="date"
-            placeholder="璇烽�夋嫨"
-            value-format="YYYY-MM-DD"
-            style="width: 300px"
-          />
+          <el-date-picker v-model="editrow.planEndTime"
+                          type="date"
+                          placeholder="璇烽�夋嫨"
+                          value-format="YYYY-MM-DD"
+                          style="width: 300px" />
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button type="primary" @click="handleUpdate">纭畾</el-button>
+          <el-button type="primary"
+                     @click="handleUpdate">纭畾</el-button>
           <el-button @click="editDialogVisible = false">鍙栨秷</el-button>
         </span>
       </template>
@@ -80,200 +72,201 @@
 </template>
 
 <script setup>
-import { getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
-import { ElMessageBox } from "element-plus";
-import {
-  productWorkOrderPage,
-  updateProductWorkOrder,
-} from "@/api/productionManagement/workOrder.js";
+  import { getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
+  import { ElMessageBox } from "element-plus";
+  import {
+    productWorkOrderPage,
+    updateProductWorkOrder,
+  } from "@/api/productionManagement/workOrder.js";
 
-const { proxy } = getCurrentInstance();
+  const { proxy } = getCurrentInstance();
 
-const tableColumn = ref([
-  {
-    label: "宸ュ崟绫诲瀷",
-    prop: "workOrderType",
-    width: "80",
-  },
-  {
-    label: "宸ュ崟缂栧彿",
-    prop: "workOrderNo",
-    width: "140",
-  },
-  {
-    label: "鐢熶骇璁㈠崟鍙�",
-    prop: "productOrderNpsNo",
-    width: "140",
-  },
-  {
-    label: "浜у搧鍚嶇О",
-    prop: "productName",
-    width: "140",
-  },
-  {
-    label: "瑙勬牸",
-    prop: "model",
-  },
-  {
-    label: "鍗曚綅",
-    prop: "unit",
-  },
-  {
-    label: "宸ュ簭鍚嶇О",
-    prop: "processName",
-  },
-  {
-    label: "闇�姹傛暟閲�",
-    prop: "planQuantity",
-    width: "140",
-  },
-  {
-    label: "瀹屾垚鏁伴噺",
-    prop: "completeQuantity",
-    width: "140",
-  },
-  {
-    label: "瀹屾垚杩涘害",
-    prop: "completionStatus",
-    dataType: "slot",
-    slot: "completionStatus",
-    width: "140",
-  },
-  {
-    label: "璁″垝寮�濮嬫椂闂�",
-    prop: "planStartTime",
-    width: "140",
-  },
-  {
-    label: "璁″垝缁撴潫鏃堕棿",
-    prop: "planEndTime",
-    width: "140",
-  },
-  {
-    label: "瀹為檯寮�濮嬫椂闂�",
-    prop: "actualStartTime",
-    width: "140",
-  },
-  {
-    label: "瀹為檯缁撴潫鏃堕棿",
-    prop: "actualEndTime",
-    width: "140",
-  },
-  {
-    label: "鎿嶄綔",
-    width: "100",
-    align: "center",
-    dataType: "action",
-    fixed: "right",
-    operation: [
-      {
-        name: "璁″垝鏃堕棿",
-        clickFun: row => {
-          handleEdit(row);
+  const tableColumn = ref([
+    {
+      label: "宸ュ崟绫诲瀷",
+      prop: "workOrderType",
+      width: "80",
+    },
+    {
+      label: "宸ュ崟缂栧彿",
+      prop: "workOrderNo",
+      width: "140",
+    },
+    {
+      label: "鐢熶骇璁㈠崟鍙�",
+      prop: "npsNo",
+      width: "140",
+    },
+    {
+      label: "浜у搧鍚嶇О",
+      prop: "productName",
+      width: "140",
+    },
+    {
+      label: "瑙勬牸",
+      prop: "model",
+    },
+    {
+      label: "鍗曚綅",
+      prop: "unit",
+    },
+    {
+      label: "宸ュ簭鍚嶇О",
+      prop: "operationName",
+      width: "100",
+    },
+    {
+      label: "闇�姹傛暟閲�",
+      prop: "planQuantity",
+      width: "140",
+    },
+    {
+      label: "瀹屾垚鏁伴噺",
+      prop: "completeQuantity",
+      width: "140",
+    },
+    {
+      label: "瀹屾垚杩涘害",
+      prop: "completionStatus",
+      dataType: "slot",
+      slot: "completionStatus",
+      width: "140",
+    },
+    {
+      label: "璁″垝寮�濮嬫椂闂�",
+      prop: "planStartTime",
+      width: "140",
+    },
+    {
+      label: "璁″垝缁撴潫鏃堕棿",
+      prop: "planEndTime",
+      width: "140",
+    },
+    {
+      label: "瀹為檯寮�濮嬫椂闂�",
+      prop: "actualStartTime",
+      width: "140",
+    },
+    {
+      label: "瀹為檯缁撴潫鏃堕棿",
+      prop: "actualEndTime",
+      width: "140",
+    },
+    {
+      label: "鎿嶄綔",
+      width: "100",
+      align: "center",
+      dataType: "action",
+      fixed: "right",
+      operation: [
+        {
+          name: "璁″垝鏃堕棿",
+          clickFun: row => {
+            handleEdit(row);
+          },
         },
-      },
-    ],
-  },
-]);
+      ],
+    },
+  ]);
 
-const tableData = ref([]);
-const tableLoading = ref(false);
-const editDialogVisible = ref(false);
-const editrow = ref(null);
-const page = reactive({
-  current: 1,
-  size: 100,
-  total: 0,
-});
+  const tableData = ref([]);
+  const tableLoading = ref(false);
+  const editDialogVisible = ref(false);
+  const editrow = ref(null);
+  const page = reactive({
+    current: 1,
+    size: 100,
+    total: 0,
+  });
 
-const data = reactive({
-  searchForm: {
-    workOrderNo: "",
-    productOrderNpsNo: "",
-  },
-});
-const { searchForm } = toRefs(data);
+  const data = reactive({
+    searchForm: {
+      workOrderNo: "",
+      productOrderNpsNo: "",
+    },
+  });
+  const { searchForm } = toRefs(data);
 
-const toProgressPercentage = val => {
-  const n = Number(val);
-  if (!Number.isFinite(n)) return 0;
-  if (n <= 0) return 0;
-  if (n >= 100) return 100;
-  return Math.round(n);
-};
+  const toProgressPercentage = val => {
+    const n = Number(val);
+    if (!Number.isFinite(n)) return 0;
+    if (n <= 0) return 0;
+    if (n >= 100) return 100;
+    return Math.round(n);
+  };
 
-const progressColor = percentage => {
-  const p = toProgressPercentage(percentage);
-  if (p < 30) return "#f56c6c";
-  if (p < 50) return "#e6a23c";
-  if (p < 80) return "#409eff";
-  return "#67c23a";
-};
+  const progressColor = percentage => {
+    const p = toProgressPercentage(percentage);
+    if (p < 30) return "#f56c6c";
+    if (p < 50) return "#e6a23c";
+    if (p < 80) return "#409eff";
+    return "#67c23a";
+  };
 
-const handleQuery = () => {
-  page.current = 1;
-  getList();
-};
+  const handleQuery = () => {
+    page.current = 1;
+    getList();
+  };
 
-const pagination = obj => {
-  page.current = obj.page;
-  page.size = obj.limit;
-  getList();
-};
+  const pagination = obj => {
+    page.current = obj.page;
+    page.size = obj.limit;
+    getList();
+  };
 
-const getList = () => {
-  tableLoading.value = true;
-  const params = { ...searchForm.value, ...page };
-  productWorkOrderPage(params)
-    .then(res => {
-      tableLoading.value = false;
-      tableData.value = res.data.records;
-      page.total = res.data.total;
-    })
-    .catch(() => {
-      tableLoading.value = false;
-    });
-};
-
-const handleEdit = row => {
-  editrow.value = JSON.parse(JSON.stringify(row));
-  editDialogVisible.value = true;
-};
-
-const handleUpdate = () => {
-  updateProductWorkOrder(editrow.value)
-    .then(() => {
-      proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
-      editDialogVisible.value = false;
-      getList();
-    })
-    .catch(() => {
-      ElMessageBox.alert("淇敼澶辫触", "鎻愮ず", {
-        confirmButtonText: "纭畾",
+  const getList = () => {
+    tableLoading.value = true;
+    const params = { ...searchForm.value, ...page };
+    productWorkOrderPage(params)
+      .then(res => {
+        tableLoading.value = false;
+        tableData.value = res.data.records;
+        page.total = res.data.total;
+      })
+      .catch(() => {
+        tableLoading.value = false;
       });
-    });
-};
+  };
 
-onMounted(() => {
-  getList();
-});
+  const handleEdit = row => {
+    editrow.value = JSON.parse(JSON.stringify(row));
+    editDialogVisible.value = true;
+  };
+
+  const handleUpdate = () => {
+    updateProductWorkOrder(editrow.value)
+      .then(() => {
+        proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+        editDialogVisible.value = false;
+        getList();
+      })
+      .catch(() => {
+        ElMessageBox.alert("淇敼澶辫触", "鎻愮ず", {
+          confirmButtonText: "纭畾",
+        });
+      });
+  };
+
+  onMounted(() => {
+    getList();
+  });
 </script>
 
 <style scoped lang="scss">
-.search-row {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-}
+  .search-row {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
 
-.search-item {
-  display: flex;
-  align-items: center;
-}
+  .search-item {
+    display: flex;
+    align-items: center;
+  }
 
-.search_title {
-  margin-right: 8px;
-  font-size: 14px;
-  color: #606266;
-}
+  .search_title {
+    margin-right: 8px;
+    font-size: 14px;
+    color: #606266;
+  }
 </style>
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 48f8839..95fb703 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -40,7 +40,6 @@
         </template>
       </PIMTable>
     </div>
-    
     <!-- 娴佽浆鍗″脊绐� -->
     <el-dialog v-model="transferCardVisible"
                title="娴佽浆鍗�"
@@ -116,7 +115,6 @@
                    @click="printTransferCard">鎵撳嵃娴佽浆鍗�</el-button>
       </div>
     </el-dialog>
-    
     <!-- 鎶ュ伐寮圭獥 -->
     <el-dialog v-model="reportDialogVisible"
                title="鎶ュ伐"
@@ -163,6 +161,75 @@
                        :value="user.userId" />
           </el-select>
         </el-form-item>
+        <div v-if="params.length > 0"
+             class="param-grid"
+             v-loading="paramLoading">
+          <el-form-item v-for="param in params"
+                        :key="param.id"
+                        :label="param.paramName"
+                        :label-width="120"
+                        class="param-item">
+            <template v-if="param.paramType == '1'">
+              <div class="param-input-group">
+                <el-input-number v-model="reportForm.paramGroups[param.id]"
+                                 controls-position="right"
+                                 :key="param.id"
+                                 style="width: 250px"
+                                 class="param-input" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else-if="param.paramType == '2'">
+              <div class="param-input-group">
+                <el-input v-model="reportForm.paramGroups[param.id]"
+                          :key="param.id"
+                          style="width: 250px"
+                          class="param-input" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else-if="param.paramType == '3'">
+              <div class="param-input-group">
+                <el-select v-model="reportForm.paramGroups[param.id]"
+                           placeholder="璇烽�夋嫨"
+                           :key="param.id"
+                           class="param-select"
+                           style="width: 250px">
+                  <el-option v-for="option in dictOptions[param.paramFormat] || []"
+                             :key="option.dictLabel"
+                             :label="option.dictLabel"
+                             :value="option.dictLabel" />
+                </el-select>
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else-if="param.paramType == '4'">
+              <div class="param-input-group">
+                <el-date-picker :value-format="param.paramFormat.replace('yyyy', 'YYYY').replace('dd', 'DD')"
+                                :format="param.paramFormat.replace('yyyy', 'YYYY').replace('dd', 'DD')"
+                                :key="param.id"
+                                :type="param.paramFormat=='yyyy-MM-dd'?'date':'datetime'"
+                                v-model="reportForm.paramGroups[param.id]"
+                                class="param-input"
+                                style="width: 250px" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else>
+              <div class="param-input-group">
+                <el-input v-model="reportForm.paramGroups[param.id]"
+                          :key="param.id"
+                          class="param-input" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+          </el-form-item>
+        </div>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
@@ -172,13 +239,9 @@
         </span>
       </template>
     </el-dialog>
-
-    <MaterialDialog
-      v-model="materialDialogVisible"
-      :row-data="currentMaterialOrderRow"
-      @refresh="getList"
-    />
-    
+    <MaterialDialog v-model="materialDialogVisible"
+                    :row-data="currentMaterialOrderRow"
+                    @refresh="getList" />
     <FilesDia ref="workOrderFilesRef" />
   </div>
 </template>
@@ -192,7 +255,9 @@
     addProductMain,
     downProductWorkOrder,
   } from "@/api/productionManagement/workOrder.js";
+  import { findProcessParamListOrder } from "@/api/productionManagement/productProcessRoute.js";
   import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
+  import { getDicts } from "@/api/system/dict/data";
   import QRCode from "qrcode";
   import { getCurrentInstance, reactive, toRefs } from "vue";
   import FilesDia from "./components/filesDia.vue";
@@ -212,7 +277,7 @@
     },
     {
       label: "鐢熶骇璁㈠崟鍙�",
-      prop: "productOrderNpsNo",
+      prop: "npsNo",
       width: "140",
     },
     {
@@ -230,7 +295,8 @@
     },
     {
       label: "宸ュ簭鍚嶇О",
-      prop: "processName",
+      prop: "operationName",
+      width: "100",
     },
     {
       label: "闇�姹傛暟閲�",
@@ -288,12 +354,12 @@
             openWorkOrderFiles(row);
           },
         },
-        {
-          name: "鐗╂枡",
-          clickFun: row => {
-            openMaterialDialog(row);
-          },
-        },
+        // {
+        //   name: "鐗╂枡",
+        //   clickFun: row => {
+        //     openMaterialDialog(row);
+        //   },
+        // },
         {
           name: "鎶ュ伐",
           clickFun: row => {
@@ -304,7 +370,7 @@
       ],
     },
   ]);
-  
+
   const tableData = ref([]);
   const tableLoading = ref(false);
   const transferCardVisible = ref(false);
@@ -325,7 +391,14 @@
     productProcessRouteItemId: "",
     userId: "",
     productMainId: null,
+    productionOrderRoutingOperationId: "",
+    productionOrderId: "",
+    paramGroups: {},
   });
+
+  const params = ref({});
+  const dictOptions = ref({});
+  const paramLoading = ref(false);
 
   // 鏈鐢熶骇鏁伴噺楠岃瘉瑙勫垯
   const validateQuantity = (rule, value, callback) => {
@@ -416,7 +489,7 @@
     // 鏈夋晥鐨勯潪璐熸暣鏁帮紙鍖呮嫭0锛�
     reportForm.scrapQty = num;
   };
-  
+
   const currentReportRowData = ref(null);
   const materialDialogVisible = ref(false);
   const currentMaterialOrderRow = ref(null);
@@ -454,13 +527,13 @@
     page.current = 1;
     getList();
   };
-  
+
   const pagination = obj => {
     page.current = obj.page;
     page.size = obj.limit;
     getList();
   };
-  
+
   const getList = () => {
     tableLoading.value = true;
     const params = { ...searchForm.value, ...page };
@@ -554,10 +627,15 @@
     reportForm.productMainId = row.productMainId;
     reportForm.scrapQty =
       row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
+    reportForm.productionOrderRoutingOperationId =
+      row.productionOrderRoutingOperationId;
+    reportForm.productionOrderId = row.productionOrderId;
     nextTick(() => {
       reportFormRef.value?.clearValidate();
+      if (row.productionOrderRoutingOperationId && row.productionOrderId) {
+        loadParams(row.productionOrderRoutingOperationId, row.productionOrderId);
+      }
     });
-    // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅锛岃缃负榛樿閫変腑
     getUserProfile()
       .then(res => {
         if (res.code === 200) {
@@ -634,18 +712,27 @@
         return;
       }
 
-      const params = {
+      const productionOperationParamList = params.value.map(param => ({
+        ...param,
+        inputValue: reportForm.paramGroups[param.id] ?? "",
+      }));
+
+      const submitParams = {
         quantity: quantity,
         scrapQty: isNaN(scrapQty) ? 0 : scrapQty,
         userId: reportForm.userId,
         userName: reportForm.userName,
-        workOrderId: reportForm.workOrderId,
+        productionOperationTaskId: reportForm.workOrderId,
         productProcessRouteItemId: reportForm.productProcessRouteItemId,
         reportWork: reportForm.reportWork,
         productMainId: reportForm.productMainId,
+        productionOrderRoutingOperationId:
+          reportForm.productionOrderRoutingOperationId,
+        productionOrderId: reportForm.productionOrderId,
+        productionOperationParamList: productionOperationParamList,
       };
 
-      addProductMain(params)
+      addProductMain(submitParams)
         .then(res => {
           proxy.$modal.msgSuccess("鎶ュ伐鎴愬姛");
           reportDialogVisible.value = false;
@@ -662,6 +749,51 @@
   const handleUserChange = val => {
     const user = userOptions.value.find(item => item.userId === val);
     reportForm.userName = user ? user.nickName : "";
+  };
+
+  const getDictOptions = async dictType => {
+    if (!dictType) return [];
+    if (dictOptions.value[dictType]) return dictOptions.value[dictType];
+    try {
+      const res = await getDicts(dictType);
+      if (res.code === 200) {
+        dictOptions.value[dictType] = res.data;
+        return res.data;
+      }
+      return [];
+    } catch (error) {
+      console.error("鑾峰彇瀛楀吀鏁版嵁澶辫触:", error);
+      return [];
+    }
+  };
+
+  const loadParams = (productionOrderRoutingOperationId, productionOrderId) => {
+    paramLoading.value = true;
+    findProcessParamListOrder({
+      productionOrderRoutingOperationId,
+      productionOrderId,
+    })
+      .then(res => {
+        if (res.code === 200) {
+          const paramList = res.data || [];
+          params.value = paramList;
+          reportForm.paramGroups = {};
+          paramList.forEach(param => {
+            if (!reportForm.paramGroups[param.id]) {
+              reportForm.paramGroups[param.id] = "";
+            }
+            if (param.paramType == "3" && param.paramFormat) {
+              getDictOptions(param.paramFormat);
+            }
+          });
+        }
+      })
+      .catch(err => {
+        console.error("鑾峰彇宸ュ簭鍙傛暟澶辫触:", err);
+      })
+      .finally(() => {
+        paramLoading.value = false;
+      });
   };
 
   onMounted(() => {
@@ -734,6 +866,30 @@
     text-align: center;
     margin-top: 20px;
   }
+  .param-grid {
+    margin-top: 10px;
+    border-top: 1px solid #ebe9f3;
+    padding-top: 10px;
+  }
+  .param-item {
+    margin-bottom: 12px;
+  }
+  .param-input-group {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  .param-input {
+    flex: 1;
+  }
+  .param-select {
+    flex: 1;
+  }
+  .param-unit {
+    color: #909399;
+    font-size: 12px;
+    min-width: 30px;
+  }
 </style>
 
 <style  lang="scss">
diff --git a/vite.config.js b/vite.config.js
index dc687a8..ac18ec5 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -8,7 +8,7 @@
   const { VITE_APP_ENV } = env;
   const baseUrl =
       env.VITE_APP_ENV === "development"
-          ? "http://1.15.17.182:9003"
+          ? "http://localhost:7005"
           : env.VITE_BASE_API;
   const javaUrl =
       env.VITE_APP_ENV === "development"

--
Gitblit v1.9.3