From 27af440fbce012aefbd1023f23762cf925e16fab Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期二, 28 四月 2026 14:25:51 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro

---
 src/views/fileManagement/document/index.vue                                            |    8 
 src/layout/index.vue                                                                   |  264 +++---
 src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue |  159 +++
 src/components/AIChatSidebar/index.vue                                                 | 1206 ++++++++++++++++++++++++++++
 src/views/productionManagement/productionProcess/Edit.vue                              |  168 +++
 src/views/productionManagement/workOrderEdit/index.vue                                 |  447 +++++-----
 src/views/productionManagement/workOrderManagement/index.vue                           |   36 
 src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue     |   72 +
 src/views/fileManagement/return/index.vue                                              |   33 
 src/views/fileManagement/borrow/index.vue                                              |   33 
 src/views/productionManagement/productionOrder/index.vue                               |   23 
 vite.config.js                                                                         |    2 
 src/views/productionManagement/productionProcess/New.vue                               |  129 +++
 13 files changed, 2,157 insertions(+), 423 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
new file mode 100644
index 0000000..d14978c
--- /dev/null
+++ b/src/components/AIChatSidebar/index.vue
@@ -0,0 +1,1206 @@
+<template>
+  <div class="ai-chat-sidebar-wrapper">
+    <!-- 鎮诞鍥炬爣 -->
+    <div class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
+      <el-tooltip content="AI 鍔╂墜" placement="left">
+        <div class="trigger-icon">
+          <el-icon :size="30" color="#fff"><Cpu /></el-icon>
+        </div>
+      </el-tooltip>
+    </div>
+
+    <!-- 渚ц竟鏍忓璇濇 -->
+    <el-drawer
+      v-model="visible"
+      :size="drawerSize"
+      direction="rtl"
+      :with-header="true"
+      class="ai-chat-drawer"
+      :modal="false"
+      :show-close="true"
+      :append-to-body="false"
+      @close="handleClose"
+    >
+      <template #header>
+        <div class="drawer-header">
+          <div class="header-left">
+            <el-icon :size="20" class="header-icon"><Cpu /></el-icon>
+            <span class="title">AI 鏅鸿兘鍔╂墜</span>
+          </div>
+          <div class="header-actions">
+            <el-tooltip content="浼氳瘽鍘嗗彶" placement="bottom">
+              <el-button link @click="toggleHistory">
+                <el-icon :size="18"><Timer /></el-icon>
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="寮�鍚柊浼氳瘽" placement="bottom">
+              <el-button link @click="newChat">
+                <el-icon :size="18"><Plus /></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="鏆傛棤鍘嗗彶浼氳瘽" />
+            </div>
+          </el-skeleton>
+        </div>
+
+        <div v-else class="chat-main">
+          <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 type="primary" size="small" @click="newChat">
+              <el-icon><Plus /></el-icon>鏂颁細璇�
+            </el-button>
+            <el-button v-if="isSending" link type="danger" size="small" @click="stopGeneration">
+              <el-icon><VideoPause /></el-icon>鍋滄鐢熸垚
+            </el-button>
+            <el-upload
+              class="file-upload-trigger"
+              action="#"
+              :auto-upload="false"
+              :show-file-list="false"
+              :on-change="handleFileChange"
+              :disabled="isSending"
+            >
+              <el-button link 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="璇疯緭鍏ユ偍鐨勯棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)"
+              resize="none"
+              @keydown.enter.exact.prevent="sendMessage"
+            />
+            <el-button 
+              type="primary" 
+              class="send-btn"
+              :disabled="isSending || (!inputMessage.trim() && !selectedFile)"
+              @click="sendMessage"
+            >
+              鍙戦��
+            </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, Loading, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+
+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 toggleHistory = () => {
+  showHistory.value = !showHistory.value
+  if (showHistory.value) {
+    loadSessions()
+  }
+}
+
+const loadSessions = async () => {
+  loadingSessions.value = true
+  try {
+    const res = await request.get('/xiaozhi/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('ai_chat_uuid', uuid.value)
+  
+  // 鍔犺浇浼氳瘽娑堟伅
+  try {
+    const res = await request.get(`/xiaozhi/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 jsonRegex = /\{"success":\s*true,[\s\S]*\}/
+          const jsonMatch = msg.content.match(jsonRegex)
+          if (jsonMatch) {
+            try {
+              const parsedData = JSON.parse(jsonMatch[0])
+              if (parsedData.success) {
+                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
+                  renderCharts(botMsgIndex, messageObj.chartOptions)
+                }
+              }
+            } catch (err) {}
+          }
+          
+          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(`/xiaozhi/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)
+})
+
+const handleWindowResize = () => {
+  windowWidth.value = window.innerWidth
+}
+
+const toggleSidebar = () => {
+  visible.value = !visible.value
+  if (visible.value) {
+    scrollToBottom()
+  }
+}
+
+const handleClose = () => {
+  visible.value = false
+}
+
+const initUUID = () => {
+  let storedUUID = localStorage.getItem('ai_chat_uuid')
+  if (!storedUUID) {
+    storedUUID = Math.random().toString(36).substring(2, 10) + Date.now().toString(36).substring(4)
+    localStorage.setItem('ai_chat_uuid', storedUUID)
+  }
+  uuid.value = storedUUID
+}
+
+const hello = () => {
+  sendRequest('浣犲ソ')
+}
+
+const newChat = () => {
+  disposeCharts()
+  messages.value = []
+  outputState.value = {}
+  localStorage.removeItem('ai_chat_uuid')
+  initUUID()
+  hello()
+}
+
+const disposeCharts = () => {
+  Object.values(chartInstances.value).forEach(chart => chart.dispose())
+  resizeHandlers.value.forEach(handler => window.removeEventListener('resize', handler))
+  chartInstances.value = {}
+  resizeHandlers.value = []
+}
+
+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('/xiaozhi/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 jsonRegex = /\{"success":\s*true,[\s\S]*\}/
+      const jsonMatch = fullText.match(jsonRegex)
+
+      if (jsonMatch) {
+        try {
+          const parsedData = JSON.parse(jsonMatch[0])
+          if (parsedData.success) {
+            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
+              if (!outputState.value[botMsgIndex].hasRenderedChart) {
+                renderCharts(botMsgIndex, currentMsg.chartOptions)
+                outputState.value[botMsgIndex].hasRenderedChart = true
+              }
+            }
+          }
+        } catch (err) {}
+      }
+
+      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 = () => {
+  if (currentAbortController.value) {
+    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 sendRequest = (message) => {
+  isSending.value = true
+  currentAbortController.value = new AbortController()
+
+  // 鐢ㄦ埛娑堟伅
+  if (messages.value.length > 0) {
+    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('/xiaozhi/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
+
+        // 瑙f瀽 JSON 鏁版嵁锛堥拡瀵瑰祵鍏ュ紡 JSON锛�
+        const jsonRegex = /\{"success":\s*true,[\s\S]*\}/
+        const jsonMatch = fullText.match(jsonRegex)
+
+        if (jsonMatch) {
+          try {
+            const parsedData = JSON.parse(jsonMatch[0])
+            if (parsedData.success) {
+              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
+                if (!outputState.value[botMsgIndex].hasRenderedChart) {
+                  renderCharts(botMsgIndex, currentMsg.chartOptions)
+                  outputState.value[botMsgIndex].hasRenderedChart = true
+                }
+              }
+            }
+          } catch (err) {}
+        }
+
+        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 fullText = currentMsg.content
+    const jsonRegex = /\{"success":\s*true,[\s\S]*\}/
+    const jsonMatch = fullText.match(jsonRegex)
+    if (jsonMatch) {
+      try {
+        const parsedData = JSON.parse(jsonMatch[0])
+        if (parsedData.success) {
+          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
+            if (!outputState.value[botMsgIndex].hasRenderedChart) {
+              renderCharts(botMsgIndex, currentMsg.chartOptions)
+              outputState.value[botMsgIndex].hasRenderedChart = true
+            }
+          }
+          currentMsg.htmlContent = convertStreamOutput(fullText, botMsgIndex)
+        }
+      } catch (err) {}
+    }
+    
+    // 鍏滃簳娓叉煋
+    if (currentMsg.chartOptions && !outputState.value[botMsgIndex].hasRenderedChart) {
+      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
+
+  const jsonRegex = /\{"success":\s*true,[\s\S]*\}/
+  const jsonMatch = output.match(jsonRegex)
+  if (jsonMatch) {
+    try {
+      const parsed = JSON.parse(jsonMatch[0])
+      display = output.replace(jsonMatch[0], '').trim()
+      if (!display && parsed.description) display = parsed.description
+    } catch (e) {
+      const start = output.search(/\{"success":\s*true/)
+      display = output.substring(0, start) + '... (姝e湪鐢熸垚鏁版嵁鍥捐〃)'
+    }
+  }
+
+  if (state.jsonBlockStartPos !== -1 && state.blockEndPos === -1) {
+    display = display.substring(0, state.jsonBlockStartPos)
+  } else if (state.jsBlockStartPos !== -1 && state.blockEndPos === -1) {
+    display = display.substring(0, state.jsBlockStartPos)
+  }
+
+  display = display.replace(/```(javascript|js)([\s\S]*?)```/g, '<pre class="code-block js-code">$2</pre>')
+  display = display.replace(/```([\s\S]*?)```/g, '<pre class="code-block">$1</pre>')
+
+  return convertTextToHtml(display)
+}
+
+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]) chartInstances.value[id].dispose()
+          const chart = echarts.init(dom)
+          chartInstances.value[id] = chart
+          chart.setOption(chartOptions[key])
+          const handler = () => chart.resize()
+          resizeHandlers.value.push(handler)
+          window.addEventListener('resize', handler)
+        } else if (count < 10) {
+          setTimeout(() => tryInit(count + 1), 200)
+        }
+      }
+      tryInit()
+    })
+  })
+}
+
+watch(messages, () => scrollToBottom(), { deep: true })
+</script>
+
+<style scoped lang="scss">
+.ai-chat-sidebar-wrapper {
+  position: fixed;
+  inset: 0;
+  z-index: 2000;
+  pointer-events: none;
+
+  :deep(.el-drawer__container) {
+    pointer-events: none;
+  }
+
+  :deep(.el-drawer) {
+    pointer-events: auto;
+  }
+}
+
+.ai-chat-trigger {
+  pointer-events: auto;
+  position: fixed;
+  right: 20px;
+  bottom: 100px;
+  width: 60px;
+  height: 60px;
+  background: linear-gradient(135deg, #409eff 0%, #007aff 100%);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  box-shadow: 0 4px 12px rgba(0, 122, 255, 0.4);
+  transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+  z-index: 2001;
+
+  &:hover {
+    transform: scale(1.1) translateY(-5px);
+    box-shadow: 0 8px 20px rgba(0, 122, 255, 0.5);
+  }
+
+  .trigger-icon {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+
+.ai-chat-drawer {
+  :deep(.el-drawer__body) {
+    padding: 0;
+    overflow: hidden;
+  }
+  :deep(.el-drawer__header) {
+    margin-bottom: 0;
+    padding: 12px 16px;
+    background: #fff;
+    border-bottom: 1px solid #ebeef5;
+    color: #303133;
+  }
+}
+
+.drawer-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding-right: 32px;
+
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    
+    .header-icon {
+      color: #409eff;
+    }
+    
+    .title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+  }
+
+  .header-actions {
+    display: flex;
+    gap: 8px;
+  }
+}
+
+.chat-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  background-color: #f5f7fa;
+  position: relative;
+}
+
+.history-panel {
+  position: absolute;
+  inset: 0;
+  background: #fff;
+  z-index: 10;
+  display: flex;
+  flex-direction: column;
+  
+  .history-header {
+    padding: 16px;
+    border-bottom: 1px solid #ebeef5;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-weight: 600;
+    font-size: 14px;
+  }
+
+  .session-list {
+    flex: 1;
+    overflow-y: auto;
+    padding: 8px;
+
+    .session-item {
+      display: flex;
+      align-items: center;
+      padding: 12px;
+      margin-bottom: 4px;
+      border-radius: 8px;
+      cursor: pointer;
+      transition: all 0.2s;
+      gap: 10px;
+      position: relative;
+      border: 1px solid transparent;
+
+      &:hover {
+        background-color: #f5f7fa;
+        .delete-btn {
+          opacity: 1;
+        }
+      }
+
+      &.active {
+        background-color: #ecf5ff;
+        border-color: #d9ecff;
+        color: #409eff;
+      }
+
+      .el-icon {
+        font-size: 16px;
+        flex-shrink: 0;
+      }
+
+      .session-name {
+        flex: 1;
+        font-size: 13px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .delete-btn {
+        opacity: 0;
+        transition: opacity 0.2s;
+        padding: 4px;
+        &:hover {
+          color: #f56c6c;
+        }
+      }
+    }
+  }
+}
+
+.chat-main {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  flex: 1;
+  overflow: hidden;
+}
+
+.message-list {
+  flex: 1;
+  overflow-y: auto;
+  padding: 20px;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: #dcdfe6;
+    border-radius: 3px;
+  }
+}
+
+.message-item {
+  display: flex;
+  gap: 12px;
+  width: 100%;
+
+  .avatar {
+    width: 36px;
+    height: 36px;
+    border-radius: 8px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+    font-size: 20px;
+  }
+
+  .message-content {
+    flex: 1;
+    overflow-x: hidden; // 淇敼涓� hidden锛屽唴閮ㄥ鍣ㄥ鐞嗘粴鍔�
+    display: flex;
+    flex-direction: column;
+    max-width: calc(100% - 48px); // 鍑忓幓澶村儚鍜岄棿璺�
+    
+    .text-box {
+      padding: 12px 16px;
+      border-radius: 12px;
+      font-size: 14px;
+      line-height: 1.6;
+      word-break: break-word;
+      max-width: 100%;
+      width: fit-content;
+      overflow-x: auto;
+      &::-webkit-scrollbar {
+        height: 6px;
+      }
+      &::-webkit-scrollbar-thumb {
+        background: #dcdfe6;
+        border-radius: 3px;
+      }
+    }
+  }
+
+  &.bot-message {
+    .message-content {
+      align-items: flex-start;
+    }
+    .avatar {
+      background-color: #409eff;
+      color: #fff;
+    }
+    .text-box {
+      background-color: #fff;
+      color: #303133;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+    }
+  }
+
+  &.user-message {
+    flex-direction: row-reverse;
+    .message-content {
+      align-items: flex-end;
+    }
+    .avatar {
+      background-color: #95d475;
+      color: #fff;
+    }
+    .text-box {
+      background-color: #409eff;
+      color: #fff;
+    }
+  }
+}
+
+.charts-wrapper {
+  margin-top: 10px;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  overflow-x: auto;
+  width: 100%;
+  padding-bottom: 8px;
+  &::-webkit-scrollbar {
+    height: 6px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: #dcdfe6;
+    border-radius: 3px;
+  }
+}
+
+.chart-item {
+  width: 100%;
+  min-width: 300px;
+  height: 300px;
+  background: #fff;
+  border-radius: 8px;
+  padding: 10px;
+}
+
+.table-wrapper {
+  margin-top: 10px;
+  background: #fff;
+  border-radius: 8px;
+  overflow: hidden;
+  overflow-x: auto;
+  width: 100%;
+  &::-webkit-scrollbar {
+    height: 6px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: #dcdfe6;
+    border-radius: 3px;
+  }
+  .el-table {
+    min-width: 300px;
+  }
+}
+
+.input-area {
+  padding: 16px;
+  background-color: #fff;
+  border-top: 1px solid #dcdfe6;
+
+  .input-actions {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 8px;
+  align-items: center;
+
+  .file-upload-trigger {
+    display: inline-flex;
+    align-items: center;
+  }
+}
+
+  .input-box {
+    padding: 12px;
+    position: relative;
+    background: #fff;
+    border: 1px solid #dcdfe6;
+    border-radius: 8px;
+    margin: 0 16px 16px;
+    transition: border-color 0.2s;
+
+    &:focus-within {
+      border-color: #409eff;
+    }
+
+    .selected-file-tag {
+      display: flex;
+      align-items: center;
+      background: #f0f7ff;
+      border: 1px solid #d9ecff;
+      border-radius: 4px;
+      padding: 4px 8px;
+      margin-bottom: 8px;
+      gap: 6px;
+      width: fit-content;
+      max-width: 100%;
+
+      .el-icon {
+        color: #409eff;
+        font-size: 14px;
+      }
+
+      .file-name {
+        font-size: 12px;
+        color: #606266;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .remove-file {
+        cursor: pointer;
+        color: #909399;
+        transition: color 0.2s;
+        &:hover {
+          color: #f56c6c;
+        }
+      }
+    }
+
+    :deep(.el-textarea__inner) {
+      padding: 0;
+      border: none;
+      box-shadow: none;
+      background: transparent;
+      font-family: inherit;
+      font-size: 14px;
+      line-height: 1.5;
+      color: #303133;
+      &::placeholder {
+        color: #c0c4cc;
+      }
+    }
+
+    .send-btn {
+      position: absolute;
+      right: 12px;
+      bottom: 12px;
+      padding: 8px 16px;
+    }
+  }
+}
+
+.typing-indicator {
+  display: flex;
+  gap: 4px;
+  padding: 8px 12px;
+  background: #fff;
+  border-radius: 12px;
+  width: fit-content;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+  margin-top: 4px;
+  .dot {
+    width: 6px;
+    height: 6px;
+    background-color: #909399;
+    border-radius: 50%;
+    animation: typing 1.4s infinite ease-in-out;
+    &:nth-child(2) { animation-delay: 0.2s; }
+    &:nth-child(3) { animation-delay: 0.4s; }
+  }
+}
+
+@keyframes typing {
+  0%, 80%, 100% { transform: scale(0); }
+  40% { transform: scale(1); }
+}
+
+.code-block {
+  background: #2d2d2d;
+  color: #ccc;
+  padding: 12px;
+  border-radius: 6px;
+  font-family: monospace;
+  margin: 8px 0;
+  overflow-x: auto;
+  &.js-code {
+    color: #f08d49;
+  }
+}
+</style>
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/fileManagement/borrow/index.vue b/src/views/fileManagement/borrow/index.vue
index 8983665..c7d8e0d 100644
--- a/src/views/fileManagement/borrow/index.vue
+++ b/src/views/fileManagement/borrow/index.vue
@@ -100,16 +100,14 @@
            </el-col>
            <el-col :span="12">
              <el-form-item label="鍊熼槄涔︾睄锛�" prop="documentationId">
-               <!-- <el-select v-model="borrowForm.documentationId" placeholder="璇烽�夋嫨鍊熼槄涔︾睄" style="width: 100%" @change="handleScanContent">
-                 <el-option 
-                   v-for="item in documentList" 
-                   :key="item.id" 
-                   :label="item.docName || item.name" 
-                   :value="item.id"
-                 />
-               </el-select> -->
                <div style="display: flex; gap: 10px;">
-                <el-select v-model="borrowForm.documentationId" placeholder="璇烽�夋嫨鍊熼槄涔︾睄" style="flex: 1;width: 100px;" @change="handleSelectChange">
+                <el-select
+                  v-if="borrowOperationType !== 'edit'"
+                  v-model="borrowForm.documentationId"
+                  placeholder="璇烽�夋嫨鍊熼槄涔︾睄"
+                  style="flex: 1;width: 100px;"
+                  @change="handleSelectChange"
+                >
                   <el-option 
                     v-for="item in documentList" 
                     :key="item.id" 
@@ -118,6 +116,13 @@
                   />
                 </el-select>
                 <el-input
+                  v-else
+                  v-model="currentEditDocName"
+                  style="flex: 1;width: 100px;"
+                  disabled
+                />
+                <el-input
+                  v-if="borrowOperationType !== 'edit'"
                   v-model="scanContent"
                   placeholder="鎵爜杈撳叆"
                   style="width: 100px;"
@@ -205,6 +210,7 @@
 const selectedRows = ref([]);
 const documentList = ref([]); // 鏂囨。鍒楄〃锛岀敤浜庡�熼槄涔︾睄閫夋嫨
 const scanContent = ref() // 鎵爜鍐呭
+const currentEditDocName = ref(''); // 缂栬緫鏃跺瓨鍌ㄧ殑鏂囨。鍚嶇О
 // 鍒嗛〉鐩稿叧
 const pagination = reactive({
   currentPage: 1,
@@ -282,6 +288,7 @@
       {
         name: "缂栬緫",
         type: "text",
+        disabled: (row) => row.borrowStatus === '褰掕繕',
         clickFun: (row) => {
           openBorrowDia('edit', row)
         },
@@ -428,13 +435,16 @@
   if (type === "edit") {
     // 缂栬緫妯″紡锛屽姞杞界幇鏈夋暟鎹�
     Object.assign(borrowForm, data);
+    // 瀛樺偍鏂囨。鍚嶇О鐢ㄤ簬鏄剧ず
+    currentEditDocName.value = data.docName || '';
   } else {
     // 鏂板妯″紡锛屾竻绌鸿〃鍗�
     Object.keys(borrowForm).forEach(key => {
       borrowForm[key] = "";
     });
-         // 璁剧疆榛樿鐘舵��
-     borrowForm.borrowStatus = "鍊熼槄";
+    currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
+    // 璁剧疆榛樿鐘舵��
+    borrowForm.borrowStatus = "鍊熼槄";
     // 璁剧疆褰撳墠鏃ユ湡涓哄�熼槄鏃ユ湡
     borrowForm.borrowDate = new Date().toISOString().split('T')[0];
   }
@@ -445,6 +455,7 @@
   proxy.$refs.borrowFormRef.resetFields();
   borrowDia.value = false;
   scanContent.value = ''; // 娓呯┖鎵爜鍐呭
+  currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
 };
 
 // 鎻愪氦鍊熼槄琛ㄥ崟
diff --git a/src/views/fileManagement/document/index.vue b/src/views/fileManagement/document/index.vue
index c31b044..f4eac2d 100644
--- a/src/views/fileManagement/document/index.vue
+++ b/src/views/fileManagement/document/index.vue
@@ -862,12 +862,14 @@
       documentForm[key] = "";
     });
     documentForm.attachments = []; // 鏂板妯″紡涓嬩篃娓呯┖闄勪欢
-    // 璁剧疆榛樿鍊� - 浣跨敤瀛楀吀鏁版嵁鐨勭涓�涓�夐」浣滀负榛樿鍊�
+    // 璁剧疆榛樿鍊� - 鏂囨。鐘舵�侀粯璁よ缃负"姝e父"
     if (document_status.value && document_status.value.length > 0) {
-      documentForm.docStatus = document_status.value[0].value;
+      const normalStatus = document_status.value.find(item => item.label === '姝e父');
+      documentForm.docStatus = normalStatus ? normalStatus.value : document_status.value[0].value;
     }
     if (document_urgency.value && document_urgency.value.length > 0) {
-      documentForm.urgencyLevel = document_urgency.value[0].value;
+      const normalUrgency = document_urgency.value.find(item => item.label === '鏅��');
+      documentForm.urgencyLevel = normalUrgency ? normalUrgency.value : document_urgency.value[0].value;
     }
   }
 };
diff --git a/src/views/fileManagement/return/index.vue b/src/views/fileManagement/return/index.vue
index bc86c56..097ab29 100644
--- a/src/views/fileManagement/return/index.vue
+++ b/src/views/fileManagement/return/index.vue
@@ -103,16 +103,14 @@
                  <el-row :gutter="20">
            <el-col :span="12">
              <el-form-item label="鏂囨。锛�" prop="borrowId">
-               <!-- <el-select v-model="returnForm.borrowId" placeholder="璇烽�夋嫨鏂囨。" style="flex: 1;" @change="handleDocumentChange">
-                 <el-option 
-                   v-for="item in documentList" 
-                   :key="item.id" 
-                   :label="item.docName || item.name" 
-                   :value="item.id"
-                 />
-               </el-select> -->
                <div style="display: flex; gap: 10px;">
-                <el-select v-model="returnForm.borrowId" placeholder="璇烽�夋嫨鏂囨。" style="width: 120px;" @change="handleDocumentChange">
+                <el-select 
+                  v-if="returnOperationType !== 'edit'"
+                  v-model="returnForm.borrowId" 
+                  placeholder="璇烽�夋嫨鏂囨。" 
+                  style="width: 120px;" 
+                  @change="handleDocumentChange"
+                >
                   <el-option 
                     v-for="item in documentList" 
                     :key="item.id" 
@@ -121,6 +119,13 @@
                   />
                 </el-select>
                 <el-input
+                  v-else
+                  v-model="currentEditDocName"
+                  style="width: 120px;"
+                  disabled
+                />
+                <el-input
+                  v-if="returnOperationType !== 'edit'"
                   v-model="scanContent"
                   placeholder="鎵爜杈撳叆"
                   style="flex: 1;"
@@ -215,6 +220,7 @@
 const documentList = ref([]); // 鏂囨。鍒楄〃
 const borrowInfoList = ref([]); // 鍊熼槄淇℃伅鍒楄〃
 const scanContent = ref(); // 鎵爜鍐呭
+const currentEditDocName = ref(''); // 缂栬緫鏃跺瓨鍌ㄧ殑鏂囨。鍚嶇О
 
 // 鍒嗛〉鐩稿叧
 const pagination = reactive({
@@ -286,6 +292,7 @@
       {
         name: "缂栬緫",
         type: "text",
+        disabled: (row) => row.borrowStatus === '褰掕繕',
         clickFun: (row) => {
           openReturnDia('edit', row)
         },
@@ -396,15 +403,14 @@
   if (type === "edit") {
     // 缂栬緫妯″紡锛屽姞杞界幇鏈夋暟鎹�
     Object.assign(returnForm, data);
-    // 缂栬緫妯″紡涓嬶紝鏂囨。閫夋嫨鍚庤嚜鍔ㄥ~鍏呭�熼槄浜哄拰搴斿綊杩樻棩鏈�
-    if (returnForm.borrowId) {
-      handleDocumentChange(returnForm.borrowId);
-    }
+    // 瀛樺偍鏂囨。鍚嶇О鐢ㄤ簬鏄剧ず
+    currentEditDocName.value = data.docName || '';
   } else {
     // 鏂板妯″紡锛屾竻绌鸿〃鍗�
     Object.keys(returnForm).forEach(key => {
       returnForm[key] = "";
     });
+    currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
     // 璁剧疆榛樿鐘舵��
     returnForm.borrowStatus = "褰掕繕";
     // 璁剧疆褰撳墠鏃ユ湡涓哄綊杩樻棩鏈�
@@ -418,6 +424,7 @@
   returnDia.value = false;
   scanContent.value = ''; // 娓呯┖鎵爜鍐呭
   borrowInfoList.value = []; // 娓呯┖鍊熼槄淇℃伅鍒楄〃
+  currentEditDocName.value = ''; // 娓呯┖缂栬緫鏃剁殑鏂囨。鍚嶇О
 };
 
 // 鎻愪氦褰掕繕琛ㄥ崟
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/index.vue b/src/views/productionManagement/productionOrder/index.vue
index f348aae..5f4fa61 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -175,6 +175,9 @@
     <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" />
@@ -205,6 +208,7 @@
   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 PIMTable from "@/components/PIMTable/PIMTable.vue";
   import { listPage } from "@/api/productionManagement/processRoute.js";
   const NewProductOrder = defineAsyncComponent(() =>
@@ -304,7 +308,7 @@
       label: "鎿嶄綔",
       align: "center",
       fixed: "right",
-      width: 360,
+      width: 260,
       operation: [
         {
           name: "宸ヨ壓璺嚎",
@@ -340,13 +344,23 @@
         {
           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);
           },
@@ -423,6 +437,8 @@
   const currentMaterialOrder = ref(null);
   const materialDetailDialogVisible = ref(false);
   const currentMaterialDetailOrder = ref(null);
+  const materialSupplementDialogVisible = ref(false);
+  const currentMaterialSupplementOrder = ref(null);
 
   const openBindRouteDialog = async (row, type) => {
     bindForm.orderId = row.id;
@@ -478,6 +494,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/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..819e3fd 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="鎶ュ伐"
@@ -172,13 +170,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>
@@ -212,7 +206,7 @@
     },
     {
       label: "鐢熶骇璁㈠崟鍙�",
-      prop: "productOrderNpsNo",
+      prop: "npsNo",
       width: "140",
     },
     {
@@ -230,7 +224,7 @@
     },
     {
       label: "宸ュ簭鍚嶇О",
-      prop: "processName",
+      prop: "operationName",
     },
     {
       label: "闇�姹傛暟閲�",
@@ -288,12 +282,12 @@
             openWorkOrderFiles(row);
           },
         },
-        {
-          name: "鐗╂枡",
-          clickFun: row => {
-            openMaterialDialog(row);
-          },
-        },
+        // {
+        //   name: "鐗╂枡",
+        //   clickFun: row => {
+        //     openMaterialDialog(row);
+        //   },
+        // },
         {
           name: "鎶ュ伐",
           clickFun: row => {
@@ -304,7 +298,7 @@
       ],
     },
   ]);
-  
+
   const tableData = ref([]);
   const tableLoading = ref(false);
   const transferCardVisible = ref(false);
@@ -416,7 +410,7 @@
     // 鏈夋晥鐨勯潪璐熸暣鏁帮紙鍖呮嫭0锛�
     reportForm.scrapQty = num;
   };
-  
+
   const currentReportRowData = ref(null);
   const materialDialogVisible = ref(false);
   const currentMaterialOrderRow = ref(null);
@@ -454,13 +448,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 };
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