zlwu 5 kuukautta sitten
vanhempi
commit
2eb1807fe6

+ 1 - 1
web-admin/graphify-out/GRAPH_REPORT.md

@@ -1,7 +1,7 @@
 # Graph Report - src  (2026-05-24)
 
 ## Corpus Check
-- Large corpus: 595 files · ~325,287 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 595 files · ~325,295 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
 - 1515 nodes · 1895 edges · 34 communities detected

+ 3 - 3
web-admin/graphify-out/manifest.json

@@ -17,8 +17,8 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/common/index.ts": 1778582068.0254738,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/directive/directive.d.ts": 1778582068.026978,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/api/api.d.ts": 1778582068.0246184,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779586894.4437778,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779586887.6455336,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779587829.0283062,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779587821.900029,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1778582068.0293276,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1778775532.895603,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1778582068.004279,
@@ -282,7 +282,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1779586846.3205786,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1779543540.724915,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1779586813.5815248,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1779506613.3205173,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1779587781.963615,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1779525781.678735,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue": 1779525781.679505,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1779525781.680895,

+ 2 - 1
web-admin/src/App.vue

@@ -85,7 +85,8 @@
     [/(工单)/, 'auto-title-receipt'],
     [/(流程|待办|已办|申请|请假|执行)/, 'auto-title-operation'],
     [/(耗材|试剂)/, 'auto-title-box'],
-    [/(工艺|路线)/, 'auto-title-settings'],
+    [/(路线)/, 'auto-title-route'],
+    [/(工艺)/, 'auto-title-settings'],
     [/(规格|价格|体系|渠道|项目)/, 'auto-title-category'],
     [/(类型)/, 'auto-title-category'],
     [/(任务|调度)/, 'auto-title-operation'],

+ 4 - 0
web-admin/src/assets/styles/core/app.scss

