From 333a93571452073a9e628c6256044d345099aa50 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 02 四月 2026 08:19:55 +0800
Subject: [PATCH] #

---
 rsf-design/src/components/core/layouts/art-page-content/index.vue                             |   24 
 rsf-design/src/views/orders/out-stock-item/outStockItemPage.helpers.js                        |    2 
 rsf-design/src/views/basic-info/warehouse-areas/index.vue                                     |   81 +
 rsf-design/src/views/manager/task/modules/task-expand-panel.vue                               |  143 +++
 rsf-design/src/api/task.js                                                                    |   16 
 rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js                                 |    5 
 rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue                           |  175 +++
 rsf-design/src/views/orders/transfer/index.vue                                                |   12 
 rsf-framework/src/main/java/com/vincent/rsf/framework/generators/RsfDesignGenerator.java      |   38 
 rsf-design/src/api/bas-station-area.js                                                        |   11 
 rsf-design/src/views/orders/transfer/transferPage.helpers.js                                  |    1 
 rsf-design/src/views/orders/purchase/purchaseTable.columns.js                                 |   10 
 rsf-design/src/main.js                                                                        |   15 
 rsf-design/src/views/orders/out-stock/index.vue                                               |  118 ++
 rsf-design/src/views/orders/delivery/index.vue                                                |   14 
 rsf-design/src/views/orders/out-stock/outStockPage.helpers.js                                 |    5 
 rsf-design/src/views/orders/preparation/preparationPage.helpers.js                            |    1 
 rsf-design/src/views/orders/delivery/deliveryPage.helpers.js                                  |    5 
 rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue                     |   46 
 rsf-design/src/views/orders/preparation/index.vue                                             |   12 
 rsf-design/src/views/orders/preparation-item/index.vue                                        |   74 +
 rsf-design/src/views/system/menu/menuTable.columns.js                                         |   20 
 rsf-design/src/api/out-stock-item.js                                                          |    7 
 rsf-design/src/views/manager/task/index.vue                                                   |  156 ++
 rsf-design/src/views/orders/asn-order-item/asnOrderItemPage.helpers.js                        |    2 
 rsf-design/src/views/manager/task/modules/task-detail-drawer.vue                              |   71 +
 rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js                         |    1 
 rsf-design/src/views/orders/purchase-item/index.vue                                           |   63 +
 rsf-design/src/views/orders/out-stock-item/index.vue                                          |   70 +
 rsf-design/src/views/orders/transfer-item/index.vue                                           |   68 +
 rsf-design/src/views/manager/task/taskPage.helpers.js                                         |   50 
 rsf-design/src/views/orders/preparation/modules/preparation-detail-drawer.vue                 |   88 +
 rsf-design/src/views/basic-info/bas-station-area/index.vue                                    |  139 ++
 rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue |  404 +++++++
 rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue                     |   82 
 rsf-design/src/views/orders/delivery-item/index.vue                                           |   67 +
 rsf-design/src/views/orders/asn-order-item/index.vue                                          |   80 +
 rsf-server/src/test/java/com/vincent/rsf/server/common/RsfDesignGeneratorRegressionTest.java  |  503 ++++++++++
 rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js                |   29 
 rsf-design/src/views/manager/task/taskTable.columns.js                                        |   11 
 rsf-design/src/views/orders/purchase/index.vue                                                |   15 
 rsf-design/src/views/orders/asn-order/index.vue                                               |   61 
 42 files changed, 2,508 insertions(+), 287 deletions(-)

diff --git a/rsf-design/src/api/bas-station-area.js b/rsf-design/src/api/bas-station-area.js
index 7919a1a..aaa0acb 100644
--- a/rsf-design/src/api/bas-station-area.js
+++ b/rsf-design/src/api/bas-station-area.js
@@ -209,6 +209,17 @@
   })
 }
 
+export async function fetchExportBasStationAreaReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/basStationArea/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
 export function fetchBasStationAreaQuery(condition = '') {
   return request.post({
     url: '/basStationArea/query',
diff --git a/rsf-design/src/api/out-stock-item.js b/rsf-design/src/api/out-stock-item.js
index 591471d..b04e653 100644
--- a/rsf-design/src/api/out-stock-item.js
+++ b/rsf-design/src/api/out-stock-item.js
@@ -48,6 +48,13 @@
     }
   }
 
+  if (params.orderId !== '' && params.orderId !== undefined && params.orderId !== null) {
+    const numericOrderId = Number(params.orderId)
+    if (!Number.isNaN(numericOrderId)) {
+      result.orderId = numericOrderId
+    }
+  }
+
   return result
 }
 
diff --git a/rsf-design/src/api/task.js b/rsf-design/src/api/task.js
index 2157d26..a1f7a74 100644
--- a/rsf-design/src/api/task.js
+++ b/rsf-design/src/api/task.js
@@ -93,3 +93,19 @@
     url: `/task/top/${id}`
   })
 }
+
+export function fetchTaskAutoRunFlag() {
+  return request.get({
+    url: '/config/flag/AUTO_RUN_CHECK_ORDERS'
+  })
+}
+
+export function fetchUpdateTaskAutoRunFlag(enabled) {
+  return request.post({
+    url: '/config/byFlag',
+    params: {
+      val: !!enabled,
+      flag: 'AUTO_RUN_CHECK_ORDERS'
+    }
+  })
+}
diff --git a/rsf-design/src/components/core/layouts/art-page-content/index.vue b/rsf-design/src/components/core/layouts/art-page-content/index.vue
index b98d865..5c1c4c3 100644
--- a/rsf-design/src/components/core/layouts/art-page-content/index.vue
+++ b/rsf-design/src/components/core/layouts/art-page-content/index.vue
@@ -22,27 +22,21 @@
       </div>
     </div>
 
-    <RouterView v-else-if="isRefresh" v-slot="{ Component, route }" :style="contentStyle">
+    <RouterView v-else-if="isRefresh" v-slot="{ Component, route }">
       <!-- 缂撳瓨璺敱鍔ㄧ敾 -->
       <Transition :name="showTransitionMask ? '' : actualTransition" mode="out-in" appear>
-        <KeepAlive :max="10" :exclude="keepAliveExclude">
-          <component
-            class="art-page-view"
-            :is="Component"
-            :key="route.path"
-            v-if="route.meta.keepAlive"
-          />
-        </KeepAlive>
+        <div v-if="route.meta.keepAlive" class="art-page-view" :style="contentStyle">
+          <KeepAlive :max="10" :exclude="keepAliveExclude">
+            <component :is="Component" :key="route.path" />
+          </KeepAlive>
+        </div>
       </Transition>
 
       <!-- 闈炵紦瀛樿矾鐢卞姩鐢� -->
       <Transition :name="showTransitionMask ? '' : actualTransition" mode="out-in" appear>
-        <component
-          class="art-page-view"
-          :is="Component"
-          :key="route.path"
-          v-if="!route.meta.keepAlive"
-        />
+        <div v-if="!route.meta.keepAlive" class="art-page-view" :style="contentStyle">
+          <component :is="Component" :key="route.path" />
+        </div>
       </Transition>
     </RouterView>
 
diff --git a/rsf-design/src/main.js b/rsf-design/src/main.js
index 8f704c3..2876cc7 100644
--- a/rsf-design/src/main.js
+++ b/rsf-design/src/main.js
@@ -13,21 +13,6 @@
 registerLocalIconCollections()
 const app = createApp(App)
 
-// 娉ㄥ叆閿欒鏃ュ織闈㈡澘鐢ㄤ簬璋冭瘯
-app.config.errorHandler = (err, vm, info) => {
-  console.error("Vue Error:", err, info);
-  const div = document.createElement("div");
-  div.style = "position:fixed;top:0;left:0;z-index:99999;background:red;color:white;padding:20px;font-size:16px;white-space:pre-wrap;width:100vw;height:100vh;overflow:auto;";
-  div.innerText = "Error: " + (err.message || err) + "\n\nStack:\n" + err.stack + "\n\nInfo: " + info;
-  document.body.appendChild(div);
-};
-window.addEventListener("error", (event) => {
-  const div = document.createElement("div");
-  div.style = "position:fixed;top:0;left:0;z-index:99999;background:red;color:white;padding:20px;font-size:16px;white-space:pre-wrap;width:100vw;height:100vh;overflow:auto;";
-  div.innerText = "Global Error: " + event.message + "\n\n" + event.error?.stack;
-  document.body.appendChild(div);
-});
-
 initStore(app)
 initRouter(app)
 setupGlobDirectives(app)
diff --git a/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
index 658dddb..c6c0c9b 100644
--- a/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
+++ b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
@@ -107,6 +107,8 @@
 export function createBasStationAreaSearchState() {
   return {
     condition: '',
+    timeStart: '',
+    timeEnd: '',
     stationAreaName: '',
     stationAreaId: '',
     type: '',
@@ -260,6 +262,8 @@
 export function buildBasStationAreaSearchParams(params = {}) {
   const searchParams = {
     condition: normalizeText(params.condition),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd),
     stationAreaName: normalizeText(params.stationAreaName),
     stationAreaId: normalizeText(params.stationAreaId),
     type:
@@ -472,3 +476,28 @@
 export function normalizeBasStationAreaListRow(record = {}, resolvers = {}) {
   return normalizeBasStationAreaDetailRecord(record, resolvers)
 }
+
+export function buildBasStationAreaPrintRows(records = [], resolvers = {}) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeBasStationAreaListRow(record, resolvers))
+}
+
+export function buildBasStationAreaReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = BAS_STATION_AREA_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: BAS_STATION_AREA_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...BAS_STATION_AREA_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/bas-station-area/index.vue b/rsf-design/src/views/basic-info/bas-station-area/index.vue
index 34c283e..9ec13a5 100644
--- a/rsf-design/src/views/basic-info/bas-station-area/index.vue
+++ b/rsf-design/src/views/basic-info/bas-station-area/index.vue
@@ -24,6 +24,21 @@
             >
               鎵归噺鍒犻櫎
             </ElButton>
+            <ListExportPrint
+              class="inline-flex"
+              :preview-visible="previewVisible"
+              @update:previewVisible="handlePreviewVisibleChange"
+              :report-title="reportTitle"
+              :selected-rows="selectedRows"
+              :query-params="reportQueryParams"
+              :columns="columns"
+              :preview-rows="previewRows"
+              :preview-meta="resolvedPreviewMeta"
+              :total="pagination.total"
+              :disabled="loading"
+              @export="handleExport"
+              @print="handlePrint"
+            />
           </ElSpace>
         </template>
       </ArtTableHeader>
@@ -62,9 +77,12 @@
 <script setup>
   import { computed, onMounted, ref } from 'vue'
   import { ElMessage } from 'element-plus'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
   import { useAuth } from '@/hooks/core/useAuth'
   import { useTable } from '@/hooks/core/useTable'
   import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import { useUserStore } from '@/store/modules/user'
   import { defaultResponseAdapter } from '@/utils/table/tableUtils'
   import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import { fetchDictDataPage } from '@/api/system-manage'
@@ -72,8 +90,10 @@
   import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
   import {
     fetchBasStationAreaDetail,
+    fetchBasStationAreaMany,
     fetchBasStationAreaPage,
     fetchDeleteBasStationArea,
+    fetchExportBasStationAreaReport,
     fetchSaveBasStationArea,
     fetchUpdateBasStationArea
   } from '@/api/bas-station-area'
@@ -81,8 +101,12 @@
   import BasStationAreaDetailDrawer from './modules/bas-station-area-detail-drawer.vue'
   import { createBasStationAreaTableColumns } from './basStationAreaTable.columns'
   import {
+    BAS_STATION_AREA_REPORT_STYLE,
+    BAS_STATION_AREA_REPORT_TITLE,
     buildBasStationAreaDialogModel,
     buildBasStationAreaPageQueryParams,
+    buildBasStationAreaPrintRows,
+    buildBasStationAreaReportMeta,
     buildBasStationAreaSavePayload,
     buildBasStationAreaSearchParams,
     createBasStationAreaSearchState,
@@ -101,6 +125,7 @@
   defineOptions({ name: 'BasStationArea' })
 
   const { hasAuth } = useAuth()
+  const userStore = useUserStore()
 
   const searchForm = ref(createBasStationAreaSearchState())
   const detailDrawerVisible = ref(false)
@@ -162,6 +187,8 @@
   const resolveTypeLabel = (value) => typeLabelMap.value.get(String(value)) || ''
   const resolveStationAliasLabel = (id) => stationLabelMap.value.get(String(id)) || ''
   const resolveUseStatusLabel = (value) => useStatusLabelMap.value.get(String(value)) || ''
+  const reportTitle = BAS_STATION_AREA_REPORT_TITLE
+  const reportQueryParams = computed(() => buildBasStationAreaSearchParams(searchForm.value))
 
   const searchItems = computed(() => [
     {
@@ -171,6 +198,28 @@
       props: {
         clearable: true,
         placeholder: '璇疯緭鍏ョ珯鐐瑰尯鍩熷悕绉�/缂栧彿/澶囨敞'
+      }
+    },
+    {
+      label: '寮�濮嬫椂闂�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        type: 'date',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨寮�濮嬫椂闂�'
+      }
+    },
+    {
+      label: '缁撴潫鏃堕棿',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        type: 'date',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨缁撴潫鏃堕棿'
       }
     },
     {
@@ -248,12 +297,39 @@
       }
     },
     {
+      label: '璺ㄥ尯鍖哄煙',
+      key: 'crossZoneArea',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ法鍖哄尯鍩�'
+      }
+    },
+    {
       label: '鏄惁WCS',
       key: 'isWcs',
       type: 'select',
       props: {
         clearable: true,
         options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: 'WCS鏁版嵁',
+      key: 'wcsData',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏CS鏁版嵁'
+      }
+    },
+    {
+      label: '瀹瑰櫒绫诲瀷',
+      key: 'containerType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鍣ㄧ被鍨�'
       }
     },
     {
@@ -272,6 +348,15 @@
       props: {
         clearable: true,
         placeholder: '璇疯緭鍏ユ潯鐮�'
+      }
+    },
+    {
+      label: '绔欑偣鍒悕',
+      key: 'stationAlias',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰埆鍚�'
       }
     },
     {
@@ -454,6 +539,60 @@
   })
   handleDeleteAction = handleDelete
 
+  const buildPreviewMeta = (rows) => {
+    const now = new Date()
+    return {
+      reportDate: now.toLocaleDateString('zh-CN'),
+      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: { ...BAS_STATION_AREA_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchBasStationAreaMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchBasStationAreaPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+      })
+    ).records
+  }
+
+  const { previewVisible, previewRows, previewMeta, handlePreviewVisibleChange, handleExport, handlePrint } =
+    usePrintExportPage({
+      downloadFileName: 'bas-station-area.xlsx',
+      requestExport: (payload) =>
+        fetchExportBasStationAreaReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) =>
+        buildBasStationAreaPrintRows(records, {
+          resolveAreaLabel,
+          resolveCrossZoneAreaLabel,
+          resolveContainerTypeLabel,
+          resolveTypeLabel,
+          resolveStationAliasLabel,
+          resolveUseStatusLabel
+        }),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildBasStationAreaReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || BAS_STATION_AREA_REPORT_STYLE.orientation
+    })
+  )
+
   function handleSearch(params) {
     replaceSearchParams(buildBasStationAreaSearchParams(params))
     getData()
diff --git a/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue b/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue
index c0f68ba..a4f8fbe 100644
--- a/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue
+++ b/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue
@@ -2,60 +2,203 @@
   <ElDrawer
     :model-value="visible"
     title="娴佺▼鍥炬煡鐪�"
-    size="900px"
+    size="92%"
     destroy-on-close
     @update:model-value="handleVisibleChange"
   >
-    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
-      <div v-if="loading" class="py-6">
-        <ElSkeleton :rows="10" animated />
-      </div>
-      <div v-else class="space-y-4">
-        <ElCard shadow="never" class="art-table-card">
+    <div class="flex h-[calc(100vh-160px)] flex-col gap-4">
+      <ElCard shadow="never" class="shrink-0">
+        <div class="flex items-start justify-between gap-4">
+          <div class="min-w-0">
+            <div class="text-base font-semibold text-[var(--art-text-primary)]">
+              {{ detail.templateName || detail.templateCode || '--' }}
+            </div>
+            <div class="mt-1 text-sm text-[var(--art-text-secondary)]">
+              妯℃澘缂栫爜 {{ detail.templateCode || '--' }}锛岃捣鐐� {{ detail.sourceType || '--' }}锛岀粓鐐�
+              {{ detail.targetType || '--' }}
+            </div>
+          </div>
+          <ElSpace wrap>
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+            <ElTag :type="detail.isCurrentType || 'info'" effect="light">
+              {{ detail.isCurrentText || '--' }}
+            </ElTag>
+          </ElSpace>
+        </div>
+      </ElCard>
+
+      <div class="grid min-h-0 flex-1 gap-4 xl:grid-cols-3">
+        <ElCard shadow="never" class="min-h-0">
           <template #header>
             <div class="flex items-center justify-between gap-3">
-              <div>
-                <h3 class="m-0 text-base font-semibold">妯℃澘娴佺▼蹇収</h3>
-                <p class="m-0 text-sm text-[var(--art-text-secondary)]">
-                  杩欓噷灞曠ず鐨勬槸鍚庣妯℃澘瀛楁缁勫悎鍑虹殑鐪熷疄娴佺▼淇℃伅锛屼笉鍋氶澶栧亣鏁版嵁鎺ㄦ紨銆�
-                </p>
-              </div>
-              <ElTag :type="detail.statusType || 'info'" effect="light">
-                {{ detail.statusText || '--' }}
-              </ElTag>
+              <span class="font-medium">妯℃澘鑺傜偣</span>
+              <span class="text-xs text-[var(--art-text-secondary)]">
+                {{ nodeLoading ? '鍔犺浇涓�' : `绗� ${nodePagination.current} 椤� / 鍏� ${nodePagination.total} 鏉 }}
+              </span>
             </div>
           </template>
 
-          <div class="grid gap-3 md:grid-cols-4">
-            <div
-              v-for="item in flowSnapshot"
-              :key="item.key"
-              class="rounded-lg border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4"
-            >
-              <div class="text-sm text-[var(--art-text-secondary)]">{{ item.title }}</div>
-              <div class="mt-2 text-base font-semibold text-[var(--art-text-primary)]">
-                {{ item.value }}
+          <div class="flex h-[calc(100vh-300px)] flex-col">
+            <ElSkeleton v-if="loading || nodeLoading" :rows="8" animated />
+            <ElEmpty
+              v-else-if="nodeRows.length === 0"
+              description="鏆傛棤鑺傜偣鏁版嵁"
+              :image-size="100"
+            />
+            <template v-else>
+              <ElTable
+                :data="nodeRows"
+                border
+                highlight-current-row
+                height="100%"
+                :current-row-key="selectedNodeId"
+                row-key="id"
+                @current-change="handleNodeClick"
+              >
+                <ElTableColumn prop="nodeOrder" label="椤哄簭" width="72" align="center" />
+                <ElTableColumn prop="nodeCode" label="鑺傜偣缂栫爜" min-width="140" show-overflow-tooltip />
+                <ElTableColumn prop="nodeName" label="鑺傜偣鍚嶇О" min-width="160" show-overflow-tooltip />
+                <ElTableColumn prop="systemCode" label="绯荤粺缂栫爜" min-width="140" show-overflow-tooltip />
+              </ElTable>
+              <div class="mt-3 flex justify-end">
+                <ElPagination
+                  small
+                  background
+                  layout="prev, pager, next"
+                  :current-page="nodePagination.current"
+                  :page-size="nodePagination.pageSize"
+                  :total="nodePagination.total"
+                  @current-change="handleNodePageChange"
+                />
               </div>
-            </div>
+            </template>
           </div>
         </ElCard>
 
-        <ElDescriptions title="娴佺▼渚濇嵁" :column="2" border>
-          <ElDescriptionsItem label="妯℃澘缂栫爜">{{ detail.templateCode || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem label="妯℃澘鍚嶇О">{{ detail.templateName || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem label="璧风偣绫诲瀷">{{ detail.sourceType || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem label="缁堢偣绫诲瀷">{{ detail.targetType || '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem label="姝ュ簭闀垮害">{{ detail.stepSize ?? '--' }}</ElDescriptionsItem>
-          <ElDescriptionsItem label="浼樺厛绾�">{{ detail.priority ?? '--' }}</ElDescriptionsItem>
-        </ElDescriptions>
+        <ElCard shadow="never" class="min-h-0">
+          <template #header>
+            <div class="flex items-center justify-between gap-3">
+              <span class="font-medium">瀛愮郴缁熸祦绋�</span>
+              <span class="text-xs text-[var(--art-text-secondary)]">
+                {{
+                  flowLoading
+                    ? '鍔犺浇涓�'
+                    : selectedNodeId
+                      ? `绗� ${flowPagination.current} 椤� / 鍏� ${flowPagination.total} 鏉
+                      : '寰呴�夋嫨鑺傜偣'
+                }}
+              </span>
+            </div>
+          </template>
+
+          <div class="flex h-[calc(100vh-300px)] flex-col">
+            <div
+              v-if="!selectedNodeId"
+              class="flex h-full items-center justify-center text-sm text-[var(--art-text-secondary)]"
+            >
+              璇峰厛閫夋嫨宸︿晶妯℃澘鑺傜偣
+            </div>
+            <ElSkeleton v-else-if="flowLoading" :rows="8" animated />
+            <ElEmpty
+              v-else-if="flowRows.length === 0"
+              description="鏆傛棤娴佺▼鏁版嵁"
+              :image-size="100"
+            />
+            <template v-else>
+              <ElTable
+                :data="flowRows"
+                border
+                highlight-current-row
+                height="100%"
+                :current-row-key="selectedFlowId"
+                row-key="id"
+                @current-change="handleFlowClick"
+              >
+                <ElTableColumn prop="flowCode" label="娴佺▼缂栫爜" min-width="160" show-overflow-tooltip />
+                <ElTableColumn prop="flowName" label="娴佺▼鍚嶇О" min-width="180" show-overflow-tooltip />
+                <ElTableColumn prop="systemCode" label="绯荤粺缂栫爜" min-width="140" show-overflow-tooltip />
+              </ElTable>
+              <div class="mt-3 flex justify-end">
+                <ElPagination
+                  small
+                  background
+                  layout="prev, pager, next"
+                  :current-page="flowPagination.current"
+                  :page-size="flowPagination.pageSize"
+                  :total="flowPagination.total"
+                  @current-change="handleFlowPageChange"
+                />
+              </div>
+            </template>
+          </div>
+        </ElCard>
+
+        <ElCard shadow="never" class="min-h-0">
+          <template #header>
+            <div class="flex items-center justify-between gap-3">
+              <span class="font-medium">娴佺▼姝ラ</span>
+              <span class="text-xs text-[var(--art-text-secondary)]">
+                {{
+                  stepLoading
+                    ? '鍔犺浇涓�'
+                    : selectedFlowId
+                      ? `绗� ${stepPagination.current} 椤� / 鍏� ${stepPagination.total} 鏉
+                      : '寰呴�夋嫨娴佺▼'
+                }}
+              </span>
+            </div>
+          </template>
+
+          <div class="flex h-[calc(100vh-300px)] flex-col">
+            <div
+              v-if="!selectedFlowId"
+              class="flex h-full items-center justify-center text-sm text-[var(--art-text-secondary)]"
+            >
+              璇峰厛閫夋嫨涓棿瀛愮郴缁熸祦绋�
+            </div>
+            <ElSkeleton v-else-if="stepLoading" :rows="8" animated />
+            <ElEmpty
+              v-else-if="stepRows.length === 0"
+              description="鏆傛棤姝ラ鏁版嵁"
+              :image-size="100"
+            />
+            <template v-else>
+              <ElTable :data="stepRows" border height="100%" row-key="id">
+                <ElTableColumn prop="stepOrder" label="椤哄簭" width="72" align="center" />
+                <ElTableColumn prop="stepCode" label="姝ラ缂栫爜" min-width="140" show-overflow-tooltip />
+                <ElTableColumn prop="stepName" label="姝ラ鍚嶇О" min-width="180" show-overflow-tooltip />
+                <ElTableColumn prop="stepType" label="姝ラ绫诲瀷" min-width="140" show-overflow-tooltip />
+              </ElTable>
+              <div class="mt-3 flex justify-end">
+                <ElPagination
+                  small
+                  background
+                  layout="prev, pager, next"
+                  :current-page="stepPagination.current"
+                  :page-size="stepPagination.pageSize"
+                  :total="stepPagination.total"
+                  @current-change="handleStepPageChange"
+                />
+              </div>
+            </template>
+          </div>
+        </ElCard>
       </div>
-    </ElScrollbar>
+    </div>
   </ElDrawer>
 </template>
 
 <script setup>
-  import { computed } from 'vue'
-  import { buildTaskPathTemplateFlowSnapshot } from '../taskPathTemplatePage.helpers'
+  import { computed, ref, watch } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchTaskPathTemplateNodePage
+  } from '@/api/task-path-template-node'
+  import { fetchSubsystemFlowTemplatePage } from '@/api/subsystem-flow-template'
+  import { fetchFlowStepTemplatePage } from '@/api/flow-step-template'
 
   const props = defineProps({
     visible: { type: Boolean, default: false },
@@ -70,9 +213,194 @@
     set: (value) => emit('update:visible', value)
   })
 
-  const flowSnapshot = computed(() => buildTaskPathTemplateFlowSnapshot(props.detail))
+  const nodeLoading = ref(false)
+  const flowLoading = ref(false)
+  const stepLoading = ref(false)
+  const nodeRows = ref([])
+  const flowRows = ref([])
+  const stepRows = ref([])
+  const selectedNodeId = ref(null)
+  const selectedFlowId = ref(null)
+  const DEFAULT_PAGE_SIZE = 20
+  const nodePagination = ref({ current: 1, pageSize: DEFAULT_PAGE_SIZE, total: 0 })
+  const flowPagination = ref({ current: 1, pageSize: DEFAULT_PAGE_SIZE, total: 0 })
+  const stepPagination = ref({ current: 1, pageSize: DEFAULT_PAGE_SIZE, total: 0 })
+
+  function normalizeRecords(response) {
+    return Array.isArray(response?.records) ? response.records : []
+  }
+
+  function normalizeTotal(response) {
+    const total = Number(response?.total)
+    return Number.isNaN(total) ? 0 : total
+  }
+
+  function resetFlowState() {
+    nodeRows.value = []
+    flowRows.value = []
+    stepRows.value = []
+    selectedNodeId.value = null
+    selectedFlowId.value = null
+    nodePagination.value.current = 1
+    nodePagination.value.total = 0
+    flowPagination.value.current = 1
+    flowPagination.value.total = 0
+    stepPagination.value.current = 1
+    stepPagination.value.total = 0
+  }
+
+  async function loadStepRows(flowId, current = stepPagination.value.current) {
+    if (!flowId) {
+      stepRows.value = []
+      selectedFlowId.value = null
+      stepPagination.value.total = 0
+      return
+    }
+
+    stepLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchFlowStepTemplatePage({
+          current,
+          pageSize: stepPagination.value.pageSize,
+          flowId
+        }),
+        { records: [] },
+        { timeoutMessage: '娴佺▼姝ラ鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      stepRows.value = normalizeRecords(response)
+      stepPagination.value.current = current
+      stepPagination.value.total = normalizeTotal(response)
+    } catch (error) {
+      stepRows.value = []
+      stepPagination.value.total = 0
+      ElMessage.error(error?.message || '娴佺▼姝ラ鍔犺浇澶辫触')
+    } finally {
+      stepLoading.value = false
+    }
+  }
+
+  async function loadFlowRows(node, current = flowPagination.value.current) {
+    if (!node?.nodeCode) {
+      flowRows.value = []
+      stepRows.value = []
+      selectedFlowId.value = null
+      flowPagination.value.total = 0
+      stepPagination.value.total = 0
+      return
+    }
+
+    flowLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchSubsystemFlowTemplatePage({
+          current,
+          pageSize: flowPagination.value.pageSize,
+          flowCode: node.nodeCode
+        }),
+        { records: [] },
+        { timeoutMessage: '瀛愮郴缁熸祦绋嬪姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+      flowRows.value = normalizeRecords(response)
+      flowPagination.value.current = current
+      flowPagination.value.total = normalizeTotal(response)
+      selectedFlowId.value = null
+      stepRows.value = []
+      stepPagination.value.current = 1
+      stepPagination.value.total = 0
+    } catch (error) {
+      flowRows.value = []
+      stepRows.value = []
+      selectedFlowId.value = null
+      flowPagination.value.total = 0
+      stepPagination.value.total = 0
+      ElMessage.error(error?.message || '瀛愮郴缁熸祦绋嬪姞杞藉け璐�')
+    } finally {
+      flowLoading.value = false
+    }
+  }
+
+  async function loadNodeRows(templateId, current = nodePagination.value.current) {
+    if (!templateId) {
+      resetFlowState()
+      return
+    }
+
+    nodeLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchTaskPathTemplateNodePage({
+          current,
+          pageSize: nodePagination.value.pageSize,
+          templateId
+        }),
+        { records: [] },
+        { timeoutMessage: '妯℃澘鑺傜偣鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      nodeRows.value = normalizeRecords(response)
+      nodePagination.value.current = current
+      nodePagination.value.total = normalizeTotal(response)
+      selectedNodeId.value = null
+      flowRows.value = []
+      stepRows.value = []
+      flowPagination.value.current = 1
+      flowPagination.value.total = 0
+      stepPagination.value.current = 1
+      stepPagination.value.total = 0
+    } catch (error) {
+      resetFlowState()
+      ElMessage.error(error?.message || '妯℃澘鑺傜偣鍔犺浇澶辫触')
+    } finally {
+      nodeLoading.value = false
+    }
+  }
+
+  function handleNodeClick(node) {
+    if (!node || selectedNodeId.value === node.id) return
+    selectedNodeId.value = node.id
+    selectedFlowId.value = null
+    stepRows.value = []
+    flowPagination.value.current = 1
+    stepPagination.value.current = 1
+    loadFlowRows(node)
+  }
+
+  function handleFlowClick(flow) {
+    if (!flow || selectedFlowId.value === flow.id) return
+    selectedFlowId.value = flow.id
+    stepPagination.value.current = 1
+    loadStepRows(flow.id)
+  }
+
+  function handleNodePageChange(current) {
+    loadNodeRows(props.detail?.id, current)
+  }
+
+  function handleFlowPageChange(current) {
+    const node = nodeRows.value.find((item) => item.id === selectedNodeId.value)
+    if (!node) return
+    loadFlowRows(node, current)
+  }
+
+  function handleStepPageChange(current) {
+    if (!selectedFlowId.value) return
+    loadStepRows(selectedFlowId.value, current)
+  }
 
   function handleVisibleChange(value) {
     visible.value = value
   }
+
+  watch(
+    () => [props.visible, props.detail?.id],
+    ([isVisible, detailId]) => {
+      if (isVisible && detailId) {
+        loadNodeRows(detailId)
+      }
+      if (!isVisible) {
+        resetFlowState()
+      }
+    },
+    { immediate: true }
+  )
 </script>
diff --git a/rsf-design/src/views/basic-info/warehouse-areas/index.vue b/rsf-design/src/views/basic-info/warehouse-areas/index.vue
index 6f6b31c..e9ad55b 100644
--- a/rsf-design/src/views/basic-info/warehouse-areas/index.vue
+++ b/rsf-design/src/views/basic-info/warehouse-areas/index.vue
@@ -22,6 +22,21 @@
             >
               鎵归噺鍒犻櫎
             </ElButton>
+            <ListExportPrint
+              class="inline-flex"
+              :preview-visible="previewVisible"
+              @update:previewVisible="handlePreviewVisibleChange"
+              :report-title="reportTitle"
+              :selected-rows="selectedRows"
+              :query-params="reportQueryParams"
+              :columns="columns"
+              :preview-rows="previewRows"
+              :preview-meta="resolvedPreviewMeta"
+              :total="pagination.total"
+              :disabled="loading"
+              @export="handleExport"
+              @print="handlePrint"
+            />
           </ElSpace>
         </template>
       </ArtTableHeader>
@@ -59,10 +74,13 @@
 <script setup>
   import { computed, onMounted, ref } from 'vue'
   import { ElMessage } from 'element-plus'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
   import { useAuth } from '@/hooks/core/useAuth'
   import { useTable } from '@/hooks/core/useTable'
   import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { useUserStore } from '@/store/modules/user'
   import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
   import { defaultResponseAdapter } from '@/utils/table/tableUtils'
   import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import { fetchDictDataPage } from '@/api/system-manage'
@@ -70,7 +88,9 @@
     fetchCompanysList,
     fetchDeleteWarehouseAreas,
     fetchWarehouseAreasDetail,
+    fetchWarehouseAreasMany,
     fetchWarehouseAreasPage,
+    fetchExportWarehouseAreasReport,
     fetchSaveWarehouseAreas,
     fetchUpdateWarehouseAreas,
     fetchWarehouseList
@@ -81,10 +101,13 @@
   import {
     buildWarehouseAreasDialogModel,
     buildWarehouseAreasPageQueryParams,
+    buildWarehouseAreasPrintRows,
     buildWarehouseAreasSavePayload,
     buildWarehouseAreasSearchParams,
     createWarehouseAreasSearchState,
     getWarehouseAreasPaginationKey,
+    WAREHOUSE_AREAS_REPORT_STYLE,
+    WAREHOUSE_AREAS_REPORT_TITLE,
     getWarehouseAreasStatusOptions,
     normalizeWarehouseAreasDetailRecord,
     normalizeWarehouseAreasListRow
@@ -93,6 +116,7 @@
   defineOptions({ name: 'WarehouseAreas' })
 
   const { hasAuth } = useAuth()
+  const userStore = useUserStore()
 
   const searchForm = ref(createWarehouseAreasSearchState())
   const detailDrawerVisible = ref(false)
@@ -164,6 +188,9 @@
       }
     }
   ])
+
+  const reportTitle = WAREHOUSE_AREAS_REPORT_TITLE
+  const reportQueryParams = computed(() => buildWarehouseAreasSearchParams(searchForm.value))
 
   async function openDetail(row) {
     detailDrawerVisible.value = true
@@ -249,6 +276,60 @@
   })
   handleDeleteAction = handleDelete
 
+  const buildPreviewMeta = (rows) => {
+    const now = new Date()
+    return {
+      reportDate: now.toLocaleDateString('zh-CN'),
+      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: { ...WAREHOUSE_AREAS_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchWarehouseAreasMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchWarehouseAreasPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'warehouse-areas.xlsx',
+    requestExport: (payload) =>
+      fetchExportWarehouseAreasReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildWarehouseAreasPrintRows(records),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() => ({
+    ...previewMeta.value,
+    reportTitle,
+    count: previewRows.value.length || previewMeta.value?.count || 0,
+    reportStyle: {
+      ...WAREHOUSE_AREAS_REPORT_STYLE,
+      ...(previewMeta.value?.reportStyle || {})
+    }
+  }))
+
   function handleSearch(params) {
     replaceSearchParams(buildWarehouseAreasSearchParams(params))
     getData()
diff --git a/rsf-design/src/views/manager/task/index.vue b/rsf-design/src/views/manager/task/index.vue
index 138697d..08fc6cf 100644
--- a/rsf-design/src/views/manager/task/index.vue
+++ b/rsf-design/src/views/manager/task/index.vue
@@ -9,7 +9,30 @@
     />
 
     <ElCard class="art-table-card">
-      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData" />
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton
+              v-if="!autoRunEnabled"
+              type="primary"
+              plain
+              :loading="autoRunLoading"
+              @click="handleToggleAutoRun(true)"
+            >
+              鑷姩涓嬪彂浠诲姟
+            </ElButton>
+            <ElButton
+              v-else
+              type="warning"
+              plain
+              :loading="autoRunLoading"
+              @click="handleToggleAutoRun(false)"
+            >
+              鏆傚仠鑷姩涓嬪彂
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
 
       <ArtTable
         :loading="loading"
@@ -21,6 +44,11 @@
       />
     </ElCard>
 
+    <TaskFlowStepDialog
+      v-model:visible="flowStepDialogVisible"
+      :task-row="activeTaskRow"
+    />
+
     <TaskDetailDrawer
       v-model:visible="detailDrawerVisible"
       :loading="detailLoading"
@@ -31,13 +59,14 @@
       @refresh="loadDetailResources"
       @size-change="handleDetailSizeChange"
       @current-change="handleDetailCurrentChange"
+      @flow-step="handleOpenFlowStepFromDetail"
     />
   </div>
 </template>
 
 <script setup>
   import { ElMessage } from 'element-plus'
-  import { computed, onMounted, reactive, ref } from 'vue'
+  import { computed, h, onMounted, onUnmounted, reactive, ref } from 'vue'
   import { useTableColumns } from '@/hooks/core/useTableColumns'
   import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import {
@@ -45,12 +74,15 @@
     fetchCompleteTask,
     fetchPickTask,
     fetchRemoveTask,
-    fetchTaskDetail,
+    fetchTaskAutoRunFlag,
     fetchTaskItemPage,
     fetchTaskPage,
-    fetchTopTask
+    fetchTopTask,
+    fetchUpdateTaskAutoRunFlag
   } from '@/api/task'
   import TaskDetailDrawer from './modules/task-detail-drawer.vue'
+  import TaskExpandPanel from './modules/task-expand-panel.vue'
+  import TaskFlowStepDialog from './modules/task-flow-step-dialog.vue'
   import { createTaskTableColumns } from './taskTable.columns'
   import {
     buildTaskPageQueryParams,
@@ -70,6 +102,10 @@
   const detailData = ref({})
   const detailTableData = ref([])
   const activeTaskRow = ref(null)
+  const flowStepDialogVisible = ref(false)
+  const autoRunEnabled = ref(false)
+  const autoRunLoading = ref(false)
+  let refreshTimer = null
 
   const pagination = reactive({
     current: 1,
@@ -192,15 +228,29 @@
 
   async function openDetailDrawer(row) {
     activeTaskRow.value = row
+    detailData.value = normalizeTaskRow(row)
     detailPagination.current = 1
     detailDrawerVisible.value = true
     await loadDetailResources()
+  }
+
+  function handleOpenFlowStepFromDetail() {
+    if (!activeTaskRow.value) {
+      return
+    }
+    flowStepDialogVisible.value = true
   }
 
   async function handleActionClick(action, row) {
     try {
       if (action.key === 'view') {
         await openDetailDrawer(row)
+        return
+      }
+
+      if (action.key === 'flowStep') {
+        activeTaskRow.value = row
+        flowStepDialogVisible.value = true
         return
       }
 
@@ -237,7 +287,17 @@
     }
   }
 
-  const { columns, columnChecks } = useTableColumns(() => createTaskTableColumns(handleActionClick))
+  const { columns, columnChecks } = useTableColumns(() =>
+    createTaskTableColumns({
+      handleActionClick,
+      createExpandContent: (row) => ({
+        name: 'TaskExpandPanelHost',
+        render() {
+          return h(TaskExpandPanel, { row })
+        }
+      })
+    })
+  )
 
   function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
     target.total = Number(response?.total || 0)
@@ -273,6 +333,33 @@
     }
   }
 
+  async function loadAutoRunConfig() {
+    autoRunLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(fetchTaskAutoRunFlag(), { val: false }, {
+        timeoutMessage: '鑷姩涓嬪彂閰嶇疆鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      const rawValue = response?.val
+      autoRunEnabled.value =
+        rawValue === true || rawValue === 'true' || rawValue === 1 || rawValue === '1'
+    } finally {
+      autoRunLoading.value = false
+    }
+  }
+
+  async function handleToggleAutoRun(enabled) {
+    autoRunLoading.value = true
+    try {
+      await fetchUpdateTaskAutoRunFlag(enabled)
+      autoRunEnabled.value = enabled
+      ElMessage.success(enabled ? '宸插紑鍚嚜鍔ㄤ笅鍙戜换鍔�' : '宸叉殏鍋滆嚜鍔ㄤ笅鍙戜换鍔�')
+    } catch (error) {
+      ElMessage.error(error?.message || '鑷姩涓嬪彂閰嶇疆鏇存柊澶辫触')
+    } finally {
+      autoRunLoading.value = false
+    }
+  }
+
   async function loadDetailResources() {
     if (!activeTaskRow.value?.id) {
       return
@@ -280,31 +367,29 @@
 
     detailLoading.value = true
     try {
-      const [detailResponse, taskItemResponse] = await Promise.all([
-        guardRequestWithMessage(fetchTaskDetail(activeTaskRow.value.id), {}, {
-          timeoutMessage: '浠诲姟璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      const taskItemResponse = await guardRequestWithMessage(
+        fetchTaskItemPage({
+          taskId: activeTaskRow.value.id,
+          current: detailPagination.current,
+          pageSize: detailPagination.size
         }),
-        guardRequestWithMessage(
-          fetchTaskItemPage({
-            taskId: activeTaskRow.value.id,
-            current: detailPagination.current,
-            pageSize: detailPagination.size
-          }),
-          {
-            records: [],
-            total: 0,
-            current: detailPagination.current,
-            size: detailPagination.size
-          },
-          { timeoutMessage: '浠诲姟鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
-        )
-      ])
+        {
+          records: [],
+          total: 0,
+          current: detailPagination.current,
+          size: detailPagination.size
+        },
+        { timeoutMessage: '浠诲姟鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
 
-      detailData.value = normalizeTaskRow(detailResponse)
+      detailData.value = normalizeTaskRow(activeTaskRow.value)
       detailTableData.value = Array.isArray(taskItemResponse?.records)
         ? taskItemResponse.records.map((record) => normalizeTaskItemRow(record))
         : []
       updatePaginationState(detailPagination, taskItemResponse, detailPagination.current, detailPagination.size)
+    } catch (error) {
+      detailTableData.value = []
+      ElMessage.error(error?.message || '浠诲姟鏄庣粏鍔犺浇澶辫触')
     } finally {
       detailLoading.value = false
     }
@@ -348,5 +433,26 @@
     loadDetailResources()
   }
 
-  onMounted(loadPageData)
+  function startAutoRefresh() {
+    clearAutoRefresh()
+    refreshTimer = window.setInterval(() => {
+      void loadPageData()
+    }, 5000)
+  }
+
+  function clearAutoRefresh() {
+    if (refreshTimer) {
+      window.clearInterval(refreshTimer)
+      refreshTimer = null
+    }
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadPageData(), loadAutoRunConfig()])
+    startAutoRefresh()
+  })
+
+  onUnmounted(() => {
+    clearAutoRefresh()
+  })
 </script>
diff --git a/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue b/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
index 7a65cb6..53cee56 100644
--- a/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
+++ b/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
@@ -6,27 +6,58 @@
     @update:model-value="handleVisibleChange"
   >
     <div class="flex h-full flex-col gap-4">
-      <ElDescriptions :column="4" border>
-        <ElDescriptionsItem label="浠诲姟鍙�">{{ detail.taskCode || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="浠诲姟鐘舵��">{{ detail.taskStatusLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="浠诲姟绫诲瀷">{{ detail.taskTypeLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="璁惧绫诲瀷">{{ detail.warehTypeLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="婧愬簱浣�">{{ detail.orgLoc || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="婧愮珯鐐�">{{ detail.orgSiteLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鐩爣搴撲綅">{{ detail.targLoc || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鐩爣绔欑偣">{{ detail.targSiteLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鏈哄櫒浜虹紪鐮�">{{ detail.robotCode || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="浼樺厛绾�">{{ detail.sort ?? '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
-      </ElDescriptions>
+      <div class="grid gap-4 xl:grid-cols-[1.45fr_1fr]">
+        <ElCard shadow="never" class="border border-[var(--el-border-color-lighter)]">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="font-medium text-[var(--art-text-gray-900)]">浠诲姟鍩虹淇℃伅</span>
+              <ElTag size="small" effect="plain" type="primary">
+                {{ detail.taskCode || '--' }}
+              </ElTag>
+            </div>
+          </template>
+
+          <ElDescriptions :column="2" border>
+            <ElDescriptionsItem label="浠诲姟鐘舵��">{{ detail.taskStatusLabel || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="浠诲姟绫诲瀷">{{ detail.taskTypeLabel || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="璁惧绫诲瀷">{{ detail.warehTypeLabel || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="浼樺厛绾�">{{ detail.sort ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏈哄櫒浜虹紪鐮�">{{ detail.robotCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+        </ElCard>
+
+        <ElCard shadow="never" class="border border-[var(--el-border-color-lighter)]">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="font-medium text-[var(--art-text-gray-900)]">鎵ц璺緞</span>
+              <ElButton text type="primary" @click="$emit('flow-step')">娴佺▼姝ラ</ElButton>
+            </div>
+          </template>
+
+          <ElDescriptions :column="1" border>
+            <ElDescriptionsItem label="婧愬簱浣�">{{ detail.orgLoc || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="婧愮珯鐐�">{{ detail.orgSiteLabel || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐩爣搴撲綅">{{ detail.targLoc || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐩爣绔欑偣">{{ detail.targSiteLabel || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+        </ElCard>
+      </div>
 
       <div class="flex items-center justify-between">
-        <div class="text-sm text-[var(--art-gray-600)]">浠诲姟鏄庣粏</div>
-        <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+        <div>
+          <div class="text-sm font-medium text-[var(--art-text-gray-900)]">浠诲姟鏄庣粏</div>
+          <div class="mt-1 text-xs text-[var(--art-text-gray-500)]">
+            鏌ョ湅褰撳墠浠诲姟鍏宠仈鐨勪笟鍔″崟鎹�佺墿鏂欏拰鎵ц璁板綍
+          </div>
+        </div>
+        <div class="flex items-center gap-2">
+          <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+        </div>
       </div>
 
       <ArtTable
@@ -51,7 +82,7 @@
     pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
   })
 
-  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change', 'flow-step'])
 
   function handleVisibleChange(visible) {
     emit('update:visible', visible)
diff --git a/rsf-design/src/views/manager/task/modules/task-expand-panel.vue b/rsf-design/src/views/manager/task/modules/task-expand-panel.vue
new file mode 100644
index 0000000..9028100
--- /dev/null
+++ b/rsf-design/src/views/manager/task/modules/task-expand-panel.vue
@@ -0,0 +1,143 @@
+<template>
+  <div class="rounded-xl bg-[var(--el-fill-color-blank)] px-4 py-4">
+    <div class="mb-3 flex items-center justify-between">
+      <div class="text-sm font-medium text-[var(--art-gray-900)]">浠诲姟鏄庣粏</div>
+      <ElButton text size="small" :loading="loading" @click="loadData">鍒锋柊</ElButton>
+    </div>
+
+    <ArtTable
+      :loading="loading"
+      :data="rows"
+      :columns="columns"
+      empty-text="鏆傛棤浠诲姟鏄庣粏"
+      :empty-height="'220px'"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { onMounted, ref, watch } from 'vue'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchTaskItemPage } from '@/api/task'
+  import { normalizeTaskItemRow } from '../taskPage.helpers'
+
+  const props = defineProps({
+    row: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const loading = ref(false)
+  const rows = ref([])
+
+  const columns = [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'orderTypeLabel',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platWorkCode',
+      label: '宸ュ崟鍙�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platItemId',
+      label: '琛屽彿',
+      minWidth: 100,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    }
+  ]
+
+  async function loadData() {
+    if (!props.row?.id) {
+      rows.value = []
+      return
+    }
+
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchTaskItemPage({
+          taskId: props.row.id,
+          current: 1,
+          pageSize: 50
+        }),
+        { records: [] },
+        { timeoutMessage: '浠诲姟鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      rows.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeTaskItemRow(record))
+        : []
+    } finally {
+      loading.value = false
+    }
+  }
+
+  watch(
+    () => props.row?.id,
+    () => {
+      void loadData()
+    }
+  )
+
+  onMounted(() => {
+    void loadData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue b/rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue
new file mode 100644
index 0000000..98b15bf
--- /dev/null
+++ b/rsf-design/src/views/manager/task/modules/task-flow-step-dialog.vue
@@ -0,0 +1,175 @@
+<template>
+  <ElDialog
+    :model-value="visible"
+    width="80%"
+    top="6vh"
+    destroy-on-close
+    title="娴佺▼姝ラ"
+    @update:model-value="emit('update:visible', $event)"
+  >
+    <div class="flex flex-col gap-4">
+      <div class="rounded-xl border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-blank)] px-4 py-3">
+        <div class="text-sm text-[var(--art-gray-500)]">褰撳墠浠诲姟</div>
+        <div class="mt-1 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-[var(--art-gray-900)]">
+          <span>浠诲姟鍙凤細{{ taskRow?.taskCode || '--' }}</span>
+          <span>浠诲姟鐘舵�侊細{{ taskRow?.taskStatusLabel || '--' }}</span>
+          <span>浠诲姟绫诲瀷锛歿{ taskRow?.taskTypeLabel || '--' }}</span>
+        </div>
+      </div>
+
+      <ArtTable
+        :loading="loading"
+        :data="rows"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </div>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, reactive, ref, watch } from 'vue'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchFlowStepInstancePage } from '@/api/flow-step-instance'
+  import { normalizeFlowStepInstanceRow } from '@/views/system/flow-step-instance/flowStepInstancePage.helpers'
+
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    taskRow: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const loading = ref(false)
+  const rows = ref([])
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const columns = computed(() => [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'flowInstanceNo',
+      label: '娴佺▼瀹炰緥鍙�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stepCode',
+      label: '姝ラ缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stepName',
+      label: '姝ラ鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stepType',
+      label: '姝ラ绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'executeResult',
+      label: '鎵ц缁撴灉',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'startTimeText',
+      label: '寮�濮嬫椂闂�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'endTimeText',
+      label: '缁撴潫鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    }
+  ])
+
+  function updatePaginationState(response) {
+    pagination.total = Number(response?.total || 0)
+    pagination.current = Number(response?.current || pagination.current || 1)
+    pagination.size = Number(response?.size || pagination.size || 20)
+  }
+
+  async function loadRows() {
+    if (!props.visible || !props.taskRow?.taskCode) {
+      return
+    }
+
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchFlowStepInstancePage({
+          taskNo: props.taskRow.taskCode,
+          current: pagination.current,
+          pageSize: pagination.size
+        }),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        { timeoutMessage: '娴佺▼姝ラ鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      rows.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeFlowStepInstanceRow(record))
+        : []
+      updatePaginationState(response)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    void loadRows()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    void loadRows()
+  }
+
+  watch(
+    () => [props.visible, props.taskRow?.taskCode],
+    ([visible]) => {
+      if (!visible) {
+        rows.value = []
+        pagination.current = 1
+        return
+      }
+      pagination.current = 1
+      void loadRows()
+    }
+  )
+</script>
diff --git a/rsf-design/src/views/manager/task/taskPage.helpers.js b/rsf-design/src/views/manager/task/taskPage.helpers.js
index 0fa5bda..bf7eb7c 100644
--- a/rsf-design/src/views/manager/task/taskPage.helpers.js
+++ b/rsf-design/src/views/manager/task/taskPage.helpers.js
@@ -55,7 +55,8 @@
     statusText: record['status$'] || '-',
     updateTimeText: record['updateTime$'] || record.updateTime || '-',
     createTimeText: record['createTime$'] || record.createTime || '-',
-    canComplete: record.canComplete === true
+    canComplete: record.canComplete === true,
+    canCancel: record.canCancel === true
   }
 }
 
@@ -84,12 +85,25 @@
   return Number(row.taskStatus) === 199 && Number(row.taskType) === 103
 }
 
+export function canTopTask(row = {}) {
+  const taskStatus = Number(row.taskStatus)
+  const taskType = Number(row.taskType)
+  const allowedStatuses = [1, 101]
+  const allowedTypes = [1, 101, 10, 103, 11]
+  return allowedStatuses.includes(taskStatus) && allowedTypes.includes(taskType)
+}
+
 export function getTaskActionList(row = {}) {
   return [
     {
       key: 'view',
       label: '鏌ョ湅璇︽儏',
       icon: 'ri:eye-line'
+    },
+    {
+      key: 'flowStep',
+      label: '娴佺▼姝ラ',
+      icon: 'ri:node-tree'
     },
     ...(row.canComplete
       ? [
@@ -121,19 +135,27 @@
           }
         ]
       : []),
-    {
-      key: 'top',
-      label: '浠诲姟缃《',
-      icon: 'ri:pushpin-line',
-      auth: 'update'
-    },
-    {
-      key: 'remove',
-      label: '鍙栨秷浠诲姟',
-      icon: 'ri:close-circle-line',
-      color: '#f56c6c',
-      auth: 'delete'
-    }
+    ...(canTopTask(row)
+      ? [
+          {
+            key: 'top',
+            label: '浠诲姟缃《',
+            icon: 'ri:pushpin-line',
+            auth: 'update'
+          }
+        ]
+      : []),
+    ...(row.canCancel
+      ? [
+          {
+            key: 'remove',
+            label: '鍙栨秷浠诲姟',
+            icon: 'ri:close-circle-line',
+            color: '#f56c6c',
+            auth: 'delete'
+          }
+        ]
+      : [])
   ]
 }
 
diff --git a/rsf-design/src/views/manager/task/taskTable.columns.js b/rsf-design/src/views/manager/task/taskTable.columns.js
index 6482c27..d924763 100644
--- a/rsf-design/src/views/manager/task/taskTable.columns.js
+++ b/rsf-design/src/views/manager/task/taskTable.columns.js
@@ -2,8 +2,17 @@
 import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
 import { getTaskActionList } from './taskPage.helpers'
 
-export function createTaskTableColumns(handleActionClick) {
+export function createTaskTableColumns({ handleActionClick, createExpandContent }) {
   return [
+    ...(createExpandContent
+      ? [
+          {
+            type: 'expand',
+            width: 56,
+            formatter: (row) => createExpandContent(row)
+          }
+        ]
+      : []),
     {
       prop: 'taskCode',
       label: '浠诲姟鍙�',
diff --git a/rsf-design/src/views/orders/asn-order-item/asnOrderItemPage.helpers.js b/rsf-design/src/views/orders/asn-order-item/asnOrderItemPage.helpers.js
index d5c43d2..dbf84a9 100644
--- a/rsf-design/src/views/orders/asn-order-item/asnOrderItemPage.helpers.js
+++ b/rsf-design/src/views/orders/asn-order-item/asnOrderItemPage.helpers.js
@@ -78,6 +78,7 @@
 export function createAsnOrderItemSearchState() {
   return {
     condition: '',
+    orderId: '',
     orderCode: '',
     poCode: '',
     platWorkCode: '',
@@ -109,6 +110,7 @@
 
   ;[
     'condition',
+    'orderId',
     'orderCode',
     'poCode',
     'platWorkCode',
diff --git a/rsf-design/src/views/orders/asn-order-item/index.vue b/rsf-design/src/views/orders/asn-order-item/index.vue
index f1f4cce..a10b8b2 100644
--- a/rsf-design/src/views/orders/asn-order-item/index.vue
+++ b/rsf-design/src/views/orders/asn-order-item/index.vue
@@ -1,5 +1,15 @@
 <template>
   <div class="asn-order-item-page art-full-height">
+    <ElCard v-if="activeSourceSummary" class="mb-3">
+      <div class="flex items-center justify-between gap-3">
+        <div class="flex items-center gap-2 text-sm text-[var(--art-text-gray-600)]">
+          <span class="font-medium text-[var(--art-text-gray-900)]">褰撳墠鏉ユ簮</span>
+          <span>鍏ュ簱閫氱煡鍗旾D锛歿{ activeSourceSummary.orderId }}</span>
+        </div>
+        <ElButton link type="primary" @click="handleClearSourceFilter">鏌ョ湅鍏ㄩ儴</ElButton>
+      </div>
+    </ElCard>
+
     <ArtSearchBar
       v-model="searchForm"
       :items="searchItems"
@@ -49,8 +59,9 @@
 </template>
 
 <script setup>
-  import { computed, ref } from 'vue'
-  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, ref, watch } from 'vue'
+  import { ElButton, ElMessage } from 'element-plus'
+  import { useRoute, useRouter } from 'vue-router'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
   import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
@@ -81,6 +92,8 @@
   const DEFAULT_PAGE_SIZE = 20
 
   const userStore = useUserStore()
+  const route = useRoute()
+  const router = useRouter()
   const reportTitle = ASN_ORDER_ITEM_REPORT_TITLE
   const searchForm = ref(createAsnOrderItemSearchState())
   const selectedRows = ref([])
@@ -88,6 +101,15 @@
   const detailLoading = ref(false)
   const detailData = ref({})
   const activeItemId = ref(null)
+
+  const activeSourceSummary = computed(() => {
+    if (searchForm.value.orderId === '' || searchForm.value.orderId === undefined || searchForm.value.orderId === null) {
+      return null
+    }
+    return {
+      orderId: searchForm.value.orderId
+    }
+  })
 
   const reportQueryParams = computed(() => ({
     ...buildAsnOrderItemSearchParams(searchForm.value),
@@ -274,6 +296,32 @@
     resetSearchParams()
   }
 
+  function applyRouteSearch() {
+    const orderId = route.query.orderId
+    if (orderId === undefined || orderId === null || orderId === '') {
+      return
+    }
+    searchForm.value.orderId = String(orderId)
+  }
+
+  function handleClearSourceFilter() {
+    searchForm.value.orderId = ''
+    router.replace({
+      path: route.path,
+      query: {
+        ...route.query,
+        orderId: undefined
+      }
+    })
+    replaceSearchParams(
+      buildAsnOrderItemPageQueryParams({
+        ...searchForm.value,
+        orderBy: 'id desc'
+      })
+    )
+    getData()
+  }
+
   async function openDetail(row) {
     activeItemId.value = row.id
     detailData.value = normalizeAsnOrderItemDetail(row)
@@ -383,4 +431,32 @@
         ASN_ORDER_ITEM_REPORT_STYLE.orientation
     })
   )
+
+  watch(
+    () => route.query.orderId,
+    (value) => {
+      if (value === undefined || value === null || value === '') {
+        return
+      }
+      applyRouteSearch()
+      replaceSearchParams(
+        buildAsnOrderItemPageQueryParams({
+          ...searchForm.value,
+          orderBy: 'id desc'
+        })
+      )
+      getData()
+    }
+  )
+
+  onMounted(() => {
+    applyRouteSearch()
+    replaceSearchParams(
+      buildAsnOrderItemPageQueryParams({
+        ...searchForm.value,
+        orderBy: 'id desc'
+      })
+    )
+    getData()
+  })
 </script>
diff --git a/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js b/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
index 0edd1f6..6bf4f06 100644
--- a/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
+++ b/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
@@ -245,6 +245,11 @@
       icon: 'ri:eye-line'
     },
     {
+      key: 'items',
+      label: '鏀惰揣鏄庣粏',
+      icon: 'ri:list-check-3'
+    },
+    {
       key: 'print',
       label: '鎵撳嵃',
       icon: 'ri:printer-line'
diff --git a/rsf-design/src/views/orders/asn-order/index.vue b/rsf-design/src/views/orders/asn-order/index.vue
index 1ef8bd6..c771230 100644
--- a/rsf-design/src/views/orders/asn-order/index.vue
+++ b/rsf-design/src/views/orders/asn-order/index.vue
@@ -60,6 +60,7 @@
 
 <script setup>
   import { computed, reactive, ref } from 'vue'
+  import { useRouter } from 'vue-router'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
@@ -72,7 +73,6 @@
     fetchAsnOrderPage,
     fetchCompleteAsnOrder,
     fetchExportAsnOrderReport,
-    fetchGetAsnOrderDetail,
     fetchGetAsnOrderMany
   } from '@/api/asn-order'
   import AsnOrderDetailDrawer from './modules/asn-order-detail-drawer.vue'
@@ -98,6 +98,7 @@
   defineOptions({ name: 'AsnOrder' })
 
   const userStore = useUserStore()
+  const router = useRouter()
   const reportTitle = ASN_ORDER_REPORT_TITLE
   const searchForm = ref(createAsnOrderSearchState())
   const selectedRows = ref([])
@@ -106,6 +107,7 @@
   const detailData = ref({})
   const detailTableData = ref([])
   const activeOrderId = ref(null)
+  const activeOrderRow = ref(null)
   const poDialogVisible = ref(false)
 
   const detailPagination = reactive({
@@ -184,6 +186,8 @@
 
   async function openDetail(row) {
     activeOrderId.value = row.id
+    activeOrderRow.value = row
+    detailData.value = normalizeAsnOrderRow(row)
     detailPagination.current = 1
     detailDrawerVisible.value = true
     await loadDetailResources()
@@ -202,6 +206,16 @@
 
       if (action.key === 'print') {
         await handlePrint({ ids: [row.id], pageSize: 1 })
+        return
+      }
+
+      if (action.key === 'items') {
+        router.push({
+          path: '/orders/asn-order-item',
+          query: {
+            orderId: String(row.id)
+          }
+        })
         return
       }
 
@@ -267,35 +281,26 @@
 
     detailLoading.value = true
     try {
-      const [detailResponse, itemResponse] = await Promise.all([
-        guardRequestWithMessage(
-          fetchGetAsnOrderDetail(activeOrderId.value),
-          {},
-          {
-            timeoutMessage: '鍏ュ簱閫氱煡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
-          }
-        ),
-        guardRequestWithMessage(
-          fetchAsnOrderItemPage(
-            buildAsnOrderDetailQueryParams({
-              orderId: activeOrderId.value,
-              current: detailPagination.current,
-              pageSize: detailPagination.size
-            })
-          ),
-          {
-            records: [],
-            total: 0,
+      const itemResponse = await guardRequestWithMessage(
+        fetchAsnOrderItemPage(
+          buildAsnOrderDetailQueryParams({
+            orderId: activeOrderId.value,
             current: detailPagination.current,
-            size: detailPagination.size
-          },
-          {
-            timeoutMessage: '鍏ュ簱閫氱煡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
-          }
-        )
-      ])
+            pageSize: detailPagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: detailPagination.current,
+          size: detailPagination.size
+        },
+        {
+          timeoutMessage: '鍏ュ簱閫氱煡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+        }
+      )
 
-      detailData.value = normalizeAsnOrderRow(detailResponse)
+      detailData.value = normalizeAsnOrderRow(activeOrderRow.value || {})
       detailTableData.value = Array.isArray(itemResponse?.records)
         ? itemResponse.records.map((item) => normalizeAsnOrderItemRow(item))
         : []
diff --git a/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue b/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
index f9c0ebe..a5534ca 100644
--- a/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
@@ -2,54 +2,48 @@
   <ElDrawer
     :model-value="visible"
     title="鍏ュ簱閫氱煡鍗曡鎯�"
-    size="88%"
+    size="1180px"
+    destroy-on-close
     @update:model-value="handleVisibleChange"
   >
-    <div class="flex h-full flex-col gap-4">
-      <ElDescriptions :column="4" border>
-        <ElDescriptionsItem label="ASN鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{
-          detail.orderTypeLabel || '--'
-        }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鍗曟嵁鐘舵��">{{
-          detail.exceStatusText || '--'
-        }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="閲囪喘缁勭粐">{{
-          detail.purchaseOrgName || '--'
-        }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="閲囪喘鍛�">{{
-          detail.purchaseUserName || '--'
-        }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="渚涘簲鍟�">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="搴旀敹鏁伴噺">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="宸叉敹鏁伴噺">{{ detail.qty ?? 0 }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{
-          detail.updateTimeText || '--'
-        }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{
-          detail.createTimeText || '--'
-        }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
-      </ElDescriptions>
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="4" border>
+          <ElDescriptionsItem label="ASN鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.orderTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁鐘舵��">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲囪喘缁勭粐">{{ detail.purchaseOrgName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲囪喘鍛�">{{ detail.purchaseUserName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟�">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴旀敹鏁伴噺">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸叉敹鏁伴噺">{{ detail.qty ?? 0 }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
 
-      <div class="flex items-center justify-between">
-        <div class="text-sm text-[var(--art-gray-600)]"
-          >鏄庣粏娓呭崟锛堢墿鏂欑紪鐮�/鐗╂枡鍚嶇О/渚涘簲鍟嗘壒娆★級</div
-        >
-        <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+        <div class="space-y-3">
+          <div class="flex items-center justify-between">
+            <div class="text-sm font-medium text-[var(--art-gray-900)]">鍗曟嵁鏄庣粏</div>
+            <div class="flex items-center gap-3">
+              <ElTag effect="plain">鍏� {{ data.length }} 鏉�</ElTag>
+              <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+            </div>
+          </div>
+
+          <ArtTable
+            :loading="loading"
+            :data="data"
+            :columns="columns"
+            :pagination="pagination"
+            @pagination:size-change="$emit('size-change', $event)"
+            @pagination:current-change="$emit('current-change', $event)"
+          />
+        </div>
       </div>
-
-      <ArtTable
-        :loading="loading"
-        :data="data"
-        :columns="columns"
-        :pagination="pagination"
-        @pagination:size-change="$emit('size-change', $event)"
-        @pagination:current-change="$emit('current-change', $event)"
-      />
-    </div>
+    </ElScrollbar>
   </ElDrawer>
 </template>
 
diff --git a/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js b/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
index c38a44f..afe7c69 100644
--- a/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
+++ b/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
@@ -37,6 +37,7 @@
 export function createDeliveryItemSearchState() {
   return {
     condition: '',
+    deliveryId: '',
     deliveryCode: '',
     platItemId: '',
     matnrCode: '',
diff --git a/rsf-design/src/views/orders/delivery-item/index.vue b/rsf-design/src/views/orders/delivery-item/index.vue
index eea4e50..fdd6cc6 100644
--- a/rsf-design/src/views/orders/delivery-item/index.vue
+++ b/rsf-design/src/views/orders/delivery-item/index.vue
@@ -1,5 +1,15 @@
 <template>
   <div class="delivery-item-page art-full-height">
+    <ElCard v-if="activeSourceSummary" class="mb-3">
+      <div class="flex items-center justify-between gap-3">
+        <div class="flex items-center gap-2 text-sm text-[var(--art-text-gray-600)]">
+          <span class="font-medium text-[var(--art-text-gray-900)]">褰撳墠鏉ユ簮</span>
+          <span>DO鍗旾D锛歿{ activeSourceSummary.deliveryId }}</span>
+        </div>
+        <ElButton link type="primary" @click="handleClearSourceFilter">鏌ョ湅鍏ㄩ儴</ElButton>
+      </div>
+    </ElCard>
+
     <ArtSearchBar
       v-model="searchForm"
       :items="searchItems"
@@ -30,8 +40,9 @@
 </template>
 
 <script setup>
-  import { computed, ref } from 'vue'
-  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, ref, watch } from 'vue'
+  import { ElButton, ElMessage } from 'element-plus'
+  import { useRoute, useRouter } from 'vue-router'
   import { useTable } from '@/hooks/core/useTable'
   import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import { fetchDeliveryItemPage, fetchGetDeliveryItemDetail } from '@/api/delivery'
@@ -45,10 +56,21 @@
 
   defineOptions({ name: 'DeliveryItem' })
 
+  const route = useRoute()
+  const router = useRouter()
   const searchForm = ref(createDeliveryItemSearchState())
   const detailDrawerVisible = ref(false)
   const detailLoading = ref(false)
   const detailData = ref({})
+
+  const activeSourceSummary = computed(() => {
+    if (searchForm.value.deliveryId === '' || searchForm.value.deliveryId === undefined || searchForm.value.deliveryId === null) {
+      return null
+    }
+    return {
+      deliveryId: searchForm.value.deliveryId
+    }
+  })
 
   const searchItems = computed(() => [
     {
@@ -175,4 +197,45 @@
     Object.assign(searchForm.value, createDeliveryItemSearchState())
     resetSearchParams()
   }
+
+  function applyRouteSearch() {
+    const deliveryId = route.query.deliveryId
+    if (deliveryId === undefined || deliveryId === null || deliveryId === '') {
+      return
+    }
+    searchForm.value.deliveryId = Number.isFinite(Number(deliveryId))
+      ? Number(deliveryId)
+      : searchForm.value.deliveryId
+  }
+
+  function handleClearSourceFilter() {
+    searchForm.value.deliveryId = ''
+    router.replace({
+      path: route.path,
+      query: {
+        ...route.query,
+        deliveryId: undefined
+      }
+    })
+    replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  watch(
+    () => route.query.deliveryId,
+    (value) => {
+      if (value === undefined || value === null || value === '') {
+        return
+      }
+      applyRouteSearch()
+      replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+      getData()
+    }
+  )
+
+  onMounted(() => {
+    applyRouteSearch()
+    replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+    getData()
+  })
 </script>
diff --git a/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js b/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
index c770e3c..e993d8e 100644
--- a/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
+++ b/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
@@ -248,6 +248,11 @@
       key: 'view',
       label: '鏌ョ湅璇︽儏',
       icon: 'ri:eye-line'
+    },
+    {
+      key: 'items',
+      label: '鏄庣粏',
+      icon: 'ri:list-check-3'
     }
   ]
 
diff --git a/rsf-design/src/views/orders/delivery/index.vue b/rsf-design/src/views/orders/delivery/index.vue
index 851a0d5..f3ec265 100644
--- a/rsf-design/src/views/orders/delivery/index.vue
+++ b/rsf-design/src/views/orders/delivery/index.vue
@@ -55,6 +55,7 @@
 
 <script setup>
   import { computed, reactive, ref } from 'vue'
+  import { useRouter } from 'vue-router'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
@@ -87,6 +88,7 @@
   defineOptions({ name: 'Delivery' })
 
   const userStore = useUserStore()
+  const router = useRouter()
   const reportTitle = 'DO鍗曟姤琛�'
   const searchForm = ref(createDeliverySearchState())
   const selectedRows = ref([])
@@ -267,6 +269,18 @@
       openDetail(row)
       return
     }
+    if (action.key === 'items') {
+      if (!row?.id) {
+        return
+      }
+      router.push({
+        path: '/orders/delivery-item',
+        query: {
+          deliveryId: String(row.id)
+        }
+      })
+      return
+    }
     if (action.key === 'delete') {
       handleDelete(row)
     }
diff --git a/rsf-design/src/views/orders/out-stock-item/index.vue b/rsf-design/src/views/orders/out-stock-item/index.vue
index 58ecafa..03c29d0 100644
--- a/rsf-design/src/views/orders/out-stock-item/index.vue
+++ b/rsf-design/src/views/orders/out-stock-item/index.vue
@@ -1,5 +1,15 @@
 <template>
   <div class="out-stock-item-page art-full-height">
+    <ElCard v-if="activeSourceSummary" class="mb-3">
+      <div class="flex items-center justify-between gap-3">
+        <div class="flex items-center gap-2 text-sm text-[var(--art-text-gray-600)]">
+          <span class="font-medium text-[var(--art-text-gray-900)]">褰撳墠鏉ユ簮</span>
+          <span>鍑哄簱鍗旾D锛歿{ activeSourceSummary.orderId }}</span>
+        </div>
+        <ElButton link type="primary" @click="handleClearSourceFilter">鏌ョ湅鍏ㄩ儴</ElButton>
+      </div>
+    </ElCard>
+
     <ArtSearchBar
       v-model="searchForm"
       :items="searchItems"
@@ -30,8 +40,9 @@
 </template>
 
 <script setup>
-  import { computed, ref } from 'vue'
-  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, ref, watch } from 'vue'
+  import { ElButton, ElMessage } from 'element-plus'
+  import { useRoute, useRouter } from 'vue-router'
   import { useTable } from '@/hooks/core/useTable'
   import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import { fetchGetOutStockItemDetail, fetchOutStockItemPage } from '@/api/out-stock-item'
@@ -45,10 +56,24 @@
 
   defineOptions({ name: 'OutStockItem' })
 
+  const route = useRoute()
+  const router = useRouter()
   const searchForm = ref(createOutStockItemSearchState())
   const detailDrawerVisible = ref(false)
   const detailLoading = ref(false)
   const detailData = ref({})
+  const activeSourceSummary = computed(() => {
+    if (
+      searchForm.value.orderId === '' ||
+      searchForm.value.orderId === undefined ||
+      searchForm.value.orderId === null
+    ) {
+      return null
+    }
+    return {
+      orderId: searchForm.value.orderId
+    }
+  })
 
   const searchItems = computed(() => [
     {
@@ -214,4 +239,45 @@
     Object.assign(searchForm.value, createOutStockItemSearchState())
     resetSearchParams()
   }
+
+  function applyRouteSearch() {
+    const orderId = route.query.orderId
+    if (orderId === undefined || orderId === null || orderId === '') {
+      return
+    }
+    searchForm.value.orderId = Number.isFinite(Number(orderId))
+      ? Number(orderId)
+      : searchForm.value.orderId
+  }
+
+  function handleClearSourceFilter() {
+    searchForm.value.orderId = ''
+    router.replace({
+      path: route.path,
+      query: {
+        ...route.query,
+        orderId: undefined
+      }
+    })
+    replaceSearchParams(buildOutStockItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  watch(
+    () => route.query.orderId,
+    (value) => {
+      if (value === undefined || value === null || value === '') {
+        return
+      }
+      applyRouteSearch()
+      replaceSearchParams(buildOutStockItemPageQueryParams(searchForm.value))
+      getData()
+    }
+  )
+
+  onMounted(() => {
+    applyRouteSearch()
+    replaceSearchParams(buildOutStockItemPageQueryParams(searchForm.value))
+    getData()
+  })
 </script>
diff --git a/rsf-design/src/views/orders/out-stock-item/outStockItemPage.helpers.js b/rsf-design/src/views/orders/out-stock-item/outStockItemPage.helpers.js
index 27cffa4..7f92e10 100644
--- a/rsf-design/src/views/orders/out-stock-item/outStockItemPage.helpers.js
+++ b/rsf-design/src/views/orders/out-stock-item/outStockItemPage.helpers.js
@@ -43,6 +43,7 @@
 export function createOutStockItemSearchState() {
   return {
     condition: '',
+    orderId: '',
     orderCode: '',
     poCode: '',
     platItemId: '',
@@ -75,6 +76,7 @@
   ].forEach((key) => pushText(result, key, params[key]))
 
   pushNumber(result, 'status', params.status)
+  pushNumber(result, 'orderId', params.orderId)
 
   return result
 }
diff --git a/rsf-design/src/views/orders/out-stock/index.vue b/rsf-design/src/views/orders/out-stock/index.vue
index 5f73851..ab3fd34 100644
--- a/rsf-design/src/views/orders/out-stock/index.vue
+++ b/rsf-design/src/views/orders/out-stock/index.vue
@@ -39,19 +39,32 @@
       />
     </ElCard>
 
-    <OutStockDetailDrawer v-model:visible="detailDrawerVisible" :loading="detailLoading" :detail="detailData" />
+    <OutStockDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :items-loading="detailItemsLoading"
+      :detail="detailData"
+      :item-rows="detailItemRows"
+      :item-columns="detailItemColumns"
+      :pagination="detailItemPagination"
+      @refresh="loadDetailResources"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
   </div>
 </template>
 
 <script setup>
-  import { computed, ref } from 'vue'
+  import { computed, reactive, ref } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useRouter } from 'vue-router'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
   import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
   import ListExportPrint from '@/components/biz/list-export-print/index.vue'
   import { defaultResponseAdapter } from '@/utils/table/tableUtils'
   import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchOutStockItemPage } from '@/api/out-stock-item'
   import {
     fetchCancelOutStock,
     fetchCompleteOutStock,
@@ -73,17 +86,30 @@
     normalizeOutStockRow
   } from './outStockPage.helpers'
   import { createOutStockTableColumns } from './outStockTable.columns'
+  import { createOutStockItemTableColumns } from '../out-stock-item/outStockItemTable.columns.js'
+  import { normalizeOutStockItemRow } from '../out-stock-item/outStockItemPage.helpers.js'
 
   defineOptions({ name: 'OutStock' })
 
   const userStore = useUserStore()
+  const router = useRouter()
   const reportTitle = OUT_STOCK_REPORT_TITLE
   const searchForm = ref(createOutStockSearchState())
   const selectedRows = ref([])
   const detailDrawerVisible = ref(false)
   const detailLoading = ref(false)
+  const detailItemsLoading = ref(false)
   const detailData = ref({})
+  const detailItemRows = ref([])
   const activeOutStockId = ref(null)
+  const detailItemPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+  const detailItemColumns = computed(() =>
+    createOutStockItemTableColumns().filter((column) => column.prop !== 'operation')
+  )
 
   const reportQueryParams = computed(() => buildOutStockSearchParams(searchForm.value))
   const searchItems = computed(() => [
@@ -138,22 +164,69 @@
     { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } }
   ])
 
-  function openDetail(row) {
+  async function loadDetailResources() {
+    if (!activeOutStockId.value) {
+      return
+    }
+
+    detailLoading.value = true
+    detailItemsLoading.value = true
+    try {
+      const [detailResponse, itemResponse] = await Promise.all([
+        guardRequestWithMessage(fetchGetOutStockDetail(activeOutStockId.value), {}, { timeoutMessage: '鍑哄簱鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }),
+        guardRequestWithMessage(
+          fetchOutStockItemPage({
+            orderId: activeOutStockId.value,
+            current: detailItemPagination.current,
+            pageSize: detailItemPagination.size
+          }),
+          {
+            records: [],
+            total: 0,
+            current: detailItemPagination.current,
+            size: detailItemPagination.size
+          },
+          { timeoutMessage: '鍑哄簱鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+        )
+      ])
+
+      detailData.value = normalizeOutStockRow(detailResponse)
+      const normalized = defaultResponseAdapter(itemResponse)
+      detailItemRows.value = normalized.records.map((item) => normalizeOutStockItemRow(item))
+      detailItemPagination.total = Number(normalized.total || 0)
+      detailItemPagination.current = Number(normalized.current || detailItemPagination.current || 1)
+      detailItemPagination.size = Number(normalized.size || detailItemPagination.size || 20)
+    } finally {
+      detailLoading.value = false
+      detailItemsLoading.value = false
+    }
+  }
+
+  async function openDetail(row) {
     activeOutStockId.value = row.id
     detailDrawerVisible.value = true
-    detailLoading.value = true
-    guardRequestWithMessage(fetchGetOutStockDetail(row.id), {}, { timeoutMessage: '鍑哄簱鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' })
-      .then((detail) => {
-        detailData.value = normalizeOutStockRow(detail)
-      })
-      .catch((error) => {
-        detailDrawerVisible.value = false
-        detailData.value = {}
-        ElMessage.error(error?.message || '鑾峰彇鍑哄簱鍗曡鎯呭け璐�')
-      })
-      .finally(() => {
-        detailLoading.value = false
-      })
+    detailItemPagination.current = 1
+    detailData.value = {}
+    detailItemRows.value = []
+    try {
+      await loadDetailResources()
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      detailItemRows.value = []
+      ElMessage.error(error?.message || '鑾峰彇鍑哄簱鍗曡鎯呭け璐�')
+    }
+  }
+
+  function handleDetailSizeChange(size) {
+    detailItemPagination.size = size
+    detailItemPagination.current = 1
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailItemPagination.current = current
+    loadDetailResources()
   }
 
   async function handleComplete(row) {
@@ -167,7 +240,7 @@
       ElMessage.success('瀹屾垚鎴愬姛')
       await refreshData()
       if (detailDrawerVisible.value && activeOutStockId.value === row.id) {
-        openDetail(row)
+        await loadDetailResources()
       }
     } catch (error) {
       if (error === 'cancel' || error?.message === 'cancel') return
@@ -186,7 +259,7 @@
       ElMessage.success('鍙栨秷鎴愬姛')
       await refreshData()
       if (detailDrawerVisible.value && activeOutStockId.value === row.id) {
-        openDetail(row)
+        await loadDetailResources()
       }
     } catch (error) {
       if (error === 'cancel' || error?.message === 'cancel') return
@@ -216,6 +289,15 @@
       openDetail(row)
       return
     }
+    if (action.key === 'items') {
+      router.push({
+        path: '/orders/out-stock-item',
+        query: {
+          orderId: String(row.id)
+        }
+      })
+      return
+    }
     if (action.key === 'print') {
       await handlePrint({ ids: [row.id], pageSize: 1 })
       return
diff --git a/rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue b/rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue
index feebb70..98a20e1 100644
--- a/rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue
+++ b/rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue
@@ -2,14 +2,14 @@
   <ElDrawer
     v-model="visibleProxy"
     :title="'鍑哄簱鍗曡鎯�'"
-    size="720px"
+    size="1180px"
     destroy-on-close
     append-to-body
   >
-    <ElScrollbar class="h-full">
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
       <ElSkeleton :loading="loading" animated :rows="8">
         <div class="space-y-4">
-          <ElDescriptions :column="2" border>
+          <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
             <ElDescriptionsItem label="鍑哄簱鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
             <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
             <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
@@ -33,12 +33,31 @@
             <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
           </ElDescriptions>
 
-          <ElDescriptions :column="2" border>
+          <ElDescriptions title="瀹¤淇℃伅" :column="2" border>
             <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
             <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
             <ElDescriptionsItem label="淇敼浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
             <ElDescriptionsItem label="淇敼鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
           </ElDescriptions>
+
+          <div class="space-y-3">
+            <div class="flex items-center justify-between">
+              <div class="text-sm font-medium text-[var(--art-gray-900)]">鍗曟嵁鏄庣粏</div>
+              <div class="flex items-center gap-3">
+                <ElTag effect="plain">鍏� {{ itemRows.length }} 鏉�</ElTag>
+                <ElButton :loading="itemsLoading" @click="$emit('refresh')">鍒锋柊</ElButton>
+              </div>
+            </div>
+
+            <ArtTable
+              :loading="itemsLoading"
+              :data="itemRows"
+              :columns="itemColumns"
+              :pagination="pagination"
+              @pagination:size-change="$emit('size-change', $event)"
+              @pagination:current-change="$emit('current-change', $event)"
+            />
+          </div>
         </div>
       </ElSkeleton>
     </ElScrollbar>
@@ -47,6 +66,7 @@
 
 <script setup>
   import { computed } from 'vue'
+  import ArtTable from '@/components/core/tables/art-table/index.vue'
 
   defineOptions({ name: 'OutStockDetailDrawer' })
 
@@ -59,13 +79,29 @@
       type: Boolean,
       default: false
     },
+    itemsLoading: {
+      type: Boolean,
+      default: false
+    },
     detail: {
       type: Object,
       default: () => ({})
+    },
+    itemRows: {
+      type: Array,
+      default: () => []
+    },
+    itemColumns: {
+      type: Array,
+      default: () => []
+    },
+    pagination: {
+      type: Object,
+      default: () => ({ current: 1, size: 20, total: 0 })
     }
   })
 
-  const emit = defineEmits(['update:visible'])
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
 
   const visibleProxy = computed({
     get: () => props.visible,
diff --git a/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js b/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
index d31e6ed..19012d1 100644
--- a/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
+++ b/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
@@ -157,6 +157,11 @@
       icon: 'ri:eye-line'
     },
     {
+      key: 'items',
+      label: '鏄庣粏',
+      icon: 'ri:list-check-3'
+    },
+    {
       key: 'print',
       label: '鎵撳嵃',
       icon: 'ri:printer-line'
diff --git a/rsf-design/src/views/orders/preparation-item/index.vue b/rsf-design/src/views/orders/preparation-item/index.vue
index 07a12ef..60cb842 100644
--- a/rsf-design/src/views/orders/preparation-item/index.vue
+++ b/rsf-design/src/views/orders/preparation-item/index.vue
@@ -1,5 +1,15 @@
 <template>
   <div class="preparation-item-page art-full-height">
+    <ElCard v-if="activeSourceSummary" class="mb-3">
+      <div class="flex items-center justify-between gap-3">
+        <div class="flex items-center gap-2 text-sm text-[var(--art-text-gray-600)]">
+          <span class="font-medium text-[var(--art-text-gray-900)]">褰撳墠鏉ユ簮</span>
+          <span>澶囨枡鍗旾D锛歿{ activeSourceSummary.orderId }}</span>
+        </div>
+        <ElButton link type="primary" @click="handleClearSourceFilter">鏌ョ湅鍏ㄩ儴</ElButton>
+      </div>
+    </ElCard>
+
     <ArtSearchBar
       v-model="searchForm"
       :items="searchItems"
@@ -49,9 +59,9 @@
 </template>
 
 <script setup>
-  import { computed, ref } from 'vue'
-  import { useRoute } from 'vue-router'
-  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, ref, watch } from 'vue'
+  import { useRoute, useRouter } from 'vue-router'
+  import { ElButton, ElMessage } from 'element-plus'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
   import { defaultResponseAdapter } from '@/utils/table/tableUtils'
@@ -82,6 +92,7 @@
   defineOptions({ name: 'PreparationItem' })
 
   const route = useRoute()
+  const router = useRouter()
   const userStore = useUserStore()
   const initialOrderId = route.query.orderId || route.query.id
   const searchForm = ref(
@@ -96,6 +107,18 @@
   const reportTitle = PREPARATION_ITEM_REPORT_TITLE
   const reportColumns = getPreparationItemReportColumns()
   const reportQueryParams = computed(() => buildPreparationItemSearchParams(searchForm.value))
+  const activeSourceSummary = computed(() => {
+    if (
+      searchForm.value.orderId === '' ||
+      searchForm.value.orderId === undefined ||
+      searchForm.value.orderId === null
+    ) {
+      return null
+    }
+    return {
+      orderId: searchForm.value.orderId
+    }
+  })
 
   const searchItems = computed(() => [
     {
@@ -247,6 +270,45 @@
     resetSearchParams(buildPreparationItemPageQueryParams(createPreparationItemSearchState(resetSeed)))
   }
 
+  function applyRouteSearch() {
+    const orderId = route.query.orderId || route.query.id
+    if (orderId === undefined || orderId === null || orderId === '') {
+      return
+    }
+    searchForm.value.orderId = Number.isFinite(Number(orderId))
+      ? Number(orderId)
+      : searchForm.value.orderId
+  }
+
+  function handleClearSourceFilter() {
+    searchForm.value.orderId = ''
+    router.replace({
+      path: route.path,
+      query: {
+        ...route.query,
+        orderId: undefined,
+        id: undefined
+      }
+    })
+    replaceSearchParams(buildPreparationItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  watch(
+    () => [route.query.orderId, route.query.id],
+    ([orderId, id]) => {
+      if (
+        (orderId === undefined || orderId === null || orderId === '') &&
+        (id === undefined || id === null || id === '')
+      ) {
+        return
+      }
+      applyRouteSearch()
+      replaceSearchParams(buildPreparationItemPageQueryParams(searchForm.value))
+      getData()
+    }
+  )
+
   const resolvePrintRecords = async (payload) => {
     if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
       return defaultResponseAdapter(await fetchGetPreparationItemMany(payload.ids)).records
@@ -297,4 +359,10 @@
         previewMeta.value?.reportStyle?.orientation || PREPARATION_ITEM_REPORT_STYLE.orientation
     })
   )
+
+  onMounted(() => {
+    applyRouteSearch()
+    replaceSearchParams(buildPreparationItemPageQueryParams(searchForm.value))
+    getData()
+  })
 </script>
diff --git a/rsf-design/src/views/orders/preparation/index.vue b/rsf-design/src/views/orders/preparation/index.vue
index d918f5b..1e162a0 100644
--- a/rsf-design/src/views/orders/preparation/index.vue
+++ b/rsf-design/src/views/orders/preparation/index.vue
@@ -58,6 +58,7 @@
 <script setup>
   import { computed, reactive, ref } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useRouter } from 'vue-router'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
   import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
@@ -93,6 +94,7 @@
   defineOptions({ name: 'Preparation' })
 
   const userStore = useUserStore()
+  const router = useRouter()
   const reportTitle = PREPARATION_REPORT_TITLE
   const searchForm = ref(createPreparationSearchState())
   const selectedRows = ref([])
@@ -230,6 +232,16 @@
         return
       }
 
+      if (action.key === 'items') {
+        router.push({
+          path: '/orders/preparation-item',
+          query: {
+            orderId: String(row.id)
+          }
+        })
+        return
+      }
+
       if (action.key === 'complete') {
         await ElMessageBox.confirm(`纭畾瀹屾垚澶囨枡鍗� ${row.code || ''} 鍚楋紵`, '瀹屾垚纭', {
           confirmButtonText: '纭畾',
diff --git a/rsf-design/src/views/orders/preparation/modules/preparation-detail-drawer.vue b/rsf-design/src/views/orders/preparation/modules/preparation-detail-drawer.vue
index 8e2c335..e5c7cf2 100644
--- a/rsf-design/src/views/orders/preparation/modules/preparation-detail-drawer.vue
+++ b/rsf-design/src/views/orders/preparation/modules/preparation-detail-drawer.vue
@@ -2,48 +2,60 @@
   <ElDrawer
     :model-value="visible"
     title="澶囨枡鍗曡鎯�"
-    size="88%"
+    size="1180px"
+    destroy-on-close
     @update:model-value="handleVisibleChange"
   >
-    <div class="flex h-full flex-col gap-4">
-      <ElDescriptions :column="4" border>
-        <ElDescriptionsItem label="澶囨枡鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鍗曟嵁鐘舵��">
-          <ElTag :type="detail.exceStatusTagType || 'info'">{{ detail.exceStatusText || '--' }}</ElTag>
-        </ElDescriptionsItem>
-        <ElDescriptionsItem label="閲婃斁鐘舵��">
-          <ElTag :type="detail.rleStatusTagType || 'info'">{{ detail.rleStatusText || '--' }}</ElTag>
-        </ElDescriptionsItem>
-        <ElDescriptionsItem label="鐗╂祦鍗曞彿">{{ detail.logisNo || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="涓氬姟鏃堕棿">{{ detail.businessTimeText || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="閿�鍞粍缁�">{{ detail.saleOrgName || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="閿�鍞憳">{{ detail.saleUserName || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="瀹㈡埛缂栫爜">{{ detail.customerId || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="瀹㈡埛鍚嶇О">{{ detail.customerName || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="浠撳簱缁勭粐">{{ detail.stockOrgName || '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="搴斿嚭鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="宸插嚭鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
-        <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
-      </ElDescriptions>
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="4" border>
+          <ElDescriptionsItem label="澶囨枡鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁鐘舵��">
+            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
+              {{ detail.exceStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="閲婃斁鐘舵��">
+            <ElTag :type="detail.rleStatusTagType || 'info'" effect="light">
+              {{ detail.rleStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂祦鍗曞彿">{{ detail.logisNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟鏃堕棿">{{ detail.businessTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閿�鍞粍缁�">{{ detail.saleOrgName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閿�鍞憳">{{ detail.saleUserName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹㈡埛缂栫爜">{{ detail.customerId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹㈡埛鍚嶇О">{{ detail.customerName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠撳簱缁勭粐">{{ detail.stockOrgName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴斿嚭鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸插嚭鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
 
-      <div class="flex items-center justify-between">
-        <div class="text-sm text-[var(--art-gray-600)]">鏄庣粏娓呭崟锛堢墿鏂欑紪鐮�/鐗╂枡鍚嶇О/渚涘簲鍟嗘壒娆★級</div>
-        <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+        <div class="space-y-3">
+          <div class="flex items-center justify-between">
+            <div class="text-sm font-medium text-[var(--art-gray-900)]">鍗曟嵁鏄庣粏</div>
+            <div class="flex items-center gap-3">
+              <ElTag effect="plain">鍏� {{ data.length }} 鏉�</ElTag>
+              <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+            </div>
+          </div>
+
+          <ArtTable
+            :loading="loading"
+            :data="data"
+            :columns="columns"
+            :pagination="pagination"
+            @pagination:size-change="$emit('size-change', $event)"
+            @pagination:current-change="$emit('current-change', $event)"
+          />
+        </div>
       </div>
-
-      <ArtTable
-        :loading="loading"
-        :data="data"
-        :columns="columns"
-        :pagination="pagination"
-        @pagination:size-change="$emit('size-change', $event)"
-        @pagination:current-change="$emit('current-change', $event)"
-      />
-    </div>
+    </ElScrollbar>
   </ElDrawer>
 </template>
 
diff --git a/rsf-design/src/views/orders/preparation/preparationPage.helpers.js b/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
index 5a42752..6278cca 100644
--- a/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
+++ b/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
@@ -186,6 +186,7 @@
   const normalizedRow = normalizePreparationRow(row)
   return [
     { key: 'view', label: '鏌ョ湅璇︽儏', icon: 'ri:eye-line' },
+    { key: 'items', label: '鏄庣粏', icon: 'ri:list-check-3' },
     { key: 'print', label: '鎵撳嵃', icon: 'ri:printer-line' },
     {
       key: 'complete',
diff --git a/rsf-design/src/views/orders/purchase-item/index.vue b/rsf-design/src/views/orders/purchase-item/index.vue
index 8ded304..f120beb 100644
--- a/rsf-design/src/views/orders/purchase-item/index.vue
+++ b/rsf-design/src/views/orders/purchase-item/index.vue
@@ -1,5 +1,15 @@
 <template>
   <div class="purchase-item-page art-full-height">
+    <ElCard v-if="activeSourceSummary" class="mb-3">
+      <div class="flex items-center justify-between gap-3">
+        <div class="flex items-center gap-2 text-sm text-[var(--art-text-gray-600)]">
+          <span class="font-medium text-[var(--art-text-gray-900)]">褰撳墠鏉ユ簮</span>
+          <span>PO鍗旾D锛歿{ activeSourceSummary.purchaseId }}</span>
+        </div>
+        <ElButton link type="primary" @click="handleClearSourceFilter">鏌ョ湅鍏ㄩ儴</ElButton>
+      </div>
+    </ElCard>
+
     <ArtSearchBar
       v-model="searchForm"
       :items="searchItems"
@@ -48,8 +58,9 @@
 </template>
 
 <script setup>
-  import { computed, onMounted, ref } from 'vue'
-  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, ref, watch } from 'vue'
+  import { ElButton, ElMessage } from 'element-plus'
+  import { useRoute, useRouter } from 'vue-router'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
   import { defaultResponseAdapter } from '@/utils/table/tableUtils'
@@ -79,12 +90,23 @@
   defineOptions({ name: 'PurchaseItem' })
 
   const userStore = useUserStore()
+  const route = useRoute()
+  const router = useRouter()
   const reportTitle = PURCHASE_ITEM_REPORT_TITLE
   const searchForm = ref(createPurchaseItemSearchState())
   const selectedRows = ref([])
   const detailDrawerVisible = ref(false)
   const detailLoading = ref(false)
   const detailData = ref({})
+
+  const activeSourceSummary = computed(() => {
+    if (searchForm.value.purchaseId === '' || searchForm.value.purchaseId === undefined || searchForm.value.purchaseId === null) {
+      return null
+    }
+    return {
+      purchaseId: searchForm.value.purchaseId
+    }
+  })
 
   const searchItems = computed(() => [
     {
@@ -306,6 +328,29 @@
     resetSearchParams()
   }
 
+  function applyRouteSearch() {
+    const purchaseId = route.query.purchaseId
+    if (purchaseId === undefined || purchaseId === null || purchaseId === '') {
+      return
+    }
+    searchForm.value.purchaseId = Number.isFinite(Number(purchaseId))
+      ? Number(purchaseId)
+      : searchForm.value.purchaseId
+  }
+
+  function handleClearSourceFilter() {
+    searchForm.value.purchaseId = ''
+    router.replace({
+      path: route.path,
+      query: {
+        ...route.query,
+        purchaseId: undefined
+      }
+    })
+    replaceSearchParams(buildPurchaseItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
   const buildPreviewMeta = (rows) => {
     const now = new Date()
     return {
@@ -364,7 +409,21 @@
     })
   )
 
+  watch(
+    () => route.query.purchaseId,
+    (value) => {
+      if (value === undefined || value === null || value === '') {
+        return
+      }
+      applyRouteSearch()
+      replaceSearchParams(buildPurchaseItemPageQueryParams(searchForm.value))
+      getData()
+    }
+  )
+
   onMounted(() => {
+    applyRouteSearch()
+    replaceSearchParams(buildPurchaseItemPageQueryParams(searchForm.value))
     getData()
   })
 </script>
diff --git a/rsf-design/src/views/orders/purchase/index.vue b/rsf-design/src/views/orders/purchase/index.vue
index 35b855d..c3c6463 100644
--- a/rsf-design/src/views/orders/purchase/index.vue
+++ b/rsf-design/src/views/orders/purchase/index.vue
@@ -74,6 +74,7 @@
 
 <script setup>
   import { computed, onMounted, ref } from 'vue'
+  import { useRouter } from 'vue-router'
   import { ElMessage } from 'element-plus'
   import { useUserStore } from '@/store/modules/user'
   import { useAuth } from '@/hooks/core/useAuth'
@@ -119,6 +120,7 @@
 
   const { hasAuth } = useAuth()
   const userStore = useUserStore()
+  const router = useRouter()
 
   const reportTitle = PURCHASE_REPORT_TITLE
   const searchForm = ref(createPurchaseSearchState())
@@ -276,6 +278,18 @@
     }
   }
 
+  function openPurchaseItems(row) {
+    if (!row?.id) {
+      return
+    }
+    router.push({
+      path: '/orders/purchase-item',
+      query: {
+        purchaseId: String(row.id)
+      }
+    })
+  }
+
   const {
     columns,
     columnChecks,
@@ -299,6 +313,7 @@
       columnsFactory: () =>
         createPurchaseTableColumns({
           handleView: openDetail,
+          handleViewItems: openPurchaseItems,
           handleEdit: hasAuth('update') ? openEditDialog : null,
           handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
           canEdit: hasAuth('update'),
diff --git a/rsf-design/src/views/orders/purchase/purchaseTable.columns.js b/rsf-design/src/views/orders/purchase/purchaseTable.columns.js
index a279aff..2d81a20 100644
--- a/rsf-design/src/views/orders/purchase/purchaseTable.columns.js
+++ b/rsf-design/src/views/orders/purchase/purchaseTable.columns.js
@@ -5,6 +5,7 @@
 
 export function createPurchaseTableColumns({
   handleView,
+  handleViewItems,
   handleEdit,
   handleDelete,
   canEdit = true,
@@ -152,6 +153,14 @@
       formatter: (row) => {
         const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
 
+        if (handleViewItems) {
+          operations.push({
+            key: 'items',
+            label: '鏄庣粏',
+            icon: 'ri:list-check-3'
+          })
+        }
+
         if (canEdit && handleEdit) {
           operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
         }
@@ -169,6 +178,7 @@
           list: operations,
           onClick: (item) => {
             if (item.key === 'view') handleView?.(row)
+            if (item.key === 'items') handleViewItems?.(row)
             if (item.key === 'edit') handleEdit?.(row)
             if (item.key === 'delete') handleDelete?.(row)
           }
diff --git a/rsf-design/src/views/orders/transfer-item/index.vue b/rsf-design/src/views/orders/transfer-item/index.vue
index 47bff6c..4981645 100644
--- a/rsf-design/src/views/orders/transfer-item/index.vue
+++ b/rsf-design/src/views/orders/transfer-item/index.vue
@@ -1,5 +1,15 @@
 <template>
   <div class="transfer-item-page art-full-height">
+    <ElCard v-if="activeSourceSummary" class="mb-3">
+      <div class="flex items-center justify-between gap-3">
+        <div class="flex items-center gap-2 text-sm text-[var(--art-text-gray-600)]">
+          <span class="font-medium text-[var(--art-text-gray-900)]">褰撳墠鏉ユ簮</span>
+          <span>璋冩嫧鍗旾D锛歿{ activeSourceSummary.transferId }}</span>
+        </div>
+        <ElButton link type="primary" @click="handleClearSourceFilter">鏌ョ湅鍏ㄩ儴</ElButton>
+      </div>
+    </ElCard>
+
     <ArtSearchBar
       v-model="searchForm"
       :items="searchItems"
@@ -48,8 +58,9 @@
 </template>
 
 <script setup>
-  import { computed, ref } from 'vue'
-  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, ref, watch } from 'vue'
+  import { ElButton, ElMessage } from 'element-plus'
+  import { useRoute, useRouter } from 'vue-router'
   import { useUserStore } from '@/store/modules/user'
   import { useTable } from '@/hooks/core/useTable'
   import { defaultResponseAdapter } from '@/utils/table/tableUtils'
@@ -80,12 +91,23 @@
   defineOptions({ name: 'TransferItem' })
 
   const userStore = useUserStore()
+  const route = useRoute()
+  const router = useRouter()
   const reportTitle = TRANSFER_ITEM_REPORT_TITLE
   const searchForm = ref(createTransferItemSearchState())
   const selectedRows = ref([])
   const detailDrawerVisible = ref(false)
   const detailLoading = ref(false)
   const detailData = ref({})
+
+  const activeSourceSummary = computed(() => {
+    if (searchForm.value.transferId === '' || searchForm.value.transferId === undefined || searchForm.value.transferId === null) {
+      return null
+    }
+    return {
+      transferId: searchForm.value.transferId
+    }
+  })
 
   const searchItems = computed(() => [
     {
@@ -372,6 +394,29 @@
     resetSearchParams()
   }
 
+  function applyRouteSearch() {
+    const transferId = route.query.transferId
+    if (transferId === undefined || transferId === null || transferId === '') {
+      return
+    }
+    searchForm.value.transferId = Number.isFinite(Number(transferId))
+      ? Number(transferId)
+      : searchForm.value.transferId
+  }
+
+  function handleClearSourceFilter() {
+    searchForm.value.transferId = ''
+    router.replace({
+      path: route.path,
+      query: {
+        ...route.query,
+        transferId: undefined
+      }
+    })
+    replaceSearchParams(buildTransferItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
   const resolvePrintRecords = async (payload) => {
     if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
       return defaultResponseAdapter(await fetchTransferItemMany(payload.ids)).records
@@ -425,5 +470,22 @@
       orientation: previewMeta.value?.reportStyle?.orientation || TRANSFER_ITEM_REPORT_STYLE.orientation
     })
   )
-</script>
 
+  watch(
+    () => route.query.transferId,
+    (value) => {
+      if (value === undefined || value === null || value === '') {
+        return
+      }
+      applyRouteSearch()
+      replaceSearchParams(buildTransferItemPageQueryParams(searchForm.value))
+      getData()
+    }
+  )
+
+  onMounted(() => {
+    applyRouteSearch()
+    replaceSearchParams(buildTransferItemPageQueryParams(searchForm.value))
+    getData()
+  })
+</script>
diff --git a/rsf-design/src/views/orders/transfer/index.vue b/rsf-design/src/views/orders/transfer/index.vue
index f85b2d4..8a42133 100644
--- a/rsf-design/src/views/orders/transfer/index.vue
+++ b/rsf-design/src/views/orders/transfer/index.vue
@@ -77,6 +77,7 @@
 
 <script setup>
   import { computed, onMounted, reactive, ref } from 'vue'
+  import { useRouter } from 'vue-router'
   import { ElMessage, ElMessageBox } from 'element-plus'
   import { useAuth } from '@/hooks/core/useAuth'
   import { useUserStore } from '@/store/modules/user'
@@ -129,6 +130,7 @@
 
   const { hasAuth } = useAuth()
   const userStore = useUserStore()
+  const router = useRouter()
 
   const reportTitle = TRANSFER_REPORT_TITLE
   const searchForm = ref(createTransferSearchState())
@@ -296,6 +298,16 @@
       await openDetail(row)
       return
     }
+    if (action?.key === 'items') {
+      if (!row?.id) return
+      router.push({
+        path: '/orders/transfer-item',
+        query: {
+          transferId: String(row.id)
+        }
+      })
+      return
+    }
     if (action?.key === 'edit') {
       if (!canUpdate.value) return
       await openEditDialog(row)
diff --git a/rsf-design/src/views/orders/transfer/transferPage.helpers.js b/rsf-design/src/views/orders/transfer/transferPage.helpers.js
index 7e06d2c..0d11a6a 100644
--- a/rsf-design/src/views/orders/transfer/transferPage.helpers.js
+++ b/rsf-design/src/views/orders/transfer/transferPage.helpers.js
@@ -306,6 +306,7 @@
   const normalizedRow = normalizeTransferRow(row)
   const actions = [
     { key: 'view', label: '鏌ョ湅璇︽儏', icon: 'ri:eye-line' },
+    { key: 'items', label: '鏄庣粏', icon: 'ri:list-check-3' },
     { key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' }
   ]
   if (Number(normalizedRow.exceStatus) === 0) {
diff --git a/rsf-design/src/views/system/menu/menuTable.columns.js b/rsf-design/src/views/system/menu/menuTable.columns.js
index a27b767..e98239c 100644
--- a/rsf-design/src/views/system/menu/menuTable.columns.js
+++ b/rsf-design/src/views/system/menu/menuTable.columns.js
@@ -62,6 +62,16 @@
       }
     },
     {
+      prop: 'component',
+      label: '缁勪欢鏍囪瘑',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => {
+        if (row.meta?.isAuthButton) return ''
+        return row.component || ''
+      }
+    },
+    {
       prop: 'authority',
       label: '鏉冮檺鏍囪瘑',
       minWidth: 180,
@@ -77,6 +87,12 @@
       prop: 'sort',
       label: '鎺掑簭',
       width: 90
+    },
+    {
+      prop: 'id',
+      label: 'ID',
+      width: 96,
+      align: 'center'
     },
     {
       prop: 'status',
@@ -98,9 +114,9 @@
       prop: 'operation',
       label: '鎿嶄綔',
       width: 180,
-      align: 'right',
+      align: 'center',
       formatter: (row) => {
-        const buttonStyle = { class: 'flex justify-end' }
+        const buttonStyle = { class: 'flex justify-center' }
         if (row.meta?.isAuthButton) {
           return h('div', buttonStyle, [
             h(ArtButtonTable, {
diff --git a/rsf-framework/src/main/java/com/vincent/rsf/framework/generators/RsfDesignGenerator.java b/rsf-framework/src/main/java/com/vincent/rsf/framework/generators/RsfDesignGenerator.java
index c805452..3d428e3 100644
--- a/rsf-framework/src/main/java/com/vincent/rsf/framework/generators/RsfDesignGenerator.java
+++ b/rsf-framework/src/main/java/com/vincent/rsf/framework/generators/RsfDesignGenerator.java
@@ -186,7 +186,7 @@
         writeTemplate("TableColumns", pageDirectory, simpleEntityName + "Table.columns.js");
         writeTemplate("Search", modulesDirectory, kebabEntityName + "-search.vue");
         writeTemplate("EditDialog", modulesDirectory, kebabEntityName + "-edit-dialog.vue");
-        writeTemplate("Api", resolveFrontendApiDirectory(), normalizedFrontendApiModule + ".js");
+        writeTemplate("Api", resolveFrontendApiDirectory(), resolveFrontendApiFileName());
     }
 
     private String resolveControllerDirectory() {
@@ -207,6 +207,14 @@
             return directory;
         }
         return directory + normalizedFrontendApiModule.substring(0, index + 1);
+    }
+
+    private String resolveFrontendApiFileName() {
+        int index = normalizedFrontendApiModule.lastIndexOf('/');
+        if (index < 0) {
+            return normalizedFrontendApiModule + ".js";
+        }
+        return normalizedFrontendApiModule.substring(index + 1) + ".js";
     }
 
     private void writeTemplate(String templateName, String directory, String fileName) throws IOException {
@@ -677,14 +685,6 @@
                         .append("StatusMeta(row.statusBool ?? row.status)),\n");
                 continue;
             }
-            if (isNumericColumn(column)) {
-                sb.append("    createNumberColumn('")
-                        .append(column.getHumpName())
-                        .append("', '")
-                        .append(escapeJs(resolveFieldLabel(column)))
-                        .append("', 120),\n");
-                continue;
-            }
             if (isDisplayTextColumn(column)) {
                 sb.append("    createTextColumn('")
                         .append(column.getHumpName())
@@ -695,6 +695,14 @@
                         .append("),\n");
                 continue;
             }
+            if (isNumericColumn(column)) {
+                sb.append("    createNumberColumn('")
+                        .append(column.getHumpName())
+                        .append("', '")
+                        .append(escapeJs(resolveFieldLabel(column)))
+                        .append("', 120),\n");
+                continue;
+            }
             sb.append("    createTextColumn('")
                     .append(column.getHumpName())
                     .append("', '")
@@ -703,7 +711,7 @@
                     .append(resolveTextColumnWidth(column))
                     .append("),\n");
         }
-        return trimTrailingLineBreak(sb);
+        return trimTrailingLineBreakKeepComma(sb);
     }
 
     private String buildExportRowContent() {
@@ -898,6 +906,16 @@
         return sb.toString();
     }
 
+    private String trimTrailingLineBreakKeepComma(StringBuilder sb) {
+        if (sb.length() == 0) {
+            return "";
+        }
+        while (sb.length() > 0 && (sb.charAt(sb.length() - 1) == '\n' || sb.charAt(sb.length() - 1) == '\r')) {
+            sb.deleteCharAt(sb.length() - 1);
+        }
+        return sb.toString();
+    }
+
     private String safeText(String value) {
         return value == null ? "" : value.trim();
     }
diff --git a/rsf-server/src/test/java/com/vincent/rsf/server/common/RsfDesignGeneratorRegressionTest.java b/rsf-server/src/test/java/com/vincent/rsf/server/common/RsfDesignGeneratorRegressionTest.java
new file mode 100644
index 0000000..14cf07d
--- /dev/null
+++ b/rsf-server/src/test/java/com/vincent/rsf/server/common/RsfDesignGeneratorRegressionTest.java
@@ -0,0 +1,503 @@
+package com.vincent.rsf.server.common;
+
+import com.vincent.rsf.framework.generators.ReactGenerator;
+import com.vincent.rsf.framework.generators.RsfDesignGenerator;
+import com.vincent.rsf.framework.generators.constant.SqlOsType;
+import com.vincent.rsf.framework.generators.domain.Column;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RsfDesignGeneratorRegressionTest {
+
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void buildFrontendArtifactsKeepsDisplayColumnsCommaAndNestedApiPath() throws Exception {
+        RsfDesignGenerator generator = createGenerator(
+                "sys_test",
+                "绯荤粺娴嬭瘯琛�",
+                "test",
+                "Test",
+                "system/test",
+                "system/test",
+                buildColumns()
+        );
+
+        invokePrivate(generator, "buildFrontendArtifacts");
+
+        Path tableColumnsFile = tempDir.resolve("src/views/system/test/testTable.columns.js");
+        Path pageHelpersFile = tempDir.resolve("src/views/system/test/testPage.helpers.js");
+        Path apiFile = tempDir.resolve("src/api/system/test.js");
+
+        String tableColumnsContent = Files.readString(tableColumnsFile, StandardCharsets.UTF_8);
+        String pageHelpersContent = Files.readString(pageHelpersFile, StandardCharsets.UTF_8);
+
+        assertAll(
+                () -> assertTrue(Files.exists(apiFile), "宓屽 api 妯″潡璺緞搴旂敓鎴愬埌 src/api/system/test.js"),
+                () -> assertTrue(tableColumnsContent.contains("createTextColumn('levelText', '绛夌骇', 160),"), "鏁板�兼灇涓惧垪搴旀樉绀轰负 levelText"),
+                () -> assertTrue(tableColumnsContent.contains("createTextColumn('roleIdText', '瑙掕壊', 160),"), "鏁板�煎閿垪搴旀樉绀轰负 roleIdText"),
+                () -> assertFalse(tableColumnsContent.contains("createNumberColumn('level',"), "鏁板�兼灇涓惧垪涓嶅簲鍐嶈蛋鍘熷鏁板瓧鍒�"),
+                () -> assertFalse(tableColumnsContent.contains("createNumberColumn('roleId',"), "鏁板�煎閿垪涓嶅簲鍐嶈蛋鍘熷鏁板瓧鍒�"),
+                () -> assertTrue(
+                        tableColumnsContent.contains("createTextColumn('memo', '澶囨敞', 220),\r\n    {")
+                                || tableColumnsContent.contains("createTextColumn('memo', '澶囨敞', 220),\n    {"),
+                        "鏈�鍚庝竴涓笟鍔″垪鍜屾搷浣滃垪涔嬮棿搴斾繚鐣欓�楀彿"
+                ),
+                () -> assertTrue(pageHelpersContent.contains("{ source: 'levelText', label: '绛夌骇' }"), "鎶ヨ〃鍒楀簲鍖呭惈 levelText"),
+                () -> assertTrue(pageHelpersContent.contains("{ source: 'roleIdText', label: '瑙掕壊' }"), "鎶ヨ〃鍒楀簲鍖呭惈 roleIdText")
+        );
+    }
+
+    @Test
+    void buildFrontendArtifactsKeepsBooleanAndStatusTextMappingForPrintExport() throws Exception {
+        RsfDesignGenerator generator = createGenerator(
+                "sys_switch",
+                "绯荤粺寮�鍏宠〃",
+                "switchItem",
+                "SwitchItem",
+                "system/switch-item",
+                "system/switch-item",
+                buildBooleanColumns()
+        );
+
+        invokePrivate(generator, "buildFrontendArtifacts");
+
+        Path tableColumnsFile = tempDir.resolve("src/views/system/switch-item/switchItemTable.columns.js");
+        Path pageHelpersFile = tempDir.resolve("src/views/system/switch-item/switchItemPage.helpers.js");
+
+        String tableColumnsContent = Files.readString(tableColumnsFile, StandardCharsets.UTF_8);
+        String pageHelpersContent = Files.readString(pageHelpersFile, StandardCharsets.UTF_8);
+
+        assertAll(
+                () -> assertTrue(tableColumnsContent.contains("createTextColumn('enabledText', '鏄惁鍚敤', 160),"), "甯冨皵瀛楁鍒楄〃鍒楀簲鏄剧ず涓� enabledText"),
+                () -> assertTrue(tableColumnsContent.contains("createTagColumn('status', '鐘舵��', 120"), "status 瀛楁搴旂户缁蛋鏍囩鍒�"),
+                () -> assertTrue(pageHelpersContent.contains("enabledText: formatBooleanText(toOptionalBoolean(record.enabled))"), "甯冨皵瀛楁搴旂敓鎴� formatBooleanText 鏄犲皠"),
+                () -> assertTrue(pageHelpersContent.contains("{ source: 'enabledText', label: '鏄惁鍚敤' }"), "甯冨皵瀛楁鎶ヨ〃鍒楀簲浣跨敤 enabledText"),
+                () -> assertTrue(pageHelpersContent.contains("{ source: 'statusText', label: '鐘舵��' }"), "status 鎶ヨ〃鍒楀簲浣跨敤 statusText"),
+                () -> assertTrue(pageHelpersContent.contains("status: 'statusText'"), "status 瀵煎嚭鍒楀埆鍚嶅簲鏄犲皠鍒� statusText")
+        );
+    }
+
+    @Test
+    void buildFrontendArtifactsSupportsDeepNestedViewAndApiModules() throws Exception {
+        RsfDesignGenerator generator = createGenerator(
+                "sys_deep_case",
+                "娣卞眰鐩綍娴嬭瘯琛�",
+                "deepCase",
+                "DeepCase",
+                "basic-info/warehouse/deep-case",
+                "system/admin/deep-case",
+                buildColumns()
+        );
+
+        invokePrivate(generator, "buildFrontendArtifacts");
+
+        assertAll(
+                () -> assertTrue(Files.exists(tempDir.resolve("src/views/basic-info/warehouse/deep-case/index.vue")), "娣卞眰 view 璺緞搴旂敓鎴� index.vue"),
+                () -> assertTrue(Files.exists(tempDir.resolve("src/views/basic-info/warehouse/deep-case/deepCasePage.helpers.js")), "娣卞眰 view 璺緞搴旂敓鎴� helpers"),
+                () -> assertTrue(Files.exists(tempDir.resolve("src/views/basic-info/warehouse/deep-case/deepCaseTable.columns.js")), "娣卞眰 view 璺緞搴旂敓鎴愬垪鏂囦欢"),
+                () -> assertTrue(Files.exists(tempDir.resolve("src/views/basic-info/warehouse/deep-case/modules/deep-case-search.vue")), "娣卞眰 view 璺緞搴旂敓鎴� search 缁勪欢"),
+                () -> assertTrue(Files.exists(tempDir.resolve("src/views/basic-info/warehouse/deep-case/modules/deep-case-edit-dialog.vue")), "娣卞眰 view 璺緞搴旂敓鎴� edit dialog"),
+                () -> assertTrue(Files.exists(tempDir.resolve("src/api/system/admin/deep-case.js")), "娣卞眰 api 妯″潡璺緞搴旂敓鎴愬埌姝g‘鐩綍")
+        );
+    }
+
+    @Test
+    void buildFrontendArtifactsKeepsFormDefaultsPayloadAndRulesAligned() throws Exception {
+        RsfDesignGenerator generator = createGenerator(
+                "sys_default_case",
+                "榛樿鍊兼祴璇曡〃",
+                "defaultCase",
+                "DefaultCase",
+                "system/default-case",
+                "system/default-case",
+                buildDefaultCaseColumns()
+        );
+
+        invokePrivate(generator, "buildFrontendArtifacts");
+
+        Path pageHelpersFile = tempDir.resolve("src/views/system/default-case/defaultCasePage.helpers.js");
+        Path editDialogFile = tempDir.resolve("src/views/system/default-case/modules/default-case-edit-dialog.vue");
+
+        String pageHelpersContent = Files.readString(pageHelpersFile, StandardCharsets.UTF_8);
+        String editDialogContent = Files.readString(editDialogFile, StandardCharsets.UTF_8);
+
+        assertAll(
+                () -> assertTrue(pageHelpersContent.contains("status: 1,"), "鐘舵�佸瓧娈佃〃鍗曢粯璁ゅ�煎簲浣跨敤棣栦釜鏋氫妇鍊�"),
+                () -> assertTrue(pageHelpersContent.contains("level: void 0,"), "鏁板瓧瀛楁琛ㄥ崟榛樿鍊煎簲涓� void 0"),
+                () -> assertTrue(pageHelpersContent.contains("enabled: void 0,"), "甯冨皵瀛楁琛ㄥ崟榛樿鍊煎簲涓� void 0"),
+                () -> assertTrue(pageHelpersContent.contains("status: hasValue(record.status) ? toOptionalNumber(record.status) : 1,"), "鐘舵�佸瓧娈靛脊绐楁ā鍨嬪簲淇濈暀榛樿鍥為��鍊�"),
+                () -> assertTrue(pageHelpersContent.contains("...buildNumberField('status', formData.status),"), "鐘舵�佸瓧娈典繚瀛� payload 搴旇蛋鏁板瓧瀛楁鏋勯��"),
+                () -> assertTrue(pageHelpersContent.contains("...(hasValue(formData.enabled) ? { enabled: toOptionalBoolean(formData.enabled) } : {}),"), "甯冨皵瀛楁淇濆瓨 payload 搴旇蛋甯冨皵杞崲"),
+                () -> assertTrue(editDialogContent.contains("createInputFormItem('鍚嶇О', 'name', '璇疯緭鍏ュ悕绉�'),"), "鏅�氭枃鏈瓧娈靛簲鐢熸垚杈撳叆妗�"),
+                () -> assertTrue(editDialogContent.contains("createInputFormItem('绛夌骇', 'level', '璇疯緭鍏ョ瓑绾�', { type: 'number' }),"), "鏁板瓧瀛楁搴旂敓鎴� number 杈撳叆妗�"),
+                () -> assertTrue(editDialogContent.contains("createSelectFormItem('鏄惁鍚敤', 'enabled', '璇烽�夋嫨鏄惁鍚敤', getDefaultCaseFieldOptions('enabled')),") , "甯冨皵瀛楁搴旂敓鎴愪笅鎷夐」"),
+                () -> assertTrue(editDialogContent.contains("createSelectFormItem('鐘舵��', 'status', '璇烽�夋嫨鐘舵��', getDefaultCaseFieldOptions('status')),") , "鏋氫妇鐘舵�佸瓧娈靛簲鐢熸垚涓嬫媺椤�"),
+                () -> assertTrue(editDialogContent.contains("name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }],"), "鏂囨湰蹇呭~瑙勫垯搴斾娇鐢� blur/璇疯緭鍏�"),
+                () -> assertTrue(editDialogContent.contains("status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]"), "閫夋嫨绫诲繀濉鍒欏簲浣跨敤 change/璇烽�夋嫨")
+        );
+    }
+
+    @Test
+    void buildFrontendArtifactsKeepsSearchItemsAndQueryParamsAligned() throws Exception {
+        RsfDesignGenerator generator = createGenerator(
+                "sys_search_case",
+                "鎼滅储娴嬭瘯琛�",
+                "searchCase",
+                "SearchCase",
+                "system/search-case",
+                "system/search-case",
+                buildSearchCaseColumns()
+        );
+
+        invokePrivate(generator, "buildFrontendArtifacts");
+
+        Path pageHelpersFile = tempDir.resolve("src/views/system/search-case/searchCasePage.helpers.js");
+        Path searchFile = tempDir.resolve("src/views/system/search-case/modules/search-case-search.vue");
+
+        String pageHelpersContent = Files.readString(pageHelpersFile, StandardCharsets.UTF_8);
+        String searchContent = Files.readString(searchFile, StandardCharsets.UTF_8);
+
+        assertAll(
+                () -> assertTrue(pageHelpersContent.contains("keyword: '',"), "鏂囨湰鎼滅储瀛楁搴斿嚭鐜板湪鎼滅储鐘舵�佷腑"),
+                () -> assertTrue(pageHelpersContent.contains("level: '',"), "鏁板瓧鎼滅储瀛楁搴斿嚭鐜板湪鎼滅储鐘舵�佷腑"),
+                () -> assertTrue(pageHelpersContent.contains("enabled: '',"), "甯冨皵鎼滅储瀛楁搴斿嚭鐜板湪鎼滅储鐘舵�佷腑"),
+                () -> assertTrue(pageHelpersContent.contains("status: ''"), "鏋氫妇鎼滅储瀛楁搴斿嚭鐜板湪鎼滅储鐘舵�佷腑"),
+                () -> assertFalse(pageHelpersContent.contains("createTime: '',"), "鏃ユ湡瀛楁涓嶅簲杩涘叆鎼滅储鐘舵��"),
+                () -> assertFalse(pageHelpersContent.contains("deleted: '',"), "鎵樼瀛楁涓嶅簲杩涘叆鎼滅储鐘舵��"),
+                () -> assertTrue(pageHelpersContent.contains("keyword: normalizeText(params.keyword),"), "鏂囨湰鎼滅储鍙傛暟搴旇蛋 normalizeText"),
+                () -> assertTrue(pageHelpersContent.contains("level: toOptionalNumber(params.level),"), "鏁板瓧鎼滅储鍙傛暟搴旇蛋 toOptionalNumber"),
+                () -> assertTrue(pageHelpersContent.contains("enabled: toOptionalBoolean(params.enabled),"), "甯冨皵鎼滅储鍙傛暟搴旇蛋 toOptionalBoolean"),
+                () -> assertTrue(pageHelpersContent.contains("status: toOptionalNumber(params.status)"), "鏋氫妇鏁板瓧鎼滅储鍙傛暟搴旇蛋 toOptionalNumber"),
+                () -> assertTrue(pageHelpersContent.contains("current: params.current || 1,"), "鍒嗛〉鍙傛暟搴旂粺涓� current 榛樿鍊�"),
+                () -> assertTrue(pageHelpersContent.contains("pageSize: params.pageSize || params.size || 20,"), "鍒嗛〉鍙傛暟搴斿吋瀹� size/pageSize"),
+                () -> assertTrue(searchContent.contains("createInputSearchItem('鍏抽敭瀛�', 'condition', '璇疯緭鍏ユ悳绱㈡祴璇曡〃鍏抽敭瀛�'),"), "鎼滅储鏍忓簲濮嬬粓鍖呭惈 condition 鍏抽敭瀛楁悳绱�"),
+                () -> assertTrue(searchContent.contains("createInputSearchItem('鍏抽敭瀛楀瓧娈�', 'keyword', '璇疯緭鍏ュ叧閿瓧瀛楁'),"), "鏂囨湰鎼滅储瀛楁搴旂敓鎴愯緭鍏ユ"),
+                () -> assertTrue(searchContent.contains("createInputSearchItem('绛夌骇', 'level', '璇疯緭鍏ョ瓑绾�'),"), "鏁板瓧鎼滅储瀛楁褰撳墠搴旂敓鎴愯緭鍏ユ"),
+                () -> assertTrue(searchContent.contains("createSelectSearchItem('鏄惁鍚敤', 'enabled', '璇烽�夋嫨鏄惁鍚敤', getSearchCaseFieldOptions('enabled')),") , "甯冨皵鎼滅储瀛楁搴旂敓鎴愪笅鎷夐」"),
+                () -> assertTrue(searchContent.contains("createSelectSearchItem('鐘舵��', 'status', '璇烽�夋嫨鐘舵��', getSearchCaseFieldOptions('status'))") , "鏋氫妇鎼滅储瀛楁搴旂敓鎴愪笅鎷夐」"),
+                () -> assertFalse(searchContent.contains("createTime"), "鏃ユ湡瀛楁涓嶅簲鐢熸垚鎼滅储椤�"),
+                () -> assertFalse(searchContent.contains("deleted"), "鎵樼瀛楁涓嶅簲鐢熸垚鎼滅储椤�")
+        );
+    }
+
+    @Test
+    void writeControllerTemplateKeepsCrudAndExportContractAligned() throws Exception {
+        RsfDesignGenerator generator = createGenerator(
+                "sys_controller_case",
+                "鎺у埗鍣ㄦ祴璇曡〃",
+                "controllerCase",
+                "ControllerCase",
+                "system/controller-case",
+                "system/controller-case",
+                buildControllerCaseColumns()
+        );
+        generator.backendPrefixPath = tempDir.toString().replace("\\", "/");
+
+        String controllerDirectory = (String) invokePrivate(generator, "resolveControllerDirectory");
+        invokePrivate(
+                generator,
+                "writeTemplate",
+                new Class<?>[]{String.class, String.class, String.class},
+                new Object[]{"Controller", controllerDirectory, "ControllerCaseController.java"}
+        );
+
+        Path controllerFile = tempDir.resolve("src/main/java/com/vincent/rsf/server/system/controller/ControllerCaseController.java");
+        String controllerContent = Files.readString(controllerFile, StandardCharsets.UTF_8);
+
+        assertAll(
+                () -> assertTrue(controllerContent.contains("public class ControllerCaseController extends BaseController"), "鎺у埗鍣ㄧ被鍚嶅簲鍜屽疄浣撲繚鎸佷竴鑷�"),
+                () -> assertTrue(controllerContent.contains("private ControllerCaseService controllerCaseService;"), "鎺у埗鍣ㄥ簲娉ㄥ叆 service"),
+                () -> assertTrue(controllerContent.contains("private ListExportService listExportService;"), "鎺у埗鍣ㄥ簲娉ㄥ叆瀵煎嚭鏈嶅姟"),
+                () -> assertTrue(controllerContent.contains("@PostMapping(\"/controllerCase/page\")"), "鍒嗛〉鎺ュ彛璺緞搴斾繚鎸� page"),
+                () -> assertTrue(controllerContent.contains("@PostMapping(\"/controllerCase/save\")"), "鏂板鎺ュ彛璺緞搴斾繚鎸� save"),
+                () -> assertTrue(controllerContent.contains("@PostMapping(\"/controllerCase/update\")"), "鏇存柊鎺ュ彛璺緞搴斾繚鎸� update"),
+                () -> assertTrue(controllerContent.contains("@PostMapping(\"/controllerCase/remove/{ids}\")"), "鍒犻櫎鎺ュ彛璺緞搴斾繚鎸� remove"),
+                () -> assertTrue(controllerContent.contains("@PostMapping(\"/controllerCase/query\")"), "鏌ヨ鎺ュ彛璺緞搴斾繚鎸� query"),
+                () -> assertTrue(controllerContent.contains("@PostMapping(\"/controllerCase/export\")"), "瀵煎嚭鎺ュ彛璺緞搴斾繚鎸� export"),
+                () -> assertTrue(controllerContent.contains("listExportService.export(map, exportMap -> buildParam(exportMap, BaseParam.class), controllerCaseExportHandler, response);"), "瀵煎嚭鎺ュ彛搴旂户缁蛋 ListExportService"),
+                () -> assertTrue(controllerContent.contains("controllerCase.setCreateBy(getLoginUserId());"), "鏂板鍒濆鍖栧簲琛� createBy"),
+                () -> assertTrue(controllerContent.contains("controllerCase.setCreateTime(new Date());"), "鏂板鍒濆鍖栧簲琛� createTime"),
+                () -> assertTrue(controllerContent.contains("controllerCase.setUpdateBy(getLoginUserId());"), "鏂板鍜屾洿鏂板垵濮嬪寲閮藉簲琛� updateBy"),
+                () -> assertTrue(controllerContent.contains("controllerCase.setUpdateTime(new Date());"), "鏂板鍜屾洿鏂板垵濮嬪寲閮藉簲琛� updateTime"),
+                () -> assertTrue(controllerContent.contains("row.put(\"statusText\", record.getStatus$());"), "瀵煎嚭琛屽簲杈撳嚭 statusText"),
+                () -> assertTrue(controllerContent.contains("row.put(\"ownerIdText\", record.getOwnerId$());"), "瀵煎嚭琛屽簲杈撳嚭澶栭敭鏂囨湰瀛楁"),
+                () -> assertTrue(controllerContent.contains("wrapper.like(ControllerCase::getName, condition);"), "query 鎺ュ彛搴旀寜涓诲瓧娈垫ā绯婃煡璇�"),
+                () -> assertTrue(controllerContent.contains("new KeyValVo(item.getId(), item.getName())"), "query 鎺ュ彛搴旇繑鍥炰富閿拰涓诲瓧娈�")
+        );
+    }
+
+    @Test
+    void buildFrontendArtifactsKeepsApiRequestContractAligned() throws Exception {
+        RsfDesignGenerator generator = createGenerator(
+                "sys_api_case",
+                "鎺ュ彛娴嬭瘯琛�",
+                "apiCase",
+                "ApiCase",
+                "system/api-case",
+                "system/api-case",
+                buildColumns()
+        );
+
+        invokePrivate(generator, "buildFrontendArtifacts");
+
+        Path apiFile = tempDir.resolve("src/api/system/api-case.js");
+        String apiContent = Files.readString(apiFile, StandardCharsets.UTF_8);
+
+        assertAll(
+                () -> assertTrue(apiContent.contains("function normalizeIds(ids) {"), "api 鏂囦欢搴斿寘鍚� ids 褰掍竴鍖栧伐鍏�"),
+                () -> assertTrue(apiContent.contains("export function fetchApiCasePage(params = {}) {"), "搴旂敓鎴� page 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: '/apiCase/page'"), "page 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("export function fetchApiCaseList(params = {}) {"), "搴旂敓鎴� list 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: '/apiCase/list'"), "list 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("export function fetchGetApiCaseDetail(id) {"), "搴旂敓鎴� detail 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: `/apiCase/${id}`"), "detail 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("export function fetchGetApiCaseMany(ids) {"), "搴旂敓鎴� many 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: `/apiCase/many/${normalizeIds(ids)}`"), "many 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("export function fetchSaveApiCase(params = {}) {"), "搴旂敓鎴� save 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: '/apiCase/save'"), "save 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("export function fetchUpdateApiCase(params = {}) {"), "搴旂敓鎴� update 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: '/apiCase/update'"), "update 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("export function fetchDeleteApiCase(ids) {"), "搴旂敓鎴� delete 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: `/apiCase/remove/${normalizeIds(ids)}`"), "delete 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("export function fetchApiCaseQuery(condition = '') {"), "搴旂敓鎴� query 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("url: '/apiCase/query'"), "query 璇锋眰璺緞搴旀纭�"),
+                () -> assertTrue(apiContent.contains("condition: normalizeText(condition)"), "query 璇锋眰搴斿 condition 鍋� trim"),
+                () -> assertTrue(apiContent.contains("export async function fetchExportApiCaseReport(payload = {}, options = {}) {"), "搴旂敓鎴� export 璇锋眰鍑芥暟"),
+                () -> assertTrue(apiContent.contains("fetch(`${import.meta.env.VITE_API_URL}/apiCase/export`"), "export 搴旂洿鎺ヨ姹傚畬鏁村鍑哄湴鍧�"),
+                () -> assertTrue(apiContent.contains("method: 'POST'"), "export 璇锋眰搴斾娇鐢� POST"),
+                () -> assertTrue(apiContent.contains("body: JSON.stringify(payload)"), "export 璇锋眰搴斿彂閫� JSON body")
+        );
+    }
+
+    @Test
+    void writeBackendArtifactsKeepEntityServiceMapperXmlAndSqlAligned() throws Exception {
+        ReactGenerator generator = createReactGenerator(
+                "sys_backend_case",
+                "鍚庣娴嬭瘯琛�",
+                "backendCase",
+                "BackendCase",
+                buildBackendCaseColumns()
+        );
+
+        writeReactTemplate(generator, "Entity", tempDir.resolve("src/main/java/com/vincent/rsf/server/system/entity").toString() + "/", "BackendCase.java");
+        writeReactTemplate(generator, "Service", tempDir.resolve("src/main/java/com/vincent/rsf/server/system/service").toString() + "/", "BackendCaseService.java");
+        writeReactTemplate(generator, "ServiceImpl", tempDir.resolve("src/main/java/com/vincent/rsf/server/system/service/impl").toString() + "/", "BackendCaseServiceImpl.java");
+        writeReactTemplate(generator, "Mapper", tempDir.resolve("src/main/java/com/vincent/rsf/server/system/mapper").toString() + "/", "BackendCaseMapper.java");
+        writeReactTemplate(generator, "Xml", tempDir.resolve("src/main/resources/mapper/system").toString() + "/", "BackendCaseMapper.xml");
+        writeReactTemplate(generator, "Sql", tempDir.resolve("src/main/java").toString() + "/", "backendCase.sql");
+
+        Charset systemCharset = Charset.defaultCharset();
+        String entityContent = Files.readString(tempDir.resolve("src/main/java/com/vincent/rsf/server/system/entity/BackendCase.java"), systemCharset);
+        String serviceContent = Files.readString(tempDir.resolve("src/main/java/com/vincent/rsf/server/system/service/BackendCaseService.java"), systemCharset);
+        String serviceImplContent = Files.readString(tempDir.resolve("src/main/java/com/vincent/rsf/server/system/service/impl/BackendCaseServiceImpl.java"), systemCharset);
+        String mapperContent = Files.readString(tempDir.resolve("src/main/java/com/vincent/rsf/server/system/mapper/BackendCaseMapper.java"), systemCharset);
+        String xmlContent = Files.readString(tempDir.resolve("src/main/resources/mapper/system/BackendCaseMapper.xml"), systemCharset);
+        String sqlContent = Files.readString(tempDir.resolve("src/main/java/backendCase.sql"), systemCharset);
+
+        assertAll(
+                () -> assertTrue(entityContent.contains("@TableName(\"sys_backend_case\")"), "瀹炰綋搴旂粦瀹氱湡瀹炶〃鍚�"),
+                () -> assertTrue(entityContent.contains("private Long id;"), "瀹炰綋搴旂敓鎴愪富閿瓧娈�"),
+                () -> assertTrue(entityContent.contains("private String name;"), "瀹炰綋搴旂敓鎴愭櫘閫氭枃鏈瓧娈�"),
+                () -> assertTrue(entityContent.contains("private Long ownerId;"), "瀹炰綋搴旂敓鎴愬閿瓧娈�"),
+                () -> assertTrue(entityContent.contains("private Integer status;"), "瀹炰綋搴旂敓鎴愮姸鎬佸瓧娈�"),
+                () -> assertTrue(entityContent.contains("@TableLogic"), "瀹炰綋搴斾负 deleted 瀛楁娣诲姞閫昏緫鍒犻櫎娉ㄨВ"),
+                () -> assertTrue(entityContent.contains("@DateTimeFormat(pattern=\"yyyy-MM-dd HH:mm:ss\")"), "瀹炰綋搴斾负鏃ユ湡瀛楁娣诲姞鏃堕棿鏍煎紡娉ㄨВ"),
+                () -> assertTrue(entityContent.contains("public Boolean getStatusBool(){"), "瀹炰綋搴斾繚鐣� statusBool 渚挎嵎鏂规硶"),
+                () -> assertTrue(serviceContent.contains("public interface BackendCaseService extends IService<BackendCase>"), "Service 鎺ュ彛搴旂户鎵� IService"),
+                () -> assertTrue(serviceImplContent.contains("public class BackendCaseServiceImpl extends ServiceImpl<BackendCaseMapper, BackendCase> implements BackendCaseService"), "ServiceImpl 搴旂粦瀹� mapper 鍜� entity"),
+                () -> assertTrue(serviceImplContent.contains("@Service(\"backendCaseService\")"), "ServiceImpl bean 鍚嶅簲浣跨敤灏忛┘宄�"),
+                () -> assertTrue(mapperContent.contains("public interface BackendCaseMapper extends BaseMapper<BackendCase>"), "Mapper 搴旂户鎵� BaseMapper"),
+                () -> assertTrue(xmlContent.contains("<mapper namespace=\"com.vincent.rsf.server.system.mapper.BackendCaseMapper\">"), "Xml namespace 搴旀寚鍚戞纭� mapper"),
+                () -> assertTrue(sqlContent.contains("insert into `sys_menu`"), "Sql 妯℃澘搴旂敓鎴愯彍鍗� SQL"),
+                () -> assertTrue(sqlContent.contains("'system:backendCase:list'"), "Sql 妯℃澘搴旂敓鎴� list 鏉冮檺"),
+                () -> assertTrue(sqlContent.contains("'system:backendCase:save'"), "Sql 妯℃澘搴旂敓鎴� save 鏉冮檺"),
+                () -> assertTrue(sqlContent.contains("'system:backendCase:update'"), "Sql 妯℃澘搴旂敓鎴� update 鏉冮檺"),
+                () -> assertTrue(sqlContent.contains("'system:backendCase:remove'"), "Sql 妯℃澘搴旂敓鎴� remove 鏉冮檺"),
+                () -> assertTrue(sqlContent.contains("backendCase: 'BackendCase'"), "Sql 妯℃澘搴旂敓鎴� locale 鑿滃崟鍚�"),
+                () -> assertTrue(sqlContent.contains("import backendCase from './backendCase';"), "Sql 妯℃澘搴旂敓鎴愯祫婧愬鍏ョ墖娈�")
+        );
+    }
+
+    private List<Column> buildColumns() {
+        Column id = new Column(null, "id", "Long", "ID", true, false, true, 20, false, SqlOsType.MYSQL);
+        Column name = new Column(null, "name", "String", "鍚嶇О(*)", false, false, true, 255, false, SqlOsType.MYSQL);
+        Column level = new Column(null, "level", "Integer", "绛夌骇{1:楂�,0:浣巬", false, false, false, 11, false, SqlOsType.MYSQL);
+        Column roleId = new Column(null, "role_id", "Long", "瑙掕壊", false, false, false, 20, false, SqlOsType.MYSQL);
+        Column status = new Column(null, "status", "Integer", "鐘舵�亄1:姝e父,0:绂佺敤}", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column deleted = new Column(null, "deleted", "Integer", "鏄惁鍒犻櫎{1:鏄�,0:鍚", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column createTime = new Column(null, "create_time", "Date", "娣诲姞鏃堕棿", false, false, false, null, false, SqlOsType.MYSQL);
+        Column updateTime = new Column(null, "update_time", "Date", "淇敼鏃堕棿", false, false, false, null, false, SqlOsType.MYSQL);
+        Column memo = new Column(null, "memo", "String", "澶囨敞", false, false, false, 255, false, SqlOsType.MYSQL);
+
+        roleId.setForeignKeyMajor("Name");
+
+        return List.of(id, name, level, roleId, status, deleted, createTime, updateTime, memo);
+    }
+
+    private List<Column> buildBooleanColumns() {
+        Column id = new Column(null, "id", "Long", "ID", true, false, true, 20, false, SqlOsType.MYSQL);
+        Column name = new Column(null, "name", "String", "鍚嶇О(*)", false, false, true, 255, false, SqlOsType.MYSQL);
+        Column enabled = new Column(null, "enabled", "Boolean", "鏄惁鍚敤", false, false, false, 1, false, SqlOsType.MYSQL);
+        Column status = new Column(null, "status", "Integer", "鐘舵�亄1:姝e父,0:绂佺敤}", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column deleted = new Column(null, "deleted", "Integer", "鏄惁鍒犻櫎{1:鏄�,0:鍚", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column memo = new Column(null, "memo", "String", "澶囨敞", false, false, false, 255, false, SqlOsType.MYSQL);
+
+        return List.of(id, name, enabled, status, deleted, memo);
+    }
+
+    private List<Column> buildDefaultCaseColumns() {
+        Column id = new Column(null, "id", "Long", "ID", true, false, true, 20, false, SqlOsType.MYSQL);
+        Column name = new Column(null, "name", "String", "鍚嶇О(*)", false, false, true, 255, false, SqlOsType.MYSQL);
+        Column level = new Column(null, "level", "Integer", "绛夌骇", false, false, false, 11, false, SqlOsType.MYSQL);
+        Column enabled = new Column(null, "enabled", "Boolean", "鏄惁鍚敤", false, false, false, 1, false, SqlOsType.MYSQL);
+        Column status = new Column(null, "status", "Integer", "鐘舵�亄1:姝e父,0:绂佺敤}", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column deleted = new Column(null, "deleted", "Integer", "鏄惁鍒犻櫎{1:鏄�,0:鍚", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column memo = new Column(null, "memo", "String", "澶囨敞", false, false, false, 255, false, SqlOsType.MYSQL);
+
+        return List.of(id, name, level, enabled, status, deleted, memo);
+    }
+
+    private List<Column> buildSearchCaseColumns() {
+        Column id = new Column(null, "id", "Long", "ID", true, false, true, 20, false, SqlOsType.MYSQL);
+        Column keyword = new Column(null, "keyword", "String", "鍏抽敭瀛楀瓧娈�", false, false, false, 255, false, SqlOsType.MYSQL);
+        Column level = new Column(null, "level", "Integer", "绛夌骇", false, false, false, 11, false, SqlOsType.MYSQL);
+        Column enabled = new Column(null, "enabled", "Boolean", "鏄惁鍚敤", false, false, false, 1, false, SqlOsType.MYSQL);
+        Column status = new Column(null, "status", "Integer", "鐘舵�亄1:姝e父,0:绂佺敤}", false, false, false, 1, false, SqlOsType.MYSQL);
+        Column createTime = new Column(null, "create_time", "Date", "娣诲姞鏃堕棿", false, false, false, null, false, SqlOsType.MYSQL);
+        Column deleted = new Column(null, "deleted", "Integer", "鏄惁鍒犻櫎{1:鏄�,0:鍚", false, false, true, 1, false, SqlOsType.MYSQL);
+
+        return List.of(id, keyword, level, enabled, status, createTime, deleted);
+    }
+
+    private List<Column> buildControllerCaseColumns() {
+        Column id = new Column(null, "id", "Long", "ID", true, false, true, 20, false, SqlOsType.MYSQL);
+        Column name = new Column(null, "name", "String", "鍚嶇О(*)", false, false, true, 255, false, SqlOsType.MYSQL);
+        Column ownerId = new Column(null, "owner_id", "Long", "璐熻矗浜�", false, false, false, 20, false, SqlOsType.MYSQL);
+        Column status = new Column(null, "status", "Integer", "鐘舵�亄1:姝e父,0:绂佺敤}", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column createBy = new Column(null, "create_by", "Long", "鍒涘缓浜�", false, false, false, 20, false, SqlOsType.MYSQL);
+        Column createTime = new Column(null, "create_time", "Date", "鍒涘缓鏃堕棿", false, false, false, null, false, SqlOsType.MYSQL);
+        Column updateBy = new Column(null, "update_by", "Long", "鏇存柊浜�", false, false, false, 20, false, SqlOsType.MYSQL);
+        Column updateTime = new Column(null, "update_time", "Date", "鏇存柊鏃堕棿", false, false, false, null, false, SqlOsType.MYSQL);
+
+        ownerId.setForeignKeyMajor("Name");
+
+        return List.of(id, name, ownerId, status, createBy, createTime, updateBy, updateTime);
+    }
+
+    private List<Column> buildBackendCaseColumns() {
+        Column id = new Column(null, "id", "Long", "ID", true, false, true, 20, false, SqlOsType.MYSQL);
+        Column name = new Column(null, "name", "String", "鍚嶇О(*)", false, false, true, 255, false, SqlOsType.MYSQL);
+        Column ownerId = new Column(null, "owner_id", "Long", "璐熻矗浜�", false, false, false, 20, false, SqlOsType.MYSQL);
+        Column status = new Column(null, "status", "Integer", "鐘舵�亄1:姝e父,0:绂佺敤}", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column deleted = new Column(null, "deleted", "Integer", "鏄惁鍒犻櫎{1:鏄�,0:鍚", false, false, true, 1, false, SqlOsType.MYSQL);
+        Column createTime = new Column(null, "create_time", "Date", "鍒涘缓鏃堕棿", false, false, false, null, false, SqlOsType.MYSQL);
+        ownerId.setForeignKey("User");
+        ownerId.setForeignKeyMajor("Name");
+        return List.of(id, name, ownerId, status, deleted, createTime);
+    }
+
+    private RsfDesignGenerator createGenerator(
+            String table,
+            String tableDesc,
+            String simpleEntityName,
+            String fullEntityName,
+            String frontendViewPath,
+            String frontendApiModule,
+            List<Column> columns
+    ) throws Exception {
+        RsfDesignGenerator generator = new RsfDesignGenerator();
+        generator.table = table;
+        generator.tableDesc = tableDesc;
+        generator.packagePath = "com.vincent.rsf.server.system";
+        generator.frontendPrefixPath = tempDir.toString().replace("\\", "/");
+        generator.frontendViewPath = frontendViewPath;
+        generator.frontendApiModule = frontendApiModule;
+        generator.sqlOsType = SqlOsType.MYSQL;
+
+        setField(generator, "columns", columns);
+        setField(generator, "fullEntityName", fullEntityName);
+        setField(generator, "simpleEntityName", simpleEntityName);
+        setField(generator, "kebabEntityName", toKebab(simpleEntityName));
+        setField(generator, "constantPrefix", simpleEntityName.replaceAll("([a-z0-9])([A-Z])", "$1_$2").toUpperCase());
+        setField(generator, "primaryKeyColumn", "id");
+        setField(generator, "majorColumn", "name");
+        setField(generator, "itemName", "system");
+        setField(generator, "normalizedFrontendViewPath", frontendViewPath);
+        setField(generator, "normalizedFrontendApiModule", frontendApiModule);
+        return generator;
+    }
+
+    private String toKebab(String value) {
+        return value.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase();
+    }
+
+    private ReactGenerator createReactGenerator(
+            String table,
+            String tableDesc,
+            String simpleEntityName,
+            String fullEntityName,
+            List<Column> columns
+    ) throws Exception {
+        ReactGenerator generator = new ReactGenerator();
+        generator.table = table;
+        generator.tableDesc = tableDesc;
+        generator.packagePath = "com.vincent.rsf.server.system";
+        generator.backendPrefixPath = tempDir.toString().replace("\\", "/") + "/";
+        generator.frontendPrefixPath = tempDir.toString().replace("\\", "/");
+
+        setField(generator, "columns", columns);
+        setField(generator, "fullEntityName", fullEntityName);
+        setField(generator, "simpleEntityName", simpleEntityName);
+        setField(generator, "itemName", "system");
+        setField(generator, "systemPackagePath", "com.vincent.rsf.server.system");
+        setField(generator, "systemPackage", "com.vincent.rsf.server.system");
+
+        setField(generator, "entityContent", invokePrivate(generator, "createEntityMsg"));
+        setField(generator, "primaryKeyColumn", invokePrivate(generator, "createPrimaryMsg"));
+        setField(generator, "majorColumn", invokePrivate(generator, "createMajorMsg"));
+        setField(generator, "reactLocaleContent", invokePrivate(generator, "createReactLocaleContent"));
+        return generator;
+    }
+
+    private void writeReactTemplate(ReactGenerator generator, String templateName, String directory, String fileName) throws Exception {
+        String content = (String) invokePrivate(generator, "readFile", new Class<?>[]{String.class}, new Object[]{templateName});
+        invokePrivate(
+                generator,
+                "writeFile",
+                new Class<?>[]{String.class, String.class, String.class, String.class},
+                new Object[]{content, directory.replace("\\", "/"), fileName, templateName}
+        );
+    }
+
+    private void setField(Object target, String name, Object value) throws Exception {
+        Field field = target.getClass().getDeclaredField(name);
+        field.setAccessible(true);
+        field.set(target, value);
+    }
+
+    private Object invokePrivate(Object target, String name) throws Exception {
+        Method method = target.getClass().getDeclaredMethod(name);
+        method.setAccessible(true);
+        return method.invoke(target);
+    }
+
+    private Object invokePrivate(Object target, String name, Class<?>[] parameterTypes, Object[] args) throws Exception {
+        Method method = target.getClass().getDeclaredMethod(name, parameterTypes);
+        method.setAccessible(true);
+        return method.invoke(target, args);
+    }
+}

--
Gitblit v1.9.1