@@ -499,6 +499,10 @@ body {
 .console-container .panel-top h2.auto-title-user {
   --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E");
 }
+.page-container :is(.panel-top h2, .panel-top h3, .storage-content-header h3, .storage-aside__head h2, .user-dept-aside__head h2).auto-title-route,
+.console-container .panel-top h2.auto-title-route {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='6' cy='19' r='3'/%3E%3Cpath d='M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15'/%3E%3Ccircle cx='18' cy='5' r='3'/%3E%3C/svg%3E");
+}
 .page-container :is(.panel-top h2, .panel-top h3, .storage-content-header h3, .storage-aside__head h2, .user-dept-aside__head h2).auto-title-settings,
 .console-container .panel-top h2.auto-title-settings {
   --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z'/%3E%3Cpath d='M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-1.5 2.6-0.2-.1a2 2 0 0 0-2.1.4 1.8 1.8 0 0 0-.6 2v.2H9.6v-.2a1.8 1.8 0 0 0-.6-2 2 2 0 0 0-2.1-.4l-.2.1-1.5-2.6.1-.1A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.6-1.1H3v-3h.0a1.7 1.7 0 0 0 1.6-1.1 1.7 1.7 0 0 0-.3-1.9l-.1-.1 1.5-2.6.2.1a2 2 0 0 0 2.1-.4 1.8 1.8 0 0 0 .6-2V2h4.8v.2a1.8 1.8 0 0 0 .6 2 2 2 0 0 0 2.1.4l.2-.1 1.5 2.6-.1.1a1.7 1.7 0 0 0-.3 1.9A1.7 1.7 0 0 0 21 10.9h.0v3H21a1.7 1.7 0 0 0-1.6 1.1z'/%3E%3C/svg%3E");

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
web-admin/src/graphify-out/cache/ast/1560ac6ae09ceffb84f948613069ffaeb24017fa58cbd1763b67e353ad7879b6.json


+ 140 - 20
web-admin/src/views/production/consumable/index.vue

@@ -146,6 +146,7 @@
                   <el-select v-model="logSearchForm.changeType" placeholder="全部" clearable>
                     <el-option label="入库" value="in" />
                     <el-option label="出库" value="out" />
+                    <el-option label="生产扣减" value="use" />
                   </el-select>
                 </el-form-item>
                 <el-form-item label="时间范围">
@@ -162,7 +163,7 @@
                 </el-form-item>
               </div>
               <div class="filter-actions-row">
-                <span class="filter-hint">按耗材、变动类型和时间范围查询变动记录。</span>
+                <span class="filter-hint">按耗材编码/名称、变动类型和时间范围查询变动记录。</span>
                 <div class="filter-actions-end">
                   <el-button class="btn-ghost" @click="handleLogReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
                   <el-button class="btn-main" type="primary" @click="handleLogSearch"><el-icon><Search /></el-icon>搜索</el-button>
@@ -178,6 +179,7 @@
               v-loading="logLoading"
               stripe
               row-key="id"
+              @row-dblclick="handleLogView"
             >
               <el-table-column
                 type="index"
@@ -191,8 +193,8 @@
               <el-table-column prop="materialName" label="耗材名称" min-width="140" align="center" header-align="center" />
               <el-table-column label="变动类型" min-width="100" align="center" header-align="center">
                 <template #default="{ row }">
-                  <el-tag :type="getChangeTypeTagType(row.changeType)" size="small">
-                    {{ getChangeTypeLabel(row.changeType) }}
+                  <el-tag :type="getChangeTypeTagType(row)" size="small">
+                    {{ getChangeTypeLabel(row) }}
                   </el-tag>
                 </template>
               </el-table-column>
@@ -205,6 +207,12 @@
               </el-table-column>
               <el-table-column prop="stockBefore" label="变动前库存" min-width="120" align="center" header-align="center" />
               <el-table-column prop="stockAfter" label="变动后库存" min-width="120" align="center" header-align="center" />
+              <el-table-column label="工单号" min-width="140" align="center" header-align="center">
+                <template #default="{ row }">{{ row.taskNo || '-' }}</template>
+              </el-table-column>
+              <el-table-column label="步骤名称" min-width="140" align="center" header-align="center">
+                <template #default="{ row }">{{ row.stepName || '-' }}</template>
+              </el-table-column>
               <el-table-column label="来源类型" min-width="120" align="center" header-align="center">
                 <template #default="{ row }">
                   <el-tag :type="getSourceTagType(row.sourceType)" size="small">
@@ -212,23 +220,27 @@
                   </el-tag>
                 </template>
               </el-table-column>
-              <el-table-column label="工单号" min-width="140" align="center" header-align="center">
-                <template #default="{ row }">{{ row.taskNo || '-' }}</template>
-              </el-table-column>
-              <el-table-column label="步骤名称" min-width="140" align="center" header-align="center">
-                <template #default="{ row }">{{ row.stepName || '-' }}</template>
-              </el-table-column>
               <el-table-column label="领用人" min-width="100" align="center" header-align="center">
-                <template #default="{ row }">{{ row.receiver || '-' }}</template>
+                <template #default="{ row }">{{ displayLogReceiver(row) }}</template>
               </el-table-column>
               <el-table-column label="用途" min-width="120" align="center" header-align="center" show-overflow-tooltip>
-                <template #default="{ row }">{{ row.purpose || '-' }}</template>
+                <template #default="{ row }">{{ displayLogPurpose(row) }}</template>
+              </el-table-column>
+              <el-table-column label="领用时间" min-width="170" align="center" header-align="center">
+                <template #default="{ row }">{{ displayLogUseTime(row) }}</template>
               </el-table-column>
               <el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip />
               <el-table-column label="操作人" min-width="100" align="center" header-align="center">
                 <template #default="{ row }">{{ row.createByName || row.createBy || '-' }}</template>
               </el-table-column>
               <el-table-column prop="createTime" label="变动时间" min-width="170" align="center" header-align="center" />
+              <el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
+                <template #default="{ row }">
+                  <el-button class="btn-op-item" @click="handleLogView(row)">
+                    <el-icon><View /></el-icon>查看
+                  </el-button>
+                </template>
+              </el-table-column>
             </el-table>
             <div class="pagination list-pager-bar">
               <el-pagination
@@ -770,8 +782,8 @@
             />
             <el-table-column label="变动类型" min-width="100" align="center" header-align="center">
               <template #default="{ row }">
-                <el-tag :type="getChangeTypeTagType(row.changeType)" size="small">
-                  {{ getChangeTypeLabel(row.changeType) }}
+                <el-tag :type="getChangeTypeTagType(row)" size="small">
+                  {{ getChangeTypeLabel(row) }}
                 </el-tag>
               </template>
             </el-table-column>
@@ -832,6 +844,15 @@
         code-label="耗材编码"
         :sections="viewSections"
       />
+
+      <!-- 变动记录查看弹窗 -->
+      <SystemViewDialog
+        v-model:visible="logViewVisible"
+        :title="logViewRow?.materialName || '变动记录详情'"
+        :icon="Document"
+        :meta="logViewRow?.createTime || ''"
+        :sections="logViewSections"
+      />
     </section>
   </div>
 </template>
@@ -1015,20 +1036,101 @@
   const logTableData = ref<any[]>([])
   const logLoading = ref(false)
   const logPagination = reactive({ page: 1, pageSize: 10, total: 0 })
+  const logViewVisible = ref(false)
+  const logViewRow = ref<any>(null)
 
   // 变动类型/来源映射
-  function getChangeTypeLabel(changeType: string) {
-    if (changeType === 'in') return '入库'
-    if (changeType === 'use') return '出库'
-    return changeType || '-'
+  function isManualOutLogRow(row: { changeType?: string; sourceType?: string }) {
+    return row.sourceType === 'manual_out'
+  }
+
+  function isStepUseLogRow(row: { changeType?: string; sourceType?: string }) {
+    return row.changeType === 'use' && row.sourceType === 'step_save'
+  }
+
+  function getChangeTypeLabel(row: { changeType?: string; sourceType?: string }) {
+    if (row.changeType === 'in') return '入库'
+    if (isManualOutLogRow(row)) return '出库'
+    if (isStepUseLogRow(row)) return '生产扣减'
+    if (row.changeType === 'use') return '出库'
+    return row.changeType || '-'
   }
 
-  function getChangeTypeTagType(changeType: string) {
-    if (changeType === 'in') return 'success'
-    if (changeType === 'use') return 'warning'
+  function getChangeTypeTagType(row: { changeType?: string; sourceType?: string }) {
+    if (row.changeType === 'in') return 'success'
+    if (isManualOutLogRow(row)) return 'warning'
+    if (isStepUseLogRow(row)) return 'primary'
+    if (row.changeType === 'use') return 'warning'
     return 'info'
   }
 
+  function displayLogReceiver(row: any) {
+    if (!isManualOutLogRow(row)) return '-'
+    return (row.receiver || '').trim() || '-'
+  }
+
+  function displayLogPurpose(row: any) {
+    if (!isManualOutLogRow(row)) return '-'
+    return (row.purpose || '').trim() || '-'
+  }
+
+  function displayLogUseTime(row: any) {
+    if (!isManualOutLogRow(row)) return '-'
+    return (row.useTime || row.createTime || '').trim() || '-'
+  }
+
+  const logViewSections = computed(() => {
+    if (!logViewRow.value) return []
+    const row = logViewRow.value
+    return [
+      {
+        title: '变动信息',
+        fields: [
+          { label: '耗材编码', value: row.materialCode },
+          { label: '耗材名称', value: row.materialName },
+          {
+            label: '变动类型',
+            value: getChangeTypeLabel(row),
+            tagType: getChangeTypeTagType(row) as 'success' | 'warning' | 'primary' | 'info'
+          },
+          {
+            label: '变动数量',
+            value: row.changeQty < 0 ? String(row.changeQty) : `+${row.changeQty}`
+          },
+          { label: '变动前库存', value: String(row.stockBefore ?? 0) },
+          { label: '变动后库存', value: String(row.stockAfter ?? 0) },
+          { label: '工单号', value: row.taskNo || '-' },
+          { label: '步骤名称', value: row.stepName || '-' },
+          {
+            label: '来源类型',
+            value: getSourceLabel(row.sourceType),
+            tagType: getSourceTagType(row.sourceType) as 'success' | 'warning' | 'primary' | 'info'
+          },
+          {
+            label: '领用人',
+            value: displayLogReceiver(row) === '-' ? '—' : displayLogReceiver(row)
+          },
+          {
+            label: '用途',
+            value: displayLogPurpose(row) === '-' ? '—' : displayLogPurpose(row)
+          },
+          {
+            label: '领用时间',
+            value: displayLogUseTime(row) === '-' ? '—' : displayLogUseTime(row)
+          },
+          { label: '备注', value: row.remark || '—', span: true },
+          { label: '操作人', value: row.createByName || row.createBy || '-' },
+          { label: '变动时间', value: row.createTime || '—' }
+        ]
+      }
+    ]
+  })
+
+  function handleLogView(row: any) {
+    logViewRow.value = row
+    logViewVisible.value = true
+  }
+
   function getSourceLabel(sourceType: string) {
     if (sourceType === 'manual_in') return '手动入库'
     if (sourceType === 'manual_out') return '手动出库'
@@ -1540,9 +1642,27 @@
 
   watch(activeTab, (val) => {
     if (val === 'logs') {
+      logPagination.page = 1
       loadLogData()
     }
   })
+
+  watchDebounced(
+    logSearchForm,
+    () => {
+      if (activeTab.value !== 'logs') return
+      logPagination.page = 1
+      loadLogData()
+    },
+    { debounce: 300, deep: true }
+  )
+
+  watch(logDateRange, () => {
+    if (activeTab.value !== 'logs') return
+    logPagination.page = 1
+    loadLogData()
+  })
+
   // 输入检索条件时自动搜索(防抖 300ms)
   watchDebounced(
     searchForm,

+ 174 - 50
web-admin/src/views/production/reagent/index.vue

@@ -219,8 +219,9 @@
                 </el-form-item>
                 <el-form-item label="变动类型">
                   <el-select v-model="allLogFilter.changeType" placeholder="全部" clearable>
-<el-option label="入库" value="in" />
-                  <el-option label="出库" value="out" />
+                    <el-option label="入库" value="in" />
+                    <el-option label="出库" value="out" />
+                    <el-option label="生产扣减" value="use" />
                   </el-select>
                 </el-form-item>
                 <el-form-item label="时间范围">
@@ -231,12 +232,13 @@
                     start-placeholder="开始日期"
                     end-placeholder="结束日期"
                     value-format="YYYY-MM-DD"
+                    clearable
                     style="width: 100%"
                   />
                 </el-form-item>
               </div>
               <div class="filter-actions-row">
-                <span class="filter-hint">按试剂编码/名称、变动类型、时间范围筛选。</span>
+                <span class="filter-hint">按试剂编码/名称、变动类型和时间范围查询变动记录。</span>
                 <div class="filter-actions-end">
                   <el-button class="btn-ghost" @click="handleAllLogReset"
                     ><el-icon><RefreshLeft /></el-icon>重置</el-button
@@ -255,6 +257,8 @@
               :data="allLogData"
               v-loading="allLogLoading"
               stripe
+              row-key="id"
+              @row-dblclick="handleAllLogView"
             >
               <el-table-column
                 type="index"
@@ -280,95 +284,86 @@
               />
               <el-table-column label="变动类型" min-width="100" align="center" header-align="center">
                 <template #default="{ row }">
-                  <el-tag
-                    :type="row.changeType === 'in' ? 'success' : 'warning'"
-                    size="small"
-                  >
-                    {{ row.changeType === 'in' ? '入库' : '出库' }}
+                  <el-tag :type="getChangeTypeTagType(row)" size="small">
+                    {{ getChangeTypeLabel(row) }}
                   </el-tag>
                 </template>
               </el-table-column>
-              <el-table-column label="变动数量" min-width="100" align="center" header-align="center">
+              <el-table-column label="变动数量" min-width="120" align="center" header-align="center">
                 <template #default="{ row }">
-                  <span :style="{ color: row.changeQty >= 0 ? '#10b981' : '#ef4444' }">
-                    {{ row.changeQty >= 0 ? '+' : '' }}{{ row.changeQty }}
+                  <span :class="row.changeQty < 0 ? 'qty-negative' : 'qty-positive'">
+                    {{ row.changeQty < 0 ? row.changeQty : `+${row.changeQty}` }}
                   </span>
                 </template>
               </el-table-column>
               <el-table-column
                 prop="stockBefore"
                 label="变动前库存"
-                min-width="110"
+                min-width="120"
                 align="center"
                 header-align="center"
               />
               <el-table-column
                 prop="stockAfter"
                 label="变动后库存"
-                min-width="110"
+                min-width="120"
                 align="center"
                 header-align="center"
               />
-              <el-table-column label="来源" min-width="100" align="center" header-align="center">
+              <el-table-column label="工单号" min-width="140" align="center" header-align="center">
+                <template #default="{ row }">{{ row.taskNo || '-' }}</template>
+              </el-table-column>
+              <el-table-column label="步骤名称" min-width="140" align="center" header-align="center">
+                <template #default="{ row }">{{ row.stepName || '-' }}</template>
+              </el-table-column>
+              <el-table-column label="来源类型" min-width="120" align="center" header-align="center">
                 <template #default="{ row }">
-                  <el-tag
-                    :type="sourceTypeTagType(row.sourceType)"
-                    size="small"
-                  >
+                  <el-tag :type="sourceTypeTagType(row.sourceType)" size="small">
                     {{ sourceTypeLabel(row.sourceType) }}
                   </el-tag>
                 </template>
               </el-table-column>
-              <el-table-column label="工单号" min-width="140" align="center" header-align="center">
-                <template #default="{ row }">{{ row.taskNo || '-' }}</template>
+              <el-table-column label="领用人" min-width="100" align="center" header-align="center">
+                <template #default="{ row }">{{ displayLogReceiver(row) }}</template>
               </el-table-column>
-              <el-table-column label="步骤名称" min-width="140" align="center" header-align="center">
-                <template #default="{ row }">{{ row.stepName || '-' }}</template>
+              <el-table-column label="用途" min-width="120" align="center" header-align="center" show-overflow-tooltip>
+                <template #default="{ row }">{{ displayLogPurpose(row) }}</template>
+              </el-table-column>
+              <el-table-column label="领用时间" min-width="170" align="center" header-align="center">
+                <template #default="{ row }">{{ displayLogUseTime(row) }}</template>
               </el-table-column>
-              <el-table-column
-                prop="receiver"
-                label="领用人"
-                min-width="100"
-                align="center"
-                header-align="center"
-              />
-              <el-table-column
-                prop="purpose"
-                label="用途"
-                min-width="120"
-                align="center"
-                header-align="center"
-                show-overflow-tooltip
-              />
               <el-table-column
                 prop="remark"
                 label="备注"
-                min-width="120"
+                min-width="140"
                 align="center"
                 header-align="center"
                 show-overflow-tooltip
               />
-              <el-table-column
-                prop="createByName"
-                label="操作人"
-                min-width="100"
-                align="center"
-                header-align="center"
-              />
+              <el-table-column label="操作人" min-width="100" align="center" header-align="center">
+                <template #default="{ row }">{{ row.createByName || row.createBy || '-' }}</template>
+              </el-table-column>
               <el-table-column
                 prop="createTime"
-                label="操作时间"
-                min-width="180"
+                label="变动时间"
+                min-width="170"
                 align="center"
                 header-align="center"
               />
+              <el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
+                <template #default="{ row }">
+                  <el-button class="btn-op-item" @click="handleAllLogView(row)">
+                    <el-icon><View /></el-icon>查看
+                  </el-button>
+                </template>
+              </el-table-column>
             </el-table>
             <div class="pagination list-pager-bar">
               <el-pagination
                 :current-page="allLogPagination.page"
                 :page-size="allLogPagination.pageSize"
                 :total="allLogPagination.total"
-                :page-sizes="[10, 20, 50]"
+                :page-sizes="[10, 20, 50, 100]"
                 layout="total, sizes, prev, pager, next"
                 @size-change="
                   (val) => ((allLogPagination.page = 1), (allLogPagination.pageSize = val), loadAllLogs())
@@ -1059,6 +1054,15 @@
         code-label="试剂编码"
         :sections="viewSections"
       />
+
+      <!-- 变动记录查看弹窗 -->
+      <SystemViewDialog
+        v-model:visible="allLogViewVisible"
+        :title="allLogViewRow?.materialName || '变动记录详情'"
+        :icon="Document"
+        :meta="allLogViewRow?.createTime || ''"
+        :sections="allLogViewSections"
+      />
     </section>
   </div>
 </template>
@@ -1120,9 +1124,48 @@
     const map: Record<string, string> = {
       manual_in: 'success',
       manual_out: 'warning',
-      step_save: 'info'
+      step_save: 'primary'
     }
-    return (map[sourceType] || 'info') as 'success' | 'warning' | 'info'
+    return (map[sourceType] || 'info') as 'success' | 'warning' | 'primary' | 'info'
+  }
+
+  function isManualOutLogRow(row: { changeType?: string; sourceType?: string }) {
+    return row.sourceType === 'manual_out'
+  }
+
+  function isStepUseLogRow(row: { changeType?: string; sourceType?: string }) {
+    return row.changeType === 'use' && row.sourceType === 'step_save'
+  }
+
+  function getChangeTypeLabel(row: { changeType?: string; sourceType?: string }) {
+    if (row.changeType === 'in') return '入库'
+    if (isManualOutLogRow(row)) return '出库'
+    if (isStepUseLogRow(row)) return '生产扣减'
+    if (row.changeType === 'use') return '出库'
+    return row.changeType || '-'
+  }
+
+  function getChangeTypeTagType(row: { changeType?: string; sourceType?: string }) {
+    if (row.changeType === 'in') return 'success'
+    if (isManualOutLogRow(row)) return 'warning'
+    if (isStepUseLogRow(row)) return 'primary'
+    if (row.changeType === 'use') return 'warning'
+    return 'info'
+  }
+
+  function displayLogReceiver(row: any) {
+    if (!isManualOutLogRow(row)) return '-'
+    return (row.receiver || '').trim() || '-'
+  }
+
+  function displayLogPurpose(row: any) {
+    if (!isManualOutLogRow(row)) return '-'
+    return (row.purpose || '').trim() || '-'
+  }
+
+  function displayLogUseTime(row: any) {
+    if (!isManualOutLogRow(row)) return '-'
+    return (row.useTime || row.createTime || '').trim() || '-'
   }
 
   const activeTab = ref('list')
@@ -1278,6 +1321,60 @@
   const allLogFilter = reactive({ keyword: '', changeType: '' })
   const allLogDateRange = ref<string[] | null>(null)
   const allLogPagination = reactive({ page: 1, pageSize: 10, total: 0 })
+  const allLogViewVisible = ref(false)
+  const allLogViewRow = ref<any>(null)
+
+  const allLogViewSections = computed(() => {
+    if (!allLogViewRow.value) return []
+    const row = allLogViewRow.value
+    return [
+      {
+        title: '变动信息',
+        fields: [
+          { label: '试剂编码', value: row.materialCode },
+          { label: '试剂名称', value: row.materialName },
+          {
+            label: '变动类型',
+            value: getChangeTypeLabel(row),
+            tagType: getChangeTypeTagType(row) as 'success' | 'warning' | 'primary' | 'info'
+          },
+          {
+            label: '变动数量',
+            value: row.changeQty < 0 ? String(row.changeQty) : `+${row.changeQty}`
+          },
+          { label: '变动前库存', value: String(row.stockBefore ?? 0) },
+          { label: '变动后库存', value: String(row.stockAfter ?? 0) },
+          { label: '工单号', value: row.taskNo || '-' },
+          { label: '步骤名称', value: row.stepName || '-' },
+          {
+            label: '来源类型',
+            value: sourceTypeLabel(row.sourceType),
+            tagType: sourceTypeTagType(row.sourceType)
+          },
+          {
+            label: '领用人',
+            value: displayLogReceiver(row) === '-' ? '—' : displayLogReceiver(row)
+          },
+          {
+            label: '用途',
+            value: displayLogPurpose(row) === '-' ? '—' : displayLogPurpose(row)
+          },
+          {
+            label: '领用时间',
+            value: displayLogUseTime(row) === '-' ? '—' : displayLogUseTime(row)
+          },
+          { label: '备注', value: row.remark || '—', span: true },
+          { label: '操作人', value: row.createByName || row.createBy || '-' },
+          { label: '变动时间', value: row.createTime || '—' }
+        ]
+      }
+    ]
+  })
+
+  function handleAllLogView(row: any) {
+    allLogViewRow.value = row
+    allLogViewVisible.value = true
+  }
 
   const handleSelectionChange = (rows: any[]) => {
     selectedRows.value = rows
@@ -1805,6 +1902,23 @@
       loadAllLogs()
     }
   })
+
+  watchDebounced(
+    allLogFilter,
+    () => {
+      if (activeTab.value !== 'stockLogs') return
+      allLogPagination.page = 1
+      loadAllLogs()
+    },
+    { debounce: 300, deep: true }
+  )
+
+  watch(allLogDateRange, () => {
+    if (activeTab.value !== 'stockLogs') return
+    allLogPagination.page = 1
+    loadAllLogs()
+  })
+
   // 输入检索条件时自动搜索(防抖 300ms)
   watchDebounced(
     searchForm,
@@ -1829,6 +1943,16 @@
     }
   }
 
+  .qty-positive {
+    color: var(--el-color-success);
+    font-weight: 500;
+  }
+
+  .qty-negative {
+    color: var(--el-color-danger);
+    font-weight: 500;
+  }
+
   .reagent-batch-stock-dialog {
     .batch-stock-move-head {
       margin-bottom: 16px;

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä